@playfast/reform 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boundary/boundary.d.ts +72 -0
- package/dist/boundary/boundary.d.ts.map +1 -0
- package/dist/boundary/boundary.js +83 -0
- package/dist/boundary/boundary.js.map +1 -0
- package/dist/calc/asyncCalc.d.ts +91 -0
- package/dist/calc/asyncCalc.d.ts.map +1 -0
- package/dist/calc/asyncCalc.js +95 -0
- package/dist/calc/asyncCalc.js.map +1 -0
- package/{src/calc/asyncData.ts → dist/calc/asyncData.d.ts} +22 -53
- package/dist/calc/asyncData.d.ts.map +1 -0
- package/dist/calc/asyncData.js +34 -0
- package/dist/calc/asyncData.js.map +1 -0
- package/dist/calc/calc.d.ts +57 -0
- package/dist/calc/calc.d.ts.map +1 -0
- package/dist/calc/calc.js +58 -0
- package/dist/calc/calc.js.map +1 -0
- package/dist/calc/calcFamily.d.ts +57 -0
- package/dist/calc/calcFamily.d.ts.map +1 -0
- package/dist/calc/calcFamily.js +124 -0
- package/dist/calc/calcFamily.js.map +1 -0
- package/dist/calc/compose.d.ts +16 -0
- package/dist/calc/compose.d.ts.map +1 -0
- package/dist/calc/compose.js +7 -0
- package/dist/calc/compose.js.map +1 -0
- package/dist/channel/channel.d.ts +115 -0
- package/dist/channel/channel.d.ts.map +1 -0
- package/dist/channel/channel.js +138 -0
- package/dist/channel/channel.js.map +1 -0
- package/dist/compose/composition.d.ts +72 -0
- package/dist/compose/composition.d.ts.map +1 -0
- package/dist/compose/composition.js +46 -0
- package/dist/compose/composition.js.map +1 -0
- package/{src/compose/host.ts → dist/compose/host.d.ts} +8 -6
- package/dist/compose/host.d.ts.map +1 -0
- package/dist/compose/host.js +6 -0
- package/dist/compose/host.js.map +1 -0
- package/dist/compose/props.d.ts +13 -0
- package/dist/compose/props.d.ts.map +1 -0
- package/{src/compose/props.ts → dist/compose/props.js} +5 -3
- package/dist/compose/props.js.map +1 -0
- package/dist/compose/provide.d.ts +22 -0
- package/dist/compose/provide.d.ts.map +1 -0
- package/dist/compose/provide.js +28 -0
- package/dist/compose/provide.js.map +1 -0
- package/{src/compose/slot.ts → dist/compose/slot.d.ts} +19 -36
- package/dist/compose/slot.d.ts.map +1 -0
- package/dist/compose/slot.js +23 -0
- package/dist/compose/slot.js.map +1 -0
- package/dist/compose/ui.d.ts +135 -0
- package/dist/compose/ui.d.ts.map +1 -0
- package/dist/compose/ui.js +63 -0
- package/dist/compose/ui.js.map +1 -0
- package/dist/definition/definition.d.ts +33 -0
- package/dist/definition/definition.d.ts.map +1 -0
- package/dist/definition/definition.js +42 -0
- package/dist/definition/definition.js.map +1 -0
- package/dist/event/event.d.ts +33 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +30 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/eventGroup.d.ts +9 -0
- package/dist/event/eventGroup.d.ts.map +1 -0
- package/dist/event/eventGroup.js +4 -0
- package/dist/event/eventGroup.js.map +1 -0
- package/{src/feature/feature.ts → dist/feature/feature.d.ts} +92 -178
- package/dist/feature/feature.d.ts.map +1 -0
- package/dist/feature/feature.js +98 -0
- package/dist/feature/feature.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/{src/internal/capture.ts → dist/internal/capture.d.ts} +11 -13
- package/dist/internal/capture.d.ts.map +1 -0
- package/dist/internal/capture.js +12 -0
- package/dist/internal/capture.js.map +1 -0
- package/{src/internal/ctx.ts → dist/internal/ctx.d.ts} +4 -8
- package/dist/internal/ctx.d.ts.map +1 -0
- package/dist/internal/ctx.js +2 -0
- package/dist/internal/ctx.js.map +1 -0
- package/dist/internal/errors.d.ts +89 -0
- package/dist/internal/errors.d.ts.map +1 -0
- package/dist/internal/errors.js +102 -0
- package/dist/internal/errors.js.map +1 -0
- package/dist/internal/inspect.d.ts +17 -0
- package/dist/internal/inspect.d.ts.map +1 -0
- package/dist/internal/inspect.js +32 -0
- package/dist/internal/inspect.js.map +1 -0
- package/dist/internal/queryDriver.d.ts +65 -0
- package/dist/internal/queryDriver.d.ts.map +1 -0
- package/dist/internal/queryDriver.js +134 -0
- package/dist/internal/queryDriver.js.map +1 -0
- package/dist/internal/reuse.d.ts +10 -0
- package/dist/internal/reuse.d.ts.map +1 -0
- package/dist/internal/reuse.js +68 -0
- package/dist/internal/reuse.js.map +1 -0
- package/dist/internal/scheduler.d.ts +47 -0
- package/dist/internal/scheduler.d.ts.map +1 -0
- package/dist/internal/scheduler.js +70 -0
- package/dist/internal/scheduler.js.map +1 -0
- package/{src/internal/seeds.ts → dist/internal/seeds.d.ts} +3 -6
- package/dist/internal/seeds.d.ts.map +1 -0
- package/dist/internal/seeds.js +17 -0
- package/dist/internal/seeds.js.map +1 -0
- package/dist/internal/sources.d.ts +39 -0
- package/dist/internal/sources.d.ts.map +1 -0
- package/dist/internal/sources.js +59 -0
- package/dist/internal/sources.js.map +1 -0
- package/dist/internal/stateRegistry.d.ts +2 -0
- package/dist/internal/stateRegistry.d.ts.map +1 -0
- package/dist/internal/stateRegistry.js +28 -0
- package/dist/internal/stateRegistry.js.map +1 -0
- package/dist/internal/store.d.ts +47 -0
- package/dist/internal/store.d.ts.map +1 -0
- package/dist/internal/store.js +73 -0
- package/dist/internal/store.js.map +1 -0
- package/{src/internal/track.ts → dist/internal/track.d.ts} +11 -15
- package/dist/internal/track.d.ts.map +1 -0
- package/dist/internal/track.js +19 -0
- package/dist/internal/track.js.map +1 -0
- package/dist/procedure/procedure.d.ts +40 -0
- package/dist/procedure/procedure.d.ts.map +1 -0
- package/dist/procedure/procedure.js +50 -0
- package/dist/procedure/procedure.js.map +1 -0
- package/dist/reducer/reducer.d.ts +44 -0
- package/dist/reducer/reducer.d.ts.map +1 -0
- package/dist/reducer/reducer.js +63 -0
- package/dist/reducer/reducer.js.map +1 -0
- package/dist/remote/remoteState.d.ts +119 -0
- package/dist/remote/remoteState.d.ts.map +1 -0
- package/dist/remote/remoteState.js +270 -0
- package/dist/remote/remoteState.js.map +1 -0
- package/dist/runtime/bus.d.ts +27 -0
- package/dist/runtime/bus.d.ts.map +1 -0
- package/{src/runtime/bus.ts → dist/runtime/bus.js} +8 -23
- package/dist/runtime/bus.js.map +1 -0
- package/dist/runtime/loop.d.ts +45 -0
- package/dist/runtime/loop.d.ts.map +1 -0
- package/dist/runtime/loop.js +117 -0
- package/dist/runtime/loop.js.map +1 -0
- package/dist/scene/scene.d.ts +44 -0
- package/dist/scene/scene.d.ts.map +1 -0
- package/dist/scene/scene.js +31 -0
- package/dist/scene/scene.js.map +1 -0
- package/dist/state/state.d.ts +37 -0
- package/dist/state/state.d.ts.map +1 -0
- package/dist/state/state.js +46 -0
- package/dist/state/state.js.map +1 -0
- package/dist/state/stateFamily.d.ts +79 -0
- package/dist/state/stateFamily.d.ts.map +1 -0
- package/dist/state/stateFamily.js +99 -0
- package/dist/state/stateFamily.js.map +1 -0
- package/dist/state/stateGroup.d.ts +50 -0
- package/dist/state/stateGroup.d.ts.map +1 -0
- package/dist/state/stateGroup.js +54 -0
- package/dist/state/stateGroup.js.map +1 -0
- package/{src/state/token.ts → dist/state/token.d.ts} +13 -21
- package/dist/state/token.d.ts.map +1 -0
- package/dist/state/token.js +20 -0
- package/dist/state/token.js.map +1 -0
- package/dist/synced/syncedStore.d.ts +51 -0
- package/dist/synced/syncedStore.d.ts.map +1 -0
- package/dist/synced/syncedStore.js +43 -0
- package/dist/synced/syncedStore.js.map +1 -0
- package/{src/ui/node.ts → dist/ui/node.d.ts} +3 -3
- package/dist/ui/node.d.ts.map +1 -0
- package/dist/ui/node.js +2 -0
- package/dist/ui/node.js.map +1 -0
- package/{src/ui/trigger.ts → dist/ui/trigger.d.ts} +2 -1
- package/dist/ui/trigger.d.ts.map +1 -0
- package/dist/ui/trigger.js +2 -0
- package/dist/ui/trigger.js.map +1 -0
- package/dist/wire/tree.d.ts +66 -0
- package/dist/wire/tree.d.ts.map +1 -0
- package/dist/wire/tree.js +60 -0
- package/dist/wire/tree.js.map +1 -0
- package/dist/wire/triggers.d.ts +44 -0
- package/dist/wire/triggers.d.ts.map +1 -0
- package/dist/wire/triggers.js +31 -0
- package/dist/wire/triggers.js.map +1 -0
- package/package.json +32 -11
- package/src/boundary/boundary.ts +0 -192
- package/src/calc/asyncCalc.ts +0 -267
- package/src/calc/calc.ts +0 -120
- package/src/calc/calcFamily.ts +0 -194
- package/src/calc/compose.ts +0 -69
- package/src/channel/channel.ts +0 -265
- package/src/compose/composition.ts +0 -112
- package/src/compose/provide.ts +0 -70
- package/src/compose/ui.ts +0 -97
- package/src/definition/definition.ts +0 -74
- package/src/event/event.ts +0 -65
- package/src/event/eventGroup.ts +0 -14
- package/src/index.ts +0 -140
- package/src/internal/errors.ts +0 -69
- package/src/internal/inspect.ts +0 -34
- package/src/internal/queryDriver.ts +0 -247
- package/src/internal/reuse.ts +0 -73
- package/src/internal/scheduler.ts +0 -91
- package/src/internal/sources.ts +0 -104
- package/src/internal/store.ts +0 -105
- package/src/procedure/procedure.ts +0 -89
- package/src/reducer/reducer.ts +0 -137
- package/src/remote/remoteState.ts +0 -571
- package/src/runtime/loop.ts +0 -169
- package/src/scene/scene.ts +0 -76
- package/src/state/state.ts +0 -91
- package/src/state/stateFamily.ts +0 -171
- package/src/state/stateGroup.ts +0 -78
package/src/calc/asyncCalc.ts
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { Context, Effect, Layer, Option, Schema } from 'effect'
|
|
2
|
-
import { type Manifest, yieldableClass } from '../definition/definition'
|
|
3
|
-
import { type AnyEvent } from '../event/event'
|
|
4
|
-
import {
|
|
5
|
-
type InputsObject,
|
|
6
|
-
type InputStores,
|
|
7
|
-
type InvalidateBy,
|
|
8
|
-
} from '../internal/sources'
|
|
9
|
-
import {
|
|
10
|
-
bumpRevision,
|
|
11
|
-
type GatedOf,
|
|
12
|
-
gatedFlag,
|
|
13
|
-
makeQueryDriver,
|
|
14
|
-
RevisionSchema,
|
|
15
|
-
revisionZero,
|
|
16
|
-
} from '../internal/queryDriver'
|
|
17
|
-
import { type Store } from '../internal/store'
|
|
18
|
-
import { readTracked } from '../internal/track'
|
|
19
|
-
import * as Reducer from '../reducer/reducer'
|
|
20
|
-
import { Reducers } from '../runtime/loop'
|
|
21
|
-
import * as State from '../state/state'
|
|
22
|
-
import { type AnySource } from '../state/token'
|
|
23
|
-
import { type AsyncData, narrowStore } from './asyncData'
|
|
24
|
-
|
|
25
|
-
// `AsyncCalc` is `Calc`'s async sibling: a derived store whose value is the
|
|
26
|
-
// lifecycle of an Effect (a query), recomputed reactively from its inputs — the
|
|
27
|
-
// React-Query model, but inside the reform graph and without dispatching events.
|
|
28
|
-
// Definition (`make`) is reflectable data; implementation (`live`) is the effect.
|
|
29
|
-
// The reactive machinery lives in `internal/queryDriver` (shared with
|
|
30
|
-
// `RemoteState`); this module owns the definition shape and the `invalidateOn`
|
|
31
|
-
// sugar.
|
|
32
|
-
|
|
33
|
-
export interface AsyncCalcManifest<N extends string, A, E> extends Manifest {
|
|
34
|
-
readonly kind: 'AsyncCalc'
|
|
35
|
-
readonly name: N
|
|
36
|
-
readonly output: Schema.Schema<A, any>
|
|
37
|
-
readonly error?: Schema.Schema<E, any>
|
|
38
|
-
/** Whether the query can be disabled (drives the `Idle` arm). */
|
|
39
|
-
readonly gated: boolean
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface AsyncCalcClass<
|
|
43
|
-
out N extends string,
|
|
44
|
-
out Inputs extends ReadonlyArray<AnySource>,
|
|
45
|
-
in out A,
|
|
46
|
-
in out E,
|
|
47
|
-
in out Gated extends boolean,
|
|
48
|
-
> extends Effect.Effect<AsyncData<A, E, Gated>, never, Store<AsyncData<A, E, Gated>>> {
|
|
49
|
-
new (): {}
|
|
50
|
-
readonly manifest: AsyncCalcManifest<N, A, E>
|
|
51
|
-
readonly store: Context.Tag<Store<AsyncData<A, E, Gated>>, Store<AsyncData<A, E, Gated>>>
|
|
52
|
-
/** The calc's name, so it doubles as a `Source` input to another (async) calc. */
|
|
53
|
-
readonly name: N
|
|
54
|
-
/** Marker data read by `AsyncCalc.live`. */
|
|
55
|
-
readonly inputs: Inputs
|
|
56
|
-
/** Runtime mirror of `Gated`: whether `disabled` is honored / `Idle` can occur. */
|
|
57
|
-
readonly gated: Gated
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface AsyncCalcConfig<Inputs extends ReadonlyArray<AnySource>, A, E, AlwaysOn extends boolean> {
|
|
61
|
-
readonly inputs: Inputs
|
|
62
|
-
/** Schema of the `Success` value. */
|
|
63
|
-
readonly output: Schema.Schema<A, any>
|
|
64
|
-
/** Schema of the failure. Omitted ⇒ the query is infallible and there is no `Error` arm. */
|
|
65
|
-
readonly error?: Schema.Schema<E, any>
|
|
66
|
-
/** `true` ⇒ the query is always on: no `Idle` arm and `disabled` is rejected on `.live`. */
|
|
67
|
-
readonly alwaysOn?: AlwaysOn
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* The `.live` config: the effect plus the reactive logic. `disabled` is only
|
|
72
|
-
* accepted for a gateable query (no `alwaysOn`); `invalidateBy` is the same
|
|
73
|
-
* `queryKey` projection `Calc` uses.
|
|
74
|
-
*/
|
|
75
|
-
export type AsyncCalcLive<
|
|
76
|
-
Inputs extends ReadonlyArray<AnySource>,
|
|
77
|
-
A,
|
|
78
|
-
E,
|
|
79
|
-
Gated extends boolean,
|
|
80
|
-
R,
|
|
81
|
-
> = {
|
|
82
|
-
readonly query: (inputs: InputsObject<Inputs>) => Effect.Effect<A, E, R>
|
|
83
|
-
readonly invalidateBy?: InvalidateBy<Inputs>
|
|
84
|
-
/**
|
|
85
|
-
* Refetch conciliation. `'switch'` (default): a new key cancels the in-flight
|
|
86
|
-
* run (latest-wins). `'trailing'`: the in-flight run completes; key changes
|
|
87
|
-
* that arrive meanwhile conflate to the latest, and exactly ONE trailing
|
|
88
|
-
* refetch runs after it settles — a burst of N invalidations during one
|
|
89
|
-
* flight costs 2 fetches, not N cancel-restarts.
|
|
90
|
-
*/
|
|
91
|
-
readonly coalesce?: 'switch' | 'trailing'
|
|
92
|
-
/**
|
|
93
|
-
* Structural sharing for `Success` values: reconcile each refetch result
|
|
94
|
-
* against the previous one, substituting previous nodes wherever value
|
|
95
|
-
* equality holds — refetches returning mostly-identical data keep unchanged
|
|
96
|
-
* subtree identities, so downstream memo boundaries skip them. Same pass as
|
|
97
|
-
* `Calc`'s `reuse`; opt-in (one O(result) walk per settle).
|
|
98
|
-
*/
|
|
99
|
-
readonly reuse?: boolean
|
|
100
|
-
} & (Gated extends true
|
|
101
|
-
? { readonly disabled?: (inputs: InputsObject<Inputs>) => boolean }
|
|
102
|
-
: { readonly disabled?: never })
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Define an async derived value. `output`/`error` schemas and `alwaysOn` shape
|
|
106
|
-
* the value type, so `yield* MyQuery` is typed to exactly the arms that can
|
|
107
|
-
* occur. The query effect itself is supplied by `AsyncCalc.live`.
|
|
108
|
-
*/
|
|
109
|
-
export const make = <
|
|
110
|
-
const N extends string,
|
|
111
|
-
const Inputs extends ReadonlyArray<AnySource>,
|
|
112
|
-
A,
|
|
113
|
-
E = never,
|
|
114
|
-
const AlwaysOn extends boolean = false,
|
|
115
|
-
>(
|
|
116
|
-
name: N,
|
|
117
|
-
config: AsyncCalcConfig<Inputs, A, E, AlwaysOn>,
|
|
118
|
-
): AsyncCalcClass<N, Inputs, A, E, GatedOf<AlwaysOn>> => {
|
|
119
|
-
const store = Context.GenericTag<Store<AsyncData<A, E, GatedOf<AlwaysOn>>>>(
|
|
120
|
-
`reform/asyncCalc/${name}`,
|
|
121
|
-
)
|
|
122
|
-
// Runtime mirror of the type-level `Gated`, typed as its literal in one place.
|
|
123
|
-
const gated = gatedFlag(config.alwaysOn)
|
|
124
|
-
const manifest: AsyncCalcManifest<N, A, E> = {
|
|
125
|
-
kind: 'AsyncCalc',
|
|
126
|
-
name,
|
|
127
|
-
output: config.output,
|
|
128
|
-
gated,
|
|
129
|
-
...(config.error !== undefined ? { error: config.error } : {}),
|
|
130
|
-
}
|
|
131
|
-
const read = Effect.flatMap(store, readTracked)
|
|
132
|
-
return yieldableClass(read, {
|
|
133
|
-
manifest,
|
|
134
|
-
store,
|
|
135
|
-
name,
|
|
136
|
-
inputs: config.inputs,
|
|
137
|
-
gated,
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Wire the query and its reactive logic. The query runs on the runtime (its `R`
|
|
143
|
-
* — RPC clients, etc. — is captured from the layer context). A change to an
|
|
144
|
-
* input re-runs it latest-wins (a new run cancels the in-flight one; or, with
|
|
145
|
-
* `coalesce: 'trailing'`, lets it finish and runs one trailing refetch); while a
|
|
146
|
-
* re-run is in flight the last `Success`/`Error` is kept with `refetching: true`.
|
|
147
|
-
* Re-runs are driven by inputs / `invalidateBy` / `disabled` — and, with
|
|
148
|
-
* `invalidateOn`, by listed events: pure sugar that generates a hidden revision
|
|
149
|
-
* state + reducer (the sole writer) and appends the revision to the calc's key,
|
|
150
|
-
* so an event-driven refetch is still just key movement under `Equal.equals`.
|
|
151
|
-
* The hidden pair stays internal — the user's `query`/`invalidateBy` see exactly
|
|
152
|
-
* the declared inputs — and costs the layer one extra requirement: the
|
|
153
|
-
* `Reducers` registry (the `Engine`).
|
|
154
|
-
*/
|
|
155
|
-
export function live<
|
|
156
|
-
N extends string,
|
|
157
|
-
Inputs extends ReadonlyArray<AnySource>,
|
|
158
|
-
A,
|
|
159
|
-
E,
|
|
160
|
-
Gated extends boolean,
|
|
161
|
-
R,
|
|
162
|
-
>(
|
|
163
|
-
calc: AsyncCalcClass<N, Inputs, A, E, Gated>,
|
|
164
|
-
config: AsyncCalcLive<Inputs, A, E, Gated, R> & {
|
|
165
|
-
readonly invalidateOn: ReadonlyArray<AnyEvent>
|
|
166
|
-
},
|
|
167
|
-
): Layer.Layer<Store<AsyncData<A, E, Gated>>, never, InputStores<Inputs> | R | Reducers>
|
|
168
|
-
export function live<
|
|
169
|
-
N extends string,
|
|
170
|
-
Inputs extends ReadonlyArray<AnySource>,
|
|
171
|
-
A,
|
|
172
|
-
E,
|
|
173
|
-
Gated extends boolean,
|
|
174
|
-
R,
|
|
175
|
-
>(
|
|
176
|
-
calc: AsyncCalcClass<N, Inputs, A, E, Gated>,
|
|
177
|
-
config: AsyncCalcLive<Inputs, A, E, Gated, R>,
|
|
178
|
-
): Layer.Layer<Store<AsyncData<A, E, Gated>>, never, InputStores<Inputs> | R>
|
|
179
|
-
export function live<
|
|
180
|
-
N extends string,
|
|
181
|
-
Inputs extends ReadonlyArray<AnySource>,
|
|
182
|
-
A,
|
|
183
|
-
E,
|
|
184
|
-
Gated extends boolean,
|
|
185
|
-
R,
|
|
186
|
-
>(
|
|
187
|
-
calc: AsyncCalcClass<N, Inputs, A, E, Gated>,
|
|
188
|
-
config: AsyncCalcLive<Inputs, A, E, Gated, R> & {
|
|
189
|
-
readonly invalidateOn?: ReadonlyArray<AnyEvent>
|
|
190
|
-
},
|
|
191
|
-
// The wide-R implementation signature behind precise overloads — the same
|
|
192
|
-
// seam `Reducer.live` uses; both overload returns are assignable (RIn is
|
|
193
|
-
// covariant), so no value is ever cast.
|
|
194
|
-
): Layer.Layer<Store<AsyncData<A, E, Gated>>, never, any> {
|
|
195
|
-
// The hidden revision pair, built once per `live` call. Namespacing the state
|
|
196
|
-
// by the calc's name (`reform/state/${calcName}/invalidateOn`) keeps the tag
|
|
197
|
-
// out of any plausible user namespace; the duplicate-reducer check is
|
|
198
|
-
// warning-only and calc names are unique by convention.
|
|
199
|
-
const revisionState =
|
|
200
|
-
config.invalidateOn === undefined
|
|
201
|
-
? undefined
|
|
202
|
-
: State.make(`${calc.manifest.name}/invalidateOn`, RevisionSchema)
|
|
203
|
-
|
|
204
|
-
// Scoped so the source subscriptions and the driver fiber are released when
|
|
205
|
-
// the layer's scope closes (each proof/test builds and disposes its runtime).
|
|
206
|
-
const driver = Layer.scoped(
|
|
207
|
-
calc.store,
|
|
208
|
-
Effect.gen(function* () {
|
|
209
|
-
// `disabled` is rejected at the type level for non-gated calcs (erased to
|
|
210
|
-
// `never` there); read it through a loose view for the runtime.
|
|
211
|
-
const cfg = config as {
|
|
212
|
-
readonly query: (inputs: InputsObject<Inputs>) => Effect.Effect<A, E, R>
|
|
213
|
-
readonly invalidateBy?: InvalidateBy<Inputs>
|
|
214
|
-
readonly disabled?: (inputs: InputsObject<Inputs>) => boolean
|
|
215
|
-
readonly coalesce?: 'switch' | 'trailing'
|
|
216
|
-
readonly reuse?: boolean
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// The hidden revision store, read requirement-free (`serviceOption`): the
|
|
220
|
-
// assembly below always provides it alongside this driver, and feeding it
|
|
221
|
-
// through `extraKey` (not `wireSources`) keeps it out of the snapshot —
|
|
222
|
-
// the user's `query` and `invalidateBy` receive exactly
|
|
223
|
-
// `InputsObject<Inputs>`, with no hidden property to leak into
|
|
224
|
-
// spread-into-RPC payloads.
|
|
225
|
-
const revision =
|
|
226
|
-
revisionState === undefined
|
|
227
|
-
? undefined
|
|
228
|
-
: Option.getOrUndefined(yield* Effect.serviceOption(revisionState.store))
|
|
229
|
-
const driver = yield* makeQueryDriver({
|
|
230
|
-
name: calc.manifest.name,
|
|
231
|
-
label: 'AsyncCalc',
|
|
232
|
-
gated: calc.gated,
|
|
233
|
-
inputs: calc.inputs,
|
|
234
|
-
query: cfg.query,
|
|
235
|
-
invalidateBy: cfg.invalidateBy,
|
|
236
|
-
disabled: cfg.disabled,
|
|
237
|
-
coalesce: cfg.coalesce,
|
|
238
|
-
reuse: cfg.reuse,
|
|
239
|
-
extraKey:
|
|
240
|
-
revision === undefined
|
|
241
|
-
? undefined
|
|
242
|
-
: {
|
|
243
|
-
read: () => revision.getSnapshot(),
|
|
244
|
-
subscribe: (listener) => revision.subscribe(listener),
|
|
245
|
-
},
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// The internal store is the full union; narrow to the definition's arms.
|
|
249
|
-
return narrowStore<A, E, Gated>(driver.store)
|
|
250
|
-
}),
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
if (config.invalidateOn === undefined || revisionState === undefined) return driver
|
|
254
|
-
// The hidden reducer: an ordinary `Reducer` folding every listed event to
|
|
255
|
-
// n + 1 — the revision's sole writer, registered/unregistered with this
|
|
256
|
-
// layer's scope like any user reducer. `Layer.provide` builds the hidden
|
|
257
|
-
// state once and feeds the same store to both the driver and the reducer,
|
|
258
|
-
// while the outer context (user inputs, the `Reducers` registry) passes
|
|
259
|
-
// through untouched.
|
|
260
|
-
const revisionReducer = Reducer.make(`${calc.manifest.name}/invalidateOn`, {
|
|
261
|
-
states: [revisionState],
|
|
262
|
-
events: config.invalidateOn,
|
|
263
|
-
})
|
|
264
|
-
return Layer.merge(driver, Reducer.live(revisionReducer, bumpRevision)).pipe(
|
|
265
|
-
Layer.provide(State.live(revisionState, revisionZero)),
|
|
266
|
-
)
|
|
267
|
-
}
|
package/src/calc/calc.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { Context, Effect, Layer, MutableRef, type Schema } from 'effect'
|
|
2
|
-
import { type Manifest, yieldableClass } from '../definition/definition'
|
|
3
|
-
import {
|
|
4
|
-
type InputsObject,
|
|
5
|
-
type InputStores,
|
|
6
|
-
type InvalidateBy,
|
|
7
|
-
sameKey,
|
|
8
|
-
wireSources,
|
|
9
|
-
} from '../internal/sources'
|
|
10
|
-
import { resolveScheduler } from '../internal/scheduler'
|
|
11
|
-
import { reuse } from '../internal/reuse'
|
|
12
|
-
import { makeDerivedStore, type Store } from '../internal/store'
|
|
13
|
-
import { readTracked } from '../internal/track'
|
|
14
|
-
import { type AnySource } from '../state/token'
|
|
15
|
-
|
|
16
|
-
// Re-exported so `Calc.InputsObject` / `Calc.InvalidateBy` keep their home here
|
|
17
|
-
// even though the reactive plumbing now lives in `internal/sources`.
|
|
18
|
-
export { type InputsObject, type InputStores, type InvalidateBy } from '../internal/sources'
|
|
19
|
-
|
|
20
|
-
export interface CalcOptions<Inputs extends ReadonlyArray<AnySource>> {
|
|
21
|
-
/**
|
|
22
|
-
* Custom invalidation: recompute only when the projected key changes by value
|
|
23
|
-
* equality (React-Query's `queryKey`). Omitted ⇒ recompute when any input
|
|
24
|
-
* value changes. Shared with `AsyncCalc`.
|
|
25
|
-
*/
|
|
26
|
-
readonly invalidateBy?: InvalidateBy<Inputs>
|
|
27
|
-
/**
|
|
28
|
-
* Structural sharing: reconcile each recomputed output against the previous
|
|
29
|
-
* one, substituting previous nodes wherever value equality holds — so
|
|
30
|
-
* identities only move where values moved and downstream memo boundaries
|
|
31
|
-
* (React subtrees) skip unchanged regions. Referentially transparent (the
|
|
32
|
-
* result is value-equal to the fresh output); costs one O(output) walk per
|
|
33
|
-
* recompute, so opt in for large projections.
|
|
34
|
-
*/
|
|
35
|
-
readonly reuse?: boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface CalcManifest<N extends string, Out> extends Manifest {
|
|
39
|
-
readonly kind: 'Calc'
|
|
40
|
-
readonly name: N
|
|
41
|
-
readonly output: Schema.Schema<Out, any>
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface CalcClass<out N extends string, out Inputs extends ReadonlyArray<AnySource>, in out Out>
|
|
45
|
-
extends Effect.Effect<Out, never, Store<Out>> {
|
|
46
|
-
new (): {}
|
|
47
|
-
readonly manifest: CalcManifest<N, Out>
|
|
48
|
-
readonly store: Context.Tag<Store<Out>, Store<Out>>
|
|
49
|
-
/** The calc's name, so it doubles as a `Source` input to another calc. */
|
|
50
|
-
readonly name: N
|
|
51
|
-
/** Marker data read by `Calc.live`. */
|
|
52
|
-
readonly inputs: Inputs
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface CalcConfig<Inputs extends ReadonlyArray<AnySource>, Out> {
|
|
56
|
-
readonly inputs: Inputs
|
|
57
|
-
readonly output: Schema.Schema<Out, any>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* A derived, memoized value over explicit inputs (state members, other calcs, or
|
|
62
|
-
* async calcs). The *definition* fixes the inputs and the output schema; the
|
|
63
|
-
* *implementation* is the pure projection. Recomputes only when an input changes;
|
|
64
|
-
* `yield* Calc` reads the memoized value.
|
|
65
|
-
*/
|
|
66
|
-
export const make = <const N extends string, const Inputs extends ReadonlyArray<AnySource>, Out>(
|
|
67
|
-
name: N,
|
|
68
|
-
config: CalcConfig<Inputs, Out>,
|
|
69
|
-
): CalcClass<N, Inputs, Out> => {
|
|
70
|
-
const store = Context.GenericTag<Store<Out>, Store<Out>>(`reform/calc/${name}`)
|
|
71
|
-
const manifest: CalcManifest<N, Out> = { kind: 'Calc', name, output: config.output }
|
|
72
|
-
const read = Effect.flatMap(store, readTracked)
|
|
73
|
-
return yieldableClass(read, { manifest, store, name, inputs: config.inputs })
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Wire the pure projection for a calc. Recomputes only when its invalidation key
|
|
78
|
-
* changes (by default every input value; override with `invalidateBy`), memoized
|
|
79
|
-
* on that key; `yield* Calc` reads the memoized value. The derived store reuses
|
|
80
|
-
* the shared coalescing scheduler, so a recompute that yields an equal value
|
|
81
|
-
* wakes no subscriber.
|
|
82
|
-
*/
|
|
83
|
-
export const live = <N extends string, Inputs extends ReadonlyArray<AnySource>, Out>(
|
|
84
|
-
calc: CalcClass<N, Inputs, Out>,
|
|
85
|
-
compute: (inputs: InputsObject<Inputs>) => Out,
|
|
86
|
-
options: CalcOptions<Inputs> = {},
|
|
87
|
-
): Layer.Layer<Store<Out>, never, InputStores<Inputs>> =>
|
|
88
|
-
// Scoped so the source subscriptions are released when the layer's scope
|
|
89
|
-
// closes (each proof/test builds and disposes its own runtime).
|
|
90
|
-
Layer.scoped(
|
|
91
|
-
calc.store,
|
|
92
|
-
Effect.gen(function* () {
|
|
93
|
-
const scheduler = yield* resolveScheduler
|
|
94
|
-
const sources = yield* wireSources(calc.inputs, options.invalidateBy)
|
|
95
|
-
|
|
96
|
-
// Memo cell: key and output captured together so reads are always
|
|
97
|
-
// consistent (no separate output that could lag the key it was computed for).
|
|
98
|
-
const memo = MutableRef.make<
|
|
99
|
-
{ readonly key: ReadonlyArray<unknown>; readonly output: Out } | undefined
|
|
100
|
-
>(undefined)
|
|
101
|
-
const recompute = (): Out => {
|
|
102
|
-
const args = sources.snapshot()
|
|
103
|
-
const key = sources.keyOf(args)
|
|
104
|
-
const prev = MutableRef.get(memo)
|
|
105
|
-
if (prev !== undefined && sameKey(key, prev.key)) return prev.output
|
|
106
|
-
const fresh = compute(args)
|
|
107
|
-
// With `reuse`, a recompute that lands value-equal to the previous
|
|
108
|
-
// output returns the previous reference — the derived store's Equal
|
|
109
|
-
// gate then wakes no subscriber at all.
|
|
110
|
-
const output =
|
|
111
|
-
options.reuse === true && prev !== undefined ? reuse(prev.output, fresh) : fresh
|
|
112
|
-
MutableRef.set(memo, { key, output })
|
|
113
|
-
return output
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const derived = makeDerivedStore(recompute, sources.subscribe, scheduler)
|
|
117
|
-
yield* Effect.addFinalizer(() => Effect.sync(derived.unsubscribe))
|
|
118
|
-
return derived.store
|
|
119
|
-
}),
|
|
120
|
-
)
|
package/src/calc/calcFamily.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { Context, Effect, Layer, MutableRef, type Schema } from 'effect'
|
|
2
|
-
import { type Manifest, definitionClass } from '../definition/definition'
|
|
3
|
-
import {
|
|
4
|
-
type InputsObject,
|
|
5
|
-
type InputStores,
|
|
6
|
-
type InvalidateBy,
|
|
7
|
-
sameKey,
|
|
8
|
-
wireSources,
|
|
9
|
-
} from '../internal/sources'
|
|
10
|
-
import { resolveScheduler } from '../internal/scheduler'
|
|
11
|
-
import { makeDerivedStore, type Store } from '../internal/store'
|
|
12
|
-
import { readTracked } from '../internal/track'
|
|
13
|
-
import { type FamilyOptions, type FamilyStore } from '../state/stateFamily'
|
|
14
|
-
import { type AnySource } from '../state/token'
|
|
15
|
-
|
|
16
|
-
// `CalcFamily` is `Calc`'s keyed sibling, modeled on `StateFamily`: one derived,
|
|
17
|
-
// memoized store per key over a SHARED set of inputs. Where a monolithic calc
|
|
18
|
-
// rebuilds its whole output on any change (waking every consumer), a family
|
|
19
|
-
// member recomputes lazily and notifies only when ITS slice of the projection
|
|
20
|
-
// moves (the derived store's Equal gate) — per-row/per-group UI subscribes per
|
|
21
|
-
// key and unrelated changes never wake it. Members are pure projections; a
|
|
22
|
-
// Reducer cannot target a CalcFamily (its config requires a `StateFamily`
|
|
23
|
-
// manifest), so calc purity holds structurally.
|
|
24
|
-
|
|
25
|
-
export interface CalcFamilyManifest<N extends string, K, Out> extends Manifest {
|
|
26
|
-
readonly kind: 'CalcFamily'
|
|
27
|
-
readonly name: N
|
|
28
|
-
readonly key: Schema.Schema<K, any>
|
|
29
|
-
readonly output: Schema.Schema<Out, any>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface CalcFamilyClass<
|
|
33
|
-
out N extends string,
|
|
34
|
-
out Inputs extends ReadonlyArray<AnySource>,
|
|
35
|
-
in out K,
|
|
36
|
-
in out Out,
|
|
37
|
-
> {
|
|
38
|
-
/** Instance carries the key phantom so `Family['Key']` resolves in key types. */
|
|
39
|
-
new (): { readonly Key: K }
|
|
40
|
-
readonly manifest: CalcFamilyManifest<N, K, Out>
|
|
41
|
-
readonly store: Context.Tag<FamilyStore<K, Out>, FamilyStore<K, Out>>
|
|
42
|
-
/** Marker data read by `CalcFamily.live` and the editor's graph builder. */
|
|
43
|
-
readonly inputs: Inputs
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface CalcFamilyConfig<Inputs extends ReadonlyArray<AnySource>, K, Out> {
|
|
47
|
-
readonly key: Schema.Schema<K, any>
|
|
48
|
-
readonly inputs: Inputs
|
|
49
|
-
readonly output: Schema.Schema<Out, any>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Define a keyed derived value: per-key memoized projections over shared
|
|
54
|
-
* inputs. The *definition* fixes the key/output schemas and the inputs; the
|
|
55
|
-
* *implementation* (the per-key projection) is supplied by `CalcFamily.live`.
|
|
56
|
-
*/
|
|
57
|
-
export const make = <const N extends string, const Inputs extends ReadonlyArray<AnySource>, K, Out>(
|
|
58
|
-
name: N,
|
|
59
|
-
config: CalcFamilyConfig<Inputs, K, Out>,
|
|
60
|
-
): CalcFamilyClass<N, Inputs, K, Out> => {
|
|
61
|
-
const store = Context.GenericTag<FamilyStore<K, Out>, FamilyStore<K, Out>>(
|
|
62
|
-
`reform/calcFamily/${name}`,
|
|
63
|
-
)
|
|
64
|
-
const manifest: CalcFamilyManifest<N, K, Out> = {
|
|
65
|
-
kind: 'CalcFamily',
|
|
66
|
-
name,
|
|
67
|
-
key: config.key,
|
|
68
|
-
output: config.output,
|
|
69
|
-
}
|
|
70
|
-
return definitionClass<CalcFamilyClass<N, Inputs, K, Out>>({
|
|
71
|
-
manifest,
|
|
72
|
-
store,
|
|
73
|
-
inputs: config.inputs,
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Read one member by key — `CalcFamily.read(GroupProjection, id)`. Inside a
|
|
79
|
-
* render it subscribes exactly that member's store (unrelated members never
|
|
80
|
-
* wake the subtree); elsewhere it just snapshots — the same one-read-API
|
|
81
|
-
* contract as `StateFamily.read`.
|
|
82
|
-
*/
|
|
83
|
-
export const read = <N extends string, Inputs extends ReadonlyArray<AnySource>, K, Out>(
|
|
84
|
-
family: CalcFamilyClass<N, Inputs, K, Out>,
|
|
85
|
-
key: K,
|
|
86
|
-
): Effect.Effect<Out, never, FamilyStore<K, Out>> =>
|
|
87
|
-
Effect.flatMap(family.store, (fs) => readTracked(fs.at(key)))
|
|
88
|
-
|
|
89
|
-
export interface CalcFamilyLiveOptions<Inputs extends ReadonlyArray<AnySource>>
|
|
90
|
-
extends FamilyOptions {
|
|
91
|
-
/**
|
|
92
|
-
* Family-wide invalidation key over the SHARED inputs (the member key plays
|
|
93
|
-
* no part — it is fixed per member). Same contract as `Calc`'s.
|
|
94
|
-
*/
|
|
95
|
-
readonly invalidateBy?: InvalidateBy<Inputs>
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Wire the per-key projection. `compute` is curried — `(key) => (inputs) =>
|
|
100
|
-
* Out` — so per-key construction (closing over key-derived constants) runs once
|
|
101
|
-
* per member while the inner projection runs per recompute. Each member is a
|
|
102
|
-
* lazy derived store over the shared wired sources: memoized on the family's
|
|
103
|
-
* invalidation key, notifying only when its own output moves. Unlike a
|
|
104
|
-
* `StateFamily` entry, a member holds an upstream subscription, so every
|
|
105
|
-
* removal path (forget / clear / eviction / the layer finalizer) releases it.
|
|
106
|
-
*/
|
|
107
|
-
export const live = <N extends string, Inputs extends ReadonlyArray<AnySource>, K, Out>(
|
|
108
|
-
family: CalcFamilyClass<N, Inputs, K, Out>,
|
|
109
|
-
compute: (key: K) => (inputs: InputsObject<Inputs>) => Out,
|
|
110
|
-
options: CalcFamilyLiveOptions<Inputs> = {},
|
|
111
|
-
): Layer.Layer<FamilyStore<K, Out>, never, InputStores<Inputs>> =>
|
|
112
|
-
Layer.scoped(
|
|
113
|
-
family.store,
|
|
114
|
-
Effect.gen(function* () {
|
|
115
|
-
const scheduler = yield* resolveScheduler
|
|
116
|
-
// Wired ONCE for the whole family — members share the sensing surface.
|
|
117
|
-
const sources = yield* wireSources(family.inputs, options.invalidateBy)
|
|
118
|
-
const entries = new Map<K, { readonly store: Store<Out>; readonly unsubscribe: () => void }>()
|
|
119
|
-
const evictWhenUnused = options.evictWhenUnused === true
|
|
120
|
-
// Live subscriber count per key — maintained only when eviction is on.
|
|
121
|
-
const subscribers = new Map<K, number>()
|
|
122
|
-
|
|
123
|
-
const dropEntry = (key: K): void => {
|
|
124
|
-
const entry = entries.get(key)
|
|
125
|
-
if (entry !== undefined) entry.unsubscribe()
|
|
126
|
-
entries.delete(key)
|
|
127
|
-
subscribers.delete(key)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// One member: the same memo block as `Calc.live`, with the key closed over.
|
|
131
|
-
const createMember = (key: K): { readonly store: Store<Out>; readonly unsubscribe: () => void } => {
|
|
132
|
-
const body = compute(key)
|
|
133
|
-
const memo = MutableRef.make<
|
|
134
|
-
{ readonly key: ReadonlyArray<unknown>; readonly output: Out } | undefined
|
|
135
|
-
>(undefined)
|
|
136
|
-
const recompute = (): Out => {
|
|
137
|
-
const args = sources.snapshot()
|
|
138
|
-
const memoKey = sources.keyOf(args)
|
|
139
|
-
const prev = MutableRef.get(memo)
|
|
140
|
-
if (prev !== undefined && sameKey(memoKey, prev.key)) return prev.output
|
|
141
|
-
const output = body(args)
|
|
142
|
-
MutableRef.set(memo, { key: memoKey, output })
|
|
143
|
-
return output
|
|
144
|
-
}
|
|
145
|
-
return makeDerivedStore(recompute, sources.subscribe, scheduler)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// The `StateFamily` ref-count idiom: evict on the next microtask once a
|
|
149
|
-
// key falls idle (a same-commit re-subscribe cancels it) — here eviction
|
|
150
|
-
// also releases the member's upstream subscription via `dropEntry`.
|
|
151
|
-
const refCounted = (key: K, store: Store<Out>): Store<Out> => ({
|
|
152
|
-
...store,
|
|
153
|
-
subscribe: (listener) => {
|
|
154
|
-
subscribers.set(key, (subscribers.get(key) ?? 0) + 1)
|
|
155
|
-
const off = store.subscribe(listener)
|
|
156
|
-
const released = { done: false }
|
|
157
|
-
return () => {
|
|
158
|
-
if (released.done) return
|
|
159
|
-
released.done = true
|
|
160
|
-
off()
|
|
161
|
-
const remaining = (subscribers.get(key) ?? 1) - 1
|
|
162
|
-
if (remaining > 0) {
|
|
163
|
-
subscribers.set(key, remaining)
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
subscribers.delete(key)
|
|
167
|
-
queueMicrotask(() => {
|
|
168
|
-
if ((subscribers.get(key) ?? 0) === 0) dropEntry(key)
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
const familyStore: FamilyStore<K, Out> = {
|
|
175
|
-
at: (key) => {
|
|
176
|
-
const existing = entries.get(key)
|
|
177
|
-
if (existing !== undefined) return existing.store
|
|
178
|
-
const member = createMember(key)
|
|
179
|
-
const created = evictWhenUnused
|
|
180
|
-
? { store: refCounted(key, member.store), unsubscribe: member.unsubscribe }
|
|
181
|
-
: member
|
|
182
|
-
entries.set(key, created)
|
|
183
|
-
return created.store
|
|
184
|
-
},
|
|
185
|
-
forget: dropEntry,
|
|
186
|
-
clear: () => {
|
|
187
|
-
for (const key of [...entries.keys()]) dropEntry(key)
|
|
188
|
-
},
|
|
189
|
-
size: () => entries.size,
|
|
190
|
-
}
|
|
191
|
-
yield* Effect.addFinalizer(() => Effect.sync(() => familyStore.clear()))
|
|
192
|
-
return familyStore
|
|
193
|
-
}),
|
|
194
|
-
)
|
package/src/calc/compose.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Layer } from 'effect'
|
|
2
|
-
|
|
3
|
-
// Wiring a calc-on-calc chain (`FilteredAgents` ← `AllAgents` ← `AgentsQuery`)
|
|
4
|
-
// is the one calc-graph shape that trips people up: each upstream calc's store
|
|
5
|
-
// must be *provided into* the downstream live, in dependency order. A flat
|
|
6
|
-
// `Layer.mergeAll(FilteredLive, AllLive, QueryLive)` does NOT do this — `mergeAll`
|
|
7
|
-
// merges outputs but never provides members to one another, so the upstream
|
|
8
|
-
// calc stores stay as unmet `Store<…>` requirements (the TS2322 the migration
|
|
9
|
-
// hit). The correct shape is `leaf.pipe(provideMerge(u1), provideMerge(u2), …)`;
|
|
10
|
-
// `composeCalcs` is that fold under one name so the right thing is the easy thing.
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Compose a calc-dependency chain: provide each upstream calc's `.live` layer
|
|
14
|
-
* into the downstream one, nearest dependency first. The result exposes every
|
|
15
|
-
* calc store in the chain and requires only the chain's *external* inputs (the
|
|
16
|
-
* base state stores) — never the intermediate calc stores.
|
|
17
|
-
*
|
|
18
|
-
* Order is leaf-first, then its upstreams in dependency order:
|
|
19
|
-
* `composeCalcs(FilteredLive, AllLive, QueryLive)` ≡
|
|
20
|
-
* `FilteredLive.pipe(Layer.provideMerge(AllLive), Layer.provideMerge(QueryLive))`.
|
|
21
|
-
* For chains deeper than three upstreams, nest `composeCalcs`.
|
|
22
|
-
*/
|
|
23
|
-
export function composeCalcs<LOut, LErr, LIn, AOut, AErr, AIn>(
|
|
24
|
-
leaf: Layer.Layer<LOut, LErr, LIn>,
|
|
25
|
-
upstream: Layer.Layer<AOut, AErr, AIn>,
|
|
26
|
-
): Layer.Layer<LOut | AOut, LErr | AErr, AIn | Exclude<LIn, AOut>>
|
|
27
|
-
export function composeCalcs<LOut, LErr, LIn, AOut, AErr, AIn, BOut, BErr, BIn>(
|
|
28
|
-
leaf: Layer.Layer<LOut, LErr, LIn>,
|
|
29
|
-
upstream1: Layer.Layer<AOut, AErr, AIn>,
|
|
30
|
-
upstream2: Layer.Layer<BOut, BErr, BIn>,
|
|
31
|
-
): Layer.Layer<
|
|
32
|
-
LOut | AOut | BOut,
|
|
33
|
-
LErr | AErr | BErr,
|
|
34
|
-
BIn | Exclude<AIn | Exclude<LIn, AOut>, BOut>
|
|
35
|
-
>
|
|
36
|
-
export function composeCalcs<
|
|
37
|
-
LOut,
|
|
38
|
-
LErr,
|
|
39
|
-
LIn,
|
|
40
|
-
AOut,
|
|
41
|
-
AErr,
|
|
42
|
-
AIn,
|
|
43
|
-
BOut,
|
|
44
|
-
BErr,
|
|
45
|
-
BIn,
|
|
46
|
-
COut,
|
|
47
|
-
CErr,
|
|
48
|
-
CIn,
|
|
49
|
-
>(
|
|
50
|
-
leaf: Layer.Layer<LOut, LErr, LIn>,
|
|
51
|
-
upstream1: Layer.Layer<AOut, AErr, AIn>,
|
|
52
|
-
upstream2: Layer.Layer<BOut, BErr, BIn>,
|
|
53
|
-
upstream3: Layer.Layer<COut, CErr, CIn>,
|
|
54
|
-
): Layer.Layer<
|
|
55
|
-
LOut | AOut | BOut | COut,
|
|
56
|
-
LErr | AErr | BErr | CErr,
|
|
57
|
-
CIn | Exclude<BIn | Exclude<AIn | Exclude<LIn, AOut>, BOut>, COut>
|
|
58
|
-
>
|
|
59
|
-
export function composeCalcs(
|
|
60
|
-
leaf: Layer.Layer<unknown, unknown, unknown>,
|
|
61
|
-
...upstream: ReadonlyArray<Layer.Layer<unknown, unknown, unknown>>
|
|
62
|
-
): Layer.Layer<unknown, unknown, unknown> {
|
|
63
|
-
// Fold leaf-first: each upstream is provided into the accumulated downstream,
|
|
64
|
-
// so dependency order is exactly the argument order.
|
|
65
|
-
return upstream.reduce<Layer.Layer<unknown, unknown, unknown>>(
|
|
66
|
-
(downstream, up) => Layer.provideMerge(downstream, up),
|
|
67
|
-
leaf,
|
|
68
|
-
)
|
|
69
|
-
}
|