/* eslint-disable @typescript-eslint/no-explicit-any */ import { constant, pipe } from "@effect-ts-app/core/Function" import * as NonEmptySet from "@effect-ts-app/core/NonEmptySet" import { typedKeysOf } from "@effect-ts-app/core/utils" import type { ComputeFlat } from "@effect-ts/core/Utils" import type { None, Some } from "@fp-ts/data/Option" import { v4 } from "uuid" import type { FromProperty } from "./_api.js" import { set, setIdentifier } from "./_api.js" import { nonEmptySet } from "./_api/nonEmptySet.js" import * as MO from "./_schema.js" import type { Property, UUID } from "./_schema.js" import { propDef, propOpt, propReq } from "./_schema.js" import * as Eq from "@effect-ts/core/Equal" export function partialConstructor(model: { new(inp: ConstructorInput): ParsedShape }): >( // TODO: Prevent over provide partConstructor: PartialConstructorInput ) => ( restConstructor: ComputeFlat> ) => ParsedShape { return partConstructor => restConstructor => partialConstructor_(model, partConstructor)(restConstructor) } export function partialConstructor_< ConstructorInput, ParsedShape, PartialConstructorInput extends Partial >( model: { new(inp: ConstructorInput): ParsedShape }, // TODO: Prevent over provide partConstructor: PartialConstructorInput ): ( restConstructor: ComputeFlat> ) => ParsedShape { return restConstructor => new model({ ...partConstructor, ...restConstructor } as any) } export function partialConstructorF( constr: (inp: ConstructorInput) => ParsedShape ): >( // TODO: Prevent over provide partConstructor: PartialConstructorInput ) => ( restConstructor: ComputeFlat> ) => ParsedShape { return partConstructor => restConstructor => partialConstructorF_(constr, partConstructor)(restConstructor) } export function partialConstructorF_< ConstructorInput, ParsedShape, PartialConstructorInput extends Partial >( constr: (inp: ConstructorInput) => ParsedShape, // TODO: Prevent over provide partConstructor: PartialConstructorInput ): ( restConstructor: ComputeFlat> ) => ParsedShape { return restConstructor => constr({ ...partConstructor, ...restConstructor } as any) } // TODO: morph the schema instead. export function derivePartialConstructor(model: { [MO.schemaField]: MO.Schema new(inp: ConstructorInput): ParsedShape }): >( // TODO: Prevent over provide partConstructor: PartialConstructorInput ) => ( restConstructor: ComputeFlat> ) => ParsedShape { return partConstructor => restConstructor => derivePartialConstructor_(model, partConstructor)(restConstructor) } export function derivePartialConstructor_< ConstructorInput, ParsedShape, PartialConstructorInput extends Partial >( model: { [MO.schemaField]: MO.Schema new(inp: ConstructorInput): ParsedShape }, // TODO: Prevent over provide partConstructor: PartialConstructorInput ): ( restConstructor: ComputeFlat> ) => ParsedShape { return restConstructor => new model({ ...partConstructor, ...restConstructor } as any) } export type GetPartialConstructor any> = Parameters< ReturnType >[0] export function makeUuid() { return v4() as MO.UUID } type LazyPartial = { [P in keyof T]?: LazyArg } export function withDefaultConstructorFields< ParserInput, ParsedShape, ConstructorInput, Encoded, Api >(self: MO.Schema) { // TODO: but allow NO OTHERS! return >( kvs: Changes ): MO.Schema< ParserInput, ParsedShape, & Omit & // @ts-expect-error we know keyof Changes matches Partial>, Encoded, Api > => { const constructSelf = MO.Constructor.for(self) return pipe( self, MO.constructor((u: any) => constructSelf({ ...u, ...Object.keys(kvs).reduce((prev, cur) => { if (typeof u[cur] === "undefined") { prev[cur] = kvs[cur]() } return prev }, {} as any) }) ) ) } } export function makeCurrentDate() { return new Date() } export function defaultConstructor< Self extends MO.SchemaUPI, As extends Opt, Def extends Opt<["parser" | "constructor" | "both", () => MO.ParsedShapeOf]> >(p: MO.Property) { return (makeDefault: () => MO.ParsedShapeOf) => propDef(p, makeDefault, "constructor") } type SupportedDefaults = | ROSet | ReadonlyArray | Some | None | Date | boolean | UUID export function findAnnotation( schema: MO.SchemaAny, id: MO.Annotation ): A | undefined { if (MO.isAnnotated(schema, id)) { return schema.meta } if (MO.hasContinuation(schema)) { return findAnnotation(schema[MO.SchemaContinuationSymbol], id) } return undefined } export type SupportedDefaultsSchema = MO.Schema export type DefaultProperty = FromProperty export type DefaultPropertyRecord = Record export type WithDefault< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api, As extends Opt > = MO.Property< MO.Schema, "required", As, Some<["constructor", () => ParsedShape]> > export type WithInputDefault< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api, As extends Opt > = MO.Property< MO.Schema, "required", As, Some<["both", () => ParsedShape]> > export function withDefault< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api, As extends Opt, Def extends Opt< [ "parser" | "constructor" | "both", () => MO.ParsedShapeOf< MO.Schema > ] > >( p: MO.Property< MO.Schema, "required", As, Def > ): WithDefault { if (findAnnotation(p._schema, MO.dateIdentifier)) { return propDef(p, makeCurrentDate as any, "constructor") } if (findAnnotation(p._schema, MO.optionFromNullIdentifier)) { return propDef(p, () => Opt.none as any, "constructor") } if (findAnnotation(p._schema, MO.nullableIdentifier)) { return propDef(p, () => null as any, "constructor") } if (findAnnotation(p._schema, MO.arrayIdentifier)) { return propDef(p, () => [] as any, "constructor") } if (findAnnotation(p._schema, setIdentifier)) { return propDef(p, () => new Set() as any, "constructor") } if (findAnnotation(p._schema, MO.boolIdentifier)) { return propDef(p, () => false as any, "constructor") } if (findAnnotation(p._schema, MO.UUIDIdentifier)) { return propDef(p, makeUuid as any, "constructor") } throw new Error("Not supported") } export function withInputDefault< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api, As extends Opt, Def extends Opt< [ "parser" | "constructor" | "both", () => MO.ParsedShapeOf< MO.Schema > ] > >( p: MO.Property< MO.Schema, "required", As, Def > ): WithInputDefault { if (findAnnotation(p._schema, MO.dateIdentifier)) { return propDef(p, makeCurrentDate as any, "both") } if (findAnnotation(p._schema, MO.optionFromNullIdentifier)) { return propDef(p, () => Opt.none as any, "both") } if (findAnnotation(p._schema, MO.nullableIdentifier)) { return propDef(p, () => null as any, "both") } if (findAnnotation(p._schema, MO.arrayIdentifier)) { return propDef(p, () => [] as any, "both") } if (findAnnotation(p._schema, setIdentifier)) { return propDef(p, () => new Set() as any, "both") } if (findAnnotation(p._schema, MO.boolIdentifier)) { return propDef(p, () => false as any, "both") } if (findAnnotation(p._schema, MO.UUIDIdentifier)) { return propDef(p, makeUuid as any, "both") } throw new Error("Not supported") } type Optionality = "parser" | "constructor" | "both" function defProp( schema: Self, makeDefault: () => MO.ParsedShapeOf, optionality: "parser" ): MO.Property MO.ParsedShapeOf]>> function defProp( schema: Self, makeDefault: () => MO.ParsedShapeOf, optionality: "both" ): MO.Property MO.ParsedShapeOf]>> function defProp( schema: Self, makeDefault: () => MO.ParsedShapeOf ): MO.Property< Self, "required", None, Some<["constructor", () => MO.ParsedShapeOf]> > function defProp( schema: Self, makeDefault: () => MO.ParsedShapeOf, optionality: Optionality = "constructor" ) { return propDef(MO.prop(schema), makeDefault, optionality) } export function optProp( schema: Self ): Property { return propOpt(MO.prop(schema)) } export function defaultProp( schema: MO.SchemaDefaultSchema, makeDefault: () => ParsedShape ): MO.Property< MO.SchemaDefaultSchema, "required", None, Some<["constructor", () => ParsedShape]> > export function defaultProp< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api >( schema: MO.SchemaDefaultSchema ): FromProperty< MO.SchemaDefaultSchema, "required", None, Some<["constructor", () => ParsedShape]> > export function defaultProp( schema: MO.SchemaDefaultSchema ): null extends ParsedShape ? FromProperty< MO.SchemaDefaultSchema, "required", None, Some<["constructor", () => ParsedShape]> > : ["Not a supported type, see SupportedTypes", never] export function defaultProp( schema: MO.Schema, makeDefault: () => ParsedShape ): MO.Property< MO.Schema, "required", None, Some<["constructor", () => ParsedShape]> > export function defaultProp< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api >( schema: MO.Schema ): FromProperty< MO.Schema, "required", None, Some<["constructor", () => ParsedShape]> > export function defaultProp( schema: MO.Schema ): null extends ParsedShape ? FromProperty< MO.Schema, "required", None, Some<["constructor", () => ParsedShape]> > : ["Not a supported type, see SupportedTypes", never] export function defaultProp( schema: MO.Schema, makeDefault?: () => any ) { return makeDefault ? defProp(schema, makeDefault) : MO.prop(schema) >= withDefault } export function defaultInputProp( schema: MO.SchemaDefaultSchema, makeDefault: () => ParsedShape ): MO.Property< MO.SchemaDefaultSchema, "required", None, Some<["both", () => ParsedShape]> > export function defaultInputProp< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api >( schema: MO.SchemaDefaultSchema ): FromProperty< MO.SchemaDefaultSchema, "required", None, Some<["both", () => ParsedShape]> > export function defaultInputProp( schema: MO.SchemaDefaultSchema ): null extends ParsedShape ? FromProperty< MO.SchemaDefaultSchema, "required", None, Some<["both", () => ParsedShape]> > : ["Not a supported type, see SupportedTypes", never] export function defaultInputProp( schema: MO.Schema, makeDefault: () => ParsedShape ): MO.Property< MO.Schema, "required", None, Some<["both", () => ParsedShape]> > export function defaultInputProp< ParsedShape extends SupportedDefaults, ConstructorInput, Encoded, Api >( schema: MO.Schema ): FromProperty< MO.Schema, "required", None, Some<["both", () => ParsedShape]> > export function defaultInputProp( schema: MO.Schema ): null extends ParsedShape ? FromProperty< MO.Schema, "required", None, Some<["both", () => ParsedShape]> > : ["Not a supported type, see SupportedTypes", never] export function defaultInputProp( schema: MO.Schema, makeDefault?: () => any ) { return makeDefault ? defProp(schema, makeDefault, "both") : MO.prop(schema) >= withInputDefault } export function makeOptional>( t: NER // TODO: enforce non empty ): { [K in keyof NER]: MO.Property< NER[K]["_schema"], "optional", NER[K]["_as"], NER[K]["_def"] > } { return typedKeysOf(t).reduce((prev, cur) => { prev[cur] = propOpt(t[cur]) return prev }, {} as any) } export function makeRequired>( t: NER // TODO: enforce non empty ): { [K in keyof NER]: MO.Property< NER[K]["_schema"], "required", NER[K]["_as"], NER[K]["_def"] > } { return typedKeysOf(t).reduce((prev, cur) => { prev[cur] = propReq(t[cur]) return prev }, {} as any) } export function createUnorder(): Ord { return { compare: (_a: T, _b: T) => 0 } } export function makeSet( // eslint-disable-next-line @typescript-eslint/ban-types type: MO.Schema, ord: Ord, eq?: Equal ) { const s = set(type, ord, eq) return Object.assign(s, ROSet.make(ord, eq)) } // export function makeUnorderedContramappedStringSet< // ParsedShape, // ConstructorInput, // Encoded, // Api, // MA extends string // >( // // eslint-disable-next-line @typescript-eslint/ban-types // type: MO.Schema, // contramap: (a: ParsedShape) => MA // ) { // return makeUnorderedSet(type, Eq.contramap(contramap)(Eq.string)) // } export function makeUnorderedStringSet( // eslint-disable-next-line @typescript-eslint/ban-types type: MO.Schema< unknown, // ParserInput, A, any, // ConstructorInput, any, // Encoded any // Api > ) { return makeUnorderedSet(type, Eq.string) } export function makeUnorderedSet( // eslint-disable-next-line @typescript-eslint/ban-types type: MO.Schema, eq: Eq.Equal ) { return makeSet(type, createUnorder(), eq) } // export function makeContramappedSet( // // eslint-disable-next-line @typescript-eslint/ban-types // type: MO.Schema, // contramap: (a: ParsedShape) => MA, // ord: Ord, // eq: Eq.Equal // ) { // return makeSet(type, LegacyOrd.contramap_(ord, contramap), Eq.contramap(contramap)(eq)) // } export function makeNonEmptySet( // eslint-disable-next-line @typescript-eslint/ban-types type: MO.Schema, ord: Ord, eq?: Equal ) { const s = nonEmptySet(type, ord, eq) return Object.assign(s, NonEmptySet.make(ord, eq)) } // export function makeUnorderedContramappedStringNonEmptySet< // ParsedShape, // ConstructorInput, // Encoded, // Api, // MA extends string // >( // // eslint-disable-next-line @typescript-eslint/ban-types // type: MO.Schema, // contramap: (a: ParsedShape) => MA // ) { // return makeUnorderedNonEmptySet(type, Eq.contramap(contramap)(Eq.string)) // } export function makeUnorderedStringNonEmptySet( // eslint-disable-next-line @typescript-eslint/ban-types type: MO.Schema< unknown, // ParserInput, A, any, // ConstructorInput, any, // Encoded any // Api > ) { return makeUnorderedNonEmptySet(type, Eq.string) } export function makeUnorderedNonEmptySet( // eslint-disable-next-line @typescript-eslint/ban-types type: MO.Schema, eq: Eq.Equal ) { return makeNonEmptySet(type, createUnorder(), eq) } // export function makeContramappedNonEmptySet< // ParsedShape, // ConstructorInput, // Encoded, // Api, // MA // >( // // eslint-disable-next-line @typescript-eslint/ban-types // type: MO.Schema, // contramap: (a: ParsedShape) => MA, // ord: Ord, // eq: Eq.Equal // ) { // return makeNonEmptySet( // type, // LegacyOrd.contramap_(ord, contramap), // Eq.contramap(contramap)(eq) // ) // } export const constArray = constant(ReadonlyArray.empty) export type ParserInputFromSchemaProperties = T extends { Api: { props: infer Props } } ? Props extends MO.PropertyRecord ? MO.ParserInputFromProperties : never : never /** * We know that the Parser will work from `unknown`, but we also want to expose the knowledge that we can parse from a ParserInput of type X * as such we can use fromProps, fromProp, fromArray etc, but still embed this Schema into one that parses from unknown. */ export type AsUPI = MO.Schema< unknown, ParsedShape, ConstructorInput, Encoded, Api > /** * @see AsUPI */ export const asUpi = ( s: MO.Schema ) => s as AsUPI