@playfast/reform 1.1.1 → 1.2.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 (106) hide show
  1. package/README.md +85 -55
  2. package/package.json +17 -17
  3. package/src/boundary/boundary.test.ts +82 -33
  4. package/src/boundary/boundary.ts +141 -32
  5. package/src/calc/asyncCalc.invalidate.test.ts +733 -32
  6. package/src/calc/asyncCalc.test.ts +397 -225
  7. package/src/calc/asyncCalc.ts +115 -100
  8. package/src/calc/asyncCalcTypes.ts +147 -0
  9. package/src/calc/calc.test.ts +13 -16
  10. package/src/calc/calc.ts +44 -18
  11. package/src/calc/calcFamily.test.ts +90 -32
  12. package/src/calc/calcFamily.ts +56 -25
  13. package/src/calc/compose.ts +1 -14
  14. package/src/channel/channel.ts +25 -6
  15. package/src/channel/{procedureRegistry.ts → procedures.ts} +41 -23
  16. package/src/compose/composition.test.ts +19 -0
  17. package/src/compose/composition.ts +168 -81
  18. package/src/compose/compositionTypes.ts +249 -0
  19. package/src/compose/props.ts +13 -6
  20. package/src/compose/provide.ts +187 -46
  21. package/src/compose/slot.ts +156 -19
  22. package/src/compose/structure.test.ts +6 -3
  23. package/src/compose/structure.ts +106 -13
  24. package/src/compose/ui.test.ts +19 -3
  25. package/src/compose/ui.ts +147 -54
  26. package/src/compose/ui.typecheck.ts +40 -4
  27. package/src/definition/definition.ts +22 -9
  28. package/src/event/event.fromSource.test.ts +172 -0
  29. package/src/event/event.test.ts +10 -0
  30. package/src/event/event.ts +102 -18
  31. package/src/feature/feature.mount.test.ts +123 -56
  32. package/src/feature/feature.test.ts +67 -63
  33. package/src/feature/feature.ts +222 -262
  34. package/src/feature/feature.typecheck.ts +224 -91
  35. package/src/feature/featureBinding.ts +130 -20
  36. package/src/feature/featureClass.ts +162 -0
  37. package/src/feature/featureConfig.ts +192 -0
  38. package/src/feature/featureFactory.ts +163 -0
  39. package/src/feature/featureModule.ts +136 -0
  40. package/src/feature/featureModule.typecheck.ts +63 -0
  41. package/src/feature/featureNativeTree.ts +152 -0
  42. package/src/feature/featureRequirement.ts +82 -0
  43. package/src/feature/featureVariants.ts +234 -0
  44. package/src/feature/mountFeature.ts +54 -0
  45. package/src/graph/closure.ts +222 -0
  46. package/src/graph/services.ts +95 -0
  47. package/src/graph/summary.ts +134 -0
  48. package/src/index.ts +171 -245
  49. package/src/internal/bucketCache.ts +39 -0
  50. package/src/internal/capture.ts +9 -4
  51. package/src/internal/env.ts +1 -3
  52. package/src/internal/errors.ts +21 -10
  53. package/src/internal/queryDriver.ts +61 -25
  54. package/src/internal/queryDriverHydrate.ts +45 -0
  55. package/src/internal/queryDriverStore.ts +8 -5
  56. package/src/internal/queryDriverTypes.ts +11 -11
  57. package/src/internal/reuse.test.ts +10 -3
  58. package/src/internal/reuse.ts +5 -2
  59. package/src/internal/scheduler.ts +4 -1
  60. package/src/internal/sources.ts +25 -11
  61. package/src/internal/store.test.ts +27 -6
  62. package/src/internal/track.ts +3 -2
  63. package/src/internal.ts +222 -0
  64. package/src/namespace/namespace.ts +46 -25
  65. package/src/procedure/procedure.ts +38 -11
  66. package/src/reducer/reducer.ts +78 -34
  67. package/src/remote/pendingQueue.ts +145 -0
  68. package/src/remote/remoteState.test.ts +536 -354
  69. package/src/remote/remoteState.ts +145 -106
  70. package/src/remote/remoteState.typecheck.ts +47 -23
  71. package/src/remote/remoteStateMake.ts +97 -0
  72. package/src/remote/remoteStateSend.ts +95 -37
  73. package/src/remote/remoteStateTypes.ts +155 -0
  74. package/src/runtime/appRuntime.activation.test.ts +104 -0
  75. package/src/runtime/appRuntime.test.ts +187 -111
  76. package/src/runtime/appRuntime.ts +36 -130
  77. package/src/runtime/capturedAppRuntime.ts +274 -0
  78. package/src/runtime/eventBudget.test.ts +39 -12
  79. package/src/runtime/eventBudget.ts +9 -8
  80. package/src/runtime/featureMount.ts +94 -0
  81. package/src/runtime/hardening.test.ts +22 -13
  82. package/src/runtime/instrumentation.test.ts +213 -194
  83. package/src/runtime/instrumentation.ts +6 -11
  84. package/src/runtime/loop.test.ts +84 -7
  85. package/src/runtime/loop.ts +69 -40
  86. package/src/runtime/runtimeHandle.ts +124 -0
  87. package/src/scene/featureScene.test.ts +4 -2
  88. package/src/scene/scene.ts +234 -64
  89. package/src/scene/seedScene.test.ts +69 -86
  90. package/src/state/state.nominal.typecheck.ts +68 -0
  91. package/src/state/state.test.ts +17 -0
  92. package/src/state/state.ts +79 -26
  93. package/src/state/stateFamily.test.ts +39 -8
  94. package/src/state/stateFamily.ts +55 -22
  95. package/src/state/stateGroup.ts +53 -17
  96. package/src/state/token.ts +40 -10
  97. package/src/synced/syncedStore.ts +46 -23
  98. package/src/testkit/flight.testkit.ts +84 -0
  99. package/src/wire/tree.test.ts +8 -4
  100. package/src/wire/triggers.test.ts +6 -2
  101. package/src/wire/triggers.ts +14 -10
  102. package/src/calc/asyncCalcDefinitions.ts +0 -48
  103. package/src/internal/ctx.ts +0 -9
  104. package/src/remote/remoteStateDefinition.ts +0 -135
  105. package/src/remote/remoteStateLayers.ts +0 -118
  106. package/src/remote/remoteStateLiveTypes.ts +0 -59
