/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as St from "@effect-ts/core/Structural" import type { ComputeFlat } from "@effect-ts/core/Utils" import * as Lens from "@effect-ts/monocle/Lens" import omit from "lodash/omit.js" import pick from "lodash/pick.js" import type { EncSchemaForModel, EParserFor, FromPropertyRecord } from "./_api.js" import { fromProps } from "./_api.js" import * as MO from "./_schema.js" import { schemaField } from "./_schema.js" import type { AnyProperty, EncodedOf, ParsedShapeOf, PropertyRecord } from "./custom.js" import { unsafe } from "./custom/_api/condemn.js" import { include } from "./utils.js" export const nModelBrand = Symbol() export type StringRecord = Record export type AnyRecord = Record export type AnyRecordSchema = MO.Schema // Not inheriting from Schemed because we don't want `copy` // passing SelfM down to Model2 so we only compute it once. export interface Model extends Model2< ParsedShape, Self, EncSchemaForModel>, // makes it pretty, but also helps compatibility with WebStorm it seems... ComputeFlat> > {} export interface Model3 extends Model2< ParsedShape, Self, EncSchemaForModel>, ParsedShape2 > {} export interface ModelEnc< ParsedShape, Self extends MO.SchemaAny, MEnc, // makes it pretty, but also helps compatibility with WebStorm it seems... ParsedShape2 = ComputeFlat> > extends MM< Self, EncSchemaForModel, ParsedShape, MO.ConstructorInputOf, MEnc, GetApiProps, ParsedShape2 > {} export interface ModelEnc3 extends MM< Self, EncSchemaForModel, ParsedShape, MO.ConstructorInputOf, MEnc, GetApiProps, ParsedShape2 > {} export interface Model2< M, Self extends MO.SchemaAny, SelfM extends MO.SchemaAny, ParsedShape2 > extends MM< Self, SelfM, M, MO.ConstructorInputOf, MO.EncodedOf, GetApiProps, ParsedShape2 > {} type GetApiProps = T extends MO.SchemaProperties ? Props : never export interface MNModel< Self extends MO.SchemaAny, ParsedShape = MO.ParsedShapeOf, ConstructorInput = MO.ConstructorInputOf, Encoded = MO.EncodedOf, Props = GetApiProps > extends MM< Self, MO.Schema, ParsedShape, ConstructorInput, Encoded, Props, // makes it pretty, but also helps compatibility with WebStorm it seems... ComputeFlat> > {} export interface MM< Self extends MO.SchemaAny, SelfM extends MO.SchemaAny, ParsedShape, ConstructorInput, Encoded, Props, ParsedShape2 > extends MO.Schema { new(_: ConstructorInput): ParsedShape2 [MO.schemaField]: Self readonly parsed: ParsedShapeOf readonly encoded: EncodedOf readonly Model: SelfM // added readonly lens: Lens.Lens // added readonly lenses: RecordSchemaToLenses readonly Parser: MO.ParserFor readonly EParser: EParserFor readonly Constructor: MO.ConstructorFor readonly Encoder: MO.EncoderFor readonly Guard: MO.GuardFor readonly Arbitrary: MO.ArbitraryFor } export function Model(__name?: string) { return (props: Props) => ModelSpecial(__name)(MO.props(props)) } export function ModelEnc(__name?: string) { return (props: Props) => ModelSpecialEnc(__name)(MO.props(props)) } export function Model3(__name?: string) { return (props: Props) => ModelSpecial3(__name)(MO.props(props)) } export function Model4(__name?: string) { return (props: Props) => ModelSpecial3(__name)(MO.props(props)) } export function ModelEnc3(__name?: string) { return (props: Props) => ModelSpecialEnc3(__name)(MO.props(props)) } export function ModelEnc4(__name?: string) { return (props: Props) => ModelSpecialEnc3(__name)(MO.props(props)) } export function MNModel( __name?: string ) { return (props: ProvidedProps) => { const self = MO.props(props) return makeSpecial(__name, self) as & MNModel< typeof self, ParsedShape, ConstructorInput, Encoded, Props > & PropsExtensions } // MNModelSpecial(__name)(MO.props(props)) } // export function MNModel3( // __name?: string // ) { // return (props: ProvidedProps) => { // const self = MO.props(props) // return makeSpecial(__name, self) as MNModel< // typeof self, // ParsedShape, // ConstructorInput, // Encoded, // Props, // > & // PropsExtensions // } // //MNModelSpecial(__name)(MO.props(props)) // } // export function MNModel4( // __name?: string // ) { // return (props: ProvidedProps) => { // const self = MO.props(props) // return makeSpecial(__name, self) as MNModel< // typeof self, // ParsedShape, // ConstructorInput, // Encoded, // Props, // ProvidedProps, // {} // > & // PropsExtensions // } // //MNModelSpecial(__name)(MO.props(props)) // } export function fromModel(__name?: string) { return (props: Props) => ModelSpecial(__name)(fromProps(props)) } export type RecordSchemaToLenses = { [K in keyof ParsedShapeOf]: Lens.Lens[K]> } export type PropsToLenses = { [K in keyof Props]: Lens.Lens> } export function lensFromProps() { return (props: Props): PropsToLenses => { const id = Lens.id() return Object.keys(props).reduce((prev, cur) => { prev[cur] = id.prop(cur as any) return prev }, {} as any) } } export function setSchema>( schemed: any, self: Self ) { schemed[MO.SchemaContinuationSymbol] = schemed[schemaField] = schemed.Model = self // Object.defineProperty(schemed, MO.SchemaContinuationSymbol, { // value: self, // }) Object.defineProperty(schemed, "include", { value: include(self.Api.props), configurable: true }) Object.defineProperty(schemed, "lenses", { value: lensFromProps()(self.Api.props), configurable: true }) Object.defineProperty(schemed, "Api", { value: self.Api, configurable: true }) Object.defineProperty(schemed, ">>>", { value: self[">>>"], configurable: true }) Object.defineProperty(schemed, "Parser", { value: MO.Parser.for(self), configurable: true }) Object.defineProperty(schemed, "EParser", { value: MO.Parser.for(self), configurable: true }) Object.defineProperty(schemed, "Constructor", { value: MO.Constructor.for(self), configurable: true }) Object.defineProperty(schemed, "Encoder", { value: MO.Encoder.for(self), configurable: true }) Object.defineProperty(schemed, "Guard", { value: MO.Guard.for(self), configurable: true }) Object.defineProperty(schemed, "Arbitrary", { value: MO.Arbitrary.for(self), configurable: true }) Object.defineProperty(schemed, "annotate", { value: (identifier: MO.Annotation, meta: Meta) => new MO.SchemaAnnotated(self, identifier, meta), configurable: true }) } /** * Automatically assign the name of the Class to the MO. */ export function useClassNameForSchema(cls: any) { setSchema(cls, pipe(cls[schemaField], MO.named(cls.name)) as any) return cls } export type GetProps = Self extends { Api: { props: infer Props } } ? Props extends PropertyRecord ? Props : never : never export interface PropsExtensions { include: >( fnc: (props: Props) => NewProps ) => NewProps pick:

