Skip to content

Instantly share code, notes, and snippets.

@Talor-A
Last active June 15, 2021 14:41
Show Gist options
  • Save Talor-A/df50913f373f0d55c7a1a7fb22cf03c3 to your computer and use it in GitHub Desktop.
Save Talor-A/df50913f373f0d55c7a1a7fb22cf03c3 to your computer and use it in GitHub Desktop.
/**
* get all keys from T of type `string` (exclude `number` and `symbol`)
*/
export type ObjectKeys<T> = Extract<keyof T, string>
export type ObjectValues<T> = T[ObjectKeys<T>]
/**
* Object.keys(), but assert that an array of T's keys is returned, not `string[]`
*/
export const objectKeys = <T>(obj: T) => Object.keys(obj) as ObjectKeys<T>[]
/**
* assert that T has length >=2.
*/
export type Tuplify<T extends any[]> = [T[number], T[number], ...T]
import * as z from "zod"
import { JSONValue, MaybePromise, WriteTransaction } from "replicache/out/replicache"
import { objectKeys, ObjectKeys, ObjectValues, Tuplify } from "./helper.ts"
export interface ZodMutationSchema {
[key: string]: z.ZodObject<any>
}
/**
* for an object mapping [key: mutator name] -> validation schema,
* get the type of the functions that should be provided to replicache `mutators` key
*/
export type MutatorFns<Schema extends ZodMutationSchema> = {
[K in ObjectKeys<Schema>]: (
tx: WriteTransaction,
args: z.infer<Schema[K]>
) => MaybePromise<JSONValue | void>
}
/**
* For each mutator function, returns a function that will first check that
* the provided args pass validation and then call the mutator.
*
* @param schema the schema of [key: name] -> zod validator
* @param mutators functions taking tx and args and applying a transformation
* @returns an object of mutators that check their associated validator before applying a transformation
*/
export const applyValidators = <S extends ZodMutationSchema>(schema: S, mutators: MutatorFns<S>) =>
Object.fromEntries(
objectKeys(mutators).map((name) => {
const fn = mutators[name] as MutatorFns<S>[keyof MutatorFns<S>]
const validator = schema[name] as S[keyof S]
const validationFn: typeof fn = (tx, args) => {
const result = validator.parse(args)
fn(tx, result)
}
return [name, validationFn] as const
})
) as MutatorFns<S>
type SerializedMutationValidator<K extends string, T extends z.ZodObject<any>> = z.ZodObject<{
name: z.ZodLiteral<K>
id: z.ZodNumber
args: T
}>
export type SerializedMutations<Schema extends ZodMutationSchema> = {
[K in ObjectKeys<Schema>]: SerializedMutationValidator<K, Schema[K]>
}
export const toSerializedMutations = <Schema extends ZodMutationSchema>(schema: Schema) => {
return objectKeys(schema)
.map(
(name) =>
[
name,
z.object({
name: z.literal(name),
id: z.number(),
args: schema[name]!,
}),
] as const
)
.reduce(
(acc, [name, z]) => ({ ...acc, [name]: z }),
{} as Record<ObjectKeys<Schema>, z.ZodObject<any>>
) as SerializedMutations<Schema>
}
/**
*
* @param schema our mutation schema.
* @returns
*/
export const createPushRequestValidator = <Schema extends ZodMutationSchema>(schema: Schema) => {
const serialized = toSerializedMutations(schema)
const mutationsArr = Object.values(serialized) as ObjectValues<typeof serialized>[]
const pushRequest = z.object({
clientID: z.string(),
// z.union expects at least 2 entries, so a plain array type won't suffice.
mutations: z.array(z.union(mutationsArr as Tuplify<ObjectValues<typeof serialized>[]>)),
})
return pushRequest
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment