@playfast/reform 0.0.1
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/README.md +64 -0
- package/dist/cjs/boundary/boundary.js +86 -0
- package/dist/cjs/calc/asyncCalc.js +128 -0
- package/dist/cjs/calc/asyncData.js +37 -0
- package/dist/cjs/calc/calc.js +58 -0
- package/dist/cjs/calc/calcFamily.js +127 -0
- package/dist/cjs/channel/channel.js +142 -0
- package/dist/cjs/compose/composition.js +50 -0
- package/dist/cjs/compose/host.js +8 -0
- package/dist/cjs/compose/props.js +14 -0
- package/dist/cjs/compose/provide.js +30 -0
- package/dist/cjs/compose/slot.js +27 -0
- package/dist/cjs/compose/ui.js +61 -0
- package/dist/cjs/definition/definition.js +46 -0
- package/dist/cjs/event/event.js +36 -0
- package/dist/cjs/event/eventGroup.js +7 -0
- package/dist/cjs/feature/feature.js +102 -0
- package/dist/cjs/index.js +116 -0
- package/dist/cjs/internal/capture.js +14 -0
- package/dist/cjs/internal/ctx.js +2 -0
- package/dist/cjs/internal/errors.js +62 -0
- package/dist/cjs/internal/inspect.js +36 -0
- package/dist/cjs/internal/queryDriver.js +138 -0
- package/dist/cjs/internal/reuse.js +71 -0
- package/dist/cjs/internal/scheduler.js +73 -0
- package/dist/cjs/internal/seeds.js +19 -0
- package/dist/cjs/internal/sources.js +61 -0
- package/dist/cjs/internal/store.js +77 -0
- package/dist/cjs/internal/track.js +22 -0
- package/dist/cjs/package.json +4 -0
- package/dist/cjs/procedure/procedure.js +52 -0
- package/dist/cjs/reducer/reducer.js +64 -0
- package/dist/cjs/remote/remoteState.js +307 -0
- package/dist/cjs/runtime/bus.js +25 -0
- package/dist/cjs/runtime/loop.js +119 -0
- package/dist/cjs/scene/scene.js +36 -0
- package/dist/cjs/state/state.js +47 -0
- package/dist/cjs/state/stateFamily.js +101 -0
- package/dist/cjs/state/stateGroup.js +47 -0
- package/dist/cjs/state/token.js +23 -0
- package/dist/cjs/ui/node.js +2 -0
- package/dist/cjs/ui/trigger.js +2 -0
- package/dist/dts/boundary/boundary.d.ts +72 -0
- package/dist/dts/boundary/boundary.d.ts.map +1 -0
- package/dist/dts/calc/asyncCalc.d.ts +91 -0
- package/dist/dts/calc/asyncCalc.d.ts.map +1 -0
- package/dist/dts/calc/asyncData.d.ts +55 -0
- package/dist/dts/calc/asyncData.d.ts.map +1 -0
- package/dist/dts/calc/calc.d.ts +57 -0
- package/dist/dts/calc/calc.d.ts.map +1 -0
- package/dist/dts/calc/calcFamily.d.ts +57 -0
- package/dist/dts/calc/calcFamily.d.ts.map +1 -0
- package/dist/dts/channel/channel.d.ts +115 -0
- package/dist/dts/channel/channel.d.ts.map +1 -0
- package/dist/dts/compose/composition.d.ts +72 -0
- package/dist/dts/compose/composition.d.ts.map +1 -0
- package/dist/dts/compose/host.d.ts +17 -0
- package/dist/dts/compose/host.d.ts.map +1 -0
- package/dist/dts/compose/props.d.ts +13 -0
- package/dist/dts/compose/props.d.ts.map +1 -0
- package/dist/dts/compose/provide.d.ts +22 -0
- package/dist/dts/compose/provide.d.ts.map +1 -0
- package/dist/dts/compose/slot.d.ts +49 -0
- package/dist/dts/compose/slot.d.ts.map +1 -0
- package/dist/dts/compose/ui.d.ts +50 -0
- package/dist/dts/compose/ui.d.ts.map +1 -0
- package/dist/dts/definition/definition.d.ts +33 -0
- package/dist/dts/definition/definition.d.ts.map +1 -0
- package/dist/dts/event/event.d.ts +33 -0
- package/dist/dts/event/event.d.ts.map +1 -0
- package/dist/dts/event/eventGroup.d.ts +9 -0
- package/dist/dts/event/eventGroup.d.ts.map +1 -0
- package/dist/dts/feature/feature.d.ts +220 -0
- package/dist/dts/feature/feature.d.ts.map +1 -0
- package/dist/dts/index.d.ts +43 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/capture.d.ts +28 -0
- package/dist/dts/internal/capture.d.ts.map +1 -0
- package/dist/dts/internal/ctx.d.ts +12 -0
- package/dist/dts/internal/ctx.d.ts.map +1 -0
- package/dist/dts/internal/errors.d.ts +69 -0
- package/dist/dts/internal/errors.d.ts.map +1 -0
- package/dist/dts/internal/inspect.d.ts +17 -0
- package/dist/dts/internal/inspect.d.ts.map +1 -0
- package/dist/dts/internal/queryDriver.d.ts +65 -0
- package/dist/dts/internal/queryDriver.d.ts.map +1 -0
- package/dist/dts/internal/reuse.d.ts +10 -0
- package/dist/dts/internal/reuse.d.ts.map +1 -0
- package/dist/dts/internal/scheduler.d.ts +47 -0
- package/dist/dts/internal/scheduler.d.ts.map +1 -0
- package/dist/dts/internal/seeds.d.ts +17 -0
- package/dist/dts/internal/seeds.d.ts.map +1 -0
- package/dist/dts/internal/sources.d.ts +39 -0
- package/dist/dts/internal/sources.d.ts.map +1 -0
- package/dist/dts/internal/store.d.ts +47 -0
- package/dist/dts/internal/store.d.ts.map +1 -0
- package/dist/dts/internal/track.d.ts +33 -0
- package/dist/dts/internal/track.d.ts.map +1 -0
- package/dist/dts/procedure/procedure.d.ts +40 -0
- package/dist/dts/procedure/procedure.d.ts.map +1 -0
- package/dist/dts/reducer/reducer.d.ts +44 -0
- package/dist/dts/reducer/reducer.d.ts.map +1 -0
- package/dist/dts/remote/remoteState.d.ts +119 -0
- package/dist/dts/remote/remoteState.d.ts.map +1 -0
- package/dist/dts/runtime/bus.d.ts +27 -0
- package/dist/dts/runtime/bus.d.ts.map +1 -0
- package/dist/dts/runtime/loop.d.ts +45 -0
- package/dist/dts/runtime/loop.d.ts.map +1 -0
- package/dist/dts/scene/scene.d.ts +44 -0
- package/dist/dts/scene/scene.d.ts.map +1 -0
- package/dist/dts/state/state.d.ts +37 -0
- package/dist/dts/state/state.d.ts.map +1 -0
- package/dist/dts/state/stateFamily.d.ts +79 -0
- package/dist/dts/state/stateFamily.d.ts.map +1 -0
- package/dist/dts/state/stateGroup.d.ts +36 -0
- package/dist/dts/state/stateGroup.d.ts.map +1 -0
- package/dist/dts/state/token.d.ts +30 -0
- package/dist/dts/state/token.d.ts.map +1 -0
- package/dist/dts/ui/node.d.ts +9 -0
- package/dist/dts/ui/node.d.ts.map +1 -0
- package/dist/dts/ui/trigger.d.ts +7 -0
- package/dist/dts/ui/trigger.d.ts.map +1 -0
- package/dist/esm/boundary/boundary.js +83 -0
- package/dist/esm/boundary/boundary.js.map +1 -0
- package/dist/esm/calc/asyncCalc.js +95 -0
- package/dist/esm/calc/asyncCalc.js.map +1 -0
- package/dist/esm/calc/asyncData.js +34 -0
- package/dist/esm/calc/asyncData.js.map +1 -0
- package/dist/esm/calc/calc.js +58 -0
- package/dist/esm/calc/calc.js.map +1 -0
- package/dist/esm/calc/calcFamily.js +124 -0
- package/dist/esm/calc/calcFamily.js.map +1 -0
- package/dist/esm/channel/channel.js +136 -0
- package/dist/esm/channel/channel.js.map +1 -0
- package/dist/esm/compose/composition.js +46 -0
- package/dist/esm/compose/composition.js.map +1 -0
- package/dist/esm/compose/host.js +5 -0
- package/dist/esm/compose/host.js.map +1 -0
- package/dist/esm/compose/props.js +11 -0
- package/dist/esm/compose/props.js.map +1 -0
- package/dist/esm/compose/provide.js +28 -0
- package/dist/esm/compose/provide.js.map +1 -0
- package/dist/esm/compose/slot.js +23 -0
- package/dist/esm/compose/slot.js.map +1 -0
- package/dist/esm/compose/ui.js +57 -0
- package/dist/esm/compose/ui.js.map +1 -0
- package/dist/esm/definition/definition.js +42 -0
- package/dist/esm/definition/definition.js.map +1 -0
- package/dist/esm/event/event.js +30 -0
- package/dist/esm/event/event.js.map +1 -0
- package/dist/esm/event/eventGroup.js +4 -0
- package/dist/esm/event/eventGroup.js.map +1 -0
- package/dist/esm/feature/feature.js +98 -0
- package/dist/esm/feature/feature.js.map +1 -0
- package/dist/esm/index.js +45 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/capture.js +11 -0
- package/dist/esm/internal/capture.js.map +1 -0
- package/dist/esm/internal/ctx.js +2 -0
- package/dist/esm/internal/ctx.js.map +1 -0
- package/dist/esm/internal/errors.js +54 -0
- package/dist/esm/internal/errors.js.map +1 -0
- package/dist/esm/internal/inspect.js +32 -0
- package/dist/esm/internal/inspect.js.map +1 -0
- package/dist/esm/internal/queryDriver.js +134 -0
- package/dist/esm/internal/queryDriver.js.map +1 -0
- package/dist/esm/internal/reuse.js +68 -0
- package/dist/esm/internal/reuse.js.map +1 -0
- package/dist/esm/internal/scheduler.js +69 -0
- package/dist/esm/internal/scheduler.js.map +1 -0
- package/dist/esm/internal/seeds.js +17 -0
- package/dist/esm/internal/seeds.js.map +1 -0
- package/dist/esm/internal/sources.js +59 -0
- package/dist/esm/internal/sources.js.map +1 -0
- package/dist/esm/internal/store.js +73 -0
- package/dist/esm/internal/store.js.map +1 -0
- package/dist/esm/internal/track.js +18 -0
- package/dist/esm/internal/track.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/procedure/procedure.js +50 -0
- package/dist/esm/procedure/procedure.js.map +1 -0
- package/dist/esm/reducer/reducer.js +63 -0
- package/dist/esm/reducer/reducer.js.map +1 -0
- package/dist/esm/remote/remoteState.js +270 -0
- package/dist/esm/remote/remoteState.js.map +1 -0
- package/dist/esm/runtime/bus.js +20 -0
- package/dist/esm/runtime/bus.js.map +1 -0
- package/dist/esm/runtime/loop.js +116 -0
- package/dist/esm/runtime/loop.js.map +1 -0
- package/dist/esm/scene/scene.js +31 -0
- package/dist/esm/scene/scene.js.map +1 -0
- package/dist/esm/state/state.js +43 -0
- package/dist/esm/state/state.js.map +1 -0
- package/dist/esm/state/stateFamily.js +96 -0
- package/dist/esm/state/stateFamily.js.map +1 -0
- package/dist/esm/state/stateGroup.js +46 -0
- package/dist/esm/state/stateGroup.js.map +1 -0
- package/dist/esm/state/token.js +20 -0
- package/dist/esm/state/token.js.map +1 -0
- package/dist/esm/ui/node.js +2 -0
- package/dist/esm/ui/node.js.map +1 -0
- package/dist/esm/ui/trigger.js +2 -0
- package/dist/esm/ui/trigger.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option, Schema } from 'effect';
|
|
2
|
+
import { yieldableClass } from '../definition/definition.js';
|
|
3
|
+
import {} from '../event/event.js';
|
|
4
|
+
import {} from '../internal/sources.js';
|
|
5
|
+
import { bumpRevision, gatedFlag, makeQueryDriver, RevisionSchema, revisionZero, } from '../internal/queryDriver.js';
|
|
6
|
+
import {} from '../internal/store.js';
|
|
7
|
+
import { readTracked } from '../internal/track.js';
|
|
8
|
+
import * as Reducer from '../reducer/reducer.js';
|
|
9
|
+
import { Reducers } from '../runtime/loop.js';
|
|
10
|
+
import * as State from '../state/state.js';
|
|
11
|
+
import {} from '../state/token.js';
|
|
12
|
+
import { narrowStore } from './asyncData.js';
|
|
13
|
+
/**
|
|
14
|
+
* Define an async derived value. `output`/`error` schemas and `alwaysOn` shape
|
|
15
|
+
* the value type, so `yield* MyQuery` is typed to exactly the arms that can
|
|
16
|
+
* occur. The query effect itself is supplied by `AsyncCalc.live`.
|
|
17
|
+
*/
|
|
18
|
+
export const make = (name, config) => {
|
|
19
|
+
const store = Context.GenericTag(`reform/asyncCalc/${name}`);
|
|
20
|
+
// Runtime mirror of the type-level `Gated`, typed as its literal in one place.
|
|
21
|
+
const gated = gatedFlag(config.alwaysOn);
|
|
22
|
+
const manifest = {
|
|
23
|
+
kind: 'AsyncCalc',
|
|
24
|
+
name,
|
|
25
|
+
output: config.output,
|
|
26
|
+
gated,
|
|
27
|
+
...(config.error !== undefined ? { error: config.error } : {}),
|
|
28
|
+
};
|
|
29
|
+
const read = Effect.flatMap(store, readTracked);
|
|
30
|
+
return yieldableClass(read, {
|
|
31
|
+
manifest,
|
|
32
|
+
store,
|
|
33
|
+
name,
|
|
34
|
+
inputs: config.inputs,
|
|
35
|
+
gated,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
export function live(calc, config) {
|
|
39
|
+
// The hidden revision pair, built once per `live` call. Namespacing the state
|
|
40
|
+
// by the calc's name (`reform/state/${calcName}/invalidateOn`) keeps the tag
|
|
41
|
+
// out of any plausible user namespace; the duplicate-reducer check is
|
|
42
|
+
// warning-only and calc names are unique by convention.
|
|
43
|
+
const revisionState = config.invalidateOn === undefined
|
|
44
|
+
? undefined
|
|
45
|
+
: State.make(`${calc.manifest.name}/invalidateOn`, RevisionSchema);
|
|
46
|
+
// Scoped so the source subscriptions and the driver fiber are released when
|
|
47
|
+
// the layer's scope closes (each proof/test builds and disposes its runtime).
|
|
48
|
+
const driver = Layer.scoped(calc.store, Effect.gen(function* () {
|
|
49
|
+
// `disabled` is rejected at the type level for non-gated calcs (erased to
|
|
50
|
+
// `never` there); read it through a loose view for the runtime.
|
|
51
|
+
const cfg = config;
|
|
52
|
+
// The hidden revision store, read requirement-free (`serviceOption`): the
|
|
53
|
+
// assembly below always provides it alongside this driver, and feeding it
|
|
54
|
+
// through `extraKey` (not `wireSources`) keeps it out of the snapshot —
|
|
55
|
+
// the user's `query` and `invalidateBy` receive exactly
|
|
56
|
+
// `InputsObject<Inputs>`, with no hidden property to leak into
|
|
57
|
+
// spread-into-RPC payloads.
|
|
58
|
+
const revision = revisionState === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: Option.getOrUndefined(yield* Effect.serviceOption(revisionState.store));
|
|
61
|
+
const driver = yield* makeQueryDriver({
|
|
62
|
+
name: calc.manifest.name,
|
|
63
|
+
label: 'AsyncCalc',
|
|
64
|
+
gated: calc.gated,
|
|
65
|
+
inputs: calc.inputs,
|
|
66
|
+
query: cfg.query,
|
|
67
|
+
invalidateBy: cfg.invalidateBy,
|
|
68
|
+
disabled: cfg.disabled,
|
|
69
|
+
coalesce: cfg.coalesce,
|
|
70
|
+
reuse: cfg.reuse,
|
|
71
|
+
extraKey: revision === undefined
|
|
72
|
+
? undefined
|
|
73
|
+
: {
|
|
74
|
+
read: () => revision.getSnapshot(),
|
|
75
|
+
subscribe: (listener) => revision.subscribe(listener),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
// The internal store is the full union; narrow to the definition's arms.
|
|
79
|
+
return narrowStore(driver.store);
|
|
80
|
+
}));
|
|
81
|
+
if (config.invalidateOn === undefined || revisionState === undefined)
|
|
82
|
+
return driver;
|
|
83
|
+
// The hidden reducer: an ordinary `Reducer` folding every listed event to
|
|
84
|
+
// n + 1 — the revision's sole writer, registered/unregistered with this
|
|
85
|
+
// layer's scope like any user reducer. `Layer.provide` builds the hidden
|
|
86
|
+
// state once and feeds the same store to both the driver and the reducer,
|
|
87
|
+
// while the outer context (user inputs, the `Reducers` registry) passes
|
|
88
|
+
// through untouched.
|
|
89
|
+
const revisionReducer = Reducer.make(`${calc.manifest.name}/invalidateOn`, {
|
|
90
|
+
states: [revisionState],
|
|
91
|
+
events: config.invalidateOn,
|
|
92
|
+
});
|
|
93
|
+
return Layer.merge(driver, Reducer.live(revisionReducer, bumpRevision)).pipe(Layer.provide(State.live(revisionState, revisionZero)));
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=asyncCalc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncCalc.js","sourceRoot":"","sources":["../../../src/calc/asyncCalc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {} from '../internal/store.js';
|
|
2
|
+
const idle = { _tag: 'Idle' };
|
|
3
|
+
const loading = { _tag: 'Loading' };
|
|
4
|
+
const success = (value, refetching = false) => ({
|
|
5
|
+
_tag: 'Success',
|
|
6
|
+
value,
|
|
7
|
+
refetching,
|
|
8
|
+
});
|
|
9
|
+
const error = (err, refetching = false) => ({
|
|
10
|
+
_tag: 'Error',
|
|
11
|
+
error: err,
|
|
12
|
+
refetching,
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Constructors for the arms, namespaced under the same name as the type so call
|
|
16
|
+
* sites read `AsyncData.success(v)` / `AsyncData.error(e)` — no `error` import
|
|
17
|
+
* shadowing the keyword and one obvious home for every arm.
|
|
18
|
+
*/
|
|
19
|
+
export const AsyncData = { idle, loading, success, error };
|
|
20
|
+
/**
|
|
21
|
+
* Narrow the live store (which holds the full `AnyAsyncData` union) to the arms
|
|
22
|
+
* the definition actually permits (`Gated`/`E`). The single documented home for
|
|
23
|
+
* that narrowing, so `AsyncCalc.live` returns it without an inline cast.
|
|
24
|
+
*/
|
|
25
|
+
export const narrowStore = (store) => store;
|
|
26
|
+
/**
|
|
27
|
+
* The read-only inverse of `narrowStore`: widen a definition-narrowed store back
|
|
28
|
+
* to the full union, so a consumer (the `RemoteState` overlay) can dispatch on the
|
|
29
|
+
* lifecycle arms without carrying the definition's `Gated`/`E` conditionals.
|
|
30
|
+
* Sound for reads only — every narrowed value IS an `AnyAsyncData` — so callers
|
|
31
|
+
* must never `set` through the widened view (derived stores ignore `set` anyway).
|
|
32
|
+
*/
|
|
33
|
+
export const widenStore = (store) => store;
|
|
34
|
+
//# sourceMappingURL=asyncData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncData.js","sourceRoot":"","sources":["../../../src/calc/asyncData.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,mBAAmB,CAAA;AAiD9C,MAAM,IAAI,GAAc,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACxC,MAAM,OAAO,GAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;AACjD,MAAM,OAAO,GAAG,CAAI,KAAQ,EAAE,UAAU,GAAG,KAAK,EAAmB,EAAE,CAAC,CAAC;IACrE,IAAI,EAAE,SAAS;IACf,KAAK;IACL,UAAU;CACX,CAAC,CAAA;AACF,MAAM,KAAK,GAAG,CAAI,GAAM,EAAE,UAAU,GAAG,KAAK,EAAiB,EAAE,CAAC,CAAC;IAC/D,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,GAAG;IACV,UAAU;CACX,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAW,CAAA;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,KAAgC,EACD,EAAE,CAAC,KAAiD,CAAA;AAErF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,KAAoC,EACT,EAAE,CAAC,KAA6C,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Context, Effect, Layer, MutableRef } from 'effect';
|
|
2
|
+
import { yieldableClass } from '../definition/definition.js';
|
|
3
|
+
import { sameKey, wireSources, } from '../internal/sources.js';
|
|
4
|
+
import { resolveScheduler } from '../internal/scheduler.js';
|
|
5
|
+
import { reuse } from '../internal/reuse.js';
|
|
6
|
+
import { makeDerivedStore } from '../internal/store.js';
|
|
7
|
+
import { readTracked } from '../internal/track.js';
|
|
8
|
+
import {} from '../state/token.js';
|
|
9
|
+
// Re-exported so `Calc.InputsObject` / `Calc.InvalidateBy` keep their home here
|
|
10
|
+
// even though the reactive plumbing now lives in `internal/sources`.
|
|
11
|
+
export {} from '../internal/sources.js';
|
|
12
|
+
/**
|
|
13
|
+
* A derived, memoized value over explicit inputs (state members, other calcs, or
|
|
14
|
+
* async calcs). The *definition* fixes the inputs and the output schema; the
|
|
15
|
+
* *implementation* is the pure projection. Recomputes only when an input changes;
|
|
16
|
+
* `yield* Calc` reads the memoized value.
|
|
17
|
+
*/
|
|
18
|
+
export const make = (name, config) => {
|
|
19
|
+
const store = Context.GenericTag(`reform/calc/${name}`);
|
|
20
|
+
const manifest = { kind: 'Calc', name, output: config.output };
|
|
21
|
+
const read = Effect.flatMap(store, readTracked);
|
|
22
|
+
return yieldableClass(read, { manifest, store, name, inputs: config.inputs });
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Wire the pure projection for a calc. Recomputes only when its invalidation key
|
|
26
|
+
* changes (by default every input value; override with `invalidateBy`), memoized
|
|
27
|
+
* on that key; `yield* Calc` reads the memoized value. The derived store reuses
|
|
28
|
+
* the shared coalescing scheduler, so a recompute that yields an equal value
|
|
29
|
+
* wakes no subscriber.
|
|
30
|
+
*/
|
|
31
|
+
export const live = (calc, compute, options = {}) =>
|
|
32
|
+
// Scoped so the source subscriptions are released when the layer's scope
|
|
33
|
+
// closes (each proof/test builds and disposes its own runtime).
|
|
34
|
+
Layer.scoped(calc.store, Effect.gen(function* () {
|
|
35
|
+
const scheduler = yield* resolveScheduler;
|
|
36
|
+
const sources = yield* wireSources(calc.inputs, options.invalidateBy);
|
|
37
|
+
// Memo cell: key and output captured together so reads are always
|
|
38
|
+
// consistent (no separate output that could lag the key it was computed for).
|
|
39
|
+
const memo = MutableRef.make(undefined);
|
|
40
|
+
const recompute = () => {
|
|
41
|
+
const args = sources.snapshot();
|
|
42
|
+
const key = sources.keyOf(args);
|
|
43
|
+
const prev = MutableRef.get(memo);
|
|
44
|
+
if (prev !== undefined && sameKey(key, prev.key))
|
|
45
|
+
return prev.output;
|
|
46
|
+
const fresh = compute(args);
|
|
47
|
+
// With `reuse`, a recompute that lands value-equal to the previous
|
|
48
|
+
// output returns the previous reference — the derived store's Equal
|
|
49
|
+
// gate then wakes no subscriber at all.
|
|
50
|
+
const output = options.reuse === true && prev !== undefined ? reuse(prev.output, fresh) : fresh;
|
|
51
|
+
MutableRef.set(memo, { key, output });
|
|
52
|
+
return output;
|
|
53
|
+
};
|
|
54
|
+
const derived = makeDerivedStore(recompute, sources.subscribe, scheduler);
|
|
55
|
+
yield* Effect.addFinalizer(() => Effect.sync(derived.unsubscribe));
|
|
56
|
+
return derived.store;
|
|
57
|
+
}));
|
|
58
|
+
//# sourceMappingURL=calc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calc.js","sourceRoot":"","sources":["../../../src/calc/calc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Context, Effect, Layer, MutableRef } from 'effect';
|
|
2
|
+
import { definitionClass } from '../definition/definition.js';
|
|
3
|
+
import { sameKey, wireSources, } from '../internal/sources.js';
|
|
4
|
+
import { resolveScheduler } from '../internal/scheduler.js';
|
|
5
|
+
import { makeDerivedStore } from '../internal/store.js';
|
|
6
|
+
import { readTracked } from '../internal/track.js';
|
|
7
|
+
import {} from '../state/stateFamily.js';
|
|
8
|
+
import {} from '../state/token.js';
|
|
9
|
+
/**
|
|
10
|
+
* Define a keyed derived value: per-key memoized projections over shared
|
|
11
|
+
* inputs. The *definition* fixes the key/output schemas and the inputs; the
|
|
12
|
+
* *implementation* (the per-key projection) is supplied by `CalcFamily.live`.
|
|
13
|
+
*/
|
|
14
|
+
export const make = (name, config) => {
|
|
15
|
+
const store = Context.GenericTag(`reform/calcFamily/${name}`);
|
|
16
|
+
const manifest = {
|
|
17
|
+
kind: 'CalcFamily',
|
|
18
|
+
name,
|
|
19
|
+
key: config.key,
|
|
20
|
+
output: config.output,
|
|
21
|
+
};
|
|
22
|
+
return definitionClass({
|
|
23
|
+
manifest,
|
|
24
|
+
store,
|
|
25
|
+
inputs: config.inputs,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Read one member by key — `CalcFamily.read(GroupProjection, id)`. Inside a
|
|
30
|
+
* render it subscribes exactly that member's store (unrelated members never
|
|
31
|
+
* wake the subtree); elsewhere it just snapshots — the same one-read-API
|
|
32
|
+
* contract as `StateFamily.read`.
|
|
33
|
+
*/
|
|
34
|
+
export const read = (family, key) => Effect.flatMap(family.store, (fs) => readTracked(fs.at(key)));
|
|
35
|
+
/**
|
|
36
|
+
* Wire the per-key projection. `compute` is curried — `(key) => (inputs) =>
|
|
37
|
+
* Out` — so per-key construction (closing over key-derived constants) runs once
|
|
38
|
+
* per member while the inner projection runs per recompute. Each member is a
|
|
39
|
+
* lazy derived store over the shared wired sources: memoized on the family's
|
|
40
|
+
* invalidation key, notifying only when its own output moves. Unlike a
|
|
41
|
+
* `StateFamily` entry, a member holds an upstream subscription, so every
|
|
42
|
+
* removal path (forget / clear / eviction / the layer finalizer) releases it.
|
|
43
|
+
*/
|
|
44
|
+
export const live = (family, compute, options = {}) => Layer.scoped(family.store, Effect.gen(function* () {
|
|
45
|
+
const scheduler = yield* resolveScheduler;
|
|
46
|
+
// Wired ONCE for the whole family — members share the sensing surface.
|
|
47
|
+
const sources = yield* wireSources(family.inputs, options.invalidateBy);
|
|
48
|
+
const entries = new Map();
|
|
49
|
+
const evictWhenUnused = options.evictWhenUnused === true;
|
|
50
|
+
// Live subscriber count per key — maintained only when eviction is on.
|
|
51
|
+
const subscribers = new Map();
|
|
52
|
+
const dropEntry = (key) => {
|
|
53
|
+
const entry = entries.get(key);
|
|
54
|
+
if (entry !== undefined)
|
|
55
|
+
entry.unsubscribe();
|
|
56
|
+
entries.delete(key);
|
|
57
|
+
subscribers.delete(key);
|
|
58
|
+
};
|
|
59
|
+
// One member: the same memo block as `Calc.live`, with the key closed over.
|
|
60
|
+
const createMember = (key) => {
|
|
61
|
+
const body = compute(key);
|
|
62
|
+
const memo = MutableRef.make(undefined);
|
|
63
|
+
const recompute = () => {
|
|
64
|
+
const args = sources.snapshot();
|
|
65
|
+
const memoKey = sources.keyOf(args);
|
|
66
|
+
const prev = MutableRef.get(memo);
|
|
67
|
+
if (prev !== undefined && sameKey(memoKey, prev.key))
|
|
68
|
+
return prev.output;
|
|
69
|
+
const output = body(args);
|
|
70
|
+
MutableRef.set(memo, { key: memoKey, output });
|
|
71
|
+
return output;
|
|
72
|
+
};
|
|
73
|
+
return makeDerivedStore(recompute, sources.subscribe, scheduler);
|
|
74
|
+
};
|
|
75
|
+
// The `StateFamily` ref-count idiom: evict on the next microtask once a
|
|
76
|
+
// key falls idle (a same-commit re-subscribe cancels it) — here eviction
|
|
77
|
+
// also releases the member's upstream subscription via `dropEntry`.
|
|
78
|
+
const refCounted = (key, store) => ({
|
|
79
|
+
...store,
|
|
80
|
+
subscribe: (listener) => {
|
|
81
|
+
subscribers.set(key, (subscribers.get(key) ?? 0) + 1);
|
|
82
|
+
const off = store.subscribe(listener);
|
|
83
|
+
const released = { done: false };
|
|
84
|
+
return () => {
|
|
85
|
+
if (released.done)
|
|
86
|
+
return;
|
|
87
|
+
released.done = true;
|
|
88
|
+
off();
|
|
89
|
+
const remaining = (subscribers.get(key) ?? 1) - 1;
|
|
90
|
+
if (remaining > 0) {
|
|
91
|
+
subscribers.set(key, remaining);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
subscribers.delete(key);
|
|
95
|
+
queueMicrotask(() => {
|
|
96
|
+
if ((subscribers.get(key) ?? 0) === 0)
|
|
97
|
+
dropEntry(key);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const familyStore = {
|
|
103
|
+
at: (key) => {
|
|
104
|
+
const existing = entries.get(key);
|
|
105
|
+
if (existing !== undefined)
|
|
106
|
+
return existing.store;
|
|
107
|
+
const member = createMember(key);
|
|
108
|
+
const created = evictWhenUnused
|
|
109
|
+
? { store: refCounted(key, member.store), unsubscribe: member.unsubscribe }
|
|
110
|
+
: member;
|
|
111
|
+
entries.set(key, created);
|
|
112
|
+
return created.store;
|
|
113
|
+
},
|
|
114
|
+
forget: dropEntry,
|
|
115
|
+
clear: () => {
|
|
116
|
+
for (const key of [...entries.keys()])
|
|
117
|
+
dropEntry(key);
|
|
118
|
+
},
|
|
119
|
+
size: () => entries.size,
|
|
120
|
+
};
|
|
121
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => familyStore.clear()));
|
|
122
|
+
return familyStore;
|
|
123
|
+
}));
|
|
124
|
+
//# sourceMappingURL=calcFamily.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calcFamily.js","sourceRoot":"","sources":["../../../src/calc/calcFamily.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Chunk, Context, Effect, Layer, Match, Queue, Stream } from 'effect';
|
|
2
|
+
import { definitionClass } from '../definition/definition.js';
|
|
3
|
+
import { DuplicateRegistration } from '../internal/errors.js';
|
|
4
|
+
export class Procedures extends Context.Tag('reform/Procedures')() {
|
|
5
|
+
}
|
|
6
|
+
/** Get a map entry, creating and inserting it on first access (avoids `let`). */
|
|
7
|
+
const getOrCreate = (map, key, make) => {
|
|
8
|
+
const existing = map.get(key);
|
|
9
|
+
if (existing !== undefined)
|
|
10
|
+
return existing;
|
|
11
|
+
const created = make();
|
|
12
|
+
map.set(key, created);
|
|
13
|
+
return created;
|
|
14
|
+
};
|
|
15
|
+
/** Drop an item from a `Map<K, Array>` bucket, pruning the key when it empties. */
|
|
16
|
+
const dropFrom = (map, key, item) => {
|
|
17
|
+
const bucket = map.get(key);
|
|
18
|
+
if (bucket === undefined)
|
|
19
|
+
return;
|
|
20
|
+
const next = bucket.filter((value) => value !== item);
|
|
21
|
+
if (next.length === 0)
|
|
22
|
+
map.delete(key);
|
|
23
|
+
else
|
|
24
|
+
map.set(key, next);
|
|
25
|
+
};
|
|
26
|
+
const makeProcedureRegistry = () => {
|
|
27
|
+
const entries = [];
|
|
28
|
+
const byTag = new Map();
|
|
29
|
+
const channelsByTag = new Map();
|
|
30
|
+
const byChannelTag = new Map();
|
|
31
|
+
return {
|
|
32
|
+
entries,
|
|
33
|
+
byTag,
|
|
34
|
+
channelsByTag,
|
|
35
|
+
byChannelTag,
|
|
36
|
+
register: (entry) => {
|
|
37
|
+
entries.push(entry);
|
|
38
|
+
for (const tag of entry.handles) {
|
|
39
|
+
getOrCreate(byTag, tag, () => []).push(entry);
|
|
40
|
+
const channels = getOrCreate(channelsByTag, tag, () => []);
|
|
41
|
+
if (!channels.includes(entry.channelName))
|
|
42
|
+
channels.push(entry.channelName);
|
|
43
|
+
getOrCreate(getOrCreate(byChannelTag, entry.channelName, () => new Map()), tag, () => []).push(entry);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
unregister: (entry) => {
|
|
47
|
+
const index = entries.indexOf(entry);
|
|
48
|
+
if (index >= 0)
|
|
49
|
+
entries.splice(index, 1);
|
|
50
|
+
for (const tag of entry.handles) {
|
|
51
|
+
dropFrom(byTag, tag, entry);
|
|
52
|
+
const channelMap = byChannelTag.get(entry.channelName);
|
|
53
|
+
if (channelMap !== undefined) {
|
|
54
|
+
dropFrom(channelMap, tag, entry);
|
|
55
|
+
// If no procedure on this channel still handles the tag, drop the
|
|
56
|
+
// channel from `channelsByTag[tag]` so the loop stops offering to it.
|
|
57
|
+
if (channelMap.get(tag) === undefined)
|
|
58
|
+
dropFrom(channelsByTag, tag, entry.channelName);
|
|
59
|
+
if (channelMap.size === 0)
|
|
60
|
+
byChannelTag.delete(entry.channelName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export const proceduresLayer = Layer.sync(Procedures, makeProcedureRegistry);
|
|
67
|
+
/** Registry of live channels, keyed by name. The single loop routes through it. */
|
|
68
|
+
export class Channels extends Context.Tag('reform/Channels')() {
|
|
69
|
+
}
|
|
70
|
+
export const channelsLayer = Layer.sync(Channels, () => ({ byName: new Map() }));
|
|
71
|
+
/**
|
|
72
|
+
* A scheduling lane for procedures. The *definition* (`Channel.make`) is a
|
|
73
|
+
* reflectable name + policy; the *implementation* (`Channel.live`) builds the
|
|
74
|
+
* queue + fiber that applies the policy. Procedures reference a channel by value
|
|
75
|
+
* and register into it; `Channel.live` is merged into the app once per channel.
|
|
76
|
+
*/
|
|
77
|
+
export const make = (name, config) => definitionClass({
|
|
78
|
+
manifest: { kind: 'Channel', name },
|
|
79
|
+
policy: config.policy,
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Build and register the live machinery for a channel. Idempotent by name, so
|
|
83
|
+
* procedures sharing an `exclusive`/`latest` channel share one queue + fiber +
|
|
84
|
+
* semaphore (and therefore truly serialize / cancel across each other).
|
|
85
|
+
*/
|
|
86
|
+
export const live = (channel) => Layer.scopedDiscard(Effect.gen(function* () {
|
|
87
|
+
const name = channel.manifest.name;
|
|
88
|
+
const channels = yield* Channels;
|
|
89
|
+
const existing = channels.byName.get(name);
|
|
90
|
+
if (existing !== undefined) {
|
|
91
|
+
// Idempotent for the *same* channel `.live`'d through several sub-layers
|
|
92
|
+
// (shared `exclusive`/`latest` lane). A *different* channel reusing the
|
|
93
|
+
// name is a silent footgun — the second policy would be dropped — so
|
|
94
|
+
// reject it loudly instead.
|
|
95
|
+
if (existing.policy !== channel.policy) {
|
|
96
|
+
throw new DuplicateRegistration({ kind: 'Channel', name });
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const procedures = yield* Procedures;
|
|
101
|
+
const queue = yield* Queue.unbounded();
|
|
102
|
+
// Run every procedure on THIS channel that handles the event. Read the live
|
|
103
|
+
// index each time so procedures registered later are still seen.
|
|
104
|
+
// A procedure is expected to model expected failures as events; a leaked
|
|
105
|
+
// defect (a bug in the body) is logged before being isolated, so one
|
|
106
|
+
// procedure's crash never tears down the channel fiber — but is observable.
|
|
107
|
+
const runMatching = (event) => Effect.forEach(procedures.byChannelTag.get(name)?.get(event._tag) ?? [], (e) => e.run(event).pipe(Effect.tapErrorCause((cause) => Effect.logError(`reform: procedure on channel '${name}' failed`, cause)), Effect.catchAllCause(() => Effect.void)), { discard: true, concurrency: 'unbounded' });
|
|
108
|
+
// Map the policy to its scheduling stream. `Match.exhaustive` makes a new
|
|
109
|
+
// policy variant a compile error, and each arm is a self-contained const.
|
|
110
|
+
const events = Stream.fromQueue(queue);
|
|
111
|
+
const driven = Match.value(channel.policy).pipe(Match.tag('merge', () => Stream.mapEffect(events, runMatching, { concurrency: 'unbounded' })), Match.tag('latest', () => Stream.flatMap(events, (e) => Stream.fromEffect(runMatching(e)), { switch: true })), Match.tag('debounce', (policy) => events.pipe(Stream.debounce(policy.duration), Stream.mapEffect(runMatching, { concurrency: 'unbounded' }))), Match.tag('throttle', (policy) => events.pipe(Stream.throttle({
|
|
112
|
+
cost: (chunk) => Chunk.size(chunk) * (policy.cost ?? 1),
|
|
113
|
+
units: policy.units,
|
|
114
|
+
duration: policy.duration,
|
|
115
|
+
...(policy.burst !== undefined ? { burst: policy.burst } : {}),
|
|
116
|
+
strategy: policy.strategy ?? 'shape',
|
|
117
|
+
}), Stream.mapEffect(runMatching, { concurrency: 'unbounded' }))),
|
|
118
|
+
// `concurrency: 1` runs one event at a time AND in offer order — a true
|
|
119
|
+
// serialized lane. (An unbounded fork racing for a single semaphore permit
|
|
120
|
+
// would serialize but could reorder under contention.)
|
|
121
|
+
Match.tag('exclusive', () => Stream.mapEffect(events, runMatching, { concurrency: 1 })), Match.exhaustive);
|
|
122
|
+
yield* Effect.forkScoped(Stream.runDrain(driven));
|
|
123
|
+
channels.byName.set(name, {
|
|
124
|
+
name,
|
|
125
|
+
policy: channel.policy,
|
|
126
|
+
offer: (event) => {
|
|
127
|
+
Queue.unsafeOffer(queue, event);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
// On scope close (a lazy feature unmounting) remove the live channel so the
|
|
131
|
+
// loop stops routing to it; the drain fiber is already torn down by
|
|
132
|
+
// `forkScoped`. Eager channels live on the root scope, so this is a no-op
|
|
133
|
+
// until app dispose.
|
|
134
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => channels.byName.delete(name)));
|
|
135
|
+
}));
|
|
136
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../../src/channel/channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from 'effect';
|
|
2
|
+
import { definitionClass } from '../definition/definition.js';
|
|
3
|
+
import {} from '../internal/ctx.js';
|
|
4
|
+
import { CurrentTracker } from '../internal/track.js';
|
|
5
|
+
import { CurrentSlots } from './host.js';
|
|
6
|
+
import { Props } from './props.js';
|
|
7
|
+
/**
|
|
8
|
+
* A component's logic. The *definition* (`Composition.make`) is a reflectable
|
|
9
|
+
* manifest that doubles as a requirement/tag; the *implementation*
|
|
10
|
+
* (`Composition.live`) is the synchronous body, provided separately and
|
|
11
|
+
* checked against it.
|
|
12
|
+
*/
|
|
13
|
+
export const make = (name,
|
|
14
|
+
// Constrain `ui` to the full `UiClass<C>` so `C` is inferred here; the stored
|
|
15
|
+
// manifest still sees only the erased `{ manifest }` carrier (CompositionConfig),
|
|
16
|
+
// so the manifest type — and its variance — is unchanged.
|
|
17
|
+
config) => {
|
|
18
|
+
const tag = Context.GenericTag(`reform/composition/${name}`);
|
|
19
|
+
const manifest = { kind: 'Composition', name, ...config };
|
|
20
|
+
return definitionClass({ manifest, tag });
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Wire a composition's synchronous logic (D2): read state, acquire triggers,
|
|
24
|
+
* resolve the UI, return the view. Its requirements (minus `Props`, which the
|
|
25
|
+
* host injects per render) surface as the Layer's `RIn`; the runtime runs the
|
|
26
|
+
* body per render within the captured context.
|
|
27
|
+
*/
|
|
28
|
+
export const live = (
|
|
29
|
+
// `live` doesn't use the contract type; accept any composition.
|
|
30
|
+
composition, body) => {
|
|
31
|
+
const logic = Effect.gen(body);
|
|
32
|
+
return Layer.effect(composition.tag, Effect.gen(function* () {
|
|
33
|
+
// Capture the build-time context (state stores, calc, ui, bus). `Props`,
|
|
34
|
+
// the tracker, and slots are injected per render below.
|
|
35
|
+
const context = yield* Effect.context();
|
|
36
|
+
const render = (env) =>
|
|
37
|
+
// Total once fully provided: the body is synchronous, reads can't fail,
|
|
38
|
+
// and every requirement is now satisfied (D2). `Effect.gen` widens the
|
|
39
|
+
// error channel to `unknown` for a generic body, so we restate it.
|
|
40
|
+
logic.pipe(Effect.provideService(Props, env.props), Effect.provideService(CurrentTracker, env.tracker), Effect.provideService(CurrentSlots, env.slots), Effect.provide(context));
|
|
41
|
+
return { render };
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
44
|
+
/** Run a mounted composition for one frame, producing its node. */
|
|
45
|
+
export const render = (service, env) => service.render(env);
|
|
46
|
+
//# sourceMappingURL=composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.js","sourceRoot":"","sources":["../../../src/compose/composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAe,MAAM,QAAQ,CAAA;AAE5D,OAAO,EAAiB,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAY,MAAM,iBAAiB,CAAA;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAiB,MAAM,QAAQ,CAAA;AAGpD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AA+C/B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,IAAY;AACZ,8EAA8E;AAC9E,kFAAkF;AAClF,0DAA0D;AAC1D,MAA4C,EACN,EAAE;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAC5B,sBAAsB,IAAI,EAAE,CAC7B,CAAA;IACD,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,aAAsB,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAA;IAClE,OAAO,eAAe,CAAuC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAA;AACjF,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;AAClB,gEAAgE;AAChE,WAAqC,EACrC,IAAuC,EAC2B,EAAE;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC9B,OAAO,KAAK,CAAC,MAAM,CACjB,WAAW,CAAC,GAAG,EACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,yEAAyE;QACzE,wDAAwD;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAA4B,CAAA;QACjE,MAAM,MAAM,GAAG,CAAC,GAAc,EAAqC,EAAE;QACnE,wEAAwE;QACxE,uEAAuE;QACvE,mEAAmE;QACnE,KAAK,CAAC,IAAI,CACR,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,EACvC,MAAM,CAAC,cAAc,CAAC,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,EAClD,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,GAAG,CAAC,KAAK,CAAC,EAC9C,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CACa,CAAA;QACxC,OAAO,EAAE,MAAM,EAAE,CAAA;IACnB,CAAC,CAAC,CACH,CAAA;AACH,CAAC,CAAA;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAA2B,EAAE,GAAc,EAAqC,EAAE,CACvG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../../src/compose/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAahC,+EAA+E;AAC/E,MAAM,OAAO,YAAa,SAAQ,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAA0B;CAAG"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The current composition instance's props. Bound per render by the runtime; a
|
|
4
|
+
* composition body reads them with `const { todo } = yield* Props`.
|
|
5
|
+
*
|
|
6
|
+
* (Per-instance typing of Props is DESIGN #26 — for now the value is opaque and
|
|
7
|
+
* narrowed at the use site against the composition's declared props schema.)
|
|
8
|
+
*/
|
|
9
|
+
export class Props extends Context.Tag('reform/Props')() {
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"props.js","sourceRoot":"","sources":["../../../src/compose/props.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAEhC;;;;;;GAMG;AACH,MAAM,OAAO,KAAM,SAAQ,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAc;CAAG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { InvalidProvideTarget } from '../internal/errors.js';
|
|
3
|
+
import { isFeature, } from '../feature/feature.js';
|
|
4
|
+
import { isSlot } from './slot.js';
|
|
5
|
+
import { isUi } from './ui.js';
|
|
6
|
+
export function provide(target, impl) {
|
|
7
|
+
// Discriminate the target by its nominal brand, not by which DI-hole property
|
|
8
|
+
// happens to exist: a UI contract provides under its `impl` tag, a slot under
|
|
9
|
+
// its `tag`. Anything else is a wiring mistake. The overloads above pair
|
|
10
|
+
// `impl` with the chosen tag; this implementation sees both erased, so view
|
|
11
|
+
// the tag loosely (the one documented cast) to provide the service under it.
|
|
12
|
+
const tag = (isUi(target) ? target.impl : isSlot(target) ? target.tag : undefined);
|
|
13
|
+
if (tag === undefined)
|
|
14
|
+
throw new InvalidProvideTarget();
|
|
15
|
+
if (isFeature(impl)) {
|
|
16
|
+
const eager = impl.eagerModule;
|
|
17
|
+
// Lazy: bind the `FeatureBinding`; the host drives load → mount, painting the
|
|
18
|
+
// placeholders. Default: bind the composition (so the existing `Compose` path
|
|
19
|
+
// renders it, no host, no flash) and merge the eager `.live` layer, whose `RIn`
|
|
20
|
+
// surfaces to the scene. Either way the overload's return type carries the
|
|
21
|
+
// shared `RIn`, so the scene still type-demands `Core`.
|
|
22
|
+
return eager === undefined
|
|
23
|
+
? Layer.succeed(tag, impl.binding)
|
|
24
|
+
: Layer.merge(Layer.succeed(tag, impl.binding.composition), eager.layer);
|
|
25
|
+
}
|
|
26
|
+
return Layer.succeed(tag, impl);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=provide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provide.js","sourceRoot":"","sources":["../../../src/compose/provide.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,EAGL,SAAS,GAGV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,MAAM,EAAkC,MAAM,QAAQ,CAAA;AAC/D,OAAO,EAAE,IAAI,EAAgD,MAAM,MAAM,CAAA;AAoCzE,MAAM,UAAU,OAAO,CAAC,MAAe,EAAE,IAAa;IACpD,8EAA8E;IAC9E,8EAA8E;IAC9E,yEAAyE;IACzE,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAEpE,CAAA;IACb,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,IAAI,oBAAoB,EAAE,CAAA;IACvD,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAA;QAC9B,8EAA8E;QAC9E,8EAA8E;QAC9E,gFAAgF;QAChF,2EAA2E;QAC3E,wDAAwD;QACxD,OAAO,KAAK,KAAK,SAAS;YACxB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC;YAClC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IAC5E,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;AACjC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
import { definitionClass } from '../definition/definition.js';
|
|
3
|
+
/**
|
|
4
|
+
* A DI hole for a child composition. The parent declares
|
|
5
|
+
* `slot('Item')<typeof TodoItem>()` and renders through it; the concrete child
|
|
6
|
+
* is wired with `provide(Item, TodoItem)`, so the parent never imports the
|
|
7
|
+
* child's *implementation* (the `typeof` is a type-only reference to the
|
|
8
|
+
* definition). `Comp` lives only in the phantom instance (so `SlotClass` is
|
|
9
|
+
* covariant and a `{ Item: ItemSlot }` map is well-typed); the match is enforced
|
|
10
|
+
* by `provide`'s signature, not the (invariant) tag.
|
|
11
|
+
*/
|
|
12
|
+
/** Nominal brand identifying a slot at runtime (Effect's `TypeId` idiom). */
|
|
13
|
+
export const SlotTypeId = Symbol.for('reform/Slot');
|
|
14
|
+
/** Whether a value is a slot — discriminates a `provide` target by brand. */
|
|
15
|
+
export const isSlot = (u) => (typeof u === 'function' || typeof u === 'object') && u !== null && SlotTypeId in u;
|
|
16
|
+
export const slot = (name) =>
|
|
17
|
+
// Defaulted so a slot whose child carries no meaningful props/contract can be
|
|
18
|
+
// declared bare (`slot('Tree')()`); pass `typeof Child` to type the facade.
|
|
19
|
+
() => {
|
|
20
|
+
const tag = Context.GenericTag(`reform/slot/${name}`);
|
|
21
|
+
return definitionClass({ [SlotTypeId]: SlotTypeId, kind: 'Slot', tag });
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=slot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot.js","sourceRoot":"","sources":["../../../src/compose/slot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AA6B1D;;;;;;;;GAQG;AACH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,UAAU,GAAkB,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;AAWlE,6EAA6E;AAC7E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAU,EAAkB,EAAE,CACnD,CAAC,OAAO,CAAC,KAAK,UAAU,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,UAAU,IAAI,CAAC,CAAA;AAKrF,MAAM,CAAC,MAAM,IAAI,GACf,CAAC,IAAY,EAAE,EAAE;AACjB,8EAA8E;AAC9E,4EAA4E;AAC5E,GAAkE,EAAE;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAuB,eAAe,IAAI,EAAE,CAAC,CAAA;IAC3E,OAAO,eAAe,CAAkB,EAAE,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,MAAe,EAAE,GAAG,EAAE,CAAC,CAAA;AACnG,CAAC,CAAA"}
|