@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
@@ -1,9 +1,17 @@
1
1
  import { Context, Layer, Schema as S } from 'effect'
2
- import type { Store } from '../internal/store'
3
2
  import * as Composition from '../compose/composition'
4
3
  import { provide } from '../compose/provide'
5
- import { type SlotChild, type SlotClass, slot } from '../compose/slot'
4
+ import {
5
+ type ContractOfSlotTarget,
6
+ type FeatureSlotTarget,
7
+ type PropsOfSlotTarget,
8
+ type SlotChild,
9
+ type SlotService,
10
+ slot,
11
+ } from '../compose/slot'
12
+ import { mount } from '../compose/structure'
6
13
  import { type Contract, type UiClass, ui } from '../compose/ui'
14
+ import { scene } from '../scene/scene'
7
15
  import * as State from '../state/state'
8
16
  import * as StateGroup from '../state/stateGroup'
9
17
  import { type EngineServices, type FeatureModule, featureModule, lazyImport } from './feature'
@@ -25,97 +33,281 @@ const ClockBase: Context.TagClass<Clock, 'feature.typecheck.Clock', ClockService
25
33
  )<Clock, ClockService>()
26
34
  class Clock extends ClockBase {}
27
35
 
28
- declare const readsFeed: Layer.Layer<{ readonly _out: true }, never, Store<string> | EngineServices>
36
+ declare const readsFeed: Layer.Layer<
37
+ { readonly _out: true },
38
+ never,
39
+ State.StateStore<'feed', string> | EngineServices
40
+ >
41
+
42
+ declare const readsTodos: Layer.Layer<
43
+ { readonly _out: true },
44
+ never,
45
+ State.StateStore<'feed', string> | State.StateStore<'filter', string> | EngineServices
46
+ >
29
47
 
30
- export const okState: FeatureModule<{ readonly _out: true }, readonly [typeof FeedState]> =
31
- featureModule([FeedState], readsFeed)
48
+ const okState: FeatureModule<{ readonly _out: true }, readonly [typeof FeedState]> = featureModule(
49
+ [FeedState],
50
+ readsFeed,
51
+ )
32
52
 
33
- export const okGroup: FeatureModule<{ readonly _out: true }, readonly [typeof TodosStates]> =
34
- featureModule([TodosStates], readsFeed)
53
+ const okGroup: FeatureModule<{ readonly _out: true }, readonly [typeof TodosStates]> =
54
+ featureModule([TodosStates], readsTodos)
35
55
 
36
- export const badEmpty: FeatureModule<{ readonly _out: true }, readonly []> =
56
+ const badEmpty =
37
57
  // @ts-expect-error feature layer reads a store it does not declare in `requires`
38
58
  featureModule([], readsFeed)
39
59
 
40
60
  declare const readsClock: Layer.Layer<{ readonly _clock: true }, never, Clock | EngineServices>
41
61
 
42
- export const okService: FeatureModule<{ readonly _clock: true }, readonly [typeof Clock]> =
43
- featureModule([Clock], readsClock)
62
+ const okService: FeatureModule<{ readonly _clock: true }, readonly [typeof Clock]> = featureModule(
63
+ [Clock],
64
+ readsClock,
65
+ )
44
66
 
45
- export const badService: FeatureModule<{ readonly _clock: true }, readonly []> =
67
+ const badService =
46
68
  // @ts-expect-error feature layer reads an ordinary Context service it did not declare
47
69
  featureModule([], readsClock)
48
70
 
