Form work
All checks were successful
Lint / lint (push) Successful in 39s

This commit is contained in:
Julien Valverdé
2025-08-26 04:13:22 +02:00
parent 30f3ef2353
commit edb4b7ccd8
3 changed files with 32 additions and 37 deletions

View File

@@ -1,8 +1,11 @@
import { Effect, Option, Pipeable, Schema, Subscribable, SubscriptionRef, type ParseResult } from "effect" import { Array, Effect, Option, ParseResult, Pipeable, Schema, Stream, Subscribable, SubscriptionRef } from "effect"
import type * as PropertyPath from "./PropertyPath.js" 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 type TypeId = typeof TypeId
export interface Form<in out A, in out I = A, out R = never> export interface Form<in out A, in out I = A, out R = never>
@@ -11,8 +14,8 @@ extends Pipeable.Pipeable {
readonly valueRef: SubscriptionRef.SubscriptionRef<A> readonly valueRef: SubscriptionRef.SubscriptionRef<A>
readonly errorSubscribable: Subscribable.Subscribable<Option.Option<ParseResult.ParseError>> readonly errorSubscribable: Subscribable.Subscribable<Option.Option<ParseResult.ParseError>>
useRef<P extends PropertyPath.Paths<A>>(path: P): Effect.Effect<SubscriptionRef.SubscriptionRef<PropertyPath.ValueFromPath<A, P>>> useRef<P extends PropertyPath.Paths<A>>(path: P): SubscriptionRef.SubscriptionRef<PropertyPath.ValueFromPath<A, P>>
useIssues(path: PropertyPath.Paths<A>): Effect.Effect<readonly ParseResult.ParseIssue[]> useIssuesSubscribable(path: PropertyPath.Paths<A>): Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>
} }
class FormImpl<in out A, in out I, out R> class FormImpl<in out A, in out I, out R>
@@ -27,14 +30,26 @@ extends Pipeable.Class() implements Form<A, I, R> {
super() super()
} }
useRef<P extends PropertyPath.Paths<A>>( useRef<P extends PropertyPath.Paths<A>>(path: P) {
path: P return React.useMemo(() => SubscriptionSubRef.makeFromPath(this.valueRef, path), [this.valueRef, ...path])
): Effect.Effect<SubscriptionRef.SubscriptionRef<PropertyPath.ValueFromPath<A, P>>, never, never> {
throw new Error("Method not implemented.")
} }
useIssues(path: PropertyPath.Paths<A>): Effect.Effect<readonly ParseResult.ParseIssue[]> { useIssuesSubscribable(path: PropertyPath.Paths<A>) {
throw new Error("Method not implemented.") 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])
} }
} }

View File

@@ -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] type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
export type Paths<T, D extends number = 5, Seen = never> = readonly [] | ( export type Paths<T, D extends number = 5, Seen = never> = readonly [] | (
@@ -36,8 +38,8 @@ export type ValueFromPath<T, P extends readonly any[]> = P extends [infer Head,
: never : never
: T : T
export type AnyPath = readonly PropertyKey[]
export const equivalence: Equivalence.Equivalence<PropertyPath> = Equivalence.array(Equivalence.strict())
export const unsafeGet: { export const unsafeGet: {
<T, const P extends Paths<T>>(path: P): (self: T) => ValueFromPath<T, P> <T, const P extends Paths<T>>(path: P): (self: T) => ValueFromPath<T, P>
@@ -65,13 +67,13 @@ export const immutableSet: {
<T, const P extends Paths<T>>(path: P, value: ValueFromPath<T, P>): (self: T) => ValueFromPath<T, P> <T, const P extends Paths<T>>(path: P, value: ValueFromPath<T, P>): (self: T) => ValueFromPath<T, P>
<T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T> <T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T>
} = Function.dual(3, <T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T> => { } = Function.dual(3, <T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T> => {
const key = Array.head(path as AnyPath) const key = Array.head(path as PropertyPath)
if (Option.isNone(key)) if (Option.isNone(key))
return Option.some(value as T) return Option.some(value as T)
if (!Predicate.hasProperty(self, key.value)) if (!Predicate.hasProperty(self, key.value))
return Option.none() return Option.none()
const child = immutableSet<any, any>(self[key.value], Option.getOrThrow(Array.tail(path as AnyPath)), value) const child = immutableSet<any, any>(self[key.value], Option.getOrThrow(Array.tail(path as PropertyPath)), value)
if (Option.isNone(child)) if (Option.isNone(child))
return child return child

View File

@@ -2,28 +2,6 @@ import { Array, Function, Option, Predicate, Schema } from "effect"
import type { Simplify } from "effect/Types" import type { Simplify } from "effect/Types"
type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
export type Paths<S, D extends number = 5, Seen = never> = readonly [] | (
D extends never ? readonly [] :
S extends Seen ? readonly [] :
S extends Schema.Union<infer Members> ? UnionPaths<Members, D, Seen | S> :
S extends Schema.TupleType<infer Elements, infer Rest> ? FormField.TupleFormField<S, Elements, Rest> :
S extends Schema.Array$<infer Value> ? FormField.ArrayFormField<S, Value> :
S extends Schema.Struct<infer Fields> ? FormField.StructFormField<S, Fields> :
S extends Schema.Schema.Any ? FormField.GenericFormField<S> :
S extends Schema.PropertySignature<
infer TypeToken,
infer Type,
infer Key,
infer EncodedToken,
infer Encoded,
infer HasDefault,
infer R
> ? FormField.PropertySignatureFormField<S, TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R> :
never
)
export type SchemaFromPath<S, P extends readonly any[]> = S extends Schema.Schema.Any export type SchemaFromPath<S, P extends readonly any[]> = S extends Schema.Schema.Any
? P extends [infer Head, ...infer Tail] ? P extends [infer Head, ...infer Tail]
? Head extends keyof S["Type"] ? Head extends keyof S["Type"]