@playfast/reform 1.2.0 → 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/package.json +1 -1
- package/src/boundary/boundary.test.ts +26 -23
- package/src/calc/asyncCalc.invalidate.test.ts +258 -55
- package/src/calc/asyncCalc.test.ts +225 -85
- package/src/calc/asyncCalc.ts +27 -141
- package/src/calc/asyncCalcTypes.ts +147 -0
- package/src/calc/calc.test.ts +10 -11
- package/src/calc/calcFamily.test.ts +12 -12
- package/src/channel/channel.ts +8 -106
- package/src/channel/procedures.ts +108 -0
- package/src/compose/composition.ts +49 -246
- package/src/compose/compositionTypes.ts +249 -0
- package/src/event/event.fromSource.test.ts +12 -12
- package/src/feature/feature.ts +50 -1210
- package/src/feature/feature.typecheck.ts +2 -61
- package/src/feature/featureBinding.ts +158 -0
- 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 +45 -226
- package/src/graph/services.ts +95 -0
- package/src/graph/summary.ts +134 -0
- package/src/internal/queryDriver.ts +20 -89
- package/src/internal/queryDriverHydrate.ts +45 -0
- package/src/internal/queryDriverTypes.ts +8 -10
- package/src/internal/store.test.ts +27 -6
- package/src/remote/pendingQueue.ts +145 -0
- package/src/remote/remoteState.test.ts +28 -22
- package/src/remote/remoteState.ts +63 -489
- package/src/remote/remoteStateMake.ts +97 -0
- package/src/remote/remoteStateSend.ts +122 -0
- package/src/remote/remoteStateTypes.ts +155 -0
- package/src/runtime/appRuntime.activation.test.ts +9 -5
- package/src/runtime/appRuntime.test.ts +40 -16
- package/src/runtime/appRuntime.ts +17 -472
- package/src/runtime/capturedAppRuntime.ts +274 -0
- package/src/runtime/eventBudget.test.ts +38 -8
- package/src/runtime/featureMount.ts +94 -0
- package/src/runtime/hardening.test.ts +18 -4
- package/src/runtime/instrumentation.test.ts +49 -12
- package/src/runtime/loop.test.ts +51 -10
- package/src/runtime/runtimeHandle.ts +124 -0
- package/src/state/stateFamily.test.ts +25 -8
- package/src/testkit/flight.testkit.ts +9 -0
|
@@ -1,476 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
CompositionActivation,
|
|
4
|
-
CompositionId,
|
|
5
|
-
RequiredSlotIdentities,
|
|
6
|
-
} from '../compose/composition'
|
|
1
|
+
import type { CompositionId, RequiredSlotIdentities } from '../compose/composition'
|
|
7
2
|
import type { UiContract } from '../compose/ui'
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from '../graph/closure'
|
|
25
|
-
import { FeatureLoadFailed, forceSync } from '../internal/errors'
|
|
26
|
-
import { type Node } from '../ui/node'
|
|
27
|
-
import { type AnyScene, type CapturedScene, type Scene, sceneInstrumentation } from '../scene/scene'
|
|
28
|
-
import { Bus, publish, type Priority, type Tagged } from './bus'
|
|
29
|
-
import { CurrentEventBudget, type ReformOptions, resolveEventBudget } from './eventBudget'
|
|
30
|
-
import { type Instrumentation } from './instrumentation'
|
|
31
|
-
import { EngineRequirements, type EngineServices } from './loop'
|
|
32
|
-
|
|
33
|
-
export const UnspecifiedRuntimeServicesTypeId: unique symbol = Symbol.for(
|
|
34
|
-
'reform/UnspecifiedRuntimeServices',
|
|
35
|
-
)
|
|
36
|
-
export type UnspecifiedRuntimeServicesTypeId = typeof UnspecifiedRuntimeServicesTypeId
|
|
37
|
-
export interface UnspecifiedRuntimeServices {
|
|
38
|
-
readonly [UnspecifiedRuntimeServicesTypeId]: UnspecifiedRuntimeServicesTypeId
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type FeatureMountHandlersExternalApi<UsesInput extends boolean, Input, Services> = {
|
|
42
|
-
readonly onLive: (runtime: RuntimeHandle<Services>) => void
|
|
43
|
-
readonly onFailed: (error: FeatureLoadFailed) => void
|
|
44
|
-
} & (UsesInput extends true ? { readonly props: Input } : { readonly props?: never })
|
|
45
|
-
|
|
46
|
-
export type FeatureMountHandlers<
|
|
47
|
-
UsesInput extends boolean,
|
|
48
|
-
Input,
|
|
49
|
-
Services,
|
|
50
|
-
> = FeatureMountHandlersExternalApi<UsesInput, Input, Services>
|
|
51
|
-
|
|
52
|
-
export type RuntimeFeatureAvailability<Services, OpenServices> = [
|
|
53
|
-
EngineServices | OpenServices,
|
|
54
|
-
] extends [Services]
|
|
55
|
-
? object
|
|
56
|
-
: {
|
|
57
|
-
readonly missingFeatureServices: Exclude<EngineServices | OpenServices, Services>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface AnyFeatureMountHandlers {
|
|
61
|
-
readonly props: unknown
|
|
62
|
-
readonly onLive: <Services>(runtime: RuntimeHandle<Services>) => void
|
|
63
|
-
readonly onFailed: (error: FeatureLoadFailed) => void
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface CapturedFeatureMountHandlers<
|
|
67
|
-
Props,
|
|
68
|
-
HostServices,
|
|
69
|
-
FeatureServices,
|
|
70
|
-
C extends UiContract,
|
|
71
|
-
N extends string,
|
|
72
|
-
Identity,
|
|
73
|
-
> {
|
|
74
|
-
readonly props: Props
|
|
75
|
-
readonly onLive: (
|
|
76
|
-
runtime: RuntimeHandle<
|
|
77
|
-
HostServices | FeatureServices | CompositionId<N, Identity, RequiredSlotIdentities<C>>
|
|
78
|
-
>,
|
|
79
|
-
) => void
|
|
80
|
-
readonly onFailed: (error: FeatureLoadFailed) => void
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const isObjectLike = (candidate: unknown): candidate is object =>
|
|
84
|
-
(typeof candidate === 'object' || typeof candidate === 'function') && candidate !== null
|
|
85
|
-
|
|
86
|
-
const isContextTag = (candidate: unknown): candidate is Context.Tag<unknown, unknown> =>
|
|
87
|
-
isObjectLike(candidate) &&
|
|
88
|
-
Context.TagTypeId in candidate &&
|
|
89
|
-
'key' in candidate &&
|
|
90
|
-
typeof candidate.key === 'string'
|
|
91
|
-
|
|
92
|
-
const isUnknownArray = (candidate: unknown): candidate is ReadonlyArray<unknown> =>
|
|
93
|
-
Array.isArray(candidate)
|
|
94
|
-
|
|
95
|
-
const requirementAvailable = <Services>(
|
|
96
|
-
context: Context.Context<Services>,
|
|
97
|
-
requirement: unknown,
|
|
98
|
-
): boolean => {
|
|
99
|
-
if (isContextTag(requirement)) {
|
|
100
|
-
return Option.isSome(Context.getOption(context, requirement))
|
|
101
|
-
}
|
|
102
|
-
if (!isObjectLike(requirement)) {
|
|
103
|
-
return false
|
|
104
|
-
}
|
|
105
|
-
if ('store' in requirement) {
|
|
106
|
-
return requirementAvailable(context, requirement.store)
|
|
107
|
-
}
|
|
108
|
-
if ('members' in requirement && isUnknownArray(requirement.members)) {
|
|
109
|
-
return requirement.members.every((member) => requirementAvailable(context, member))
|
|
110
|
-
}
|
|
111
|
-
return false
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const hasFeatureRequirements = <
|
|
115
|
-
Services,
|
|
116
|
-
OpenServices,
|
|
117
|
-
const DirectRequires extends ReadonlyArray<FeatureRequirement>,
|
|
118
|
-
>(
|
|
119
|
-
context: Context.Context<Services>,
|
|
120
|
-
requires: DirectRequires,
|
|
121
|
-
): context is Context.Context<Services | EngineServices | OpenServices> =>
|
|
122
|
-
EngineRequirements.every((tag) => requirementAvailable(context, tag)) &&
|
|
123
|
-
requires.every((requirement) => requirementAvailable(context, requirement))
|
|
124
|
-
|
|
125
|
-
const featureMountFailure = (message: string): FeatureLoadFailed =>
|
|
126
|
-
new FeatureLoadFailed({ cause: message })
|
|
127
|
-
|
|
128
|
-
export interface RuntimeHandle<Services = UnspecifiedRuntimeServices> {
|
|
129
|
-
read<Identifier extends Services, Service>(tag: Context.Tag<Identifier, Service>): Service
|
|
130
|
-
readOption<Identifier, Service>(tag: Context.Tag<Identifier, Service>): Option.Option<Service>
|
|
131
|
-
runRender(effect: Effect.Effect<Node, never, never>): Node
|
|
132
|
-
dispatch(priority: Priority, event: Tagged): void
|
|
133
|
-
mountScoped(activation: CompositionActivation): () => void
|
|
134
|
-
mountFeature<
|
|
135
|
-
Summary extends AnyFeatureGraphSummary,
|
|
136
|
-
ROut,
|
|
137
|
-
const DirectRequires extends ReadonlyArray<FeatureRequirement>,
|
|
138
|
-
>(
|
|
139
|
-
binding: [EngineServices | FeatureSummaryOpenServices<Summary>] extends [Services]
|
|
140
|
-
? FeatureBinding<Summary, ROut, DirectRequires>
|
|
141
|
-
: never,
|
|
142
|
-
handlers: FeatureMountHandlers<
|
|
143
|
-
FeatureSummaryUsesInput<Summary>,
|
|
144
|
-
FeatureSummaryInput<Summary>,
|
|
145
|
-
Services | ROut
|
|
146
|
-
>,
|
|
147
|
-
): () => void
|
|
148
|
-
mountCapturedFeature<
|
|
149
|
-
Summary extends AnyFeatureGraphSummary,
|
|
150
|
-
ROut,
|
|
151
|
-
const DirectRequires extends ReadonlyArray<FeatureRequirement>,
|
|
152
|
-
>(
|
|
153
|
-
binding: CapturedFeatureBinding<Summary, ROut, DirectRequires> &
|
|
154
|
-
RuntimeFeatureAvailability<Services, FeatureSummaryOpenServices<Summary>>,
|
|
155
|
-
handlers: CapturedFeatureMountHandlers<
|
|
156
|
-
FeatureSummaryMountProps<Summary>,
|
|
157
|
-
Services,
|
|
158
|
-
ROut,
|
|
159
|
-
FeatureSummaryContract<Summary>,
|
|
160
|
-
FeatureSummaryName<Summary>,
|
|
161
|
-
FeatureSummaryCompositionIdentity<Summary>
|
|
162
|
-
>,
|
|
163
|
-
): () => void
|
|
164
|
-
mountAnyFeature(binding: AnyFeatureBinding, handlers: AnyFeatureMountHandlers): () => void
|
|
165
|
-
readonly instrumentation: Instrumentation
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export interface AppRuntime<Services = UnspecifiedRuntimeServices> extends RuntimeHandle<Services> {
|
|
169
|
-
boot(): void
|
|
170
|
-
dispose(): void
|
|
171
|
-
isDisposed(): boolean
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const makeCapturedAppRuntime = <
|
|
175
|
-
C extends UiContract,
|
|
176
|
-
S extends ReadonlyArray<unknown>,
|
|
177
|
-
Services,
|
|
178
|
-
P,
|
|
179
|
-
N extends string,
|
|
180
|
-
Identity,
|
|
181
|
-
>(
|
|
182
|
-
scene: CapturedScene<C, S, Services, P, N, Identity>,
|
|
183
|
-
options: ReformOptions = {},
|
|
184
|
-
): AppRuntime<Services | CompositionId<N, Identity, RequiredSlotIdentities<C>>> => {
|
|
185
|
-
const instrumentation = sceneInstrumentation(scene)
|
|
186
|
-
const provide = scene.provide.map(Layer.locally(CurrentEventBudget, resolveEventBudget(options)))
|
|
187
|
-
const layer = provide.reduce((accLayer, nextLayer) => Layer.merge(accLayer, nextLayer))
|
|
188
|
-
const runtime = ManagedRuntime.make(layer)
|
|
189
|
-
const capturedContext = forceSync(() => runtime.runSync(Effect.context<Services>()))
|
|
190
|
-
const rootCompositionService = Option.getOrThrow(
|
|
191
|
-
Context.getOption(capturedContext, scene.composition.tag),
|
|
192
|
-
)
|
|
193
|
-
// Re-add the exact tag so the captured context retains the root's nominal identity.
|
|
194
|
-
const rootContext = Context.add(capturedContext, scene.composition.tag, rootCompositionService)
|
|
195
|
-
const rootBus = Context.getOption(rootContext, Bus)
|
|
196
|
-
|
|
197
|
-
// Hosts can still render tearing-down trees after the managed runtime is disposed.
|
|
198
|
-
const status = { disposed: false, booted: false }
|
|
199
|
-
const activationDisposers = new Set<() => void>()
|
|
200
|
-
|
|
201
|
-
// A closed render Effect remains safe on the default runtime after disposal.
|
|
202
|
-
const runRender = (effect: Effect.Effect<Node, never, never>): Node =>
|
|
203
|
-
status.disposed ? Effect.runSync(effect) : runtime.runSync(effect)
|
|
204
|
-
|
|
205
|
-
const dispatch = (priority: Priority, event: Tagged): void => {
|
|
206
|
-
if (status.disposed || Option.isNone(rootBus)) {
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
runtime.runFork(publish(priority, event).pipe(Effect.provideService(Bus, rootBus.value)))
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const mountScoped = (activation: CompositionActivation): (() => void) => {
|
|
213
|
-
if (status.disposed) {
|
|
214
|
-
return () => {}
|
|
215
|
-
}
|
|
216
|
-
const scope = Effect.runSync(Scope.make())
|
|
217
|
-
const liveness = { active: true }
|
|
218
|
-
const fiber = runtime.runFork(
|
|
219
|
-
Layer.build(activation).pipe(Effect.provideService(Scope.Scope, scope)),
|
|
220
|
-
)
|
|
221
|
-
const dispose = (): void => {
|
|
222
|
-
if (!liveness.active) {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
liveness.active = false
|
|
226
|
-
fiber.unsafeInterruptAsFork(fiber.id())
|
|
227
|
-
const close = Fiber.await(fiber).pipe(
|
|
228
|
-
Effect.zipRight(Scope.close(scope, Exit.succeed(undefined))),
|
|
229
|
-
Effect.ensuring(Effect.sync(() => activationDisposers.delete(dispose))),
|
|
230
|
-
)
|
|
231
|
-
Effect.runFork(close)
|
|
232
|
-
}
|
|
233
|
-
activationDisposers.add(dispose)
|
|
234
|
-
return dispose
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const makeChildRuntime = <ChildServices>(
|
|
238
|
-
context: Context.Context<ChildServices>,
|
|
239
|
-
): RuntimeHandle<ChildServices> => ({
|
|
240
|
-
read: <Identifier extends ChildServices, Service>(
|
|
241
|
-
tag: Context.Tag<Identifier, Service>,
|
|
242
|
-
): Service => Context.get(context, tag),
|
|
243
|
-
readOption: <Identifier, Service>(
|
|
244
|
-
tag: Context.Tag<Identifier, Service>,
|
|
245
|
-
): Option.Option<Service> => Context.getOption(context, tag),
|
|
246
|
-
runRender,
|
|
247
|
-
dispatch,
|
|
248
|
-
mountScoped,
|
|
249
|
-
mountFeature: (binding, handlers) => mountFeatureOnto(context, binding, handlers),
|
|
250
|
-
mountCapturedFeature: (binding, handlers) =>
|
|
251
|
-
mountCapturedFeatureOnto(context, binding, handlers),
|
|
252
|
-
mountAnyFeature: (binding, handlers) => mountAnyFeatureOnto(context, binding, handlers),
|
|
253
|
-
instrumentation,
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
const mountFeatureOnto = <
|
|
257
|
-
HostServices,
|
|
258
|
-
Summary extends AnyFeatureGraphSummary,
|
|
259
|
-
ROut,
|
|
260
|
-
const DirectRequires extends ReadonlyArray<FeatureRequirement>,
|
|
261
|
-
>(
|
|
262
|
-
hostContext: Context.Context<HostServices>,
|
|
263
|
-
binding: FeatureBinding<Summary, ROut, DirectRequires>,
|
|
264
|
-
handlers: FeatureMountHandlers<
|
|
265
|
-
FeatureSummaryUsesInput<Summary>,
|
|
266
|
-
FeatureSummaryInput<Summary>,
|
|
267
|
-
HostServices | ROut
|
|
268
|
-
>,
|
|
269
|
-
): (() => void) => {
|
|
270
|
-
if (status.disposed) {
|
|
271
|
-
return () => {}
|
|
272
|
-
}
|
|
273
|
-
const scope = Effect.runSync(Scope.make())
|
|
274
|
-
const mount = Effect.flatMap(binding.load, (loaded) => {
|
|
275
|
-
if (
|
|
276
|
-
!hasFeatureRequirements<HostServices, FeatureSummaryOpenServices<Summary>, DirectRequires>(
|
|
277
|
-
hostContext,
|
|
278
|
-
loaded.requires,
|
|
279
|
-
)
|
|
280
|
-
) {
|
|
281
|
-
return Effect.fail(
|
|
282
|
-
featureMountFailure(
|
|
283
|
-
`Feature ${binding.manifest.name} requires services missing from the host runtime`,
|
|
284
|
-
),
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
return Effect.gen(function* () {
|
|
288
|
-
const bus = Context.get(hostContext, Bus)
|
|
289
|
-
const featureContext = Context.add(hostContext, FeatureInput, handlers.props)
|
|
290
|
-
const context = yield* Layer.build(
|
|
291
|
-
Layer.provide(loaded.layer, Layer.succeedContext(featureContext)),
|
|
292
|
-
)
|
|
293
|
-
yield* Effect.forEach(binding.boot, (event) => publish('High', event), {
|
|
294
|
-
discard: true,
|
|
295
|
-
}).pipe(Effect.provideService(Bus, bus))
|
|
296
|
-
const merged = Context.merge(hostContext, context)
|
|
297
|
-
yield* Effect.sync(() => handlers.onLive(makeChildRuntime<HostServices | ROut>(merged)))
|
|
298
|
-
})
|
|
299
|
-
})
|
|
300
|
-
const fiber = runtime.runFork(
|
|
301
|
-
mount.pipe(
|
|
302
|
-
Effect.provideService(Scope.Scope, scope),
|
|
303
|
-
Effect.matchCause({
|
|
304
|
-
onFailure: (cause) => {
|
|
305
|
-
// Disposer interruption is cancellation, not a failed feature.
|
|
306
|
-
if (!Cause.isInterruptedOnly(cause)) {
|
|
307
|
-
handlers.onFailed(
|
|
308
|
-
Option.getOrElse(
|
|
309
|
-
Cause.failureOption(cause),
|
|
310
|
-
() => new FeatureLoadFailed({ cause }),
|
|
311
|
-
),
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
onSuccess: () => undefined,
|
|
316
|
-
}),
|
|
317
|
-
),
|
|
318
|
-
)
|
|
319
|
-
return () => {
|
|
320
|
-
// Interrupt first, then await finalizer registration before closing the scope.
|
|
321
|
-
fiber.unsafeInterruptAsFork(fiber.id())
|
|
322
|
-
const close = Fiber.await(fiber).pipe(
|
|
323
|
-
Effect.zipRight(Scope.close(scope, Exit.succeed(undefined))),
|
|
324
|
-
)
|
|
325
|
-
if (status.disposed) {
|
|
326
|
-
Effect.runFork(close)
|
|
327
|
-
} else {
|
|
328
|
-
runtime.runFork(close)
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const mountCapturedFeatureOnto = <
|
|
334
|
-
HostServices,
|
|
335
|
-
Summary extends AnyFeatureGraphSummary,
|
|
336
|
-
ROut,
|
|
337
|
-
const DirectRequires extends ReadonlyArray<FeatureRequirement>,
|
|
338
|
-
>(
|
|
339
|
-
hostContext: Context.Context<HostServices>,
|
|
340
|
-
binding: CapturedFeatureBinding<Summary, ROut, DirectRequires>,
|
|
341
|
-
handlers: CapturedFeatureMountHandlers<
|
|
342
|
-
FeatureSummaryMountProps<Summary>,
|
|
343
|
-
HostServices,
|
|
344
|
-
ROut,
|
|
345
|
-
FeatureSummaryContract<Summary>,
|
|
346
|
-
FeatureSummaryName<Summary>,
|
|
347
|
-
FeatureSummaryCompositionIdentity<Summary>
|
|
348
|
-
>,
|
|
349
|
-
): (() => void) => {
|
|
350
|
-
if (status.disposed) {
|
|
351
|
-
return () => {}
|
|
352
|
-
}
|
|
353
|
-
const scope = Effect.runSync(Scope.make())
|
|
354
|
-
const mount = Effect.flatMap(binding.load, (loaded) => {
|
|
355
|
-
if (
|
|
356
|
-
!hasFeatureRequirements<HostServices, FeatureSummaryOpenServices<Summary>, DirectRequires>(
|
|
357
|
-
hostContext,
|
|
358
|
-
loaded.requires,
|
|
359
|
-
)
|
|
360
|
-
) {
|
|
361
|
-
return Effect.fail(
|
|
362
|
-
featureMountFailure(
|
|
363
|
-
`Feature ${binding.manifest.name} requires services missing from the host runtime`,
|
|
364
|
-
),
|
|
365
|
-
)
|
|
366
|
-
}
|
|
367
|
-
const input = loaded.usesInput ? binding.parseInput(handlers.props) : Option.some(undefined)
|
|
368
|
-
return Option.match(input, {
|
|
369
|
-
onNone: () =>
|
|
370
|
-
Effect.fail(
|
|
371
|
-
featureMountFailure(`Feature ${binding.manifest.name} received invalid props`),
|
|
372
|
-
),
|
|
373
|
-
onSome: (props) =>
|
|
374
|
-
Effect.gen(function* () {
|
|
375
|
-
const bus = Context.get(hostContext, Bus)
|
|
376
|
-
const featureContext = Context.add(hostContext, FeatureInput, props)
|
|
377
|
-
const context = yield* Layer.build(
|
|
378
|
-
Layer.provide(loaded.layer, Layer.succeedContext(featureContext)),
|
|
379
|
-
)
|
|
380
|
-
yield* Effect.forEach(binding.boot, (event) => publish('High', event), {
|
|
381
|
-
discard: true,
|
|
382
|
-
}).pipe(Effect.provideService(Bus, bus))
|
|
383
|
-
const merged = Context.merge(hostContext, context)
|
|
384
|
-
const compositionService = Option.getOrThrow(
|
|
385
|
-
Context.getOption(merged, binding.composition.tag),
|
|
386
|
-
)
|
|
387
|
-
const rooted = Context.add(merged, binding.composition.tag, compositionService)
|
|
388
|
-
yield* Effect.sync(() => handlers.onLive(makeChildRuntime(rooted)))
|
|
389
|
-
}),
|
|
390
|
-
})
|
|
391
|
-
})
|
|
392
|
-
const fiber = runtime.runFork(
|
|
393
|
-
mount.pipe(
|
|
394
|
-
Effect.provideService(Scope.Scope, scope),
|
|
395
|
-
Effect.matchCause({
|
|
396
|
-
onFailure: (cause) => {
|
|
397
|
-
if (!Cause.isInterruptedOnly(cause)) {
|
|
398
|
-
handlers.onFailed(
|
|
399
|
-
Option.getOrElse(
|
|
400
|
-
Cause.failureOption(cause),
|
|
401
|
-
() => new FeatureLoadFailed({ cause }),
|
|
402
|
-
),
|
|
403
|
-
)
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
onSuccess: () => undefined,
|
|
407
|
-
}),
|
|
408
|
-
),
|
|
409
|
-
)
|
|
410
|
-
return () => {
|
|
411
|
-
fiber.unsafeInterruptAsFork(fiber.id())
|
|
412
|
-
const close = Fiber.await(fiber).pipe(
|
|
413
|
-
Effect.zipRight(Scope.close(scope, Exit.succeed(undefined))),
|
|
414
|
-
)
|
|
415
|
-
if (status.disposed) {
|
|
416
|
-
Effect.runFork(close)
|
|
417
|
-
} else {
|
|
418
|
-
runtime.runFork(close)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const mountAnyFeatureOnto = <HostServices>(
|
|
424
|
-
hostContext: Context.Context<HostServices>,
|
|
425
|
-
binding: AnyFeatureBinding,
|
|
426
|
-
handlers: AnyFeatureMountHandlers,
|
|
427
|
-
): (() => void) =>
|
|
428
|
-
binding.capture((exactBinding) =>
|
|
429
|
-
mountCapturedFeatureOnto(hostContext, exactBinding, {
|
|
430
|
-
props: handlers.props,
|
|
431
|
-
onLive: (childRuntime) => handlers.onLive(childRuntime),
|
|
432
|
-
onFailed: handlers.onFailed,
|
|
433
|
-
}),
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
type RootServices = Services | CompositionId<N, Identity, RequiredSlotIdentities<C>>
|
|
437
|
-
const app: AppRuntime<RootServices> = {
|
|
438
|
-
// Captured reads stay available while the managed runtime is tearing down.
|
|
439
|
-
read: <Identifier extends RootServices, Service>(
|
|
440
|
-
tag: Context.Tag<Identifier, Service>,
|
|
441
|
-
): Service => Context.get(rootContext, tag),
|
|
442
|
-
readOption: <Identifier, Service>(
|
|
443
|
-
tag: Context.Tag<Identifier, Service>,
|
|
444
|
-
): Option.Option<Service> => Context.getOption(rootContext, tag),
|
|
445
|
-
runRender,
|
|
446
|
-
dispatch,
|
|
447
|
-
mountScoped,
|
|
448
|
-
mountFeature: (binding, handlers) => mountFeatureOnto(rootContext, binding, handlers),
|
|
449
|
-
mountCapturedFeature: (binding, handlers) =>
|
|
450
|
-
mountCapturedFeatureOnto(rootContext, binding, handlers),
|
|
451
|
-
mountAnyFeature: (binding, handlers) => mountAnyFeatureOnto(rootContext, binding, handlers),
|
|
452
|
-
boot: () => {
|
|
453
|
-
if (status.disposed || status.booted) {
|
|
454
|
-
return
|
|
455
|
-
}
|
|
456
|
-
status.booted = true
|
|
457
|
-
Option.fromNullable(scene.boot)
|
|
458
|
-
.pipe(Option.getOrElse((): ReadonlyArray<Tagged> => []))
|
|
459
|
-
.forEach((event) => dispatch('High', event))
|
|
460
|
-
},
|
|
461
|
-
dispose: () => {
|
|
462
|
-
if (status.disposed) {
|
|
463
|
-
return
|
|
464
|
-
}
|
|
465
|
-
status.disposed = true
|
|
466
|
-
activationDisposers.forEach((dispose) => dispose())
|
|
467
|
-
void runtime.dispose()
|
|
468
|
-
},
|
|
469
|
-
isDisposed: () => status.disposed,
|
|
470
|
-
instrumentation,
|
|
471
|
-
}
|
|
472
|
-
return app
|
|
473
|
-
}
|
|
3
|
+
import type { AnyScene, CapturedScene, Scene } from '../scene/scene'
|
|
4
|
+
import { makeCapturedAppRuntime } from './capturedAppRuntime'
|
|
5
|
+
import type { ReformOptions } from './eventBudget'
|
|
6
|
+
import type { AppRuntime, UnspecifiedRuntimeServices } from './runtimeHandle'
|
|
7
|
+
|
|
8
|
+
export { UnspecifiedRuntimeServicesTypeId } from './runtimeHandle'
|
|
9
|
+
export type {
|
|
10
|
+
AnyFeatureMountHandlers,
|
|
11
|
+
AppRuntime,
|
|
12
|
+
CapturedFeatureMountHandlers,
|
|
13
|
+
FeatureMountHandlers,
|
|
14
|
+
FeatureMountHandlersExternalApi,
|
|
15
|
+
RuntimeFeatureAvailability,
|
|
16
|
+
RuntimeHandle,
|
|
17
|
+
UnspecifiedRuntimeServices,
|
|
18
|
+
} from './runtimeHandle'
|
|
474
19
|
|
|
475
20
|
export function makeAppRuntime<
|
|
476
21
|
C extends UiContract,
|