@@ -1,121 +1,254 @@
1
- import { Context, Layer, Schema as S } from 'effect'
2
- import type { Store } from '../internal/store'
1
+ import { Layer, Schema as S } from 'effect'
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'
7
- import * as State from '../state/state'
8
- import * as StateGroup from '../state/stateGroup'
14
+ import { scene } from '../scene/scene'
9
15
  import { type EngineServices, type FeatureModule, featureModule, lazyImport } from './feature'
10
16
  import * as Feature from './feature'
17
+ import { Clock, okService } from './featureModule.typecheck'
11
18
 
12
- const FeedStateBase: State.StateClass<'feed', string> = State.make('feed', S.String)
13
- class FeedState extends FeedStateBase {}
14
- const FilterStateBase: State.StateClass<'filter', string> = State.make('filter', S.String)
15
- class FilterState extends FilterStateBase {}
16
- const TodosStatesBase: StateGroup.StateGroupClass<readonly [typeof FeedState, typeof FilterState]> =
17
- StateGroup.make(FeedState, FilterState)
18
- class TodosStates extends TodosStatesBase {}
19
19
 
20
- interface ClockService {
21
- readonly now: () => number
22
- }
23
- const ClockBase: Context.TagClass<Clock, 'feature.typecheck.Clock', ClockService> = Context.Tag(
24
- 'feature.typecheck.Clock',
25
- )<Clock, ClockService>()
26
- class Clock extends ClockBase {}
27
-
28
- declare const readsFeed: Layer.Layer<{ readonly _out: true }, never, Store<string> | EngineServices>
20
+ const AppUiBase: UiClass<{ props: { readonly a: string }; events: {} }, 'AppUi'> = ui('AppUi')<{
21
+ props: { readonly a: string }
22
+ events: {}
23
+ }>()
24
+ class AppUi extends AppUiBase {}
25
+ class AppComp extends Composition.make('AppComp', {
26
+ title: 'App',
27
+ props: S.Struct({ a: S.String }),
28
+ ui: AppUi,
29
+ })<AppComp>() {}
30
+ const AppLive = Composition.live(AppComp, function* () {
31
+ const props = yield* AppComp.props
32
+ return mount({ props: { a: props.a }, slots: {} })
33
+ })
34
+ declare const FailedPlaceholder: Composition.AnyComposition &
35
+ Composition.CompositionPropsReceiver<Feature.FailedProps>
29
36
 
