@playfast/reform-proof 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 +12 -12
- package/package.json +18 -18
- package/src/{assertions.ts → assert.ts} +3 -3
- package/src/engine.ts +285 -8
- package/src/engineMounted.ts +271 -0
- package/src/engineMounts.ts +165 -0
- package/src/engineRender.ts +136 -0
- package/src/engineSink.ts +195 -0
- package/src/engineTree.ts +298 -0
- package/src/engineTreeDriver.ts +247 -0
- package/src/engineTreeFacade.ts +183 -0
- package/src/{runtimeTreeTypes.ts → engineTreeTypes.ts} +26 -22
- package/src/errors.ts +19 -0
- package/src/facade.ts +71 -251
- package/src/fingerprint.test.ts +24 -0
- package/src/fingerprint.ts +73 -0
- package/src/index.ts +165 -115
- package/src/nested-feature.test.ts +174 -0
- package/src/product.ts +50 -19
- package/src/proofCase.ts +46 -0
- package/src/runner.ts +5 -1
- package/src/structure-events.test.ts +9 -3
- package/src/structure-locators.test.ts +19 -9
- package/src/test-clock.test.ts +135 -0
- package/src/typed-seams.test.ts +6 -3
- package/src/driverTypes.ts +0 -19
- package/src/execution.ts +0 -100
- package/src/runtimeFacade.ts +0 -269
- package/src/runtimeTreeDispose.ts +0 -21
- package/src/runtimeTreeDriver.ts +0 -285
- package/src/runtimeTreeFacade.ts +0 -134
- package/src/runtimeTreeSettle.ts +0 -34
- package/src/sink.ts +0 -73
- package/src/treeBindings.ts +0 -121
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Drive a scene's compositions and assert on what they compute — deterministical
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
A reform
|
|
16
|
+
A reform _scene_ is renderer-neutral, so it can be exercised without a host. `@playfast/reform-proof` runs that same scene against a capturing UI: it dispatches typed events, settles the engine, and lets you assert on the **props a composition computed** — pure data, never rendered markup. The scene you prove is the exact scene `@playfast/reform-react` renders, so there is no seam between a passing test and production behavior.
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
@@ -39,27 +39,27 @@ const CounterScene = scene(Counter, { provide: [CounterLive] })
|
|
|
39
39
|
class Bumps extends ProductRequirement.make(Counter, 'bumps the count') {}
|
|
40
40
|
|
|
41
41
|
const bumps = Proof.implement(Bumps, CounterScene, function* (app) {
|
|
42
|
-
yield* app.actions.bump({})
|
|
43
|
-
const props = yield* app.props
|
|
42
|
+
yield* app.actions.bump({}) // dispatch a contract event, settle
|
|
43
|
+
const props = yield* app.props // read what the composition computed
|
|
44
44
|
yield* expect(props.count).toBe(1)
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
const product = Product.make(Counter, { requirements: [Bumps] })
|
|
48
48
|
const suite = Proof.suite(product, { proofs: [bumps] })
|
|
49
49
|
|
|
50
|
-
const result = await Proof.run(suite)
|
|
50
|
+
const result = await Proof.run(suite) // { product, results, ok }
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## The workflow
|
|
54
54
|
|
|
55
|
-
| step
|
|
56
|
-
|
|
|
57
|
-
| Declare a requirement | `ProductRequirement.make(composition, statement)`
|
|
58
|
-
| Group requirements
|
|
59
|
-
| Implement a proof
|
|
60
|
-
| Compose a suite
|
|
61
|
-
| Run it
|
|
62
|
-
| Step it (for tooling) | `Proof.driver(suite) → ProofDriver[]`
|
|
55
|
+
| step | API |
|
|
56
|
+
| --------------------- | ------------------------------------------------------------ |
|
|
57
|
+
| Declare a requirement | `ProductRequirement.make(composition, statement)` |
|
|
58
|
+
| Group requirements | `Product.make(composition, { requirements })` |
|
|
59
|
+
| Implement a proof | `Proof.implement(requirement, scene, function* (app) { … })` |
|
|
60
|
+
| Compose a suite | `Proof.suite(product, { proofs })` |
|
|
61
|
+
| Run it | `Proof.run(suite) → Promise<SuiteResult>` |
|
|
62
|
+
| Step it (for tooling) | `Proof.driver(suite) → ProofDriver[]` |
|
|
63
63
|
|
|
64
64
|
The `statement` is a string-literal type, matched at `Proof.implement` so a requirement's name and its proof can never drift apart.
|
|
65
65
|
|
package/package.json
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-proof",
|
|
3
|
-
"
|
|
4
|
-
"version": "1.1.1",
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "1.2.1",
|
|
6
4
|
"description": "Headless testing toolkit for reform — drive a scene's compositions and assert on the props they compute, with no DOM, timers, or text matching.",
|
|
7
5
|
"keywords": [
|
|
8
|
-
"reform",
|
|
9
6
|
"effect",
|
|
10
|
-
"testing",
|
|
11
7
|
"headless",
|
|
12
|
-
"proof"
|
|
8
|
+
"proof",
|
|
9
|
+
"reform",
|
|
10
|
+
"testing"
|
|
13
11
|
],
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/playfast/reform/issues"
|
|
14
|
+
},
|
|
14
15
|
"license": "MIT",
|
|
15
16
|
"repository": {
|
|
16
17
|
"type": "git",
|
|
17
18
|
"url": "https://github.com/playfast/reform.git",
|
|
18
19
|
"directory": "packages/proof"
|
|
19
20
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
21
|
+
"files": [
|
|
22
|
+
"src",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
23
26
|
"sideEffects": false,
|
|
24
27
|
"exports": {
|
|
25
28
|
"./package.json": "./package.json",
|
|
26
29
|
".": "./src/index.ts",
|
|
27
30
|
"./*": "./src/*.ts"
|
|
28
31
|
},
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
33
35
|
"scripts": {
|
|
34
36
|
"clean": "rm -rf dist .tsbuildinfo",
|
|
35
37
|
"check": "tsc --noEmit",
|
|
@@ -41,11 +43,9 @@
|
|
|
41
43
|
"lint:fix": "oxlint --fix src"
|
|
42
44
|
},
|
|
43
45
|
"peerDependencies": {
|
|
46
|
+
"@playfast/reform": "*",
|
|
44
47
|
"effect": "*",
|
|
45
|
-
"react": "^19.0.0"
|
|
46
|
-
"@playfast/reform": "*"
|
|
48
|
+
"react": "^19.0.0"
|
|
47
49
|
},
|
|
48
|
-
"
|
|
49
|
-
"access": "public"
|
|
50
|
-
}
|
|
50
|
+
"playbook": "./playbook"
|
|
51
51
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Array as Arr, Effect, Option, Record as Rec } from 'effect'
|
|
2
2
|
import { AssertionFailed } from './errors'
|
|
3
|
+
import { formatFingerprint } from './fingerprint'
|
|
3
4
|
|
|
4
5
|
interface ComparePair {
|
|
5
6
|
readonly left: unknown
|
|
6
7
|
readonly right: unknown
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
const show = (subject: unknown): string => JSON.stringify(subject)
|
|
10
|
+
const show = (subject: unknown): string => formatFingerprint(subject)
|
|
11
11
|
|
|
12
12
|
const deepEqual = ({ left, right }: ComparePair): boolean =>
|
|
13
13
|
Object.is(left, right) || show(left) === show(right)
|
|
@@ -34,7 +34,7 @@ export const expect = <A>(actual: A) => ({
|
|
|
34
34
|
},
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
const isRecord = (candidate: unknown): candidate is Record<string, unknown> =>
|
|
37
|
+
export const isRecord = (candidate: unknown): candidate is Record<string, unknown> =>
|
|
38
38
|
typeof candidate === 'object' && candidate !== null
|
|
39
39
|
|
|
40
40
|
interface MatchInput {
|
package/src/engine.ts
CHANGED
|
@@ -1,8 +1,285 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { Cause, Effect, Option, Record as Rec, Ref } from 'effect'
|
|
2
|
+
import { yieldWrapGet } from 'effect/Utils'
|
|
3
|
+
import {
|
|
4
|
+
type CapturedScene,
|
|
5
|
+
type CompositionClass,
|
|
6
|
+
makeAppRuntime,
|
|
7
|
+
sceneInstrumentation,
|
|
8
|
+
type UiContract,
|
|
9
|
+
} from '@playfast/reform'
|
|
10
|
+
import { type AnyComposition, type AnyScene } from '@playfast/reform/internal'
|
|
11
|
+
import {
|
|
12
|
+
invalidCompositionProps,
|
|
13
|
+
type HostRuntime,
|
|
14
|
+
makeSink,
|
|
15
|
+
messageOf,
|
|
16
|
+
type RenderRuntime,
|
|
17
|
+
type Sink,
|
|
18
|
+
} from './engineSink'
|
|
19
|
+
import { childComposition, readRuntimeSlotChild, slotsOf } from './engineTree'
|
|
20
|
+
import type { RuntimeTreeFacade } from './engineTreeTypes'
|
|
21
|
+
import { makeRuntimeTreeFacade } from './engineTreeFacade'
|
|
22
|
+
import type { ProofCase, ProofResult } from './proofCase'
|
|
23
|
+
import type { PumpStep } from './engineTree'
|
|
24
|
+
import type { DriveResult, StepFrame } from './index'
|
|
25
|
+
import type { ProductComposition } from './product'
|
|
26
|
+
import type { Proof } from './index'
|
|
27
|
+
|
|
28
|
+
export { makeSink } from './engineSink'
|
|
29
|
+
export type { Sink } from './engineSink'
|
|
30
|
+
export { proofLayer } from './engineTree'
|
|
31
|
+
export { makeFacade, makeProofFacade, makeRuntimeHandleFacade } from './engineMounted'
|
|
32
|
+
export type {
|
|
33
|
+
MountedFacade,
|
|
34
|
+
MountedSlotFacade,
|
|
35
|
+
MountedSlotFacadesOf,
|
|
36
|
+
} from './engineSink'
|
|
37
|
+
export { makeRuntimeTreeFacade } from './engineTreeFacade'
|
|
38
|
+
export type { RuntimeTreeFacade } from './engineTreeTypes'
|
|
39
|
+
|
|
40
|
+
const proofRootProps = <
|
|
41
|
+
P,
|
|
42
|
+
C extends UiContract,
|
|
43
|
+
S extends ReadonlyArray<unknown>,
|
|
44
|
+
N extends string,
|
|
45
|
+
Identity,
|
|
46
|
+
>(
|
|
47
|
+
composition: CompositionClass<P, C, S, N, Identity>,
|
|
48
|
+
): Effect.Effect<P, never, never> =>
|
|
49
|
+
Option.match(composition.parseProps({}), {
|
|
50
|
+
onNone: () => Effect.dieMessage(invalidCompositionProps(composition)),
|
|
51
|
+
onSome: Effect.succeed,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const withProofRuntimeTree = <
|
|
55
|
+
A,
|
|
56
|
+
E,
|
|
57
|
+
C extends UiContract,
|
|
58
|
+
S extends ReadonlyArray<unknown>,
|
|
59
|
+
Services,
|
|
60
|
+
P,
|
|
61
|
+
N extends string,
|
|
62
|
+
Identity,
|
|
63
|
+
>(
|
|
64
|
+
scene: CapturedScene<C, S, Services, P, N, Identity>,
|
|
65
|
+
sink: Sink,
|
|
66
|
+
dispatched: Set<string>,
|
|
67
|
+
use: (runtime: HostRuntime, tree: RuntimeTreeFacade<C>) => Effect.Effect<A, E, never>,
|
|
68
|
+
): Effect.Effect<A, E, never> =>
|
|
69
|
+
Effect.acquireUseRelease(
|
|
70
|
+
Effect.sync(() => makeAppRuntime(scene)),
|
|
71
|
+
(runtime) =>
|
|
72
|
+
Effect.acquireUseRelease(
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const rootProps = yield* proofRootProps(scene.composition)
|
|
75
|
+
yield* Effect.sync(() => runtime.boot())
|
|
76
|
+
return makeRuntimeTreeFacade(runtime, scene.composition, rootProps, sink, dispatched)
|
|
77
|
+
}),
|
|
78
|
+
(tree) => use(runtime, tree),
|
|
79
|
+
(tree) => Effect.sync(tree.dispose),
|
|
80
|
+
),
|
|
81
|
+
(runtime) => Effect.sync(runtime.dispose),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const missingCoverage = (
|
|
85
|
+
proof: Pick<Proof, 'requirement'>,
|
|
86
|
+
dispatched: Set<string>,
|
|
87
|
+
): ReadonlyArray<string> =>
|
|
88
|
+
Option.match(proof.requirement.manifest.events, {
|
|
89
|
+
onNone: () => [],
|
|
90
|
+
onSome: (declared) => declared.map(String).filter((event) => !dispatched.has(event)),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
interface ProofPlacement {
|
|
94
|
+
readonly requirement: Proof['requirement']
|
|
95
|
+
readonly placement: 'Root' | 'Slot'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const compositionIdentity = (composition: AnyComposition): symbol =>
|
|
99
|
+
composition.capture<symbol>((exact) => exact.identity)
|
|
100
|
+
|
|
101
|
+
const verifyProofPlacement = (
|
|
102
|
+
proof: ProofPlacement,
|
|
103
|
+
scene: AnyScene,
|
|
104
|
+
runtime: RenderRuntime,
|
|
105
|
+
): Effect.Effect<void, never, never> => {
|
|
106
|
+
const expected = proof.requirement.manifest.composition
|
|
107
|
+
if (
|
|
108
|
+
proof.placement === 'Root' &&
|
|
109
|
+
Object.is(compositionIdentity(scene.composition), compositionIdentity(expected))
|
|
110
|
+
) {
|
|
111
|
+
return Effect.void
|
|
112
|
+
}
|
|
113
|
+
if (proof.placement === 'Slot') {
|
|
114
|
+
const matches = Rec.values(slotsOf(scene.composition)).some((slotClass) => {
|
|
115
|
+
const child = childComposition(
|
|
116
|
+
readRuntimeSlotChild({
|
|
117
|
+
runtime,
|
|
118
|
+
slotDefinition: slotClass,
|
|
119
|
+
parent: scene.composition,
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
return Object.is(compositionIdentity(child), compositionIdentity(expected))
|
|
123
|
+
})
|
|
124
|
+
if (matches) {
|
|
125
|
+
return Effect.void
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const location = proof.placement === 'Root' ? 'scene root' : 'direct scene slot'
|
|
129
|
+
return Effect.dieMessage(
|
|
130
|
+
`reform-proof: requirement composition ${expected.manifest.name} is not the ${location} by definition identity`,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const executeProofWithSink = Effect.fn('executeProofWithSink')(function* <
|
|
135
|
+
Comp extends ProductComposition,
|
|
136
|
+
C extends UiContract,
|
|
137
|
+
S extends ReadonlyArray<unknown>,
|
|
138
|
+
Services,
|
|
139
|
+
P,
|
|
140
|
+
N extends string,
|
|
141
|
+
Identity,
|
|
142
|
+
>(
|
|
143
|
+
proof: ProofCase<Comp, C, S, Services, P, N, Identity>,
|
|
144
|
+
scene: CapturedScene<C, S, Services, P, N, Identity>,
|
|
145
|
+
sink: Sink,
|
|
146
|
+
): Effect.fn.Return<ProofResult, never, never> {
|
|
147
|
+
const dispatched = new Set<string>()
|
|
148
|
+
const statement = proof.requirement.manifest.statement
|
|
149
|
+
const runGenerator = Effect.fn('runProofGenerator')(function* (
|
|
150
|
+
generator: ReturnType<typeof proof.body>,
|
|
151
|
+
input: unknown,
|
|
152
|
+
settleEffect: Effect.Effect<void, never, never>,
|
|
153
|
+
): Effect.fn.Return<void, unknown, never> {
|
|
154
|
+
const next = generator.next(input)
|
|
155
|
+
if (next.done === true) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
const output = yield* yieldWrapGet(next.value)
|
|
159
|
+
yield* settleEffect
|
|
160
|
+
yield* runGenerator(generator, output, settleEffect)
|
|
161
|
+
})
|
|
162
|
+
return yield* withProofRuntimeTree(scene, sink, dispatched, (runtime, tree) =>
|
|
163
|
+
Effect.gen(function* () {
|
|
164
|
+
yield* verifyProofPlacement(proof, scene, runtime)
|
|
165
|
+
yield* tree.settle
|
|
166
|
+
yield* runGenerator(proof.body(tree.facade), undefined, tree.settle)
|
|
167
|
+
const missing = missingCoverage(proof, dispatched)
|
|
168
|
+
const coverageError = `requirement declares events never dispatched: ${missing.join(', ')}`
|
|
169
|
+
return missing.length > 0
|
|
170
|
+
? { requirement: statement, ok: false, error: coverageError }
|
|
171
|
+
: { requirement: statement, ok: true, error: undefined }
|
|
172
|
+
}),
|
|
173
|
+
).pipe(
|
|
174
|
+
Effect.catchAllCause((cause) =>
|
|
175
|
+
Effect.succeed({
|
|
176
|
+
requirement: statement,
|
|
177
|
+
ok: false,
|
|
178
|
+
error: messageOf(Cause.squash(cause)),
|
|
179
|
+
}),
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
export const executeProofEffect = (proof: Proof): Effect.Effect<ProofResult, never, never> =>
|
|
185
|
+
proof.capture(
|
|
186
|
+
<
|
|
187
|
+
Comp extends ProductComposition,
|
|
188
|
+
C extends UiContract,
|
|
189
|
+
S extends ReadonlyArray<unknown>,
|
|
190
|
+
Services,
|
|
191
|
+
P,
|
|
192
|
+
N extends string,
|
|
193
|
+
Identity,
|
|
194
|
+
>(
|
|
195
|
+
exact: ProofCase<Comp, C, S, Services, P, N, Identity>,
|
|
196
|
+
): Effect.Effect<ProofResult, never, never> => {
|
|
197
|
+
const sink = makeSink(sceneInstrumentation(exact.scene))
|
|
198
|
+
return executeProofWithSink<Comp, C, S, Services, P, N, Identity>(exact, exact.scene, sink)
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
export const executeProof = (proof: Proof): Promise<ProofResult> =>
|
|
203
|
+
Effect.runPromise(executeProofEffect(proof))
|
|
204
|
+
|
|
205
|
+
const driveProofWithSink = Effect.fn('driveProofWithSink')(function* <
|
|
206
|
+
Comp extends ProductComposition,
|
|
207
|
+
C extends UiContract,
|
|
208
|
+
S extends ReadonlyArray<unknown>,
|
|
209
|
+
Services,
|
|
210
|
+
P,
|
|
211
|
+
N extends string,
|
|
212
|
+
Identity,
|
|
213
|
+
>(
|
|
214
|
+
proof: ProofCase<Comp, C, S, Services, P, N, Identity>,
|
|
215
|
+
scene: CapturedScene<C, S, Services, P, N, Identity>,
|
|
216
|
+
sink: Sink,
|
|
217
|
+
): Effect.fn.Return<DriveResult, never, never> {
|
|
218
|
+
const frames = yield* Ref.make<ReadonlyArray<StepFrame>>([])
|
|
219
|
+
const statement = proof.requirement.manifest.statement
|
|
220
|
+
// Driving tracks dispatches for the facade without enforcing coverage.
|
|
221
|
+
const dispatched = new Set<string>()
|
|
222
|
+
const record = (index: number): Effect.Effect<void> =>
|
|
223
|
+
Ref.update(frames, (current) => [...current, { index, captures: [...sink.captures] }])
|
|
224
|
+
const pump = Effect.fn('pump')(function* (
|
|
225
|
+
generator: ReturnType<typeof proof.body>,
|
|
226
|
+
step: PumpStep,
|
|
227
|
+
settleEffect: Effect.Effect<void, never, never>,
|
|
228
|
+
): Effect.fn.Return<void, unknown, never> {
|
|
229
|
+
const next = generator.next(step.input)
|
|
230
|
+
if (next.done === true) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
const output = yield* yieldWrapGet(next.value)
|
|
234
|
+
yield* settleEffect
|
|
235
|
+
yield* record(step.index)
|
|
236
|
+
yield* pump(generator, { input: output, index: step.index + 1 }, settleEffect)
|
|
237
|
+
})
|
|
238
|
+
return yield* withProofRuntimeTree(scene, sink, dispatched, (runtime, tree) =>
|
|
239
|
+
Effect.gen(function* () {
|
|
240
|
+
yield* verifyProofPlacement(proof, scene, runtime)
|
|
241
|
+
yield* tree.settle
|
|
242
|
+
yield* record(0)
|
|
243
|
+
yield* pump(proof.body(tree.facade), { input: undefined, index: 1 }, tree.settle)
|
|
244
|
+
return {
|
|
245
|
+
requirement: statement,
|
|
246
|
+
frames: yield* Ref.get(frames),
|
|
247
|
+
ok: true,
|
|
248
|
+
error: undefined,
|
|
249
|
+
}
|
|
250
|
+
}),
|
|
251
|
+
).pipe(
|
|
252
|
+
Effect.catchAllCause((cause) =>
|
|
253
|
+
Ref.get(frames).pipe(
|
|
254
|
+
Effect.map((collected) => ({
|
|
255
|
+
requirement: statement,
|
|
256
|
+
frames: collected,
|
|
257
|
+
ok: false,
|
|
258
|
+
error: messageOf(Cause.squash(cause)),
|
|
259
|
+
})),
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
export const driveProofEffect = (proof: Proof): Effect.Effect<DriveResult, never, never> =>
|
|
266
|
+
proof.capture(
|
|
267
|
+
<
|
|
268
|
+
Comp extends ProductComposition,
|
|
269
|
+
C extends UiContract,
|
|
270
|
+
S extends ReadonlyArray<unknown>,
|
|
271
|
+
Services,
|
|
272
|
+
P,
|
|
273
|
+
N extends string,
|
|
274
|
+
Identity,
|
|
275
|
+
>(
|
|
276
|
+
exact: ProofCase<Comp, C, S, Services, P, N, Identity>,
|
|
277
|
+
): Effect.Effect<DriveResult, never, never> => {
|
|
278
|
+
const sink = makeSink(sceneInstrumentation(exact.scene))
|
|
279
|
+
return driveProofWithSink<Comp, C, S, Services, P, N, Identity>(exact, exact.scene, sink)
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
export const driveProof = (proof: Proof): Promise<DriveResult> =>
|
|
284
|
+
Effect.runPromise(driveProofEffect(proof))
|
|
285
|
+
|