Update dependency @effect/language-service to ^0.84.0 #39

Closed
renovate-bot wants to merge 65 commits from renovate/bun-minor-patch into next
11 changed files with 644 additions and 441 deletions

591
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@effect-fc/monorepo",
"packageManager": "bun@1.3.6",
"packageManager": "bun@1.3.11",
"private": true,
"workspaces": [
"./packages/*"
@@ -15,12 +15,12 @@
"clean:modules": "turbo clean:modules && rm -rf node_modules"
},
"devDependencies": {
"@biomejs/biome": "^2.3.11",
"@effect/language-service": "^0.75.0",
"@types/bun": "^1.3.6",
"npm-check-updates": "^19.3.1",
"@biomejs/biome": "^2.4.8",
"@effect/language-service": "^0.84.0",
"@types/bun": "^1.3.11",
"npm-check-updates": "^19.6.5",
"npm-sort": "^0.0.4",
"turbo": "^2.7.5",
"typescript": "^5.9.3"
"turbo": "^2.8.20",
"typescript": "^6.0.2"
}
}

View File

@@ -27,7 +27,7 @@
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.9.2",
"typescript": "~5.6.2"
"typescript": "~5.9.0"
},
"browserslist": {
"production": [

View File

@@ -38,11 +38,14 @@
"clean:modules": "rm -rf node_modules"
},
"devDependencies": {
"@effect/platform-browser": "^0.74.0"
"@effect/platform-browser": "^0.76.0"
},
"peerDependencies": {
"@types/react": "^19.2.0",
"effect": "^3.19.0",
"effect": "^3.21.0",
"react": "^19.2.0"
},
"dependencies": {
"effect-lens": "^0.1.1"
}
}

View File