30
- export const okState: FeatureModule<{ readonly _out: true }, readonly [typeof FeedState]> =
31
- featureModule([FeedState], readsFeed)
37
+ class AppFeature extends Feature.make('appFeature', {
38
+ loadingStrategy: 'lazy',
39
+ composition: AppComp,
40
+ load: lazyImport(async () => ({ default: featureModule([], AppLive) })),
41
+ placeholder: { loading: AppComp, failed: FailedPlaceholder },
42
+ }) {}
43
+ type AppFeatureTarget = FeatureSlotTarget<typeof AppFeature>
44
+ class AppFeatureSlot extends slot('AppFeature')<AppFeatureSlot, AppFeatureTarget>() {}
32
45
 
33
- export const okGroup: FeatureModule<{ readonly _out: true }, readonly [typeof TodosStates]> =
34
- featureModule([TodosStates], readsFeed)
46
+ class EmptyAppFeature extends Feature.make('emptyAppFeature', {
47
+ composition: AppComp,
48
+ // @ts-expect-error Layer.empty does not provide CompositionId<'AppComp'>
49
+ module: featureModule([], Layer.empty),
50
+ }) {}
35
51
 
36
- export const badEmpty: FeatureModule<{ readonly _out: true }, readonly []> =
37
- // @ts-expect-error feature layer reads a store it does not declare in `requires`
38
- featureModule([], readsFeed)
52
+ declare const exactUnionModule: FeatureModule<
53
+ Composition.CompositionId<'AppComp', AppComp> | Clock,
54
+ readonly []
55
+ >
39
56
 
40
- declare const readsClock: Layer.Layer<{ readonly _clock: true }, never, Clock | EngineServices>
57
+ class ExactUnionAppFeature extends Feature.make('exactUnionAppFeature', {
58
+ composition: AppComp,
59
+ module: exactUnionModule,
60
+ }) {}
41
61
 
42
- export const okService: FeatureModule<{ readonly _clock: true }, readonly [typeof Clock]> =
43
- featureModule([Clock], readsClock)
62
+ declare const unknownRootModule: FeatureModule<unknown, readonly []>
63
+ class UnknownRootAppFeature extends Feature.make('unknownRootAppFeature', {
64
+ composition: AppComp,
65
+ // @ts-expect-error unknown output does not prove the exact root composition service
66
+ module: unknownRootModule,
67
+ }) {}
44
68
 
45
- export const badService: FeatureModule<{ readonly _clock: true }, readonly []> =
46
- // @ts-expect-error feature layer reads an ordinary Context service it did not declare
47
- featureModule([], readsClock)
69
+ declare const broadRootModule: FeatureModule<
70
+ Composition.CompositionId<string, unknown>,
71
+ readonly []
72
+ >
73
+ class BroadRootAppFeature extends Feature.make('broadRootAppFeature', {
74
+ composition: AppComp,
75
+ // @ts-expect-error a broad identifier does not prove the exact root identity
76
+ module: broadRootModule,
77
+ }) {}
48
78
 
49
- const AppUiBase: UiClass<{ props: { readonly a: string }; events: {} }> = ui('AppUi')<{
50
- props: { readonly a: string }
51
- events: {}
52
- }>()
53
- 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>,
79
+ declare const exactUnionLayer: Layer.Layer<
80
+ Composition.CompositionId<'AppComp', AppComp> | Clock,
67
81
  never,
