// deno-lint-ignore-file no-explicit-any import { isRecordEmpty, type Keyof, Result, TaggedError } from "@fabric/core"; import { fieldParsers, type FieldParsingError } from "./field-parsers.ts"; import { Field, type FieldDefinition, type FieldToType } from "./fields.ts"; /** * A model is a schema definition for some type of structured data. */ export class Model< TName extends string = string, TFields extends ModelFields = ModelFields, > { static from( name: TName, fields: TFields, ) { return new Model(name, fields); } static aggregateFrom( name: TName, fields: TFields, ): Model { return new Model(name, { ...fields, ...DefaultAggregateFields }); } static entityFrom( name: TName, fields: TFields, ): Model { return new Model(name, { ...fields, ...DefaultEntityFields }); } private constructor(readonly name: TName, readonly fields: TFields) {} public parse( value: unknown, ): Result, SchemaParsingError> { const parsingErrors = {} as Record; const parsedValue = {} as ModelToType; for (const key in this.fields) { const field = this.fields[key]!; const fieldResult = fieldParsers[field._tag]( field as any, (value as any)[key], ); if (fieldResult.isOk()) { parsedValue[key as keyof ModelToType] = fieldResult.value; } else { parsingErrors[key] = fieldResult.unwrapErrorOrThrow(); } } if (!isRecordEmpty(parsingErrors)) { return Result.failWith( new SchemaParsingError(parsingErrors, parsedValue), ); } else { return Result.succeedWith(parsedValue); } } } export type EntityModel = Model< string, typeof DefaultEntityFields & ModelFields >; export type AggregateModel = Model< string, typeof DefaultAggregateFields & ModelFields >; export type ModelToType = & ModelToOptionalFields & ModelToRequiredFields; export type ModelFieldNames = Keyof< TModel["fields"] >; export type ModelAddressableFields = { [K in Keyof]: TModel["fields"][K] extends { isUnique: true } ? K : never; }[Keyof]; export class SchemaParsingError extends TaggedError<"SchemaParsingFailed"> { constructor( public readonly errors: Record, public readonly value?: Partial>, ) { super( "SchemaParsingFailed", ); } } type ModelFields = Record; const DefaultEntityFields = { id: Field.uuid({ isPrimaryKey: true }), } as const; const DefaultAggregateFields = { ...DefaultEntityFields, streamId: Field.uuid({ isIndexed: true }), streamVersion: Field.integer({ isUnsigned: true, hasArbitraryPrecision: true, }), deletedAt: Field.posixDate({ isOptional: true }), } as const; type ModelToOptionalFields = { [K in OptionalFields]?: FieldToType< TModel["fields"][K] >; }; type ModelToRequiredFields = { [K in RequiredFields]: FieldToType< TModel["fields"][K] >; }; type OptionalFields = { [K in Keyof]: TModel["fields"][K] extends { isOptional: true; } ? K : never; }[Keyof]; type RequiredFields = { [K in Keyof]: TModel["fields"][K] extends { isOptional: true; } ? never : K; }[Keyof];