From edb4b7ccd8e0f0aefe497240df4750266bad692c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 26 Aug 2025 04:13:22 +0200 Subject: [PATCH] Form work --- packages/effect-fc/src/types/Form.ts | 37 ++++++++++++++------ packages/effect-fc/src/types/PropertyPath.ts | 10 +++--- packages/effect-fc/src/types/Schema.ts | 22 ------------ 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/packages/effect-fc/src/types/Form.ts b/packages/effect-fc/src/types/Form.ts index 676af19..e3af1d8 100644 --- a/packages/effect-fc/src/types/Form.ts +++ b/packages/effect-fc/src/types/Form.ts @@ -1,8 +1,11 @@ -import { Effect, Option, Pipeable, Schema, Subscribable, SubscriptionRef, type ParseResult } from "effect" -import type * as PropertyPath from "./PropertyPath.js" +import { Array, Effect, Option, ParseResult, Pipeable, Schema, Stream, Subscribable, SubscriptionRef } from "effect" +import * as React from "react" +import { SubscriptionSubRef } from "./index.js" +import * as PropertyPath from "./PropertyPath.js" +import * as InternalSubscribable from "./Subscribable.js" -export const TypeId: unique symbol = Symbol.for("effect-fc/types/Form") +export const TypeId: unique symbol = Symbol.for("effect-fc/Form") export type TypeId = typeof TypeId export interface Form @@ -11,8 +14,8 @@ extends Pipeable.Pipeable { readonly valueRef: SubscriptionRef.SubscriptionRef readonly errorSubscribable: Subscribable.Subscribable> - useRef

>(path: P): Effect.Effect>> - useIssues(path: PropertyPath.Paths): Effect.Effect + useRef

>(path: P): SubscriptionRef.SubscriptionRef> + useIssuesSubscribable(path: PropertyPath.Paths): Subscribable.Subscribable } class FormImpl @@ -27,14 +30,26 @@ extends Pipeable.Class() implements Form { super() } - useRef

>( - path: P - ): Effect.Effect>, never, never> { - throw new Error("Method not implemented.") + useRef

>(path: P) { + return React.useMemo(() => SubscriptionSubRef.makeFromPath(this.valueRef, path), [this.valueRef, ...path]) } - useIssues(path: PropertyPath.Paths): Effect.Effect { - throw new Error("Method not implemented.") + useIssuesSubscribable(path: PropertyPath.Paths) { + return React.useMemo(() => { + const filter = Option.match({ + onSome: (v: ParseResult.ParseError) => Effect.andThen( + ParseResult.ArrayFormatter.formatError(v), + Array.filter(issue => PropertyPath.equivalence(issue.path, path)), + ), + onNone: () => Effect.succeed([]), + }) + + const errorSubscribable = this.errorSubscribable + return InternalSubscribable.make({ + get: Effect.andThen(errorSubscribable.get, filter), + get changes() { return Stream.flatMap(errorSubscribable.changes, filter) }, + }) + }, [this.errorSubscribable, ...path]) } } diff --git a/packages/effect-fc/src/types/PropertyPath.ts b/packages/effect-fc/src/types/PropertyPath.ts index 0f26cf8..08c00ac 100644 --- a/packages/effect-fc/src/types/PropertyPath.ts +++ b/packages/effect-fc/src/types/PropertyPath.ts @@ -1,6 +1,8 @@ -import { Array, Function, Option, Predicate } from "effect" +import { Array, Equivalence, Function, Option, Predicate } from "effect" +export type PropertyPath = readonly PropertyKey[] + type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] export type Paths = readonly [] | ( @@ -36,8 +38,8 @@ export type ValueFromPath = P extends [infer Head, : never : T -export type AnyPath = readonly PropertyKey[] +export const equivalence: Equivalence.Equivalence = Equivalence.array(Equivalence.strict()) export const unsafeGet: { >(path: P): (self: T) => ValueFromPath @@ -65,13 +67,13 @@ 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) + const key = Array.head(path as PropertyPath) 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) + const child = immutableSet(self[key.value], Option.getOrThrow(Array.tail(path as PropertyPath)), value) if (Option.isNone(child)) return child diff --git a/packages/effect-fc/src/types/Schema.ts b/packages/effect-fc/src/types/Schema.ts index d2ee2bb..65ad399 100644 --- a/packages/effect-fc/src/types/Schema.ts +++ b/packages/effect-fc/src/types/Schema.ts @@ -2,28 +2,6 @@ import { Array, Function, Option, Predicate, Schema } from "effect" import type { Simplify } from "effect/Types" -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 SchemaFromPath = S extends Schema.Schema.Any ? P extends [infer Head, ...infer Tail] ? Head extends keyof S["Type"]