Created
May 7, 2023 07:48
-
-
Save scarf005/d513014549de4a7a3785a639d04121f8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from "https://deno.land/x/zod@v3.20.5/mod.ts" | |
import { HasType } from "./type_helper.ts" | |
export type AnyZodEffect = z.ZodEffects<z.ZodTypeAny, unknown, unknown> | |
export type ObjectSchema = z.ZodObject<z.ZodRawShape> | |
/** Recursively checks if schema has a transform effect for either the whole object or a property. */ | |
export type HasZodEffect<T> = T extends ObjectSchema | |
? HasType<T["shape"], AnyZodEffect> extends true ? true | |
: { | |
[K in keyof T["shape"]]: HasZodEffect<T["shape"][K]> | |
}[keyof T["shape"]] extends false ? false | |
: true | |
: never | |
/** | |
* Asserts that schema has a transform effect for either the whole object or a property. | |
* | |
* @example | |
* ```ts | |
* const propertyMigration = z.object({ a: z.number().transform(id) }) | |
* const objectMigration = z.object({ b: z.string() }).transform(id) | |
* const bothMigration = z.object({ a: z.number().transform(id) }).transform(id) | |
* const nestedMigration = z.object({ b: propertyMigration }) | |
* const notMigration = z.object({ a: z.number() }) | |
* | |
* test("MigrationSchema", (t) => [ | |
* t.equal<MigrationSchema<typeof propertyMigration>, typeof propertyMigration>(), | |
* t.equal<MigrationSchema<typeof objectMigration>, typeof objectMigration>(), | |
* t.equal<MigrationSchema<typeof bothMigration>, typeof bothMigration>(), | |
* t.equal<MigrationSchema<typeof nestedMigration>, typeof nestedMigration>(), | |
* t.equal<MigrationSchema<typeof notMigration>, never>(), | |
* ]) | |
* ``` | |
*/ | |
export type MigrationSchema<T> = T extends ZodTransformObject ? T | |
: HasZodEffect<T> extends true ? T | |
: never | |
type ZodTransformObject = z.ZodEffects<ObjectSchema> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from "https://deno.land/x/zod@v3.20.5/mod.ts" | |
import { test } from "npm:ts-spec" | |
import { MigrationSchema } from "./schema_type.ts" | |
import { id } from "../utils/id.ts" | |
const propertyMigration = z.object({ a: z.number().transform(id) }) | |
const objectMigration = z.object({ b: z.string() }).transform(id) | |
const bothMigration = z.object({ a: z.number().transform(id) }).transform(id) | |
const nestedMigration = z.object({ b: propertyMigration }) | |
const notMigration = z.object({ a: z.number() }) | |
test("MigrationSchema", (t) => [ | |
t.equal<MigrationSchema<typeof propertyMigration>, typeof propertyMigration>(), | |
t.equal<MigrationSchema<typeof objectMigration>, typeof objectMigration>(), | |
t.equal<MigrationSchema<typeof bothMigration>, typeof bothMigration>(), | |
t.equal<MigrationSchema<typeof nestedMigration>, typeof nestedMigration>(), | |
t.equal<MigrationSchema<typeof notMigration>, never>(), | |
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Get values of an object. Object needs to be immutable in order to narrow down the type. | |
* | |
* @example | |
* ```ts | |
* test("ValueOf", (t) => [ | |
* t.equal<ValueOf<{ a: 1; b: "b" }>, 1 | "b">(), | |
* t.equal<ValueOf<{ a: true; b: false }>, boolean>(), | |
* // @ts-expect-error: type should be narrowed down to true | |
* t.equal<ValueOf<{ a: true; b: true }>, boolean>(), | |
* t.equal<ValueOf<{ a: true; b: true }>, true>(), | |
* t.equal<ValueOf<{ a: false; b: false }>, false>(), | |
* ]) | |
* ``` | |
*/ | |
export type ValueOf<T> = T[keyof T] | |
/** | |
* Maps all values of an object to true if they match the given type, false otherwise. | |
* | |
* @example | |
* ```ts | |
* const obj = { a: 1, b: "b" } | |
* type Result = TypeExistsMap<typeof before, number> // { a: true, b: false } | |
*/ | |
type TypeExistsMap<Object, Type> = { | |
[K in keyof Object]: Object[K] extends Type ? true : false | |
} | |
/** | |
* Checks that an object has a value of given type. | |
* | |
* @returns true if object has a value of given type, false otherwise. | |
* | |
* @example | |
* ```ts | |
* test("HasType", (t) => [ | |
* t.equal<HasType<{ a: 1; b: "b" }, number>, true>(), | |
* t.equal<HasType<{ a: 1; b: "b" }, 1>, true>(), | |
* t.equal<HasType<{ b: "b" }, number>, false>(), | |
* ]) | |
* ``` | |
*/ | |
export type HasType<Object, Type> = ValueOf<TypeExistsMap<Object, Type>> extends false ? false | |
: true | |
export type NestedHasType<Object, U> = Object extends U ? true | |
: Object extends Record<string, unknown> ? { | |
[K in keyof Object]: NestedHasType<Object[K], U> | |
}[keyof Object] extends false ? false | |
: true | |
: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { test } from "npm:ts-spec" | |
import { HasType, NestedHasType, ValueOf } from "./type_helper.ts" | |
test("ValueOf", (t) => [ | |
t.equal<ValueOf<{ a: 1; b: "b" }>, 1 | "b">(), | |
t.equal<ValueOf<{ a: true; b: false }>, boolean>(), | |
// @ts-expect-error: type should be narrowed down to true | |
t.equal<ValueOf<{ a: true; b: true }>, boolean>(), | |
t.equal<ValueOf<{ a: true; b: true }>, true>(), | |
t.equal<ValueOf<{ a: false; b: false }>, false>(), | |
]) | |
test("HasType", (t) => [ | |
t.equal<HasType<{ a: 1; b: "b" }, number>, true>(), | |
t.equal<HasType<{ a: 1; b: "b" }, 1>, true>(), | |
t.equal<HasType<{ b: "b" }, number>, false>(), | |
]) | |
test("HasNestedType", (t) => [ | |
t.equal<NestedHasType<1, number>, true>(), | |
t.equal<NestedHasType<{ a: 1 }, number>, true>(), | |
t.equal<NestedHasType<{ a: 1; b: "b" }, number>, true>(), | |
t.equal<NestedHasType<{ a: 1; b: "b" }, 1>, true>(), | |
t.equal<NestedHasType<{ b: "b" }, number>, false>(), | |
t.equal<NestedHasType<{ a: { b: 1 } }, number>, true>(), | |
t.equal<NestedHasType<{ a: { b: 1 } }, 1>, true>(), | |
t.equal<NestedHasType<{ a: { b: 1 } }, string>, false>(), | |
]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment