UNPKG

zod

Version:

Typescript-first schema declaration and validation library with static type inference

336 lines (288 loc) • 8.84 kB
import * as z from '.'; import { ZodType } from './types/base'; // ########################## // ###### SCHEMA TYPES ###### // ########################## export type PropSchema<T> = { t: ZodType<T, any>; label: string; }; export interface HasProps<T> { props: { [k in keyof T]: PropSchema<T[k]>; }; } export type PropValidator<T extends HasProps<any>> = z.ZodObject< { [k in keyof T['props']]: T['props'][k]['t'] } >; // ######################### // ###### Node Schema ###### // ######################### export interface NodePropSchema<T> extends PropSchema<T> {} interface NodeSchema<T> { props: { [k in keyof T]: NodePropSchema<T[k]>; }; } type NodesSchema<T extends { [k: string]: any }> = { [k in keyof T]: NodeSchema<T[k]>; }; // type asdfd = NodeSchema<{}> // ######################### // ###### EDGE SCHEMA ###### // ######################### export enum EdgeMode { OneToOne = 'one2one', OneToMany = 'one2many', ManyToMany = 'many2many', ManyToOne = 'many2one', } interface EdgePropSchema<T> extends PropSchema<T> {} interface EdgeSchema<Nodes, Props> extends HasProps<Props> { // type: EdgeType; start: keyof Nodes; toKey: string; end: keyof Nodes; fromKey: string; mode: EdgeMode; props: { [prop in keyof Props]: EdgePropSchema<Props[prop]>; }; } type EdgesSchema< Nodes extends NodesSchema<any>, Props extends { [k: string]: any } > = { [k in keyof Props]: EdgeSchema<Nodes, Props[k]>; }; // ################### export enum RelMode { ToMany = '2many', ToOne = '2one', } // export interface OneWayRelationTemplate<Nodes, Edges> { // start: keyof Nodes; // key: string; // relation: RelationTemplate<Nodes, Edges>; // } // export interface RelItem<Nodes, Edges> { // edgeData: any; // edgeType: keyof Edges; // nodeLabels: (keyof Nodes)[]; // nodeId: string; // nodeData: any; // } interface RelationSearchParams<Nodes, Edges> { start?: keyof Nodes; end?: keyof Nodes; edgeType?: keyof Edges; key?: string; outgoing?: boolean; } // ########################## // ###### SCHEMA CLASS ###### // ########################## interface RelationSchema< Nodes extends NodesSchema<any>, Edges extends { [k: string]: any } > { type: keyof Edges; start: keyof Nodes; end: keyof Nodes; key: string; mode: RelMode; reverse: boolean; inverse?: RelationSchema<Nodes, Edges>; } export interface AnyRelation { type: string; start: string; end: string; key: string; mode: RelMode; reverse: boolean; inverse?: AnyRelation; } export type AnySchema = Schema<any, any>; export type AnyNode = Node<any>; export type AnyEdge = Edge<any>; export class Node<T extends NodeSchema<any>> implements NodeSchema<any> { name: string; schema: Schema<any, any>; props: unknown extends T['props'] ? {} : T['props']; validator: z.ZodObject<{ [k in keyof T['props']]: T['props'][k]['t'] }>; constructor(name: string, def: T) { this.name = name; this.props = (def.props || {}) as any; this.validator = Schema.validator(def) as any; } } export class Edge<T extends EdgeSchema<any, any>> implements EdgeSchema<any, any> { name: string; schema: Schema<any, any>; props: undefined extends T['props'] ? {} : T['props']; start: T['start']; toKey: T['toKey']; end: T['end']; fromKey: T['fromKey']; mode: T['mode']; validator: z.ZodObject<{ [k in keyof T['props']]: T['props'][k]['t'] }>; constructor(name: string, def: T) { this.name = name; this.props = (def.props || {}) as any; this.start = def.start; this.toKey = def.toKey; this.end = def.end; this.fromKey = def.fromKey; this.mode = def.mode; this.validator = Schema.validator(def) as any; } } export default class Schema< Nodes extends NodesSchema<{ [k: string]: any }>, Edges extends EdgesSchema<Nodes, { [k: string]: any }> > { static Nodes = <T extends NodesSchema<any>>(schema: T): T => schema; static Edges = <N extends NodesSchema<any>, E extends EdgesSchema<N, any>>( _nodes: N, edges: E ): E => edges; nodes: { [k in keyof Nodes]: Node<Nodes[k]> }; nt: { [k in keyof Nodes]: k }; edges: { [k in keyof Edges]: Edge<Edges[k]> }; et: { [k in keyof Nodes]: k }; relations: RelationSchema<Nodes, Edges>[]; constructor(nodes: Nodes, edges: Edges) { const instantiatedNodes: any = {}; for (const nk in nodes) { const newNode = new Node(nk, nodes[nk]); newNode.schema = this; instantiatedNodes[nk] = newNode; } this.nodes = instantiatedNodes; const instantiatedEdges: any = {}; for (const ek in edges) { const newEdge = new Edge(ek, edges[ek]); newEdge.schema = this; instantiatedEdges[ek] = newEdge; } this.edges = instantiatedEdges; this.nt = Object.assign({}, ...Object.keys(nodes).map(k => ({ [k]: k }))); this.et = Object.assign({}, ...Object.keys(edges).map(k => ({ [k]: k }))); this.relations = this.edgesToRelations(edges); } edgesToRelations = (edges: Edges) => { const relations: RelationSchema<Nodes, Edges>[] = []; for (const type of Object.keys(edges)) { // console.log(`edge: ${type}`); const edgeSchema: EdgeSchema<Nodes, any> = edges[type]; const { mode, start, end, toKey, fromKey } = edgeSchema; // console.log(`(:${start})-[${type}]->(:${end})`); const toMany = mode === EdgeMode.OneToMany || mode === EdgeMode.ManyToMany; const fromOne = mode === EdgeMode.OneToMany || mode === EdgeMode.OneToOne; const toEdge: RelationSchema<Nodes, Edges> = { key: toKey, type: type, start, end, mode: toMany ? RelMode.ToMany : RelMode.ToOne, reverse: false, }; const fromEdge: RelationSchema<Nodes, Edges> = { key: fromKey, type: type, start: end, end: start, mode: fromOne ? RelMode.ToOne : RelMode.ToMany, reverse: true, }; toEdge.inverse = fromEdge; fromEdge.inverse = toEdge; const isReflexive = start === end; if (isReflexive) { relations.push(toEdge); } else { relations.push(toEdge); relations.push(fromEdge); } } return relations; }; findEdge = (node: keyof Nodes, key: string): EdgeSchema<Nodes, any> => { const found = Object.values(this.edges).find(edge => { return ( (edge.start === node && edge.toKey === key) || (edge.end === node && edge.fromKey === key) ); }); if (!found) throw new Error(`Edge ${node}.${key} not found.`); return found; }; findRelations = ( params: RelationSearchParams<Nodes, Edges> ): RelationSchema<Nodes, Edges>[] => { const start = params.start; const end = params.end; const edgeType = params.edgeType; const outgoing = params.outgoing; const key = params.key; // this.relations[0].sta // const allRels = RelationsByNode[start] || []; const matches = this.relations.filter(rel => { if (start && rel.start !== start) return false; if (end && rel.end !== end) return false; if (edgeType && rel.type !== edgeType) return false; if (key && rel.key !== key) return false; if (outgoing !== undefined && outgoing === rel.reverse) return false; return true; }); return matches || []; }; findRelation = ( params: RelationSearchParams<Nodes, Edges> ): RelationSchema<Nodes, Edges> | null => { const matches = this.findRelations(params); if (matches.length > 1) { console.log('Error'); // console.log(JSON.stringify(params, null, 2)); throw new Error(`More than one relation matches these criteria.`); } if (matches.length === 0) { return null; } return matches[0]; }; getRelatedType = ( params: RelationSearchParams<Nodes, Edges> ): keyof Nodes => { const rel = this.findRelation(params); if (!rel) { throw new Error( `Relation does not exist: ${JSON.stringify(params, null, 2)}` ); } // if (rel.end.length > 1) { // throw new Error( // 'Cannot call getRelatedType for multitype relation' // ); // } // return rel.end[0]; return rel.end; }; static validator = <T extends HasProps<any>>( schema: T ): unknown extends T ? z.ZodObject<{}> : z.ZodObject<{ [k in keyof T['props']]: T['props'][k]['t'] }> => { const fullValidator: any = {}; const props = schema.props; //{ [x: string]: z.ZodSchema<any> } = schema.props as any; for (const key in props) { fullValidator[key] = props[key].t; } return z.object(fullValidator) as any; }; } export type NodeTypes<T extends Schema<any, any>> = keyof T['nt']; export type EdgeTypes<T extends Schema<any, any>> = keyof T['et'];