@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.
- package/README.md +85 -55
- package/package.json +17 -17
- package/src/boundary/boundary.test.ts +82 -33
- package/src/boundary/boundary.ts +141 -32
- package/src/calc/asyncCalc.invalidate.test.ts +733 -32
- package/src/calc/asyncCalc.test.ts +397 -225
- package/src/calc/asyncCalc.ts +115 -100
- package/src/calc/asyncCalcTypes.ts +147 -0
- package/src/calc/calc.test.ts +13 -16
- package/src/calc/calc.ts +44 -18
- package/src/calc/calcFamily.test.ts +90 -32
- package/src/calc/calcFamily.ts +56 -25
- package/src/calc/compose.ts +1 -14
- package/src/channel/channel.ts +25 -6
- package/src/channel/{procedureRegistry.ts → procedures.ts} +41 -23
- package/src/compose/composition.test.ts +19 -0
- package/src/compose/composition.ts +168 -81
- package/src/compose/compositionTypes.ts +249 -0
- package/src/compose/props.ts +13 -6
- package/src/compose/provide.ts +187 -46
- package/src/compose/slot.ts +156 -19
- package/src/compose/structure.test.ts +6 -3
- package/src/compose/structure.ts +106 -13
- package/src/compose/ui.test.ts +19 -3
- package/src/compose/ui.ts +147 -54
- package/src/compose/ui.typecheck.ts +40 -4
- package/src/definition/definition.ts +22 -9
- package/src/event/event.fromSource.test.ts +172 -0
- package/src/event/event.test.ts +10 -0
- package/src/event/event.ts +102 -18
- package/src/feature/feature.mount.test.ts +123 -56
- package/src/feature/feature.test.ts +67 -63
- package/src/feature/feature.ts +222 -262
- package/src/feature/feature.typecheck.ts +224 -91
- package/src/feature/featureBinding.ts +130 -20
- package/src/feature/featureClass.ts +162 -0
- package/src/feature/featureConfig.ts +192 -0
- package/src/feature/featureFactory.ts +163 -0
- package/src/feature/featureModule.ts +136 -0
- package/src/feature/featureModule.typecheck.ts +63 -0
- package/src/feature/featureNativeTree.ts +152 -0
- package/src/feature/featureRequirement.ts +82 -0
- package/src/feature/featureVariants.ts +234 -0
- package/src/feature/mountFeature.ts +54 -0
- package/src/graph/closure.ts +222 -0
- package/src/graph/services.ts +95 -0
- package/src/graph/summary.ts +134 -0
- package/src/index.ts +171 -245
- package/src/internal/bucketCache.ts +39 -0
- package/src/internal/capture.ts +9 -4
- package/src/internal/env.ts +1 -3
- package/src/internal/errors.ts +21 -10
- package/src/internal/queryDriver.ts +61 -25
- package/src/internal/queryDriverHydrate.ts +45 -0
- package/src/internal/queryDriverStore.ts +8 -5
- package/src/internal/queryDriverTypes.ts +11 -11
- package/src/internal/reuse.test.ts +10 -3
- package/src/internal/reuse.ts +5 -2
- package/src/internal/scheduler.ts +4 -1
- package/src/internal/sources.ts +25 -11
- package/src/internal/store.test.ts +27 -6
- package/src/internal/track.ts +3 -2
- package/src/internal.ts +222 -0
- package/src/namespace/namespace.ts +46 -25
- package/src/procedure/procedure.ts +38 -11
- package/src/reducer/reducer.ts +78 -34
- package/src/remote/pendingQueue.ts +145 -0
- package/src/remote/remoteState.test.ts +536 -354
- package/src/remote/remoteState.ts +145 -106
- package/src/remote/remoteState.typecheck.ts +47 -23
- package/src/remote/remoteStateMake.ts +97 -0
- package/src/remote/remoteStateSend.ts +95 -37
- package/src/remote/remoteStateTypes.ts +155 -0
- package/src/runtime/appRuntime.activation.test.ts +104 -0
- package/src/runtime/appRuntime.test.ts +187 -111
- package/src/runtime/appRuntime.ts +36 -130
- package/src/runtime/capturedAppRuntime.ts +274 -0
- package/src/runtime/eventBudget.test.ts +39 -12
- package/src/runtime/eventBudget.ts +9 -8
- package/src/runtime/featureMount.ts +94 -0
- package/src/runtime/hardening.test.ts +22 -13
- package/src/runtime/instrumentation.test.ts +213 -194
- package/src/runtime/instrumentation.ts +6 -11
- package/src/runtime/loop.test.ts +84 -7
- package/src/runtime/loop.ts +69 -40
- package/src/runtime/runtimeHandle.ts +124 -0
- package/src/scene/featureScene.test.ts +4 -2
- package/src/scene/scene.ts +234 -64
- package/src/scene/seedScene.test.ts +69 -86
- package/src/state/state.nominal.typecheck.ts +68 -0
- package/src/state/state.test.ts +17 -0
- package/src/state/state.ts +79 -26
- package/src/state/stateFamily.test.ts +39 -8
- package/src/state/stateFamily.ts +55 -22
- package/src/state/stateGroup.ts +53 -17
- package/src/state/token.ts +40 -10
- package/src/synced/syncedStore.ts +46 -23
- package/src/testkit/flight.testkit.ts +84 -0
- package/src/wire/tree.test.ts +8 -4
- package/src/wire/triggers.test.ts +6 -2
- package/src/wire/triggers.ts +14 -10
- package/src/calc/asyncCalcDefinitions.ts +0 -48
- package/src/internal/ctx.ts +0 -9
- package/src/remote/remoteStateDefinition.ts +0 -135
- package/src/remote/remoteStateLayers.ts +0 -118
- 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
|
+
>
|