import * as Schema from "effect/Schema" import * as AST from "effect/SchemaAST" export const TypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Field") export type TypeId = typeof TypeId export interface FieldDef { readonly _tag: "field" readonly key: K readonly schema: S } export interface ArrayFieldDef { readonly _tag: "array" readonly key: K readonly itemSchema: S } export type AnyFieldDef = FieldDef | ArrayFieldDef export type FieldsRecord = Record export const isArrayFieldDef = (def: AnyFieldDef): def is ArrayFieldDef => def._tag === "array" export const isFieldDef = (def: AnyFieldDef): def is FieldDef => def._tag === "field" export const makeField = ( key: K, schema: S ): FieldDef => ({ _tag: "field", key, schema }) export const makeArrayField = ( key: K, itemSchema: S ): ArrayFieldDef => ({ _tag: "array", key, itemSchema }) export type EncodedFromFields = { readonly [K in keyof T]: T[K] extends FieldDef ? Schema.Schema.Encoded : T[K] extends ArrayFieldDef ? ReadonlyArray> : never } export type DecodedFromFields = { readonly [K in keyof T]: T[K] extends FieldDef ? Schema.Schema.Type : T[K] extends ArrayFieldDef ? ReadonlyArray> : never } export const getDefaultFromSchema = (schema: Schema.Schema.Any): unknown => { const ast = schema.ast switch (ast._tag) { case "StringKeyword": case "TemplateLiteral": return "" case "NumberKeyword": return 0 case "BooleanKeyword": return false case "Literal": return ast.literal case "Enums": { const first = ast.enums[0] return first ? first[1] : undefined } case "TypeLiteral": { const result: Record = {} for (const prop of ast.propertySignatures) { result[prop.name as string] = getDefaultFromSchema(Schema.make(prop.type)) } return result } case "Union": { const first = ast.types[0] return first ? getDefaultFromSchema(Schema.make(first)) : undefined } case "NeverKeyword": return undefined case "Transformation": return getDefaultFromSchema(Schema.make(ast.from)) case "Refinement": return getDefaultFromSchema(Schema.make(ast.from)) case "Suspend": return getDefaultFromSchema(Schema.make(ast.f())) default: return "" } } export const getDefaultEncodedValues = (fields: FieldsRecord): Record => { const result: Record = {} for (const [key, def] of Object.entries(fields)) { if (isArrayFieldDef(def)) { result[key] = [] } else { result[key] = "" } } return result } export const createTouchedRecord = (fields: FieldsRecord, value: boolean): Record => { const result: Record = {} for (const key of Object.keys(fields)) { result[key] = value } return result } export const extractStructFieldDefs = ( schema: Schema.Schema.Any ): ReadonlyArray> | undefined => { const unwrapTypeLiteral = (ast: AST.AST): AST.TypeLiteral | undefined => { if (AST.isTypeLiteral(ast)) return ast if (AST.isRefinement(ast)) return unwrapTypeLiteral(ast.from) if (AST.isTransformation(ast)) return unwrapTypeLiteral(ast.from) if (AST.isSuspend(ast)) return unwrapTypeLiteral(ast.f()) return undefined } const typeLiteral = unwrapTypeLiteral(schema.ast) if (!typeLiteral) return undefined return typeLiteral.propertySignatures.map((prop) => makeField(prop.name as string, { ast: prop.type } as Schema.Schema.Any) ) }