68
- readonly []
69
- > = Feature.make('appFeature', {
82
+ never
83
+ >
84
+
85
+ const exactUnionScene = scene(AppComp, { provide: [exactUnionLayer] })
86
+
87
+ declare const unknownRootLayer: Layer.Layer<unknown, never, never>
88
+ // @ts-expect-error unknown services do not prove the exact scene root
89
+ const unknownRootScene = scene(AppComp, { provide: [unknownRootLayer] })
90
+
91
+ declare const broadRootLayer: Layer.Layer<Composition.CompositionId<string, unknown>, never, never>
92
+ // @ts-expect-error a broad composition identifier does not prove the exact scene root
93
+ const broadRootScene = scene(AppComp, { provide: [broadRootLayer] })
94
+
95
+
96
+ const wired = provide(AppFeatureSlot, AppFeature)
97
+
98
+ type Equal<Left, Right> =
99
+ (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2
100
+ ? true
101
+ : false
102
+ type Assert<Condition extends true> = Condition
103
+ type WiredRequirementsAreExact = Assert<Equal<Layer.Layer.Context<typeof wired>, EngineServices>>
104
+ type WiredOutputIsOnlySummary = Assert<
105
+ Equal<
106
+ Layer.Layer.Success<typeof wired>,
107
+ SlotService<'AppFeature', AppFeatureSlot, typeof AppFeature.summary>
108
+ >
109
+ >
110
+ type FeatureTargetSummaryIsExact = Assert<
111
+ Equal<AppFeatureTarget['summary'], typeof AppFeature.summary>
112
+ >
113
+ type FeatureTargetPropsAreExact = Assert<
114
+ Equal<PropsOfSlotTarget<AppFeatureTarget>, { readonly a: string }>
115
+ >
116
+ type FeatureTargetContractIsExact = Assert<
117
+ Equal<ContractOfSlotTarget<AppFeatureTarget>, Contract<typeof AppUi>>
118
+ >
119
+ type FeatureTargetCannotOpenTree = Assert<
120
+ Equal<AppFeatureTarget extends Feature.FeatureTreeCarrier ? true : false, false>
121
+ >
122
+ const appNative = Feature.tree<typeof AppFeature>(AppFeature.token)
123
+ type AppNative = typeof appNative
124
+ type ExactTreeComposition = Assert<Equal<AppNative['composition'], typeof AppComp>>
125
+ type NoDirectNativeFields = Assert<
126
+ Equal<
127
+ Extract<
128
+ 'composition' | 'binding' | 'load' | 'eagerModule' | 'supportModule' | 'tree',
129
+ keyof typeof AppFeature
130
+ >,
131
+ never
132
+ >
133
+ >
134
+
135
+ class AppFeatureTwin extends Feature.make('appFeature', {
70
136
  loadingStrategy: 'lazy',
71
137
  composition: AppComp,
72
- load: lazyImport(async () => ({ default: featureModule([], Layer.empty) })),
73
- placeholder: { loading: AppComp, failed: AppComp },
74
- })
75
- class AppFeature extends AppFeatureBase {}
76
-
77
- export const wired: Layer.Layer<SlotChild, never, EngineServices> = provide(AppSlot, AppFeature)
138
+ load: lazyImport(async () => ({ default: featureModule([], AppLive) })),
139
+ placeholder: { loading: AppComp, failed: FailedPlaceholder },
140
+ }) {}
141
+ type SameCompositionTokenIsInterchangeable = Assert<
142
+ Equal<typeof AppFeatureTwin.token extends typeof AppFeature.token ? true : false, true>
143
+ >
144
+ type BroadFeatureIdentityIsRejected = Assert<
145
+ Equal<
146
+ Feature.FeatureIdentityToken<unknown> extends Feature.FeatureIdentityToken<AppComp>
147
+ ? true
148
+ : false,
149
+ false
150
+ >
151
+ >
152
+
153
+ class AppCompositionSlot extends slot('AppComposition')<AppCompositionSlot, typeof AppComp>() {}
154
+
155
+ // @ts-expect-error composition slots accept the Composition value, not a Feature
156
+ provide(AppCompositionSlot, AppFeature)
78
157
 
79
158
  class SupportedAppFeature extends Feature.make('supportedAppFeature', {
80
159
  loadingStrategy: 'lazy',
81
160
  composition: AppComp,
82
- load: lazyImport(async () => ({ default: featureModule([], Layer.empty) })),
161
+ load: lazyImport(async () => ({ default: featureModule([], AppLive) })),
83
162
  eager: okService,
84
- placeholder: { loading: AppComp, failed: AppComp },
163
+ placeholder: { loading: AppComp, failed: FailedPlaceholder },
85
164
  }) {}
86
-
87
- export const wiredWithSupport: Layer.Layer<SlotChild, never, EngineServices | Clock> = provide(
88
- AppSlot,
89
- SupportedAppFeature,
90
- )
91
-
92
- const OtherUiBase: UiClass<{ props: { readonly n: number }; events: {} }> = ui('OtherUi')<{
165
+ type SupportedAppFeatureTarget = FeatureSlotTarget<typeof SupportedAppFeature>
166
+ class SupportedAppFeatureSlot extends slot('SupportedAppFeature')<
167
+ SupportedAppFeatureSlot,
168
+ SupportedAppFeatureTarget
169
+ >() {}
170
+
171
+ const wiredWithSupport = provide(SupportedAppFeatureSlot, SupportedAppFeature)
172
+ type SupportedRequirementsAreExact = Assert<
173
+ Equal<Layer.Layer.Context<typeof wiredWithSupport>, EngineServices | Clock>
174
+ >
175
+ type SupportedOutputIsOnlySummary = Assert<
176
+ Equal<
177
+ Layer.Layer.Success<typeof wiredWithSupport>,
178
+ SlotService<'SupportedAppFeature', SupportedAppFeatureSlot, typeof SupportedAppFeature.summary>
179
+ >
180
+ >
181
+
182
+ const OtherUiBase: UiClass<{ props: { readonly n: number }; events: {} }, 'OtherUi'> = ui(
183
+ 'OtherUi',
184
+ )<{
93
185
  props: { readonly n: number }
94
186
  events: {}
95
187
  }>()
96
188
  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', {
189
+ class OtherComp extends Composition.make('OtherComp', {
190
+ title: 'Other',
191
+ props: S.Struct({ n: S.Number }),
192
+ ui: OtherUi,
193
+ })<OtherComp>() {}
194
+ const OtherLive = Composition.live(OtherComp, function* () {
195
+ const props = yield* OtherComp.props
196
+ return mount({ props, slots: {} })
197
+ })
198
+ class OtherFeature extends Feature.make('otherFeature', {
110
199
  loadingStrategy: 'lazy',
111
200
  composition: OtherComp,
112
- load: lazyImport(async () => ({ default: featureModule([], Layer.empty) })),
113
- placeholder: { loading: OtherComp, failed: OtherComp },
114
- })
115
- class OtherFeature extends OtherFeatureBase {}
201
+ load: lazyImport(async () => ({ default: featureModule([], OtherLive) })),
202
+ placeholder: { loading: OtherComp, failed: FailedPlaceholder },
203
+ }) {}
116
204
 
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
- )
205
+ type OtherFeatureIsRejectedByAppTarget = Assert<
206
+ Equal<typeof OtherFeature extends AppFeatureTarget ? true : false, false>
207
+ >
208
+ type DifferentCompositionTokenIsRejected = Assert<
209
+ Equal<typeof OtherFeature.token extends typeof AppFeature.token ? true : false, false>
210
+ >
211
+ const otherNative = Feature.tree<typeof OtherFeature>(OtherFeature.token)
212
+ type OtherNative = typeof otherNative
213
+ type DifferentCompositionBindingIsRejected = Assert<
214
+ Equal<OtherNative['binding'] extends SlotChild<AppFeatureTarget> ? true : false, false>
215
+ >
216
+
217
+ // @ts-expect-error OtherFeature.token does not belong to AppFeature
218
+ Feature.tree<typeof AppFeature>(OtherFeature.token)
219
+
220
+ // @ts-expect-error AppFeatureSlot accepts only features over AppComp
221
+ provide(AppFeatureSlot, OtherFeature)
222
+
223
+ type FeatureTypeProofs = [
224
+ WiredRequirementsAreExact,
225
+ WiredOutputIsOnlySummary,
226
+ FeatureTargetSummaryIsExact,
227
+ FeatureTargetPropsAreExact,
228
+ FeatureTargetContractIsExact,
229
+ FeatureTargetCannotOpenTree,
230
+ ExactTreeComposition,
231
+ NoDirectNativeFields,
232
+ SameCompositionTokenIsInterchangeable,
233
+ BroadFeatureIdentityIsRejected,
234
+ DifferentCompositionTokenIsRejected,
235
+ DifferentCompositionBindingIsRejected,
236
+ SupportedRequirementsAreExact,
237
+ SupportedOutputIsOnlySummary,
238
+ OtherFeatureIsRejectedByAppTarget,
239
+ ]
240
+ declare const featureTypeProofs: FeatureTypeProofs
241
+
242
+ void featureTypeProofs
243
+ void appNative
244
+ void otherNative
245
+ void AppFeatureTwin
246
+ void EmptyAppFeature
247
+ void ExactUnionAppFeature
248
+ void UnknownRootAppFeature
249
+ void BroadRootAppFeature
250
+ void exactUnionScene
251
+ void unknownRootScene
252
+ void broadRootScene
253
+
254
+ export {}
@@ -1,9 +1,23 @@
1
- import { Effect, Layer, Option } from 'effect'
1
+ import type { Effect, Option } from 'effect'
2
+ import type { AnyComposition, CompositionClass } from '../compose/composition'
2
3
  import type { Manifest } from '../definition/definition'
