@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
@@ -0,0 +1,82 @@
1
+ import { Context } from 'effect'
2
+ import { CompositionIdTypeId } from '../compose/composition'
3
+ import type { OpenServicesFromFeatureSlotWitnesses } from '../graph/closure'
4
+ import type { EngineServices } from '../runtime/loop'
5
+
6
+ const FeatureInputBase: Context.TagClass<FeatureInput, 'reform/FeatureInput', unknown> =
7
+ Context.Tag('reform/FeatureInput')<FeatureInput, unknown>()
8
+ export class FeatureInput extends FeatureInputBase {}
9
+
10
+ // Context.Tag is invariant, so erased requirements use its structural identity carrier.
11
+ export interface FeatureServiceTag<out Identifier = unknown, out Service = unknown> {
12
+ readonly [Context.TagTypeId]: object
13
+ readonly Identifier: Identifier
14
+ readonly Service: Service
15
+ }
16
+
17
+ export interface StoredFeatureRequirement<out Identifier = unknown, out Service = unknown> {
18
+ readonly store: FeatureServiceTag<Identifier, Service>
19
+ }
20
+
21
+ export type RequireCarrier =
22
+ | StoredFeatureRequirement
23
+ | {
24
+ readonly members: ReadonlyArray<StoredFeatureRequirement>
25
+ }
26
+
27
+ export type FeatureRequirement = RequireCarrier | FeatureServiceTag
28
+
29
+ export type TagRequirement<Tag> =
30
+ Tag extends FeatureServiceTag<infer Identifier, infer _Service> ? Identifier : never
31
+
32
+ export type StoredRequirementIdentifier<Carrier> = Carrier extends {
33
+ readonly store: infer Tag
34
+ }
35
+ ? TagRequirement<Tag>
36
+ : never
37
+
38
+ export type RequirementIdentifier<Requirement> = Requirement extends FeatureServiceTag
39
+ ? TagRequirement<Requirement>
40
+ : Requirement extends {
41
+ readonly members: infer Members extends ReadonlyArray<StoredFeatureRequirement>
42
+ }
43
+ ? StoredRequirementIdentifier<Members[number]>
44
+ : StoredRequirementIdentifier<Requirement>
45
+
46
+ export type ProvidedBy<Requires extends ReadonlyArray<FeatureRequirement>> = RequirementIdentifier<
47
+ Requires[number]
48
+ >
49
+
50
+ export type DefaultFeatureOpenServices<ROut, DirectRequires extends ReadonlyArray<FeatureRequirement>> =
51
+ | ProvidedBy<DirectRequires>
52
+ | OpenServicesFromFeatureSlotWitnesses<ROut>
53
+
54
+ export type CompositionNames<ROut> = ROut extends {
55
+ readonly [CompositionIdTypeId]: {
56
+ readonly name: infer Name extends string
57
+ }
58
+ }
59
+ ? Name
60
+ : never
61
+
62
+ export type CompositionIdentities<ROut> = ROut extends {
63
+ readonly [CompositionIdTypeId]: {
64
+ readonly identity: infer Identity
65
+ }
66
+ }
67
+ ? Identity
68
+ : never
69
+
70
+ export type RootCompositionName<ROut> = [CompositionNames<ROut>] extends [never]
71
+ ? string
72
+ : CompositionNames<ROut>
73
+
74
+ export type RootCompositionIdentity<ROut> = [CompositionIdentities<ROut>] extends [never]
75
+ ? unknown
76
+ : CompositionIdentities<ROut>
77
+
78
+ // Feature layers may require the shared engine but must never provide it.
79
+ export type FeatureModuleInput<OpenServices, UsesInput extends boolean> =
80
+ | EngineServices
81
+ | OpenServices
82
+ | (UsesInput extends true ? FeatureInput : never)
@@ -0,0 +1,234 @@
1
+ import { Effect, Option, type Schema } from 'effect'
2
+ import type { AnyComposition, CompositionClass } from '../compose/composition'
3
+ import type { UiContract } from '../compose/ui'
4
+ import { definitionClass } from '../definition/definition'
5
+ import {
6
+ FeatureNativeTypeId,
7
+ type FeatureToken,
8
+ FeatureTokenIdentityTypeId,
9
+ } from './featureBinding'
10
+ import type { EagerFeatureClass, LazyFeatureClass } from './featureClass'
11
+ import type {
12
+ EagerFeatureConfigExternalApi,
13
+ ExactFeatureCompositionConfig,
14
+ FeatureInputValue,
15
+ FeatureMountProps,
16
+ FeatureUsesInput,
17
+ LazyFeatureConfigExternalApi,
18
+ RuntimePlaceholders,
19
+ } from './featureConfig'
20
+ import { makeFeatureBinding, makeFeatureManifest, makeFeatureSummary } from './featureFactory'
21
+ import type { EagerFeatureNativeTree, LazyFeatureNativeTree } from './featureNativeTree'
22
+ import type { FeatureRequirement } from './featureRequirement'
23
+
24
+ export const makeEagerFeature = <
25
+ P,
26
+ C extends UiContract,
27
+ ROut,
28
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
29
+ OpenServices,
30
+ S extends ReadonlyArray<unknown>,
31
+ N extends string,
32
+ Identity,
33
+ Composition extends CompositionClass<P, C, S, N, Identity>,
34
+ >(
35
+ name: string,
36
+ config: EagerFeatureConfigExternalApi<P, C, ROut, DirectRequires, OpenServices, S, N, Identity> &
37
+ ExactFeatureCompositionConfig<Composition>,
38
+ ): EagerFeatureClass<
39
+ Identity,
40
+ P,
41
+ C,
42
+ ROut,
43
+ DirectRequires,
44
+ OpenServices,
45
+ S,
46
+ N,
47
+ Identity,
48
+ Composition
49
+ > => {
50
+ const featureIdentity = Symbol(name)
51
+ const placeholder = Option.none<RuntimePlaceholders>()
52
+ const manifest = makeFeatureManifest(name, 'default', config.composition, placeholder)
53
+ const load = Effect.succeed(config.module)
54
+ const summary = makeFeatureSummary<
55
+ Identity,
56
+ P,
57
+ P,
58
+ C,
59
+ S,
60
+ N,
61
+ Identity,
62
+ OpenServices,
63
+ never,
64
+ false,
65
+ never
66
+ >(config.composition)
67
+ const binding = makeFeatureBinding<typeof summary, ROut, DirectRequires>(
68
+ featureIdentity,
69
+ manifest,
70
+ summary,
71
+ config.composition,
72
+ load,
73
+ placeholder,
74
+ undefined,
75
+ Option.getOrElse(Option.fromNullable(config.boot), () => []),
76
+ )
77
+ const native: EagerFeatureNativeTree<typeof summary, ROut, DirectRequires, Composition> = {
78
+ identity: featureIdentity,
79
+ summary,
80
+ composition: config.composition,
81
+ load,
82
+ binding,
83
+ eagerModule: config.module,
84
+ supportModule: undefined,
85
+ }
86
+ const token: FeatureToken<Identity, typeof native> = {
87
+ identity: featureIdentity,
88
+ [FeatureTokenIdentityTypeId]: [],
89
+ [FeatureNativeTypeId]: native,
90
+ }
91
+ return definitionClass<
92
+ EagerFeatureClass<
93
+ Identity,
94
+ P,
95
+ C,
96
+ ROut,
97
+ DirectRequires,
98
+ OpenServices,
99
+ S,
100
+ N,
101
+ Identity,
102
+ Composition
103
+ >
104
+ >({
105
+ manifest,
106
+ summary,
107
+ token,
108
+ })
109
+ }
110
+
111
+ export const makeLazyFeature = <
112
+ CompositionProps,
113
+ C extends UiContract,
114
+ ROut,
115
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
116
+ OpenServices,
117
+ SupportOut,
118
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
119
+ SupportOpenServices,
120
+ S extends ReadonlyArray<unknown>,
121
+ N extends string,
122
+ Identity,
123
+ Composition extends CompositionClass<CompositionProps, C, S, N, Identity>,
124
+ InputSchema extends Schema.Schema.AnyNoContext,
125
+ Loading extends AnyComposition,
126
+ Failed extends AnyComposition,
127
+ >(
128
+ name: string,
129
+ config: LazyFeatureConfigExternalApi<
130
+ CompositionProps,
131
+ C,
132
+ ROut,
133
+ DirectRequires,
134
+ OpenServices,
135
+ SupportOut,
136
+ SupportDirectRequires,
137
+ SupportOpenServices,
138
+ S,
139
+ N,
140
+ Identity,
141
+ InputSchema,
142
+ Loading,
143
+ Failed
144
+ > &
145
+ ExactFeatureCompositionConfig<Composition>,
146
+ ): LazyFeatureClass<
147
+ Identity,
148
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
149
+ C,
150
+ ROut,
151
+ DirectRequires,
152
+ OpenServices,
153
+ SupportOut,
154
+ SupportDirectRequires,
155
+ SupportOpenServices,
156
+ FeatureUsesInput<InputSchema>,
157
+ S,
158
+ N,
159
+ CompositionProps,
160
+ Identity,
161
+ Composition
162
+ > => {
163
+ const featureIdentity = Symbol(name)
164
+ const placeholder = Option.some<RuntimePlaceholders>(config.placeholder)
165
+ const manifest = makeFeatureManifest(name, 'lazy', config.composition, placeholder)
166
+ const inputSchema = 'input' in config ? config.input : undefined
167
+ const summary = makeFeatureSummary<
168
+ Identity,
169
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
170
+ CompositionProps,
171
+ C,
172
+ S,
173
+ N,
174
+ Identity,
175
+ OpenServices,
176
+ SupportOpenServices,
177
+ FeatureUsesInput<InputSchema>,
178
+ FeatureInputValue<InputSchema>
179
+ >(config.composition)
180
+ const binding = makeFeatureBinding<typeof summary, ROut, DirectRequires>(
181
+ featureIdentity,
182
+ manifest,
183
+ summary,
184
+ config.composition,
185
+ config.load,
186
+ placeholder,
187
+ inputSchema,
188
+ Option.getOrElse(Option.fromNullable(config.boot), () => []),
189
+ )
190
+ const native: LazyFeatureNativeTree<
191
+ typeof summary,
192
+ ROut,
193
+ DirectRequires,
194
+ SupportOut,
195
+ SupportDirectRequires,
196
+ Composition
197
+ > = {
198
+ identity: featureIdentity,
199
+ summary,
200
+ composition: config.composition,
201
+ load: config.load,
202
+ binding,
203
+ eagerModule: undefined,
204
+ supportModule: config.eager,
205
+ }
206
+ const token: FeatureToken<Identity, typeof native> = {
207
+ identity: featureIdentity,
208
+ [FeatureTokenIdentityTypeId]: [],
209
+ [FeatureNativeTypeId]: native,
210
+ }
211
+ return definitionClass<
212
+ LazyFeatureClass<
213
+ Identity,
214
+ FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>,
215
+ C,
216
+ ROut,
217
+ DirectRequires,
218
+ OpenServices,
219
+ SupportOut,
220
+ SupportDirectRequires,
221
+ SupportOpenServices,
222
+ FeatureUsesInput<InputSchema>,
223
+ S,
224
+ N,
225
+ CompositionProps,
226
+ Identity,
227
+ Composition
228
+ >
229
+ >({
230
+ manifest,
231
+ summary,
232
+ token,
233
+ })
234
+ }
@@ -0,0 +1,54 @@
1
+ import { Context, Effect, Layer, type Scope } from 'effect'
2
+ import type {
3
+ AnyFeatureGraphSummary,
4
+ FeatureSummaryInput,
5
+ FeatureSummaryOpenServices,
6
+ } from '../graph/closure'
7
+ import type { FeatureLoadFailed } from '../internal/errors'
8
+ import { Bus, publish } from '../runtime/bus'
9
+ import type { EngineServices } from '../runtime/loop'
10
+ import type { FeatureBinding } from './featureBinding'
11
+ import { FeatureInput, type FeatureRequirement } from './featureRequirement'
12
+
13
+ type MountFeature = <
14
+ Summary extends AnyFeatureGraphSummary,
15
+ ROut,
16
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
17
+ HostServices,
18
+ >(
19
+ binding: FeatureBinding<Summary, ROut, DirectRequires>,
20
+ engineContext: Context.Context<
21
+ HostServices | EngineServices | FeatureSummaryOpenServices<Summary>
22
+ >,
23
+ props?: FeatureSummaryInput<Summary>,
24
+ ) => Effect.Effect<
25
+ Context.Context<HostServices | EngineServices | FeatureSummaryOpenServices<Summary> | ROut>,
26
+ FeatureLoadFailed,
27
+ Scope.Scope
28
+ >
29
+
30
+ export const mountFeature: MountFeature = Effect.fn('mountFeature')(function* <
31
+ Summary extends AnyFeatureGraphSummary,
32
+ ROut,
33
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
34
+ HostServices,
35
+ >(
36
+ binding: FeatureBinding<Summary, ROut, DirectRequires>,
37
+ engineContext: Context.Context<
38
+ HostServices | EngineServices | FeatureSummaryOpenServices<Summary>
39
+ >,
40
+ props: FeatureSummaryInput<Summary> | undefined = undefined,
41
+ ): Effect.fn.Return<
42
+ Context.Context<HostServices | EngineServices | FeatureSummaryOpenServices<Summary> | ROut>,
43
+ FeatureLoadFailed,
44
+ Scope.Scope
45
+ > {
46
+ const { layer } = yield* binding.load
47
+ const bus = Context.get(engineContext, Bus)
48
+ const featureContext = Context.add(engineContext, FeatureInput, props)
49
+ const context = yield* Layer.build(Layer.provide(layer, Layer.succeedContext(featureContext)))
50
+ yield* Effect.forEach(binding.boot, (event) => publish('High', event), {
51
+ discard: true,
52
+ }).pipe(Effect.provideService(Bus, bus))
53
+ return Context.merge(engineContext, context)
54
+ })
@@ -0,0 +1,222 @@
1
+ import type { Layer } from 'effect'
2
+ import { CompositionIdTypeId } from '../compose/composition'
3
+ import { SlotServiceTypeId } from '../compose/slot'
4
+ import type { FeatureRequirement, ProvidedBy } from '../feature/feature'
5
+ import type { EngineServices } from '../runtime/loop'
6
+ import type {
7
+ ExactIdentity,
8
+ ExactServiceExclude,
9
+ ExactServiceExtract,
10
+ GraphValidationPassed,
11
+ InputService,
12
+ IsErased,
13
+ } from './services'
14
+ import type { CompositionChildSummary } from './summary'
15
+ import type { DescendantOpenServices } from './services'
16
+
17
+ export {
18
+ CompositionChildSummaryTypeId,
19
+ FeatureChildSummaryTypeId,
20
+ FeatureGraphSummaryTypeId,
21
+ FeatureGraphValidTypeId,
22
+ FeatureModuleGraphValidTypeId,
23
+ SlotBindingSummaryTypeId,
24
+ } from './summary'
25
+ export type {
26
+ AnyFeatureGraphSummary,
27
+ CompositionChildSummary,
28
+ FeatureChildSummary,
29
+ FeatureGraphSummary,
30
+ FeatureSummaryCompositionIdentity,
31
+ FeatureSummaryCompositionProps,
32
+ FeatureSummaryContract,
33
+ FeatureSummaryIdentity,
34
+ FeatureSummaryInput,
35
+ FeatureSummaryMountProps,
36
+ FeatureSummaryName,
37
+ FeatureSummaryOpenServices,
38
+ FeatureSummaryStates,
39
+ FeatureSummarySupportOpenServices,
40
+ FeatureSummaryUsesInput,
41
+ SlotBindingSummary,
42
+ } from './summary'
43
+ export type {
44
+ DescendantOpenServices,
45
+ ExactServiceExclude,
46
+ ModuleOpenServices,
47
+ OpenServicesFromFeatureSlotWitnesses,
48
+ } from './services'
49
+
50
+ type LayerErrorIsNever<L extends Layer.Layer.Any> =
51
+ IsErased<Layer.Layer.Error<L>> extends true
52
+ ? {
53
+ readonly featureLayerErrorMustBeNever: 'A feature layer error channel cannot be erased'
54
+ }
55
+ : [Layer.Layer.Error<L>] extends [never]
56
+ ? GraphValidationPassed
57
+ : {
58
+ readonly featureLayerErrorMustBeNever: 'A feature layer must have an error channel of exactly never'
59
+ }
60
+
61
+ type LayerOutputsAreExact<L extends Layer.Layer.Any> =
62
+ IsErased<Layer.Layer.Success<L>> extends true
63
+ ? {
64
+ readonly featureLayerOutputsMustBeExact: 'A feature layer output cannot be erased'
65
+ }
66
+ : GraphValidationPassed
67
+
68
+ type DescendantOpenServicesAreExact<L extends Layer.Layer.Any> =
69
+ IsErased<DescendantOpenServices<L>> extends true
70
+ ? {
71
+ readonly featureDescendantOpenServicesMustBeExact: "A child feature's open services cannot be erased"
72
+ }
73
+ : GraphValidationPassed
74
+
75
+ type UnexpectedOpenServices<
76
+ L extends Layer.Layer.Any,
77
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
78
+ UsesInput extends boolean,
79
+ > = ExactServiceExclude<
80
+ Layer.Layer.Context<L>,
81
+ EngineServices | InputService<UsesInput> | ProvidedBy<DirectRequires> | DescendantOpenServices<L>
82
+ >
83
+
84
+ type OpenServicesAreDeclared<
85
+ L extends Layer.Layer.Any,
86
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
87
+ UsesInput extends boolean,
88
+ > =
89
+ IsErased<Layer.Layer.Context<L>> extends true
90
+ ? {
91
+ readonly featureLayerContextMustBeExact: 'A feature layer context cannot be erased'
92
+ }
93
+ : [UnexpectedOpenServices<L, DirectRequires, UsesInput>] extends [never]
94
+ ? GraphValidationPassed
95
+ : {
96
+ readonly undeclaredFeatureOpenServices: UnexpectedOpenServices<
97
+ L,
98
+ DirectRequires,
99
+ UsesInput
100
+ >
101
+ }
102
+
103
+ type MissingDirectRequirements<
104
+ L extends Layer.Layer.Any,
105
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
106
+ > = ExactServiceExclude<ProvidedBy<DirectRequires>, Layer.Layer.Context<L>>
107
+
108
+ type DirectRequirementsAreOpen<
109
+ L extends Layer.Layer.Any,
110
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
111
+ > = [MissingDirectRequirements<L, DirectRequires>] extends [never]
112
+ ? GraphValidationPassed
113
+ : {
114
+ readonly unusedDirectFeatureRequirements: MissingDirectRequirements<L, DirectRequires>
115
+ }
116
+
117
+ type PrivateEngineOutputs<L extends Layer.Layer.Any> = Extract<
118
+ Layer.Layer.Success<L>,
119
+ EngineServices
120
+ >
121
+
122
+ type EngineOutputsAreShared<L extends Layer.Layer.Any> = [PrivateEngineOutputs<L>] extends [never]
123
+ ? GraphValidationPassed
124
+ : {
125
+ readonly featureCannotProvideEngineServices: PrivateEngineOutputs<L>
126
+ }
127
+
128
+ type SelfRequiredOutputs<L extends Layer.Layer.Any> = ExactServiceExtract<
129
+ Layer.Layer.Context<L>,
130
+ Layer.Layer.Success<L>
131
+ >
132
+
133
+ type OutputsAreNotSelfRequired<L extends Layer.Layer.Any> = [SelfRequiredOutputs<L>] extends [never]
134
+ ? GraphValidationPassed
135
+ : {
136
+ readonly featureCannotRequireItsOwnOutputs: SelfRequiredOutputs<L>
137
+ }
138
+
139
+ type SlotIdentity<Descriptor> =
140
+ // Erased descriptors must FAIL validation: `unknown` never matches a bound
141
+ // identity, so it surfaces in `unboundFeatureSlotIdentities`.
142
+ IsErased<Descriptor> extends true
143
+ ? unknown
144
+ : Descriptor extends ReadonlyArray<infer Entry>
145
+ ? SlotIdentity<Entry>
146
+ : Descriptor extends {
147
+ readonly [SlotServiceTypeId]: {
148
+ readonly identity: infer Identity
149
+ }
150
+ }
151
+ ? Identity
152
+ : Descriptor
153
+
154
+ type RequiredSlotIdentities<Services> = Services extends {
155
+ readonly [CompositionIdTypeId]: {
156
+ readonly requiredSlots: infer RequiredSlots
157
+ }
158
+ }
159
+ ? SlotIdentity<RequiredSlots>
160
+ : never
161
+
162
+ type BoundSlotIdentities<Services> = Services extends {
163
+ readonly [SlotServiceTypeId]: {
164
+ readonly identity: infer Identity
165
+ }
166
+ }
167
+ ? ExactIdentity<Identity>
168
+ : never
169
+
170
+ type MissingSlotBindings<L extends Layer.Layer.Any> = Exclude<
171
+ RequiredSlotIdentities<Layer.Layer.Success<L>>,
172
+ BoundSlotIdentities<Layer.Layer.Success<L>>
173
+ >
174
+
175
+ type RequiredSlotsAreBound<L extends Layer.Layer.Any> = [MissingSlotBindings<L>] extends [never]
176
+ ? GraphValidationPassed
177
+ : {
178
+ readonly unboundFeatureSlotIdentities: MissingSlotBindings<L>
179
+ }
180
+
181
+ type PlainChildCompositionIdentities<Services> = Services extends {
182
+ readonly [SlotServiceTypeId]: {
183
+ readonly binding: CompositionChildSummary<infer CompositionIdentity>
184
+ }
185
+ }
186
+ ? CompositionIdentity
187
+ : never
188
+
189
+ type LiveCompositionIdentities<Services> = Services extends {
190
+ readonly [CompositionIdTypeId]: {
191
+ readonly identity: infer Identity
192
+ }
193
+ }
194
+ ? ExactIdentity<Identity>
195
+ : never
196
+
197
+ type MissingPlainChildCompositions<L extends Layer.Layer.Any> = Exclude<
198
+ PlainChildCompositionIdentities<Layer.Layer.Success<L>>,
199
+ LiveCompositionIdentities<Layer.Layer.Success<L> | Layer.Layer.Context<L>>
200
+ >
201
+
202
+ type PlainChildrenAreLive<L extends Layer.Layer.Any> = [MissingPlainChildCompositions<L>] extends [
203
+ never,
204
+ ]
205
+ ? GraphValidationPassed
206
+ : {
207
+ readonly missingPlainChildCompositions: MissingPlainChildCompositions<L>
208
+ }
209
+
210
+ export type ValidateFeatureGraphLayer<
211
+ L extends Layer.Layer.Any,
212
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
213
+ UsesInput extends boolean,
214
+ > = LayerErrorIsNever<L> &
215
+ LayerOutputsAreExact<L> &
216
+ DescendantOpenServicesAreExact<L> &
217
+ OpenServicesAreDeclared<L, DirectRequires, UsesInput> &
218
+ DirectRequirementsAreOpen<L, DirectRequires> &
219
+ EngineOutputsAreShared<L> &
220
+ OutputsAreNotSelfRequired<L> &
221
+ RequiredSlotsAreBound<L> &
222
+ PlainChildrenAreLive<L>
@@ -0,0 +1,95 @@
1
+ import type { Layer } from 'effect'
2
+ import { SlotServiceTypeId } from '../compose/slot'
3
+ import type { FeatureInput } from '../feature/feature'
4
+ import type { EngineServices } from '../runtime/loop'
5
+ import type { StateStore } from '../state/state'
6
+ import type { StateFamilyStore } from '../state/stateFamily'
7
+ import type { FeatureChildSummary } from './summary'
8
+
9
+ export type IsAny<Value> = 0 extends 1 & Value ? true : false
10
+
11
+ export type IsErased<Value> = IsAny<Value> extends true ? true : unknown extends Value ? true : false
12
+
13
+ export type ExactStateStoreName<Service> =
14
+ IsAny<Service> extends true
15
+ ? never
16
+ : Service extends StateStore<infer Name, infer _Value>
17
+ ? string extends Name
18
+ ? never
19
+ : Name
20
+ : never
21
+
22
+ export type ExactStateFamilyStoreName<Service> =
23
+ IsAny<Service> extends true
24
+ ? never
25
+ : Service extends StateFamilyStore<infer Name, infer _Key, infer _Value>
26
+ ? string extends Name
27
+ ? never
28
+ : Name
29
+ : never
30
+
31
+ export type ServiceIsProvided<Required, Provided> =
32
+ IsErased<Provided> extends true
33
+ ? false
34
+ : Required extends StateStore<infer Name, infer _Value>
35
+ ? Name extends ExactStateStoreName<Provided>
36
+ ? true
37
+ : false
38
+ : Required extends StateFamilyStore<infer Name, infer _Key, infer _Value>
39
+ ? Name extends ExactStateFamilyStoreName<Provided>
40
+ ? true
41
+ : false
42
+ : [Required] extends [Provided]
43
+ ? true
44
+ : false
45
+
46
+ /**
47
+ * Remove only services the provider can satisfy by their runtime Context tag.
48
+ * Named State/StateFamily identifiers intentionally do not collapse into their
49
+ * structurally broader Store/FamilyStore service APIs.
50
+ */
51
+ export type ExactServiceExclude<Required, Provided> = Required extends unknown
52
+ ? ServiceIsProvided<Required, Provided> extends true
53
+ ? never
54
+ : Required
55
+ : never
56
+
57
+ export type ExactServiceExtract<Required, Provided> = Required extends unknown
58
+ ? ServiceIsProvided<Required, Provided> extends true
59
+ ? Required
60
+ : never
61
+ : never
62
+
63
+ export type GraphValidationPassed = object
64
+
65
+ export type ExactIdentity<Identity> = IsErased<Identity> extends true ? never : Identity
66
+
67
+ export type InputService<UsesInput extends boolean> = UsesInput extends true ? FeatureInput : never
68
+
69
+ export type OpenServicesFromFeatureSlotWitnesses<Services> = Services extends {
70
+ readonly [SlotServiceTypeId]: {
71
+ readonly binding: FeatureChildSummary<
72
+ infer _FeatureIdentity,
73
+ infer _MountProps,
74
+ infer _CompositionProps,
75
+ infer _Contract,
76
+ infer _States,
77
+ infer _Name,
78
+ infer _CompositionIdentity,
79
+ infer OpenServices,
80
+ infer SupportOpenServices,
81
+ infer _UsesInput,
82
+ infer _Input
83
+ >
84
+ }
85
+ }
86
+ ? OpenServices | SupportOpenServices
87
+ : never
88
+
89
+ export type DescendantOpenServices<L extends Layer.Layer.Any> =
90
+ OpenServicesFromFeatureSlotWitnesses<Layer.Layer.Success<L>>
91
+
92
+ export type ModuleOpenServices<L extends Layer.Layer.Any, UsesInput extends boolean> = Exclude<
93
+ Layer.Layer.Context<L>,
94
+ EngineServices | InputService<UsesInput>
95
+ >