@forgeax/engine-project 0.1.20 → 0.1.23
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 +8 -13
- package/dist/__tests__/errors-v2.test-d.d.ts +2 -0
- package/dist/__tests__/errors-v2.test-d.d.ts.map +1 -0
- package/dist/__tests__/schema-v2.test.d.ts +2 -0
- package/dist/__tests__/schema-v2.test.d.ts.map +1 -0
- package/dist/index.mjs +10 -48
- package/dist/index.mjs.map +1 -1
- package/dist/schema.d.ts +9 -190
- package/dist/schema.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/errors-v2.test-d.ts +56 -0
- package/src/__tests__/loader.test.ts +18 -15
- package/src/__tests__/resolve.test.ts +4 -2
- package/src/__tests__/schema-v2.test.ts +130 -0
- package/src/__tests__/schema.test.ts +62 -270
- package/src/__tests__/structural.test.ts +3 -2
- package/src/loader.ts +3 -3
- package/src/schema.ts +14 -71
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/ac14-migration.test.d.ts +0 -2
- package/dist/__tests__/ac14-migration.test.d.ts.map +0 -1
- package/src/__tests__/ac14-migration.test.ts +0 -124
- package/src/__tests__/fixtures/games/cow-level/forge.json +0 -7
- package/src/__tests__/fixtures/games/cow-survivor/forge.json +0 -8
- package/src/__tests__/fixtures/games/fps/forge.json +0 -7
- package/src/__tests__/fixtures/games/hellforge/forge.json +0 -24
- package/src/__tests__/fixtures/games/shoot-opt/forge.json +0 -6
- package/src/__tests__/fixtures/games/spin-cube/forge.json +0 -6
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@ Game-project SSOT — zod schema + injectable loader + resolve layer for `forge.
|
|
|
10
10
|
{
|
|
11
11
|
"id": "my-game",
|
|
12
12
|
"name": "My Game",
|
|
13
|
-
"schemaVersion": "
|
|
14
|
-
"
|
|
13
|
+
"schemaVersion": "2.0.0",
|
|
14
|
+
"defaultScene": "00000000-0000-4000-8000-000000000001",
|
|
15
15
|
"plugins": [
|
|
16
|
-
{ "id": "gameplay", "name": "./
|
|
16
|
+
{ "id": "gameplay", "name": "./assets/plugin.ts", "realm": "engine" }
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
```
|
|
@@ -27,7 +27,7 @@ See [`AGENTS.md`](../../AGENTS.md) § "Project model" for the conceptual model.
|
|
|
27
27
|
`loadGameProject` is the primary entry point. Inject a reader so the loader stays free of `node:fs` / `fetch` (the same loader runs in server, editor, and tests):
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { loadGameProject, FORGE_JSON } from '@forgeax/engine
|
|
30
|
+
import { loadGameProject, FORGE_JSON } from '@forgeax/engine/project';
|
|
31
31
|
import { readFile } from 'node:fs/promises';
|
|
32
32
|
|
|
33
33
|
const result = await loadGameProject((path) => readFile(`/games/my-game/${path}`, 'utf-8'));
|
|
@@ -43,7 +43,7 @@ if (result.ok) {
|
|
|
43
43
|
Synchronous consumers (e.g. a sync `ContextSlot`) use the companion `loadGameProjectSync` — identical injection contract with a sync reader `(path) => string`:
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
|
-
import { loadGameProjectSync } from '@forgeax/engine
|
|
46
|
+
import { loadGameProjectSync } from '@forgeax/engine/project';
|
|
47
47
|
import { readFileSync } from 'node:fs';
|
|
48
48
|
|
|
49
49
|
const r = loadGameProjectSync((path) => readFileSync(`/games/my-game/${path}`, 'utf-8'));
|
|
@@ -61,17 +61,12 @@ Every failure returns a `GameProjectError` with `.code` / `.expected` / `.hint`
|
|
|
61
61
|
| `forge-missing` | reader threw / forge.json not found |
|
|
62
62
|
| `forge-parse-failed` | invalid JSON |
|
|
63
63
|
| `forge-schema-invalid` | valid JSON, fails schema (missing / wrong-typed required field) |
|
|
64
|
-
| `forge-unknown-field` | `.strict()` rejected
|
|
64
|
+
| `forge-unknown-field` | `.strict()` rejected a field outside the schema |
|
|
65
65
|
| `forge-guid-malformed` | `defaultScene` present but not a valid GUID |
|
|
66
66
|
| `forge-scene-unresolved` | `resolveDefaultScene` could not resolve the GUID to a `kind: 'scene'` asset |
|
|
67
67
|
|
|
68
68
|
## Schema as contract
|
|
69
69
|
|
|
70
|
-
`GameProjectSchema` is the authoritative field list (charter P2) — read it instead of prose, and derive types via `import type { GameProject } from '@forgeax/engine
|
|
70
|
+
`GameProjectSchema` is the authoritative field list (charter P2) — read it instead of prose, and derive types via `import type { GameProject } from '@forgeax/engine/project'`.
|
|
71
71
|
|
|
72
|
-
`
|
|
73
|
-
into the realm-local `ExecutionBootstrapEntry` contract from
|
|
74
|
-
`@forgeax/engine-app`; it is never inferred from filename conventions. A host
|
|
75
|
-
may use `entry` for DOM-only presentation while the execution module owns the
|
|
76
|
-
real World, Renderer, AssetRegistry, plugins, and render features. Games without
|
|
77
|
-
`executionEntry` remain on the host assembly path.
|
|
72
|
+
Schema `2.0.0` uses `plugins[]` as the persistent Entry tree; Group children use `group: true` with nested `config`. `realm` is the only ForgeaX placement extension, and `id` is the stable Entry identity.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-v2.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/errors-v2.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-v2.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema-v2.test.ts"],"names":[],"mappings":""}
|
package/dist/index.mjs
CHANGED
|
@@ -28,25 +28,6 @@ var GuidString = z.string().refine(
|
|
|
28
28
|
return { message };
|
|
29
29
|
}
|
|
30
30
|
);
|
|
31
|
-
var PhysicsUnion = z.union([z.enum(["3d", "2d", "rapier-3d", "rapier-2d"]), z.boolean()]);
|
|
32
|
-
var SkinSchema = z.object({
|
|
33
|
-
sceneGuid: z.string().optional(),
|
|
34
|
-
clipGuids: z.array(z.string()).optional(),
|
|
35
|
-
clipDefault: z.string().optional(),
|
|
36
|
-
scale: z.number().optional(),
|
|
37
|
-
pos: z.array(z.number()).optional()
|
|
38
|
-
}).passthrough();
|
|
39
|
-
var PreviewSchema = z.object({ skin: SkinSchema }).passthrough();
|
|
40
|
-
var NpcBudgetSchema = z.object({
|
|
41
|
-
maxCallsPerMinute: z.number().int().positive().optional(),
|
|
42
|
-
maxTokensPerMinute: z.number().int().positive().optional(),
|
|
43
|
-
maxConcurrent: z.number().int().positive().optional()
|
|
44
|
-
}).strict();
|
|
45
|
-
var NpcConfigSchema = z.object({
|
|
46
|
-
model: z.string().min(1).optional(),
|
|
47
|
-
maxTokens: z.number().int().positive().optional(),
|
|
48
|
-
budget: NpcBudgetSchema.optional()
|
|
49
|
-
}).strict();
|
|
50
31
|
var PluginRealmSchema = z.enum(["host", "engine", "build"]);
|
|
51
32
|
var PluginInjectSchema = z.union([
|
|
52
33
|
z.array(z.string().min(1)),
|
|
@@ -55,8 +36,8 @@ var PluginInjectSchema = z.union([
|
|
|
55
36
|
var PluginEntryBase = z.object({
|
|
56
37
|
id: z.string().min(1),
|
|
57
38
|
name: z.string().min(1),
|
|
58
|
-
disabled: z.boolean().
|
|
59
|
-
inject: PluginInjectSchema.
|
|
39
|
+
disabled: z.boolean().optional(),
|
|
40
|
+
inject: PluginInjectSchema.optional(),
|
|
60
41
|
realm: PluginRealmSchema.optional()
|
|
61
42
|
});
|
|
62
43
|
var GameProjectPluginEntrySchema = z.lazy(
|
|
@@ -66,7 +47,7 @@ var GameProjectPluginEntrySchema = z.lazy(
|
|
|
66
47
|
config: z.array(GameProjectPluginEntrySchema)
|
|
67
48
|
}).strict(),
|
|
68
49
|
PluginEntryBase.extend({
|
|
69
|
-
group: z.
|
|
50
|
+
group: z.literal(false).optional(),
|
|
70
51
|
config: z.unknown().optional()
|
|
71
52
|
}).strict()
|
|
72
53
|
])
|
|
@@ -88,30 +69,11 @@ var GameProjectPluginsSchema = z.array(GameProjectPluginEntrySchema).superRefine
|
|
|
88
69
|
visit(entries);
|
|
89
70
|
});
|
|
90
71
|
var GameProjectSchema = z.object({
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
name: z.string(),
|
|
95
|
-
/** forge.json schema version, e.g. "1.0.0" — required so loaders can gate on format. */
|
|
96
|
-
schemaVersion: z.string(),
|
|
97
|
-
/** Optional bootstrap hook: entry module relative to game root (e.g. "main.ts", "src/main.ts"). */
|
|
98
|
-
entry: z.string().optional(),
|
|
99
|
-
/** Optional realm-local ExecutionBootstrapEntry module relative to game root. */
|
|
100
|
-
executionEntry: z.string().optional(),
|
|
101
|
-
/** Optional GUID of the scene to load first; must be a real scene asset GUID (GuidString-validated). */
|
|
72
|
+
id: z.string().min(1),
|
|
73
|
+
name: z.string().min(1),
|
|
74
|
+
schemaVersion: z.literal("2.0.0"),
|
|
102
75
|
defaultScene: GuidString.optional(),
|
|
103
|
-
|
|
104
|
-
physics: PhysicsUnion.optional(),
|
|
105
|
-
/** Optional flag requesting pointer-lock (FPS-style mouse capture) at play time. */
|
|
106
|
-
pointerLock: z.boolean().optional(),
|
|
107
|
-
/** Optional input scheme hint consumed by play-runtime (e.g. "fps"). */
|
|
108
|
-
input: z.string().optional(),
|
|
109
|
-
/** Optional editor preview config (skin scene/clips for the launcher card). */
|
|
110
|
-
preview: PreviewSchema.optional(),
|
|
111
|
-
/** Native DeepSeek Harness Entry tree, plus the ForgeaX build-only realm selector. */
|
|
112
|
-
plugins: GameProjectPluginsSchema.optional(),
|
|
113
|
-
/** Optional Digital Life NPC Brain policy consumed by the server adapter. */
|
|
114
|
-
npc: NpcConfigSchema.optional()
|
|
76
|
+
plugins: GameProjectPluginsSchema
|
|
115
77
|
}).strict();
|
|
116
78
|
|
|
117
79
|
// src/loader.ts
|
|
@@ -167,7 +129,7 @@ function validateGameProject(raw) {
|
|
|
167
129
|
return err(
|
|
168
130
|
"forge-schema-invalid",
|
|
169
131
|
"forge.json to satisfy GameProjectSchema",
|
|
170
|
-
"check forge.json against GameProjectSchema (z.infer for types); required fields: id, name, schemaVersion
|
|
132
|
+
"check forge.json against GameProjectSchema (z.infer for types); required fields: id, name, schemaVersion=2.0.0, and plugins",
|
|
171
133
|
{ path: FORGE_JSON, zodErrors }
|
|
172
134
|
);
|
|
173
135
|
}
|
|
@@ -181,7 +143,7 @@ async function loadGameProject(read) {
|
|
|
181
143
|
return err(
|
|
182
144
|
"forge-missing",
|
|
183
145
|
"forge.json file to exist at the game root",
|
|
184
|
-
"verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion
|
|
146
|
+
"verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a schema 2.0 forge.json with id, name, schemaVersion=2.0.0, and plugins",
|
|
185
147
|
{ path: FORGE_JSON }
|
|
186
148
|
);
|
|
187
149
|
}
|
|
@@ -195,7 +157,7 @@ function loadGameProjectSync(read) {
|
|
|
195
157
|
return err(
|
|
196
158
|
"forge-missing",
|
|
197
159
|
"forge.json file to exist at the game root",
|
|
198
|
-
"verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion
|
|
160
|
+
"verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a schema 2.0 forge.json with id, name, schemaVersion=2.0.0, and plugins",
|
|
199
161
|
{ path: FORGE_JSON }
|
|
200
162
|
);
|
|
201
163
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/paths.ts","../src/schema.ts","../src/loader.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// errors.ts — GameProjectError closed error surface (D-2)\n//\n// Four-field surface: .code / .expected / .hint / .detail\n// Structurally parallel to PackError (engine-pack) — same interface shape\n// (charter P4 consistent abstraction), but own code union (D-2: pipe isolation).\n// GUID format failure translates PackError → GameProjectError(code='forge-guid-malformed')\n// so PackError never leaks to engine-project public surface.\n//\n// AI users consume via exhaustive switch on .code — no default case needed:\n// ```ts\n// switch (err.code) {\n// case 'forge-missing': console.error(err.detail.path); break;\n// case 'forge-parse-failed': console.error(err.detail.rawMessage); break;\n// // ... all 6 cases\n// }\n// ```\n\nimport type { z } from 'zod';\n\n/** forge.json file not found at the expected path. */\nexport interface ForgeMissingDetail {\n readonly path: string;\n}\n\n/** forge.json exists but contains invalid JSON. */\nexport interface ForgeParseFailedDetail {\n readonly path: string;\n readonly rawMessage: string;\n}\n\n/** forge.json is valid JSON but fails zod schema validation (missing required fields, wrong types). */\nexport interface ForgeSchemaInvalidDetail {\n readonly path: string;\n /** The zod issues from `safeParse(...).error.issues` — each names the failing path + reason. */\n readonly zodErrors: readonly z.ZodIssue[];\n}\n\n/** forge.json has .strict() rejected an unknown field (e.g. scenes[]). */\nexport interface ForgeUnknownFieldDetail {\n readonly path: string;\n readonly fieldNames: string[];\n}\n\n/** defaultScene GUID string is not a valid 36-char RFC 4122 dash-form UUID.\n * cause is the original PackError from AssetGuid.parse (for debugging), NOT part of reserved union. */\nexport interface ForgeGuidMalformedDetail {\n readonly field: string;\n readonly rawInput: string;\n readonly cause?: unknown; // PackError from engine-pack, optional for debugging (D-2)\n}\n\n/** defaultScene GUID is valid format but resolveGuid could not find it or found wrong kind. */\nexport interface ForgeSceneUnresolvedDetail {\n readonly guid: string;\n}\n\ntype GameProjectErrorDetailByCode = {\n 'forge-missing': ForgeMissingDetail;\n 'forge-parse-failed': ForgeParseFailedDetail;\n 'forge-schema-invalid': ForgeSchemaInvalidDetail;\n 'forge-unknown-field': ForgeUnknownFieldDetail;\n 'forge-guid-malformed': ForgeGuidMalformedDetail;\n 'forge-scene-unresolved': ForgeSceneUnresolvedDetail;\n};\n\nexport type GameProjectErrorCode = keyof GameProjectErrorDetailByCode;\nexport type GameProjectErrorDetail = GameProjectErrorDetailByCode[GameProjectErrorCode];\n\n// ── constructor args ────────────────────────────────────────────────────────\n\nexport type GameProjectErrorArgs<C extends GameProjectErrorCode = GameProjectErrorCode> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: GameProjectErrorDetailByCode[C];\n};\n\n// ── GameProjectError class (D-2) ─────────────────────────────────────────────\n\n/**\n * Structured error for the @forgeax/engine-project loader + resolve layer.\n *\n * AI users consume via exhaustive switch on .code — no default case needed.\n * Aligns with PackError four-field surface (.code/.expected/.hint/.detail)\n * per charter P4 (consistent abstraction) and AC-10.\n */\nclass GameProjectErrorClass extends Error {\n readonly code: GameProjectErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: GameProjectErrorDetail;\n\n constructor(args: GameProjectErrorArgs) {\n super(`[GameProjectError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'GameProjectError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n\ntype GameProjectErrorVariant<C extends GameProjectErrorCode> = GameProjectErrorClass & {\n readonly code: C;\n readonly detail: GameProjectErrorDetailByCode[C];\n};\n\nexport type GameProjectError = {\n [C in GameProjectErrorCode]: GameProjectErrorVariant<C>;\n}[GameProjectErrorCode];\n\ninterface GameProjectErrorConstructor {\n new <C extends GameProjectErrorCode>(args: GameProjectErrorArgs<C>): GameProjectErrorVariant<C>;\n readonly prototype: GameProjectErrorClass;\n}\n\nexport const GameProjectError: GameProjectErrorConstructor =\n GameProjectErrorClass as unknown as GameProjectErrorConstructor;\n","// paths.ts — path constants SSOT (D-8 bottom tier)\n//\n// All path literals for forge.json live here as the single authoritative source.\n// Consumers import FORGE_JSON rather than hardcoding 'forge.json' strings.\n\n/** The forge.json manifest filename, relative to a game root directory. */\nexport const FORGE_JSON = 'forge.json' as const;\n","// schema.ts — GameProjectSchema + GuidString refinement (D-4, D-7)\n//\n// D-4 fields: id:string / name:string / schemaVersion:string / entry?:string /\n// executionEntry?:string /\n// defaultScene?:GuidString / physics?:union(enum+bool) / pointerLock?:bool /\n// input?:string / preview?:nested-object. .strict() rejects unknown fields.\n//\n// D-7: GuidString refinement lives here (engine-project), calls\n// AssetGuid.parse from engine-pack, translates PackError to zod error message.\n\nimport { AssetGuid } from '@forgeax/engine-pack/guid';\nimport { z } from 'zod';\n\n// ── GuidString: zod refinement calling AssetGuid.parse (AC-04) ──────────────\nexport const GuidString = z.string().refine(\n (val) => AssetGuid.parse(val).ok,\n (val) => {\n const parsed = AssetGuid.parse(val);\n const message = !parsed.ok ? parsed.error.hint : `invalid GUID: ${val}`;\n return { message };\n },\n);\n\n// ── physics union: all 5 known values (D-4) ─────────────────────────────────\nconst PhysicsUnion = z.union([z.enum(['3d', '2d', 'rapier-3d', 'rapier-2d']), z.boolean()]);\n\n// ── preview.skin nested object (D-4) ────────────────────────────────────────\nconst SkinSchema = z\n .object({\n sceneGuid: z.string().optional(),\n clipGuids: z.array(z.string()).optional(),\n clipDefault: z.string().optional(),\n scale: z.number().optional(),\n pos: z.array(z.number()).optional(),\n })\n .passthrough();\n\nconst PreviewSchema = z.object({ skin: SkinSchema }).passthrough();\n\n// Digital Life NPC runtime policy embedded in forge.json. The Brain owns\n// prompt/model execution; the game manifest only declares the bounded model\n// and governor ceilings consumed by the server-side NPC adapter.\nconst NpcBudgetSchema = z\n .object({\n maxCallsPerMinute: z.number().int().positive().optional(),\n maxTokensPerMinute: z.number().int().positive().optional(),\n maxConcurrent: z.number().int().positive().optional(),\n })\n .strict();\n\nconst NpcConfigSchema = z\n .object({\n model: z.string().min(1).optional(),\n maxTokens: z.number().int().positive().optional(),\n budget: NpcBudgetSchema.optional(),\n })\n .strict();\n\nexport const PluginRealmSchema = z.enum(['host', 'engine', 'build']);\n\nconst PluginInjectSchema = z.union([\n z.array(z.string().min(1)),\n z.record(z.string().min(1), z.unknown()),\n]);\n\nconst PluginEntryBase = z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n disabled: z.boolean().nullable().optional(),\n inject: PluginInjectSchema.nullable().optional(),\n realm: PluginRealmSchema.optional(),\n});\n\nexport interface GameProjectPluginEntry {\n readonly id: string;\n readonly name: string;\n readonly config?: unknown;\n readonly group?: boolean | null | undefined;\n readonly disabled?: boolean | null | undefined;\n readonly inject?: readonly string[] | Readonly<Record<string, unknown>> | null | undefined;\n readonly realm?: z.infer<typeof PluginRealmSchema> | undefined;\n}\n\nexport const GameProjectPluginEntrySchema: z.ZodType<GameProjectPluginEntry> = z.lazy(() =>\n z.union([\n PluginEntryBase.extend({\n group: z.literal(true),\n config: z.array(GameProjectPluginEntrySchema),\n }).strict(),\n PluginEntryBase.extend({\n group: z.union([z.literal(false), z.null()]).optional(),\n config: z.unknown().optional(),\n }).strict(),\n ]),\n);\n\nexport const GameProjectPluginsSchema = z\n .array(GameProjectPluginEntrySchema)\n .superRefine((entries, ctx) => {\n const seen = new Set<string>();\n const visit = (items: readonly GameProjectPluginEntry[]): void => {\n for (const entry of items) {\n if (seen.has(entry.id)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `duplicate plugin entry id: ${entry.id}`,\n });\n }\n seen.add(entry.id);\n if (entry.group === true) visit(entry.config as readonly GameProjectPluginEntry[]);\n }\n };\n visit(entries);\n });\n\n// ── GameProjectSchema: strict zod object (AC-03, AC-05) ─────────────────────\n// This schema is the authoritative field list for forge.json (charter P2) —\n// read it instead of prose docs. Each field carries its contract inline.\nexport const GameProjectSchema = z\n .object({\n /** Stable game slug; matches the `.forgeax/games/<id>/` directory name. */\n id: z.string(),\n /** Human-facing display name (may contain any unicode). */\n name: z.string(),\n /** forge.json schema version, e.g. \"1.0.0\" — required so loaders can gate on format. */\n schemaVersion: z.string(),\n /** Optional bootstrap hook: entry module relative to game root (e.g. \"main.ts\", \"src/main.ts\"). */\n entry: z.string().optional(),\n /** Optional realm-local ExecutionBootstrapEntry module relative to game root. */\n executionEntry: z.string().optional(),\n /** Optional GUID of the scene to load first; must be a real scene asset GUID (GuidString-validated). */\n defaultScene: GuidString.optional(),\n /** Optional physics backend: enum tag ('3d'/'2d'/'rapier-3d'/'rapier-2d') or boolean on/off. */\n physics: PhysicsUnion.optional(),\n /** Optional flag requesting pointer-lock (FPS-style mouse capture) at play time. */\n pointerLock: z.boolean().optional(),\n /** Optional input scheme hint consumed by play-runtime (e.g. \"fps\"). */\n input: z.string().optional(),\n /** Optional editor preview config (skin scene/clips for the launcher card). */\n preview: PreviewSchema.optional(),\n /** Native DeepSeek Harness Entry tree, plus the ForgeaX build-only realm selector. */\n plugins: GameProjectPluginsSchema.optional(),\n /** Optional Digital Life NPC Brain policy consumed by the server adapter. */\n npc: NpcConfigSchema.optional(),\n })\n .strict();\n\n// ── GameProject type: z.infer-derived (AC-02, AC-05) ────────────────────────\nexport type GameProject = z.infer<typeof GameProjectSchema>;\n","// loader.ts — loadGameProject + resolveDefaultScene (D-6, AC-06/07/08/09)\n//\n// loadGameProject: injection-only `read:(path)=>Promise<string>`, returns\n// GuidResult<GameProject, GameProjectError>. Five return paths:\n// 1. read throws → ok:false, error(code='forge-missing')\n// 2. JSON.parse fails → ok:false, error(code='forge-parse-failed')\n// 3. schema.parse fails (missing fields) → ok:false, error(code='forge-schema-invalid')\n// 3b. schema.parse fails (unknown fields via .strict()) → ok:false, error(code='forge-unknown-field')\n// 4. defaultScene GUID format invalid → ok:false, error(code='forge-guid-malformed')\n// 5. all valid → ok:true, value:GameProject\n//\n// resolveDefaultScene: {read, resolveGuid} dual-injection (AC-07). Independent\n// of loadGameProject (does NOT call resolveGuid internally).\n//\n// D-6: return type GuidResult<T,E> — same as AssetGuid.parse (engine-pack).\n// D-2: PackError→GameProjectError translation for GUID format failures.\n\nimport type { GuidResult } from '@forgeax/engine-pack/guid';\nimport {\n GameProjectError,\n type GameProjectErrorArgs,\n type GameProjectErrorCode,\n} from './errors.js';\nimport { FORGE_JSON } from './paths.js';\nimport type { GameProject } from './schema.js';\nimport { GameProjectSchema } from './schema.js';\n\n// ── helpers ─────────────────────────────────────────────────────────────────\n\nfunction err<C extends GameProjectErrorCode>(\n code: C,\n expected: string,\n hint: string,\n detail: GameProjectErrorArgs<C>['detail'],\n) {\n return { ok: false as const, error: new GameProjectError({ code, expected, hint, detail }) };\n}\n\nfunction ok<T>(value: T) {\n return { ok: true as const, value };\n}\n\n// ── validateGameProject: sync pure core (JSON.parse → schema validate → 5 return paths) ──\n\n/**\n * Validate raw forge.json text against GameProjectSchema.\n * Pure sync function — no I/O, no `read` injection.\n *\n * ## Return paths (4 — missing/read-error handled by callers)\n *\n * | # | Condition | ok | code |\n * |:--|:--|:--|:--|\n * | 1 | `JSON.parse` fails | false | `forge-parse-failed` |\n * | 2a | zod `.strict()` unknown fields (e.g. `scenes[]`) | false | `forge-unknown-field` |\n * | 2b | zod required field missing / wrong type | false | `forge-schema-invalid` |\n * | 2c | `defaultScene` present, GUID format invalid | false | `forge-guid-malformed` |\n * | 3 | all valid | true | — |\n */\nexport function validateGameProject(raw: string): GuidResult<GameProject, GameProjectError> {\n // ── 1. Parse JSON ─────────────────────────────────────────────────────────\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e: unknown) {\n return err(\n 'forge-parse-failed',\n 'forge.json to be valid JSON',\n 'check forge.json for JSON syntax errors (trailing commas, unquoted keys, etc.)',\n {\n path: FORGE_JSON,\n rawMessage: e instanceof Error ? e.message : String(e),\n },\n );\n }\n\n // ── 2. Schema validate (.strict() rejects unknown fields) ─────────────────\n const schemaResult = GameProjectSchema.safeParse(parsed);\n if (!schemaResult.success) {\n const zodErrors = schemaResult.error.issues;\n\n // 2a: unknown fields (scenes[] etc.) — detect by looking for unrecognized_keys\n const unknownFieldIssues = zodErrors.filter((i) => i.code === 'unrecognized_keys');\n if (unknownFieldIssues.length > 0) {\n const fieldNames = unknownFieldIssues.flatMap((i) => (i as { keys?: string[] }).keys ?? []);\n return err(\n 'forge-unknown-field',\n 'forge.json to not contain unknown fields (e.g. scenes[])',\n 'remove unknown fields from forge.json; refer to GameProjectSchema in @forgeax/engine-project for the authoritative field list',\n { path: FORGE_JSON, fieldNames },\n );\n }\n\n // 2c: GuidString refinement failure → forge-guid-malformed (D-2 translation)\n const guidFieldIssues = zodErrors.filter(\n (i) => Array.isArray(i.path) && i.path.includes('defaultScene'),\n );\n if (guidFieldIssues.length > 0) {\n const rawInput =\n typeof (parsed as Record<string, unknown>).defaultScene === 'string'\n ? String((parsed as Record<string, unknown>).defaultScene)\n : '';\n return err(\n 'forge-guid-malformed',\n 'defaultScene to be a valid 36-char RFC 4122 dash-form UUID',\n 'use a valid scene GUID; find it in the scene pack.json assets[].guid where kind=scene',\n {\n field: 'defaultScene',\n rawInput,\n },\n );\n }\n\n // 2b: missing required fields or wrong types\n return err(\n 'forge-schema-invalid',\n 'forge.json to satisfy GameProjectSchema',\n 'check forge.json against GameProjectSchema (z.infer for types); required fields: id, name, schemaVersion (all strings)',\n { path: FORGE_JSON, zodErrors },\n );\n }\n\n // ── 3. Success ────────────────────────────────────────────────────────────\n return ok(schemaResult.data);\n}\n\n// ── loadGameProject: async (injection-only read) ────────────────────────────────\n\n/**\n * Load and validate a game project manifest from the abstract async reader.\n *\n * Wraps `validateGameProject`: reads `FORGE_JSON` via `read`, then validates.\n * If `read` throws/rejects → `forge-missing`.\n *\n * ## Return paths (5)\n *\n * | # | Condition | ok | code |\n * |:--|:--|:--|:--|\n * | 1 | `read(FORGE_JSON)` throws / rejects | false | `forge-missing` |\n * | 2 | `JSON.parse` fails | false | `forge-parse-failed` |\n * | 3a | zod `.strict()` unknown fields (e.g. `scenes[]`) | false | `forge-unknown-field` |\n * | 3b | zod required field missing / wrong type | false | `forge-schema-invalid` |\n * | 4 | `defaultScene` present, GUID format invalid (AssetGuid.parse fails) | false | `forge-guid-malformed` |\n * | 5 | all valid | true | — |\n *\n * ## Charter mapping\n *\n * - **P3 (explicit failure)**: every failure path returns structured `GameProjectError`\n * with `.code`/`.expected`/`.hint`/`.detail`; no silent catch-and-ignore or fallback.\n * - **P4 (consistent abstraction)**: return shape is `GuidResult` (same alias as\n * `AssetGuid.parse` from engine-pack).\n * - **SSOT**: this is the ONE authoritative forge.json loader — all consumers\n * import it instead of hand-rolling their own fetch+as / JSON.parse+as.\n */\nexport async function loadGameProject(\n read: (path: string) => Promise<string>,\n): Promise<GuidResult<GameProject, GameProjectError>> {\n let raw: string;\n try {\n raw = await read(FORGE_JSON);\n } catch (_e: unknown) {\n return err(\n 'forge-missing',\n 'forge.json file to exist at the game root',\n 'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion fields',\n { path: FORGE_JSON },\n );\n }\n\n return validateGameProject(raw);\n}\n\n// ── loadGameProjectSync: sync companion (for sync-only callers like ContextSlot) ──\n\n/**\n * Synchronous version of loadGameProject — for callers that cannot use async\n * (e.g. sync `ContextSlot` content functions).\n *\n * Accepts a sync `read:(path)=>string` injection (same contract shape,\n * sync return). If `read` throws → `forge-missing`.\n *\n * Uses the same `validateGameProject` pure core as `loadGameProject`.\n */\nexport function loadGameProjectSync(\n read: (path: string) => string,\n): GuidResult<GameProject, GameProjectError> {\n let raw: string;\n try {\n raw = read(FORGE_JSON);\n } catch (_e: unknown) {\n return err(\n 'forge-missing',\n 'forge.json file to exist at the game root',\n 'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion fields',\n { path: FORGE_JSON },\n );\n }\n\n return validateGameProject(raw);\n}\n\n// ── resolveDefaultScene: {read, resolveGuid} dual injection (AC-07) ─────────\n\n/** Resolved scene asset with GUID and kind='scene' confirmation. */\nexport interface ResolvedScene {\n readonly guid: string;\n readonly kind: 'scene';\n}\n\n/**\n * Resolve the defaultScene GUID to a scene asset using the injected resolveGuid.\n *\n * ## Dual injection (AC-07)\n *\n * - `read`: same `(path)=>Promise<string>` as loadGameProject — reads forge.json.\n * - `resolveGuid`: resolves a GUID string to an asset with `kind` field.\n * Signature: `(guid: string) => Promise<{ok:true, value:{kind:string, guid:string}} | {ok:false, error:unknown}>`\n *\n * `loadGameProject` does NOT call resolveGuid; resolveDefaultScene calls both\n * independently (charter SSOT: two-layer separation, format vs resolve).\n *\n * ## Return paths\n *\n * | Condition | ok | code |\n * |:--|:--|:--|\n * | loadGameProject returns !ok | false | propagated from loader |\n * | no defaultScene | false | `forge-scene-unresolved` |\n * | resolveGuid returns !ok | false | `forge-scene-unresolved` |\n * | resolveGuid returns asset.kind !== 'scene' | false | `forge-scene-unresolved` |\n * | resolveGuid returns asset.kind === 'scene' | true | — |\n */\nexport async function resolveDefaultScene(opts: {\n read: (path: string) => Promise<string>;\n resolveGuid: (\n guid: string,\n ) => Promise<{ ok: true; value: { kind: string; guid: string } } | { ok: false; error: unknown }>;\n}): Promise<GuidResult<ResolvedScene, GameProjectError>> {\n const { read, resolveGuid } = opts;\n\n // 1. Load the game project\n const gpResult = await loadGameProject(read);\n if (!gpResult.ok) {\n return gpResult; // propagate loader error\n }\n\n // 2. Check if defaultScene exists\n const gp = gpResult.value;\n const defaultSceneGuid = gp.defaultScene;\n if (defaultSceneGuid === undefined || defaultSceneGuid === null) {\n return err(\n 'forge-scene-unresolved',\n 'a defaultScene field in forge.json to resolve',\n 'add a defaultScene with a valid scene asset GUID to forge.json, or select a scene at runtime',\n { guid: '' },\n );\n }\n\n // 3. Resolve the GUID\n const guidStr = defaultSceneGuid as string;\n const resolveResult = await resolveGuid(guidStr);\n\n if (!resolveResult.ok) {\n return err(\n 'forge-scene-unresolved',\n 'the defaultScene GUID to resolve to an existing scene asset',\n \"verify the defaultScene GUID matches a scene asset's pack.json assets[].guid (where kind=scene)\",\n { guid: guidStr },\n );\n }\n\n // 4. Verify kind === 'scene'\n const asset = resolveResult.value;\n if (asset.kind !== 'scene') {\n return err(\n 'forge-scene-unresolved',\n 'the defaultScene GUID to point to a scene asset (kind=scene)',\n `resolved asset kind is \"${asset.kind}\", not \"scene\"; verify the GUID points to a scene pack entry`,\n { guid: guidStr },\n );\n }\n\n return ok({ guid: guidStr, kind: 'scene' });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/paths.ts","../src/schema.ts","../src/loader.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// errors.ts — GameProjectError closed error surface (D-2)\n//\n// Four-field surface: .code / .expected / .hint / .detail\n// Structurally parallel to PackError (engine-pack) — same interface shape\n// (charter P4 consistent abstraction), but own code union (D-2: pipe isolation).\n// GUID format failure translates PackError → GameProjectError(code='forge-guid-malformed')\n// so PackError never leaks to engine-project public surface.\n//\n// AI users consume via exhaustive switch on .code — no default case needed:\n// ```ts\n// switch (err.code) {\n// case 'forge-missing': console.error(err.detail.path); break;\n// case 'forge-parse-failed': console.error(err.detail.rawMessage); break;\n// // ... all 6 cases\n// }\n// ```\n\nimport type { z } from 'zod';\n\n/** forge.json file not found at the expected path. */\nexport interface ForgeMissingDetail {\n readonly path: string;\n}\n\n/** forge.json exists but contains invalid JSON. */\nexport interface ForgeParseFailedDetail {\n readonly path: string;\n readonly rawMessage: string;\n}\n\n/** forge.json is valid JSON but fails zod schema validation (missing required fields, wrong types). */\nexport interface ForgeSchemaInvalidDetail {\n readonly path: string;\n /** The zod issues from `safeParse(...).error.issues` — each names the failing path + reason. */\n readonly zodErrors: readonly z.ZodIssue[];\n}\n\n/** forge.json has .strict() rejected an unknown field (e.g. scenes[]). */\nexport interface ForgeUnknownFieldDetail {\n readonly path: string;\n readonly fieldNames: string[];\n}\n\n/** defaultScene GUID string is not a valid 36-char RFC 4122 dash-form UUID.\n * cause is the original PackError from AssetGuid.parse (for debugging), NOT part of reserved union. */\nexport interface ForgeGuidMalformedDetail {\n readonly field: string;\n readonly rawInput: string;\n readonly cause?: unknown; // PackError from engine-pack, optional for debugging (D-2)\n}\n\n/** defaultScene GUID is valid format but resolveGuid could not find it or found wrong kind. */\nexport interface ForgeSceneUnresolvedDetail {\n readonly guid: string;\n}\n\ntype GameProjectErrorDetailByCode = {\n 'forge-missing': ForgeMissingDetail;\n 'forge-parse-failed': ForgeParseFailedDetail;\n 'forge-schema-invalid': ForgeSchemaInvalidDetail;\n 'forge-unknown-field': ForgeUnknownFieldDetail;\n 'forge-guid-malformed': ForgeGuidMalformedDetail;\n 'forge-scene-unresolved': ForgeSceneUnresolvedDetail;\n};\n\nexport type GameProjectErrorCode = keyof GameProjectErrorDetailByCode;\nexport type GameProjectErrorDetail = GameProjectErrorDetailByCode[GameProjectErrorCode];\n\n// ── constructor args ────────────────────────────────────────────────────────\n\nexport type GameProjectErrorArgs<C extends GameProjectErrorCode = GameProjectErrorCode> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: GameProjectErrorDetailByCode[C];\n};\n\n// ── GameProjectError class (D-2) ─────────────────────────────────────────────\n\n/**\n * Structured error for the @forgeax/engine-project loader + resolve layer.\n *\n * AI users consume via exhaustive switch on .code — no default case needed.\n * Aligns with PackError four-field surface (.code/.expected/.hint/.detail)\n * per charter P4 (consistent abstraction) and AC-10.\n */\nclass GameProjectErrorClass extends Error {\n readonly code: GameProjectErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: GameProjectErrorDetail;\n\n constructor(args: GameProjectErrorArgs) {\n super(`[GameProjectError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'GameProjectError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n\ntype GameProjectErrorVariant<C extends GameProjectErrorCode> = GameProjectErrorClass & {\n readonly code: C;\n readonly detail: GameProjectErrorDetailByCode[C];\n};\n\nexport type GameProjectError = {\n [C in GameProjectErrorCode]: GameProjectErrorVariant<C>;\n}[GameProjectErrorCode];\n\ninterface GameProjectErrorConstructor {\n new <C extends GameProjectErrorCode>(args: GameProjectErrorArgs<C>): GameProjectErrorVariant<C>;\n readonly prototype: GameProjectErrorClass;\n}\n\nexport const GameProjectError: GameProjectErrorConstructor =\n GameProjectErrorClass as unknown as GameProjectErrorConstructor;\n","// paths.ts — path constants SSOT (D-8 bottom tier)\n//\n// All path literals for forge.json live here as the single authoritative source.\n// Consumers import FORGE_JSON rather than hardcoding 'forge.json' strings.\n\n/** The forge.json manifest filename, relative to a game root directory. */\nexport const FORGE_JSON = 'forge.json' as const;\n","// schema.ts — GameProjectSchema + GuidString refinement (D-4, D-7)\n//\n// Final project fields: id, name, schemaVersion=2.0.0, optional defaultScene,\n// and the required persistent plugins Entry tree. .strict() rejects unknown fields.\n//\n// D-7: GuidString refinement lives here (engine-project), calls\n// AssetGuid.parse from engine-pack, translates PackError to zod error message.\n\nimport { AssetGuid } from '@forgeax/engine-pack/guid';\nimport { z } from 'zod';\n\n// ── GuidString: zod refinement calling AssetGuid.parse (AC-04) ──────────────\nexport const GuidString = z.string().refine(\n (val) => AssetGuid.parse(val).ok,\n (val) => {\n const parsed = AssetGuid.parse(val);\n const message = !parsed.ok ? parsed.error.hint : `invalid GUID: ${val}`;\n return { message };\n },\n);\n\nexport const PluginRealmSchema = z.enum(['host', 'engine', 'build']);\n\nconst PluginInjectSchema = z.union([\n z.array(z.string().min(1)),\n z.record(z.string().min(1), z.unknown()),\n]);\n\nconst PluginEntryBase = z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n disabled: z.boolean().optional(),\n inject: PluginInjectSchema.optional(),\n realm: PluginRealmSchema.optional(),\n});\n\nexport interface GameProjectPluginEntry {\n readonly id: string;\n readonly name: string;\n readonly config?: unknown;\n readonly group?: boolean | undefined;\n readonly disabled?: boolean | undefined;\n readonly inject?: readonly string[] | Readonly<Record<string, unknown>> | undefined;\n readonly realm?: z.infer<typeof PluginRealmSchema> | undefined;\n}\n\nexport const GameProjectPluginEntrySchema: z.ZodType<GameProjectPluginEntry> = z.lazy(() =>\n z.union([\n PluginEntryBase.extend({\n group: z.literal(true),\n config: z.array(GameProjectPluginEntrySchema),\n }).strict(),\n PluginEntryBase.extend({\n group: z.literal(false).optional(),\n config: z.unknown().optional(),\n }).strict(),\n ]),\n);\n\nexport const GameProjectPluginsSchema = z\n .array(GameProjectPluginEntrySchema)\n .superRefine((entries, ctx) => {\n const seen = new Set<string>();\n const visit = (items: readonly GameProjectPluginEntry[]): void => {\n for (const entry of items) {\n if (seen.has(entry.id)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `duplicate plugin entry id: ${entry.id}`,\n });\n }\n seen.add(entry.id);\n if (entry.group === true) visit(entry.config as readonly GameProjectPluginEntry[]);\n }\n };\n visit(entries);\n });\n\n// ── GameProjectSchema: strict zod object (schema 2.0) ───────────────────────\n// This schema is the authoritative field list for forge.json.\nexport const GameProjectSchema = z\n .object({\n id: z.string().min(1),\n name: z.string().min(1),\n schemaVersion: z.literal('2.0.0'),\n defaultScene: GuidString.optional(),\n plugins: GameProjectPluginsSchema,\n })\n .strict();\n\n// ── GameProject type: z.infer-derived (AC-02, AC-05) ────────────────────────\nexport type GameProject = z.infer<typeof GameProjectSchema>;\n","// loader.ts — loadGameProject + resolveDefaultScene (D-6, AC-06/07/08/09)\n//\n// loadGameProject: injection-only `read:(path)=>Promise<string>`, returns\n// GuidResult<GameProject, GameProjectError>. Five return paths:\n// 1. read throws → ok:false, error(code='forge-missing')\n// 2. JSON.parse fails → ok:false, error(code='forge-parse-failed')\n// 3. schema.parse fails (missing fields) → ok:false, error(code='forge-schema-invalid')\n// 3b. schema.parse fails (unknown fields via .strict()) → ok:false, error(code='forge-unknown-field')\n// 4. defaultScene GUID format invalid → ok:false, error(code='forge-guid-malformed')\n// 5. all valid → ok:true, value:GameProject\n//\n// resolveDefaultScene: {read, resolveGuid} dual-injection (AC-07). Independent\n// of loadGameProject (does NOT call resolveGuid internally).\n//\n// D-6: return type GuidResult<T,E> — same as AssetGuid.parse (engine-pack).\n// D-2: PackError→GameProjectError translation for GUID format failures.\n\nimport type { GuidResult } from '@forgeax/engine-pack/guid';\nimport {\n GameProjectError,\n type GameProjectErrorArgs,\n type GameProjectErrorCode,\n} from './errors.js';\nimport { FORGE_JSON } from './paths.js';\nimport type { GameProject } from './schema.js';\nimport { GameProjectSchema } from './schema.js';\n\n// ── helpers ─────────────────────────────────────────────────────────────────\n\nfunction err<C extends GameProjectErrorCode>(\n code: C,\n expected: string,\n hint: string,\n detail: GameProjectErrorArgs<C>['detail'],\n) {\n return { ok: false as const, error: new GameProjectError({ code, expected, hint, detail }) };\n}\n\nfunction ok<T>(value: T) {\n return { ok: true as const, value };\n}\n\n// ── validateGameProject: sync pure core (JSON.parse → schema validate → 5 return paths) ──\n\n/**\n * Validate raw forge.json text against GameProjectSchema.\n * Pure sync function — no I/O, no `read` injection.\n *\n * ## Return paths (4 — missing/read-error handled by callers)\n *\n * | # | Condition | ok | code |\n * |:--|:--|:--|:--|\n * | 1 | `JSON.parse` fails | false | `forge-parse-failed` |\n * | 2a | zod `.strict()` unknown fields (e.g. `scenes[]`) | false | `forge-unknown-field` |\n * | 2b | zod required field missing / wrong type | false | `forge-schema-invalid` |\n * | 2c | `defaultScene` present, GUID format invalid | false | `forge-guid-malformed` |\n * | 3 | all valid | true | — |\n */\nexport function validateGameProject(raw: string): GuidResult<GameProject, GameProjectError> {\n // ── 1. Parse JSON ─────────────────────────────────────────────────────────\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e: unknown) {\n return err(\n 'forge-parse-failed',\n 'forge.json to be valid JSON',\n 'check forge.json for JSON syntax errors (trailing commas, unquoted keys, etc.)',\n {\n path: FORGE_JSON,\n rawMessage: e instanceof Error ? e.message : String(e),\n },\n );\n }\n\n // ── 2. Schema validate (.strict() rejects unknown fields) ─────────────────\n const schemaResult = GameProjectSchema.safeParse(parsed);\n if (!schemaResult.success) {\n const zodErrors = schemaResult.error.issues;\n\n // 2a: unknown fields (scenes[] etc.) — detect by looking for unrecognized_keys\n const unknownFieldIssues = zodErrors.filter((i) => i.code === 'unrecognized_keys');\n if (unknownFieldIssues.length > 0) {\n const fieldNames = unknownFieldIssues.flatMap((i) => (i as { keys?: string[] }).keys ?? []);\n return err(\n 'forge-unknown-field',\n 'forge.json to not contain unknown fields (e.g. scenes[])',\n 'remove unknown fields from forge.json; refer to GameProjectSchema in @forgeax/engine-project for the authoritative field list',\n { path: FORGE_JSON, fieldNames },\n );\n }\n\n // 2c: GuidString refinement failure → forge-guid-malformed (D-2 translation)\n const guidFieldIssues = zodErrors.filter(\n (i) => Array.isArray(i.path) && i.path.includes('defaultScene'),\n );\n if (guidFieldIssues.length > 0) {\n const rawInput =\n typeof (parsed as Record<string, unknown>).defaultScene === 'string'\n ? String((parsed as Record<string, unknown>).defaultScene)\n : '';\n return err(\n 'forge-guid-malformed',\n 'defaultScene to be a valid 36-char RFC 4122 dash-form UUID',\n 'use a valid scene GUID; find it in the scene pack.json assets[].guid where kind=scene',\n {\n field: 'defaultScene',\n rawInput,\n },\n );\n }\n\n // 2b: missing required fields or wrong types\n return err(\n 'forge-schema-invalid',\n 'forge.json to satisfy GameProjectSchema',\n 'check forge.json against GameProjectSchema (z.infer for types); required fields: id, name, schemaVersion=2.0.0, and plugins',\n { path: FORGE_JSON, zodErrors },\n );\n }\n\n // ── 3. Success ────────────────────────────────────────────────────────────\n return ok(schemaResult.data);\n}\n\n// ── loadGameProject: async (injection-only read) ────────────────────────────────\n\n/**\n * Load and validate a game project manifest from the abstract async reader.\n *\n * Wraps `validateGameProject`: reads `FORGE_JSON` via `read`, then validates.\n * If `read` throws/rejects → `forge-missing`.\n *\n * ## Return paths (5)\n *\n * | # | Condition | ok | code |\n * |:--|:--|:--|:--|\n * | 1 | `read(FORGE_JSON)` throws / rejects | false | `forge-missing` |\n * | 2 | `JSON.parse` fails | false | `forge-parse-failed` |\n * | 3a | zod `.strict()` unknown fields (e.g. `scenes[]`) | false | `forge-unknown-field` |\n * | 3b | zod required field missing / wrong type | false | `forge-schema-invalid` |\n * | 4 | `defaultScene` present, GUID format invalid (AssetGuid.parse fails) | false | `forge-guid-malformed` |\n * | 5 | all valid | true | — |\n *\n * ## Charter mapping\n *\n * - **P3 (explicit failure)**: every failure path returns structured `GameProjectError`\n * with `.code`/`.expected`/`.hint`/`.detail`; no silent catch-and-ignore or fallback.\n * - **P4 (consistent abstraction)**: return shape is `GuidResult` (same alias as\n * `AssetGuid.parse` from engine-pack).\n * - **SSOT**: this is the ONE authoritative forge.json loader — all consumers\n * import it instead of hand-rolling their own fetch+as / JSON.parse+as.\n */\nexport async function loadGameProject(\n read: (path: string) => Promise<string>,\n): Promise<GuidResult<GameProject, GameProjectError>> {\n let raw: string;\n try {\n raw = await read(FORGE_JSON);\n } catch (_e: unknown) {\n return err(\n 'forge-missing',\n 'forge.json file to exist at the game root',\n 'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a schema 2.0 forge.json with id, name, schemaVersion=2.0.0, and plugins',\n { path: FORGE_JSON },\n );\n }\n\n return validateGameProject(raw);\n}\n\n// ── loadGameProjectSync: sync companion (for sync-only callers like ContextSlot) ──\n\n/**\n * Synchronous version of loadGameProject — for callers that cannot use async\n * (e.g. sync `ContextSlot` content functions).\n *\n * Accepts a sync `read:(path)=>string` injection (same contract shape,\n * sync return). If `read` throws → `forge-missing`.\n *\n * Uses the same `validateGameProject` pure core as `loadGameProject`.\n */\nexport function loadGameProjectSync(\n read: (path: string) => string,\n): GuidResult<GameProject, GameProjectError> {\n let raw: string;\n try {\n raw = read(FORGE_JSON);\n } catch (_e: unknown) {\n return err(\n 'forge-missing',\n 'forge.json file to exist at the game root',\n 'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a schema 2.0 forge.json with id, name, schemaVersion=2.0.0, and plugins',\n { path: FORGE_JSON },\n );\n }\n\n return validateGameProject(raw);\n}\n\n// ── resolveDefaultScene: {read, resolveGuid} dual injection (AC-07) ─────────\n\n/** Resolved scene asset with GUID and kind='scene' confirmation. */\nexport interface ResolvedScene {\n readonly guid: string;\n readonly kind: 'scene';\n}\n\n/**\n * Resolve the defaultScene GUID to a scene asset using the injected resolveGuid.\n *\n * ## Dual injection (AC-07)\n *\n * - `read`: same `(path)=>Promise<string>` as loadGameProject — reads forge.json.\n * - `resolveGuid`: resolves a GUID string to an asset with `kind` field.\n * Signature: `(guid: string) => Promise<{ok:true, value:{kind:string, guid:string}} | {ok:false, error:unknown}>`\n *\n * `loadGameProject` does NOT call resolveGuid; resolveDefaultScene calls both\n * independently (charter SSOT: two-layer separation, format vs resolve).\n *\n * ## Return paths\n *\n * | Condition | ok | code |\n * |:--|:--|:--|\n * | loadGameProject returns !ok | false | propagated from loader |\n * | no defaultScene | false | `forge-scene-unresolved` |\n * | resolveGuid returns !ok | false | `forge-scene-unresolved` |\n * | resolveGuid returns asset.kind !== 'scene' | false | `forge-scene-unresolved` |\n * | resolveGuid returns asset.kind === 'scene' | true | — |\n */\nexport async function resolveDefaultScene(opts: {\n read: (path: string) => Promise<string>;\n resolveGuid: (\n guid: string,\n ) => Promise<{ ok: true; value: { kind: string; guid: string } } | { ok: false; error: unknown }>;\n}): Promise<GuidResult<ResolvedScene, GameProjectError>> {\n const { read, resolveGuid } = opts;\n\n // 1. Load the game project\n const gpResult = await loadGameProject(read);\n if (!gpResult.ok) {\n return gpResult; // propagate loader error\n }\n\n // 2. Check if defaultScene exists\n const gp = gpResult.value;\n const defaultSceneGuid = gp.defaultScene;\n if (defaultSceneGuid === undefined || defaultSceneGuid === null) {\n return err(\n 'forge-scene-unresolved',\n 'a defaultScene field in forge.json to resolve',\n 'add a defaultScene with a valid scene asset GUID to forge.json, or select a scene at runtime',\n { guid: '' },\n );\n }\n\n // 3. Resolve the GUID\n const guidStr = defaultSceneGuid as string;\n const resolveResult = await resolveGuid(guidStr);\n\n if (!resolveResult.ok) {\n return err(\n 'forge-scene-unresolved',\n 'the defaultScene GUID to resolve to an existing scene asset',\n \"verify the defaultScene GUID matches a scene asset's pack.json assets[].guid (where kind=scene)\",\n { guid: guidStr },\n );\n }\n\n // 4. Verify kind === 'scene'\n const asset = resolveResult.value;\n if (asset.kind !== 'scene') {\n return err(\n 'forge-scene-unresolved',\n 'the defaultScene GUID to point to a scene asset (kind=scene)',\n `resolved asset kind is \"${asset.kind}\", not \"scene\"; verify the GUID points to a scene pack entry`,\n { guid: guidStr },\n );\n }\n\n return ok({ guid: guidStr, kind: 'scene' });\n}\n"]}
|
package/dist/schema.d.ts
CHANGED
|
@@ -5,212 +5,31 @@ export interface GameProjectPluginEntry {
|
|
|
5
5
|
readonly id: string;
|
|
6
6
|
readonly name: string;
|
|
7
7
|
readonly config?: unknown;
|
|
8
|
-
readonly group?: boolean |
|
|
9
|
-
readonly disabled?: boolean |
|
|
10
|
-
readonly inject?: readonly string[] | Readonly<Record<string, unknown>> |
|
|
8
|
+
readonly group?: boolean | undefined;
|
|
9
|
+
readonly disabled?: boolean | undefined;
|
|
10
|
+
readonly inject?: readonly string[] | Readonly<Record<string, unknown>> | undefined;
|
|
11
11
|
readonly realm?: z.infer<typeof PluginRealmSchema> | undefined;
|
|
12
12
|
}
|
|
13
13
|
export declare const GameProjectPluginEntrySchema: z.ZodType<GameProjectPluginEntry>;
|
|
14
14
|
export declare const GameProjectPluginsSchema: z.ZodEffects<z.ZodArray<z.ZodType<GameProjectPluginEntry, z.ZodTypeDef, GameProjectPluginEntry>, "many">, GameProjectPluginEntry[], GameProjectPluginEntry[]>;
|
|
15
15
|
export declare const GameProjectSchema: z.ZodObject<{
|
|
16
|
-
/** Stable game slug; matches the `.forgeax/games/<id>/` directory name. */
|
|
17
16
|
id: z.ZodString;
|
|
18
|
-
/** Human-facing display name (may contain any unicode). */
|
|
19
17
|
name: z.ZodString;
|
|
20
|
-
|
|
21
|
-
schemaVersion: z.ZodString;
|
|
22
|
-
/** Optional bootstrap hook: entry module relative to game root (e.g. "main.ts", "src/main.ts"). */
|
|
23
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
24
|
-
/** Optional realm-local ExecutionBootstrapEntry module relative to game root. */
|
|
25
|
-
executionEntry: z.ZodOptional<z.ZodString>;
|
|
26
|
-
/** Optional GUID of the scene to load first; must be a real scene asset GUID (GuidString-validated). */
|
|
18
|
+
schemaVersion: z.ZodLiteral<"2.0.0">;
|
|
27
19
|
defaultScene: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
28
|
-
|
|
29
|
-
physics: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["3d", "2d", "rapier-3d", "rapier-2d"]>, z.ZodBoolean]>>;
|
|
30
|
-
/** Optional flag requesting pointer-lock (FPS-style mouse capture) at play time. */
|
|
31
|
-
pointerLock: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
-
/** Optional input scheme hint consumed by play-runtime (e.g. "fps"). */
|
|
33
|
-
input: z.ZodOptional<z.ZodString>;
|
|
34
|
-
/** Optional editor preview config (skin scene/clips for the launcher card). */
|
|
35
|
-
preview: z.ZodOptional<z.ZodObject<{
|
|
36
|
-
skin: z.ZodObject<{
|
|
37
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
38
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
39
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
40
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
41
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
42
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
43
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
44
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
45
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
46
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
47
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
48
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
49
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
50
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
51
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
52
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
53
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
54
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
55
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
56
|
-
skin: z.ZodObject<{
|
|
57
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
58
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
59
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
60
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
61
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
62
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
63
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
64
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
65
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
66
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
67
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
68
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
69
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
70
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
71
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
72
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
73
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
74
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
75
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
76
|
-
skin: z.ZodObject<{
|
|
77
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
78
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
79
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
80
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
81
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
82
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
83
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
84
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
85
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
86
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
87
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
88
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
89
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
90
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
91
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
92
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
93
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
94
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
95
|
-
}, z.ZodTypeAny, "passthrough">>>;
|
|
96
|
-
/** Native DeepSeek Harness Entry tree, plus the ForgeaX build-only realm selector. */
|
|
97
|
-
plugins: z.ZodOptional<z.ZodEffects<z.ZodArray<z.ZodType<GameProjectPluginEntry, z.ZodTypeDef, GameProjectPluginEntry>, "many">, GameProjectPluginEntry[], GameProjectPluginEntry[]>>;
|
|
98
|
-
/** Optional Digital Life NPC Brain policy consumed by the server adapter. */
|
|
99
|
-
npc: z.ZodOptional<z.ZodObject<{
|
|
100
|
-
model: z.ZodOptional<z.ZodString>;
|
|
101
|
-
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
102
|
-
budget: z.ZodOptional<z.ZodObject<{
|
|
103
|
-
maxCallsPerMinute: z.ZodOptional<z.ZodNumber>;
|
|
104
|
-
maxTokensPerMinute: z.ZodOptional<z.ZodNumber>;
|
|
105
|
-
maxConcurrent: z.ZodOptional<z.ZodNumber>;
|
|
106
|
-
}, "strict", z.ZodTypeAny, {
|
|
107
|
-
maxCallsPerMinute?: number | undefined;
|
|
108
|
-
maxTokensPerMinute?: number | undefined;
|
|
109
|
-
maxConcurrent?: number | undefined;
|
|
110
|
-
}, {
|
|
111
|
-
maxCallsPerMinute?: number | undefined;
|
|
112
|
-
maxTokensPerMinute?: number | undefined;
|
|
113
|
-
maxConcurrent?: number | undefined;
|
|
114
|
-
}>>;
|
|
115
|
-
}, "strict", z.ZodTypeAny, {
|
|
116
|
-
model?: string | undefined;
|
|
117
|
-
maxTokens?: number | undefined;
|
|
118
|
-
budget?: {
|
|
119
|
-
maxCallsPerMinute?: number | undefined;
|
|
120
|
-
maxTokensPerMinute?: number | undefined;
|
|
121
|
-
maxConcurrent?: number | undefined;
|
|
122
|
-
} | undefined;
|
|
123
|
-
}, {
|
|
124
|
-
model?: string | undefined;
|
|
125
|
-
maxTokens?: number | undefined;
|
|
126
|
-
budget?: {
|
|
127
|
-
maxCallsPerMinute?: number | undefined;
|
|
128
|
-
maxTokensPerMinute?: number | undefined;
|
|
129
|
-
maxConcurrent?: number | undefined;
|
|
130
|
-
} | undefined;
|
|
131
|
-
}>>;
|
|
20
|
+
plugins: z.ZodEffects<z.ZodArray<z.ZodType<GameProjectPluginEntry, z.ZodTypeDef, GameProjectPluginEntry>, "many">, GameProjectPluginEntry[], GameProjectPluginEntry[]>;
|
|
132
21
|
}, "strict", z.ZodTypeAny, {
|
|
133
22
|
id: string;
|
|
134
23
|
name: string;
|
|
135
|
-
schemaVersion:
|
|
136
|
-
|
|
137
|
-
executionEntry?: string | undefined;
|
|
24
|
+
schemaVersion: "2.0.0";
|
|
25
|
+
plugins: GameProjectPluginEntry[];
|
|
138
26
|
defaultScene?: string | undefined;
|
|
139
|
-
physics?: boolean | "3d" | "2d" | "rapier-3d" | "rapier-2d" | undefined;
|
|
140
|
-
pointerLock?: boolean | undefined;
|
|
141
|
-
input?: string | undefined;
|
|
142
|
-
preview?: z.objectOutputType<{
|
|
143
|
-
skin: z.ZodObject<{
|
|
144
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
145
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
146
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
147
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
148
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
149
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
150
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
151
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
152
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
153
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
154
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
155
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
156
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
157
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
158
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
159
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
160
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
161
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
162
|
-
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
163
|
-
plugins?: GameProjectPluginEntry[] | undefined;
|
|
164
|
-
npc?: {
|
|
165
|
-
model?: string | undefined;
|
|
166
|
-
maxTokens?: number | undefined;
|
|
167
|
-
budget?: {
|
|
168
|
-
maxCallsPerMinute?: number | undefined;
|
|
169
|
-
maxTokensPerMinute?: number | undefined;
|
|
170
|
-
maxConcurrent?: number | undefined;
|
|
171
|
-
} | undefined;
|
|
172
|
-
} | undefined;
|
|
173
27
|
}, {
|
|
174
28
|
id: string;
|
|
175
29
|
name: string;
|
|
176
|
-
schemaVersion:
|
|
177
|
-
|
|
178
|
-
executionEntry?: string | undefined;
|
|
30
|
+
schemaVersion: "2.0.0";
|
|
31
|
+
plugins: GameProjectPluginEntry[];
|
|
179
32
|
defaultScene?: string | undefined;
|
|
180
|
-
physics?: boolean | "3d" | "2d" | "rapier-3d" | "rapier-2d" | undefined;
|
|
181
|
-
pointerLock?: boolean | undefined;
|
|
182
|
-
input?: string | undefined;
|
|
183
|
-
preview?: z.objectInputType<{
|
|
184
|
-
skin: z.ZodObject<{
|
|
185
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
186
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
187
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
188
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
189
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
190
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
191
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
192
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
193
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
194
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
195
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
196
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
197
|
-
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
198
|
-
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
199
|
-
clipDefault: z.ZodOptional<z.ZodString>;
|
|
200
|
-
scale: z.ZodOptional<z.ZodNumber>;
|
|
201
|
-
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
202
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
203
|
-
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
204
|
-
plugins?: GameProjectPluginEntry[] | undefined;
|
|
205
|
-
npc?: {
|
|
206
|
-
model?: string | undefined;
|
|
207
|
-
maxTokens?: number | undefined;
|
|
208
|
-
budget?: {
|
|
209
|
-
maxCallsPerMinute?: number | undefined;
|
|
210
|
-
maxTokensPerMinute?: number | undefined;
|
|
211
|
-
maxConcurrent?: number | undefined;
|
|
212
|
-
} | undefined;
|
|
213
|
-
} | undefined;
|
|
214
33
|
}>;
|
|
215
34
|
export type GameProject = z.infer<typeof GameProjectSchema>;
|
|
216
35
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,UAAU,2CAOtB,CAAC;AAEF,eAAO,MAAM,iBAAiB,wCAAsC,CAAC;AAerE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IACpF,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,GAAG,SAAS,CAAC;CAChE;AAED,eAAO,MAAM,4BAA4B,EAAE,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAW1E,CAAC;AAEF,eAAO,MAAM,wBAAwB,+JAiBjC,CAAC;AAIL,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;EAQnB,CAAC;AAGZ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-project",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-pack": "0.1.
|
|
25
|
+
"@forgeax/engine-pack": "0.1.23",
|
|
26
26
|
"zod": "^3.25.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { expectTypeOf, it } from 'vitest';
|
|
2
|
+
import type { GameProjectError, GameProjectErrorCode } from '../errors.js';
|
|
3
|
+
import { GameProjectError as GameProjectErrorClass } from '../errors.js';
|
|
4
|
+
|
|
5
|
+
const projectErrorCodes = [
|
|
6
|
+
'forge-missing',
|
|
7
|
+
'forge-parse-failed',
|
|
8
|
+
'forge-schema-invalid',
|
|
9
|
+
'forge-unknown-field',
|
|
10
|
+
'forge-guid-malformed',
|
|
11
|
+
'forge-scene-unresolved',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
expectTypeOf<GameProjectErrorCode>().toEqualTypeOf<(typeof projectErrorCodes)[number]>();
|
|
15
|
+
|
|
16
|
+
const errors: GameProjectError[] = [
|
|
17
|
+
new GameProjectErrorClass({
|
|
18
|
+
code: 'forge-schema-invalid',
|
|
19
|
+
expected: 'forge.json to satisfy schema 2.0',
|
|
20
|
+
hint: 'remove the legacy field and validate the manifest again',
|
|
21
|
+
detail: { path: 'forge.json', zodErrors: [] },
|
|
22
|
+
}),
|
|
23
|
+
new GameProjectErrorClass({
|
|
24
|
+
code: 'forge-unknown-field',
|
|
25
|
+
expected: 'only schema 2.0 fields',
|
|
26
|
+
hint: 'remove the unknown field',
|
|
27
|
+
detail: { path: 'forge.json', fieldNames: ['entry'] },
|
|
28
|
+
}),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function describe(error: GameProjectError): string {
|
|
32
|
+
switch (error.code) {
|
|
33
|
+
case 'forge-missing':
|
|
34
|
+
return error.detail.path;
|
|
35
|
+
case 'forge-parse-failed':
|
|
36
|
+
return error.detail.rawMessage;
|
|
37
|
+
case 'forge-schema-invalid':
|
|
38
|
+
return `${error.detail.path}:${error.detail.zodErrors.length}`;
|
|
39
|
+
case 'forge-unknown-field':
|
|
40
|
+
return error.detail.fieldNames.join(',');
|
|
41
|
+
case 'forge-guid-malformed':
|
|
42
|
+
return error.detail.rawInput;
|
|
43
|
+
case 'forge-scene-unresolved':
|
|
44
|
+
return error.detail.guid;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
it('keeps project failures as a closed code/expected/hint/detail union', () => {
|
|
49
|
+
for (const error of errors) {
|
|
50
|
+
expectTypeOf(error.code).toEqualTypeOf<GameProjectErrorCode>();
|
|
51
|
+
expectTypeOf(error.expected).toBeString();
|
|
52
|
+
expectTypeOf(error.hint).toBeString();
|
|
53
|
+
expectTypeOf(error.detail).toMatchTypeOf<object>();
|
|
54
|
+
}
|
|
55
|
+
void describe;
|
|
56
|
+
});
|