@@ -1,12 +1,12 @@
import { Array, Cause, Chunk, type Context, type Duration, Effect, Equal, Exit, Fiber, flow, Hash, HashMap, identity, Option, ParseResult, Pipeable, Predicate, Ref, Schema, type Scope, Stream } from "effect"
import type * as React from "react"
import * as Component from "./Component.js"
import * as Lens from "./Lens.js"
import * as Mutation from "./Mutation.js"
import * as PropertyPath from "./PropertyPath.js"
import * as Result from "./Result.js"
import * as Subscribable from "./Subscribable.js"
import * as SubscriptionRef from "./SubscriptionRef.js"
import * as SubscriptionSubRef from "./SubscriptionSubRef.js"
export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form")
@@ -26,7 +26,7 @@ extends Pipeable.Pipeable {
readonly debounce: Option.Option<Duration.DurationInput>
readonly value: Subscribable.Subscribable<Option.Option<A>>
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>
readonly encodedValue: Lens.Lens<I>
readonly error: Subscribable.Subscribable<Option.Option<ParseResult.ParseError>>
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>
@@ -54,10 +54,10 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
readonly autosubmit: boolean,
readonly debounce: Option.Option<Duration.DurationInput>,
readonly value: SubscriptionRef.SubscriptionRef<Option.Option<A>>,
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>,
readonly error: SubscriptionRef.SubscriptionRef<Option.Option<ParseResult.ParseError>>,
readonly validationFiber: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>,
readonly value: Lens.Lens<Option.Option<A>>,
readonly encodedValue: Lens.Lens<I>,
readonly error: Lens.Lens<Option.Option<ParseResult.ParseError>>,
readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>,
readonly runSemaphore: Effect.Semaphore,
readonly fieldCache: Ref.Ref<HashMap.HashMap<FormFieldKey, FormField<unknown, unknown>>>,
@@ -99,7 +99,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
Option.isSome(this.debounce) ? Stream.debounce(this.debounce.value) : identity
),
encodedValue => this.validationFiber.pipe(
encodedValue => Lens.get(this.validationFiber).pipe(
Effect.andThen(Option.match({
onSome: Fiber.interrupt,
onNone: () => Effect.void,
@@ -110,18 +110,18 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
exit => Effect.andThen(
Exit.matchEffect(exit, {
onSuccess: v => Effect.andThen(
Ref.set(this.value, Option.some(v)),
Ref.set(this.error, Option.none()),
Lens.set(this.value, Option.some(v)),
Lens.set(this.error, Option.none()),
),
onFailure: c => Option.match(Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), {
onSome: e => Ref.set(this.error, Option.some(e)),
onSome: e => Lens.set(this.error, Option.some(e)),
onNone: () => Effect.void,
}),
}),
Ref.set(this.validationFiber, Option.none()),
Lens.set(this.validationFiber, Option.none()),
),
)).pipe(
Effect.tap(fiber => Ref.set(this.validationFiber, Option.some(fiber))),
Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))),
Effect.andThen(Fiber.join),
Effect.andThen(value => this.autosubmit
? Effect.asVoid(Effect.forkScoped(this.submitValue(value)))
@@ -136,7 +136,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
}
get submit(): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException> {
return this.value.pipe(
return Lens.get(this.value).pipe(
Effect.andThen(identity),
Effect.andThen(value => this.submitValue(value)),
)
@@ -153,7 +153,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
e => e._tag === "ParseError",
),
{
onSome: e => Ref.set(this.error, Option.some(e)),
onSome: e => Lens.set(this.error, Option.some(e)),
onNone: () => Effect.void,
},
)
@@ -193,10 +193,10 @@ export const make = Effect.fnUntraced(function* <A, I = A, R = never, MA = void,
options.autosubmit ?? false,
Option.fromNullable(options.debounce),
yield* SubscriptionRef.make(Option.none<A>()),
yield* SubscriptionRef.make(options.initialEncodedValue),
yield* SubscriptionRef.make(Option.none<ParseResult.ParseError>()),
yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>()),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<A>())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(options.initialEncodedValue)),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<ParseResult.ParseError>())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>())),
yield* Effect.makeSemaphore(1),
yield* Ref.make(HashMap.empty<FormFieldKey, FormField<unknown, unknown>>()),
@@ -228,7 +228,7 @@ extends Pipeable.Pipeable {
readonly [FormFieldTypeId]: FormFieldTypeId
readonly value: Subscribable.Subscribable<Option.Option<A>, Cause.NoSuchElementException>
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>
readonly encodedValue: Lens.Lens<I>
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>
readonly isValidating: Subscribable.Subscribable<boolean>
readonly isSubmitting: Subscribable.Subscribable<boolean>
@@ -240,7 +240,7 @@ extends Pipeable.Class() implements FormField<A, I> {
constructor(
readonly value: Subscribable.Subscribable<Option.Option<A>, Cause.NoSuchElementException>,
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>,
readonly encodedValue: Lens.Lens<I>,
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>,
readonly isValidating: Subscribable.Subscribable<boolean>,
readonly isSubmitting: Subscribable.Subscribable<boolean>,
@@ -276,7 +276,7 @@ export const makeFormField = <A, I, R, MA, ME, MR, MP, const P extends PropertyP
onSome: v => Option.map(PropertyPath.get(v, path), Option.some),
onNone: () => Option.some(Option.none()),
})),
SubscriptionSubRef.makeFromPath(self.encodedValue, path),
Lens.map(self.encodedValue, a => Option.getOrThrow(PropertyPath.get(a, path)), (a, b) => Option.getOrThrow(PropertyPath.immutableSet(a, path, b))),
Subscribable.mapEffect(self.error, Option.match({
onSome: flow(
ParseResult.ArrayFormatter.formatError,
@@ -305,29 +305,35 @@ export const useInput = Effect.fnUntraced(function* <A, I>(
field: FormField<A, I>,
options?: useInput.Options,
): Effect.fn.Return<useInput.Success<I>, Cause.NoSuchElementException, Scope.Scope> {
const internalValueRef = yield* Component.useOnChange(() => Effect.tap(
Effect.andThen(field.encodedValue, SubscriptionRef.make),
internalValueRef => Effect.forkScoped(Effect.all([
const internalValueLens = yield* Component.useOnChange(() => Effect.gen(function*() {
const internalValueLens = yield* Lens.get(field.encodedValue).pipe(
Effect.flatMap(SubscriptionRef.make),
Effect.map(Lens.fromSubscriptionRef),
)
yield* Effect.forkScoped(Effect.all([
Stream.runForEach(
Stream.drop(field.encodedValue, 1),
Stream.drop(field.encodedValue.changes, 1),
upstreamEncodedValue => Effect.whenEffect(
Ref.set(internalValueRef, upstreamEncodedValue),
Effect.andThen(internalValueRef, internalValue => !Equal.equals(upstreamEncodedValue, internalValue)),
Lens.set(internalValueLens, upstreamEncodedValue),
Effect.andThen(Lens.get(internalValueLens), internalValue => !Equal.equals(upstreamEncodedValue, internalValue)),
),
),
Stream.runForEach(
internalValueRef.changes.pipe(
internalValueLens.changes.pipe(
Stream.drop(1),
Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity,
),
internalValue => Ref.set(field.encodedValue, internalValue),
internalValue => Lens.set(field.encodedValue, internalValue),
),
], { concurrency: "unbounded" })),
), [field, options?.debounce])
], { concurrency: "unbounded" }))
const [value, setValue] = yield* SubscriptionRef.useSubscriptionRefState(internalValueRef)
return internalValueLens
}), [field, options?.debounce])
const [value, setValue] = yield* Lens.useState(internalValueLens)
return { value, setValue }
})
@@ -346,51 +352,59 @@ export const useOptionalInput = Effect.fnUntraced(function* <A, I>(
field: FormField<A, Option.Option<I>>,
options: useOptionalInput.Options<I>,
): Effect.fn.Return<useOptionalInput.Success<I>, Cause.NoSuchElementException, Scope.Scope> {
const [enabledRef, internalValueRef] = yield* Component.useOnChange(() => Effect.tap(
Effect.andThen(
field.encodedValue,
const [enabledLens, internalValueLens] = yield* Component.useOnChange(() => Effect.gen(function*() {
const [enabledLens, internalValueLens] = yield* Effect.flatMap(
Lens.get(field.encodedValue),
Option.match({
onSome: v => Effect.all([SubscriptionRef.make(true), SubscriptionRef.make(v)]),
onNone: () => Effect.all([SubscriptionRef.make(false), SubscriptionRef.make(options.defaultValue)]),
onSome: v => Effect.all([
Effect.map(SubscriptionRef.make(true), Lens.fromSubscriptionRef),
Effect.map(SubscriptionRef.make(v), Lens.fromSubscriptionRef),
]),
onNone: () => Effect.all([
Effect.map(SubscriptionRef.make(false), Lens.fromSubscriptionRef),
Effect.map(SubscriptionRef.make(options.defaultValue), Lens.fromSubscriptionRef),
]),
}),
),
)
([enabledRef, internalValueRef]) => Effect.forkScoped(Effect.all([
yield* Effect.forkScoped(Effect.all([
Stream.runForEach(
Stream.drop(field.encodedValue, 1),
Stream.drop(field.encodedValue.changes, 1),
upstreamEncodedValue => Effect.whenEffect(
Option.match(upstreamEncodedValue, {
onSome: v => Effect.andThen(
Ref.set(enabledRef, true),
Ref.set(internalValueRef, v),
Lens.set(enabledLens, true),
Lens.set(internalValueLens, v),
),
onNone: () => Effect.andThen(
Ref.set(enabledRef, false),
Ref.set(internalValueRef, options.defaultValue),
Lens.set(enabledLens, false),
Lens.set(internalValueLens, options.defaultValue),
),
}),
Effect.andThen(
Effect.all([enabledRef, internalValueRef]),
Effect.all([Lens.get(enabledLens), Lens.get(internalValueLens)]),
([enabled, internalValue]) => !Equal.equals(upstreamEncodedValue, enabled ? Option.some(internalValue) : Option.none()),
),
),
),
Stream.runForEach(
enabledRef.changes.pipe(
Stream.zipLatest(internalValueRef.changes),
enabledLens.changes.pipe(
Stream.zipLatest(internalValueLens.changes),
Stream.drop(1),
Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity,
),
([enabled, internalValue]) => Ref.set(field.encodedValue, enabled ? Option.some(internalValue) : Option.none()),
([enabled, internalValue]) => Lens.set(field.encodedValue, enabled ? Option.some(internalValue) : Option.none()),
),
], { concurrency: "unbounded" })),
), [field, options.debounce])
], { concurrency: "unbounded" }))
const [enabled, setEnabled] = yield* SubscriptionRef.useSubscriptionRefState(enabledRef)
const [value, setValue] = yield* SubscriptionRef.useSubscriptionRefState(internalValueRef)
return [enabledLens, internalValueLens] as const
}), [field, options.debounce])
const [enabled, setEnabled] = yield* Lens.useState(enabledLens)
const [value, setValue] = yield* Lens.useState(internalValueLens)
return { enabled, setEnabled, value, setValue }
})

View File

@@ -0,0 +1,63 @@
import { Effect, Equivalence, Stream } 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 declare namespace useState {
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useState = Effect.fnUntraced(function* <A, ER, EW, RR, RW>(
lens: Lens.Lens<A, ER, EW, RR, RW>,
options?: useState.Options<NoInfer<A>>,
): Effect.fn.Return<readonly [A, React.Dispatch<React.SetStateAction<A>>], ER, RR | RW> {
const [reactStateValue, setReactStateValue] = React.useState(yield* Component.useOnMount(() => Lens.get(lens)))
yield* Component.useReactEffect(() => Effect.forkScoped(
Stream.runForEach(
Stream.changesWith(lens.changes, options?.equivalence ?? Equivalence.strict()),
v => Effect.sync(() => setReactStateValue(v)),
)
), [lens])
const setValue = yield* Component.useCallbackSync(
(setStateAction: React.SetStateAction<A>) => Effect.andThen(
Lens.updateAndGet(lens, prevState => SetStateAction.value(setStateAction, prevState)),
v => setReactStateValue(v),
),
[lens],
)
return [reactStateValue, setValue]
})
export declare namespace useFromState {
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useFromState = Effect.fnUntraced(function* <A>(
[value, setValue]: readonly [A, React.Dispatch<React.SetStateAction<A>>],
options?: useFromState.Options<NoInfer<A>>,
): Effect.fn.Return<Lens.Lens<A, never, never, never, never>> {
const lens = yield* Component.useOnMount(() => Effect.map(
SubscriptionRef.make(value),
Lens.fromSubscriptionRef,
))
yield* Component.useReactEffect(() => Effect.forkScoped(Stream.runForEach(
Stream.changesWith(lens.changes, options?.equivalence ?? Equivalence.strict()),
v => Effect.sync(() => setValue(v)),
)), [setValue])
yield* Component.useReactEffect(() => Lens.set(lens, value), [value])
return lens
})
export * from "effect-lens/Lens"

View File

@@ -0,0 +1,67 @@
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]))
})
})

View File

@@ -0,0 +1,183 @@
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
})
})

