Skip to content

Instantly share code, notes, and snippets.

@scarf005
Created May 7, 2023 07:48
Show Gist options
  • Save scarf005/d513014549de4a7a3785a639d04121f8 to your computer and use it in GitHub Desktop.
Save scarf005/d513014549de4a7a3785a639d04121f8 to your computer and use it in GitHub Desktop.
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>
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>(),
])
/**
* 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
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