@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
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { expect, it } from
|
|
2
|
-
import {
|
|
1
|
+
import { expect, it } from '@effect/vitest'
|
|
2
|
+
import { Effect, Layer, Schema as S } from 'effect'
|
|
3
3
|
import {
|
|
4
4
|
AsyncCalc,
|
|
5
5
|
Calc,
|
|
6
6
|
Channel,
|
|
7
7
|
Composition,
|
|
8
|
-
CurrentInstrumentation,
|
|
9
8
|
Engine,
|
|
10
9
|
Event,
|
|
11
|
-
type Instrumentation,
|
|
12
10
|
mount,
|
|
13
|
-
noopInstrumentation,
|
|
14
11
|
type Priority,
|
|
15
12
|
Procedure,
|
|
16
13
|
profiledScene,
|
|
@@ -23,64 +20,66 @@ import {
|
|
|
23
20
|
StateGroup,
|
|
24
21
|
Ui,
|
|
25
22
|
ui,
|
|
26
|
-
} from
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
} from '../index'
|
|
24
|
+
import { makeFlightGate, until } from '../testkit/flight.testkit'
|
|
25
|
+
import {
|
|
26
|
+
CurrentInstrumentation,
|
|
27
|
+
type Instrumentation,
|
|
28
|
+
noopInstrumentation,
|
|
29
|
+
} from './instrumentation'
|
|
29
30
|
|
|
30
31
|
const makeRecorder = () => {
|
|
31
|
-
const events: Array<{ readonly tag: string; readonly priority: Priority }> =
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const calcs: Array<string> = [];
|
|
37
|
-
const queries: Array<string> = [];
|
|
32
|
+
const events: Array<{ readonly tag: string; readonly priority: Priority }> = []
|
|
33
|
+
const reducers: Array<{ readonly name: string; readonly eventTag: string }> = []
|
|
34
|
+
const states: Array<string> = []
|
|
35
|
+
const calcs: Array<string> = []
|
|
36
|
+
const queries: Array<string> = []
|
|
38
37
|
const procedures: Array<{
|
|
39
|
-
readonly name: string
|
|
40
|
-
readonly channel: string
|
|
41
|
-
readonly eventTag: string
|
|
42
|
-
}> = []
|
|
43
|
-
const frames: Array<number> = []
|
|
44
|
-
const ends = { reducer: 0, calc: 0, query: 0, procedure: 0, frame: 0 }
|
|
38
|
+
readonly name: string
|
|
39
|
+
readonly channel: string
|
|
40
|
+
readonly eventTag: string
|
|
41
|
+
}> = []
|
|
42
|
+
const frames: Array<number> = []
|
|
43
|
+
const ends = { reducer: 0, calc: 0, query: 0, procedure: 0, frame: 0 }
|
|
45
44
|
const instrumentation: Instrumentation = {
|
|
46
45
|
eventDispatched: (tag, priority) => {
|
|
47
|
-
events.push({ tag, priority })
|
|
46
|
+
events.push({ tag, priority })
|
|
48
47
|
},
|
|
49
48
|
reducerRun: (name, eventTag) => {
|
|
50
|
-
reducers.push({ name, eventTag })
|
|
49
|
+
reducers.push({ name, eventTag })
|
|
51
50
|
return () => {
|
|
52
|
-
ends.reducer += 1
|
|
53
|
-
}
|
|
51
|
+
ends.reducer += 1
|
|
52
|
+
}
|
|
54
53
|
},
|
|
55
54
|
stateUpdated: (name) => {
|
|
56
|
-
states.push(name)
|
|
55
|
+
states.push(name)
|
|
57
56
|
},
|
|
58
57
|
calcRecomputed: (name) => {
|
|
59
|
-
calcs.push(name)
|
|
58
|
+
calcs.push(name)
|
|
60
59
|
return () => {
|
|
61
|
-
ends.calc += 1
|
|
62
|
-
}
|
|
60
|
+
ends.calc += 1
|
|
61
|
+
}
|
|
63
62
|
},
|
|
64
63
|
queryRun: (name) => {
|
|
65
|
-
queries.push(name)
|
|
64
|
+
queries.push(name)
|
|
66
65
|
return () => {
|
|
67
|
-
ends.query += 1
|
|
68
|
-
}
|
|
66
|
+
ends.query += 1
|
|
67
|
+
}
|
|
69
68
|
},
|
|
70
69
|
procedureRun: (name, channel, eventTag) => {
|
|
71
|
-
procedures.push({ name, channel, eventTag })
|
|
70
|
+
procedures.push({ name, channel, eventTag })
|
|
72
71
|
return () => {
|
|
73
|
-
ends.procedure += 1
|
|
74
|
-
}
|
|
72
|
+
ends.procedure += 1
|
|
73
|
+
}
|
|
75
74
|
},
|
|
76
75
|
uiRendered: () => () => {},
|
|
77
76
|
frame: (eventCount) => {
|
|
78
|
-
frames.push(eventCount)
|
|
77
|
+
frames.push(eventCount)
|
|
79
78
|
return () => {
|
|
80
|
-
ends.frame += 1
|
|
81
|
-
}
|
|
79
|
+
ends.frame += 1
|
|
80
|
+
}
|
|
82
81
|
},
|
|
83
|
-
}
|
|
82
|
+
}
|
|
84
83
|
return {
|
|
85
84
|
instrumentation,
|
|
86
85
|
events,
|
|
@@ -91,186 +90,207 @@ const makeRecorder = () => {
|
|
|
91
90
|
procedures,
|
|
92
91
|
frames,
|
|
93
92
|
ends,
|
|
94
|
-
}
|
|
95
|
-
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
96
95
|
|
|
97
|
-
it.live(
|
|
98
|
-
|
|
99
|
-
()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class Fold extends Reducer.make("InstrFold", {
|
|
107
|
-
states: [Counter],
|
|
108
|
-
events: [Bumped],
|
|
109
|
-
}) {}
|
|
110
|
-
const FoldLive = Reducer.live(Fold, (_, event) => event.to);
|
|
96
|
+
it.live('the engine reports frames, events, reducer spans, and state updates', () => {
|
|
97
|
+
const recorder = makeRecorder()
|
|
98
|
+
class Counter extends State.make('instr-counter', S.Number) {}
|
|
99
|
+
class Bumped extends Event.make('InstrBumped', S.Struct({ to: S.Number })) {}
|
|
100
|
+
class Fold extends Reducer.make('InstrFold', {
|
|
101
|
+
states: [Counter],
|
|
102
|
+
events: [Bumped],
|
|
103
|
+
}) {}
|
|
104
|
+
const FoldLive = Reducer.live(Fold, (_, event) => event.to)
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
const TestLayer = FoldLive.pipe(
|
|
107
|
+
Layer.provideMerge(Layer.mergeAll(State.live(Counter, 0), Engine)),
|
|
108
|
+
Layer.locally(CurrentInstrumentation, recorder.instrumentation),
|
|
109
|
+
)
|
|
116
110
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
return Effect.gen(function* () {
|
|
112
|
+
yield* publish('High', Event.construct(Bumped, { to: 1 }))
|
|
113
|
+
yield* publish('Normal', Event.construct(Bumped, { to: 2 }))
|
|
114
|
+
yield* until(
|
|
115
|
+
() => recorder.ends.frame,
|
|
116
|
+
(n) => n >= 1,
|
|
117
|
+
)
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
119
|
+
expect(recorder.frames).toEqual([2])
|
|
120
|
+
expect(recorder.ends.frame).toBe(1)
|
|
121
|
+
expect(recorder.events).toEqual([
|
|
122
|
+
{ tag: 'InstrBumped', priority: 'High' },
|
|
123
|
+
{ tag: 'InstrBumped', priority: 'Normal' },
|
|
124
|
+
])
|
|
125
|
+
expect(recorder.reducers).toEqual([
|
|
126
|
+
{ name: 'InstrFold', eventTag: 'InstrBumped' },
|
|
127
|
+
{ name: 'InstrFold', eventTag: 'InstrBumped' },
|
|
128
|
+
])
|
|
129
|
+
expect(recorder.ends.reducer).toBe(2)
|
|
130
|
+
expect(recorder.states).toEqual(['instr-counter', 'instr-counter'])
|
|
131
|
+
}).pipe(Effect.provide(TestLayer))
|
|
132
|
+
})
|
|
137
133
|
|
|
138
|
-
it.live(
|
|
139
|
-
const recorder = makeRecorder()
|
|
140
|
-
class Cell extends State.make(
|
|
141
|
-
class Wrote extends Event.make(
|
|
142
|
-
class Write extends Reducer.make(
|
|
134
|
+
it.live('an equal-value write is not reported as a state update', () => {
|
|
135
|
+
const recorder = makeRecorder()
|
|
136
|
+
class Cell extends State.make('instr-cell', S.Number) {}
|
|
137
|
+
class Wrote extends Event.make('InstrWrote', S.Struct({ to: S.Number })) {}
|
|
138
|
+
class Write extends Reducer.make('InstrWrite', {
|
|
143
139
|
states: [Cell],
|
|
144
140
|
events: [Wrote],
|
|
145
141
|
}) {}
|
|
146
|
-
const WriteLive = Reducer.live(Write, (_, event) => event.to)
|
|
142
|
+
const WriteLive = Reducer.live(Write, (_, event) => event.to)
|
|
147
143
|
|
|
148
144
|
const TestLayer = WriteLive.pipe(
|
|
149
145
|
Layer.provideMerge(Layer.mergeAll(State.live(Cell, 7), Engine)),
|
|
150
146
|
Layer.locally(CurrentInstrumentation, recorder.instrumentation),
|
|
151
|
-
)
|
|
147
|
+
)
|
|
152
148
|
|
|
153
149
|
return Effect.gen(function* () {
|
|
154
|
-
yield* publish(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
yield* publish('High', Event.construct(Wrote, { to: 7 }))
|
|
151
|
+
// The state update, if any, is reported inside the reducer's span.
|
|
152
|
+
yield* until(
|
|
153
|
+
() => recorder.ends.reducer,
|
|
154
|
+
(n) => n >= 1,
|
|
155
|
+
)
|
|
156
|
+
expect(recorder.reducers.length).toBe(1)
|
|
157
|
+
expect(recorder.states).toEqual([])
|
|
158
|
+
}).pipe(Effect.provide(TestLayer))
|
|
159
|
+
})
|
|
160
160
|
|
|
161
|
-
it.live(
|
|
162
|
-
const recorder = makeRecorder()
|
|
163
|
-
class Count extends State.make(
|
|
161
|
+
it.live('calc recomputes are spanned; memo hits are not counted', () => {
|
|
162
|
+
const recorder = makeRecorder()
|
|
163
|
+
class Count extends State.make('instr-calc-count', S.Number) {}
|
|
164
164
|
class Inputs extends StateGroup.make(Count) {}
|
|
165
|
-
class Doubled extends Calc.make(
|
|
166
|
-
inputs: [StateGroup.select(Inputs,
|
|
165
|
+
class Doubled extends Calc.make('InstrDoubled', {
|
|
166
|
+
inputs: [StateGroup.select(Inputs, 'instr-calc-count')],
|
|
167
167
|
output: S.Number,
|
|
168
168
|
}) {}
|
|
169
|
-
const DoubledLive = Calc.live(
|
|
170
|
-
Doubled,
|
|
171
|
-
(inputs) => inputs["instr-calc-count"] * 2,
|
|
172
|
-
);
|
|
169
|
+
const DoubledLive = Calc.live(Doubled, (inputs) => inputs['instr-calc-count'] * 2)
|
|
173
170
|
|
|
174
171
|
const TestLayer = DoubledLive.pipe(
|
|
175
|
-
Layer.provideMerge(StateGroup.live(Inputs, {
|
|
172
|
+
Layer.provideMerge(StateGroup.live(Inputs, { 'instr-calc-count': 5 })),
|
|
176
173
|
Layer.locally(CurrentInstrumentation, recorder.instrumentation),
|
|
177
|
-
)
|
|
174
|
+
)
|
|
178
175
|
|
|
179
176
|
return Effect.gen(function* () {
|
|
180
|
-
const derived = yield* Doubled.store
|
|
181
|
-
const builds = recorder.calcs.length
|
|
182
|
-
expect(derived.get()).toBe(10)
|
|
183
|
-
expect(derived.get()).toBe(10)
|
|
184
|
-
expect(recorder.calcs.length).toBe(builds)
|
|
177
|
+
const derived = yield* Doubled.store
|
|
178
|
+
const builds = recorder.calcs.length
|
|
179
|
+
expect(derived.get()).toBe(10)
|
|
180
|
+
expect(derived.get()).toBe(10)
|
|
181
|
+
expect(recorder.calcs.length).toBe(builds)
|
|
185
182
|
|
|
186
|
-
const source = yield* StateGroup.select(Inputs,
|
|
187
|
-
source.set(6)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
183
|
+
const source = yield* StateGroup.select(Inputs, 'instr-calc-count').store
|
|
184
|
+
source.set(6)
|
|
185
|
+
// Wait on the recompute itself — pulling `get()` would recompute under the
|
|
186
|
+
// poller and hide the propagation that reports the derived state update.
|
|
187
|
+
yield* until(
|
|
188
|
+
() => recorder.calcs.length,
|
|
189
|
+
(n) => n === builds + 1,
|
|
190
|
+
)
|
|
191
|
+
expect(derived.get()).toBe(12)
|
|
192
|
+
expect(recorder.calcs.length).toBe(builds + 1)
|
|
193
|
+
expect(recorder.calcs.every((name) => name === 'InstrDoubled')).toBe(true)
|
|
194
|
+
expect(recorder.ends.calc).toBe(recorder.calcs.length)
|
|
195
|
+
expect(recorder.states).toContain('InstrDoubled')
|
|
196
|
+
}).pipe(Effect.provide(TestLayer))
|
|
197
|
+
})
|
|
196
198
|
|
|
197
|
-
it.live(
|
|
198
|
-
const recorder = makeRecorder()
|
|
199
|
-
class Count extends State.make(
|
|
199
|
+
it.live('async calc query runs are spanned start-to-settle', () => {
|
|
200
|
+
const recorder = makeRecorder()
|
|
201
|
+
class Count extends State.make('instr-query-count', S.Number) {}
|
|
200
202
|
class Inputs extends StateGroup.make(Count) {}
|
|
201
|
-
class Fetched extends AsyncCalc.make(
|
|
202
|
-
inputs: [StateGroup.select(Inputs,
|
|
203
|
+
class Fetched extends AsyncCalc.make('InstrFetched', {
|
|
204
|
+
inputs: [StateGroup.select(Inputs, 'instr-query-count')],
|
|
203
205
|
output: S.Number,
|
|
204
206
|
alwaysOn: true,
|
|
205
207
|
}) {}
|
|
208
|
+
const gate = makeFlightGate()
|
|
206
209
|
const FetchedLive = AsyncCalc.live(Fetched, {
|
|
207
|
-
query: (inputs) =>
|
|
208
|
-
|
|
209
|
-
Effect.delay(Duration.millis(5)),
|
|
210
|
-
),
|
|
211
|
-
});
|
|
210
|
+
query: (inputs) => gate.through(Effect.succeed(inputs['instr-query-count'] * 2)),
|
|
211
|
+
})
|
|
212
212
|
|
|
213
213
|
const TestLayer = FetchedLive.pipe(
|
|
214
|
-
Layer.provideMerge(StateGroup.live(Inputs, {
|
|
214
|
+
Layer.provideMerge(StateGroup.live(Inputs, { 'instr-query-count': 3 })),
|
|
215
215
|
Layer.locally(CurrentInstrumentation, recorder.instrumentation),
|
|
216
|
-
)
|
|
216
|
+
)
|
|
217
217
|
|
|
218
218
|
return Effect.gen(function* () {
|
|
219
|
-
|
|
220
|
-
yield*
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
expect(recorder.
|
|
224
|
-
|
|
225
|
-
});
|
|
219
|
+
yield* gate.hold
|
|
220
|
+
const store = yield* Fetched.store
|
|
221
|
+
// Parked mid-flight: the span is open, so its end must not have run yet.
|
|
222
|
+
yield* gate.awaitEntry(1)
|
|
223
|
+
expect(recorder.queries).toEqual(['InstrFetched'])
|
|
224
|
+
expect(recorder.ends.query).toBe(0)
|
|
226
225
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
yield* gate.release
|
|
227
|
+
yield* until(
|
|
228
|
+
() => store.get()._tag,
|
|
229
|
+
(tag) => tag === 'Success',
|
|
230
|
+
)
|
|
231
|
+
expect(store.get()._tag).toBe('Success')
|
|
232
|
+
expect(recorder.queries).toEqual(['InstrFetched'])
|
|
233
|
+
expect(recorder.ends.query).toBe(1)
|
|
234
|
+
}).pipe(Effect.provide(TestLayer))
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it.live('procedure runs are spanned with their channel and trigger tag', () => {
|
|
238
|
+
const recorder = makeRecorder()
|
|
239
|
+
class Kicked extends Event.make('InstrKicked', S.Struct({})) {}
|
|
240
|
+
class Lane extends Channel.make('InstrLane', { policy: { _tag: 'merge' } }) {}
|
|
241
|
+
class Work extends Procedure.make('InstrWork', {
|
|
232
242
|
events: [Kicked],
|
|
233
243
|
channel: Lane,
|
|
234
244
|
}) {}
|
|
245
|
+
const gate = makeFlightGate()
|
|
235
246
|
const WorkLive = Procedure.live(Work, function* () {
|
|
236
|
-
yield*
|
|
237
|
-
})
|
|
247
|
+
yield* gate.through(Effect.void)
|
|
248
|
+
})
|
|
238
249
|
|
|
239
250
|
const TestLayer = Layer.mergeAll(Channel.live(Lane), WorkLive).pipe(
|
|
240
251
|
Layer.provideMerge(Engine),
|
|
241
252
|
Layer.locally(CurrentInstrumentation, recorder.instrumentation),
|
|
242
|
-
)
|
|
253
|
+
)
|
|
243
254
|
|
|
244
255
|
return Effect.gen(function* () {
|
|
245
|
-
yield*
|
|
246
|
-
yield*
|
|
256
|
+
yield* gate.hold
|
|
257
|
+
yield* publish('Normal', Event.construct(Kicked, {}))
|
|
258
|
+
// Parked mid-run: the span opened with its channel and trigger, and stays open.
|
|
259
|
+
yield* gate.awaitEntry(1)
|
|
247
260
|
expect(recorder.procedures).toEqual([
|
|
248
|
-
{ name:
|
|
249
|
-
])
|
|
250
|
-
expect(recorder.ends.procedure).toBe(
|
|
251
|
-
|
|
252
|
-
|
|
261
|
+
{ name: 'InstrWork', channel: 'InstrLane', eventTag: 'InstrKicked' },
|
|
262
|
+
])
|
|
263
|
+
expect(recorder.ends.procedure).toBe(0)
|
|
264
|
+
|
|
265
|
+
yield* gate.release
|
|
266
|
+
yield* until(
|
|
267
|
+
() => recorder.ends.procedure,
|
|
268
|
+
(n) => n >= 1,
|
|
269
|
+
)
|
|
270
|
+
expect(recorder.ends.procedure).toBe(1)
|
|
271
|
+
}).pipe(Effect.provide(TestLayer))
|
|
272
|
+
})
|
|
253
273
|
|
|
254
|
-
class SceneCount extends State.make(
|
|
274
|
+
class SceneCount extends State.make('instr-scene-count', S.Number) {}
|
|
255
275
|
class SceneStates extends StateGroup.make(SceneCount) {}
|
|
256
|
-
class SceneBumped extends Event.make(
|
|
257
|
-
class SceneBump extends Reducer.make(
|
|
276
|
+
class SceneBumped extends Event.make('InstrSceneBumped', S.Struct({})) {}
|
|
277
|
+
class SceneBump extends Reducer.make('InstrSceneBump', {
|
|
258
278
|
states: [SceneCount],
|
|
259
279
|
events: [SceneBumped],
|
|
260
280
|
}) {}
|
|
261
|
-
const SceneBumpLive = Reducer.live(SceneBump, (n) => n + 1)
|
|
281
|
+
const SceneBumpLive = Reducer.live(SceneBump, (n) => n + 1)
|
|
262
282
|
|
|
263
|
-
class SceneUi extends ui(
|
|
264
|
-
class SceneRoot extends Composition.make(
|
|
265
|
-
title:
|
|
283
|
+
class SceneUi extends ui('InstrScene')<{ props: { count: number } }>() {}
|
|
284
|
+
class SceneRoot extends Composition.make('InstrScene', {
|
|
285
|
+
title: 'InstrScene',
|
|
266
286
|
states: [SceneStates],
|
|
267
287
|
events: [SceneBumped],
|
|
268
288
|
ui: SceneUi,
|
|
269
|
-
}) {}
|
|
289
|
+
})<SceneRoot>() {}
|
|
270
290
|
const SceneRootLive = Composition.live(SceneRoot, function* () {
|
|
271
|
-
const count = yield* StateGroup.select(SceneStates,
|
|
272
|
-
return mount({ props: { count }, slots: {} })
|
|
273
|
-
})
|
|
291
|
+
const count = yield* StateGroup.select(SceneStates, 'instr-scene-count')
|
|
292
|
+
return mount({ props: { count }, slots: {} })
|
|
293
|
+
})
|
|
274
294
|
|
|
275
295
|
const SceneApp = SceneRootLive.pipe(
|
|
276
296
|
Layer.provideMerge(
|
|
@@ -282,43 +302,42 @@ const SceneApp = SceneRootLive.pipe(
|
|
|
282
302
|
Layer.provideMerge(
|
|
283
303
|
Layer.mergeAll(SceneBumpLive).pipe(
|
|
284
304
|
Layer.provideMerge(
|
|
285
|
-
Layer.mergeAll(
|
|
286
|
-
Engine,
|
|
287
|
-
StateGroup.live(SceneStates, { "instr-scene-count": 0 }),
|
|
288
|
-
),
|
|
305
|
+
Layer.mergeAll(Engine, StateGroup.live(SceneStates, { 'instr-scene-count': 0 })),
|
|
289
306
|
),
|
|
290
307
|
),
|
|
291
308
|
),
|
|
292
|
-
)
|
|
293
|
-
const InstrScene = scene(SceneRoot, { provide: [SceneApp] })
|
|
309
|
+
)
|
|
310
|
+
const InstrScene = scene(SceneRoot, { provide: [SceneApp] })
|
|
294
311
|
|
|
295
312
|
const runScene = (target: typeof InstrScene) => {
|
|
296
|
-
const layer = target.provide.reduce((a, b) => Layer.merge(a, b))
|
|
313
|
+
const layer = target.provide.reduce((a, b) => Layer.merge(a, b))
|
|
297
314
|
const drive = Effect.gen(function* () {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
315
|
+
// Each run builds its own state store, so the fold itself is the barrier —
|
|
316
|
+
// it settles whether or not the run is being recorded.
|
|
317
|
+
const count = yield* StateGroup.select(SceneStates, 'instr-scene-count').store
|
|
318
|
+
yield* publish('High', Event.construct(SceneBumped, {}))
|
|
319
|
+
yield* until(
|
|
320
|
+
() => count.get(),
|
|
321
|
+
(n) => n === 1,
|
|
322
|
+
)
|
|
323
|
+
})
|
|
324
|
+
return { layer, drive }
|
|
325
|
+
}
|
|
303
326
|
|
|
304
|
-
it.scopedLive(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const profiled = profiledScene(InstrScene, recorder.instrumentation);
|
|
310
|
-
expect(sceneInstrumentation(profiled)).toBe(recorder.instrumentation);
|
|
327
|
+
it.scopedLive('profiledScene reaches nested layers and carries the handle', () =>
|
|
328
|
+
Effect.gen(function* () {
|
|
329
|
+
const recorder = makeRecorder()
|
|
330
|
+
const profiled = profiledScene(InstrScene, recorder.instrumentation)
|
|
331
|
+
expect(sceneInstrumentation(profiled)).toBe(recorder.instrumentation)
|
|
311
332
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}),
|
|
324
|
-
);
|
|
333
|
+
const profiledHarness = runScene(profiled)
|
|
334
|
+
yield* profiledHarness.drive.pipe(Effect.provide(profiledHarness.layer))
|
|
335
|
+
expect(recorder.reducers).toEqual([{ name: 'InstrSceneBump', eventTag: 'InstrSceneBumped' }])
|
|
336
|
+
expect(recorder.states).toEqual(['instr-scene-count'])
|
|
337
|
+
const before = recorder.reducers.length
|
|
338
|
+
expect(sceneInstrumentation(InstrScene)).toBe(noopInstrumentation)
|
|
339
|
+
const original = runScene(InstrScene)
|
|
340
|
+
yield* original.drive.pipe(Effect.provide(original.layer))
|
|
341
|
+
expect(recorder.reducers.length).toBe(before)
|
|
342
|
+
}),
|
|
343
|
+
)
|
|
@@ -27,18 +27,13 @@ export const noopInstrumentation: Instrumentation = {
|
|
|
27
27
|
frame: () => noopSpanEnd,
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export const CurrentInstrumentation: FiberRef.FiberRef<Instrumentation> =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
export const CurrentInstrumentation: FiberRef.FiberRef<Instrumentation> = GlobalValue.globalValue(
|
|
31
|
+
Symbol.for('reform/CurrentInstrumentation'),
|
|
32
|
+
() => FiberRef.unsafeMake<Instrumentation>(noopInstrumentation),
|
|
33
|
+
)
|
|
34
34
|
|
|
35
35
|
export const resolveInstrumentation: Effect.Effect<Instrumentation> =
|
|
36
36
|
FiberRef.get(CurrentInstrumentation)
|
|
37
37
|
|
|
38
|
-
export const stateUpdateHook = (
|
|
39
|
-
instrumentation:
|
|
40
|
-
name: string,
|
|
41
|
-
): (() => void) =>
|
|
42
|
-
instrumentation === noopInstrumentation
|
|
43
|
-
? noopSpanEnd
|
|
44
|
-
: () => instrumentation.stateUpdated(name)
|
|
38
|
+
export const stateUpdateHook = (instrumentation: Instrumentation, name: string): (() => void) =>
|
|
39
|
+
instrumentation === noopInstrumentation ? noopSpanEnd : () => instrumentation.stateUpdated(name)
|