View File

@@ -2,6 +2,7 @@ export * as Async from "./Async.js"
export * as Component from "./Component.js"
export * as ErrorObserver from "./ErrorObserver.js"
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"

View File

@@ -34,5 +34,6 @@
]
},
"include": ["./src"]
"include": ["./src"],
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}

View File

@@ -13,30 +13,30 @@
"clean:modules": "rm -rf node_modules"
},
"devDependencies": {
"@tanstack/react-router": "^1.154.12",
"@tanstack/react-router-devtools": "^1.154.12",
"@tanstack/router-plugin": "^1.154.12",
"@types/react": "^19.2.9",
"@tanstack/react-router": "^1.168.3",
"@tanstack/react-router-devtools": "^1.166.11",
"@tanstack/router-plugin": "^1.167.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"globals": "^17.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"type-fest": "^5.4.1",
"vite": "^7.3.1"
"@vitejs/plugin-react": "^6.0.1",
"globals": "^17.4.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"type-fest": "^5.5.0",
"vite": "^8.0.2"
},
"dependencies": {
"@effect/platform": "^0.94.2",
"@effect/platform-browser": "^0.74.0",
"@radix-ui/themes": "^3.2.1",
"@effect/platform": "^0.96.0",
"@effect/platform-browser": "^0.76.0",
"@radix-ui/themes": "^3.3.0",
"@typed/id": "^0.17.2",
"effect": "^3.19.15",
"effect": "^3.21.0",
"effect-fc": "workspace:*",
"react-icons": "^5.5.0"
"react-icons": "^5.6.0"
},
"overrides": {
"@types/react": "^19.2.9",
"effect": "^3.19.15",
"react": "^19.2.3"
"@types/react": "^19.2.14",
"effect": "^3.21.0",
"react": "^19.2.4"
}
}