3
- import type { CompositionClass } from '../compose/composition'
4
- import type { Tagged } from '../runtime/bus'
4
+ import {
5
+ type AnyFeatureGraphSummary,
6
+ FeatureGraphValidTypeId,
7
+ type FeatureSummaryCompositionIdentity,
8
+ type FeatureSummaryCompositionProps,
9
+ type FeatureSummaryContract,
10
+ type FeatureSummaryInput,
11
+ type FeatureSummaryName,
12
+ type FeatureSummaryOpenServices,
13
+ type FeatureSummaryStates,
14
+ type FeatureSummarySupportOpenServices,
15
+ type FeatureSummaryUsesInput,
16
+ } from '../graph/closure'
5
17
  import type { FeatureLoadFailed } from '../internal/errors'
6
- import type { FeatureRequirement } from './feature'
18
+ import type { Tagged } from '../runtime/bus'
19
+ import type { LoadedModule } from './featureModule'
20
+ import type { FeatureRequirement } from './featureRequirement'
7
21
 
8
22
  export interface FailedProps {
9
23
  readonly error: FeatureLoadFailed
@@ -21,28 +35,124 @@ export interface FeatureManifest extends Manifest {
21
35
  }>
22
36
  }
23
37
 
24
- export interface LoadedModule {
25
- readonly requires: ReadonlyArray<FeatureRequirement>
26
- readonly usesInput: boolean
27
- readonly layer: Layer.Layer<never, never, unknown>
28
- }
29
-
30
38
  export const FeatureBindingTypeId: unique symbol = Symbol.for('reform/FeatureBinding')