49
- const AppUiBase: UiClass<{ props: { readonly a: string }; events: {} }> = ui('AppUi')<{
71
+ const AppUiBase: UiClass<{ props: { readonly a: string }; events: {} }, 'AppUi'> = ui('AppUi')<{
50
72
  props: { readonly a: string }
51
73
  events: {}
52
74
  }>()
53
75
  class AppUi extends AppUiBase {}
54
- const AppCompBase: Composition.CompositionClass<{ readonly a: string }, Contract<typeof AppUi>> =
55
- Composition.make('AppComp', {
56
- title: 'App',
57
- props: S.Struct({ a: S.String }),
58
- ui: AppUi,
59
- })
60
- class AppComp extends AppCompBase {}
61
- const AppSlotBase: SlotClass<typeof AppComp> = slot('App')<typeof AppComp>()
62
- class AppSlot extends AppSlotBase {}
63
-
64
- const AppFeatureBase: Feature.FeatureClass<
65
- { readonly a: string },
66
- Contract<typeof AppUi>,
67
- never,
76
+ class AppComp extends Composition.make('AppComp', {
77
+ title: 'App',
78
+ props: S.Struct({ a: S.String }),
79
+ ui: AppUi,
80
+ })<AppComp>() {}
81
+ const AppLive = Composition.live(AppComp, function* () {
82
+ const props = yield* AppComp.props
83
+ return mount({ props: { a: props.a }, slots: {} })
84
+ })
85
+ declare const FailedPlaceholder: Composition.AnyComposition &
86
+ Composition.CompositionPropsReceiver<Feature.FailedProps>
87
+
88
+ class AppFeature extends Feature.make('appFeature', {
89
+ loadingStrategy: 'lazy',
90
+ composition: AppComp,
91
+ load: lazyImport(async () => ({ default: featureModule([], AppLive) })),
92
+ placeholder: { loading: AppComp, failed: FailedPlaceholder },
93
+ }) {}
94
+ type AppFeatureTarget = FeatureSlotTarget<typeof AppFeature>
95
+ class AppFeatureSlot extends slot('AppFeature')<AppFeatureSlot, AppFeatureTarget>() {}
96
+
97
+ class EmptyAppFeature extends Feature.make('emptyAppFeature', {
98
+ composition: AppComp,
99
+ // @ts-expect-error Layer.empty does not provide CompositionId<'AppComp'>
100
+ module: featureModule([], Layer.empty),
101
+ }) {}
102
+
103
+ declare const exactUnionModule: FeatureModule<
104
+ Composition.CompositionId<'AppComp', AppComp> | Clock,
105
+ readonly []
106
+ >
107
+
108
+ class ExactUnionAppFeature extends Feature.make('exactUnionAppFeature', {
109
+ composition: AppComp,
110
+ module: exactUnionModule,
111
+ }) {}
112
+
113
+ declare const unknownRootModule: FeatureModule<unknown, readonly []>
114
+ class UnknownRootAppFeature extends Feature.make('unknownRootAppFeature', {
115
+ composition: AppComp,
116
+ // @ts-expect-error unknown output does not prove the exact root composition service
117
+ module: unknownRootModule,
118
+ }) {}
119
+
120
+ declare const broadRootModule: FeatureModule<
121
+ Composition.CompositionId<string, unknown>,
68
122
  readonly []
69
- > = Feature.make('appFeature', {
123
+ >
124
+ class BroadRootAppFeature extends Feature.make('broadRootAppFeature', {
125
+ composition: AppComp,
126
+ // @ts-expect-error a broad identifier does not prove the exact root identity
127
+ module: broadRootModule,
128
+ }) {}
129
+
130
+ declare const exactUnionLayer: Layer.Layer<
131
+ Composition.CompositionId<'AppComp', AppComp> | Clock,
132
+ never,
133
+ never
134
+ >
135
+
136
+ const exactUnionScene = scene(AppComp, { provide: [exactUnionLayer] })
137
+
138
+ declare const unknownRootLayer: Layer.Layer<unknown, never, never>
139
+ // @ts-expect-error unknown services do not prove the exact scene root
140
+ const unknownRootScene = scene(AppComp, { provide: [unknownRootLayer] })
141
+
142
+ declare const broadRootLayer: Layer.Layer<Composition.CompositionId<string, unknown>, never, never>
143
+ // @ts-expect-error a broad composition identifier does not prove the exact scene root
144
+ const broadRootScene = scene(AppComp, { provide: [broadRootLayer] })
145
+
146
+ declare const privateEngine: Layer.Layer<EngineServices, never, EngineServices>
147
+ // @ts-expect-error a Feature module cannot provide its own Engine services
148
+ const invalidPrivateEngine = featureModule([], privateEngine)
149
+
150
+ const wired = provide(AppFeatureSlot, AppFeature)
151
+
152
+ type Equal<Left, Right> =
153
+ (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2
154
+ ? true
155
+ : false
156
+ type Assert<Condition extends true> = Condition
157
+ type WiredRequirementsAreExact = Assert<Equal<Layer.Layer.Context<typeof wired>, EngineServices>>
158
+ type WiredOutputIsOnlySummary = Assert<
159
+ Equal<
160
+ Layer.Layer.Success<typeof wired>,
161
+ SlotService<'AppFeature', AppFeatureSlot, typeof AppFeature.summary>
162
+ >
163
+ >
164
+ type FeatureTargetSummaryIsExact = Assert<
165
+ Equal<AppFeatureTarget['summary'], typeof AppFeature.summary>
166
+ >
167
+ type FeatureTargetPropsAreExact = Assert<
168
+ Equal<PropsOfSlotTarget<AppFeatureTarget>, { readonly a: string }>
169
+ >
170
+ type FeatureTargetContractIsExact = Assert<
171
+ Equal<ContractOfSlotTarget<AppFeatureTarget>, Contract<typeof AppUi>>
172
+ >
173
+ type FeatureTargetCannotOpenTree = Assert<
174
+ Equal<AppFeatureTarget extends Feature.FeatureTreeCarrier ? true : false, false>
175
+ >
176
+ const appNative = Feature.tree<typeof AppFeature>(AppFeature.token)
177
+ type AppNative = typeof appNative
178
+ type ExactTreeComposition = Assert<Equal<AppNative['composition'], typeof AppComp>>
179
+ type NoDirectNativeFields = Assert<
180
+ Equal<
181
+ Extract<
182
+ 'composition' | 'binding' | 'load' | 'eagerModule' | 'supportModule' | 'tree',
183
+ keyof typeof AppFeature
184
+ >,
185
+ never
186
+ >
187
+ >
188
+
189
+ class AppFeatureTwin extends Feature.make('appFeature', {
70
190
  loadingStrategy: 'lazy',
71
191
  composition: AppComp,
72
- load: lazyImport(async () => ({ default: featureModule([], Layer.empty) })),
73
- placeholder: { loading: AppComp, failed: AppComp },
74
- })
75
- class AppFeature extends AppFeatureBase {}
192
+ load: lazyImport(async () => ({ default: featureModule([], AppLive) })),
193
+ placeholder: { loading: AppComp, failed: FailedPlaceholder },
194
+ }) {}
195
+ type SameCompositionTokenIsInterchangeable = Assert<
196
+ Equal<typeof AppFeatureTwin.token extends typeof AppFeature.token ? true : false, true>
197
+ >
198
+ type BroadFeatureIdentityIsRejected = Assert<
199
+ Equal<
200
+ Feature.FeatureIdentityToken<unknown> extends Feature.FeatureIdentityToken<AppComp>
201
+ ? true
202
+ : false,
203
+ false
204
+ >
205
+ >
206
+
207
+ class AppCompositionSlot extends slot('AppComposition')<AppCompositionSlot, typeof AppComp>() {}
76
208
 
77
- export const wired: Layer.Layer<SlotChild, never, EngineServices> = provide(AppSlot, AppFeature)
209
+ // @ts-expect-error composition slots accept the Composition value, not a Feature
210
+ provide(AppCompositionSlot, AppFeature)
78
211
 
79
212
  class SupportedAppFeature extends Feature.make('supportedAppFeature', {
80
213
  loadingStrategy: 'lazy',
81
214
  composition: AppComp,
82
- load: lazyImport(async () => ({ default: featureModule([], Layer.empty) })),
215
+ load: lazyImport(async () => ({ default: featureModule([], AppLive) })),
83
216
  eager: okService,
84
- placeholder: { loading: AppComp, failed: AppComp },
217
+ placeholder: { loading: AppComp, failed: FailedPlaceholder },
85
218
  }) {}
219
+ type SupportedAppFeatureTarget = FeatureSlotTarget<typeof SupportedAppFeature>
220
+ class SupportedAppFeatureSlot extends slot('SupportedAppFeature')<
221
+ SupportedAppFeatureSlot,
222
+ SupportedAppFeatureTarget
223
+ >() {}
86
224
 
87
- export const wiredWithSupport: Layer.Layer<SlotChild, never, EngineServices | Clock> = provide(
88
- AppSlot,
89
- SupportedAppFeature,
90
- )
225
+ const wiredWithSupport = provide(SupportedAppFeatureSlot, SupportedAppFeature)
226
+ type SupportedRequirementsAreExact = Assert<
227
+ Equal<Layer.Layer.Context<typeof wiredWithSupport>, EngineServices | Clock>
228
+ >
229
+ type SupportedOutputIsOnlySummary = Assert<
230
+ Equal<
231
+ Layer.Layer.Success<typeof wiredWithSupport>,
232
+ SlotService<'SupportedAppFeature', SupportedAppFeatureSlot, typeof SupportedAppFeature.summary>
233
+ >
234
+ >
91
235
 
92
- const OtherUiBase: UiClass<{ props: { readonly n: number }; events: {} }> = ui('OtherUi')<{
236
+ const OtherUiBase: UiClass<{ props: { readonly n: number }; events: {} }, 'OtherUi'> = ui(
237
+ 'OtherUi',
238
+ )<{
93
239
  props: { readonly n: number }
94
240
  events: {}
95
241
  }>()
96
242
  class OtherUi extends OtherUiBase {}
97
- const OtherCompBase: Composition.CompositionClass<{ readonly n: number }, Contract<typeof OtherUi>> =
98
- Composition.make('OtherComp', {
99
- title: 'Other',
100
- props: S.Struct({ n: S.Number }),
101
- ui: OtherUi,
102
- })
103
- class OtherComp extends OtherCompBase {}
104
- const OtherFeatureBase: Feature.FeatureClass<
105
- { readonly n: number },
106
- Contract<typeof OtherUi>,
107
- never,
108
- readonly []
109
- > = Feature.make('otherFeature', {
243
+ class OtherComp extends Composition.make('OtherComp', {
244
+ title: 'Other',
245
+ props: S.Struct({ n: S.Number }),
246
+ ui: OtherUi,
247
+ })<OtherComp>() {}
248
+ const OtherLive = Composition.live(OtherComp, function* () {
249
+ const props = yield* OtherComp.props
250
+ return mount({ props, slots: {} })
251
+ })
252
+ class OtherFeature extends Feature.make('otherFeature', {
110
253
  loadingStrategy: 'lazy',
111
254
  composition: OtherComp,
112
- load: lazyImport(async () => ({ default: featureModule([], Layer.empty) })),
113
- placeholder: { loading: OtherComp, failed: OtherComp },
114
- })
115
- class OtherFeature extends OtherFeatureBase {}
255
+ load: lazyImport(async () => ({ default: featureModule([], OtherLive) })),
256
+ placeholder: { loading: OtherComp, failed: FailedPlaceholder },
257
+ }) {}
116
258
 
117
- // @ts-expect-error feature composition does not match the slot's child type
118
- export const mismatched: Layer.Layer<SlotChild, never, EngineServices> = provide(
119
- AppSlot,
120
- OtherFeature,
121
- )
259
+ type OtherFeatureIsRejectedByAppTarget = Assert<
260
+ Equal<typeof OtherFeature extends AppFeatureTarget ? true : false, false>
261
+ >
262
+ type DifferentCompositionTokenIsRejected = Assert<
263
+ Equal<typeof OtherFeature.token extends typeof AppFeature.token ? true : false, false>
264
+ >
265
+ const otherNative = Feature.tree<typeof OtherFeature>(OtherFeature.token)
266
+ type OtherNative = typeof otherNative
267
+ type DifferentCompositionBindingIsRejected = Assert<
268
+ Equal<OtherNative['binding'] extends SlotChild<AppFeatureTarget> ? true : false, false>
269
+ >
270
+
271
+ // @ts-expect-error OtherFeature.token does not belong to AppFeature
272
+ Feature.tree<typeof AppFeature>(OtherFeature.token)
273
+
274
+ // @ts-expect-error AppFeatureSlot accepts only features over AppComp
275
+ provide(AppFeatureSlot, OtherFeature)
276
+
277
+ type FeatureTypeProofs = [
278
+ WiredRequirementsAreExact,
279
+ WiredOutputIsOnlySummary,
280
+ FeatureTargetSummaryIsExact,
281
+ FeatureTargetPropsAreExact,
282
+ FeatureTargetContractIsExact,
283
+ FeatureTargetCannotOpenTree,
284
+ ExactTreeComposition,
285
+ NoDirectNativeFields,
286
+ SameCompositionTokenIsInterchangeable,
287
+ BroadFeatureIdentityIsRejected,
288
+ DifferentCompositionTokenIsRejected,
289
+ DifferentCompositionBindingIsRejected,
290
+ SupportedRequirementsAreExact,
291
+ SupportedOutputIsOnlySummary,
292
+ OtherFeatureIsRejectedByAppTarget,
293
+ ]
294
+ declare const featureTypeProofs: FeatureTypeProofs
295
+
296
+ void featureTypeProofs
297
+ void appNative
298
+ void otherNative
299
+ void AppFeatureTwin
300
+ void okState
301
+ void okGroup
302
+ void badEmpty
303
+ void badService
304
+ void EmptyAppFeature
305
+ void ExactUnionAppFeature
306
+ void UnknownRootAppFeature
307
+ void BroadRootAppFeature
308
+ void exactUnionScene
309
+ void unknownRootScene
310
+ void broadRootScene
311
+ void invalidPrivateEngine
312
+
313
+ export {}