@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,84 +1,227 @@
1
- import { Context, Effect, Layer, Option, type Scope } from 'effect'
2
- import { type Engine } from '../runtime/loop'
3
- import { type StoresOf } from '../internal/sources'
4
- import { FeatureLoadFailed } from '../internal/errors'
5
- import { definitionClass } from '../definition/definition'
6
- import { type CompositionClass } from '../compose/composition'
1
+ import { Context, Effect, Layer, Option, Schema, type Scope } from 'effect'
2
+ import {
3
+ type AnyComposition,
4
+ CompositionIdTypeId,
5
+ type CompositionClass,
6
+ type HasExactCompositionId,
7
+ type CompositionPropsReceiver,
8
+ } from '../compose/composition'
7
9
  import { type UiContract } from '../compose/ui'
8
- import { publish, type Tagged } from '../runtime/bus'
9
- import type { AnyValue } from '../internal/variance'
10
+ import { type Manifest, definitionClass } from '../definition/definition'
10
11
  import {
11
- type FeatureBinding,
12
- FeatureBindingTypeId,
13
- type FeatureManifest,
14
- } from './featureBinding'
15
- export {
16
- type FailedProps,
17
- type FeatureBinding,
18
- FeatureBindingTypeId,
19
- type FeatureManifest,
20
- isFeatureBinding,
21
- type LoadedModule,
22
- } from './featureBinding'
23
- export type { FeatureBindingTypeId as FeatureBindingType } from './featureBinding'
24
-
25
- // Features must only name EngineServices as open RIn — never provide them (shared engine)
26
- export type EngineServices = Layer.Layer.Success<typeof Engine>
27
-
28
- const FeatureInputBase: Context.TagClass<FeatureInput, 'reform/FeatureInput', unknown> = Context.Tag(
29
- 'reform/FeatureInput',
30
- )<FeatureInput, unknown>()
12
+ type AnyFeatureGraphSummary,
13
+ type FeatureGraphSummary,
14
+ FeatureGraphSummaryTypeId,
15
+ FeatureGraphValidTypeId,
16
+ FeatureChildSummaryTypeId,
17
+ FeatureModuleGraphValidTypeId,
18
+ type FeatureSummaryCompositionIdentity,
19
+ type FeatureSummaryCompositionProps,
20
+ type FeatureSummaryContract,
21
+ type FeatureSummaryInput,
22
+ type FeatureSummaryName,
23
+ type FeatureSummaryOpenServices,
24
+ type FeatureSummaryStates,
25
+ type FeatureSummarySupportOpenServices,
26
+ type FeatureSummaryUsesInput,
27
+ type ModuleOpenServices,
28
+ type OpenServicesFromFeatureSlotWitnesses,
29
+ SlotBindingSummaryTypeId,
30
+ type ValidateFeatureGraphLayer,
31
+ } from '../graph/closure'
32
+ import { FeatureLoadFailed } from '../internal/errors'
33
+ import { Bus, publish, type Tagged } from '../runtime/bus'
34
+ import { type EngineServices } from '../runtime/loop'
35
+
36
+ export type { EngineServices } from '../runtime/loop'
37
+ export { FeatureGraphValidTypeId }
38
+
39
+ const FeatureInputBase: Context.TagClass<FeatureInput, 'reform/FeatureInput', unknown> =
40
+ Context.Tag('reform/FeatureInput')<FeatureInput, unknown>()
31
41
  export class FeatureInput extends FeatureInputBase {}
32
42
 
43
+ // Context.Tag is invariant, so erased requirements use its structural identity carrier.
44
+ interface FeatureServiceTag<out Identifier = unknown, out Service = unknown> {
45
+ readonly [Context.TagTypeId]: object
46
+ readonly Identifier: Identifier
47
+ readonly Service: Service
48
+ }
49
+
50
+ interface StoredFeatureRequirement<out Identifier = unknown, out Service = unknown> {
51
+ readonly store: FeatureServiceTag<Identifier, Service>
52
+ }
53
+
33
54
  export type RequireCarrier =
34
- | { readonly store: Context.Tag<AnyValue, AnyValue> }
35
- | { readonly members: ReadonlyArray<{ readonly store: Context.Tag<AnyValue, AnyValue> }> }
55
+ | StoredFeatureRequirement
56
+ | {
57
+ readonly members: ReadonlyArray<StoredFeatureRequirement>
58
+ }
59
+
60
+ export type FeatureRequirement = RequireCarrier | FeatureServiceTag
61
+
62
+ type TagRequirement<Tag> =
63
+ Tag extends FeatureServiceTag<infer Identifier, infer _Service> ? Identifier : never
64
+
65
+ type StoredRequirementIdentifier<Carrier> = Carrier extends {
66
+ readonly store: infer Tag
67
+ }
68
+ ? TagRequirement<Tag>
69
+ : never
70
+
71
+ type RequirementIdentifier<Requirement> = Requirement extends FeatureServiceTag
72
+ ? TagRequirement<Requirement>
73
+ : Requirement extends {
74
+ readonly members: infer Members extends ReadonlyArray<StoredFeatureRequirement>
75
+ }
76
+ ? StoredRequirementIdentifier<Members[number]>
77
+ : StoredRequirementIdentifier<Requirement>
78
+
79
+ export type ProvidedBy<Requires extends ReadonlyArray<FeatureRequirement>> = RequirementIdentifier<
80
+ Requires[number]
81
+ >
36
82
 
37
- export type FeatureRequirement = RequireCarrier | Context.Tag<AnyValue, AnyValue>
83
+ type DefaultFeatureOpenServices<ROut, DirectRequires extends ReadonlyArray<FeatureRequirement>> =
84
+ | ProvidedBy<DirectRequires>
85
+ | OpenServicesFromFeatureSlotWitnesses<ROut>
38
86
 
