@forgeax/engine-project 0.1.26 → 0.1.28
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/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/schema.ts +3 -0
package/dist/index.mjs
CHANGED
|
@@ -36,6 +36,8 @@ var PluginInjectSchema = z.union([
|
|
|
36
36
|
var PluginEntryBase = z.object({
|
|
37
37
|
id: z.string().min(1),
|
|
38
38
|
name: z.string().min(1),
|
|
39
|
+
/** Pure Tool Runtime contract; the executor is loaded only on invocation. */
|
|
40
|
+
commandContract: z.string().min(1).optional(),
|
|
39
41
|
disabled: z.boolean().optional(),
|
|
40
42
|
inject: PluginInjectSchema.optional(),
|
|
41
43
|
realm: PluginRealmSchema.optional()
|
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// 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"]}
|
|
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 /** Pure Tool Runtime contract; the executor is loaded only on invocation. */\n commandContract: z.string().min(1).optional(),\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 commandContract?: string | undefined;\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
|
@@ -4,6 +4,7 @@ export declare const PluginRealmSchema: z.ZodEnum<["host", "engine", "build"]>;
|
|
|
4
4
|
export interface GameProjectPluginEntry {
|
|
5
5
|
readonly id: string;
|
|
6
6
|
readonly name: string;
|
|
7
|
+
readonly commandContract?: string | undefined;
|
|
7
8
|
readonly config?: unknown;
|
|
8
9
|
readonly group?: boolean | undefined;
|
|
9
10
|
readonly disabled?: boolean | undefined;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAiBrE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,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.28",
|
|
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.28",
|
|
26
26
|
"zod": "^3.25.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
package/src/schema.ts
CHANGED
|
@@ -29,6 +29,8 @@ const PluginInjectSchema = z.union([
|
|
|
29
29
|
const PluginEntryBase = z.object({
|
|
30
30
|
id: z.string().min(1),
|
|
31
31
|
name: z.string().min(1),
|
|
32
|
+
/** Pure Tool Runtime contract; the executor is loaded only on invocation. */
|
|
33
|
+
commandContract: z.string().min(1).optional(),
|
|
32
34
|
disabled: z.boolean().optional(),
|
|
33
35
|
inject: PluginInjectSchema.optional(),
|
|
34
36
|
realm: PluginRealmSchema.optional(),
|
|
@@ -37,6 +39,7 @@ const PluginEntryBase = z.object({
|
|
|
37
39
|
export interface GameProjectPluginEntry {
|
|
38
40
|
readonly id: string;
|
|
39
41
|
readonly name: string;
|
|
42
|
+
readonly commandContract?: string | undefined;
|
|
40
43
|
readonly config?: unknown;
|
|
41
44
|
readonly group?: boolean | undefined;
|
|
42
45
|
readonly disabled?: boolean | undefined;
|