import {z, ZodObject} from "zod"; import {ZodRawShape} from "zod/lib/types"; import {Identifiable} from "../identifiable"; export const UnsafeEntitySymbol: symbol = Symbol("MyType"); export const EntitySymbol: symbol = Symbol("SafeEntity"); export type EntityCore = Identifiable & { createdAt: Date, updatedAt: Date, } export const EntityCoreShape = { id: z.string(), createdAt: z.coerce.date(), updatedAt: z.coerce.date(), }; export type EntityCoreShapeType = typeof EntityCoreShape type EntityFn = { copy: (fields: Partial

) => Zentity, toPlain: () => P & EntityCore } type NonBrandedEntity = EntityCore & P & EntityFn & E export type UnsafeZentity = NonBrandedEntity & { _brand: typeof UnsafeEntitySymbol, validate: () => Zentity } export type Zentity = UnsafeZentity & { _safe: typeof EntitySymbol } export function isZentity(data: any): data is Zentity { return data?.['_safe'] === EntitySymbol } export type EntityClass = { name: string, create: (params: ZodShapeType & EntityCore) => Zentity, R>, new: (params: ZodShapeType & { id: string }) => Zentity, R>, unsafe: (params: ZodShapeType & EntityCore) => UnsafeZentity, R>, schema: ZodObject, shape: S & EntityCoreShapeType, extend: (e: EntityCore & ZodShapeType) => R, } export type inferPlainTypeFromClass = E extends EntityClass ? ZodShapeType & EntityCore : never export type inferZentityTypeFromClass = E extends EntityClass ? Zentity, R> : never export type inferZentityType = E extends EntityClass ? ZodShapeType & R : never type inferUnsafeEntity = UnsafeZentity, any> export type inferEntity = E extends EntityClass ? Zentity, R> : never export type inferPlainTypeFromZentity = E extends Zentity ? P & EntityCore : never export type ZodShapeType = z.infer> export type Extender = (e: EntityCore & ZodShapeType) => R export function entity(options: { name: string, shape: S, extend?: Extender, }): EntityClass { const shape = { ...EntityCoreShape, ...options.shape, } const schema = z.object(shape) const extend = options.extend ?? ((_: EntityCore & ZodShapeType) => ({} as R)) type P = ZodShapeType const create = (params: P & EntityCore): Zentity => { const data = schema.parse(params) as any return { _brand: UnsafeEntitySymbol, _safe: EntitySymbol, ...data, copy: copyFn(data), ...extend(data), validate: toSafeFn(data), toPlain: () => data } } const toSafeFn = (params: P & EntityCore) => () => { return create(params) } const copyFn = (params: P & EntityCore) => (fields: Partial

) => { return create({ ...params, ...fields, }) } return { name: options.name, create, new: (params: P & { id: string }): Zentity => { const createdAt = new Date() const updatedAt = new Date() return create({ ...params, createdAt, updatedAt, }) }, unsafe: (params: P & EntityCore): UnsafeZentity => { return { _brand: UnsafeEntitySymbol, ...params, copy: copyFn(params), ...extend(params), validate: toSafeFn(params), toPlain: () => { return params } } }, schema, shape, extend, } }