(...keys: readonly P[]) => Pick omit:

(...keys: readonly P[]) => Omit } // We don't want Copy interface from the official implementation export function ModelSpecial(__name?: string) { return ( self: Self ): Model & PropsExtensions> => { return makeSpecial(__name, self) } } export function ModelSpecialEnc(__name?: string) { return ( self: Self ): ModelEnc & PropsExtensions> => { return makeSpecial(__name, self) } } export function ModelSpecial3(__name?: string) { return ( self: Self ): Model3 & PropsExtensions> => { return makeSpecial(__name, self) } } export function ModelSpecialEnc3(__name?: string) { return ( self: Self ): & ModelEnc3 & PropsExtensions> => { return makeSpecial(__name, self) } } // export function MNModelSpecial(__name?: string) { // return ( // self: Self // ): MNModel & PropsExtensions> => { // return makeSpecial(__name, self) // } // } function makeSpecial(__name: any, self: Self): any { const schema = __name ? self >= MO.named(__name) : self // TODO ?? "Model(Anonymous)", but atm auto deriving openapiRef from this. const of_ = MO.Constructor.for(schema) >= unsafe const fromFields = (fields: any, target: any) => { for (const k of Object.keys(fields)) { target[k] = fields[k] } } const parser = MO.Parser.for(schema) return class { static [nModelBrand] = nModelBrand static [schemaField] = schema static [MO.SchemaContinuationSymbol] = schema static Model = schema static Api = schema.Api static [">>>"] = schema[">>>"] static Parser = parser static EParser = parser static Encoder = MO.Encoder.for(schema) static Constructor = MO.Constructor.for(schema) static Guard = MO.Guard.for(schema) static Arbitrary = MO.Arbitrary.for(schema) static lens = Lens.id() static lenses = lensFromProps()(schema.Api.props) static include = include(schema.Api.props) static pick = (...props: any[]) => pick(schema.Api.props, props) static omit = (...props: any[]) => omit(schema.Api.props, props) static annotate = (identifier: MO.Annotation, meta: Meta) => new MO.SchemaAnnotated(self, identifier, meta) constructor(inp: MO.ConstructorInputOf) { // ideally inp would be optional, and default to {}, but only if the constructor input has only optional inputs.. fromFields(of_(inp), this) } get [St.hashSym](): number { const ka = Object.keys(this).sort() if (ka.length === 0) { return 0 } let hash = St.combineHash(St.hashString(ka[0]!), St.hash(this[ka[0]!])) let i = 1 while (hash && i < ka.length) { hash = St.combineHash( hash, St.combineHash(St.hashString(ka[i]!), St.hash(this[ka[i]!])) ) i++ } return hash } [St.equalsSym](that: unknown): boolean { if (!(that instanceof this.constructor)) { return false } const ka = Object.keys(this) const kb = Object.keys(that) if (ka.length !== kb.length) { return false } let eq = true let i = 0 const ka_ = ka.sort() const kb_ = kb.sort() while (eq && i < ka.length) { eq = ka_[i] === kb_[i] && St.equals(this[ka_[i]!], this[kb_[i]!]) i++ } return eq } // static copy(this, that) { // return fromFields(that, this) // } } }