@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.
- package/README.md +85 -55
- package/package.json +17 -17
- package/src/boundary/boundary.test.ts +62 -16
- package/src/boundary/boundary.ts +141 -32
- package/src/calc/asyncCalc.invalidate.test.ts +516 -18
- package/src/calc/asyncCalc.test.ts +207 -175
- package/src/calc/asyncCalc.ts +168 -39
- package/src/calc/calc.test.ts +3 -5
- package/src/calc/calc.ts +44 -18
- package/src/calc/calcFamily.test.ts +80 -22
- package/src/calc/calcFamily.ts +56 -25
- package/src/calc/compose.ts +1 -14
- package/src/channel/channel.ts +128 -11
- package/src/compose/composition.test.ts +19 -0
- package/src/compose/composition.ts +346 -62
- 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 +1317 -197
- package/src/feature/feature.typecheck.ts +253 -61
- package/src/graph/closure.ts +403 -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 +120 -15
- package/src/internal/queryDriverStore.ts +8 -5
- package/src/internal/queryDriverTypes.ts +3 -1
- 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/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/remoteState.test.ts +515 -339
- package/src/remote/remoteState.ts +572 -107
- package/src/remote/remoteState.typecheck.ts +47 -23
- package/src/runtime/appRuntime.activation.test.ts +100 -0
- package/src/runtime/appRuntime.test.ts +157 -105
- package/src/runtime/appRuntime.ts +394 -33
- package/src/runtime/eventBudget.test.ts +1 -4
- package/src/runtime/eventBudget.ts +9 -8
- package/src/runtime/hardening.test.ts +4 -9
- package/src/runtime/instrumentation.test.ts +174 -192
- package/src/runtime/instrumentation.ts +6 -11
- package/src/runtime/loop.test.ts +36 -0
- package/src/runtime/loop.ts +69 -40
- 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 +14 -0
- 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 +75 -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/channel/procedureRegistry.ts +0 -90
- package/src/feature/featureBinding.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
- package/src/remote/remoteStateSend.ts +0 -64
package/src/runtime/loop.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { Array as Arr, Cause, Chunk, Context, Effect, Layer, Option, PubSub, Queue } from 'effect'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
channelsLayer,
|
|
5
|
-
Procedures,
|
|
6
|
-
proceduresLayer,
|
|
7
|
-
} from '../channel/channel'
|
|
2
|
+
import { Channels, channelsLayer, Procedures, proceduresLayer } from '../channel/channel'
|
|
3
|
+
import { makeBucketCache } from '../internal/bucketCache'
|
|
8
4
|
import { notificationsLayer } from '../internal/scheduler'
|
|
9
5
|
import { Bus, busLayer, type Envelope, type Tagged } from './bus'
|
|
10
6
|
import { enforceBudget, makeTickCounter, resolveCurrentEventBudget } from './eventBudget'
|
|
@@ -19,56 +15,78 @@ export interface ReducerEntry {
|
|
|
19
15
|
|
|
20
16
|
export interface ReducerRegistry {
|
|
21
17
|
readonly entries: Array<ReducerEntry>
|
|
22
|
-
readonly
|
|
18
|
+
readonly byName: Map<string, Set<ReducerEntry>>
|
|
19
|
+
readonly byTag: Map<string, Set<ReducerEntry>>
|
|
20
|
+
readonly tagged: (tag: string) => ReadonlyArray<ReducerEntry>
|
|
23
21
|
readonly register: (entry: ReducerEntry) => void
|
|
24
22
|
readonly unregister: (entry: ReducerEntry) => void
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
const ReducersBase: Context.TagClass<Reducers, 'reform/Reducers', ReducerRegistry> =
|
|
28
|
-
|
|
25
|
+
const ReducersBase: Context.TagClass<Reducers, 'reform/Reducers', ReducerRegistry> = Context.Tag(
|
|
26
|
+
'reform/Reducers',
|
|
27
|
+
)<Reducers, ReducerRegistry>()
|
|
29
28
|
export class Reducers extends ReducersBase {}
|
|
30
29
|
|
|
31
|
-
const dropFromBuckets = <T>(map: Map<string,
|
|
30
|
+
const dropFromBuckets = <T>(map: Map<string, Set<T>>, key: string, target: T): void => {
|
|
32
31
|
const bucket = map.get(key)
|
|
33
32
|
if (bucket === undefined) {
|
|
34
33
|
return
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
if (
|
|
35
|
+
bucket.delete(target)
|
|
36
|
+
if (bucket.size === 0) {
|
|
38
37
|
map.delete(key)
|
|
39
|
-
} else {
|
|
40
|
-
map.set(key, next)
|
|
41
38
|
}
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
const makeReducerRegistry = (): ReducerRegistry => {
|
|
45
|
-
const
|
|
46
|
-
const
|
|
42
|
+
const entries = new Set<ReducerEntry>()
|
|
43
|
+
const byName = new Map<string, Set<ReducerEntry>>()
|
|
44
|
+
const byTag = new Map<string, Set<ReducerEntry>>()
|
|
45
|
+
const tagged = makeBucketCache(byTag)
|
|
46
|
+
const snapshot: { value: Option.Option<Array<ReducerEntry>> } = {
|
|
47
|
+
value: Option.some([]),
|
|
48
|
+
}
|
|
47
49
|
return {
|
|
48
50
|
get entries() {
|
|
49
|
-
return
|
|
51
|
+
return Option.getOrElse(snapshot.value, () => {
|
|
52
|
+
const current = Array.from(entries)
|
|
53
|
+
snapshot.value = Option.some(current)
|
|
54
|
+
return current
|
|
55
|
+
})
|
|
50
56
|
},
|
|
57
|
+
byName,
|
|
51
58
|
byTag,
|
|
59
|
+
tagged: tagged.read,
|
|
52
60
|
register: (entry) => {
|
|
53
|
-
|
|
61
|
+
entries.add(entry)
|
|
62
|
+
snapshot.value = Option.none()
|
|
63
|
+
getOrCreateBucket(byName, entry.name).add(entry)
|
|
54
64
|
entry.handles.forEach((tag) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
byTag.set(tag, [entry])
|
|
58
|
-
} else {
|
|
59
|
-
byTag.set(tag, [...bucket, entry])
|
|
60
|
-
}
|
|
65
|
+
getOrCreateBucket(byTag, tag).add(entry)
|
|
66
|
+
tagged.invalidate(tag)
|
|
61
67
|
})
|
|
62
68
|
},
|
|
63
69
|
unregister: (entry) => {
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
entries.delete(entry)
|
|
71
|
+
snapshot.value = Option.none()
|
|
72
|
+
dropFromBuckets(byName, entry.name, entry)
|
|
73
|
+
entry.handles.forEach((tag) => {
|
|
74
|
+
dropFromBuckets(byTag, tag, entry)
|
|
75
|
+
tagged.invalidate(tag)
|
|
76
|
+
})
|
|
66
77
|
},
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
const
|
|
71
|
-
|
|
81
|
+
const getOrCreateBucket = <T>(map: Map<string, Set<T>>, key: string): Set<T> => {
|
|
82
|
+
const existing = map.get(key)
|
|
83
|
+
if (existing !== undefined) {
|
|
84
|
+
return existing
|
|
85
|
+
}
|
|
86
|
+
const created = new Set<T>()
|
|
87
|
+
map.set(key, created)
|
|
88
|
+
return created
|
|
89
|
+
}
|
|
72
90
|
|
|
73
91
|
const isolateApply = (
|
|
74
92
|
reducer: ReducerEntry,
|
|
@@ -78,9 +96,10 @@ const isolateApply = (
|
|
|
78
96
|
const end = instrumentation.reducerRun(reducer.name, event._tag)
|
|
79
97
|
return Effect.runSync(
|
|
80
98
|
Effect.match(
|
|
81
|
-
Effect.try({
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
Effect.try({
|
|
100
|
+
try: () => reducer.apply(event),
|
|
101
|
+
catch: (error) => error,
|
|
102
|
+
}).pipe(Effect.ensuring(Effect.sync(end))),
|
|
84
103
|
{
|
|
85
104
|
onFailure: (error) => Option.some({ event, error }),
|
|
86
105
|
onSuccess: () => Option.none(),
|
|
@@ -91,7 +110,7 @@ const isolateApply = (
|
|
|
91
110
|
|
|
92
111
|
export const reducersLayer: Layer.Layer<Reducers> = Layer.sync(Reducers, makeReducerRegistry)
|
|
93
112
|
|
|
94
|
-
//
|
|
113
|
+
// Partitioning keeps dispatch order stable within each priority.
|
|
95
114
|
const rank = (priority: Envelope['priority']): number => (priority === 'High' ? 0 : 1)
|
|
96
115
|
|
|
97
116
|
const drain = Effect.gen(function* () {
|
|
@@ -100,8 +119,8 @@ const drain = Effect.gen(function* () {
|
|
|
100
119
|
const { byName: channels } = yield* Channels
|
|
101
120
|
const procedures = yield* Procedures
|
|
102
121
|
const instrumentation = yield* resolveInstrumentation
|
|
103
|
-
// One counter per engine: feature child runtimes share the app budget
|
|
104
122
|
const budget = yield* resolveCurrentEventBudget
|
|
123
|
+
// Child feature runtimes share this engine-level counter with the root.
|
|
105
124
|
const counter = makeTickCounter(budget)
|
|
106
125
|
const subscription = yield* PubSub.subscribe(bus)
|
|
107
126
|
|
|
@@ -115,25 +134,25 @@ const drain = Effect.gen(function* () {
|
|
|
115
134
|
const [high, normal] = Arr.partition(frame, (envelope) => rank(envelope.priority) !== 0)
|
|
116
135
|
const ordered = [...high, ...normal]
|
|
117
136
|
|
|
118
|
-
//
|
|
137
|
+
// Check before folding so runaway frames stop at the hard limit.
|
|
119
138
|
const sample = yield* Effect.sync(() => counter.add(ordered.length))
|
|
120
139
|
yield* enforceBudget(budget, sample)
|
|
121
140
|
|
|
122
|
-
//
|
|
141
|
+
// One synchronous block coalesces reducer writes into a single flush.
|
|
123
142
|
const failures = yield* Effect.sync(() => {
|
|
124
143
|
const endFrame = instrumentation.frame(ordered.length)
|
|
125
144
|
ordered.forEach((envelope) =>
|
|
126
145
|
instrumentation.eventDispatched(envelope.event._tag, envelope.priority),
|
|
127
146
|
)
|
|
128
|
-
// Isolate folds so one throw cannot kill the drain fiber
|
|
129
147
|
const collected = Arr.flatMap(ordered, (envelope) =>
|
|
130
|
-
|
|
148
|
+
// A reducer defect must not abandon the remaining frame.
|
|
149
|
+
Arr.filterMap(reducers.tagged(envelope.event._tag), (reducer) =>
|
|
131
150
|
isolateApply(reducer, envelope.event, instrumentation),
|
|
132
151
|
),
|
|
133
152
|
)
|
|
134
|
-
// Offer each distinct channel once (per-procedure would double-run shared channels)
|
|
135
153
|
ordered.forEach((envelope) => {
|
|
136
|
-
|
|
154
|
+
// channelsFor is distinct; per-procedure offers would double-run shared channels.
|
|
155
|
+
procedures.channelsFor(envelope.event._tag).forEach((channelName) => {
|
|
137
156
|
channels.get(channelName)?.offer(envelope.event)
|
|
138
157
|
})
|
|
139
158
|
})
|
|
@@ -150,7 +169,7 @@ const drain = Effect.gen(function* () {
|
|
|
150
169
|
{ discard: true },
|
|
151
170
|
)
|
|
152
171
|
}).pipe(
|
|
153
|
-
//
|
|
172
|
+
// Typed overflow halts the loop; defects are logged and the next tick continues.
|
|
154
173
|
Effect.catchAllCause((cause) =>
|
|
155
174
|
Option.match(Cause.failureOption(cause), {
|
|
156
175
|
onSome: () => Effect.failCause(cause),
|
|
@@ -180,3 +199,13 @@ export const Engine: Layer.Layer<Bus | Reducers | Channels | Procedures | Querie
|
|
|
180
199
|
),
|
|
181
200
|
),
|
|
182
201
|
)
|
|
202
|
+
|
|
203
|
+
export const EngineRequirements: readonly [
|
|
204
|
+
typeof Bus,
|
|
205
|
+
typeof Reducers,
|
|
206
|
+
typeof Channels,
|
|
207
|
+
typeof Procedures,
|
|
208
|
+
typeof Queries,
|
|
209
|
+
] = [Bus, Reducers, Channels, Procedures, Queries]
|
|
210
|
+
|
|
211
|
+
export type EngineServices = Layer.Layer.Success<typeof Engine>
|
|
@@ -19,11 +19,13 @@ const GreetingBase: Context.TagClass<Greeting, 'featureScene.Greeting', Greeting
|
|
|
19
19
|
class Greeting extends GreetingBase {}
|
|
20
20
|
|
|
21
21
|
class Started extends Event.make('featureScene.Started', S.Struct({})) {}
|
|
22
|
-
class RootUi extends ui('featureScene.RootUi')<{
|
|
22
|
+
class RootUi extends ui('featureScene.RootUi')<{
|
|
23
|
+
props: { readonly greeting: string }
|
|
24
|
+
}>() {}
|
|
23
25
|
class RootComp extends Composition.make('featureScene.RootComp', {
|
|
24
26
|
title: 'Feature root',
|
|
25
27
|
ui: RootUi,
|
|
26
|
-
}) {}
|
|
28
|
+
})<RootComp>() {}
|
|
27
29
|
|
|
28
30
|
const RootLive = Composition.live(RootComp, function* () {
|
|
29
31
|
const greeting = yield* Greeting
|
package/src/scene/scene.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { Layer, Struct } from 'effect'
|
|
2
|
-
import type {
|
|
1
|
+
import { Layer, Predicate, Struct } from 'effect'
|
|
2
|
+
import type {
|
|
3
|
+
AnyComposition,
|
|
4
|
+
CompositionClass,
|
|
5
|
+
CompositionId,
|
|
6
|
+
CompositionReflection,
|
|
7
|
+
HasExactCompositionId,
|
|
8
|
+
} from '../compose/composition'
|
|
3
9
|
import type { UiContract } from '../compose/ui'
|
|
4
10
|
import type { EventOf } from '../event/event'
|
|
5
|
-
import type
|
|
6
|
-
EagerFeatureClass,
|
|
7
|
-
EngineServices,
|
|
8
|
-
FeatureRequirement,
|
|
9
|
-
ProvidedBy,
|
|
10
|
-
} from '../feature/feature'
|
|
11
|
+
import { type EagerFeatureClass, type FeatureRequirement, tree } from '../feature/feature'
|
|
11
12
|
import { CurrentSeedOverrides } from '../internal/seeds'
|
|
12
13
|
import type { SeedsOf } from '../state/stateGroup'
|
|
13
14
|
import type { Bus } from '../runtime/bus'
|
|
@@ -16,88 +17,257 @@ import {
|
|
|
16
17
|
type Instrumentation,
|
|
17
18
|
noopInstrumentation,
|
|
18
19
|
} from '../runtime/instrumentation'
|
|
20
|
+
import type { EngineServices } from '../runtime/loop'
|
|
19
21
|
|
|
20
|
-
export type MountedServices =
|
|
22
|
+
export type MountedServices = CompositionId<string> | Bus
|
|
21
23
|
|
|
22
|
-
type
|
|
24
|
+
export type SceneCapture<C extends UiContract, S extends ReadonlyArray<unknown>, Result> = <
|
|
25
|
+
Services,
|
|
26
|
+
P,
|
|
27
|
+
N extends string,
|
|
28
|
+
Identity,
|
|
29
|
+
>(
|
|
30
|
+
scene: CapturedScene<C, S, Services, P, N, Identity>,
|
|
31
|
+
) => Result
|
|
32
|
+
|
|
33
|
+
export type AnySceneCapture<Result> = <
|
|
34
|
+
C extends UiContract,
|
|
35
|
+
S extends ReadonlyArray<unknown>,
|
|
36
|
+
Services,
|
|
37
|
+
P,
|
|
38
|
+
N extends string,
|
|
39
|
+
Identity,
|
|
40
|
+
>(
|
|
41
|
+
scene: CapturedScene<C, S, Services, P, N, Identity>,
|
|
42
|
+
) => Result
|
|
43
|
+
|
|
44
|
+
interface SceneOptionalFields {
|
|
45
|
+
readonly boot: ReadonlyArray<BootEvent>
|
|
46
|
+
// Hosts record render spans outside the scene's layer context.
|
|
47
|
+
readonly instrumentation: Instrumentation
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface AnySceneExternalApi extends Partial<SceneOptionalFields> {
|
|
51
|
+
readonly kind: 'Scene'
|
|
52
|
+
readonly composition: AnyComposition
|
|
53
|
+
readonly captureAny: <Result>(visit: AnySceneCapture<Result>) => Result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type AnyScene = AnySceneExternalApi
|
|
23
57
|
|
|
24
58
|
export interface Scene<
|
|
25
59
|
C extends UiContract = UiContract,
|
|
26
60
|
S extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
|
|
27
|
-
> {
|
|
61
|
+
> extends AnySceneExternalApi {
|
|
62
|
+
readonly composition: AnyComposition & CompositionReflection<unknown, C, S>
|
|
63
|
+
readonly capture: <Result>(visit: SceneCapture<C, S, Result>) => Result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type BootEvent = EventOf<string, unknown>
|
|
67
|
+
|
|
68
|
+
export interface CapturedScene<
|
|
69
|
+
C extends UiContract,
|
|
70
|
+
S extends ReadonlyArray<unknown>,
|
|
71
|
+
Services,
|
|
72
|
+
P,
|
|
73
|
+
N extends string,
|
|
74
|
+
Identity = unknown,
|
|
75
|
+
> extends Scene<C, S> {
|
|
28
76
|
readonly kind: 'Scene'
|
|
29
|
-
readonly composition: CompositionClass<
|
|
30
|
-
readonly provide: ReadonlyArray<Layer.Layer<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
77
|
+
readonly composition: CompositionClass<P, C, S, N, Identity>
|
|
78
|
+
readonly provide: ReadonlyArray<Layer.Layer<Services, never, never>>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface SceneConfigOptionalFields {
|
|
82
|
+
readonly boot: ReadonlyArray<BootEvent>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SceneConfig<Services> extends Partial<SceneConfigOptionalFields> {
|
|
86
|
+
readonly provide: ReadonlyArray<Layer.Layer<Services, never, never>>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type SceneConfigIncludingRootComposition<Services, N extends string, Identity> =
|
|
90
|
+
HasExactCompositionId<Services, N, Identity> extends true
|
|
91
|
+
? SceneConfig<Services>
|
|
92
|
+
: SceneConfig<Services> & {
|
|
93
|
+
readonly missingCompositionService: 'The scene must provide its root composition'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface SceneValueConfigExternalApi<Services>
|
|
97
|
+
extends SceneConfig<Services>, Partial<Pick<SceneOptionalFields, 'instrumentation'>> {}
|
|
98
|
+
|
|
99
|
+
const makeSceneValue = <
|
|
100
|
+
P,
|
|
101
|
+
C extends UiContract,
|
|
102
|
+
S extends ReadonlyArray<unknown>,
|
|
103
|
+
N extends string,
|
|
104
|
+
Identity,
|
|
105
|
+
Services,
|
|
106
|
+
>(
|
|
107
|
+
composition: CompositionClass<P, C, S, N, Identity>,
|
|
108
|
+
config: SceneValueConfigExternalApi<Services>,
|
|
109
|
+
): CapturedScene<C, S, Services, P, N, Identity> => {
|
|
110
|
+
const sceneValue: CapturedScene<C, S, Services, P, N, Identity> = {
|
|
111
|
+
kind: 'Scene',
|
|
112
|
+
composition,
|
|
113
|
+
provide: config.provide,
|
|
114
|
+
...(config.boot !== undefined ? { boot: config.boot } : {}),
|
|
115
|
+
...(config.instrumentation !== undefined ? { instrumentation: config.instrumentation } : {}),
|
|
116
|
+
capture: <Result>(visit: SceneCapture<C, S, Result>): Result => visit(sceneValue),
|
|
117
|
+
captureAny: <Result>(visit: AnySceneCapture<Result>): Result => visit(sceneValue),
|
|
118
|
+
}
|
|
119
|
+
return sceneValue
|
|
36
120
|
}
|
|
37
121
|
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
122
|
+
export const scene = <
|
|
123
|
+
P,
|
|
124
|
+
C extends UiContract,
|
|
125
|
+
S extends ReadonlyArray<unknown>,
|
|
126
|
+
N extends string,
|
|
127
|
+
Identity,
|
|
128
|
+
Services,
|
|
129
|
+
>(
|
|
130
|
+
composition: CompositionClass<P, C, S, N, Identity>,
|
|
131
|
+
config: SceneConfigIncludingRootComposition<Services, N, Identity>,
|
|
132
|
+
): CapturedScene<C, S, Services, P, N, Identity> => {
|
|
133
|
+
return makeSceneValue(composition, config)
|
|
42
134
|
}
|
|
43
135
|
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
config: SceneConfig,
|
|
47
|
-
): Scene<C, S> => ({
|
|
48
|
-
kind: 'Scene',
|
|
49
|
-
composition,
|
|
50
|
-
provide: config.provide,
|
|
51
|
-
...(config.boot !== undefined ? { boot: config.boot } : {}),
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
export interface FeatureSceneConfig<
|
|
55
|
-
Requires extends ReadonlyArray<FeatureRequirement>,
|
|
56
|
-
> {
|
|
57
|
-
readonly provide: Layer.Layer<EngineServices | ProvidedBy<Requires>, never, never>
|
|
136
|
+
export interface FeatureSceneConfig<OpenServices> {
|
|
137
|
+
readonly provide: Layer.Layer<EngineServices | OpenServices, never, never>
|
|
58
138
|
}
|
|
59
139
|
|
|
60
140
|
export const featureScene = <
|
|
141
|
+
FeatureIdentity,
|
|
61
142
|
P,
|
|
62
143
|
C extends UiContract,
|
|
63
144
|
ROut,
|
|
64
|
-
|
|
145
|
+
DirectRequires extends ReadonlyArray<FeatureRequirement>,
|
|
146
|
+
OpenServices,
|
|
147
|
+
S extends ReadonlyArray<unknown>,
|
|
148
|
+
N extends string,
|
|
149
|
+
Identity,
|
|
65
150
|
>(
|
|
66
|
-
feature: EagerFeatureClass<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
151
|
+
feature: EagerFeatureClass<
|
|
152
|
+
FeatureIdentity,
|
|
153
|
+
P,
|
|
154
|
+
C,
|
|
155
|
+
ROut,
|
|
156
|
+
DirectRequires,
|
|
157
|
+
OpenServices,
|
|
158
|
+
S,
|
|
159
|
+
N,
|
|
160
|
+
Identity
|
|
161
|
+
>,
|
|
162
|
+
config: FeatureSceneConfig<OpenServices>,
|
|
163
|
+
): CapturedScene<C, S, ROut | EngineServices | OpenServices, P, N, Identity> => {
|
|
164
|
+
const native = tree<typeof feature>(feature.token)
|
|
165
|
+
const app = native.eagerModule.layer.pipe(Layer.provideMerge(config.provide))
|
|
166
|
+
return makeSceneValue(native.composition, {
|
|
167
|
+
provide: [app],
|
|
168
|
+
boot: native.binding.boot,
|
|
75
169
|
})
|
|
76
170
|
}
|
|
77
171
|
|
|
78
|
-
|
|
79
|
-
|
|
172
|
+
export function seedScene<
|
|
173
|
+
C extends UiContract,
|
|
174
|
+
S extends ReadonlyArray<unknown>,
|
|
175
|
+
Services,
|
|
176
|
+
P,
|
|
177
|
+
N extends string,
|
|
178
|
+
Identity,
|
|
179
|
+
>(
|
|
180
|
+
base: CapturedScene<C, S, Services, P, N, Identity>,
|
|
181
|
+
seeds: SeedsOf<S>,
|
|
182
|
+
): CapturedScene<C, S, Services, P, N, Identity>
|
|
183
|
+
export function seedScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
|
|
80
184
|
base: Scene<C, S>,
|
|
81
185
|
seeds: SeedsOf<S>,
|
|
82
|
-
): Scene<C, S>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
186
|
+
): Scene<C, S>
|
|
187
|
+
export function seedScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
|
|
188
|
+
base: Scene<C, S>,
|
|
189
|
+
seeds: SeedsOf<S>,
|
|
190
|
+
): Scene<C, S> {
|
|
191
|
+
if (Struct.keys(seeds).length === 0) {
|
|
192
|
+
return base
|
|
193
|
+
}
|
|
194
|
+
// Tooling overlays closed layers; member names, not group names, are the keys.
|
|
195
|
+
return base.capture<Scene<C, S>>(
|
|
196
|
+
(exactScene): Scene<C, S> =>
|
|
197
|
+
makeSceneValue(exactScene.composition, {
|
|
198
|
+
provide: exactScene.provide.map(Layer.locally(CurrentSeedOverrides, seeds)),
|
|
199
|
+
...(exactScene.boot === undefined ? {} : { boot: exactScene.boot }),
|
|
200
|
+
...(exactScene.instrumentation === undefined
|
|
201
|
+
? {}
|
|
202
|
+
: { instrumentation: exactScene.instrumentation }),
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
86
206
|
|
|
87
|
-
export const
|
|
207
|
+
export const seedAnyScene = (
|
|
208
|
+
base: AnyScene,
|
|
209
|
+
seeds: Readonly<Record<string, unknown>>,
|
|
210
|
+
): AnyScene => {
|
|
211
|
+
if (Struct.keys(seeds).length === 0) {
|
|
212
|
+
return base
|
|
213
|
+
}
|
|
214
|
+
return base.captureAny<AnyScene>(
|
|
215
|
+
(exactScene): AnyScene =>
|
|
216
|
+
makeSceneValue(exactScene.composition, {
|
|
217
|
+
provide: exactScene.provide.map(Layer.locally(CurrentSeedOverrides, seeds)),
|
|
218
|
+
...(exactScene.boot === undefined ? {} : { boot: exactScene.boot }),
|
|
219
|
+
...(exactScene.instrumentation === undefined
|
|
220
|
+
? {}
|
|
221
|
+
: { instrumentation: exactScene.instrumentation }),
|
|
222
|
+
}),
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function profiledScene<
|
|
227
|
+
C extends UiContract,
|
|
228
|
+
S extends ReadonlyArray<unknown>,
|
|
229
|
+
Services,
|
|
230
|
+
P,
|
|
231
|
+
N extends string,
|
|
232
|
+
Identity,
|
|
233
|
+
>(
|
|
234
|
+
base: CapturedScene<C, S, Services, P, N, Identity>,
|
|
235
|
+
instrumentation: Instrumentation,
|
|
236
|
+
): CapturedScene<C, S, Services, P, N, Identity>
|
|
237
|
+
export function profiledScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
|
|
238
|
+
base: Scene<C, S>,
|
|
239
|
+
instrumentation: Instrumentation,
|
|
240
|
+
): Scene<C, S>
|
|
241
|
+
export function profiledScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
|
|
88
242
|
base: Scene<C, S>,
|
|
89
243
|
instrumentation: Instrumentation,
|
|
90
|
-
): Scene<C, S>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
244
|
+
): Scene<C, S> {
|
|
245
|
+
return base.capture<Scene<C, S>>(
|
|
246
|
+
(exactScene): Scene<C, S> =>
|
|
247
|
+
makeSceneValue(exactScene.composition, {
|
|
248
|
+
provide: exactScene.provide.map(Layer.locally(CurrentInstrumentation, instrumentation)),
|
|
249
|
+
...(exactScene.boot === undefined ? {} : { boot: exactScene.boot }),
|
|
250
|
+
instrumentation,
|
|
251
|
+
}),
|
|
252
|
+
)
|
|
253
|
+
}
|
|
95
254
|
|
|
96
|
-
export const sceneInstrumentation = (candidate:
|
|
255
|
+
export const sceneInstrumentation = (candidate: AnyScene): Instrumentation =>
|
|
97
256
|
candidate.instrumentation ?? noopInstrumentation
|
|
98
257
|
|
|
99
|
-
|
|
100
|
-
typeof candidate === '
|
|
101
|
-
candidate
|
|
102
|
-
|
|
103
|
-
candidate.kind === '
|
|
258
|
+
const isReflectableComposition = (candidate: unknown): candidate is AnyComposition =>
|
|
259
|
+
typeof candidate === 'function' &&
|
|
260
|
+
Predicate.hasProperty(candidate, 'manifest') &&
|
|
261
|
+
Predicate.isRecord(candidate.manifest) &&
|
|
262
|
+
candidate.manifest['kind'] === 'Composition' &&
|
|
263
|
+
Predicate.hasProperty(candidate, 'parseProps') &&
|
|
264
|
+
typeof candidate.parseProps === 'function' &&
|
|
265
|
+
Predicate.hasProperty(candidate, 'capture') &&
|
|
266
|
+
typeof candidate.capture === 'function'
|
|
267
|
+
|
|
268
|
+
export const isScene = (candidate: unknown): candidate is AnyScene =>
|
|
269
|
+
Predicate.isRecord(candidate) &&
|
|
270
|
+
candidate['kind'] === 'Scene' &&
|
|
271
|
+
isReflectableComposition(candidate['composition']) &&
|
|
272
|
+
typeof candidate['capture'] === 'function' &&
|
|
273
|
+
typeof candidate['captureAny'] === 'function'
|