{"version":3,"file":"workflow-state.d.ts","sourceRoot":"","sources":["../../../src/missions/workflow-state.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAElD,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACvC;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1F;AAWD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,CA0DlH","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { writePrivateAtomicJson } from \"../shared/atomic-json.ts\";\nimport { assertWorkflowJsonValue } from \"../workflows/scripted-workflow.ts\";\nimport { validateMissionId } from \"./store.ts\";\nimport type { MissionStoreLocation } from \"./types.ts\";\n\nconst STATE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nexport const MISSION_STATE_MAX_BYTES = 256 * 1024;\n\nexport interface MissionWorkflowState {\n\tpath: string;\n\tget(key: string): unknown;\n\tset(key: string, value: unknown): void;\n}\n\nexport function missionStatePath(location: MissionStoreLocation, missionId: string): string {\n\treturn path.join(location.missionDir, validateMissionId(missionId), \"state.json\");\n}\n\nfunction validateStateKey(value: unknown): string {\n\tif (typeof value !== \"string\" || !STATE_KEY_PATTERN.test(value)) {\n\t\tthrow new Error(\n\t\t\t\"state key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.\",\n\t\t);\n\t}\n\treturn value;\n}\n\nexport function createMissionWorkflowState(location: MissionStoreLocation, missionId: string): MissionWorkflowState {\n\tconst filePath = missionStatePath(location, missionId);\n\tlet loaded = false;\n\tlet values: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n\n\tconst load = (): Record<string, unknown> => {\n\t\tif (loaded) return values;\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = fs.readFileSync(filePath, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\t\tloaded = true;\n\t\t\t\treturn values;\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to read mission state '${filePath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tconst bytes = Buffer.byteLength(raw);\n\t\tif (bytes > MISSION_STATE_MAX_BYTES)\n\t\t\tthrow new Error(`Mission state file '${filePath}' exceeds the 256 KiB limit (${bytes} bytes).`);\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw);\n\t\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed))\n\t\t\t\tthrow new Error(\"root must be a JSON object\");\n\t\t\tassertWorkflowJsonValue(parsed, \"mission state\");\n\t\t\tvalues = Object.assign(Object.create(null) as Record<string, unknown>, parsed);\n\t\t\tloaded = true;\n\t\t\treturn values;\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid mission state file '${filePath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t};\n\n\treturn {\n\t\tpath: filePath,\n\t\tget(key) {\n\t\t\tconst validKey = validateStateKey(key);\n\t\t\tconst current = load();\n\t\t\treturn Object.hasOwn(current, validKey) ? current[validKey] : undefined;\n\t\t},\n\t\tset(key, value) {\n\t\t\tconst validKey = validateStateKey(key);\n\t\t\tassertWorkflowJsonValue(value, `state.set('${validKey}') value`);\n\t\t\tconst next = Object.assign(Object.create(null) as Record<string, unknown>, load(), { [validKey]: value });\n\t\t\tconst bytes = Buffer.byteLength(JSON.stringify(next, null, 2));\n\t\t\tif (bytes > MISSION_STATE_MAX_BYTES)\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Mission state exceeds the 256 KiB limit (${bytes} bytes; maximum ${MISSION_STATE_MAX_BYTES} bytes).`,\n\t\t\t\t);\n\t\t\twritePrivateAtomicJson(filePath, next);\n\t\t\tvalues = next;\n\t\t\tloaded = true;\n\t\t},\n\t};\n}\n"]}