Upgrade effect-lens
Lint / lint (push) Failing after 1m44s

This commit is contained in:
Julien Valverdé
2026-07-17 21:25:07 +02:00
parent 23cdfb3894
commit 3fc3904c6c
14 changed files with 154 additions and 193 deletions
+14 -7
View File
@@ -58,17 +58,18 @@
"name": "effect-fc-next", "name": "effect-fc-next",
"version": "0.1.0-beta.0", "version": "0.1.0-beta.0",
"dependencies": { "dependencies": {
"effect-lens": "2.0.0-beta.0", "effect-lens": "^2.0.0-beta.1",
}, },
"devDependencies": { "devDependencies": {
"@effect/platform-browser": "^4.0.0-beta.85", "@effect/platform-browser": "4.0.0-beta.98",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"effect": "4.0.0-beta.98",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"vitest": "^3.2.4", "vitest": "^3.2.4",
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"effect": "4.0.0-beta.85", "effect": "4.0.0-beta.98",
"react": "^19.2.0", "react": "^19.2.0",
}, },
}, },
@@ -3063,11 +3064,11 @@
"dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], "dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
"effect-fc-next/@effect/platform-browser": ["@effect/platform-browser@4.0.0-beta.87", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^4.0.0-beta.87" } }, "sha512-sBUUdRt5PjFPLI7+ayxEX/jgnjCbOuc3I//98CmqiwhfgWbCMZ/rofYYb8Zk5tLpNsmdHnWgHWTsBLvH2SFrCw=="], "effect-fc-next/@effect/platform-browser": ["@effect/platform-browser@4.0.0-beta.98", "", { "dependencies": { "multipasta": "^0.2.8" }, "peerDependencies": { "effect": "^4.0.0-beta.98" } }, "sha512-Y0BEe0N8clKmJTHqmZpS85pLZbqAxP5uV3IYF0tDw/1dcs8AbTRHjbRmOLdyLtmU32NBVAiaUD3Vopb5Et5R/g=="],
"effect-fc-next/effect": ["effect@4.0.0-beta.85", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Cjv9YQyv4CiIccmRAQIWAoeESCpCpiuHYY8zb5vqiYs3Ac2yE5RQAnKq7z4Ir/3VJYZb8kxh6rS8czDDnpTdkQ=="], "effect-fc-next/effect": ["effect@4.0.0-beta.98", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-oz+bsG5h+6RNrw4t5GMfQrk/xBS8ROoqkYsuvRhBr5O7mCOrpvH/hbw+QrDzvKIpX4HJClwm86F94c87W0sJxg=="],
"effect-fc-next/effect-lens": ["effect-lens@2.0.0-beta.0", "", { "peerDependencies": { "effect": "4.0.0-beta.85" } }, "sha512-ntsRQSrzoX+GsNpkNVEGqebl/g1YOEDPIFRAHigf+4P6AtGnnOFcklqDHLrbm4zqnaxuWEgQvvn1J6S3eyC4yw=="], "effect-fc-next/effect-lens": ["effect-lens@2.0.0-beta.1", "", { "peerDependencies": { "effect": "4.0.0-beta.98" } }, "sha512-3MwsrezPczZHH4Tl2SvliZdiNRq9e/XbH+Vr1ckr/FPCEp6Wv8IYQL4Ie/hhI+XBiLlxbQERbgL8FlHW21avtw=="],
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
@@ -3373,10 +3374,16 @@
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
"effect-fc-next/effect/fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], "effect-fc-next/@effect/platform-browser/multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="],
"effect-fc-next/effect/fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="],
"effect-fc-next/effect/msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="], "effect-fc-next/effect/msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="],
"effect-fc-next/effect/multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="],
"effect-fc-next/effect/toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"file-loader/schema-utils/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "file-loader/schema-utils/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
+4 -3
View File
@@ -39,17 +39,18 @@
"clean:modules": "rm -rf node_modules" "clean:modules": "rm -rf node_modules"
}, },
"devDependencies": { "devDependencies": {
"@effect/platform-browser": "^4.0.0-beta.85", "@effect/platform-browser": "4.0.0-beta.98",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"effect": "4.0.0-beta.98",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"effect": "4.0.0-beta.85", "effect": "4.0.0-beta.98",
"react": "^19.2.0" "react": "^19.2.0"
}, },
"dependencies": { "dependencies": {
"effect-lens": "2.0.0-beta.0" "effect-lens": "^2.0.0-beta.1"
} }
} }
+19 -19
View File
@@ -2,7 +2,7 @@ import { Array, type Cause, Chunk, type Duration, Effect, Equal, Function, ident
import type * as React from "react" import type * as React from "react"
import * as Component from "./Component.js" import * as Component from "./Component.js"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import * as Subscribable from "./Subscribable.js" import * as View from "./View.js"
export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form") export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form")
@@ -18,12 +18,12 @@ extends Pipeable.Pipeable {
readonly [FormTypeId]: FormTypeId readonly [FormTypeId]: FormTypeId
readonly path: P readonly path: P
readonly value: Subscribable.Subscribable<Option.Option<A>, ER, never> readonly value: View.View<Option.Option<A>, ER, never>
readonly encodedValue: Lens.Lens<I, ER, EW, never, never> readonly encodedValue: Lens.Lens<I, ER, EW, never, never>
readonly issues: Subscribable.Subscribable<readonly FormIssue[], never, never> readonly issues: View.View<readonly FormIssue[], never, never>
readonly isValidating: Subscribable.Subscribable<boolean, never, never> readonly isValidating: View.View<boolean, never, never>
readonly canCommit: Subscribable.Subscribable<boolean, never, never> readonly canCommit: View.View<boolean, never, never>
readonly isCommitting: Subscribable.Subscribable<boolean, never, never> readonly isCommitting: View.View<boolean, never, never>
} }
export class FormImpl<out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never> export class FormImpl<out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never>
@@ -32,12 +32,12 @@ extends Pipeable.Class implements Form<P, A, I, ER, EW> {
constructor( constructor(
readonly path: P, readonly path: P,
readonly value: Subscribable.Subscribable<Option.Option<A>, ER, never>, readonly value: View.View<Option.Option<A>, ER, never>,
readonly encodedValue: Lens.Lens<I, ER, EW, never, never>, readonly encodedValue: Lens.Lens<I, ER, EW, never, never>,
readonly issues: Subscribable.Subscribable<readonly FormIssue[], never, never>, readonly issues: View.View<readonly FormIssue[], never, never>,
readonly isValidating: Subscribable.Subscribable<boolean, never, never>, readonly isValidating: View.View<boolean, never, never>,
readonly canCommit: Subscribable.Subscribable<boolean, never, never>, readonly canCommit: View.View<boolean, never, never>,
readonly isCommitting: Subscribable.Subscribable<boolean, never, never>, readonly isCommitting: View.View<boolean, never, never>,
) { ) {
super() super()
} }
@@ -71,9 +71,9 @@ export const focusObjectOn: {
return new FormImpl( return new FormImpl(
path, path,
Subscribable.mapOption(form.value, a => a[key]), View.mapOption(form.value, a => a[key]),
Lens.focusObjectOn(form.encodedValue, key), Lens.focusObjectOn(form.encodedValue, key),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), View.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canCommit,
form.isCommitting, form.isCommitting,
@@ -97,9 +97,9 @@ export const focusArrayAt: {
return new FormImpl( return new FormImpl(
path, path,
Subscribable.mapOptionEffect(form.value, values => Effect.fromOption(Array.get(values, index))), View.mapOptionEffect(form.value, values => Effect.fromOption(Array.get(values, index))),
Lens.focusArrayAt(form.encodedValue, index), Lens.focusArrayAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), View.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canCommit,
form.isCommitting, form.isCommitting,
@@ -123,9 +123,9 @@ export const focusTupleAt: {
return new FormImpl( return new FormImpl(
path, path,
Subscribable.mapOption(form.value, values => values[index]), View.mapOption(form.value, values => values[index]),
Lens.focusTupleAt(form.encodedValue, index), Lens.focusTupleAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), View.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canCommit,
form.isCommitting, form.isCommitting,
@@ -149,9 +149,9 @@ export const focusChunkAt: {
return new FormImpl( return new FormImpl(
path, path,
Subscribable.mapOptionEffect(form.value, values => Effect.fromOption(Chunk.get(values, index))), View.mapOptionEffect(form.value, values => Effect.fromOption(Chunk.get(values, index))),
Lens.focusChunkAt(form.encodedValue, index), Lens.focusChunkAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), View.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canCommit,
form.isCommitting, form.isCommitting,
+14 -14
View File
@@ -1,26 +1,26 @@
import { type Context, Effect, Equal, Exit, type Fiber, Option, Pipeable, Predicate, type Scope, Stream, SubscriptionRef } from "effect" import { type Context, Effect, Equal, Exit, type Fiber, Option, Pipeable, Predicate, type Scope, Stream, SubscriptionRef } from "effect"
import { AsyncResult } from "effect/unstable/reactivity" import { AsyncResult } from "effect/unstable/reactivity"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import type * as Subscribable from "./Subscribable.js" import * as View from "./View.js"
export const MutationTypeId: unique symbol = Symbol.for("@effect-fc/Mutation/Mutation") export const MutationTypeId: unique symbol = Symbol.for("@effect-fc/Mutation/Mutation")
export type MutationTypeId = typeof MutationTypeId export type MutationTypeId = typeof MutationTypeId
export interface Mutation<in out K, in out A, in out E = never, in out R = never> export interface Mutation<in out K, out A, out E = never, in out R = never>
extends Pipeable.Pipeable { extends Pipeable.Pipeable {
readonly [MutationTypeId]: MutationTypeId readonly [MutationTypeId]: MutationTypeId
readonly context: Context.Context<Scope.Scope | R> readonly context: Context.Context<Scope.Scope | R>
readonly f: (key: K) => Effect.Effect<A, E, R> readonly f: (key: K) => Effect.Effect<A, E, R>
readonly latestKey: Subscribable.Subscribable<Option.Option<K>> readonly latestKey: View.View<Option.Option<K>>
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>> readonly fiber: View.View<Option.Option<Fiber.Fiber<A, E>>>
readonly result: Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>> readonly state: View.View<AsyncResult.AsyncResult<A, E>>
readonly latestFinalResult: Subscribable.Subscribable<Option.Option<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>> readonly latestFinalResult: View.View<Option.Option<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>>
mutate(key: K): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>> mutate(key: K): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>> mutateView(key: K): Effect.Effect<View.View<AsyncResult.AsyncResult<A, E>>>
} }
export const isMutation = (u: unknown): u is Mutation<unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId) export const isMutation = (u: unknown): u is Mutation<unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId)
@@ -36,7 +36,7 @@ extends Pipeable.Class implements Mutation<K, A, E, R> {
readonly latestKey: Lens.Lens<Option.Option<K>>, readonly latestKey: Lens.Lens<Option.Option<K>>,
readonly fiber: Lens.Lens<Option.Option<Fiber.Fiber<A, E>>>, readonly fiber: Lens.Lens<Option.Option<Fiber.Fiber<A, E>>>,
readonly result: Lens.Lens<AsyncResult.AsyncResult<A, E>>, readonly state: Lens.Lens<AsyncResult.AsyncResult<A, E>>,
readonly latestFinalResult: Lens.Lens<Option.Option<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>>, readonly latestFinalResult: Lens.Lens<Option.Option<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>>,
) { ) {
super() super()
@@ -49,7 +49,7 @@ extends Pipeable.Class implements Mutation<K, A, E, R> {
Effect.provide(this.context), Effect.provide(this.context),
) )
} }
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>> { mutateView(key: K): Effect.Effect<View.View<AsyncResult.AsyncResult<A, E>>> {
return Lens.set(this.latestKey, Option.some(key)).pipe( return Lens.set(this.latestKey, Option.some(key)).pipe(
Effect.andThen(this.start(key)), Effect.andThen(this.start(key)),
Effect.tap(state => Effect.forkScoped(this.watch(state))), Effect.tap(state => Effect.forkScoped(this.watch(state))),
@@ -58,7 +58,7 @@ extends Pipeable.Class implements Mutation<K, A, E, R> {
} }
start(key: K): Effect.Effect< start(key: K): Effect.Effect<
Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>, View.View<AsyncResult.AsyncResult<A, E>>,
never, never,
Scope.Scope | R Scope.Scope | R
> { > {
@@ -114,13 +114,13 @@ extends Pipeable.Class implements Mutation<K, A, E, R> {
} }
watch( watch(
state: Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>> state: View.View<AsyncResult.AsyncResult<A, E>>
): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>> { ): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>> {
return state.get.pipe( return View.get(state).pipe(
Effect.andThen(initial => Stream.runFoldEffect( Effect.andThen(initial => Stream.runFoldEffect(
state.changes, View.changes(state),
() => initial, () => initial,
(_, result) => Effect.as(Lens.set(this.result, result), result), (_, result) => Effect.as(Lens.set(this.state, result), result),
) as Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>), ) as Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>),
Effect.tap(result => Lens.set(this.latestFinalResult, Option.some(result))), Effect.tap(result => Lens.set(this.latestFinalResult, Option.some(result))),
) )
+28 -28
View File
@@ -2,44 +2,44 @@ import { type Cause, type Context, Duration, Effect, Equal, type Equivalence, Ex
import { AsyncResult } from "effect/unstable/reactivity" import { AsyncResult } from "effect/unstable/reactivity"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import * as QueryClient from "./QueryClient.js" import * as QueryClient from "./QueryClient.js"
import * as Subscribable from "./Subscribable.js" import * as View from "./View.js"
export const QueryTypeId: unique symbol = Symbol.for("@effect-fc/Query/Query") export const QueryTypeId: unique symbol = Symbol.for("@effect-fc/Query/Query")
export type QueryTypeId = typeof QueryTypeId export type QueryTypeId = typeof QueryTypeId
export interface Query<in out K, in out A, in out E = never, in out R = never> export interface Query<in out K, out A, out E = never, in out R = never>
extends Pipeable.Pipeable { extends Pipeable.Pipeable {
readonly [QueryTypeId]: QueryTypeId readonly [QueryTypeId]: QueryTypeId
readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | R> readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | R>
readonly key: Subscribable.Subscribable<K> readonly key: View.View<K>
readonly keyEquivalence: Equivalence.Equivalence<K> readonly keyEquivalence: Equivalence.Equivalence<K>
readonly f: (key: K) => Effect.Effect<A, E, R> readonly f: (key: K) => Effect.Effect<A, E, R>
readonly staleTime: Duration.Duration readonly staleTime: Duration.Duration
readonly refreshOnWindowFocus: boolean readonly refreshOnWindowFocus: boolean
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>> readonly fiber: View.View<Option.Option<Fiber.Fiber<A, E>>>
readonly state: Subscribable.Subscribable<QueryState<K, A, E>> readonly state: View.View<QueryState<K, A, E>>
readonly latestFinalState: Subscribable.Subscribable<Option.Option<FinalQueryState<K, A, E>>> readonly latestFinalState: View.View<Option.Option<FinalQueryState<K, A, E>>>
readonly run: Effect.Effect<void> readonly run: Effect.Effect<void>
fetch(key: K): Effect.Effect<FinalQueryState<K, A, E>, Cause.NoSuchElementError> fetch(key: K): Effect.Effect<FinalQueryState<K, A, E>, Cause.NoSuchElementError>
fetchSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<QueryState<K, A, E>>, Cause.NoSuchElementError> fetchView(key: K): Effect.Effect<View.View<QueryState<K, A, E>>, Cause.NoSuchElementError>
readonly refresh: Effect.Effect<FinalQueryState<K, A, E>, Cause.NoSuchElementError> readonly refresh: Effect.Effect<FinalQueryState<K, A, E>, Cause.NoSuchElementError>
readonly refreshSubscribable: Effect.Effect<Subscribable.Subscribable<QueryState<K, A, E>>, Cause.NoSuchElementError> readonly refreshView: Effect.Effect<View.View<QueryState<K, A, E>>, Cause.NoSuchElementError>
readonly invalidateCache: Effect.Effect<void> readonly invalidateCache: Effect.Effect<void>
invalidateCacheEntry(key: K): Effect.Effect<void> invalidateCacheEntry(key: K): Effect.Effect<void>
} }
export interface QueryState<in out K, in out A, in out E = never> { export interface QueryState<out K, out A, out E = never> {
readonly key: K readonly key: K
readonly result: AsyncResult.AsyncResult<A, E> readonly result: AsyncResult.AsyncResult<A, E>
} }
export interface FinalQueryState<in out K, in out A, in out E = never> { export interface FinalQueryState<out K, out A, out E = never> {
readonly key: K readonly key: K
readonly result: AsyncResult.Success<A, E> | AsyncResult.Failure<A, E> readonly result: AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>
} }
@@ -53,7 +53,7 @@ extends Pipeable.Class implements Query<K, A, E, R> {
constructor( constructor(
readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | R>, readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | R>,
readonly key: Subscribable.Subscribable<K>, readonly key: View.View<K>,
readonly keyEquivalence: Equivalence.Equivalence<K>, readonly keyEquivalence: Equivalence.Equivalence<K>,
readonly f: (key: K) => Effect.Effect<A, E, R>, readonly f: (key: K) => Effect.Effect<A, E, R>,
@@ -94,7 +94,7 @@ extends Pipeable.Class implements Query<K, A, E, R> {
Effect.flatMap(({ BrowserStream }) => this.refreshOnWindowFocus Effect.flatMap(({ BrowserStream }) => this.refreshOnWindowFocus
? Stream.runForEach( ? Stream.runForEach(
BrowserStream.fromEventListenerWindow("focus"), BrowserStream.fromEventListenerWindow("focus"),
() => this.refreshSubscribable, () => this.refreshView,
) )
: Effect.void : Effect.void
), ),
@@ -127,8 +127,8 @@ extends Pipeable.Class implements Query<K, A, E, R> {
) )
} }
fetchSubscribable(key: K): Effect.Effect< fetchView(key: K): Effect.Effect<
Subscribable.Subscribable<QueryState<K, A, E>>, View.View<QueryState<K, A, E>>,
Cause.NoSuchElementError Cause.NoSuchElementError
> { > {
return Effect.gen({ self: this }, function*() { return Effect.gen({ self: this }, function*() {
@@ -166,8 +166,8 @@ extends Pipeable.Class implements Query<K, A, E, R> {
) )
} }
get refreshSubscribable(): Effect.Effect< get refreshView(): Effect.Effect<
Subscribable.Subscribable<QueryState<K, A, E>>, View.View<QueryState<K, A, E>>,
Cause.NoSuchElementError Cause.NoSuchElementError
> { > {
return Effect.gen({ self: this }, function*() { return Effect.gen({ self: this }, function*() {
@@ -194,7 +194,7 @@ extends Pipeable.Class implements Query<K, A, E, R> {
startCached( startCached(
previous: QueryState<K, A, E>, previous: QueryState<K, A, E>,
): Effect.Effect< ): Effect.Effect<
Subscribable.Subscribable<QueryState<K, A, E>>, View.View<QueryState<K, A, E>>,
Cause.NoSuchElementError, Cause.NoSuchElementError,
Scope.Scope | QueryClient.QueryClient | R Scope.Scope | QueryClient.QueryClient | R
> { > {
@@ -206,7 +206,7 @@ extends Pipeable.Class implements Query<K, A, E, R> {
key: previous.key, key: previous.key,
result: entry.result as AsyncResult.AsyncResult<A, E>, result: entry.result as AsyncResult.AsyncResult<A, E>,
}) })
: Effect.succeed(Subscribable.make({ : Effect.succeed(View.make({
get: Effect.succeed({ get: Effect.succeed({
key: previous.key, key: previous.key,
result: entry.result as AsyncResult.AsyncResult<A, E>, result: entry.result as AsyncResult.AsyncResult<A, E>,
@@ -226,15 +226,15 @@ extends Pipeable.Class implements Query<K, A, E, R> {
start( start(
previous: QueryState<K, A, E>, previous: QueryState<K, A, E>,
): Effect.Effect< ): Effect.Effect<
Subscribable.Subscribable<QueryState<K, A, E>>, View.View<QueryState<K, A, E>>,
never, never,
Scope.Scope | R Scope.Scope | R
> { > {
return Effect.gen({ self: this }, function*() { return Effect.gen({ self: this }, function*() {
const subscribable = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(previous)) const state = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(previous))
const fiber = yield* Effect.forkScoped(Effect.andThen( const fiber = yield* Effect.forkScoped(Effect.andThen(
Lens.update(subscribable, previous => AsyncResult.match(previous.result, { Lens.update(state, previous => AsyncResult.match(previous.result, {
onInitial: () => ({ onInitial: () => ({
key: previous.key, key: previous.key,
result: AsyncResult.initial(true), result: AsyncResult.initial(true),
@@ -255,7 +255,7 @@ extends Pipeable.Class implements Query<K, A, E, R> {
})), })),
Effect.onExit(this.f(previous.key), exit => Lens.update( Effect.onExit(this.f(previous.key), exit => Lens.update(
subscribable, state,
previous => Exit.match(exit, { previous => Exit.match(exit, {
onSuccess: v => ({ onSuccess: v => ({
key: previous.key, key: previous.key,
@@ -295,17 +295,17 @@ extends Pipeable.Class implements Query<K, A, E, R> {
)) ))
yield* Lens.set(this.fiber, Option.some(fiber)) yield* Lens.set(this.fiber, Option.some(fiber))
return subscribable return state
}) })
} }
watch( watch(
subscribable: Subscribable.Subscribable<QueryState<K, A, E>> view: View.View<QueryState<K, A, E>>
): Effect.Effect<FinalQueryState<K, A, E>, never, QueryClient.QueryClient> { ): Effect.Effect<FinalQueryState<K, A, E>, never, QueryClient.QueryClient> {
return Effect.gen({ self: this }, function*() { return Effect.gen({ self: this }, function*() {
const initial = yield* subscribable.get const initial = yield* View.get(view)
const final = yield* Stream.runFoldEffect( const final = yield* Stream.runFoldEffect(
subscribable.changes, View.changes(view),
() => initial, () => initial,
(_, state) => Effect.as(Lens.set(this.state, state), state), (_, state) => Effect.as(Lens.set(this.state, state), state),
) as Effect.Effect<FinalQueryState<K, A, E>> ) as Effect.Effect<FinalQueryState<K, A, E>>
@@ -367,7 +367,7 @@ extends Pipeable.Class implements Query<K, A, E, R> {
export declare namespace make { export declare namespace make {
export interface Options<K, A, E = never, R = never> { export interface Options<K, A, E = never, R = never> {
readonly key: Subscribable.Subscribable<K>, readonly key: View.View<K>,
readonly keyEquivalence?: Equivalence.Equivalence<K>, readonly keyEquivalence?: Equivalence.Equivalence<K>,
readonly f: (key: K) => Effect.Effect<A, E, R> readonly f: (key: K) => Effect.Effect<A, E, R>
@@ -396,7 +396,7 @@ export const make = Effect.fnUntraced(function* <K, A, E = never, R = never>(
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, E>>())), Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, E>>())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make<QueryState<K, A, E>>({ Lens.fromSubscriptionRef(yield* SubscriptionRef.make<QueryState<K, A, E>>({
key: yield* options.key.get, key: yield* View.get(options.key),
result: AsyncResult.initial(false), result: AsyncResult.initial(false),
})), })),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<FinalQueryState<K, A, E>>())), Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<FinalQueryState<K, A, E>>())),
+2 -2
View File
@@ -1,7 +1,7 @@
import { type Cause, Context, DateTime, Duration, Effect, Equal, Equivalence, Hash, HashMap, type Option, Pipeable, Predicate, Schedule, type Scope, Semaphore, SubscriptionRef } from "effect" import { type Cause, Context, DateTime, Duration, Effect, Equal, Equivalence, Hash, HashMap, type Option, Pipeable, Predicate, Schedule, type Scope, Semaphore, SubscriptionRef } from "effect"
import type { AsyncResult } from "effect/unstable/reactivity" import type { AsyncResult } from "effect/unstable/reactivity"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import type * as Subscribable from "./Subscribable.js" import type * as View from "./View.js"
export const QueryClientServiceTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientService") export const QueryClientServiceTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientService")
@@ -10,7 +10,7 @@ export type QueryClientServiceTypeId = typeof QueryClientServiceTypeId
export interface QueryClientService extends Pipeable.Pipeable { export interface QueryClientService extends Pipeable.Pipeable {
readonly [QueryClientServiceTypeId]: QueryClientServiceTypeId readonly [QueryClientServiceTypeId]: QueryClientServiceTypeId
readonly cache: Subscribable.Subscribable<HashMap.HashMap<QueryClientCacheKey, QueryClientCacheEntry>> readonly cache: View.View<HashMap.HashMap<QueryClientCacheKey, QueryClientCacheEntry>>
readonly cacheGcTime: Duration.Duration readonly cacheGcTime: Duration.Duration
readonly defaultStaleTime: Duration.Duration readonly defaultStaleTime: Duration.Duration
readonly defaultRefreshOnWindowFocus: boolean readonly defaultRefreshOnWindowFocus: boolean
+4 -3
View File
@@ -1,5 +1,6 @@
import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, Pipeable, Predicate, pipe, type Scope, SubscriptionRef } from "effect" import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, Pipeable, Predicate, pipe, type Scope, SubscriptionRef } from "effect"
import { Lens, Subscribable } from "effect-lens" import { Lens } from "effect-lens"
import type { View } from "effect-lens/View"
export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result") export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result")
@@ -207,7 +208,7 @@ export const unsafeForkEffect = Effect.fnUntraced(function* <A, E, R, P = never>
effect: Effect.Effect<A, E, R>, effect: Effect.Effect<A, E, R>,
options?: unsafeForkEffect.Options<NoInfer<A>, NoInfer<E>, P>, options?: unsafeForkEffect.Options<NoInfer<A>, NoInfer<E>, P>,
): Effect.fn.Return< ): Effect.fn.Return<
readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>], readonly [result: View<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>],
never, never,
Scope.Scope | unsafeForkEffect.OutputContext<R, P> Scope.Scope | unsafeForkEffect.OutputContext<R, P>
> { > {
@@ -242,7 +243,7 @@ export const forkEffect: {
effect: Effect.Effect<A, E, forkEffect.InputContext<R, NoInfer<P>>>, effect: Effect.Effect<A, E, forkEffect.InputContext<R, NoInfer<P>>>,
options?: forkEffect.Options<NoInfer<A>, NoInfer<E>, P>, options?: forkEffect.Options<NoInfer<A>, NoInfer<E>, P>,
): Effect.Effect< ): Effect.Effect<
readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>], readonly [result: View<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>],
never, never,
Scope.Scope | forkEffect.OutputContext<R, P> Scope.Scope | forkEffect.OutputContext<R, P>
> >
+10 -10
View File
@@ -18,7 +18,7 @@ import * as Form from "./Form.js"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import * as Mutation from "./Mutation.js" import * as Mutation from "./Mutation.js"
import * as Result from "./Result.js" import * as Result from "./Result.js"
import * as Subscribable from "./Subscribable.js" import * as View from "./View.js"
type FormSchema<A, I, R> = Schema.Top & { type FormSchema<A, I, R> = Schema.Top & {
@@ -39,7 +39,7 @@ extends Form.Form<readonly [], A, I, never, never> {
readonly [value: A, form: SubmittableForm<A, I, R, unknown, unknown, unknown>], readonly [value: A, form: SubmittableForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP MA, ME, MR, MP
> >
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, never, never> readonly validationFiber: View.View<Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, never, never>
readonly run: Effect.Effect<void> readonly run: Effect.Effect<void>
readonly submit: Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementError> readonly submit: Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementError>
} }
@@ -50,9 +50,9 @@ extends Pipeable.Class implements SubmittableForm<A, I, R, MA, ME, MR, MP> {
readonly [SubmittableFormTypeId]: SubmittableFormTypeId = SubmittableFormTypeId readonly [SubmittableFormTypeId]: SubmittableFormTypeId = SubmittableFormTypeId
readonly path = [] as const readonly path = [] as const
readonly encodedValue: Lens.Lens<I, never, never, never, never> readonly encodedValue: Lens.Lens<I, never, never, never, never>
readonly isValidating: Subscribable.Subscribable<boolean, never, never> readonly isValidating: View.View<boolean, never, never>
readonly canCommit: Subscribable.Subscribable<boolean, never, never> readonly canCommit: View.View<boolean, never, never>
readonly isCommitting: Subscribable.Subscribable<boolean, never, never> readonly isCommitting: View.View<boolean, never, never>
constructor( constructor(
readonly schema: FormSchema<A, I, R>, readonly schema: FormSchema<A, I, R>,
@@ -77,26 +77,26 @@ extends Pipeable.Class implements SubmittableForm<A, I, R, MA, ME, MR, MP> {
), ),
lock: Lens.asLensImpl(internalEncodedValue).lock, lock: Lens.asLensImpl(internalEncodedValue).lock,
}) })
this.isValidating = Subscribable.map(validationFiber, Option.isSome) this.isValidating = View.map(validationFiber, Option.isSome)
const commitState = Subscribable.zipLatestAll( const commitState = View.zipLatestAll(
value as any, value as any,
issues as any, issues as any,
validationFiber as any, validationFiber as any,
mutation.result as any, mutation.result as any,
) as unknown as Subscribable.Subscribable<readonly [ ) as unknown as View.View<readonly [
Option.Option<A>, Option.Option<A>,
readonly Form.FormIssue[], readonly Form.FormIssue[],
Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>,
Result.Result<MA, ME, MP>, Result.Result<MA, ME, MP>,
]> ]>
this.canCommit = Subscribable.map( this.canCommit = View.map(
commitState, commitState,
([current, currentIssues, fiber, result]: readonly [Option.Option<A>, readonly Form.FormIssue[], Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, Result.Result<MA, ME, MP>]) => Option.isSome(current) ([current, currentIssues, fiber, result]: readonly [Option.Option<A>, readonly Form.FormIssue[], Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, Result.Result<MA, ME, MP>]) => Option.isSome(current)
&& currentIssues.length === 0 && currentIssues.length === 0
&& Option.isNone(fiber) && Option.isNone(fiber)
&& !(Result.isRunning(result) || Result.hasRefreshingFlag(result)), && !(Result.isRunning(result) || Result.hasRefreshingFlag(result)),
) )
this.isCommitting = Subscribable.map( this.isCommitting = View.map(
mutation.result, mutation.result,
result => Result.isRunning(result) || Result.hasRefreshingFlag(result), result => Result.isRunning(result) || Result.hasRefreshingFlag(result),
) )
@@ -1,53 +0,0 @@
import { Effect, Equivalence, Stream } from "effect"
import { Subscribable } from "effect-lens"
import * as React from "react"
import * as Component from "./Component.js"
export * from "effect-lens/Subscribable"
export const zipLatestAll = <const T extends readonly Subscribable.Subscribable<any, any, any>[]>(
...elements: T
): Subscribable.Subscribable<
[T[number]] extends [never]
? never
: { [K in keyof T]: T[K] extends Subscribable.Subscribable<infer A, infer _E, infer _R> ? A : never },
[T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer E, infer _R> ? E : never,
[T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer _E, infer R> ? R : never
> => Subscribable.make({
get: Effect.all(elements.map(v => v.get)),
changes: Stream.zipLatestAll(...elements.map(v => v.changes)),
}) as any
export declare namespace useAll {
export type Success<T extends readonly Subscribable.Subscribable<any, any, any>[]> = [T[number]] extends [never]
? never
: { [K in keyof T]: T[K] extends Subscribable.Subscribable<infer A, infer _E, infer _R> ? A : never }
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useAll = Effect.fnUntraced(function* <const T extends readonly Subscribable.Subscribable<any, any, any>[]>(
elements: T,
options?: useAll.Options<useAll.Success<NoInfer<T>>>,
): Effect.fn.Return<
useAll.Success<T>,
[T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer E, infer _R> ? E : never,
[T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer _E, infer R> ? R : never
> {
const [reactStateValue, setReactStateValue] = React.useState(
yield* Component.useOnMount(() => Effect.all(elements.map(v => v.get)))
)
yield* Component.useReactEffect(() => Stream.zipLatestAll(...elements.map(ref => ref.changes)).pipe(
Stream.changesWith((options?.equivalence as Equivalence.Equivalence<any[]> | undefined) ?? Equivalence.Array(Equivalence.strictEqual())),
Stream.runForEach(v =>
Effect.sync(() => setReactStateValue(v))
),
Effect.forkScoped,
), elements)
return reactStateValue as any
})
@@ -17,7 +17,7 @@ import {
} from "effect" } from "effect"
import * as Form from "./Form.js" import * as Form from "./Form.js"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import * as Subscribable from "./Subscribable.js" import * as View from "./View.js"
type FormSchema<A, I, R> = Schema.Top & { type FormSchema<A, I, R> = Schema.Top & {
@@ -43,7 +43,7 @@ export interface SynchronizedForm<
readonly schema: FormSchema<A, I, R> readonly schema: FormSchema<A, I, R>
readonly context: Context.Context<Scope.Scope | R | TRR | TRW> readonly context: Context.Context<Scope.Scope | R | TRR | TRW>
readonly target: Lens.Lens<A, TER, TEW, TRR, TRW> readonly target: Lens.Lens<A, TER, TEW, TRR, TRW>
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, never, never> readonly validationFiber: View.View<Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, never, never>
readonly run: Effect.Effect<void, TER> readonly run: Effect.Effect<void, TER>
} }
@@ -59,10 +59,10 @@ export class SynchronizedFormImpl<
readonly [Form.FormTypeId]: Form.FormTypeId = Form.FormTypeId readonly [Form.FormTypeId]: Form.FormTypeId = Form.FormTypeId
readonly [SynchronizedFormTypeId]: SynchronizedFormTypeId = SynchronizedFormTypeId readonly [SynchronizedFormTypeId]: SynchronizedFormTypeId = SynchronizedFormTypeId
readonly path = [] as const readonly path = [] as const
readonly value: Subscribable.Subscribable<Option.Option<A>, TER, never> readonly value: View.View<Option.Option<A>, TER, never>
readonly encodedValue: Lens.Lens<I, TER, TER | TEW, never, never> readonly encodedValue: Lens.Lens<I, TER, TER | TEW, never, never>
readonly isValidating: Subscribable.Subscribable<boolean, never, never> readonly isValidating: View.View<boolean, never, never>
readonly canCommit: Subscribable.Subscribable<boolean, never, never> readonly canCommit: View.View<boolean, never, never>
constructor( constructor(
readonly schema: FormSchema<A, I, R>, readonly schema: FormSchema<A, I, R>,
@@ -75,7 +75,7 @@ export class SynchronizedFormImpl<
readonly runSemaphore: Semaphore.Semaphore, readonly runSemaphore: Semaphore.Semaphore,
) { ) {
super() super()
this.value = Subscribable.make({ this.value = View.make({
get: Effect.provide(Effect.map(target.get, Option.some), context), get: Effect.provide(Effect.map(target.get, Option.some), context),
changes: Stream.provideContext( changes: Stream.provideContext(
target.changes.pipe( target.changes.pipe(
@@ -94,13 +94,13 @@ export class SynchronizedFormImpl<
), ),
lock: Lens.asLensImpl(internalEncodedValue).lock, lock: Lens.asLensImpl(internalEncodedValue).lock,
}) as unknown as Lens.Lens<I, TER, TER | TEW, never, never> }) as unknown as Lens.Lens<I, TER, TER | TEW, never, never>
this.isValidating = Subscribable.map(validationFiber, Option.isSome) this.isValidating = View.map(validationFiber, Option.isSome)
const commitState = Subscribable.zipLatestAll(issues as any, validationFiber as any, isCommitting as any) as unknown as Subscribable.Subscribable<readonly [ const commitState = View.zipLatestAll(issues as any, validationFiber as any, isCommitting as any) as unknown as View.View<readonly [
readonly Form.FormIssue[], readonly Form.FormIssue[],
Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>, Option.Option<Fiber.Fiber<A, SchemaIssue.Issue>>,
boolean, boolean,
]> ]>
this.canCommit = Subscribable.map( this.canCommit = View.map(
commitState, commitState,
([currentIssues, fiber, committing]) => currentIssues.length === 0 && Option.isNone(fiber) && !committing, ([currentIssues, fiber, committing]) => currentIssues.length === 0 && Option.isNone(fiber) && !committing,
) )
+40
View File
@@ -0,0 +1,40 @@
import { Effect, Equivalence, Stream } from "effect"
import { View } from "effect-lens"
import * as React from "react"
import * as Component from "./Component.js"
export * from "effect-lens/View"
export declare namespace useAll {
export type Success<T extends readonly View.View<any, any, any>[]> = [T[number]] extends [never]
? never
: { [K in keyof T]: T[K] extends View.View<infer A, infer _E, infer _R> ? A : never }
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useAll = Effect.fnUntraced(function* <const T extends readonly View.View<any, any, any>[]>(
elements: T,
options?: useAll.Options<useAll.Success<NoInfer<T>>>,
): Effect.fn.Return<
useAll.Success<T>,
[T[number]] extends [never] ? never : T[number] extends View.View<infer _A, infer E, infer _R> ? E : never,
[T[number]] extends [never] ? never : T[number] extends View.View<infer _A, infer _E, infer R> ? R : never
> {
const [reactStateValue, setReactStateValue] = React.useState(
yield* Component.useOnMount(() => Effect.all(elements.map(View.get)))
)
yield* Component.useReactEffect(() => View.changes(View.zipLatestAll(...elements)).pipe(
Stream.changesWith((options?.equivalence as Equivalence.Equivalence<any[]> | undefined) ?? Equivalence.Array(Equivalence.strictEqual())),
Stream.runForEach(v =>
Effect.sync(() => setReactStateValue(v))
),
Effect.forkScoped,
), elements)
return reactStateValue as any
})
+1 -1
View File
@@ -13,5 +13,5 @@ export * as Result from "./Result.js"
export * as SetStateAction from "./SetStateAction.js" export * as SetStateAction from "./SetStateAction.js"
export * as Stream from "./Stream.js" export * as Stream from "./Stream.js"
export * as SubmittableForm from "./SubmittableForm.js" export * as SubmittableForm from "./SubmittableForm.js"
export * as Subscribable from "./Subscribable.js"
export * as SynchronizedForm from "./SynchronizedForm.js" export * as SynchronizedForm from "./SynchronizedForm.js"
export * as View from "./View.js"
@@ -1,10 +1,10 @@
import { render, screen, waitFor } from "@testing-library/react" import { render, screen, waitFor } from "@testing-library/react"
import { Effect, Fiber, Layer, Stream, SubscriptionRef } from "effect" import { Effect, Layer, SubscriptionRef } from "effect"
import { Lens } from "effect-lens" import { Lens } from "effect-lens"
import { describe, expect, it } from "vitest" import { describe, expect, it } from "vitest"
import * as Component from "../src/Component.js" import * as Component from "../src/Component.js"
import * as ReactRuntime from "../src/ReactRuntime.js" import * as ReactRuntime from "../src/ReactRuntime.js"
import * as Subscribable from "../src/Subscribable.js" import * as View from "../src/View.js"
const makeRuntime = async () => { const makeRuntime = async () => {
@@ -18,42 +18,7 @@ const makeRuntime = async () => {
} }
} }
describe("Subscribable", () => { describe("View", () => {
it("zipLatestAll reads current values from all inputs", async () => {
const leftRef = await Effect.runPromise(SubscriptionRef.make(1))
const rightRef = await Effect.runPromise(SubscriptionRef.make("a"))
const left = Lens.fromSubscriptionRef(leftRef)
const right = Lens.fromSubscriptionRef(rightRef)
const zipped = Subscribable.zipLatestAll(left, right)
expect(await Effect.runPromise(zipped.get)).toEqual([1, "a"])
})
it("zipLatestAll emits updates when any input changes", async () => {
const leftRef = await Effect.runPromise(SubscriptionRef.make(1))
const rightRef = await Effect.runPromise(SubscriptionRef.make("a"))
const left = Lens.fromSubscriptionRef(leftRef)
const right = Lens.fromSubscriptionRef(rightRef)
const zipped = Subscribable.zipLatestAll(left, right)
const values: Array<readonly [number, string]> = []
const collector = Effect.runFork(Effect.scoped(zipped.changes.pipe(
Stream.runForEach(value => Effect.sync(() => {
values.push(value as readonly [number, string])
})),
)))
await Effect.runPromise(Lens.set(left, 2))
await waitFor(() => expect(values).toContainEqual([2, "a"]))
await Effect.runPromise(Lens.set(right, "b"))
await waitFor(() => expect(values).toContainEqual([2, "b"]))
await Effect.runPromise(Fiber.interrupt(collector))
})
it("useAll returns the latest values and rerenders when any input changes", async () => { it("useAll returns the latest values and rerenders when any input changes", async () => {
const { runtime, effectRuntime, dispose } = await makeRuntime() const { runtime, effectRuntime, dispose } = await makeRuntime()
const countRef = await Effect.runPromise(SubscriptionRef.make(1)) const countRef = await Effect.runPromise(SubscriptionRef.make(1))
@@ -61,8 +26,8 @@ describe("Subscribable", () => {
const count = Lens.fromSubscriptionRef(countRef) const count = Lens.fromSubscriptionRef(countRef)
const label = Lens.fromSubscriptionRef(labelRef) const label = Lens.fromSubscriptionRef(labelRef)
const Probe = Component.makeUntraced("SubscribableUseAllProbe")(function*() { const Probe = Component.makeUntraced("ViewUseAllProbe")(function*() {
const [currentCount, currentLabel] = yield* Subscribable.useAll([count, label]) const [currentCount, currentLabel] = yield* View.useAll([count, label])
return <div>{`${currentCount}:${currentLabel}`}</div> return <div>{`${currentCount}:${currentLabel}`}</div>
}).pipe( }).pipe(
@@ -94,8 +59,8 @@ describe("Subscribable", () => {
const item = Lens.fromSubscriptionRef(itemRef) const item = Lens.fromSubscriptionRef(itemRef)
const flag = Lens.fromSubscriptionRef(flagRef) const flag = Lens.fromSubscriptionRef(flagRef)
const Probe = Component.makeUntraced("SubscribableUseAllEquivalenceProbe")(function*() { const Probe = Component.makeUntraced("ViewUseAllEquivalenceProbe")(function*() {
const [currentItem, currentFlag] = yield* Subscribable.useAll([item, flag], { const [currentItem, currentFlag] = yield* View.useAll([item, flag], {
equivalence: ([selfItem, selfFlag], [thatItem, thatFlag]) => equivalence: ([selfItem, selfFlag], [thatItem, thatFlag]) =>
selfItem.id === thatItem.id && selfFlag === thatFlag, selfItem.id === thatItem.id && selfFlag === thatFlag,
}) })
+2 -2
View File
@@ -1,5 +1,5 @@
import { Effect, Layer, SubscriptionRef } from "effect" import { Effect, Layer, SubscriptionRef } from "effect"
import { Component, Lens, ReactRuntime, Subscribable } from "effect-fc-next" import { Component, Lens, ReactRuntime, View } from "effect-fc-next"
import { StrictMode } from "react" import { StrictMode } from "react"
import { createRoot } from "react-dom/client" import { createRoot } from "react-dom/client"
import "./index.css" import "./index.css"
@@ -10,7 +10,7 @@ const Counter = Component.make("Counter")(function*() {
SubscriptionRef.make(0), SubscriptionRef.make(0),
Lens.fromSubscriptionRef, Lens.fromSubscriptionRef,
)) ))
const [value] = yield* Subscribable.useAll([count]) const [value] = yield* View.useAll([count])
const increment = yield* Component.useCallbackSync( const increment = yield* Component.useCallbackSync(
() => Lens.update(count, n => n + 1), () => Lens.update(count, n => n + 1),
[count], [count],