diff --git a/packages/effect-fc/src/types/Form.ts b/packages/effect-fc/src/types/Form.ts index 23b14a0..676af19 100644 --- a/packages/effect-fc/src/types/Form.ts +++ b/packages/effect-fc/src/types/Form.ts @@ -1,4 +1,4 @@ -import { Effectable, Option, Schema, Subscribable, SubscriptionRef, type ParseResult, type Pipeable } from "effect" +import { Effect, Option, Pipeable, Schema, Subscribable, SubscriptionRef, type ParseResult } from "effect" import type * as PropertyPath from "./PropertyPath.js" @@ -11,13 +11,31 @@ extends Pipeable.Pipeable { readonly valueRef: SubscriptionRef.SubscriptionRef readonly errorSubscribable: Subscribable.Subscribable> - useRef

>(path: P): SubscriptionRef.SubscriptionRef> - useIssues(path: PropertyPath.Paths): readonly ParseResult.ParseIssue[] + useRef

>(path: P): Effect.Effect>> + useIssues(path: PropertyPath.Paths): Effect.Effect } class FormImpl -extends Effectable.Class<> implements Form { +extends Pipeable.Class() implements Form { readonly [TypeId]: TypeId = TypeId + + constructor( + readonly schema: Schema.Schema, + readonly valueRef: SubscriptionRef.SubscriptionRef, + readonly errorSubscribable: Subscribable.Subscribable> + ) { + super() + } + + useRef

>( + path: P + ): Effect.Effect>, never, never> { + throw new Error("Method not implemented.") + } + + useIssues(path: PropertyPath.Paths): Effect.Effect { + throw new Error("Method not implemented.") + } } diff --git a/packages/effect-fc/src/types/Schema.ts b/packages/effect-fc/src/types/Schema.ts new file mode 100644 index 0000000..f073117 --- /dev/null +++ b/packages/effect-fc/src/types/Schema.ts @@ -0,0 +1,118 @@ +import { Array, Function, Option, Predicate, Schema } from "effect" + + +type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +export type Paths = readonly [] | ( + D extends never ? readonly [] : + S extends Seen ? readonly [] : + S extends Schema.Union ? UnionPaths : + S extends Schema.TupleType ? FormField.TupleFormField : + S extends Schema.Array$ ? FormField.ArrayFormField : + S extends Schema.Struct ? FormField.StructFormField : + S extends Schema.Schema.Any ? FormField.GenericFormField : + S extends Schema.PropertySignature< + infer TypeToken, + infer Type, + infer Key, + infer EncodedToken, + infer Encoded, + infer HasDefault, + infer R + > ? FormField.PropertySignatureFormField : + never +) + +export type UnionPaths, D extends number, Seen> = { + [K in keyof T as K extends number ? K : never]: + | readonly [K] + | readonly [K, ...Paths] +} extends infer O + ? O[keyof O] + : never + +export type ArrayPaths = { + [K in keyof T as K extends number ? K : never]: + | readonly [K] + | readonly [K, ...Paths] +} extends infer O + ? O[keyof O] + : never + +export type ObjectPaths = { + [K in keyof T as K extends string | number | symbol ? K : never]-?: + NonNullable extends infer V + ? readonly [K] | readonly [K, ...Paths] + : never +} extends infer O + ? O[keyof O] + : never + +export type ValueFromPath = P extends [infer Head, ...infer Tail] + ? Head extends keyof T + ? ValueFromPath + : T extends readonly any[] + ? Head extends number + ? ValueFromPath + : never + : never + : T + +export type AnyPath = readonly PropertyKey[] + + +export const unsafeGet: { + >(path: P): (self: T) => ValueFromPath + >(self: T, path: P): ValueFromPath +} = Function.dual(2, >(self: T, path: P): ValueFromPath => + path.reduce((acc: any, key: any) => acc?.[key], self) +) + +export const get: { + >(path: P): (self: T) => Option.Option> + >(self: T, path: P): Option.Option> +} = Function.dual(2, >(self: T, path: P): Option.Option> => + path.reduce( + (acc: Option.Option, key: any): Option.Option => Option.isSome(acc) + ? Predicate.hasProperty(acc.value, key) + ? Option.some(acc.value[key]) + : Option.none() + : acc, + + Option.some(self), + ) +) + +export const immutableSet: { + >(path: P, value: ValueFromPath): (self: T) => ValueFromPath + >(self: T, path: P, value: ValueFromPath): Option.Option +} = Function.dual(3, >(self: T, path: P, value: ValueFromPath): Option.Option => { + const key = Array.head(path as AnyPath) + if (Option.isNone(key)) + return Option.some(value as T) + if (!Predicate.hasProperty(self, key.value)) + return Option.none() + + const child = immutableSet(self[key.value], Option.getOrThrow(Array.tail(path as AnyPath)), value) + if (Option.isNone(child)) + return child + + if (Array.isArray(self)) + return typeof key.value === "number" + ? Option.some([ + ...self.slice(0, key.value), + child.value, + ...self.slice(key.value + 1), + ] as T) + : Option.none() + + if (typeof self === "object") + return Option.some( + Object.assign( + Object.create(Object.getPrototypeOf(self)), + { ...self, [key.value]: child.value }, + ) + ) + + return Option.none() +})