@playfast/reform 1.1.1 → 1.2.0

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 (85) hide show
  1. package/README.md +85 -55
  2. package/package.json +17 -17
  3. package/src/boundary/boundary.test.ts +62 -16
  4. package/src/boundary/boundary.ts +141 -32
  5. package/src/calc/asyncCalc.invalidate.test.ts +516 -18
  6. package/src/calc/asyncCalc.test.ts +207 -175
  7. package/src/calc/asyncCalc.ts +168 -39
  8. package/src/calc/calc.test.ts +3 -5
  9. package/src/calc/calc.ts +44 -18
  10. package/src/calc/calcFamily.test.ts +80 -22
  11. package/src/calc/calcFamily.ts +56 -25
  12. package/src/calc/compose.ts +1 -14
  13. package/src/channel/channel.ts +128 -11
  14. package/src/compose/composition.test.ts +19 -0
  15. package/src/compose/composition.ts +346 -62
  16. package/src/compose/props.ts +13 -6
  17. package/src/compose/provide.ts +187 -46
  18. package/src/compose/slot.ts +156 -19
  19. package/src/compose/structure.test.ts +6 -3
  20. package/src/compose/structure.ts +106 -13
  21. package/src/compose/ui.test.ts +19 -3
  22. package/src/compose/ui.ts +147 -54
  23. package/src/compose/ui.typecheck.ts +40 -4
  24. package/src/definition/definition.ts +22 -9
  25. package/src/event/event.fromSource.test.ts +172 -0
  26. package/src/event/event.test.ts +10 -0
  27. package/src/event/event.ts +102 -18
  28. package/src/feature/feature.mount.test.ts +123 -56
  29. package/src/feature/feature.test.ts +67 -63
  30. package/src/feature/feature.ts +1317 -197
  31. package/src/feature/feature.typecheck.ts +253 -61
  32. package/src/graph/closure.ts +403 -0
  33. package/src/index.ts +171 -245
  34. package/src/internal/bucketCache.ts +39 -0
  35. package/src/internal/capture.ts +9 -4
  36. package/src/internal/env.ts +1 -3
  37. package/src/internal/errors.ts +21 -10
  38. package/src/internal/queryDriver.ts +120 -15
  39. package/src/internal/queryDriverStore.ts +8 -5
  40. package/src/internal/queryDriverTypes.ts +3 -1
  41. package/src/internal/reuse.test.ts +10 -3
  42. package/src/internal/reuse.ts +5 -2
  43. package/src/internal/scheduler.ts +4 -1
  44. package/src/internal/sources.ts +25 -11
  45. package/src/internal/track.ts +3 -2
  46. package/src/internal.ts +222 -0
  47. package/src/namespace/namespace.ts +46 -25
  48. package/src/procedure/procedure.ts +38 -11
  49. package/src/reducer/reducer.ts +78 -34
  50. package/src/remote/remoteState.test.ts +515 -339
  51. package/src/remote/remoteState.ts +572 -107
  52. package/src/remote/remoteState.typecheck.ts +47 -23
  53. package/src/runtime/appRuntime.activation.test.ts +100 -0
  54. package/src/runtime/appRuntime.test.ts +157 -105
  55. package/src/runtime/appRuntime.ts +394 -33
  56. package/src/runtime/eventBudget.test.ts +1 -4
  57. package/src/runtime/eventBudget.ts +9 -8
  58. package/src/runtime/hardening.test.ts +4 -9
  59. package/src/runtime/instrumentation.test.ts +174 -192
  60. package/src/runtime/instrumentation.ts +6 -11
  61. package/src/runtime/loop.test.ts +36 -0
  62. package/src/runtime/loop.ts +69 -40
  63. package/src/scene/featureScene.test.ts +4 -2
  64. package/src/scene/scene.ts +234 -64
  65. package/src/scene/seedScene.test.ts +69 -86
  66. package/src/state/state.nominal.typecheck.ts +68 -0
  67. package/src/state/state.test.ts +17 -0
  68. package/src/state/state.ts +79 -26
  69. package/src/state/stateFamily.test.ts +14 -0
  70. package/src/state/stateFamily.ts +55 -22
  71. package/src/state/stateGroup.ts +53 -17
  72. package/src/state/token.ts +40 -10
  73. package/src/synced/syncedStore.ts +46 -23
  74. package/src/testkit/flight.testkit.ts +75 -0
  75. package/src/wire/tree.test.ts +8 -4
  76. package/src/wire/triggers.test.ts +6 -2
  77. package/src/wire/triggers.ts +14 -10
  78. package/src/calc/asyncCalcDefinitions.ts +0 -48
  79. package/src/channel/procedureRegistry.ts +0 -90
  80. package/src/feature/featureBinding.ts +0 -48
  81. package/src/internal/ctx.ts +0 -9
  82. package/src/remote/remoteStateDefinition.ts +0 -135
  83. package/src/remote/remoteStateLayers.ts +0 -118
  84. package/src/remote/remoteStateLiveTypes.ts +0 -59
  85. package/src/remote/remoteStateSend.ts +0 -64
