@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.
Files changed (204) hide show
  1. package/README.md +64 -0
  2. package/dist/cjs/boundary/boundary.js +86 -0
  3. package/dist/cjs/calc/asyncCalc.js +128 -0
  4. package/dist/cjs/calc/asyncData.js +37 -0
  5. package/dist/cjs/calc/calc.js +58 -0
  6. package/dist/cjs/calc/calcFamily.js +127 -0
  7. package/dist/cjs/channel/channel.js +142 -0
  8. package/dist/cjs/compose/composition.js +50 -0
  9. package/dist/cjs/compose/host.js +8 -0
  10. package/dist/cjs/compose/props.js +14 -0
  11. package/dist/cjs/compose/provide.js +30 -0
  12. package/dist/cjs/compose/slot.js +27 -0
  13. package/dist/cjs/compose/ui.js +61 -0
  14. package/dist/cjs/definition/definition.js +46 -0
  15. package/dist/cjs/event/event.js +36 -0
  16. package/dist/cjs/event/eventGroup.js +7 -0
  17. package/dist/cjs/feature/feature.js +102 -0
  18. package/dist/cjs/index.js +116 -0
  19. package/dist/cjs/internal/capture.js +14 -0
  20. package/dist/cjs/internal/ctx.js +2 -0
  21. package/dist/cjs/internal/errors.js +62 -0
  22. package/dist/cjs/internal/inspect.js +36 -0
  23. package/dist/cjs/internal/queryDriver.js +138 -0
  24. package/dist/cjs/internal/reuse.js +71 -0
  25. package/dist/cjs/internal/scheduler.js +73 -0
  26. package/dist/cjs/internal/seeds.js +19 -0
  27. package/dist/cjs/internal/sources.js +61 -0
  28. package/dist/cjs/internal/store.js +77 -0
  29. package/dist/cjs/internal/track.js +22 -0
  30. package/dist/cjs/package.json +4 -0
  31. package/dist/cjs/procedure/procedure.js +52 -0
  32. package/dist/cjs/reducer/reducer.js +64 -0
  33. package/dist/cjs/remote/remoteState.js +307 -0
  34. package/dist/cjs/runtime/bus.js +25 -0
  35. package/dist/cjs/runtime/loop.js +119 -0
  36. package/dist/cjs/scene/scene.js +36 -0
  37. package/dist/cjs/state/state.js +47 -0
  38. package/dist/cjs/state/stateFamily.js +101 -0
  39. package/dist/cjs/state/stateGroup.js +47 -0
  40. package/dist/cjs/state/token.js +23 -0
  41. package/dist/cjs/ui/node.js +2 -0
  42. package/dist/cjs/ui/trigger.js +2 -0
  43. package/dist/dts/boundary/boundary.d.ts +72 -0
  44. package/dist/dts/boundary/boundary.d.ts.map +1 -0
  45. package/dist/dts/calc/asyncCalc.d.ts +91 -0
  46. package/dist/dts/calc/asyncCalc.d.ts.map +1 -0
  47. package/dist/dts/calc/asyncData.d.ts +55 -0
  48. package/dist/dts/calc/asyncData.d.ts.map +1 -0
  49. package/dist/dts/calc/calc.d.ts +57 -0
  50. package/dist/dts/calc/calc.d.ts.map +1 -0
  51. package/dist/dts/calc/calcFamily.d.ts +57 -0
  52. package/dist/dts/calc/calcFamily.d.ts.map +1 -0
  53. package/dist/dts/channel/channel.d.ts +115 -0
  54. package/dist/dts/channel/channel.d.ts.map +1 -0
  55. package/dist/dts/compose/composition.d.ts +72 -0
  56. package/dist/dts/compose/composition.d.ts.map +1 -0
  57. package/dist/dts/compose/host.d.ts +17 -0
  58. package/dist/dts/compose/host.d.ts.map +1 -0
  59. package/dist/dts/compose/props.d.ts +13 -0
  60. package/dist/dts/compose/props.d.ts.map +1 -0
  61. package/dist/dts/compose/provide.d.ts +22 -0
  62. package/dist/dts/compose/provide.d.ts.map +1 -0
  63. package/dist/dts/compose/slot.d.ts +49 -0
  64. package/dist/dts/compose/slot.d.ts.map +1 -0
  65. package/dist/dts/compose/ui.d.ts +50 -0
  66. package/dist/dts/compose/ui.d.ts.map +1 -0
  67. package/dist/dts/definition/definition.d.ts +33 -0
  68. package/dist/dts/definition/definition.d.ts.map +1 -0
  69. package/dist/dts/event/event.d.ts +33 -0
  70. package/dist/dts/event/event.d.ts.map +1 -0
  71. package/dist/dts/event/eventGroup.d.ts +9 -0
  72. package/dist/dts/event/eventGroup.d.ts.map +1 -0
  73. package/dist/dts/feature/feature.d.ts +220 -0
  74. package/dist/dts/feature/feature.d.ts.map +1 -0
  75. package/dist/dts/index.d.ts +43 -0
  76. package/dist/dts/index.d.ts.map +1 -0
  77. package/dist/dts/internal/capture.d.ts +28 -0
  78. package/dist/dts/internal/capture.d.ts.map +1 -0
  79. package/dist/dts/internal/ctx.d.ts +12 -0
  80. package/dist/dts/internal/ctx.d.ts.map +1 -0
  81. package/dist/dts/internal/errors.d.ts +69 -0
  82. package/dist/dts/internal/errors.d.ts.map +1 -0
  83. package/dist/dts/internal/inspect.d.ts +17 -0
  84. package/dist/dts/internal/inspect.d.ts.map +1 -0
  85. package/dist/dts/internal/queryDriver.d.ts +65 -0
  86. package/dist/dts/internal/queryDriver.d.ts.map +1 -0
  87. package/dist/dts/internal/reuse.d.ts +10 -0
  88. package/dist/dts/internal/reuse.d.ts.map +1 -0
  89. package/dist/dts/internal/scheduler.d.ts +47 -0
  90. package/dist/dts/internal/scheduler.d.ts.map +1 -0
  91. package/dist/dts/internal/seeds.d.ts +17 -0
  92. package/dist/dts/internal/seeds.d.ts.map +1 -0
  93. package/dist/dts/internal/sources.d.ts +39 -0
  94. package/dist/dts/internal/sources.d.ts.map +1 -0
  95. package/dist/dts/internal/store.d.ts +47 -0
  96. package/dist/dts/internal/store.d.ts.map +1 -0
  97. package/dist/dts/internal/track.d.ts +33 -0
  98. package/dist/dts/internal/track.d.ts.map +1 -0
  99. package/dist/dts/procedure/procedure.d.ts +40 -0
  100. package/dist/dts/procedure/procedure.d.ts.map +1 -0
  101. package/dist/dts/reducer/reducer.d.ts +44 -0
  102. package/dist/dts/reducer/reducer.d.ts.map +1 -0
  103. package/dist/dts/remote/remoteState.d.ts +119 -0
  104. package/dist/dts/remote/remoteState.d.ts.map +1 -0
  105. package/dist/dts/runtime/bus.d.ts +27 -0
  106. package/dist/dts/runtime/bus.d.ts.map +1 -0
  107. package/dist/dts/runtime/loop.d.ts +45 -0
  108. package/dist/dts/runtime/loop.d.ts.map +1 -0
  109. package/dist/dts/scene/scene.d.ts +44 -0
  110. package/dist/dts/scene/scene.d.ts.map +1 -0
  111. package/dist/dts/state/state.d.ts +37 -0
  112. package/dist/dts/state/state.d.ts.map +1 -0
  113. package/dist/dts/state/stateFamily.d.ts +79 -0
  114. package/dist/dts/state/stateFamily.d.ts.map +1 -0
  115. package/dist/dts/state/stateGroup.d.ts +36 -0
  116. package/dist/dts/state/stateGroup.d.ts.map +1 -0
  117. package/dist/dts/state/token.d.ts +30 -0
  118. package/dist/dts/state/token.d.ts.map +1 -0
  119. package/dist/dts/ui/node.d.ts +9 -0
  120. package/dist/dts/ui/node.d.ts.map +1 -0
  121. package/dist/dts/ui/trigger.d.ts +7 -0
  122. package/dist/dts/ui/trigger.d.ts.map +1 -0
  123. package/dist/esm/boundary/boundary.js +83 -0
  124. package/dist/esm/boundary/boundary.js.map +1 -0
  125. package/dist/esm/calc/asyncCalc.js +95 -0
  126. package/dist/esm/calc/asyncCalc.js.map +1 -0
  127. package/dist/esm/calc/asyncData.js +34 -0
  128. package/dist/esm/calc/asyncData.js.map +1 -0
  129. package/dist/esm/calc/calc.js +58 -0
  130. package/dist/esm/calc/calc.js.map +1 -0
  131. package/dist/esm/calc/calcFamily.js +124 -0
  132. package/dist/esm/calc/calcFamily.js.map +1 -0
  133. package/dist/esm/channel/channel.js +136 -0
  134. package/dist/esm/channel/channel.js.map +1 -0
  135. package/dist/esm/compose/composition.js +46 -0
  136. package/dist/esm/compose/composition.js.map +1 -0
  137. package/dist/esm/compose/host.js +5 -0
  138. package/dist/esm/compose/host.js.map +1 -0
  139. package/dist/esm/compose/props.js +11 -0
  140. package/dist/esm/compose/props.js.map +1 -0
  141. package/dist/esm/compose/provide.js +28 -0
  142. package/dist/esm/compose/provide.js.map +1 -0
  143. package/dist/esm/compose/slot.js +23 -0
  144. package/dist/esm/compose/slot.js.map +1 -0
  145. package/dist/esm/compose/ui.js +57 -0
  146. package/dist/esm/compose/ui.js.map +1 -0
  147. package/dist/esm/definition/definition.js +42 -0
  148. package/dist/esm/definition/definition.js.map +1 -0
  149. package/dist/esm/event/event.js +30 -0
  150. package/dist/esm/event/event.js.map +1 -0
  151. package/dist/esm/event/eventGroup.js +4 -0
  152. package/dist/esm/event/eventGroup.js.map +1 -0
  153. package/dist/esm/feature/feature.js +98 -0
  154. package/dist/esm/feature/feature.js.map +1 -0
  155. package/dist/esm/index.js +45 -0
  156. package/dist/esm/index.js.map +1 -0
  157. package/dist/esm/internal/capture.js +11 -0
  158. package/dist/esm/internal/capture.js.map +1 -0
  159. package/dist/esm/internal/ctx.js +2 -0
  160. package/dist/esm/internal/ctx.js.map +1 -0
  161. package/dist/esm/internal/errors.js +54 -0
  162. package/dist/esm/internal/errors.js.map +1 -0
  163. package/dist/esm/internal/inspect.js +32 -0
  164. package/dist/esm/internal/inspect.js.map +1 -0
  165. package/dist/esm/internal/queryDriver.js +134 -0
  166. package/dist/esm/internal/queryDriver.js.map +1 -0
  167. package/dist/esm/internal/reuse.js +68 -0
  168. package/dist/esm/internal/reuse.js.map +1 -0
  169. package/dist/esm/internal/scheduler.js +69 -0
  170. package/dist/esm/internal/scheduler.js.map +1 -0
  171. package/dist/esm/internal/seeds.js +17 -0
  172. package/dist/esm/internal/seeds.js.map +1 -0
  173. package/dist/esm/internal/sources.js +59 -0
  174. package/dist/esm/internal/sources.js.map +1 -0
  175. package/dist/esm/internal/store.js +73 -0
  176. package/dist/esm/internal/store.js.map +1 -0
  177. package/dist/esm/internal/track.js +18 -0
  178. package/dist/esm/internal/track.js.map +1 -0
  179. package/dist/esm/package.json +4 -0
  180. package/dist/esm/procedure/procedure.js +50 -0
  181. package/dist/esm/procedure/procedure.js.map +1 -0
  182. package/dist/esm/reducer/reducer.js +63 -0
  183. package/dist/esm/reducer/reducer.js.map +1 -0
  184. package/dist/esm/remote/remoteState.js +270 -0
  185. package/dist/esm/remote/remoteState.js.map +1 -0
  186. package/dist/esm/runtime/bus.js +20 -0
  187. package/dist/esm/runtime/bus.js.map +1 -0
  188. package/dist/esm/runtime/loop.js +116 -0
  189. package/dist/esm/runtime/loop.js.map +1 -0
  190. package/dist/esm/scene/scene.js +31 -0
  191. package/dist/esm/scene/scene.js.map +1 -0
  192. package/dist/esm/state/state.js +43 -0
  193. package/dist/esm/state/state.js.map +1 -0
  194. package/dist/esm/state/stateFamily.js +96 -0
  195. package/dist/esm/state/stateFamily.js.map +1 -0
  196. package/dist/esm/state/stateGroup.js +46 -0
  197. package/dist/esm/state/stateGroup.js.map +1 -0
  198. package/dist/esm/state/token.js +20 -0
  199. package/dist/esm/state/token.js.map +1 -0
  200. package/dist/esm/ui/node.js +2 -0
  201. package/dist/esm/ui/node.js.map +1 -0
  202. package/dist/esm/ui/trigger.js +2 -0
  203. package/dist/esm/ui/trigger.js.map +1 -0
  204. 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":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/D,OAAO,EAAiB,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAiB,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAIN,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,YAAY,EAEZ,SAAS,EACT,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAc,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAkB,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAkB,WAAW,EAAE,MAAM,aAAa,CAAA;AAiFzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAOlB,IAAO,EACP,MAA+C,EACK,EAAE;IACtD,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAC9B,oBAAoB,IAAI,EAAE,CAC3B,CAAA;IACD,+EAA+E;IAC/E,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACxC,MAAM,QAAQ,GAA+B;QAC3C,IAAI,EAAE,WAAW;QACjB,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK;QACL,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAA;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IAC/C,OAAO,cAAc,CAAC,IAAI,EAAE;QAC1B,QAAQ;QACR,KAAK;QACL,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK;KACN,CAAC,CAAA;AACJ,CAAC,CAAA;AAwCD,MAAM,UAAU,IAAI,CAQlB,IAA4C,EAC5C,MAEC;IAKD,8EAA8E;IAC9E,6EAA6E;IAC7E,sEAAsE;IACtE,wDAAwD;IACxD,MAAM,aAAa,GACjB,MAAM,CAAC,YAAY,KAAK,SAAS;QAC/B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,eAAe,EAAE,cAAc,CAAC,CAAA;IAEtE,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CACzB,IAAI,CAAC,KAAK,EACV,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,0EAA0E;QAC1E,gEAAgE;QAChE,MAAM,GAAG,GAAG,MAMX,CAAA;QAED,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,wDAAwD;QACxD,+DAA+D;QAC/D,4BAA4B;QAC5B,MAAM,QAAQ,GACZ,aAAa,KAAK,SAAS;YACzB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7E,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC;YACpC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;YACxB,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,QAAQ,EACN,QAAQ,KAAK,SAAS;gBACpB,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC;oBACE,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE;oBAClC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC;iBACtD;SACR,CAAC,CAAA;QAEF,yEAAyE;QACzE,OAAO,WAAW,CAAc,MAAM,CAAC,KAAK,CAAC,CAAA;IAC/C,CAAC,CAAC,CACH,CAAA;IAED,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IACnF,0EAA0E;IAC1E,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,qBAAqB;IACrB,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,eAAe,EAAE;QACzE,MAAM,EAAE,CAAC,aAAa,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC,YAAY;KAC5B,CAAC,CAAA;IACF,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAC1E,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,CACvD,CAAA;AACH,CAAC"}
@@ -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,5 @@
1
+ import { Context } from 'effect';
2
+ /** The active composition's slot bindings, provided per render by the host. */
3
+ export class CurrentSlots extends Context.Tag('reform/Slots')() {
4
+ }
5
+ //# sourceMappingURL=host.js.map
@@ -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"}