0.2.6 #49
@@ -1,9 +1,8 @@
|
||||
import { Array, type Cause, Chunk, type Duration, Effect, Equal, Function, identity, Option, type ParseResult, Pipeable, Predicate, type Scope, Stream } from "effect"
|
||||
import { Array, type Cause, Chunk, type Duration, Effect, Equal, Function, identity, Option, type ParseResult, Pipeable, Predicate, type Scope, Stream, SubscriptionRef } from "effect"
|
||||
import type * as React from "react"
|
||||
import * as Component from "./Component.js"
|
||||
import * as Lens from "./Lens.js"
|
||||
import * as Subscribable from "./Subscribable.js"
|
||||
import * as SubscriptionRef from "./SubscriptionRef.js"
|
||||
|
||||
|
||||
export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form")
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Effect, Equivalence, Stream } from "effect"
|
||||
import { Effect, Equivalence, Stream, SubscriptionRef } from "effect"
|
||||
import { Lens } from "effect-lens"
|
||||
import * as React from "react"
|
||||
import * as Component from "./Component.js"
|
||||
import * as SetStateAction from "./SetStateAction.js"
|
||||
import * as SubscriptionRef from "./SubscriptionRef.js"
|
||||
|
||||
|
||||
export * from "effect-lens/Lens"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Option } from "effect"
|
||||
import * as PropertyPath from "./PropertyPath.js"
|
||||
|
||||
|
||||
describe("immutableSet with arrays", () => {
|
||||
test("sets a top-level array element", () => {
|
||||
const arr = [1, 2, 3]
|
||||
const result = PropertyPath.immutableSet(arr, [1], 99)
|
||||
expect(result).toEqual(Option.some([1, 99, 3]))
|
||||
})
|
||||
|
||||
test("does not mutate the original array", () => {
|
||||
const arr = [1, 2, 3]
|
||||
PropertyPath.immutableSet(arr, [0], 42)
|
||||
expect(arr).toEqual([1, 2, 3])
|
||||
})
|
||||
|
||||
test("sets the first element of an array", () => {
|
||||
const arr = ["a", "b", "c"]
|
||||
const result = PropertyPath.immutableSet(arr, [0], "z")
|
||||
expect(result).toEqual(Option.some(["z", "b", "c"]))
|
||||
})
|
||||
|
||||
test("sets the last element of an array", () => {
|
||||
const arr = [10, 20, 30]
|
||||
const result = PropertyPath.immutableSet(arr, [2], 99)
|
||||
expect(result).toEqual(Option.some([10, 20, 99]))
|
||||
})
|
||||
|
||||
test("sets a nested array element inside an object", () => {
|
||||
const obj = { tags: ["foo", "bar", "baz"] }
|
||||
const result = PropertyPath.immutableSet(obj, ["tags", 1], "qux")
|
||||
expect(result).toEqual(Option.some({ tags: ["foo", "qux", "baz"] }))
|
||||
})
|
||||
|
||||
test("sets a deeply nested value inside an array of objects", () => {
|
||||
const obj = { items: [{ name: "alice" }, { name: "bob" }] }
|
||||
const result = PropertyPath.immutableSet(obj, ["items", 0, "name"], "charlie")
|
||||
expect(result).toEqual(Option.some({ items: [{ name: "charlie" }, { name: "bob" }] }))
|
||||
})
|
||||
|
||||
test("sets a value in a nested array", () => {
|
||||
const matrix = [[1, 2], [3, 4]]
|
||||
const result = PropertyPath.immutableSet(matrix, [1, 0], 99)
|
||||
expect(result).toEqual(Option.some([[1, 2], [99, 4]]))
|
||||
})
|
||||
|
||||
test("returns Option.none() for an out-of-bounds index", () => {
|
||||
const arr = [1, 2, 3]
|
||||
const result = PropertyPath.immutableSet(arr, [5], 99)
|
||||
expect(result).toEqual(Option.none())
|
||||
})
|
||||
|
||||
test("returns Option.none() for a non-numeric key on an array", () => {
|
||||
const arr = [1, 2, 3]
|
||||
// @ts-expect-error intentionally wrong key type
|
||||
const result = PropertyPath.immutableSet(arr, ["length"], 0)
|
||||
expect(result).toEqual(Option.none())
|
||||
})
|
||||
|
||||
test("empty path returns Option.some of the value itself", () => {
|
||||
const arr = [1, 2, 3]
|
||||
const result = PropertyPath.immutableSet(arr, [], [9, 9, 9] as any)
|
||||
expect(result).toEqual(Option.some([9, 9, 9]))
|
||||
})
|
||||
})
|
||||
@@ -1,98 +0,0 @@
|
||||
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<T, D extends number = 5, Seen = never> = readonly [] | (
|
||||
D extends never ? readonly [] :
|
||||
T extends Seen ? readonly [] :
|
||||
T extends readonly any[] ? {
|
||||
[K in keyof T as K extends number ? K : never]:
|
||||
| readonly [K]
|
||||
| readonly [K, ...Paths<T[K], Prev[D], Seen | T>]
|
||||
} extends infer O
|
||||
? O[keyof O]
|
||||
: never
|
||||
:
|
||||
T extends object ? {
|
||||
[K in keyof T as K extends string | number | symbol ? K : never]-?:
|
||||
NonNullable<T[K]> extends infer V
|
||||
? readonly [K] | readonly [K, ...Paths<V, Prev[D], Seen>]
|
||||
: never
|
||||
} extends infer O
|
||||
? O[keyof O]
|
||||
: never
|
||||
:
|
||||
never
|
||||
)
|
||||
|
||||
export type ValueFromPath<T, P extends readonly any[]> = P extends readonly [infer Head, ...infer Tail]
|
||||
? Head extends keyof T
|
||||
? ValueFromPath<T[Head], Tail>
|
||||
: T extends readonly any[]
|
||||
? Head extends number
|
||||
? ValueFromPath<T[number], Tail>
|
||||
: never
|
||||
: never
|
||||
: T
|
||||
|
||||
|
||||
export const equivalence: Equivalence.Equivalence<PropertyPath> = Equivalence.array(Equivalence.strict())
|
||||
|
||||
export const unsafeGet: {
|
||||
<T, const P extends Paths<T>>(path: P): (self: T) => ValueFromPath<T, P>
|
||||
<T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P>
|
||||
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P> =>
|
||||
path.reduce((acc: any, key: any) => acc?.[key], self)
|
||||
)
|
||||
|
||||
export const get: {
|
||||
<T, const P extends Paths<T>>(path: P): (self: T) => Option.Option<ValueFromPath<T, P>>
|
||||
<T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>>
|
||||
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>> =>
|
||||
path.reduce(
|
||||
(acc: Option.Option<any>, key: any): Option.Option<any> => Option.isSome(acc)
|
||||
? Predicate.hasProperty(acc.value, key)
|
||||
? Option.some(acc.value[key])
|
||||
: Option.none()
|
||||
: acc,
|
||||
|
||||
Option.some(self),
|
||||
)
|
||||
)
|
||||
|
||||
export const immutableSet: {
|
||||
<T, const P extends Paths<T>>(path: P, value: ValueFromPath<T, P>): (self: T) => 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> => {
|
||||
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<any, any>(self[key.value], Option.getOrThrow(Array.tail(path as PropertyPath)), 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()
|
||||
})
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Array, Cause, Chunk, type Context, Effect, Exit, Fiber, identity, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream } from "effect"
|
||||
import { Array, Cause, Chunk, type Context, Effect, Exit, Fiber, identity, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream, SubscriptionRef } from "effect"
|
||||
import * as Form from "./Form.js"
|
||||
import * as Lens from "./Lens.js"
|
||||
import * as Mutation from "./Mutation.js"
|
||||
import * as Result from "./Result.js"
|
||||
import * as Subscribable from "./Subscribable.js"
|
||||
import * as SubscriptionRef from "./SubscriptionRef.js"
|
||||
|
||||
|
||||
export const SubmittableFormTypeId: unique symbol = Symbol.for("@effect-fc/Form/SubmittableForm")
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Effect, Equivalence, Ref, Stream, SubscriptionRef } from "effect"
|
||||
import * as React from "react"
|
||||
import * as Component from "./Component.js"
|
||||
import * as SetStateAction from "./SetStateAction.js"
|
||||
|
||||
|
||||
export declare namespace useSubscriptionRefState {
|
||||
export interface Options<A> {
|
||||
readonly equivalence?: Equivalence.Equivalence<A>
|
||||
}
|
||||
}
|
||||
|
||||
export const useSubscriptionRefState = Effect.fnUntraced(function* <A>(
|
||||
ref: SubscriptionRef.SubscriptionRef<A>,
|
||||
options?: useSubscriptionRefState.Options<NoInfer<A>>,
|
||||
): Effect.fn.Return<readonly [A, React.Dispatch<React.SetStateAction<A>>]> {
|
||||
const [reactStateValue, setReactStateValue] = React.useState(yield* Component.useOnMount(() => ref))
|
||||
|
||||
yield* Component.useReactEffect(() => Effect.forkScoped(
|
||||
Stream.runForEach(
|
||||
Stream.changesWith(ref.changes, options?.equivalence ?? Equivalence.strict()),
|
||||
v => Effect.sync(() => setReactStateValue(v)),
|
||||
)
|
||||
), [ref])
|
||||
|
||||
const setValue = yield* Component.useCallbackSync(
|
||||
(setStateAction: React.SetStateAction<A>) => Effect.andThen(
|
||||
Ref.updateAndGet(ref, prevState => SetStateAction.value(setStateAction, prevState)),
|
||||
v => setReactStateValue(v),
|
||||
),
|
||||
[ref],
|
||||
)
|
||||
|
||||
return [reactStateValue, setValue]
|
||||
})
|
||||
|
||||
export declare namespace useSubscriptionRefFromState {
|
||||
export interface Options<A> {
|
||||
readonly equivalence?: Equivalence.Equivalence<A>
|
||||
}
|
||||
}
|
||||
|
||||
export const useSubscriptionRefFromState = Effect.fnUntraced(function* <A>(
|
||||
[value, setValue]: readonly [A, React.Dispatch<React.SetStateAction<A>>],
|
||||
options?: useSubscriptionRefFromState.Options<NoInfer<A>>,
|
||||
): Effect.fn.Return<SubscriptionRef.SubscriptionRef<A>> {
|
||||
const ref = yield* Component.useOnChange(() => Effect.tap(
|
||||
SubscriptionRef.make(value),
|
||||
ref => Effect.forkScoped(
|
||||
Stream.runForEach(
|
||||
Stream.changesWith(ref.changes, options?.equivalence ?? Equivalence.strict()),
|
||||
v => Effect.sync(() => setValue(v)),
|
||||
)
|
||||
),
|
||||
), [setValue])
|
||||
|
||||
yield* Component.useReactEffect(() => Ref.set(ref, value), [value])
|
||||
return ref
|
||||
})
|
||||
|
||||
export * from "effect/SubscriptionRef"
|
||||
@@ -1,183 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Chunk, Effect, Ref, SubscriptionRef } from "effect"
|
||||
import * as SubscriptionSubRef from "./SubscriptionSubRef.js"
|
||||
|
||||
|
||||
describe("SubscriptionSubRef with array refs", () => {
|
||||
test("creates a subref for a single array element using path", async () => {
|
||||
const value = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([{ name: "alice" }, { name: "bob" }, { name: "charlie" }]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [1, "name"])
|
||||
return subref.get
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(value).toBe("bob")
|
||||
})
|
||||
|
||||
test("modifies a single array element via subref", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([{ name: "alice" }, { name: "bob" }, { name: "charlie" }]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [1, "name"])
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, "bob-updated"),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toEqual([{ name: "alice" }, { name: "bob-updated" }, { name: "charlie" }])
|
||||
})
|
||||
|
||||
test("modifies array element at index 0", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([10, 20, 30]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [0])
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, 99),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toEqual([99, 20, 30])
|
||||
})
|
||||
|
||||
test("modifies array element at last index", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make(["a", "b", "c"]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [2])
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, "z"),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toEqual(["a", "b", "z"])
|
||||
})
|
||||
|
||||
test("modifies nested array element", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([[1, 2], [3, 4], [5, 6]]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [1, 0])
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, 99),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toEqual([[1, 2], [99, 4], [5, 6]])
|
||||
})
|
||||
|
||||
test("uses modifyEffect to transform array element", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([{ count: 1 }, { count: 2 }, { count: 3 }]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [1, "count"])
|
||||
return Effect.flatMap(
|
||||
Ref.update(subref, count => count + 100),
|
||||
() => Effect.map(Ref.get(parent), parentValue => ({ result: 102, parentValue })),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result.result).toBe(102) // count + 100
|
||||
expect(result.parentValue).toEqual([{ count: 1 }, { count: 102 }, { count: 3 }]) // count + 100
|
||||
})
|
||||
|
||||
test("uses modify to transform array element", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([10, 20, 30]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [1])
|
||||
return Effect.flatMap(
|
||||
Ref.update(subref, x => x + 5),
|
||||
() => Effect.map(Ref.get(parent), parentValue => ({ result: 25, parentValue })),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result.result).toBe(25) // 20 + 5
|
||||
expect(result.parentValue).toEqual([10, 25, 30]) // 20 + 5
|
||||
})
|
||||
|
||||
test("makeFromChunkIndex modifies chunk element", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make(Chunk.make(100, 200, 300)),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromChunkIndex(parent, 1)
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, 999),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(Chunk.toReadonlyArray(result)).toEqual([100, 999, 300])
|
||||
})
|
||||
|
||||
test("makeFromGetSet with custom getter/setter for array element", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make([{ id: 1, value: "a" }, { id: 2, value: "b" }]),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromGetSet(parent, {
|
||||
get: arr => arr[0].value,
|
||||
set: (arr, newValue) => [
|
||||
{ ...arr[0], value: newValue },
|
||||
...arr.slice(1),
|
||||
],
|
||||
})
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, "updated"),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toEqual([{ id: 1, value: "updated" }, { id: 2, value: "b" }])
|
||||
})
|
||||
|
||||
test("does not mutate original array when modifying via subref", async () => {
|
||||
const original = [{ name: "alice" }, { name: "bob" }]
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make(original),
|
||||
parent => {
|
||||
const subref = SubscriptionSubRef.makeFromPath(parent, [0, "name"])
|
||||
return Effect.flatMap(
|
||||
Ref.set(subref, "alice-updated"),
|
||||
() => Ref.get(parent),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(original).toEqual([{ name: "alice" }, { name: "bob" }]) // original unchanged
|
||||
expect(result).toEqual([{ name: "alice-updated" }, { name: "bob" }]) // new value in ref
|
||||
})
|
||||
})
|
||||
@@ -1,186 +0,0 @@
|
||||
import { Chunk, Effect, Effectable, Option, Predicate, Readable, Ref, Stream, Subscribable, SubscriptionRef, SynchronizedRef, type Types, type Unify } from "effect"
|
||||
import * as PropertyPath from "./PropertyPath.js"
|
||||
|
||||
|
||||
export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("@effect-fc/SubscriptionSubRef/SubscriptionSubRef")
|
||||
export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId
|
||||
|
||||
export interface SubscriptionSubRef<in out A, in out B extends SubscriptionRef.SubscriptionRef<any>>
|
||||
extends SubscriptionSubRef.Variance<A, B>, SubscriptionRef.SubscriptionRef<A> {
|
||||
readonly parent: B
|
||||
|
||||
readonly [Unify.typeSymbol]?: unknown
|
||||
readonly [Unify.unifySymbol]?: SubscriptionSubRefUnify<this>
|
||||
readonly [Unify.ignoreSymbol]?: SubscriptionSubRefUnifyIgnore
|
||||
}
|
||||
|
||||
export declare namespace SubscriptionSubRef {
|
||||
export interface Variance<in out A, in out B> {
|
||||
readonly [SubscriptionSubRefTypeId]: {
|
||||
readonly _A: Types.Invariant<A>
|
||||
readonly _B: Types.Invariant<B>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SubscriptionSubRefUnify<A extends { [Unify.typeSymbol]?: any }> extends SubscriptionRef.SubscriptionRefUnify<A> {
|
||||
SubscriptionSubRef?: () => Extract<A[Unify.typeSymbol], SubscriptionSubRef<any, any>>
|
||||
}
|
||||
|
||||
export interface SubscriptionSubRefUnifyIgnore extends SubscriptionRef.SubscriptionRefUnifyIgnore {
|
||||
SubscriptionRef?: true
|
||||
}
|
||||
|
||||
|
||||
const refVariance = { _A: (_: any) => _ }
|
||||
const synchronizedRefVariance = { _A: (_: any) => _ }
|
||||
const subscriptionRefVariance = { _A: (_: any) => _ }
|
||||
const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ }
|
||||
|
||||
class SubscriptionSubRefImpl<in out A, in out B extends SubscriptionRef.SubscriptionRef<any>>
|
||||
extends Effectable.Class<A> implements SubscriptionSubRef<A, B> {
|
||||
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
|
||||
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
|
||||
readonly [Ref.RefTypeId] = refVariance
|
||||
readonly [SynchronizedRef.SynchronizedRefTypeId] = synchronizedRefVariance
|
||||
readonly [SubscriptionRef.SubscriptionRefTypeId] = subscriptionRefVariance
|
||||
readonly [SubscriptionSubRefTypeId] = subscriptionSubRefVariance
|
||||
|
||||
readonly get: Effect.Effect<A>
|
||||
|
||||
constructor(
|
||||
readonly parent: B,
|
||||
readonly getter: (parentValue: Effect.Effect.Success<B>) => A,
|
||||
readonly setter: (parentValue: Effect.Effect.Success<B>, value: A) => Effect.Effect.Success<B>,
|
||||
) {
|
||||
super()
|
||||
this.get = Effect.map(this.parent, this.getter)
|
||||
}
|
||||
|
||||
commit() {
|
||||
return this.get
|
||||
}
|
||||
|
||||
get changes(): Stream.Stream<A> {
|
||||
return Stream.unwrap(
|
||||
Effect.map(this.get, a => Stream.concat(
|
||||
Stream.make(a),
|
||||
Stream.map(this.parent.changes, this.getter),
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
modify<C>(f: (a: A) => readonly [C, A]): Effect.Effect<C> {
|
||||
return this.modifyEffect(a => Effect.succeed(f(a)))
|
||||
}
|
||||
|
||||
modifyEffect<C, E, R>(f: (a: A) => Effect.Effect<readonly [C, A], E, R>): Effect.Effect<C, E, R> {
|
||||
return Effect.Do.pipe(
|
||||
Effect.bind("b", (): Effect.Effect<Effect.Effect.Success<B>> => this.parent),
|
||||
Effect.bind("ca", ({ b }) => f(this.getter(b))),
|
||||
Effect.tap(({ b, ca: [, a] }) => SubscriptionRef.set(this.parent, this.setter(b, a))),
|
||||
Effect.map(({ ca: [c] }) => c),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const isSubscriptionSubRef = (u: unknown): u is SubscriptionSubRef<unknown, SubscriptionRef.SubscriptionRef<unknown>> => Predicate.hasProperty(u, SubscriptionSubRefTypeId)
|
||||
|
||||
export const makeFromGetSet = <A, B extends SubscriptionRef.SubscriptionRef<any>>(
|
||||
parent: B,
|
||||
options: {
|
||||
readonly get: (parentValue: Effect.Effect.Success<B>) => A
|
||||
readonly set: (parentValue: Effect.Effect.Success<B>, value: A) => Effect.Effect.Success<B>
|
||||
},
|
||||
): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, options.get, options.set)
|
||||
|
||||
export const makeFromPath = <
|
||||
B extends SubscriptionRef.SubscriptionRef<any>,
|
||||
const P extends PropertyPath.Paths<Effect.Effect.Success<B>>,
|
||||
>(
|
||||
parent: B,
|
||||
path: P,
|
||||
): SubscriptionSubRef<PropertyPath.ValueFromPath<Effect.Effect.Success<B>, P>, B> => new SubscriptionSubRefImpl(
|
||||
parent,
|
||||
parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)),
|
||||
(parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)),
|
||||
)
|
||||
|
||||
export const makeFromChunkIndex: {
|
||||
<B extends SubscriptionRef.SubscriptionRef<Chunk.NonEmptyChunk<any>>>(
|
||||
parent: B,
|
||||
index: number,
|
||||
): SubscriptionSubRef<
|
||||
Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never,
|
||||
B
|
||||
>
|
||||
<B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>>(
|
||||
parent: B,
|
||||
index: number,
|
||||
): SubscriptionSubRef<
|
||||
Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
|
||||
B
|
||||
>
|
||||
} = (
|
||||
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>,
|
||||
index: number,
|
||||
) => new SubscriptionSubRefImpl(
|
||||
parent,
|
||||
parentValue => Chunk.unsafeGet(parentValue, index),
|
||||
(parentValue, value) => Chunk.replace(parentValue, index, value),
|
||||
) as any
|
||||
|
||||
export const makeFromChunkFindFirst: {
|
||||
<B extends SubscriptionRef.SubscriptionRef<Chunk.NonEmptyChunk<any>>>(
|
||||
parent: B,
|
||||
findFirstPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never>,
|
||||
): SubscriptionSubRef<
|
||||
Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never,
|
||||
B
|
||||
>
|
||||
<B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>>(
|
||||
parent: B,
|
||||
findFirstPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never>,
|
||||
): SubscriptionSubRef<
|
||||
Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
|
||||
B
|
||||
>
|
||||
} = (
|
||||
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<never>>,
|
||||
findFirstPredicate: Predicate.Predicate.Any,
|
||||
) => new SubscriptionSubRefImpl(
|
||||
parent,
|
||||
parentValue => Option.getOrThrow(Chunk.findFirst(parentValue, findFirstPredicate)),
|
||||
(parentValue, value) => Option.getOrThrow(Option.andThen(
|
||||
Chunk.findFirstIndex(parentValue, findFirstPredicate),
|
||||
index => Chunk.replace(parentValue, index, value),
|
||||
)),
|
||||
) as any
|
||||
|
||||
export const makeFromChunkFindLast: {
|
||||
<B extends SubscriptionRef.SubscriptionRef<Chunk.NonEmptyChunk<any>>>(
|
||||
parent: B,
|
||||
findLastPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never>,
|
||||
): SubscriptionSubRef<
|
||||
Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never,
|
||||
B
|
||||
>
|
||||
<B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>>(
|
||||
parent: B,
|
||||
findLastPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never>,
|
||||
): SubscriptionSubRef<
|
||||
Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
|
||||
B
|
||||
>
|
||||
} = (
|
||||
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<never>>,
|
||||
findLastPredicate: Predicate.Predicate.Any,
|
||||
) => new SubscriptionSubRefImpl(
|
||||
parent,
|
||||
parentValue => Option.getOrThrow(Chunk.findLast(parentValue, findLastPredicate)),
|
||||
(parentValue, value) => Option.getOrThrow(Option.andThen(
|
||||
Chunk.findLastIndex(parentValue, findLastPredicate),
|
||||
index => Chunk.replace(parentValue, index, value),
|
||||
)),
|
||||
) as any
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Array, Cause, Chunk, type Context, Effect, Equal, Exit, Fiber, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream } from "effect"
|
||||
import { Array, Cause, Chunk, type Context, Effect, Equal, Exit, Fiber, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream, SubscriptionRef } from "effect"
|
||||
import * as Form from "./Form.js"
|
||||
import * as Lens from "./Lens.js"
|
||||
import * as Subscribable from "./Subscribable.js"
|
||||
import * as SubscriptionRef from "./SubscriptionRef.js"
|
||||
|
||||
|
||||
export const SynchronizedFormTypeId: unique symbol = Symbol.for("@effect-fc/Form/SynchronizedForm")
|
||||
|
||||
@@ -5,7 +5,6 @@ export * as Form from "./Form.js"
|
||||
export * as Lens from "./Lens.js"
|
||||
export * as Memoized from "./Memoized.js"
|
||||
export * as Mutation from "./Mutation.js"
|
||||
export * as PropertyPath from "./PropertyPath.js"
|
||||
export * as PubSub from "./PubSub.js"
|
||||
export * as Query from "./Query.js"
|
||||
export * as QueryClient from "./QueryClient.js"
|
||||
@@ -15,6 +14,4 @@ export * as SetStateAction from "./SetStateAction.js"
|
||||
export * as Stream from "./Stream.js"
|
||||
export * as SubmittableForm from "./SubmittableForm.js"
|
||||
export * as Subscribable from "./Subscribable.js"
|
||||
export * as SubscriptionRef from "./SubscriptionRef.js"
|
||||
export * as SubscriptionSubRef from "./SubscriptionSubRef.js"
|
||||
export * as SynchronizedForm from "./SynchronizedForm.js"
|
||||
|
||||
Reference in New Issue
Block a user