@@ -0,0 +1,403 @@
1
+ import { Layer } from 'effect'
2
+ import { CompositionIdTypeId } from '../compose/composition'
3
+ import type { UiContract } from '../compose/ui'
4
+ import { SlotServiceTypeId } from '../compose/slot'
5
+ import type { FeatureInput, FeatureRequirement, ProvidedBy } from '../feature/feature'
6
+ import type { EngineServices } from '../runtime/loop'
7
+ import type { StateStore } from '../state/state'
8
+ import type { StateFamilyStore } from '../state/stateFamily'
9
+
10
+ export const FeatureGraphValidTypeId: unique symbol = Symbol.for('reform/FeatureGraphValid')
11
+ export type FeatureGraphValidTypeId = typeof FeatureGraphValidTypeId
12
+
13
+ export const FeatureModuleGraphValidTypeId: unique symbol = Symbol.for(
14
+ 'reform/FeatureModuleGraphValid',
15
+ )
16
+ export type FeatureModuleGraphValidTypeId = typeof FeatureModuleGraphValidTypeId
17
+
18
+ export const FeatureChildSummaryTypeId: unique symbol = Symbol.for('reform/FeatureChildSummary')
19
+ export type FeatureChildSummaryTypeId = typeof FeatureChildSummaryTypeId
20
+
21
+ export const FeatureGraphSummaryTypeId: unique symbol = Symbol.for('reform/FeatureGraphSummary')
22
+ export type FeatureGraphSummaryTypeId = typeof FeatureGraphSummaryTypeId
23
+
24
+ export const SlotBindingSummaryTypeId: unique symbol = Symbol.for('reform/SlotBindingSummary')
25
+ export type SlotBindingSummaryTypeId = typeof SlotBindingSummaryTypeId
26
+
27
+ export interface SlotBindingSummary {
28
+ readonly [SlotBindingSummaryTypeId]: SlotBindingSummaryTypeId
29
+ }
30
+
31
+ /**
32
+ * The constant-size proof published after a Feature's current module has passed
33
+ * graph validation. Type-only facts use empty-or-singleton tuples so the runtime
34
+ * summary stays compact while direct indexed access recovers their exact types.
35
+ */
36
+ export interface FeatureGraphSummary<
37
+ out FeatureIdentity,
38
+ out MountProps,
39
+ out CompositionProps,
40
+ out C extends UiContract,
41
+ out S extends ReadonlyArray<unknown>,
42
+ out N extends string,
43
+ out CompositionIdentity,
44
+ out OpenServices,
45
+ out SupportOpenServices,
46
+ out UsesInput extends boolean,
47
+ out Input,
48
+ > extends SlotBindingSummary {
49
+ readonly [FeatureGraphSummaryTypeId]: {
50
+ readonly featureIdentity: readonly [] | readonly [FeatureIdentity]
51
+ readonly mountProps: readonly [] | readonly [MountProps]
52
+ readonly compositionProps: readonly [] | readonly [CompositionProps]
53
+ readonly contract: readonly [] | readonly [C]
54
+ readonly states: readonly [] | readonly [S]
55
+ readonly name: N
56
+ readonly compositionIdentity: readonly [] | readonly [CompositionIdentity]
57
+ readonly openServices: readonly [] | readonly [OpenServices]
58
+ readonly supportOpenServices: readonly [] | readonly [SupportOpenServices]
59
+ readonly usesInput: readonly [] | readonly [UsesInput]
60
+ readonly input: readonly [] | readonly [Input]
61
+ readonly validated: FeatureModuleGraphValidTypeId
62
+ }
63
+ readonly [FeatureChildSummaryTypeId]: {
64
+ readonly featureIdentity: readonly [] | readonly [FeatureIdentity]
65
+ readonly compositionIdentity: readonly [] | readonly [CompositionIdentity]
66
+ }
67
+ }
68
+
69
+ export type FeatureChildSummary<
70
+ FeatureIdentity,
71
+ MountProps,
72
+ CompositionProps,
73
+ C extends UiContract,
74
+ S extends ReadonlyArray<unknown>,
75
+ N extends string,
76
+ CompositionIdentity,
77
+ OpenServices,
78
+ SupportOpenServices,
79
+ UsesInput extends boolean,
80
+ Input,
81
+ > = FeatureGraphSummary<
82
+ FeatureIdentity,
83
+ MountProps,
84
+ CompositionProps,
85
+ C,
86
+ S,
87
+ N,
88
+ CompositionIdentity,
89
+ OpenServices,
90
+ SupportOpenServices,
91
+ UsesInput,
92
+ Input
93
+ >
94
+
95
+ export type AnyFeatureGraphSummary = FeatureGraphSummary<
96
+ unknown,
97
+ unknown,
98
+ unknown,
99
+ UiContract,
100
+ ReadonlyArray<unknown>,
101
+ string,
102
+ unknown,
103
+ unknown,
104
+ unknown,
105
+ boolean,
106
+ unknown
107
+ >
108
+
109
+ export type FeatureSummaryIdentity<Summary extends AnyFeatureGraphSummary> =
110
+ Summary[FeatureGraphSummaryTypeId]['featureIdentity'][number]
111
+ export type FeatureSummaryMountProps<Summary extends AnyFeatureGraphSummary> =
112
+ Summary[FeatureGraphSummaryTypeId]['mountProps'][number]
113
+ export type FeatureSummaryCompositionProps<Summary extends AnyFeatureGraphSummary> =
114
+ Summary[FeatureGraphSummaryTypeId]['compositionProps'][number]
115
+ export type FeatureSummaryContract<Summary extends AnyFeatureGraphSummary> =
116
+ Summary[FeatureGraphSummaryTypeId]['contract'][number]
117
+ export type FeatureSummaryStates<Summary extends AnyFeatureGraphSummary> =
118
+ Summary[FeatureGraphSummaryTypeId]['states'][number]
119
+ export type FeatureSummaryName<Summary extends AnyFeatureGraphSummary> =
120
+ Summary[FeatureGraphSummaryTypeId]['name']
121
+ export type FeatureSummaryCompositionIdentity<Summary extends AnyFeatureGraphSummary> =
122
+ Summary[FeatureGraphSummaryTypeId]['compositionIdentity'][number]
123
+ export type FeatureSummaryOpenServices<Summary extends AnyFeatureGraphSummary> =
124
+ Summary[FeatureGraphSummaryTypeId]['openServices'][number]
125
+ export type FeatureSummarySupportOpenServices<Summary extends AnyFeatureGraphSummary> =
126
+ Summary[FeatureGraphSummaryTypeId]['supportOpenServices'][number]
127
+ export type FeatureSummaryUsesInput<Summary extends AnyFeatureGraphSummary> =
128
+ Summary[FeatureGraphSummaryTypeId]['usesInput'][number]
129
+ export type FeatureSummaryInput<Summary extends AnyFeatureGraphSummary> =
130
+ Summary[FeatureGraphSummaryTypeId]['input'][number]
131
+
132
+ export const CompositionChildSummaryTypeId: unique symbol = Symbol.for(
133
+ 'reform/CompositionChildSummary',
134
+ )
135
+ export type CompositionChildSummaryTypeId = typeof CompositionChildSummaryTypeId
136
+
137
+ export interface CompositionChildSummary<out CompositionIdentity> extends SlotBindingSummary {
138
+ readonly [CompositionChildSummaryTypeId]: {
139
+ readonly compositionIdentity: CompositionIdentity
140
+ }
141
+ }
142
+
143
+ type IsAny<Value> = 0 extends 1 & Value ? true : false
144
+
145
+ type IsErased<Value> = IsAny<Value> extends true ? true : unknown extends Value ? true : false
146
+
147
+ type ExactStateStoreName<Service> =
148
+ IsAny<Service> extends true
149
+ ? never
150
+ : Service extends StateStore<infer Name, infer _Value>
151
+ ? string extends Name
152
+ ? never
153
+ : Name
154
+ : never
155
+
156
+ type ExactStateFamilyStoreName<Service> =
157
+ IsAny<Service> extends true
158
+ ? never
159
+ : Service extends StateFamilyStore<infer Name, infer _Key, infer _Value>
160
+ ? string extends Name
161
+ ? never
162
+ : Name
163
+ : never
164
+
165
+ type ServiceIsProvided<Required, Provided> =
166
+ IsErased<Provided> extends true
167
+ ? false
168
+ : Required extends StateStore<infer Name, infer _Value>
169
+ ? Name extends ExactStateStoreName<Provided>
170
+ ? true
171
+ : false
172
+ : Required extends StateFamilyStore<infer Name, infer _Key, infer _Value>
173
+ ? Name extends ExactStateFamilyStoreName<Provided>
174
+ ? true
175
+ : false
176
+ : [Required] extends [Provided]
177
+ ? true
178
+ : false
179
+
180
+ /**
181
+ * Remove only services the provider can satisfy by their runtime Context tag.
182
+ * Named State/StateFamily identifiers intentionally do not collapse into their
183
+ * structurally broader Store/FamilyStore service APIs.
184
+ */
185
+ export type ExactServiceExclude<Required, Provided> = Required extends unknown
186
+ ? ServiceIsProvided<Required, Provided> extends true
187
+ ? never
188
+ : Required
189
+ : never
190
+
191
+ type ExactServiceExtract<Required, Provided> = Required extends unknown
192
+ ? ServiceIsProvided<Required, Provided> extends true
193
+ ? Required
194
+ : never
195
+ : never
196
+
197
+ type GraphValidationPassed = object
198
+
199
+ type ExactIdentity<Identity> = IsErased<Identity> extends true ? never : Identity
200
+
201
+ type InputService<UsesInput extends boolean> = UsesInput extends true ? FeatureInput : never
202
+
203
+ export type OpenServicesFromFeatureSlotWitnesses<Services> = Services extends {
204
+ readonly [SlotServiceTypeId]: {
205
+ readonly binding: FeatureChildSummary<
206
+ infer _FeatureIdentity,
207
+ infer _MountProps,
208
+ infer _CompositionProps,
209
+ infer _Contract,
210
+ infer _States,
211
+ infer _Name,
212
+ infer _CompositionIdentity,
213
+ infer OpenServices,
214
+ infer SupportOpenServices,
215
+ infer _UsesInput,
216
+ infer _Input
217
+ >
218
+ }
219
+ }
220
+ ? OpenServices | SupportOpenServices
221
+ : never
222
+
223
+ export type DescendantOpenServices<L extends Layer.Layer.Any> =
224
+ OpenServicesFromFeatureSlotWitnesses<Layer.Layer.Success<L>>
225
+
226
+ export type ModuleOpenServices<L extends Layer.Layer.Any, UsesInput extends boolean> = Exclude<
227
+ Layer.Layer.Context<L>,
228
+ EngineServices | InputService<UsesInput>
229
+ >
230
+
231
+ type LayerErrorIsNever<L extends Layer.Layer.Any> =
232
+ IsErased<Layer.Layer.Error<L>> extends true
233
+ ? {
234
+ readonly featureLayerErrorMustBeNever: 'A feature layer error channel cannot be erased'
235
+ }
236
+ : [Layer.Layer.Error<L>] extends [never]
237
+ ? GraphValidationPassed
238
+ : {
239
+ readonly featureLayerErrorMustBeNever: 'A feature layer must have an error channel of exactly never'
240
+ }
241
+
242
+ type LayerOutputsAreExact<L extends Layer.Layer.Any> =
243
+ IsErased<Layer.Layer.Success<L>> extends true
244
+ ? {
245
+ readonly featureLayerOutputsMustBeExact: 'A feature layer output cannot be erased'
246
+ }
247
+ : GraphValidationPassed
248
+
249
+ type DescendantOpenServicesAreExact<L extends Layer.Layer.Any> =
250
+ IsErased<DescendantOpenServices<L>> extends true
251
+ ? {
252
+ readonly featureDescendantOpenServicesMustBeExact: "A child feature's open services cannot be erased"
253
+ }
254
+ : GraphValidationPassed
255
+
256
+ type UnexpectedOpenServices<
257
+ L extends Layer.Layer.Any,
258
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
259
+ UsesInput extends boolean,
260
+ > = ExactServiceExclude<
261
+ Layer.Layer.Context<L>,
262
+ EngineServices | InputService<UsesInput> | ProvidedBy<DirectRequires> | DescendantOpenServices<L>
263
+ >
264
+
265
+ type OpenServicesAreDeclared<
266
+ L extends Layer.Layer.Any,
267
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
268
+ UsesInput extends boolean,
269
+ > =
270
+ IsErased<Layer.Layer.Context<L>> extends true
271
+ ? {
272
+ readonly featureLayerContextMustBeExact: 'A feature layer context cannot be erased'
273
+ }
274
+ : [UnexpectedOpenServices<L, DirectRequires, UsesInput>] extends [never]
275
+ ? GraphValidationPassed
276
+ : {
277
+ readonly undeclaredFeatureOpenServices: UnexpectedOpenServices<
278
+ L,
279
+ DirectRequires,
280
+ UsesInput
281
+ >
282
+ }
283
+
284
+ type MissingDirectRequirements<
285
+ L extends Layer.Layer.Any,
286
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
287
+ > = ExactServiceExclude<ProvidedBy<DirectRequires>, Layer.Layer.Context<L>>
288
+
289
+ type DirectRequirementsAreOpen<
290
+ L extends Layer.Layer.Any,
291
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
292
+ > = [MissingDirectRequirements<L, DirectRequires>] extends [never]
293
+ ? GraphValidationPassed
294
+ : {
295
+ readonly unusedDirectFeatureRequirements: MissingDirectRequirements<L, DirectRequires>
296
+ }
297
+
298
+ type PrivateEngineOutputs<L extends Layer.Layer.Any> = Extract<
299
+ Layer.Layer.Success<L>,
300
+ EngineServices
301
+ >
302
+
303
+ type EngineOutputsAreShared<L extends Layer.Layer.Any> = [PrivateEngineOutputs<L>] extends [never]
304
+ ? GraphValidationPassed
305
+ : {
306
+ readonly featureCannotProvideEngineServices: PrivateEngineOutputs<L>
307
+ }
308
+
309
+ type SelfRequiredOutputs<L extends Layer.Layer.Any> = ExactServiceExtract<
310
+ Layer.Layer.Context<L>,
311
+ Layer.Layer.Success<L>
312
+ >
313
+
314
+ type OutputsAreNotSelfRequired<L extends Layer.Layer.Any> = [SelfRequiredOutputs<L>] extends [never]
315
+ ? GraphValidationPassed
316
+ : {
317
+ readonly featureCannotRequireItsOwnOutputs: SelfRequiredOutputs<L>
318
+ }
319
+
320
+ type SlotIdentity<Descriptor> =
321
+ // Erased descriptors must FAIL validation: `unknown` never matches a bound
322
+ // identity, so it surfaces in `unboundFeatureSlotIdentities`.
323
+ IsErased<Descriptor> extends true
324
+ ? unknown
325
+ : Descriptor extends ReadonlyArray<infer Entry>
326
+ ? SlotIdentity<Entry>
327
+ : Descriptor extends {
328
+ readonly [SlotServiceTypeId]: {
329
+ readonly identity: infer Identity
330
+ }
331
+ }
332
+ ? Identity
333
+ : Descriptor
334
+
335
+ type RequiredSlotIdentities<Services> = Services extends {
336
+ readonly [CompositionIdTypeId]: {
337
+ readonly requiredSlots: infer RequiredSlots
338
+ }
339
+ }
340
+ ? SlotIdentity<RequiredSlots>
341
+ : never
342
+
343
+ type BoundSlotIdentities<Services> = Services extends {
344
+ readonly [SlotServiceTypeId]: {
345
+ readonly identity: infer Identity
346
+ }
347
+ }
348
+ ? ExactIdentity<Identity>
349
+ : never
350
+
351
+ type MissingSlotBindings<L extends Layer.Layer.Any> = Exclude<
352
+ RequiredSlotIdentities<Layer.Layer.Success<L>>,
353
+ BoundSlotIdentities<Layer.Layer.Success<L>>
354
+ >
355
+
356
+ type RequiredSlotsAreBound<L extends Layer.Layer.Any> = [MissingSlotBindings<L>] extends [never]
357
+ ? GraphValidationPassed
358
+ : {
359
+ readonly unboundFeatureSlotIdentities: MissingSlotBindings<L>
360
+ }
361
+
362
+ type PlainChildCompositionIdentities<Services> = Services extends {
363
+ readonly [SlotServiceTypeId]: {
364
+ readonly binding: CompositionChildSummary<infer CompositionIdentity>
365
+ }
366
+ }
367
+ ? CompositionIdentity
368
+ : never
369
+
370
+ type LiveCompositionIdentities<Services> = Services extends {
371
+ readonly [CompositionIdTypeId]: {
372
+ readonly identity: infer Identity
373
+ }
374
+ }
375
+ ? ExactIdentity<Identity>
376
+ : never
377
+
378
+ type MissingPlainChildCompositions<L extends Layer.Layer.Any> = Exclude<
379
+ PlainChildCompositionIdentities<Layer.Layer.Success<L>>,
380
+ LiveCompositionIdentities<Layer.Layer.Success<L> | Layer.Layer.Context<L>>
381
+ >
382
+
383
+ type PlainChildrenAreLive<L extends Layer.Layer.Any> = [MissingPlainChildCompositions<L>] extends [
384
+ never,
385
+ ]
386
+ ? GraphValidationPassed
387
+ : {
388
+ readonly missingPlainChildCompositions: MissingPlainChildCompositions<L>
389
+ }
390
+
391
+ export type ValidateFeatureGraphLayer<
392
+ L extends Layer.Layer.Any,
393
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
394
+ UsesInput extends boolean,
395
+ > = LayerErrorIsNever<L> &
396
+ LayerOutputsAreExact<L> &
397
+ DescendantOpenServicesAreExact<L> &
398
+ OpenServicesAreDeclared<L, DirectRequires, UsesInput> &
399
+ DirectRequirementsAreOpen<L, DirectRequires> &
400
+ EngineOutputsAreShared<L> &
401
+ OutputsAreNotSelfRequired<L> &
402
+ RequiredSlotsAreBound<L> &
403
+ PlainChildrenAreLive<L>