Nicolas Rabault Claude Sonnet 4.6 commited on
Commit
54df78b
·
1 Parent(s): ba2cfd8

feat(engine): loader for narrator JSON

Browse files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

package-lock.json CHANGED
@@ -770,6 +770,17 @@
770
  "dev": true,
771
  "license": "MIT"
772
  },
 
 
 
 
 
 
 
 
 
 
 
773
  "node_modules/@vitest/expect": {
774
  "version": "2.1.9",
775
  "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
@@ -1273,12 +1284,20 @@
1273
  "node": ">=14.17"
1274
  }
1275
  },
 
 
 
 
 
 
 
1276
  "node_modules/vite": {
1277
  "version": "5.4.21",
1278
  "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
1279
  "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
1280
  "dev": true,
1281
  "license": "MIT",
 
1282
  "dependencies": {
1283
  "esbuild": "^0.21.3",
1284
  "postcss": "^8.4.43",
@@ -1442,6 +1461,7 @@
1442
  "packages/story-engine": {
1443
  "version": "0.1.0",
1444
  "devDependencies": {
 
1445
  "vitest": "^2.1.0"
1446
  }
1447
  }
 
770
  "dev": true,
771
  "license": "MIT"
772
  },
773
+ "node_modules/@types/node": {
774
+ "version": "25.6.0",
775
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
776
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
777
+ "dev": true,
778
+ "license": "MIT",
779
+ "peer": true,
780
+ "dependencies": {
781
+ "undici-types": "~7.19.0"
782
+ }
783
+ },
784
  "node_modules/@vitest/expect": {
785
  "version": "2.1.9",
786
  "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
 
1284
  "node": ">=14.17"
1285
  }
1286
  },
1287
+ "node_modules/undici-types": {
1288
+ "version": "7.19.2",
1289
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
1290
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
1291
+ "dev": true,
1292
+ "license": "MIT"
1293
+ },
1294
  "node_modules/vite": {
1295
  "version": "5.4.21",
1296
  "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
1297
  "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
1298
  "dev": true,
1299
  "license": "MIT",
1300
+ "peer": true,
1301
  "dependencies": {
1302
  "esbuild": "^0.21.3",
1303
  "postcss": "^8.4.43",
 
1461
  "packages/story-engine": {
1462
  "version": "0.1.0",
1463
  "devDependencies": {
1464
+ "@types/node": "^25.6.0",
1465
  "vitest": "^2.1.0"
1466
  }
1467
  }
packages/story-engine/package.json CHANGED
@@ -12,6 +12,7 @@
12
  "test:watch": "vitest"
13
  },
14
  "devDependencies": {
 
15
  "vitest": "^2.1.0"
16
  }
17
  }
 
12
  "test:watch": "vitest"
13
  },
14
  "devDependencies": {
15
+ "@types/node": "^25.6.0",
16
  "vitest": "^2.1.0"
17
  }
18
  }
packages/story-engine/src/loader.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Narrator } from "./types.js";
2
+
3
+ interface RawNarrator {
4
+ name: string;
5
+ description: string;
6
+ introduction: string;
7
+ background: string;
8
+ goal: string;
9
+ voice: string;
10
+ voice_tone: string;
11
+ // legacy fields ignored: picture, tools, etc.
12
+ }
13
+
14
+ export function loadNarrator(raw: unknown): Narrator {
15
+ const r = raw as RawNarrator;
16
+ if (!r || typeof r !== "object") {
17
+ throw new Error("Narrator JSON must be an object");
18
+ }
19
+ for (const field of ["name", "description", "introduction", "background", "goal", "voice", "voice_tone"] as const) {
20
+ if (typeof r[field] !== "string" || !r[field]) {
21
+ throw new Error(`Narrator missing required string field: ${field}`);
22
+ }
23
+ }
24
+ return {
25
+ id: "narrator",
26
+ name: r.name,
27
+ description: r.description,
28
+ introduction: r.introduction,
29
+ background: r.background,
30
+ goal: r.goal,
31
+ voice: r.voice,
32
+ voiceTone: r.voice_tone,
33
+ };
34
+ }
packages/story-engine/tests/fixtures/narrator/narrator.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "The Narrator",
3
+ "description": "A cheerful storyteller",
4
+ "introduction": "Welcome adventurer!",
5
+ "background": "An entity who guides players through stories.",
6
+ "goal": "Help the player pick a story.",
7
+ "voice": "cedar",
8
+ "voice_tone": "Warm, enthusiastic, brief."
9
+ }
packages/story-engine/tests/loader.test.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { loadNarrator } from "../src/loader.js";
5
+
6
+ const narratorJson = JSON.parse(
7
+ readFileSync(resolve(__dirname, "fixtures/narrator/narrator.json"), "utf-8"),
8
+ );
9
+
10
+ describe("loadNarrator", () => {
11
+ it("normalizes a narrator JSON into the Narrator type", () => {
12
+ const narrator = loadNarrator(narratorJson);
13
+ expect(narrator.id).toBe("narrator");
14
+ expect(narrator.name).toBe("The Narrator");
15
+ expect(narrator.voice).toBe("cedar");
16
+ expect(narrator.voiceTone).toBe("Warm, enthusiastic, brief.");
17
+ });
18
+ });
packages/story-engine/tsconfig.json CHANGED
@@ -1,4 +1,7 @@
1
  {
2
  "extends": "../../tsconfig.base.json",
 
 
 
3
  "include": ["src/**/*", "tests/**/*"]
4
  }
 
1
  {
2
  "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["node"]
5
+ },
6
  "include": ["src/**/*", "tests/**/*"]
7
  }