39
- // Covariant RIn: layer reading outside EngineServices | ProvidedBy is unassignable
40
- export type ProvidedBy<Requires extends ReadonlyArray<FeatureRequirement>> = {
41
- [I in keyof Requires]: Requires[I] extends Context.Tag<infer Identifier, AnyValue>
42
- ? Identifier
43
- : Requires[I] extends {
44
- readonly members: infer M extends ReadonlyArray<unknown>
87
+ type CompositionNames<ROut> = ROut extends {
88
+ readonly [CompositionIdTypeId]: {
89
+ readonly name: infer Name extends string
45
90
  }
46
- ? StoresOf<M>
47
- : Requires[I] extends { readonly store: Context.Tag<infer Svc, AnyValue> }
48
- ? Svc
49
- : never
50
- }[number]
91
+ }
92
+ ? Name
93
+ : never
94
+
95
+ type CompositionIdentities<ROut> = ROut extends {
96
+ readonly [CompositionIdTypeId]: {
97
+ readonly identity: infer Identity
98
+ }
99
+ }
100
+ ? Identity
101
+ : never
102
+
103
+ type RootCompositionName<ROut> = [CompositionNames<ROut>] extends [never]
104
+ ? string
105
+ : CompositionNames<ROut>
106
+
107
+ type RootCompositionIdentity<ROut> = [CompositionIdentities<ROut>] extends [never]
108
+ ? unknown
109
+ : CompositionIdentities<ROut>
110
+
111
+ // Feature layers may require the shared engine but must never provide it.
112
+ export type FeatureModuleInput<OpenServices, UsesInput extends boolean> =
113
+ | EngineServices
114
+ | OpenServices
115
+ | (UsesInput extends true ? FeatureInput : never)
51
116
 
52
117
  export interface FeatureModule<
53
118
  ROut,
54
- Requires extends ReadonlyArray<FeatureRequirement>,
119
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
120
+ OpenServices = DefaultFeatureOpenServices<ROut, DirectRequires>,
55
121
  UsesInput extends boolean = false,
56
- > {
57
- readonly requires: Requires
122
+ Input = never,
123
+ > extends AnyLoadedModule {
124
+ readonly [FeatureModuleGraphValidTypeId]: FeatureModuleGraphValidTypeId
125
+ readonly requires: DirectRequires
58
126
  readonly usesInput: UsesInput
59
- readonly layer: Layer.Layer<
60
- ROut,
61
- never,
62
- | EngineServices
63
- | ProvidedBy<Requires>
64
- | (UsesInput extends true ? FeatureInput : never)
65
- >
127
+ readonly layer: Layer.Layer<ROut, never, FeatureModuleInput<OpenServices, UsesInput>>
128
+ readonly inputSchema: UsesInput extends true ? Schema.Schema.AnyNoContext : undefined
129
+ readonly Input: (input: Input) => void
130
+ }
131
+
132
+ export type LoadedModuleCapture<Result> = <
133
+ ROut,
134
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
135
+ OpenServices,
136
+ UsesInput extends boolean,
137
+ Input,
138
+ >(
139
+ module: LoadedModule<ROut, DirectRequires, OpenServices, UsesInput, Input>,
140
+ ) => Result
141
+
142
+ export interface AnyLoadedModule {
143
+ readonly capture: <Result>(visit: LoadedModuleCapture<Result>) => Result
66
144
  }
67
145
 
68
- export const featureModule = <const Requires extends ReadonlyArray<FeatureRequirement>, ROut>(
69
- requires: Requires,
70
- layer: Layer.Layer<ROut, never, EngineServices | ProvidedBy<Requires>>,
71
- ): FeatureModule<ROut, Requires, false> => ({ requires, usesInput: false, layer })
146
+ type FeatureLayerOutput<L extends Layer.Layer.Any> = Layer.Layer.Success<L>
147
+
148
+ // Reject erased contexts before graph validation can mistake them for permissive inputs.
149
+ type ExactFeatureLayer<L extends Layer.Layer.Any, UsesInput extends boolean> = Layer.Layer<
150
+ FeatureLayerOutput<L>,
151
+ never,
152
+ FeatureModuleInput<ModuleOpenServices<L, UsesInput>, UsesInput>
153
+ > &
154
+ (unknown extends Layer.Layer.Context<L>
155
+ ? {
156
+ readonly featureLayerContextMustBeExact: 'A feature layer context cannot be erased'
157
+ }
158
+ : object)
159
+
160
+ export const featureModule = <
161
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
162
+ L extends Layer.Layer.Any,
163
+ >(
164
+ requires: DirectRequires,
165
+ layer: L & ExactFeatureLayer<L, false> & ValidateFeatureGraphLayer<L, DirectRequires, false>,
166
+ ): FeatureModule<
167
+ FeatureLayerOutput<L>,
168
+ DirectRequires,
169
+ ModuleOpenServices<L, false>,
170
+ false,
171
+ never
172
+ > => {
173
+ const module: FeatureModule<
174
+ FeatureLayerOutput<L>,
175
+ DirectRequires,
176
+ ModuleOpenServices<L, false>,
177
+ false,
178
+ never
179
+ > = {
180
+ [FeatureModuleGraphValidTypeId]: FeatureModuleGraphValidTypeId,
181
+ requires,
182
+ usesInput: false,
183
+ layer,
184
+ inputSchema: undefined,
185
+ Input: () => undefined,
186
+ capture: <Result>(visit: LoadedModuleCapture<Result>): Result => visit(module),
187
+ }
188
+ return module
189
+ }
72
190
 
73
191
  export const featureModuleFromInput = <
74
- const Requires extends ReadonlyArray<FeatureRequirement>,
75
- ROut,
192
+ const InputSchema extends Schema.Schema.AnyNoContext,
193
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
194
+ L extends Layer.Layer.Any,
76
195
  >(
77
- requires: Requires,
78
- layer: Layer.Layer<ROut, never, EngineServices | ProvidedBy<Requires> | FeatureInput>,
79
- ): FeatureModule<ROut, Requires, true> => ({ requires, usesInput: true, layer })
196
+ input: InputSchema,
197
+ requires: DirectRequires,
198
+ layer: L & ExactFeatureLayer<L, true> & ValidateFeatureGraphLayer<L, DirectRequires, true>,
199
+ ): FeatureModule<
200
+ FeatureLayerOutput<L>,
201
+ DirectRequires,
202
+ ModuleOpenServices<L, true>,
203
+ true,
204
+ Schema.Schema.Type<InputSchema>
205
+ > => {
206
+ const module: FeatureModule<
207
+ FeatureLayerOutput<L>,
208
+ DirectRequires,
209
+ ModuleOpenServices<L, true>,
210
+ true,
211
+ Schema.Schema.Type<InputSchema>
212
+ > = {
213
+ [FeatureModuleGraphValidTypeId]: FeatureModuleGraphValidTypeId,
214
+ requires,
215
+ usesInput: true,
216
+ layer,
217
+ inputSchema: input,
218
+ Input: () => undefined,
219
+ capture: <Result>(visit: LoadedModuleCapture<Result>): Result => visit(module),
220
+ }
221
+ return module
222
+ }
80
223
 
81
- // Seals Promise import as Effect; rejection FeatureLoadFailed
224
+ // Seal dynamic imports as typed Effects so chunk failures become FeatureLoadFailed.
82
225
  export const lazyImport = <M>(
83
226
  thunk: () => Promise<{ readonly default: M }>,
84
227
  ): Effect.Effect<M, FeatureLoadFailed> =>
@@ -87,205 +230,1182 @@ export const lazyImport = <M>(
87
230
  catch: (cause) => new FeatureLoadFailed({ cause }),
88
231
  }).pipe(Effect.map((module) => module.default))
89
232
 
90
- export interface FeatureClass<
91
- P,
92
- C extends UiContract,
233
+ export interface FailedProps {
234
+ readonly error: FeatureLoadFailed
235
+ readonly retry: () => void
236
+ }
237
+
238
+ export interface FeatureManifest extends Manifest {
239
+ readonly kind: 'Feature'
240
+ readonly name: string
241
+ readonly strategy: 'lazy' | 'default'
242
+ readonly composition: Manifest & { readonly kind: 'Composition' }
243
+ readonly placeholder: Option.Option<{
244
+ readonly loading: Manifest & { readonly kind: 'Composition' }
245
+ readonly failed: Manifest & { readonly kind: 'Composition' }
246
+ }>
247
+ }
248
+
249
+ export interface LoadedModule<
93
250
  ROut,
94
- Requires extends ReadonlyArray<FeatureRequirement>,
95
- SupportRequires extends ReadonlyArray<FeatureRequirement> = readonly [],
251
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
252
+ OpenServices = DefaultFeatureOpenServices<ROut, DirectRequires>,
253
+ UsesInput extends boolean = false,
254
+ Input = never,
255
+ > extends FeatureModule<ROut, DirectRequires, OpenServices, UsesInput, Input> {}
256
+
257
+ export const FeatureBindingTypeId: unique symbol = Symbol.for('reform/FeatureBinding')
258
+ export type FeatureBindingTypeId = typeof FeatureBindingTypeId
259
+ export type FeatureBindingType = FeatureBindingTypeId
260
+
261
+ const FeatureTokenIdentityTypeId: unique symbol = Symbol('reform/FeatureTokenIdentity')
262
+ const FeatureNativeTypeId: unique symbol = Symbol('reform/FeatureNative')
263
+
264
+ export interface FeatureIdentityToken<out FeatureIdentity> {
265
+ readonly identity: symbol
266
+ readonly [FeatureTokenIdentityTypeId]: readonly [] | readonly [FeatureIdentity]
267
+ }
268
+
269
+ export interface FeatureToken<
270
+ out FeatureIdentity,
271
+ out NativeTree extends object,
272
+ > extends FeatureIdentityToken<FeatureIdentity> {
273
+ readonly [FeatureNativeTypeId]: NativeTree
274
+ }
275
+
276
+ export interface FeatureTreeCarrier {
277
+ readonly token: FeatureToken<unknown, object>
278
+ }
279
+
280
+ type Native<
281
+ Feature extends FeatureTreeCarrier,
282
+ Token extends Feature['token'] = Feature['token'],
283
+ > = Token[typeof FeatureNativeTypeId]
284
+
285
+ export const tree = <
286
+ Feature extends FeatureTreeCarrier,
287
+ Token extends Feature['token'] = Feature['token'],
288
+ >(
289
+ token: Token,
290
+ ): Native<Feature, Token> => token[FeatureNativeTypeId]
291
+
292
+ export interface ProvideableFeature<
293
+ out FeatureIdentity,
294
+ out Summary extends AnyFeatureGraphSummary,
96
295
  > {
97
296
  new (): {}
98
297
  readonly manifest: FeatureManifest
99
- readonly Props: P
100
- readonly Contract: C
101
- readonly composition: CompositionClass<P, C>
102
- readonly Requires: Requires
103
- readonly load: Effect.Effect<FeatureModule<ROut, Requires, boolean>, FeatureLoadFailed>
104
- readonly binding: FeatureBinding
105
- readonly eagerModule: FeatureModule<ROut, Requires, false> | undefined
106
- readonly supportModule: FeatureModule<never, SupportRequires, false> | undefined
298
+ readonly token: FeatureIdentityToken<FeatureIdentity>
299
+ readonly summary: Summary
107
300
  }