31
39
  export type FeatureBindingTypeId = typeof FeatureBindingTypeId
40
+ export type FeatureBindingType = FeatureBindingTypeId
41
+
42
+ export const FeatureTokenIdentityTypeId: unique symbol = Symbol('reform/FeatureTokenIdentity')
43
+ export const FeatureNativeTypeId: unique symbol = Symbol('reform/FeatureNative')
44
+
45
+ export interface FeatureIdentityToken<out FeatureIdentity> {
46
+ readonly identity: symbol
47
+ readonly [FeatureTokenIdentityTypeId]: readonly [] | readonly [FeatureIdentity]
48
+ }
32
49
 
33
- export interface FeatureBindingExternalApi {
50
+ export interface FeatureToken<
51
+ out FeatureIdentity,
52
+ out NativeTree extends object,
53
+ > extends FeatureIdentityToken<FeatureIdentity> {
54
+ readonly [FeatureNativeTypeId]: NativeTree
55
+ }
56
+
57
+ export interface FeatureTreeCarrier {
58
+ readonly token: FeatureToken<unknown, object>
59
+ }
60
+
61
+ type Native<
62
+ Feature extends FeatureTreeCarrier,
63
+ Token extends Feature['token'] = Feature['token'],
64
+ > = Token[typeof FeatureNativeTypeId]
65
+
66
+ export const tree = <
67
+ Feature extends FeatureTreeCarrier,
68
+ Token extends Feature['token'] = Feature['token'],
69
+ >(
70
+ token: Token,
71
+ ): Native<Feature, Token> => token[FeatureNativeTypeId]
72
+
73
+ export interface ProvideableFeature<
74
+ out FeatureIdentity,
75
+ out Summary extends AnyFeatureGraphSummary,
76
+ > {
77
+ new (): {}
78
+ readonly manifest: FeatureManifest
79
+ readonly token: FeatureIdentityToken<FeatureIdentity>
80
+ readonly summary: Summary
81
+ }
82
+
83
+ export interface FeatureDefinition<
84
+ out FeatureIdentity,
85
+ out Summary extends AnyFeatureGraphSummary,
86
+ out NativeTree extends object,
87
+ > extends ProvideableFeature<FeatureIdentity, Summary> {
88
+ readonly token: FeatureToken<FeatureIdentity, NativeTree>
89
+ }
90
+
91
+ export type FeatureOpenServices<Feature extends { readonly summary: AnyFeatureGraphSummary }> =
92
+ FeatureSummaryOpenServices<Feature['summary']>
93
+
94
+ export type FeatureSupportOpenServices<
95
+ Feature extends { readonly summary: AnyFeatureGraphSummary },
96
+ > = FeatureSummarySupportOpenServices<Feature['summary']>
97
+
98
+ export interface AnyFeatureBindingExternalApi {
34
99
  readonly [FeatureBindingTypeId]: FeatureBindingTypeId
100
+ readonly [FeatureGraphValidTypeId]: FeatureGraphValidTypeId
101
+ readonly capture: <Result>(visit: FeatureBindingCapture<Result>) => Result
102
+ }
103
+
104
+ export type AnyFeatureBinding = AnyFeatureBindingExternalApi
105
+
106
+ export interface FeatureBinding<
107
+ Summary extends AnyFeatureGraphSummary,
108
+ ROut,
109
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
110
+ > extends AnyFeatureBindingExternalApi {
35
111
  readonly manifest: FeatureManifest
36
- readonly composition: CompositionClass<unknown>
112
+ readonly identity: symbol
113
+ readonly summary: Summary
114
+ readonly composition: CompositionClass<
115
+ FeatureSummaryCompositionProps<Summary>,
116
+ FeatureSummaryContract<Summary>,
117
+ FeatureSummaryStates<Summary>,
118
+ FeatureSummaryName<Summary>,
119
+ FeatureSummaryCompositionIdentity<Summary>
120
+ >
37
121
  readonly strategy: 'lazy' | 'default'
38
- readonly load: Effect.Effect<LoadedModule, FeatureLoadFailed>
39
- readonly placeholder?: {
40
- readonly loading: CompositionClass<unknown>
41
- readonly failed: CompositionClass<unknown>
42
- }
122
+ readonly load: Effect.Effect<
123
+ LoadedModule<
124
+ ROut,
125
+ DirectRequires,
126
+ FeatureSummaryOpenServices<Summary>,
127
+ FeatureSummaryUsesInput<Summary>,
128
+ FeatureSummaryInput<Summary>
129
+ >,
130
+ FeatureLoadFailed
131
+ >
132
+ readonly placeholder: Option.Option<{
133
+ readonly loading: AnyComposition
134
+ readonly failed: AnyComposition
135
+ }>
136
+ readonly parseInput: (input: unknown) => Option.Option<unknown>
43
137
  readonly boot: ReadonlyArray<Tagged>
44
138
  }
45
- export type FeatureBinding = FeatureBindingExternalApi
46
139
 
47
- export const isFeatureBinding = (candidate: unknown): candidate is FeatureBinding =>
48
- typeof candidate === 'object' && candidate !== null && FeatureBindingTypeId in candidate
140
+ export type CapturedFeatureBinding<
141
+ Summary extends AnyFeatureGraphSummary,
142
+ ROut,
143
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
144
+ > = FeatureBinding<Summary, ROut, DirectRequires>
145
+
146
+ export type FeatureBindingCapture<Result> = <
147
+ Summary extends AnyFeatureGraphSummary,
148
+ ROut,
149
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
150
+ >(
151
+ binding: FeatureBinding<Summary, ROut, DirectRequires>,
152
+ ) => Result
153
+
154
+ export const isFeatureBinding = (candidate: unknown): candidate is AnyFeatureBinding =>
155
+ typeof candidate === 'object' &&
156
+ candidate !== null &&
157
+ FeatureBindingTypeId in candidate &&
158
+ FeatureGraphValidTypeId in candidate
@@ -0,0 +1,162 @@
1
+ import type { CompositionClass } from '../compose/composition'
2
+ import type { UiContract } from '../compose/ui'
3
+ import type { FeatureDefinition } from './featureBinding'
4
+ import type {
5
+ EagerFeatureNativeTree,
6
+ FeatureNativeTree,
7
+ FeatureSummaryOf,
8
+ LazyFeatureNativeTree,
9
+ } from './featureNativeTree'
10
+ import type {
11
+ DefaultFeatureOpenServices,
12
+ FeatureRequirement,
13
+ RootCompositionIdentity,
14
+ RootCompositionName,
15
+ } from './featureRequirement'
16
+
17
+ export interface FeatureClass<
18
+ FeatureIdentity,
19
+ P,
20
+ C extends UiContract,
21
+ ROut,
22
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
23
+ OpenServices = DefaultFeatureOpenServices<ROut, DirectRequires>,
24
+ SupportOut = never,
25
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement> = readonly [],
26
+ SupportOpenServices = DefaultFeatureOpenServices<SupportOut, SupportDirectRequires>,
27
+ UsesInput extends boolean = false,
28
+ Input = UsesInput extends true ? P : never,
29
+ S extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
30
+ N extends string = RootCompositionName<ROut>,
31
+ CompositionProps = P,
32
+ Identity = RootCompositionIdentity<ROut>,
33
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity> = CompositionClass<
34
+ CompositionProps,
35
+ C,
36
+ S,
37
+ N,
38
+ Identity
39
+ >,
40
+ > extends FeatureDefinition<
41
+ FeatureIdentity,
42
+ FeatureSummaryOf<
43
+ FeatureIdentity,
44
+ P,
45
+ CompositionProps,
46
+ C,
47
+ S,
48
+ N,
49
+ Identity,
50
+ OpenServices,
51
+ SupportOpenServices,
52
+ UsesInput,
53
+ Input
54
+ >,
55
+ FeatureNativeTree<
56
+ FeatureSummaryOf<
57
+ FeatureIdentity,
58
+ P,
59
+ CompositionProps,
60
+ C,
61
+ S,
62
+ N,
63
+ Identity,
64
+ OpenServices,
65
+ SupportOpenServices,
66
+ UsesInput,
67
+ Input
68
+ >,
69
+ ROut,
70
+ DirectRequires,
71
+ SupportOut,
72
+ SupportDirectRequires,
73
+ Composition
74
+ >
75
+ > {}
76
+
77
+ export type EagerFeatureClass<
78
+ FeatureIdentity,
79
+ P,
80
+ C extends UiContract,
81
+ ROut,
82
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
83
+ OpenServices,
84
+ S extends ReadonlyArray<unknown>,
85
+ N extends string,
86
+ Identity,
87
+ Composition extends CompositionClass<P, C, S, N, Identity> = CompositionClass<
88
+ P,
89
+ C,
90
+ S,
91
+ N,
92
+ Identity
93
+ >,
94
+ > = FeatureDefinition<
95
+ FeatureIdentity,
96
+ FeatureSummaryOf<FeatureIdentity, P, P, C, S, N, Identity, OpenServices, never, false, never>,
97
+ EagerFeatureNativeTree<
98
+ FeatureSummaryOf<FeatureIdentity, P, P, C, S, N, Identity, OpenServices, never, false, never>,
99
+ ROut,
100
+ DirectRequires,
101
+ Composition
102
+ >
103
+ >
104
+
105
+ export type LazyFeatureClass<
106
+ FeatureIdentity,
107
+ P,
108
+ C extends UiContract,
109
+ ROut,
110
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
111
+ OpenServices,
112
+ SupportOut,
113
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
114
+ SupportOpenServices,
115
+ UsesInput extends boolean,
116
+ S extends ReadonlyArray<unknown>,
117
+ N extends string,
118
+ CompositionProps,
119
+ Identity,
120
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity> = CompositionClass<
121
+ CompositionProps,
122
+ C,
123
+ S,
124
+ N,
125
+ Identity
126
+ >,
127
+ > = FeatureDefinition<
128
+ FeatureIdentity,
129
+ FeatureSummaryOf<
130
+ FeatureIdentity,
131
+ P,
132
+ CompositionProps,
133
+ C,
134
+ S,
135
+ N,
136
+ Identity,
137
+ OpenServices,
138
+ SupportOpenServices,
139
+ UsesInput,
140
+ UsesInput extends true ? P : never
141
+ >,
142
+ LazyFeatureNativeTree<
143
+ FeatureSummaryOf<
144
+ FeatureIdentity,
145
+ P,
146
+ CompositionProps,
147
+ C,
148
+ S,
149
+ N,
150
+ Identity,
151
+ OpenServices,
152
+ SupportOpenServices,
153
+ UsesInput,
154
+ UsesInput extends true ? P : never
155
+ >,
156
+ ROut,
157
+ DirectRequires,
158
+ SupportOut,
159
+ SupportDirectRequires,
160
+ Composition
161
+ >
162
+ >