@dreamboard-games/sdk 0.5.0-alpha.2 → 0.5.0-alpha.3
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 +136 -45
- package/dist/chunk-DMFVAZI2.js +16 -0
- package/dist/chunk-DMFVAZI2.js.map +1 -0
- package/dist/chunk-FMM5LVJW.js +833 -0
- package/dist/chunk-FMM5LVJW.js.map +1 -0
- package/dist/{chunk-S2JEDVWH.js → chunk-LRILEVKC.js} +6113 -6489
- package/dist/chunk-LRILEVKC.js.map +1 -0
- package/dist/chunk-SP234IY4.js +1363 -0
- package/dist/chunk-SP234IY4.js.map +1 -0
- package/dist/chunk-VUWNJXLJ.js +52 -0
- package/dist/chunk-VUWNJXLJ.js.map +1 -0
- package/dist/chunk-ZPRKWPEP.js +511 -0
- package/dist/chunk-ZPRKWPEP.js.map +1 -0
- package/dist/definition-BaHNPZol.d.ts +4639 -0
- package/dist/diagnostics-B0lgnuZ6.d.ts +65 -0
- package/dist/index.d.ts +3553 -1
- package/dist/index.js +1045 -6
- package/dist/index.js.map +1 -1
- package/dist/model-BHro6yXu.d.ts +288 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.js +80 -0
- package/dist/react.js.map +1 -0
- package/dist/reducer.d.ts +1194 -79
- package/dist/reducer.js +4326 -121
- package/dist/reducer.js.map +1 -1
- package/dist/testing.d.ts +518 -564
- package/dist/testing.js +2448 -915
- package/dist/testing.js.map +1 -1
- package/dist/types-DimqnJBq.d.ts +86 -0
- package/package.json +26 -131
- package/dist/HandView-Bi2lxzQP.d.ts +0 -79
- package/dist/ResourceCounter-BFxJknqp.d.ts +0 -98
- package/dist/ThemeProvider-CElaL2ik.d.ts +0 -99
- package/dist/attributes-DbvyMbXw.d.ts +0 -68
- package/dist/browser-interaction.d.ts +0 -910
- package/dist/browser-interaction.js +0 -119
- package/dist/browser-interaction.js.map +0 -1
- package/dist/chunk-32PFKDV7.js +0 -11705
- package/dist/chunk-32PFKDV7.js.map +0 -1
- package/dist/chunk-3BAD4VC2.js +0 -263
- package/dist/chunk-3BAD4VC2.js.map +0 -1
- package/dist/chunk-3SKDNDPG.js +0 -104
- package/dist/chunk-3SKDNDPG.js.map +0 -1
- package/dist/chunk-5YV2WHJ4.js +0 -25
- package/dist/chunk-5YV2WHJ4.js.map +0 -1
- package/dist/chunk-ANRB4XKA.js +0 -90
- package/dist/chunk-ANRB4XKA.js.map +0 -1
- package/dist/chunk-BI4G3PM2.js +0 -1864
- package/dist/chunk-BI4G3PM2.js.map +0 -1
- package/dist/chunk-EVCLTY4Q.js +0 -935
- package/dist/chunk-EVCLTY4Q.js.map +0 -1
- package/dist/chunk-GDPKTBUR.js +0 -564
- package/dist/chunk-GDPKTBUR.js.map +0 -1
- package/dist/chunk-H6VDGFL5.js +0 -2903
- package/dist/chunk-H6VDGFL5.js.map +0 -1
- package/dist/chunk-LR3ZTWQF.js +0 -5960
- package/dist/chunk-LR3ZTWQF.js.map +0 -1
- package/dist/chunk-PF7L4BMG.js +0 -117
- package/dist/chunk-PF7L4BMG.js.map +0 -1
- package/dist/chunk-PN5O6GG2.js +0 -32
- package/dist/chunk-PN5O6GG2.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-S2JEDVWH.js.map +0 -1
- package/dist/chunk-T3ZKNUZ7.js +0 -1
- package/dist/chunk-T3ZKNUZ7.js.map +0 -1
- package/dist/chunk-UGH54WZ2.js +0 -223
- package/dist/chunk-UGH54WZ2.js.map +0 -1
- package/dist/chunk-VCDWP5VL.js +0 -746
- package/dist/chunk-VCDWP5VL.js.map +0 -1
- package/dist/chunk-VDXOF4FW.js +0 -69
- package/dist/chunk-VDXOF4FW.js.map +0 -1
- package/dist/chunk-WHR5UW3F.js +0 -1988
- package/dist/chunk-WHR5UW3F.js.map +0 -1
- package/dist/chunk-Y75CFE77.js +0 -17
- package/dist/chunk-Y75CFE77.js.map +0 -1
- package/dist/chunk-Z7QCREWI.js +0 -3435
- package/dist/chunk-Z7QCREWI.js.map +0 -1
- package/dist/chunk-ZZGN2ATS.js +0 -1941
- package/dist/chunk-ZZGN2ATS.js.map +0 -1
- package/dist/components-gAbIz7hK.d.ts +0 -1284
- package/dist/definitions-BgmV_GhC.d.ts +0 -300
- package/dist/diagnostics-DIi3ina7.d.ts +0 -50
- package/dist/digest.d.ts +0 -8
- package/dist/game-CsZScpcn.d.ts +0 -763
- package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
- package/dist/index-CcT9Q7N7.d.ts +0 -193
- package/dist/index.d-DjzoK7zn.d.ts +0 -2101
- package/dist/package-set.d.ts +0 -13
- package/dist/package-set.js +0 -12
- package/dist/package-set.js.map +0 -1
- package/dist/player-state-Cqpyeql0.d.ts +0 -371
- package/dist/plugin-runtime-contract.d.ts +0 -17
- package/dist/plugin-runtime-contract.js +0 -92
- package/dist/plugin-runtime-contract.js.map +0 -1
- package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
- package/dist/protocol-dYgafTYY.d.ts +0 -314
- package/dist/reducer/advanced.d.ts +0 -82
- package/dist/reducer/advanced.js +0 -51
- package/dist/reducer/advanced.js.map +0 -1
- package/dist/reducer-contract.d.ts +0 -11
- package/dist/reducer-contract.js +0 -16
- package/dist/reducer-contract.js.map +0 -1
- package/dist/reference-games/index.d.ts +0 -31
- package/dist/reference-games/index.js +0 -48
- package/dist/reference-games/index.js.map +0 -1
- package/dist/runtime/primitives.d.ts +0 -250
- package/dist/runtime/primitives.js +0 -189
- package/dist/runtime/primitives.js.map +0 -1
- package/dist/runtime/runtime-api.d.ts +0 -2
- package/dist/runtime/runtime-api.js +0 -1
- package/dist/runtime/runtime-api.js.map +0 -1
- package/dist/runtime/workspace-contract.d.ts +0 -392
- package/dist/runtime/workspace-contract.js +0 -28
- package/dist/runtime/workspace-contract.js.map +0 -1
- package/dist/runtime-RJ5orDJU.d.ts +0 -1699
- package/dist/runtime-api-Bz5pwNU_.d.ts +0 -296
- package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
- package/dist/runtime.d.ts +0 -83
- package/dist/runtime.js +0 -242
- package/dist/runtime.js.map +0 -1
- package/dist/schema.d.ts +0 -379
- package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
- package/dist/testing-compiler.d.ts +0 -37
- package/dist/testing-compiler.js +0 -272
- package/dist/testing-compiler.js.map +0 -1
- package/dist/testing-runtime.d.ts +0 -119
- package/dist/testing-runtime.js +0 -212
- package/dist/testing-runtime.js.map +0 -1
- package/dist/types-DJj5MJkl.d.ts +0 -256
- package/dist/types-DR7DoB1x.d.ts +0 -28
- package/dist/types-JWCYHmu7.d.ts +0 -122
- package/dist/types.d.ts +0 -1958
- package/dist/types.js +0 -14
- package/dist/types.js.map +0 -1
- package/dist/ui/components.d.ts +0 -17
- package/dist/ui/components.js +0 -216
- package/dist/ui/components.js.map +0 -1
- package/dist/ui/defaults.d.ts +0 -19
- package/dist/ui/defaults.js +0 -104
- package/dist/ui/defaults.js.map +0 -1
- package/dist/ui/player-state.d.ts +0 -2
- package/dist/ui/player-state.js +0 -1
- package/dist/ui/player-state.js.map +0 -1
- package/dist/ui/plugin-styles.css +0 -2
- package/dist/ui-contract-GTUPkf8z.d.ts +0 -1168
- package/dist/ui.d.ts +0 -317
- package/dist/ui.js +0 -277
- package/dist/ui.js.map +0 -1
- package/dist/views-BpuWcOyN.d.ts +0 -1641
package/dist/testing-compiler.js
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import "./chunk-PZ5AY32C.js";
|
|
2
|
-
|
|
3
|
-
// src/testing/compile-scenario-replay.ts
|
|
4
|
-
import { createHash } from "crypto";
|
|
5
|
-
import { createRequire } from "module";
|
|
6
|
-
import {
|
|
7
|
-
lstat,
|
|
8
|
-
mkdtemp,
|
|
9
|
-
readFile,
|
|
10
|
-
realpath,
|
|
11
|
-
rm,
|
|
12
|
-
stat,
|
|
13
|
-
writeFile
|
|
14
|
-
} from "fs/promises";
|
|
15
|
-
import { tmpdir } from "os";
|
|
16
|
-
import path from "path";
|
|
17
|
-
import { pathToFileURL } from "url";
|
|
18
|
-
import { build } from "esbuild";
|
|
19
|
-
var scenarioSourceDigestVersion = "dreamboard-scenario-source-closure@1";
|
|
20
|
-
var syntheticSourceFile = "__dreamboard_compile_scenario_replay__.ts";
|
|
21
|
-
async function compileScenarioReplay(options) {
|
|
22
|
-
const source = await resolveScenarioSource(options.scenarioPath);
|
|
23
|
-
const syntheticSource = buildSyntheticSource(source);
|
|
24
|
-
const [
|
|
25
|
-
reducerEntry,
|
|
26
|
-
reducerAdvancedEntry,
|
|
27
|
-
testingEntry,
|
|
28
|
-
testingRuntimeEntry,
|
|
29
|
-
typesEntry
|
|
30
|
-
] = await Promise.all([
|
|
31
|
-
resolveCurrentSdkFacade("reducer"),
|
|
32
|
-
resolveCurrentSdkFacade("reducer/advanced"),
|
|
33
|
-
resolveCurrentSdkFacade("testing"),
|
|
34
|
-
resolveCurrentSdkFacade("testing-runtime"),
|
|
35
|
-
resolveCurrentSdkFacade("types")
|
|
36
|
-
]);
|
|
37
|
-
const bundled = await build({
|
|
38
|
-
absWorkingDir: source.projectRoot,
|
|
39
|
-
stdin: {
|
|
40
|
-
contents: syntheticSource,
|
|
41
|
-
loader: "ts",
|
|
42
|
-
resolveDir: source.projectRoot,
|
|
43
|
-
sourcefile: syntheticSourceFile
|
|
44
|
-
},
|
|
45
|
-
bundle: true,
|
|
46
|
-
alias: {
|
|
47
|
-
"@dreamboard-games/sdk/reducer": reducerEntry,
|
|
48
|
-
"@dreamboard-games/sdk/reducer/advanced": reducerAdvancedEntry,
|
|
49
|
-
"@dreamboard-games/sdk/testing": testingEntry,
|
|
50
|
-
"@dreamboard-games/sdk/testing-runtime": testingRuntimeEntry,
|
|
51
|
-
"@dreamboard-games/sdk/types": typesEntry,
|
|
52
|
-
zod: createRequire(import.meta.url).resolve("zod")
|
|
53
|
-
},
|
|
54
|
-
format: "esm",
|
|
55
|
-
platform: "node",
|
|
56
|
-
target: "node24",
|
|
57
|
-
sourcemap: "inline",
|
|
58
|
-
metafile: true,
|
|
59
|
-
write: false,
|
|
60
|
-
logLevel: "silent"
|
|
61
|
-
});
|
|
62
|
-
const output = bundled.outputFiles?.[0];
|
|
63
|
-
if (!output || !bundled.metafile) {
|
|
64
|
-
throw new Error("Scenario replay compilation did not produce a bundle.");
|
|
65
|
-
}
|
|
66
|
-
const sourceDigest = await digestSourceClosure({
|
|
67
|
-
projectRoot: source.projectRoot,
|
|
68
|
-
inputPaths: Object.keys(bundled.metafile.inputs)
|
|
69
|
-
});
|
|
70
|
-
const tempRoot = await mkdtemp(
|
|
71
|
-
path.join(tmpdir(), "dreamboard-compiled-scenario-")
|
|
72
|
-
);
|
|
73
|
-
const outputPath = path.join(tempRoot, "compiled-scenario.mjs");
|
|
74
|
-
try {
|
|
75
|
-
await writeFile(outputPath, output.text);
|
|
76
|
-
const loaded = await import(`${pathToFileURL(outputPath).href}?source=${sourceDigest.slice(7, 19)}`);
|
|
77
|
-
if (typeof loaded.compile !== "function") {
|
|
78
|
-
throw new Error("Scenario replay bundle is missing compile().");
|
|
79
|
-
}
|
|
80
|
-
const materialized = await loaded.compile({
|
|
81
|
-
...options.at === void 0 ? {} : { at: options.at },
|
|
82
|
-
scenarioPath: source.relativeScenarioPath,
|
|
83
|
-
sourceDigest
|
|
84
|
-
});
|
|
85
|
-
const compiled = {
|
|
86
|
-
schemaVersion: 1,
|
|
87
|
-
scenario: {
|
|
88
|
-
path: source.relativeScenarioPath,
|
|
89
|
-
sourceDigest
|
|
90
|
-
},
|
|
91
|
-
definition: materialized.definition,
|
|
92
|
-
checkpoint: materialized.checkpoint,
|
|
93
|
-
expected: materialized.expected
|
|
94
|
-
};
|
|
95
|
-
return assertSerializableCompiledReplay(compiled);
|
|
96
|
-
} finally {
|
|
97
|
-
await rm(tempRoot, { recursive: true, force: true });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async function resolveCurrentSdkFacade(name) {
|
|
101
|
-
const candidates = [
|
|
102
|
-
path.resolve(import.meta.dirname, `../${name}.ts`),
|
|
103
|
-
path.resolve(import.meta.dirname, `${name}.js`)
|
|
104
|
-
];
|
|
105
|
-
for (const candidate of candidates) {
|
|
106
|
-
if (await isFile(candidate)) return candidate;
|
|
107
|
-
}
|
|
108
|
-
throw new Error(
|
|
109
|
-
`Could not resolve the current @dreamboard-games/sdk/${name} facade from ${import.meta.dirname}.`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
async function resolveScenarioSource(requestedPath) {
|
|
113
|
-
if (typeof requestedPath !== "string" || requestedPath.length === 0) {
|
|
114
|
-
throw new Error("scenarioPath must be a non-empty string.");
|
|
115
|
-
}
|
|
116
|
-
const requestedAbsolute = path.resolve(process.cwd(), requestedPath);
|
|
117
|
-
const requestedStats = await lstat(requestedAbsolute).catch((error) => {
|
|
118
|
-
if (isMissingPathError(error)) return null;
|
|
119
|
-
throw error;
|
|
120
|
-
});
|
|
121
|
-
if (!requestedStats?.isFile()) {
|
|
122
|
-
throw new Error(`Scenario path does not name a file: ${requestedPath}.`);
|
|
123
|
-
}
|
|
124
|
-
const scenarioPath = await realpath(requestedAbsolute);
|
|
125
|
-
const projectRoot = await findProjectRoot(path.dirname(scenarioPath));
|
|
126
|
-
const canonicalProjectRoot = await realpath(projectRoot);
|
|
127
|
-
const scenarioRoot = path.join(canonicalProjectRoot, "test", "scenarios");
|
|
128
|
-
const canonicalScenarioRoot = await realpath(scenarioRoot).catch((error) => {
|
|
129
|
-
if (isMissingPathError(error)) return null;
|
|
130
|
-
throw error;
|
|
131
|
-
});
|
|
132
|
-
if (canonicalScenarioRoot === null || !isWithinDirectory(canonicalScenarioRoot, scenarioPath) || !scenarioPath.endsWith(".scenario.ts")) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
"scenarioPath must resolve to test/scenarios/**/*.scenario.ts inside one game package."
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
const gamePath = path.join(canonicalProjectRoot, "app", "game.ts");
|
|
138
|
-
if (!await isFile(gamePath)) {
|
|
139
|
-
throw new Error(`Game package is missing ${gamePath}.`);
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
projectRoot: canonicalProjectRoot,
|
|
143
|
-
scenarioPath,
|
|
144
|
-
relativeScenarioPath: toPosix(
|
|
145
|
-
path.relative(canonicalProjectRoot, scenarioPath)
|
|
146
|
-
),
|
|
147
|
-
gamePath
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
async function findProjectRoot(start) {
|
|
151
|
-
let current = path.resolve(start);
|
|
152
|
-
for (; ; ) {
|
|
153
|
-
if (await isFile(path.join(current, "package.json")) && await isFile(path.join(current, "app", "game.ts"))) {
|
|
154
|
-
return current;
|
|
155
|
-
}
|
|
156
|
-
const parent = path.dirname(current);
|
|
157
|
-
if (parent === current) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
"Could not find a game package containing package.json and app/game.ts."
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
current = parent;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function buildSyntheticSource(source) {
|
|
166
|
-
const gameSpecifier = localImportSpecifier(
|
|
167
|
-
source.projectRoot,
|
|
168
|
-
source.gamePath
|
|
169
|
-
);
|
|
170
|
-
const scenarioSpecifier = localImportSpecifier(
|
|
171
|
-
source.projectRoot,
|
|
172
|
-
source.scenarioPath
|
|
173
|
-
);
|
|
174
|
-
return [
|
|
175
|
-
`import * as gameModule from ${JSON.stringify(gameSpecifier)};`,
|
|
176
|
-
`import * as scenarioModule from ${JSON.stringify(scenarioSpecifier)};`,
|
|
177
|
-
'import { inspectScenario, toScenarioReplayDefinition } from "@dreamboard-games/sdk/testing";',
|
|
178
|
-
'import { digestScenarioProjection, scenarioProjectionParityFromInspectNode } from "@dreamboard-games/sdk/testing-runtime";',
|
|
179
|
-
"const game = gameModule.game ?? gameModule.default;",
|
|
180
|
-
"const scenario = scenarioModule.scenario ?? scenarioModule.default;",
|
|
181
|
-
"export async function compile(options) {",
|
|
182
|
-
" if (!game || !scenario) {",
|
|
183
|
-
' throw new Error("Compiled scenario entry is missing game or scenario authority.");',
|
|
184
|
-
" }",
|
|
185
|
-
" const definition = toScenarioReplayDefinition(scenario);",
|
|
186
|
-
" const checkpoint = typeof options.at === 'string' ? scenario.checkpoints?.[options.at] : options.at;",
|
|
187
|
-
" if (typeof options.at === 'string' && checkpoint === undefined) {",
|
|
188
|
-
" const available = Object.keys(scenario.checkpoints ?? {}).sort();",
|
|
189
|
-
" throw new Error(`Unknown checkpoint '${options.at}'; available checkpoints: ${available.join(', ') || 'none'}.`);",
|
|
190
|
-
" }",
|
|
191
|
-
" const inspected = await inspectScenario({",
|
|
192
|
-
" game,",
|
|
193
|
-
" scenario: definition,",
|
|
194
|
-
" identity: {",
|
|
195
|
-
" id: definition.id,",
|
|
196
|
-
" path: options.scenarioPath,",
|
|
197
|
-
" sourceDigest: options.sourceDigest,",
|
|
198
|
-
" },",
|
|
199
|
-
' perspective: { kind: "spectator" },',
|
|
200
|
-
" ...(checkpoint === undefined ? {} : { at: checkpoint }),",
|
|
201
|
-
" });",
|
|
202
|
-
" return {",
|
|
203
|
-
" definition,",
|
|
204
|
-
" checkpoint: inspected.node.checkpoint,",
|
|
205
|
-
" expected: {",
|
|
206
|
-
" checkpointDigest: inspected.node.checkpointDigest,",
|
|
207
|
-
" publicProjectionDigest: digestScenarioProjection(",
|
|
208
|
-
" scenarioProjectionParityFromInspectNode(inspected.node),",
|
|
209
|
-
" ),",
|
|
210
|
-
" },",
|
|
211
|
-
" };",
|
|
212
|
-
"}"
|
|
213
|
-
].join("\n");
|
|
214
|
-
}
|
|
215
|
-
async function digestSourceClosure(options) {
|
|
216
|
-
const syntheticPath = path.resolve(options.projectRoot, syntheticSourceFile);
|
|
217
|
-
const localPaths = options.inputPaths.map(
|
|
218
|
-
(inputPath) => path.isAbsolute(inputPath) ? inputPath : path.resolve(options.projectRoot, inputPath)
|
|
219
|
-
).filter(
|
|
220
|
-
(inputPath) => inputPath !== syntheticPath && isWithinDirectory(options.projectRoot, inputPath) && !toPosix(path.relative(options.projectRoot, inputPath)).split("/").includes("node_modules")
|
|
221
|
-
);
|
|
222
|
-
const inputs = await Promise.all(
|
|
223
|
-
[...new Set(localPaths)].map(async (inputPath) => {
|
|
224
|
-
const content = await readFile(inputPath);
|
|
225
|
-
return {
|
|
226
|
-
path: toPosix(path.relative(options.projectRoot, inputPath)),
|
|
227
|
-
sha256: createHash("sha256").update(content).digest("hex")
|
|
228
|
-
};
|
|
229
|
-
})
|
|
230
|
-
);
|
|
231
|
-
inputs.sort(
|
|
232
|
-
(left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0
|
|
233
|
-
);
|
|
234
|
-
return `sha256:${createHash("sha256").update(
|
|
235
|
-
JSON.stringify({
|
|
236
|
-
digestVersion: scenarioSourceDigestVersion,
|
|
237
|
-
inputs
|
|
238
|
-
})
|
|
239
|
-
).digest("hex")}`;
|
|
240
|
-
}
|
|
241
|
-
function assertSerializableCompiledReplay(value) {
|
|
242
|
-
const serialized = JSON.stringify(value);
|
|
243
|
-
const parsed = JSON.parse(serialized);
|
|
244
|
-
if (parsed.schemaVersion !== 1 || !/^sha256:[a-f0-9]{64}$/.test(parsed.scenario.sourceDigest) || !/^sha256:[a-f0-9]{64}$/.test(parsed.expected.checkpointDigest) || !/^sha256:[a-f0-9]{64}$/.test(parsed.expected.publicProjectionDigest)) {
|
|
245
|
-
throw new Error("Compiled scenario replay failed digest validation.");
|
|
246
|
-
}
|
|
247
|
-
return parsed;
|
|
248
|
-
}
|
|
249
|
-
function localImportSpecifier(projectRoot, filePath) {
|
|
250
|
-
const relative = toPosix(path.relative(projectRoot, filePath));
|
|
251
|
-
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
252
|
-
}
|
|
253
|
-
function isWithinDirectory(directory, filePath) {
|
|
254
|
-
const relative = path.relative(directory, filePath);
|
|
255
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
256
|
-
}
|
|
257
|
-
async function isFile(filePath) {
|
|
258
|
-
return stat(filePath).then((value) => value.isFile()).catch((error) => {
|
|
259
|
-
if (isMissingPathError(error)) return false;
|
|
260
|
-
throw error;
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
function isMissingPathError(error) {
|
|
264
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
265
|
-
}
|
|
266
|
-
function toPosix(value) {
|
|
267
|
-
return value.split(path.sep).join("/");
|
|
268
|
-
}
|
|
269
|
-
export {
|
|
270
|
-
compileScenarioReplay
|
|
271
|
-
};
|
|
272
|
-
//# sourceMappingURL=testing-compiler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/compile-scenario-replay.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { createRequire } from \"node:module\";\nimport {\n lstat,\n mkdtemp,\n readFile,\n realpath,\n rm,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { build } from \"esbuild\";\nimport type {\n ScenarioCheckpoint,\n ScenarioCheckpointSelector,\n ScenarioReplayDefinition,\n} from \"./definitions.js\";\nimport type { ScenarioDefinitionGameLike } from \"./scenario-definition-validation.js\";\n\nexport type CompiledScenarioReplay<\n Game extends ScenarioDefinitionGameLike = ScenarioDefinitionGameLike,\n> = {\n readonly schemaVersion: 1;\n readonly scenario: {\n readonly path: string;\n /** Covers the local game/scenario module graph, including assertions. */\n readonly sourceDigest: `sha256:${string}`;\n };\n /** Canonical serializable scenario projection; never contains `then`. */\n readonly definition: ScenarioReplayDefinition<Game>;\n readonly checkpoint: ScenarioCheckpoint;\n readonly expected: {\n readonly checkpointDigest: `sha256:${string}`;\n readonly publicProjectionDigest: `sha256:${string}`;\n };\n};\n\nexport type CompileScenarioReplayOptions = {\n readonly scenarioPath: string;\n readonly at?: ScenarioCheckpointSelector;\n};\n\ntype SyntheticCompileResult = Omit<\n CompiledScenarioReplay,\n \"schemaVersion\" | \"scenario\"\n>;\n\ntype SyntheticCompileModule = {\n readonly compile: (options: {\n readonly at?: ScenarioCheckpointSelector;\n readonly scenarioPath: string;\n readonly sourceDigest: `sha256:${string}`;\n }) => Promise<SyntheticCompileResult>;\n};\n\ntype ScenarioSource = {\n readonly projectRoot: string;\n readonly scenarioPath: string;\n readonly relativeScenarioPath: string;\n readonly gamePath: string;\n};\n\nconst scenarioSourceDigestVersion = \"dreamboard-scenario-source-closure@1\";\nconst syntheticSourceFile = \"__dreamboard_compile_scenario_replay__.ts\";\n\n/**\n * Compile one authored scenario into the trusted, serializable replay DTO used\n * by dev-host, Workbench, demo, and performance adapters.\n *\n * The DTO may contain sealed/private command parameters. It must never cross a\n * player, spectator, browser, or ordinary-log boundary.\n */\nexport async function compileScenarioReplay(\n options: CompileScenarioReplayOptions,\n): Promise<CompiledScenarioReplay> {\n const source = await resolveScenarioSource(options.scenarioPath);\n const syntheticSource = buildSyntheticSource(source);\n const [\n reducerEntry,\n reducerAdvancedEntry,\n testingEntry,\n testingRuntimeEntry,\n typesEntry,\n ] = await Promise.all([\n resolveCurrentSdkFacade(\"reducer\"),\n resolveCurrentSdkFacade(\"reducer/advanced\"),\n resolveCurrentSdkFacade(\"testing\"),\n resolveCurrentSdkFacade(\"testing-runtime\"),\n resolveCurrentSdkFacade(\"types\"),\n ]);\n const bundled = await build({\n absWorkingDir: source.projectRoot,\n stdin: {\n contents: syntheticSource,\n loader: \"ts\",\n resolveDir: source.projectRoot,\n sourcefile: syntheticSourceFile,\n },\n bundle: true,\n alias: {\n \"@dreamboard-games/sdk/reducer\": reducerEntry,\n \"@dreamboard-games/sdk/reducer/advanced\": reducerAdvancedEntry,\n \"@dreamboard-games/sdk/testing\": testingEntry,\n \"@dreamboard-games/sdk/testing-runtime\": testingRuntimeEntry,\n \"@dreamboard-games/sdk/types\": typesEntry,\n zod: createRequire(import.meta.url).resolve(\"zod\"),\n },\n format: \"esm\",\n platform: \"node\",\n target: \"node24\",\n sourcemap: \"inline\",\n metafile: true,\n write: false,\n logLevel: \"silent\",\n });\n const output = bundled.outputFiles?.[0];\n if (!output || !bundled.metafile) {\n throw new Error(\"Scenario replay compilation did not produce a bundle.\");\n }\n const sourceDigest = await digestSourceClosure({\n projectRoot: source.projectRoot,\n inputPaths: Object.keys(bundled.metafile.inputs),\n });\n\n const tempRoot = await mkdtemp(\n path.join(tmpdir(), \"dreamboard-compiled-scenario-\"),\n );\n const outputPath = path.join(tempRoot, \"compiled-scenario.mjs\");\n try {\n await writeFile(outputPath, output.text);\n const loaded = (await import(\n `${pathToFileURL(outputPath).href}?source=${sourceDigest.slice(7, 19)}`\n )) as SyntheticCompileModule;\n if (typeof loaded.compile !== \"function\") {\n throw new Error(\"Scenario replay bundle is missing compile().\");\n }\n const materialized = await loaded.compile({\n ...(options.at === undefined ? {} : { at: options.at }),\n scenarioPath: source.relativeScenarioPath,\n sourceDigest,\n });\n const compiled = {\n schemaVersion: 1,\n scenario: {\n path: source.relativeScenarioPath,\n sourceDigest,\n },\n definition: materialized.definition,\n checkpoint: materialized.checkpoint,\n expected: materialized.expected,\n } satisfies CompiledScenarioReplay;\n return assertSerializableCompiledReplay(compiled);\n } finally {\n await rm(tempRoot, { recursive: true, force: true });\n }\n}\n\nasync function resolveCurrentSdkFacade(name: string): Promise<string> {\n const candidates = [\n path.resolve(import.meta.dirname, `../${name}.ts`),\n path.resolve(import.meta.dirname, `${name}.js`),\n ];\n for (const candidate of candidates) {\n if (await isFile(candidate)) return candidate;\n }\n throw new Error(\n `Could not resolve the current @dreamboard-games/sdk/${name} facade from ${import.meta.dirname}.`,\n );\n}\n\nasync function resolveScenarioSource(\n requestedPath: string,\n): Promise<ScenarioSource> {\n if (typeof requestedPath !== \"string\" || requestedPath.length === 0) {\n throw new Error(\"scenarioPath must be a non-empty string.\");\n }\n const requestedAbsolute = path.resolve(process.cwd(), requestedPath);\n const requestedStats = await lstat(requestedAbsolute).catch((error) => {\n if (isMissingPathError(error)) return null;\n throw error;\n });\n if (!requestedStats?.isFile()) {\n throw new Error(`Scenario path does not name a file: ${requestedPath}.`);\n }\n const scenarioPath = await realpath(requestedAbsolute);\n const projectRoot = await findProjectRoot(path.dirname(scenarioPath));\n const canonicalProjectRoot = await realpath(projectRoot);\n const scenarioRoot = path.join(canonicalProjectRoot, \"test\", \"scenarios\");\n const canonicalScenarioRoot = await realpath(scenarioRoot).catch((error) => {\n if (isMissingPathError(error)) return null;\n throw error;\n });\n if (\n canonicalScenarioRoot === null ||\n !isWithinDirectory(canonicalScenarioRoot, scenarioPath) ||\n !scenarioPath.endsWith(\".scenario.ts\")\n ) {\n throw new Error(\n \"scenarioPath must resolve to test/scenarios/**/*.scenario.ts inside one game package.\",\n );\n }\n const gamePath = path.join(canonicalProjectRoot, \"app\", \"game.ts\");\n if (!(await isFile(gamePath))) {\n throw new Error(`Game package is missing ${gamePath}.`);\n }\n return {\n projectRoot: canonicalProjectRoot,\n scenarioPath,\n relativeScenarioPath: toPosix(\n path.relative(canonicalProjectRoot, scenarioPath),\n ),\n gamePath,\n };\n}\n\nasync function findProjectRoot(start: string): Promise<string> {\n let current = path.resolve(start);\n for (;;) {\n if (\n (await isFile(path.join(current, \"package.json\"))) &&\n (await isFile(path.join(current, \"app\", \"game.ts\")))\n ) {\n return current;\n }\n const parent = path.dirname(current);\n if (parent === current) {\n throw new Error(\n \"Could not find a game package containing package.json and app/game.ts.\",\n );\n }\n current = parent;\n }\n}\n\nfunction buildSyntheticSource(source: ScenarioSource): string {\n const gameSpecifier = localImportSpecifier(\n source.projectRoot,\n source.gamePath,\n );\n const scenarioSpecifier = localImportSpecifier(\n source.projectRoot,\n source.scenarioPath,\n );\n return [\n `import * as gameModule from ${JSON.stringify(gameSpecifier)};`,\n `import * as scenarioModule from ${JSON.stringify(scenarioSpecifier)};`,\n 'import { inspectScenario, toScenarioReplayDefinition } from \"@dreamboard-games/sdk/testing\";',\n 'import { digestScenarioProjection, scenarioProjectionParityFromInspectNode } from \"@dreamboard-games/sdk/testing-runtime\";',\n \"const game = gameModule.game ?? gameModule.default;\",\n \"const scenario = scenarioModule.scenario ?? scenarioModule.default;\",\n \"export async function compile(options) {\",\n \" if (!game || !scenario) {\",\n ' throw new Error(\"Compiled scenario entry is missing game or scenario authority.\");',\n \" }\",\n \" const definition = toScenarioReplayDefinition(scenario);\",\n \" const checkpoint = typeof options.at === 'string' ? scenario.checkpoints?.[options.at] : options.at;\",\n \" if (typeof options.at === 'string' && checkpoint === undefined) {\",\n \" const available = Object.keys(scenario.checkpoints ?? {}).sort();\",\n \" throw new Error(`Unknown checkpoint '${options.at}'; available checkpoints: ${available.join(', ') || 'none'}.`);\",\n \" }\",\n \" const inspected = await inspectScenario({\",\n \" game,\",\n \" scenario: definition,\",\n \" identity: {\",\n \" id: definition.id,\",\n \" path: options.scenarioPath,\",\n \" sourceDigest: options.sourceDigest,\",\n \" },\",\n ' perspective: { kind: \"spectator\" },',\n \" ...(checkpoint === undefined ? {} : { at: checkpoint }),\",\n \" });\",\n \" return {\",\n \" definition,\",\n \" checkpoint: inspected.node.checkpoint,\",\n \" expected: {\",\n \" checkpointDigest: inspected.node.checkpointDigest,\",\n \" publicProjectionDigest: digestScenarioProjection(\",\n \" scenarioProjectionParityFromInspectNode(inspected.node),\",\n \" ),\",\n \" },\",\n \" };\",\n \"}\",\n ].join(\"\\n\");\n}\n\nasync function digestSourceClosure(options: {\n readonly projectRoot: string;\n readonly inputPaths: readonly string[];\n}): Promise<`sha256:${string}`> {\n const syntheticPath = path.resolve(options.projectRoot, syntheticSourceFile);\n const localPaths = options.inputPaths\n .map((inputPath) =>\n path.isAbsolute(inputPath)\n ? inputPath\n : path.resolve(options.projectRoot, inputPath),\n )\n .filter(\n (inputPath) =>\n inputPath !== syntheticPath &&\n isWithinDirectory(options.projectRoot, inputPath) &&\n !toPosix(path.relative(options.projectRoot, inputPath))\n .split(\"/\")\n .includes(\"node_modules\"),\n );\n const inputs = await Promise.all(\n [...new Set(localPaths)].map(async (inputPath) => {\n const content = await readFile(inputPath);\n return {\n path: toPosix(path.relative(options.projectRoot, inputPath)),\n sha256: createHash(\"sha256\").update(content).digest(\"hex\"),\n };\n }),\n );\n inputs.sort((left, right) =>\n left.path < right.path ? -1 : left.path > right.path ? 1 : 0,\n );\n return `sha256:${createHash(\"sha256\")\n .update(\n JSON.stringify({\n digestVersion: scenarioSourceDigestVersion,\n inputs,\n }),\n )\n .digest(\"hex\")}`;\n}\n\nfunction assertSerializableCompiledReplay(\n value: CompiledScenarioReplay,\n): CompiledScenarioReplay {\n const serialized = JSON.stringify(value);\n const parsed = JSON.parse(serialized) as CompiledScenarioReplay;\n if (\n parsed.schemaVersion !== 1 ||\n !/^sha256:[a-f0-9]{64}$/.test(parsed.scenario.sourceDigest) ||\n !/^sha256:[a-f0-9]{64}$/.test(parsed.expected.checkpointDigest) ||\n !/^sha256:[a-f0-9]{64}$/.test(parsed.expected.publicProjectionDigest)\n ) {\n throw new Error(\"Compiled scenario replay failed digest validation.\");\n }\n return parsed;\n}\n\nfunction localImportSpecifier(projectRoot: string, filePath: string): string {\n const relative = toPosix(path.relative(projectRoot, filePath));\n return relative.startsWith(\".\") ? relative : `./${relative}`;\n}\n\nfunction isWithinDirectory(directory: string, filePath: string): boolean {\n const relative = path.relative(directory, filePath);\n return (\n relative !== \"\" &&\n relative !== \"..\" &&\n !relative.startsWith(`..${path.sep}`) &&\n !path.isAbsolute(relative)\n );\n}\n\nasync function isFile(filePath: string): Promise<boolean> {\n return stat(filePath)\n .then((value) => value.isFile())\n .catch((error) => {\n if (isMissingPathError(error)) return false;\n throw error;\n });\n}\n\nfunction isMissingPathError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { readonly code?: unknown }).code === \"ENOENT\"\n );\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { D as DispatchTraceSummaryEntry } from './diagnostics-DIi3ina7.js';
|
|
2
|
-
import { o as ScenarioDefinitionGameLike, n as ScenarioDefinition } from './definitions-BgmV_GhC.js';
|
|
3
|
-
import { i as Sha256Digest, d as InspectNode } from './types-JWCYHmu7.js';
|
|
4
|
-
export { h as ScenarioRuntimeCheckpointMaterialization, m as materializeScenarioRuntimeCheckpoint } from './types-JWCYHmu7.js';
|
|
5
|
-
import 'zod';
|
|
6
|
-
import './views-BpuWcOyN.js';
|
|
7
|
-
import './runtime-RJ5orDJU.js';
|
|
8
|
-
import './types.js';
|
|
9
|
-
import './protocol-dYgafTYY.js';
|
|
10
|
-
import './index.d-DjzoK7zn.js';
|
|
11
|
-
|
|
12
|
-
type GameLike = {
|
|
13
|
-
readonly phases: Readonly<Record<string, unknown>>;
|
|
14
|
-
};
|
|
15
|
-
declare function resolveScenarioCommandParams(options: {
|
|
16
|
-
readonly game: GameLike;
|
|
17
|
-
readonly phase: string;
|
|
18
|
-
readonly interactionId: string;
|
|
19
|
-
readonly params: unknown;
|
|
20
|
-
readonly playerIds: readonly string[];
|
|
21
|
-
readonly path: string;
|
|
22
|
-
}): Record<string, unknown>;
|
|
23
|
-
|
|
24
|
-
type ScenarioProjectionInteractionInput = {
|
|
25
|
-
readonly key: string;
|
|
26
|
-
readonly kind: string;
|
|
27
|
-
readonly domain: unknown;
|
|
28
|
-
};
|
|
29
|
-
/** Shared SDK normalization for inspect DTOs and backend parity projections. */
|
|
30
|
-
declare function scenarioProjectionInputMetadata(input: ScenarioProjectionInteractionInput): {
|
|
31
|
-
readonly key: string;
|
|
32
|
-
readonly kind: string;
|
|
33
|
-
readonly eligibleCount: number | "lazy";
|
|
34
|
-
};
|
|
35
|
-
type ScenarioProjectionParity = {
|
|
36
|
-
readonly perspective: "spectator" | {
|
|
37
|
-
readonly seat: number;
|
|
38
|
-
};
|
|
39
|
-
readonly flow: {
|
|
40
|
-
readonly phase: string;
|
|
41
|
-
readonly step: string | null;
|
|
42
|
-
readonly activeSeats: readonly number[];
|
|
43
|
-
readonly pendingSeats: readonly number[];
|
|
44
|
-
readonly continuationWaiterSeats: readonly number[];
|
|
45
|
-
readonly blockedBy: readonly {
|
|
46
|
-
readonly actorSeat: number;
|
|
47
|
-
readonly blockerSeats: readonly number[];
|
|
48
|
-
}[];
|
|
49
|
-
};
|
|
50
|
-
readonly view: unknown;
|
|
51
|
-
readonly interactions: readonly {
|
|
52
|
-
readonly actorSeat: number;
|
|
53
|
-
readonly interactionId: string;
|
|
54
|
-
readonly availability: {
|
|
55
|
-
readonly status: string;
|
|
56
|
-
readonly code?: string;
|
|
57
|
-
readonly reason?: string;
|
|
58
|
-
};
|
|
59
|
-
readonly inputs: readonly {
|
|
60
|
-
readonly key: string;
|
|
61
|
-
readonly kind: string;
|
|
62
|
-
readonly eligibleCount: number | "lazy";
|
|
63
|
-
}[];
|
|
64
|
-
}[];
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Canonical local/backend parity authority. It deliberately excludes raw
|
|
68
|
-
* reducer public/private state, interaction explanations, entropy, and trace:
|
|
69
|
-
* backend gameplay frames expose the selected projection, not reducer state.
|
|
70
|
-
*/
|
|
71
|
-
declare function scenarioProjectionParityFromInspectNode(node: InspectNode): ScenarioProjectionParity;
|
|
72
|
-
declare function digestScenarioProjection(projection: ScenarioProjectionParity): Sha256Digest;
|
|
73
|
-
|
|
74
|
-
type CandidateVerificationScenario<Game extends ScenarioDefinitionGameLike> = ScenarioDefinition<Game>;
|
|
75
|
-
type CandidateVerificationInput<Game extends ScenarioDefinitionGameLike> = {
|
|
76
|
-
/** The authored reducer game definition used by production dispatch. */
|
|
77
|
-
readonly reducer: Game;
|
|
78
|
-
readonly scenarios: Readonly<Record<string, CandidateVerificationScenario<Game>>> | readonly CandidateVerificationScenario<Game>[];
|
|
79
|
-
readonly maxScenarios?: number;
|
|
80
|
-
readonly maxStepsPerScenario?: number;
|
|
81
|
-
};
|
|
82
|
-
type CandidateVerificationReplayDiagnostic = {
|
|
83
|
-
readonly kind: "replay";
|
|
84
|
-
readonly message: string;
|
|
85
|
-
readonly scenarioId?: string;
|
|
86
|
-
readonly segment?: "given" | "when";
|
|
87
|
-
readonly index?: number;
|
|
88
|
-
readonly interactionId?: string;
|
|
89
|
-
readonly errorCode?: string;
|
|
90
|
-
readonly reducerMessage?: string;
|
|
91
|
-
readonly trace?: readonly DispatchTraceSummaryEntry[];
|
|
92
|
-
};
|
|
93
|
-
type CandidateVerificationAssertionDiagnostic = {
|
|
94
|
-
readonly kind: "assertion";
|
|
95
|
-
readonly message: string;
|
|
96
|
-
};
|
|
97
|
-
type CandidateVerificationLimitDiagnostic = {
|
|
98
|
-
readonly kind: "limit";
|
|
99
|
-
readonly message: string;
|
|
100
|
-
readonly actualSteps: number;
|
|
101
|
-
readonly maxStepsPerScenario: number;
|
|
102
|
-
};
|
|
103
|
-
type CandidateVerificationScenarioResult = {
|
|
104
|
-
readonly id: string;
|
|
105
|
-
readonly status: "passed" | "failed";
|
|
106
|
-
readonly diagnostic?: CandidateVerificationReplayDiagnostic | CandidateVerificationAssertionDiagnostic | CandidateVerificationLimitDiagnostic;
|
|
107
|
-
};
|
|
108
|
-
type CandidateVerificationResult = {
|
|
109
|
-
readonly status: "passed" | "failed";
|
|
110
|
-
readonly scenarioSummary: {
|
|
111
|
-
readonly total: number;
|
|
112
|
-
readonly passed: number;
|
|
113
|
-
readonly failed: number;
|
|
114
|
-
readonly scenarios: readonly CandidateVerificationScenarioResult[];
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
declare function runCandidateVerification<const Game extends ScenarioDefinitionGameLike>(input: CandidateVerificationInput<Game>): Promise<CandidateVerificationResult>;
|
|
118
|
-
|
|
119
|
-
export { type CandidateVerificationAssertionDiagnostic, type CandidateVerificationInput, type CandidateVerificationLimitDiagnostic, type CandidateVerificationReplayDiagnostic, type CandidateVerificationResult, type CandidateVerificationScenario, type CandidateVerificationScenarioResult, type ScenarioProjectionInteractionInput, type ScenarioProjectionParity, digestScenarioProjection, resolveScenarioCommandParams, runCandidateVerification, scenarioProjectionInputMetadata, scenarioProjectionParityFromInspectNode };
|
package/dist/testing-runtime.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ScenarioReplayError,
|
|
3
|
-
assertScenario,
|
|
4
|
-
digestScenarioJson,
|
|
5
|
-
materializeScenarioRuntimeCheckpoint,
|
|
6
|
-
replayScenario,
|
|
7
|
-
resolveScenarioCommandParams
|
|
8
|
-
} from "./chunk-BI4G3PM2.js";
|
|
9
|
-
import {
|
|
10
|
-
interactionDomainEligibleCount
|
|
11
|
-
} from "./chunk-S2JEDVWH.js";
|
|
12
|
-
import "./chunk-PN5O6GG2.js";
|
|
13
|
-
import "./chunk-VCDWP5VL.js";
|
|
14
|
-
import "./chunk-H6VDGFL5.js";
|
|
15
|
-
import "./chunk-PF7L4BMG.js";
|
|
16
|
-
import "./chunk-UGH54WZ2.js";
|
|
17
|
-
import "./chunk-PZ5AY32C.js";
|
|
18
|
-
|
|
19
|
-
// src/testing/scenario-projection-digest.ts
|
|
20
|
-
function scenarioProjectionInputMetadata(input) {
|
|
21
|
-
return {
|
|
22
|
-
key: input.key,
|
|
23
|
-
kind: input.kind,
|
|
24
|
-
eligibleCount: interactionDomainEligibleCount(input.domain)
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function scenarioProjectionParityFromInspectNode(node) {
|
|
28
|
-
return {
|
|
29
|
-
perspective: node.perspective.kind === "spectator" ? "spectator" : { seat: node.perspective.actor.seat },
|
|
30
|
-
flow: {
|
|
31
|
-
phase: node.flow.phase,
|
|
32
|
-
step: node.flow.step,
|
|
33
|
-
activeSeats: node.flow.activeActors.map(({ seat }) => seat),
|
|
34
|
-
pendingSeats: node.flow.pendingActors.map(({ seat }) => seat),
|
|
35
|
-
continuationWaiterSeats: node.flow.continuationWaiters.map(
|
|
36
|
-
({ seat }) => seat
|
|
37
|
-
),
|
|
38
|
-
blockedBy: node.flow.blockedBy.map(({ actor, blockers }) => ({
|
|
39
|
-
actorSeat: actor.seat,
|
|
40
|
-
blockerSeats: blockers.map(({ seat }) => seat)
|
|
41
|
-
}))
|
|
42
|
-
},
|
|
43
|
-
view: node.view,
|
|
44
|
-
interactions: node.interactions.map(
|
|
45
|
-
({ actor, interactionId, availability, inputs }) => ({
|
|
46
|
-
actorSeat: actor.seat,
|
|
47
|
-
interactionId,
|
|
48
|
-
availability,
|
|
49
|
-
inputs
|
|
50
|
-
})
|
|
51
|
-
)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function digestScenarioProjection(projection) {
|
|
55
|
-
return digestScenarioJson({
|
|
56
|
-
digestVersion: "scenario-projection@1",
|
|
57
|
-
projection
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// src/testing-runtime.ts
|
|
62
|
-
var CANDIDATE_INPUT_FIELDS = /* @__PURE__ */ new Set([
|
|
63
|
-
"reducer",
|
|
64
|
-
"scenarios",
|
|
65
|
-
"maxScenarios",
|
|
66
|
-
"maxStepsPerScenario"
|
|
67
|
-
]);
|
|
68
|
-
var MAX_DIAGNOSTIC_MESSAGE_LENGTH = 2e3;
|
|
69
|
-
var MAX_DIAGNOSTIC_TRACE_ENTRIES = 100;
|
|
70
|
-
async function runCandidateVerification(input) {
|
|
71
|
-
assertCandidateInputFields(input);
|
|
72
|
-
const scenarios = normalizeScenarios(input.scenarios);
|
|
73
|
-
if (scenarios.length === 0) {
|
|
74
|
-
throw new Error("Candidate verification requires at least one scenario.");
|
|
75
|
-
}
|
|
76
|
-
const maxScenarios = input.maxScenarios ?? scenarios.length;
|
|
77
|
-
assertPositiveInteger(maxScenarios, "maxScenarios");
|
|
78
|
-
if (scenarios.length > maxScenarios) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Candidate verification contains ${scenarios.length} scenarios, exceeding limit ${maxScenarios}.`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
const maxStepsPerScenario = input.maxStepsPerScenario ?? 1e3;
|
|
84
|
-
assertPositiveInteger(maxStepsPerScenario, "maxStepsPerScenario");
|
|
85
|
-
const results = [];
|
|
86
|
-
for (const scenario of scenarios) {
|
|
87
|
-
results.push(
|
|
88
|
-
await runScenario({
|
|
89
|
-
reducer: input.reducer,
|
|
90
|
-
scenario,
|
|
91
|
-
maxStepsPerScenario
|
|
92
|
-
})
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
const failed = results.filter((result) => result.status === "failed").length;
|
|
96
|
-
return {
|
|
97
|
-
status: failed === 0 ? "passed" : "failed",
|
|
98
|
-
scenarioSummary: {
|
|
99
|
-
total: results.length,
|
|
100
|
-
passed: results.length - failed,
|
|
101
|
-
failed,
|
|
102
|
-
scenarios: results
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
async function runScenario(input) {
|
|
107
|
-
const actualSteps = input.scenario.given.length + input.scenario.when.length;
|
|
108
|
-
if (actualSteps > input.maxStepsPerScenario) {
|
|
109
|
-
return {
|
|
110
|
-
id: input.scenario.id,
|
|
111
|
-
status: "failed",
|
|
112
|
-
diagnostic: {
|
|
113
|
-
kind: "limit",
|
|
114
|
-
message: `Scenario contains ${actualSteps} replay steps, exceeding maxStepsPerScenario limit ${input.maxStepsPerScenario}.`,
|
|
115
|
-
actualSteps,
|
|
116
|
-
maxStepsPerScenario: input.maxStepsPerScenario
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
let replay;
|
|
121
|
-
try {
|
|
122
|
-
replay = await replayScenario({
|
|
123
|
-
game: input.reducer,
|
|
124
|
-
scenario: input.scenario
|
|
125
|
-
});
|
|
126
|
-
} catch (error) {
|
|
127
|
-
return {
|
|
128
|
-
id: input.scenario.id,
|
|
129
|
-
status: "failed",
|
|
130
|
-
diagnostic: replayDiagnostic(error)
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
await assertScenario({ replay, assertion: input.scenario.then });
|
|
135
|
-
return { id: input.scenario.id, status: "passed" };
|
|
136
|
-
} catch (error) {
|
|
137
|
-
return {
|
|
138
|
-
id: input.scenario.id,
|
|
139
|
-
status: "failed",
|
|
140
|
-
diagnostic: {
|
|
141
|
-
kind: "assertion",
|
|
142
|
-
message: boundedMessage(error)
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function assertCandidateInputFields(input) {
|
|
148
|
-
for (const field of Object.keys(input)) {
|
|
149
|
-
if (!CANDIDATE_INPUT_FIELDS.has(field)) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`Candidate verification input contains unsupported field '${field}'.`
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function assertPositiveInteger(value, field) {
|
|
157
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
158
|
-
throw new Error(`${field} must be a positive safe integer.`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
function normalizeScenarios(input) {
|
|
162
|
-
if (typeof input !== "object" || input === null) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
"Candidate verification scenarios must be an array or record."
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
const scenarios = Array.isArray(input) ? [...input] : Object.values(input);
|
|
168
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
169
|
-
for (const scenario of scenarios) {
|
|
170
|
-
if (typeof scenario?.id !== "string" || scenario.id.length === 0) {
|
|
171
|
-
throw new Error("Candidate verification scenario id is required.");
|
|
172
|
-
}
|
|
173
|
-
if (seenIds.has(scenario.id)) {
|
|
174
|
-
throw new Error(
|
|
175
|
-
`Candidate verification scenario id '${scenario.id}' is duplicated.`
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
seenIds.add(scenario.id);
|
|
179
|
-
}
|
|
180
|
-
return scenarios;
|
|
181
|
-
}
|
|
182
|
-
function replayDiagnostic(error) {
|
|
183
|
-
if (error instanceof ScenarioReplayError) {
|
|
184
|
-
return {
|
|
185
|
-
kind: "replay",
|
|
186
|
-
message: boundedMessage(error),
|
|
187
|
-
scenarioId: error.scenarioId,
|
|
188
|
-
segment: error.segment,
|
|
189
|
-
index: error.index,
|
|
190
|
-
interactionId: error.interactionId,
|
|
191
|
-
errorCode: error.errorCode,
|
|
192
|
-
...error.reducerMessage === void 0 ? {} : { reducerMessage: boundedText(error.reducerMessage) },
|
|
193
|
-
trace: error.trace.slice(0, MAX_DIAGNOSTIC_TRACE_ENTRIES)
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
return { kind: "replay", message: boundedMessage(error) };
|
|
197
|
-
}
|
|
198
|
-
function boundedMessage(error) {
|
|
199
|
-
return boundedText(error instanceof Error ? error.message : String(error));
|
|
200
|
-
}
|
|
201
|
-
function boundedText(value) {
|
|
202
|
-
return value.slice(0, MAX_DIAGNOSTIC_MESSAGE_LENGTH);
|
|
203
|
-
}
|
|
204
|
-
export {
|
|
205
|
-
digestScenarioProjection,
|
|
206
|
-
materializeScenarioRuntimeCheckpoint,
|
|
207
|
-
resolveScenarioCommandParams,
|
|
208
|
-
runCandidateVerification,
|
|
209
|
-
scenarioProjectionInputMetadata,
|
|
210
|
-
scenarioProjectionParityFromInspectNode
|
|
211
|
-
};
|
|
212
|
-
//# sourceMappingURL=testing-runtime.js.map
|