108
301
 
109
- export interface EagerFeatureClass<
110
- P,
111
- C extends UiContract,
302
+ export interface FeatureDefinition<
303
+ out FeatureIdentity,
304
+ out Summary extends AnyFeatureGraphSummary,
305
+ out NativeTree extends object,
306
+ > extends ProvideableFeature<FeatureIdentity, Summary> {
307
+ readonly token: FeatureToken<FeatureIdentity, NativeTree>
308
+ }
309
+
310
+ export type FeatureOpenServices<Feature extends { readonly summary: AnyFeatureGraphSummary }> =
311
+ FeatureSummaryOpenServices<Feature['summary']>
312
+
313
+ export type FeatureSupportOpenServices<
314
+ Feature extends { readonly summary: AnyFeatureGraphSummary },
315
+ > = FeatureSummarySupportOpenServices<Feature['summary']>
316
+
317
+ export interface AnyFeatureBindingExternalApi {
318
+ readonly [FeatureBindingTypeId]: FeatureBindingTypeId
319
+ readonly [FeatureGraphValidTypeId]: FeatureGraphValidTypeId
320
+ readonly capture: <Result>(visit: FeatureBindingCapture<Result>) => Result
321
+ }
322
+
323
+ export type AnyFeatureBinding = AnyFeatureBindingExternalApi
324
+
325
+ export interface FeatureBinding<
326
+ Summary extends AnyFeatureGraphSummary,
327
+ ROut,
328
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
329
+ > extends AnyFeatureBindingExternalApi {
330
+ readonly manifest: FeatureManifest
331
+ readonly identity: symbol
332
+ readonly summary: Summary
333
+ readonly composition: CompositionClass<
334
+ FeatureSummaryCompositionProps<Summary>,
335
+ FeatureSummaryContract<Summary>,
336
+ FeatureSummaryStates<Summary>,
337
+ FeatureSummaryName<Summary>,
338
+ FeatureSummaryCompositionIdentity<Summary>
339
+ >
340
+ readonly strategy: 'lazy' | 'default'
341
+ readonly load: Effect.Effect<
342
+ LoadedModule<
343
+ ROut,
344
+ DirectRequires,
345
+ FeatureSummaryOpenServices<Summary>,
346
+ FeatureSummaryUsesInput<Summary>,
347
+ FeatureSummaryInput<Summary>
348
+ >,
349
+ FeatureLoadFailed
350
+ >
351
+ readonly placeholder: Option.Option<{
352
+ readonly loading: AnyComposition
353
+ readonly failed: AnyComposition
354
+ }>
355
+ readonly parseInput: (input: unknown) => Option.Option<unknown>
356
+ readonly boot: ReadonlyArray<Tagged>
357
+ }
358
+
359
+ export type CapturedFeatureBinding<
360
+ Summary extends AnyFeatureGraphSummary,
361
+ ROut,
362
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
363
+ > = FeatureBinding<Summary, ROut, DirectRequires>
364
+
365
+ export type FeatureBindingCapture<Result> = <
366
+ Summary extends AnyFeatureGraphSummary,
367
+ ROut,
368
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
369
+ >(
370
+ binding: FeatureBinding<Summary, ROut, DirectRequires>,
371
+ ) => Result
372
+
373
+ export const isFeatureBinding = (candidate: unknown): candidate is AnyFeatureBinding =>
374
+ typeof candidate === 'object' &&
375
+ candidate !== null &&
376
+ FeatureBindingTypeId in candidate &&
377
+ FeatureGraphValidTypeId in candidate
378
+
379
+ export interface FeatureNativeTree<
380
+ Summary extends AnyFeatureGraphSummary,
381
+ ROut,
382
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
383
+ SupportOut,
384
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
385
+ Composition extends CompositionClass<
386
+ FeatureSummaryCompositionProps<Summary>,
387
+ FeatureSummaryContract<Summary>,
388
+ FeatureSummaryStates<Summary>,
389
+ FeatureSummaryName<Summary>,
390
+ FeatureSummaryCompositionIdentity<Summary>
391
+ > = CompositionClass<
392
+ FeatureSummaryCompositionProps<Summary>,
393
+ FeatureSummaryContract<Summary>,
394
+ FeatureSummaryStates<Summary>,
395
+ FeatureSummaryName<Summary>,
396
+ FeatureSummaryCompositionIdentity<Summary>
397
+ >,
398
+ > {
399
+ readonly identity: symbol
400
+ readonly summary: Summary
401
+ readonly composition: Composition
402
+ readonly load: Effect.Effect<
403
+ FeatureModule<
404
+ ROut,
405
+ DirectRequires,
406
+ FeatureSummaryOpenServices<Summary>,
407
+ FeatureSummaryUsesInput<Summary>,
408
+ FeatureSummaryInput<Summary>
409
+ >,
410
+ FeatureLoadFailed
411
+ >
412
+ readonly binding: CapturedFeatureBinding<Summary, ROut, DirectRequires>
413
+ readonly eagerModule:
414
+ | FeatureModule<ROut, DirectRequires, FeatureSummaryOpenServices<Summary>, false, never>
415
+ | undefined
416
+ readonly supportModule:
417
+ | FeatureModule<
418
+ SupportOut,
419
+ SupportDirectRequires,
420
+ FeatureSummarySupportOpenServices<Summary>,
421
+ false,
422
+ never
423
+ >
424
+ | undefined
425
+ }
426
+
427
+ export interface EagerFeatureNativeTree<
428
+ Summary extends AnyFeatureGraphSummary,
112
429
  ROut,
