import * as AST from "@effect/schema/AST" import * as Schema from "@effect/schema/Schema" import * as ParseResult from "@effect/schema/ParseResult" import { dual } from "effect/Function" import * as Data from "effect/Data" /** * @since 1.0.0 * @category type ids */ export const TypeId: unique symbol = Symbol.for("@effect/schema/VariantSchema") /** * @since 1.0.0 * @category type ids */ export type TypeId = typeof TypeId /** * @since 1.0.0 * @category models */ export interface Struct { readonly [TypeId]: A } /** * @since 1.0.0 * @category models */ export declare namespace Struct { /** * @since 1.0.0 * @category models */ export type Fields = { readonly [key: string]: | Schema.Schema.All | Schema.PropertySignature.All | Field | Struct } /** * @since 1.0.0 * @category models */ export type FieldsWithKeys = { readonly [key: string]: | Schema.Schema.All | Schema.PropertySignature.All | Field> | Struct> } } /** * @since 1.0.0 * @category type ids */ export const FieldTypeId: unique symbol = Symbol.for( "@effect/schema/VariantSchema/Field", ) /** * @since 1.0.0 * @category type ids */ export type FieldTypeId = typeof FieldTypeId /** * @since 1.0.0 * @category models */ export interface Field { readonly [FieldTypeId]: FieldTypeId readonly schemas: A } /** * @since 1.0.0 * @category models */ export declare namespace Field { /** * @since 1.0.0 * @category models */ export type Config = Partial<{ readonly [key: string]: Schema.Schema.All | Schema.PropertySignature.All }> /** * @since 1.0.0 * @category models */ export type ConfigWithKeys = { readonly [P in K]?: Schema.Schema.All | Schema.PropertySignature.All } /** * @since 1.0.0 * @category models */ export type Fields = { readonly [key: string]: | Schema.Schema.All | Schema.PropertySignature.All | Field | Struct } } /** * @since 1.0.0 * @category constructors */ export const Struct = (fields: A): Struct => ({ [TypeId]: fields, }) /** * @since 1.0.0 * @category constructors */ export const Field = (schemas: A): Field => ({ [FieldTypeId]: FieldTypeId, schemas, }) /** * @since 1.0.0 * @category extractors */ export type ExtractFields = { readonly [K in keyof Fields as [Fields[K]] extends [Field] ? V extends keyof Config ? K : never : K]: [Fields[K]] extends [Struct] ? Extract : [Fields[K]] extends [Field] ? [Config[V]] extends [Schema.Schema.All | Schema.PropertySignature.All] ? Config[V] : never : [Fields[K]] extends [Schema.Schema.All | Schema.PropertySignature.All] ? Fields[K] : never } /** * @since 1.0.0 * @category extractors */ export type Extract> = [A] extends [ Struct, ] ? Schema.Struct>> : never /** * @since 1.0.0 * @category extractors */ export const extract: { ( variant: V, ): >(self: A) => Extract >(self: A, variant: V): Extract } = dual( 2, >( self: A, variant: V, ): Extract => { const fields: Record = {} for (const key of Object.keys(self[TypeId])) { const value = self[TypeId][key] if (Schema.TypeId in value) { fields[key] = value } else if (FieldTypeId in value) { if (variant in value.schemas) { fields[key] = value.schemas[variant] } } else { fields[key] = extract(value, variant) } } return Schema.Struct(fields) as any }, ) type RequiredKeys = { [K in keyof T]-?: {} extends Pick ? never : K }[keyof T] /** * @category models * @since 1.0.0 */ export interface Class< Self, Fields extends Struct.Fields, SchemaFields extends Schema.Struct.Fields, A, I, R, C, > extends Schema.Schema, R>, Struct { new ( props: RequiredKeys extends never ? void | Schema.Simplify : Schema.Simplify, options?: { readonly disableValidation?: boolean }, ): A readonly ast: AST.Transformation make, X>( this: { new (...args: Args): X }, ...args: Args ): X annotations( annotations: Schema.Annotations.Schema, ): Schema.SchemaClass, R> readonly identifier: string readonly fields: SchemaFields } type ClassFromFields< Self, Fields extends Struct.Fields, SchemaFields extends Schema.Struct.Fields, > = Class< Self, Fields, SchemaFields, Schema.Struct.Type, Schema.Struct.Encoded, Schema.Struct.Context, Schema.Struct.Constructor > type MissingSelfGeneric = `Missing \`Self\` generic - use \`class Self extends Class()(${Params}{ ... })\`` const schemaVariance = { /* c8 ignore next */ _A: (_: any) => _, /* c8 ignore next */ _I: (_: any) => _, /* c8 ignore next */ _R: (_: never) => _, } /** * @since 1.0.0 * @category constructors */ export const factory = < const Variants extends ReadonlyArray, const Default extends Variants[number], >(options: { readonly variants: Variants readonly defaultVariant: Default }): { readonly Struct: >( fields: A, ) => Struct readonly Field: >( config: A & { readonly [K in Exclude]: never }, ) => Field readonly Class: ( identifier: string, ) => ( fields: Fields, annotations?: Schema.Annotations.Schema, ) => [Self] extends [never] ? MissingSelfGeneric : ClassFromFields< Self, Fields, Schema.Simplify> > & { readonly [V in Variants[number]]: Extract> } } => { function Class(identifier: string) { return function ( fields: Struct.Fields, annotations?: Schema.Annotations.Schema, ) { const variantStruct = Struct(fields) const schema = extract(variantStruct, options.defaultVariant) const validate = ParseResult.validateSync(schema) const klass = Schema.Class(identifier)(schema.fields, annotations) class Base extends Data.Class { constructor( props: any, options?: { readonly disableValidation?: boolean }, ) { if (options?.disableValidation !== true) { props = validate(props) } super(props) } // implement Struct static [TypeId] = fields // implement Schema static [Schema.TypeId] = schemaVariance static ast = klass.ast static make = klass.make static fields = klass.fields static identifier = klass.identifier } for (const variant of options.variants) { Object.defineProperty(Base, variant, { value: extract(variantStruct, variant), }) } return Base } } return { Struct, Field, Class, } as any }