diff --git a/packages/effect-fc/src/SynchronizedForm.ts b/packages/effect-fc/src/SynchronizedForm.ts index fb73bdd..d080c7d 100644 --- a/packages/effect-fc/src/SynchronizedForm.ts +++ b/packages/effect-fc/src/SynchronizedForm.ts @@ -1,4 +1,4 @@ -import { Array, Cause, Chunk, type Context, Effect, Exit, Fiber, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream, SubscriptionRef } from "effect" +import { Array, Cause, Chunk, type Context, Effect, Exit, Fiber, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, SubscriptionRef } from "effect" import * as Form from "./Form.js" import * as Lens from "./Lens.js" import * as Subscribable from "./Subscribable.js" @@ -39,6 +39,7 @@ export class SynchronizedFormImpl< readonly [SynchronizedFormTypeId]: SynchronizedFormTypeId = SynchronizedFormTypeId readonly path = [] as const + readonly encodedValue: Lens.Lens constructor( readonly schema: Schema.Schema, @@ -46,7 +47,7 @@ export class SynchronizedFormImpl< readonly target: Lens.Lens, readonly value: Lens.Lens, never, never, never, never>, - readonly encodedValue: Lens.Lens, + readonly internalEncodedValue: Lens.Lens, readonly issues: Lens.Lens, readonly validationFiber: Lens.Lens>, never, never, never, never>, readonly isValidating: Subscribable.Subscribable, @@ -57,74 +58,106 @@ export class SynchronizedFormImpl< readonly runSemaphore: Effect.Semaphore, ) { super() + this.encodedValue = makeEncodedValueLens(this) + } + + synchronizeEncodedValue(encodedValue: I): Effect.Effect { + return Lens.get(this.validationFiber).pipe( + Effect.andThen(Option.match({ + onSome: Fiber.interrupt, + onNone: () => Effect.void, + })), + Effect.andThen( + Effect.forkScoped(Effect.onExit( + Schema.decode(this.schema, { errors: "all" })(encodedValue), + exit => Effect.andThen( + Exit.matchEffect(exit, { + onSuccess: v => Effect.andThen( + Lens.set(this.value, Option.some(v)), + Lens.set(this.issues, Array.empty()), + ), + onFailure: c => Option.match( + Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), + { + onSome: e => Effect.flatMap( + ParseResult.ArrayFormatter.formatError(e), + v => Lens.set(this.issues, v), + ), + onNone: () => Effect.void, + }, + ), + }), + Lens.set(this.validationFiber, Option.none()), + ), + )) + ), + Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))), + Effect.andThen(Fiber.join), + Effect.tap(value => Effect.onExit( + Effect.andThen( + Lens.set(this.isCommitting, true), + Lens.set(this.target, value), + ), + () => Lens.set(this.isCommitting, false), + )), + + Effect.ignore, + Effect.provide(this.context), + ) } get run(): Effect.Effect { - return this.runSemaphore.withPermits(1)(Effect.provide( - Effect.all([ - Stream.runForEach( - this.encodedValue.changes, + return Effect.void + // return this.runSemaphore.withPermits(1)(Effect.provide( + // Effect.andThen( + // Effect.flatMap( + // Lens.get(this.internalEncodedValue), + // encodedValue => this.synchronizeEncodedValue(encodedValue), + // ), + // Stream.runForEach( + // Stream.drop(this.target.changes, 1), - encodedValue => Lens.get(this.validationFiber).pipe( - Effect.andThen(Option.match({ - onSome: Fiber.interrupt, - onNone: () => Effect.void, - })), - Effect.andThen( - Effect.forkScoped(Effect.onExit( - Schema.decode(this.schema, { errors: "all" })(encodedValue), - exit => Effect.andThen( - Exit.matchEffect(exit, { - onSuccess: v => Effect.andThen( - Lens.set(this.value, Option.some(v)), - Lens.set(this.issues, Array.empty()), - ), - onFailure: c => Option.match(Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), { - onSome: e => Effect.flatMap( - ParseResult.ArrayFormatter.formatError(e), - v => Lens.set(this.issues, v), - ), - onNone: () => Effect.void, - }), - }), - Lens.set(this.validationFiber, Option.none()), - ), - )) - ), - Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))), - Effect.andThen(Fiber.join), - Effect.tap(value => Effect.onExit( - Effect.andThen( - Lens.set(this.isCommitting, true), - Lens.set(this.target, value), - ), - () => Lens.set(this.isCommitting, false), - )), - Effect.ignore, - ), - ), + // targetValue => Schema.encode(this.schema, { errors: "all" })(targetValue).pipe( + // Effect.flatMap(encodedValue => Effect.andThen( + // Effect.whenEffect( + // Lens.set(this.internalEncodedValue, encodedValue), + // Effect.map( + // Lens.get(this.internalEncodedValue), + // currentEncodedValue => !Equal.equals(encodedValue, currentEncodedValue), + // ), + // ), + // Effect.andThen( + // Lens.set(this.value, Option.some(targetValue)), + // Lens.set(this.issues, Array.empty()), + // ), + // )), + // Effect.ignore, + // ), + // ), + // ), - // Stream.runForEach( - // Stream.drop(this.target.changes, 1), - - // targetValue => Schema.encode(this.schema, { errors: "all" })(targetValue).pipe( - // Effect.flatMap(encodedValue => Effect.whenEffect( - // Lens.set(this.encodedValue, encodedValue), - // Effect.map( - // Lens.get(this.encodedValue), - // currentEncodedValue => !Equal.equals(encodedValue, currentEncodedValue), - // ), - // )), - // Effect.ignore, - // ), - // ), - ], { concurrency: "unbounded" }), - - this.context, - )) + // this.context, + // )) } } +const makeEncodedValueLens = ( + self: SynchronizedFormImpl +): Lens.Lens => Lens.make({ + get get() { return Lens.get(self.internalEncodedValue) }, + get changes() { return self.internalEncodedValue.changes }, + modify: f => Lens.get(self.internalEncodedValue).pipe( + Effect.flatMap(f), + Effect.flatMap(([b, nextEncodedValue]) => Effect.as( + Effect.andThen( + Lens.set(self.internalEncodedValue, nextEncodedValue), + self.synchronizeEncodedValue(nextEncodedValue), + ), + b, + )), + ), +}) + export const isSynchronizedForm = (u: unknown): u is SynchronizedForm => Predicate.hasProperty(u, SynchronizedFormTypeId)