113
- Requires extends ReadonlyArray<FeatureRequirement>,
114
- > extends FeatureClass<P, C, ROut, Requires> {
115
- readonly eagerModule: FeatureModule<ROut, Requires, false>
430
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
431
+ Composition extends CompositionClass<
432
+ FeatureSummaryCompositionProps<Summary>,
433
+ FeatureSummaryContract<Summary>,
434
+ FeatureSummaryStates<Summary>,
435
+ FeatureSummaryName<Summary>,
436
+ FeatureSummaryCompositionIdentity<Summary>
437
+ > = CompositionClass<
438
+ FeatureSummaryCompositionProps<Summary>,
439
+ FeatureSummaryContract<Summary>,
440
+ FeatureSummaryStates<Summary>,
441
+ FeatureSummaryName<Summary>,
442
+ FeatureSummaryCompositionIdentity<Summary>
443
+ >,
444
+ > extends FeatureNativeTree<Summary, ROut, DirectRequires, never, readonly [], Composition> {
445
+ readonly eagerModule: FeatureModule<
446
+ ROut,
447
+ DirectRequires,
448
+ FeatureSummaryOpenServices<Summary>,
449
+ false,
450
+ never
451
+ >
116
452
  readonly supportModule: undefined
117
453
  }
118
454
 
119
- export interface LazyFeatureClass<
120
- P,
121
- C extends UiContract,
455
+ export interface LazyFeatureNativeTree<
456
+ Summary extends AnyFeatureGraphSummary,
457
+ ROut,
458
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
459
+ SupportOut,
460
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
461
+ Composition extends CompositionClass<
462
+ FeatureSummaryCompositionProps<Summary>,
463
+ FeatureSummaryContract<Summary>,
464
+ FeatureSummaryStates<Summary>,
465
+ FeatureSummaryName<Summary>,
466
+ FeatureSummaryCompositionIdentity<Summary>
467
+ > = CompositionClass<
468
+ FeatureSummaryCompositionProps<Summary>,
469
+ FeatureSummaryContract<Summary>,
470
+ FeatureSummaryStates<Summary>,
471
+ FeatureSummaryName<Summary>,
472
+ FeatureSummaryCompositionIdentity<Summary>
473
+ >,
474
+ > extends FeatureNativeTree<
475
+ Summary,
122
476
  ROut,
123
- Requires extends ReadonlyArray<FeatureRequirement>,
124
- SupportRequires extends ReadonlyArray<FeatureRequirement> = readonly [],
125
- > extends FeatureClass<P, C, ROut, Requires, SupportRequires> {
477
+ DirectRequires,
478
+ SupportOut,
479
+ SupportDirectRequires,
480
+ Composition
481
+ > {
126
482
  readonly eagerModule: undefined
127
483
  }
128
484
 
129
- export type AnyFeature = FeatureClass<
130
- AnyValue,
131
- AnyValue,
132
- AnyValue,
133
- ReadonlyArray<FeatureRequirement>,
134
- ReadonlyArray<FeatureRequirement>
485
+ type FeatureSummaryOf<
486
+ FeatureIdentity,
487
+ MountProps,
488
+ CompositionProps,
489
+ C extends UiContract,
490
+ S extends ReadonlyArray<unknown>,
491
+ N extends string,
492
+ Identity,
493
+ OpenServices,
494
+ SupportOpenServices,
495
+ UsesInput extends boolean,
496
+ Input,
497
+ > = FeatureGraphSummary<
498
+ FeatureIdentity,
499
+ MountProps,
500
+ CompositionProps,
501
+ C,
502
+ S,
503
+ N,
504
+ Identity,
505
+ OpenServices,
506
+ SupportOpenServices,
507
+ UsesInput,
508
+ Input
509
+ >
510
+
511
+ export interface FeatureClass<
512
+ FeatureIdentity,
513
+ P,
514
+ C extends UiContract,
515
+ ROut,
516
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
517
+ OpenServices = DefaultFeatureOpenServices<ROut, DirectRequires>,
518
+ SupportOut = never,
519
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement> = readonly [],
520
+ SupportOpenServices = DefaultFeatureOpenServices<SupportOut, SupportDirectRequires>,
521
+ UsesInput extends boolean = false,
522
+ Input = UsesInput extends true ? P : never,
523
+ S extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
524
+ N extends string = RootCompositionName<ROut>,
525
+ CompositionProps = P,
526
+ Identity = RootCompositionIdentity<ROut>,
527
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity> = CompositionClass<
528
+ CompositionProps,
529
+ C,
530
+ S,
531
+ N,
532
+ Identity
533
+ >,
534
+ > extends FeatureDefinition<
535
+ FeatureIdentity,
536
+ FeatureSummaryOf<
537
+ FeatureIdentity,
538
+ P,
539
+ CompositionProps,
540
+ C,
541
+ S,
542
+ N,
543
+ Identity,
544
+ OpenServices,
545
+ SupportOpenServices,
546
+ UsesInput,
547
+ Input
548
+ >,
549
+ FeatureNativeTree<
550
+ FeatureSummaryOf<
551
+ FeatureIdentity,
552
+ P,
553
+ CompositionProps,
554
+ C,
555
+ S,
556
+ N,
557
+ Identity,
558
+ OpenServices,
559
+ SupportOpenServices,
560
+ UsesInput,
561
+ Input
562
+ >,
563
+ ROut,
564
+ DirectRequires,
565
+ SupportOut,
566
+ SupportDirectRequires,
567
+ Composition
568
+ >
569
+ > {}
570
+
571
+ export type EagerFeatureClass<
572
+ FeatureIdentity,
573
+ P,
574
+ C extends UiContract,
575
+ ROut,
576
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
577
+ OpenServices,
578
+ S extends ReadonlyArray<unknown>,
579
+ N extends string,
580
+ Identity,
581
+ Composition extends CompositionClass<P, C, S, N, Identity> = CompositionClass<
582
+ P,
583
+ C,
584
+ S,
585
+ N,
586
+ Identity
587
+ >,
588
+ > = FeatureDefinition<
589
+ FeatureIdentity,
590
+ FeatureSummaryOf<FeatureIdentity, P, P, C, S, N, Identity, OpenServices, never, false, never>,
591
+ EagerFeatureNativeTree<
592
+ FeatureSummaryOf<FeatureIdentity, P, P, C, S, N, Identity, OpenServices, never, false, never>,
593
+ ROut,
594
+ DirectRequires,
595
+ Composition
596
+ >
597
+ >
598
+
599
+ export type LazyFeatureClass<
600
+ FeatureIdentity,
601
+ P,
602
+ C extends UiContract,
603
+ ROut,
604
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
605
+ OpenServices,
606
+ SupportOut,
607
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
608
+ SupportOpenServices,
609
+ UsesInput extends boolean,
610
+ S extends ReadonlyArray<unknown>,
611
+ N extends string,
612
+ CompositionProps,
613
+ Identity,
614
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity> = CompositionClass<
615
+ CompositionProps,
616
+ C,
617
+ S,
618
+ N,
619
+ Identity
620
+ >,
621
+ > = FeatureDefinition<
622
+ FeatureIdentity,
623
+ FeatureSummaryOf<
624
+ FeatureIdentity,
625
+ P,
626
+ CompositionProps,
627
+ C,
628
+ S,
629
+ N,
630
+ Identity,
631
+ OpenServices,
632
+ SupportOpenServices,
633
+ UsesInput,
634
+ UsesInput extends true ? P : never
635
+ >,
636
+ LazyFeatureNativeTree<
637
+ FeatureSummaryOf<
638
+ FeatureIdentity,
639
+ P,
640
+ CompositionProps,
641
+ C,
642
+ S,
643
+ N,
644
+ Identity,
645
+ OpenServices,
646
+ SupportOpenServices,
647
+ UsesInput,
648
+ UsesInput extends true ? P : never
649
+ >,
650
+ ROut,
651
+ DirectRequires,
652
+ SupportOut,
653
+ SupportDirectRequires,
654
+ Composition
655
+ >
135
656
  >
136
657
 
137
- export const isFeature = (candidate: unknown): candidate is AnyFeature =>
658
+ interface RuntimeFeatureTree {
659
+ readonly composition: AnyComposition
660
+ readonly load: Effect.Effect<AnyLoadedModule, FeatureLoadFailed>
661
+ readonly binding: AnyFeatureBinding
662
+ readonly eagerModule: AnyLoadedModule | undefined
663
+ readonly supportModule: AnyLoadedModule | undefined
664
+ }
665
+
666
+ interface RuntimeFeature {
667
+ new (): {}
668
+ readonly manifest: FeatureManifest
669
+ readonly token: FeatureToken<unknown, RuntimeFeatureTree>
670
+ readonly summary: AnyFeatureGraphSummary
671
+ }
672
+
673
+ const isRuntimeFeatureToken = (
674
+ candidate: unknown,
675
+ ): candidate is FeatureToken<unknown, RuntimeFeatureTree> =>
676
+ typeof candidate === 'object' && candidate !== null && FeatureNativeTypeId in candidate
677
+
678
+ export const isFeature = (candidate: unknown): candidate is RuntimeFeature =>
138
679
  (typeof candidate === 'function' || typeof candidate === 'object') &&
139
680
  candidate !== null &&
140
- 'manifest' in candidate &&
141
- typeof candidate.manifest === 'object' &&
142
- candidate.manifest !== null &&
143
- 'kind' in candidate.manifest &&
144
- candidate.manifest.kind === 'Feature'
681
+ 'token' in candidate &&
682
+ isRuntimeFeatureToken(candidate.token)
145
683
 
146
- export interface Placeholders {
147
- readonly loading: CompositionClass<AnyValue, UiContract>
148
- readonly failed: CompositionClass<AnyValue, UiContract>
684
+ type PlaceholderComposition<P> = AnyComposition & CompositionPropsReceiver<P>
685
+
686
+ export interface Placeholders<P> {
687
+ readonly loading: PlaceholderComposition<P>
688
+ readonly failed: PlaceholderComposition<FailedProps>
149
689
  }
150
690
 
151
- interface FeatureConfigBaseExternalApi<P, C extends UiContract> {
152
- readonly composition: CompositionClass<P, C>
691
+ export type FeatureMountProps<
692
+ UsesInput extends boolean,
693
+ CompositionProps,
694
+ InputSchema extends Schema.Schema.AnyNoContext,
695
+ > = UsesInput extends true ? Schema.Schema.Type<InputSchema> : CompositionProps
696
+
697
+ type FeatureUsesInput<InputSchema extends Schema.Schema.AnyNoContext> = [InputSchema] extends [
698
+ never,
699
+ ]
700
+ ? false
701
+ : true
702
+
703
+ type FeatureInputValue<InputSchema extends Schema.Schema.AnyNoContext> = [InputSchema] extends [
704
+ never,
705
+ ]
706
+ ? never
707
+ : Schema.Schema.Type<InputSchema>
708
+
709
+ interface FeatureConfigBaseExternalApi<
710
+ P,
711
+ C extends UiContract,
712
+ S extends ReadonlyArray<unknown>,
713
+ N extends string,
714
+ Identity,
715
+ > {
716
+ readonly composition: CompositionClass<P, C, S, N, Identity>
153
717
  readonly boot?: ReadonlyArray<Tagged>
154
718
  }
155
719
 
720
+ interface ExactFeatureCompositionConfig<Composition extends AnyComposition> {
721
+ readonly composition: Composition
722
+ }
723
+
724
+ type IncludesComposition<ROut, N extends string, Identity> =
725
+ HasExactCompositionId<ROut, N, Identity> extends true
726
+ ? object
727
+ : {
728
+ readonly missingCompositionService: 'The feature module must provide its root composition'
729
+ }
730
+
156
731
  type EagerFeatureConfigExternalApi<
157
732
  P,
158
733
  C extends UiContract,
159
734
  ROut,
160
- Requires extends ReadonlyArray<FeatureRequirement>,
161
- > = FeatureConfigBaseExternalApi<P, C> & {
735
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
736
+ OpenServices,
737
+ S extends ReadonlyArray<unknown>,
738
+ N extends string,
739
+ Identity,
740
+ > = FeatureConfigBaseExternalApi<P, C, S, N, Identity> & {
162
741
  readonly loadingStrategy?: 'default'
163
- readonly module: FeatureModule<ROut, Requires, false>
742
+ readonly module: FeatureModule<ROut, DirectRequires, OpenServices, false, never> &
743
+ IncludesComposition<ROut, N, Identity>
164
744
  }
165
745
 
166
746
  type LazyFeatureConfigExternalApi<
167
- P,
747
+ CompositionProps,
168
748
  C extends UiContract,
169
749
  ROut,
170
- Requires extends ReadonlyArray<FeatureRequirement>,
750
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
751
+ OpenServices,
171
752
  SupportOut,
172
- SupportRequires extends ReadonlyArray<FeatureRequirement>,
173
- > = FeatureConfigBaseExternalApi<P, C> & {
753
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
754
+ SupportOpenServices,
755
+ S extends ReadonlyArray<unknown>,
756
+ N extends string,
757
+ Identity,
758
+ InputSchema extends Schema.Schema.AnyNoContext,
759
+ Loading extends AnyComposition,
760
+ Failed extends AnyComposition,
761
+ > = FeatureConfigBaseExternalApi<CompositionProps, C, S, N, Identity> & {
762
+ readonly composition: CompositionClass<CompositionProps, C, S, N, Identity> &
763
+ CompositionPropsReceiver<
764
+ NoInfer<FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>>
765
+ >
174
766
  readonly loadingStrategy: 'lazy'
175
- readonly load: Effect.Effect<FeatureModule<ROut, Requires, boolean>, FeatureLoadFailed>
176
- readonly placeholder: Placeholders
177
- readonly eager?: FeatureModule<SupportOut, SupportRequires, false>
767
+ readonly input?: InputSchema
768
+ readonly load: Effect.Effect<
769
+ FeatureModule<
770
+ ROut,
771
+ DirectRequires,
772
+ OpenServices,
773
+ FeatureUsesInput<InputSchema>,
774
+ FeatureInputValue<InputSchema>
775
+ > &
776
+ IncludesComposition<ROut, N, Identity>,
777
+ FeatureLoadFailed
778
+ >
779
+ readonly placeholder: {
780
+ readonly loading: Loading &
781
+ CompositionPropsReceiver<
782
+ NoInfer<FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>>
783
+ >
784
+ readonly failed: Failed & CompositionPropsReceiver<FailedProps>
785
+ }
786
+ readonly eager?: FeatureModule<
787
+ SupportOut,
788
+ SupportDirectRequires,
789
+ SupportOpenServices,
790
+ false,
791
+ never
792
+ >
793
+ } & (FeatureUsesInput<InputSchema> extends true ? { readonly input: InputSchema } : object)
794
+
795
+ type MakeImplementationConfigExternalApi =
796
+ | {
797
+ readonly composition: AnyComposition
798
+ readonly boot?: ReadonlyArray<Tagged>
799
+ readonly loadingStrategy?: 'default'
800
+ readonly module: AnyLoadedModule
801
+ }
802
+ | {
803
+ readonly composition: AnyComposition
804
+ readonly boot?: ReadonlyArray<Tagged>
805
+ readonly loadingStrategy: 'lazy'
806
+ readonly input?: Schema.Schema.Any
807
+ readonly load: Effect.Effect<AnyLoadedModule, FeatureLoadFailed>
808
+ readonly placeholder: {
809
+ readonly loading: AnyComposition
810
+ readonly failed: AnyComposition
811
+ }
812
+ readonly eager?: AnyLoadedModule
813
+ }
814
+
815
+ const isMakeImplementationConfig = (
816
+ candidate: unknown,
817
+ ): candidate is MakeImplementationConfigExternalApi =>
818
+ typeof candidate === 'object' &&
819
+ candidate !== null &&
820
+ 'composition' in candidate &&
821
+ (!('input' in candidate) || Schema.isSchema(candidate.input)) &&
822
+ ('module' in candidate || ('load' in candidate && 'placeholder' in candidate))
823
+
824
+ type RuntimePlaceholders = {
825
+ readonly loading: AnyComposition
826
+ readonly failed: AnyComposition
178
827
  }
179
828
 
180
- export function make<
829
+ const makeFeatureManifest = (
830
+ name: string,
831
+ strategy: 'lazy' | 'default',
832
+ composition: AnyComposition,
833
+ placeholder: Option.Option<RuntimePlaceholders>,
834
+ ): FeatureManifest => ({
835
+ kind: 'Feature',
836
+ name,
837
+ strategy,
838
+ composition: composition.manifest,
839
+ placeholder: Option.map(placeholder, (placeholders) => ({
840
+ loading: placeholders.loading.manifest,
841
+ failed: placeholders.failed.manifest,
842
+ })),
843
+ })
844
+
845
+ const makeFeatureInputParser =
846
+ (
847
+ composition: AnyComposition,
848
+ inputSchema: Schema.Schema.AnyNoContext | undefined,
849
+ placeholder: Option.Option<RuntimePlaceholders>,
850
+ ): ((input: unknown) => Option.Option<unknown>) =>
851
+ (input) =>
852
+ Option.flatMap(
853
+ Option.match(Option.fromNullable(inputSchema), {
854
+ onNone: () => Option.some(input),
855
+ onSome: (schema) => Schema.validateOption(schema)(input),
856
+ }),
857
+ (decoded) =>
858
+ Option.flatMap(composition.parseProps(decoded), () =>
859
+ Option.match(placeholder, {
860
+ onNone: () => Option.some(decoded),
861
+ onSome: (placeholders) =>
862
+ Option.map(placeholders.loading.parseProps(decoded), () => decoded),
863
+ }),
864
+ ),
865
+ )
866
+
867
+ const makeFeatureSummary = <
868
+ FeatureIdentity,
869
+ MountProps,
870
+ CompositionProps,
871
+ C extends UiContract,
872
+ S extends ReadonlyArray<unknown>,
873
+ N extends string,
874
+ Identity,
875
+ OpenServices,
876
+ SupportOpenServices,
877
+ UsesInput extends boolean,
878
+ Input,
879
+ >(
880
+ composition: CompositionClass<CompositionProps, C, S, N, Identity>,
881
+ ): FeatureSummaryOf<
882
+ FeatureIdentity,
883
+ MountProps,
884
+ CompositionProps,
885
+ C,
886
+ S,
887
+ N,
888
+ Identity,
889
+ OpenServices,
890
+ SupportOpenServices,
891
+ UsesInput,
892
+ Input
893
+ > => ({
894
+ [SlotBindingSummaryTypeId]: SlotBindingSummaryTypeId,
895
+ [FeatureChildSummaryTypeId]: {
896
+ featureIdentity: [],
897
+ compositionIdentity: [],
898
+ },
899
+ [FeatureGraphSummaryTypeId]: {
900
+ featureIdentity: [],
901
+ mountProps: [],
902
+ compositionProps: [],
903
+ contract: [],
904
+ states: [],
905
+ name: composition.manifest.name,
906
+ compositionIdentity: [],
907
+ openServices: [],
908
+ supportOpenServices: [],
909
+ usesInput: [],
910
+ input: [],
911
+ validated: FeatureModuleGraphValidTypeId,
912
+ },
913
+ })
914
+
915
+ const makeFeatureBinding = <
916
+ Summary extends AnyFeatureGraphSummary,
917
+ ROut,
918
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
919
+ >(
920
+ identity: symbol,
921
+ manifest: FeatureManifest,
922
+ summary: Summary,
923
+ composition: CompositionClass<
924
+ FeatureSummaryCompositionProps<Summary>,
925
+ FeatureSummaryContract<Summary>,
926
+ FeatureSummaryStates<Summary>,
927
+ FeatureSummaryName<Summary>,
928
+ FeatureSummaryCompositionIdentity<Summary>
929
+ >,
930
+ load: Effect.Effect<
931
+ LoadedModule<
932
+ ROut,
933
+ DirectRequires,
934
+ FeatureSummaryOpenServices<Summary>,
935
+ FeatureSummaryUsesInput<Summary>,
936
+ FeatureSummaryInput<Summary>
937
+ >,
938
+ FeatureLoadFailed
939
+ >,
940
+ placeholder: Option.Option<RuntimePlaceholders>,
941
+ inputSchema: Schema.Schema.AnyNoContext | undefined,
942
+ boot: ReadonlyArray<Tagged>,
943
+ ): FeatureBinding<Summary, ROut, DirectRequires> => {
944
+ const binding: FeatureBinding<Summary, ROut, DirectRequires> = {
945
+ [FeatureBindingTypeId]: FeatureBindingTypeId,
946
+ [FeatureGraphValidTypeId]: FeatureGraphValidTypeId,
947
+ manifest,
948
+ identity,
949
+ summary,
950
+ composition,
951
+ strategy: manifest.strategy,
952
+ load,
953
+ placeholder,
954
+ parseInput: makeFeatureInputParser(composition, inputSchema, placeholder),
955
+ boot,
956
+ capture: <Result>(visit: FeatureBindingCapture<Result>): Result => visit(binding),
957
+ }
958
+ return binding
959
+ }
960
+
961
+ const makeEagerFeature = <
181
962
  P,
182
963
  C extends UiContract,
183
964
  ROut,
184
- Requires extends ReadonlyArray<FeatureRequirement>,
965
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
966
+ OpenServices,
967
+ S extends ReadonlyArray<unknown>,
968
+ N extends string,
969
+ Identity,
970
+ Composition extends CompositionClass<P, C, S, N, Identity>,
185
971
  >(
186
972
  name: string,
187
- config: EagerFeatureConfigExternalApi<P, C, ROut, Requires>,
188
- ): EagerFeatureClass<P, C, ROut, Requires>
189
- export function make<
973
+ config: EagerFeatureConfigExternalApi<P, C, ROut, DirectRequires, OpenServices, S, N, Identity> &
974
+ ExactFeatureCompositionConfig<Composition>,
975
+ ): EagerFeatureClass<
976
+ Identity,
190
977
  P,
978
+ C,
979
+ ROut,
980
+ DirectRequires,
981
+ OpenServices,
982
+ S,
983
+ N,
984
+ Identity,
985
+ Composition
986
+ > => {
987
+ const featureIdentity = Symbol(name)
988
+ const placeholder = Option.none<RuntimePlaceholders>()
989
+ const manifest = makeFeatureManifest(name, 'default', config.composition, placeholder)
990
+ const load = Effect.succeed(config.module)
991
+ const summary = makeFeatureSummary<
992
+ Identity,
993
+ P,
994
+ P,
995
+ C,
996
+ S,
997
+ N,
998
+ Identity,
999
+ OpenServices,
1000
+ never,
1001
+ false,
1002
+ never
1003
+ >(config.composition)
1004
+ const binding = makeFeatureBinding<typeof summary, ROut, DirectRequires>(
1005
+ featureIdentity,
1006
+ manifest,
1007
+ summary,
1008
+ config.composition,
1009
+ load,
1010
+ placeholder,
1011
+ undefined,
1012
+ Option.getOrElse(Option.fromNullable(config.boot), () => []),
1013
+ )
1014
+ const native: EagerFeatureNativeTree<typeof summary, ROut, DirectRequires, Composition> = {
1015
+ identity: featureIdentity,
1016
+ summary,
1017
+ composition: config.composition,
1018
+ load,
1019
+ binding,
1020
+ eagerModule: config.module,
1021
+ supportModule: undefined,
1022
+ }
1023
+ const token: FeatureToken<Identity, typeof native> = {
1024
+ identity: featureIdentity,
1025
+ [FeatureTokenIdentityTypeId]: [],
1026
+ [FeatureNativeTypeId]: native,
1027
+ }
1028
+ return definitionClass<
1029
+ EagerFeatureClass<
1030
+ Identity,
1031
+ P,
1032
+ C,
1033
+ ROut,
1034
+ DirectRequires,
1035
+ OpenServices,
1036
+ S,
1037
+ N,
1038
+ Identity,
1039
+ Composition
1040
+ >
1041
+ >({
1042
+ manifest,
1043
+ summary,
1044
+ token,
1045
+ })
1046
+ }
1047
+
1048
+ const makeLazyFeature = <
1049
+ CompositionProps,
191
1050
  C extends UiContract,
192
1051
  ROut,
193
- Requires extends ReadonlyArray<FeatureRequirement>,
1052
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
1053
+ OpenServices,
194
1054
  SupportOut,
195
- SupportRequires extends ReadonlyArray<FeatureRequirement>,
1055
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
1056
+ SupportOpenServices,
1057
+ S extends ReadonlyArray<unknown>,
1058
+ N extends string,
1059
+ Identity,
1060
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity>,
1061
+ InputSchema extends Schema.Schema.AnyNoContext,
1062
+ Loading extends AnyComposition,
1063
+ Failed extends AnyComposition,
196
1064
  >(
197
1065
  name: string,
198
1066
  config: LazyFeatureConfigExternalApi<
199
- P,
1067
+ CompositionProps,
1068
+ C,
1069
+ ROut,
1070
+ DirectRequires,
1071
+ OpenServices,
1072
+ SupportOut,
1073
+ SupportDirectRequires,
1074
+ SupportOpenServices,
1075
+ S,
1076
+ N,
1077
+ Identity,
1078
+ InputSchema,
1079
+ Loading,
1080
+ Failed
1081
+ > &
1082
+ ExactFeatureCompositionConfig<Composition>,
1083
+ ): LazyFeatureClass<
1084
+ Identity,
1085
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
1086
+ C,
1087
+ ROut,
1088
+ DirectRequires,
1089
+ OpenServices,
1090
+ SupportOut,
1091
+ SupportDirectRequires,
1092
+ SupportOpenServices,
1093
+ FeatureUsesInput<InputSchema>,
1094
+ S,
1095
+ N,
1096
+ CompositionProps,
1097
+ Identity,
1098
+ Composition
1099
+ > => {
1100
+ const featureIdentity = Symbol(name)
1101
+ const placeholder = Option.some<RuntimePlaceholders>(config.placeholder)
1102
+ const manifest = makeFeatureManifest(name, 'lazy', config.composition, placeholder)
1103
+ const inputSchema = 'input' in config ? config.input : undefined
1104
+ const summary = makeFeatureSummary<
1105
+ Identity,
1106
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
1107
+ CompositionProps,
200
1108
  C,
1109
+ S,
1110
+ N,
1111
+ Identity,
1112
+ OpenServices,
1113
+ SupportOpenServices,
1114
+ FeatureUsesInput<InputSchema>,
1115
+ FeatureInputValue<InputSchema>
1116
+ >(config.composition)
1117
+ const binding = makeFeatureBinding<typeof summary, ROut, DirectRequires>(
1118
+ featureIdentity,
1119
+ manifest,
1120
+ summary,
1121
+ config.composition,
1122
+ config.load,
1123
+ placeholder,
1124
+ inputSchema,
1125
+ Option.getOrElse(Option.fromNullable(config.boot), () => []),
1126
+ )
1127
+ const native: LazyFeatureNativeTree<
1128
+ typeof summary,
201
1129
  ROut,
202
- Requires,
1130
+ DirectRequires,
203
1131
  SupportOut,
204
- SupportRequires
205
- > & { readonly eager: FeatureModule<SupportOut, SupportRequires, false> },
206
- ): LazyFeatureClass<P, C, ROut, Requires, SupportRequires>
1132
+ SupportDirectRequires,
1133
+ Composition
1134
+ > = {
1135
+ identity: featureIdentity,
1136
+ summary,
1137
+ composition: config.composition,
1138
+ load: config.load,
1139
+ binding,
1140
+ eagerModule: undefined,
1141
+ supportModule: config.eager,
1142
+ }
1143
+ const token: FeatureToken<Identity, typeof native> = {
1144
+ identity: featureIdentity,
1145
+ [FeatureTokenIdentityTypeId]: [],
1146
+ [FeatureNativeTypeId]: native,
1147
+ }
1148
+ return definitionClass<
1149
+ LazyFeatureClass<
1150
+ Identity,
1151
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
1152
+ C,
1153
+ ROut,
1154
+ DirectRequires,
1155
+ OpenServices,
1156
+ SupportOut,
1157
+ SupportDirectRequires,
1158
+ SupportOpenServices,
1159
+ FeatureUsesInput<InputSchema>,
1160
+ S,
1161
+ N,
1162
+ CompositionProps,
1163
+ Identity,
1164
+ Composition
1165
+ >
1166
+ >({
1167
+ manifest,
1168
+ summary,
1169
+ token,
1170
+ })
1171
+ }
1172
+
207
1173
  export function make<
208
1174
  P,
209
1175
  C extends UiContract,
210
1176
  ROut,
211
- Requires extends ReadonlyArray<FeatureRequirement>,
1177
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
1178
+ OpenServices,
1179
+ S extends ReadonlyArray<unknown>,
1180
+ N extends string,
1181
+ Identity,
1182
+ const Composition extends CompositionClass<P, C, S, N, Identity>,
212
1183
  >(
213
1184
  name: string,
214
- config: LazyFeatureConfigExternalApi<P, C, ROut, Requires, never, readonly []>,
215
- ): LazyFeatureClass<P, C, ROut, Requires>
216
- export function make(
1185
+ config: EagerFeatureConfigExternalApi<
1186
+ P,
1187
+ C,
1188
+ ROut,
1189
+ DirectRequires,
1190
+ OpenServices,
1191
+ S,
1192
+ N,
1193
+ Identity
1194
+ > & { readonly composition: Composition },
1195
+ ): EagerFeatureClass<
1196
+ Identity,
1197
+ P,
1198
+ C,
1199
+ ROut,
1200
+ DirectRequires,
1201
+ OpenServices,
1202
+ S,
1203
+ N,
1204
+ Identity,
1205
+ Composition
1206
+ >
1207
+ export function make<
1208
+ CompositionProps,
1209
+ C extends UiContract,
1210
+ ROut,
1211
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
1212
+ OpenServices,
1213
+ S extends ReadonlyArray<unknown>,
1214
+ N extends string,
1215
+ Identity,
1216
+ const Composition extends CompositionClass<CompositionProps, C, S, N, Identity>,
1217
+ const Loading extends AnyComposition,
1218
+ const Failed extends AnyComposition,
1219
+ const InputSchema extends Schema.Schema.AnyNoContext = never,
1220
+ SupportOut = never,
1221
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement> = readonly [],
1222
+ SupportOpenServices = never,
1223
+ >(
217
1224
  name: string,
218
- config:
1225
+ config: LazyFeatureConfigExternalApi<
1226
+ CompositionProps,
1227
+ C,
1228
+ ROut,
1229
+ DirectRequires,
1230
+ OpenServices,
1231
+ SupportOut,
1232
+ SupportDirectRequires,
1233
+ SupportOpenServices,
1234
+ S,
1235
+ N,
1236
+ Identity,
1237
+ InputSchema,
1238
+ Loading,
1239
+ Failed
1240
+ > & { readonly composition: Composition },
1241
+ ): LazyFeatureClass<
1242
+ Identity,
1243
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
1244
+ C,
1245
+ ROut,
1246
+ DirectRequires,
1247
+ OpenServices,
1248
+ SupportOut,
1249
+ SupportDirectRequires,
1250
+ SupportOpenServices,
1251
+ FeatureUsesInput<InputSchema>,
1252
+ S,
1253
+ N,
1254
+ CompositionProps,
1255
+ Identity,
1256
+ Composition
1257
+ >
1258
+ export function make<
1259
+ CompositionProps,
1260
+ C extends UiContract,
1261
+ ROut,
1262
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
1263
+ OpenServices,
1264
+ S extends ReadonlyArray<unknown>,
1265
+ N extends string,
1266
+ Identity,
1267
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity>,
1268
+ Loading extends AnyComposition,
1269
+ Failed extends AnyComposition,
1270
+ InputSchema extends Schema.Schema.AnyNoContext = never,
1271
+ SupportOut = never,
1272
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement> = readonly [],
1273
+ SupportOpenServices = never,
1274
+ >(
1275
+ name: string,
1276
+ config: (
219
1277
  | EagerFeatureConfigExternalApi<
220
- AnyValue,
221
- UiContract,
222
- never,
223
- ReadonlyArray<FeatureRequirement>
1278
+ CompositionProps,
1279
+ C,
1280
+ ROut,
1281
+ DirectRequires,
1282
+ OpenServices,
1283
+ S,
1284
+ N,
1285
+ Identity
224
1286
  >
225
1287
  | LazyFeatureConfigExternalApi<
226
- AnyValue,
227
- UiContract,
228
- never,
229
- ReadonlyArray<FeatureRequirement>,
230
- never,
231
- ReadonlyArray<FeatureRequirement>
232
- >,
233
- ): AnyFeature {
234
- const strategy: 'lazy' | 'default' = config.loadingStrategy ?? 'default'
235
- const load = 'load' in config ? config.load : Effect.succeed(config.module)
236
- const eagerModule = 'module' in config ? config.module : undefined
237
- const supportModule = 'eager' in config ? config.eager : undefined
238
- const placeholder = 'placeholder' in config ? config.placeholder : undefined
239
-
240
- const manifest: FeatureManifest = {
241
- kind: 'Feature',
242
- name,
243
- strategy,
244
- composition: config.composition.manifest,
245
- placeholder: Option.fromNullable(placeholder).pipe(
246
- Option.map((placeholders) => ({
247
- loading: placeholders.loading.manifest,
248
- failed: placeholders.failed.manifest,
249
- })),
250
- ),
1288
+ CompositionProps,
1289
+ C,
1290
+ ROut,
1291
+ DirectRequires,
1292
+ OpenServices,
1293
+ SupportOut,
1294
+ SupportDirectRequires,
1295
+ SupportOpenServices,
1296
+ S,
1297
+ N,
1298
+ Identity,
1299
+ InputSchema,
1300
+ Loading,
1301
+ Failed
1302
+ >
1303
+ ) &
1304
+ ExactFeatureCompositionConfig<Composition>,
1305
+ ):
1306
+ | EagerFeatureClass<
1307
+ Identity,
1308
+ CompositionProps,
1309
+ C,
1310
+ ROut,
1311
+ DirectRequires,
1312
+ OpenServices,
1313
+ S,
1314
+ N,
1315
+ Identity,
1316
+ Composition
1317
+ >
1318
+ | LazyFeatureClass<
1319
+ Identity,
1320
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
1321
+ C,
1322
+ ROut,
1323
+ DirectRequires,
1324
+ OpenServices,
1325
+ SupportOut,
1326
+ SupportDirectRequires,
1327
+ SupportOpenServices,
1328
+ FeatureUsesInput<InputSchema>,
1329
+ S,
1330
+ N,
1331
+ CompositionProps,
1332
+ Identity,
1333
+ Composition
1334
+ > {
1335
+ if (!isMakeImplementationConfig(config)) {
1336
+ return Effect.runSync(Effect.dieMessage(`Invalid Feature config for ${name}`))
251
1337
  }
252
-
253
- const binding: FeatureBinding = {
254
- [FeatureBindingTypeId]: FeatureBindingTypeId,
255
- manifest,
256
- composition: config.composition,
257
- strategy,
258
- load,
259
- ...(placeholder ? { placeholder: { loading: placeholder.loading, failed: placeholder.failed } } : {}),
260
- boot: Option.getOrElse(Option.fromNullable(config.boot), () => []),
1338
+ if (config.loadingStrategy === 'lazy') {
1339
+ return makeLazyFeature<
1340
+ CompositionProps,
1341
+ C,
1342
+ ROut,
1343
+ DirectRequires,
1344
+ OpenServices,
1345
+ SupportOut,
1346
+ SupportDirectRequires,
1347
+ SupportOpenServices,
1348
+ S,
1349
+ N,
1350
+ Identity,
1351
+ Composition,
1352
+ InputSchema,
1353
+ Loading,
1354
+ Failed
1355
+ >(name, config)
261
1356
  }
262
-
263
- return definitionClass<AnyFeature>({
264
- manifest,
265
- load,
266
- binding,
267
- eagerModule,
268
- supportModule,
269
- composition: config.composition,
270
- })
1357
+ return makeEagerFeature<
1358
+ CompositionProps,
1359
+ C,
1360
+ ROut,
1361
+ DirectRequires,
1362
+ OpenServices,
1363
+ S,
1364
+ N,
1365
+ Identity,
1366
+ Composition
1367
+ >(name, config)
271
1368
  }
272
1369
 
273
- // oxlint-disable-next-line reform-rules/prefer-effect-fn -- generic export: Effect.fn's inferred type isn't portable under isolatedDeclarations
274
- export const mountFeature = <R>(
275
- binding: FeatureBinding,
276
- engineContext: Context.Context<R>,
277
- props?: unknown,
278
- ): Effect.Effect<Context.Context<unknown>, FeatureLoadFailed, Scope.Scope> =>
279
- Effect.gen(function* () {
280
- const module = yield* binding.load
281
- // oxlint-disable-next-line reform-rules/no-type-assertion -- documented erasure boundary: re-attach the concrete engine context to the type-erased `LoadedModule` layer (mirrors `buildRuntime`); RIn was verified cast-free at `provide`/scene wiring
282
- const featureLayer = module.layer as Layer.Layer<unknown, never, R | FeatureInput>
283
- const featureContext = Context.add(engineContext, FeatureInput, props)
284
- const context = yield* Layer.build(
285
- Layer.provideMerge(featureLayer, Layer.succeedContext(featureContext)),
286
- )
287
- yield* Effect.forEach(binding.boot, (event) => publish('High', event), { discard: true }).pipe(
288
- Effect.provide(context),
289
- )
290
- return context
291
- })
1370
+ type MountFeature = <
1371
+ Summary extends AnyFeatureGraphSummary,
1372
+ ROut,
1373
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
1374
+ HostServices,
1375
+ >(
1376
+ binding: FeatureBinding<Summary, ROut, DirectRequires>,
1377
+ engineContext: Context.Context<
1378
+ HostServices | EngineServices | FeatureSummaryOpenServices<Summary>
1379
+ >,
1380
+ props?: FeatureSummaryInput<Summary>,
1381
+ ) => Effect.Effect<
1382
+ Context.Context<HostServices | EngineServices | FeatureSummaryOpenServices<Summary> | ROut>,
1383
+ FeatureLoadFailed,
1384
+ Scope.Scope
1385
+ >
1386
+
1387
+ export const mountFeature: MountFeature = Effect.fn('mountFeature')(function* <
1388
+ Summary extends AnyFeatureGraphSummary,
1389
+ ROut,
1390
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
1391
+ HostServices,
1392
+ >(
1393
+ binding: FeatureBinding<Summary, ROut, DirectRequires>,
1394
+ engineContext: Context.Context<
1395
+ HostServices | EngineServices | FeatureSummaryOpenServices<Summary>
1396
+ >,
1397
+ props: FeatureSummaryInput<Summary> | undefined = undefined,
1398
+ ): Effect.fn.Return<
1399
+ Context.Context<HostServices | EngineServices | FeatureSummaryOpenServices<Summary> | ROut>,
1400
+ FeatureLoadFailed,
1401
+ Scope.Scope
1402
+ > {
1403
+ const { layer } = yield* binding.load
1404
+ const bus = Context.get(engineContext, Bus)
1405
+ const featureContext = Context.add(engineContext, FeatureInput, props)
1406
+ const context = yield* Layer.build(Layer.provide(layer, Layer.succeedContext(featureContext)))
1407
+ yield* Effect.forEach(binding.boot, (event) => publish('High', event), {
1408
+ discard: true,
1409
+ }).pipe(Effect.provideService(Bus, bus))
1410
+ return Context.merge(engineContext, context)
1411
+ })