@zocomputer/system-api 0.0.0-stage → 0.1.0

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.
Files changed (57) hide show
  1. package/README.md +51 -2
  2. package/dist/catalog/index.d.ts +106 -0
  3. package/dist/catalog/index.js +237 -0
  4. package/dist/core/catalog.d.ts +155 -0
  5. package/dist/core/catalog.js +24 -0
  6. package/dist/core/client.d.ts +156 -0
  7. package/dist/core/client.js +87 -0
  8. package/dist/core/database.d.ts +124 -0
  9. package/dist/core/database.js +314 -0
  10. package/dist/core/envelope.d.ts +45 -0
  11. package/dist/core/envelope.js +112 -0
  12. package/dist/core/errors.d.ts +52 -0
  13. package/dist/core/errors.js +33 -0
  14. package/dist/core/index.d.ts +14 -0
  15. package/dist/core/index.js +13 -0
  16. package/dist/core/json-call.d.ts +4 -0
  17. package/dist/core/json-call.js +109 -0
  18. package/dist/core/json.d.ts +17 -0
  19. package/dist/core/json.js +4 -0
  20. package/dist/core/transport.d.ts +47 -0
  21. package/dist/core/transport.js +367 -0
  22. package/dist/core/validator.d.ts +52 -0
  23. package/dist/core/validator.js +1 -0
  24. package/dist/generate/cli.d.ts +44 -0
  25. package/dist/generate/cli.js +358 -0
  26. package/dist/generate/generate.d.ts +46 -0
  27. package/dist/generate/generate.js +215 -0
  28. package/dist/generate/identifiers.d.ts +27 -0
  29. package/dist/generate/identifiers.js +67 -0
  30. package/dist/generate/index.d.ts +15 -0
  31. package/dist/generate/index.js +15 -0
  32. package/dist/generate/manifest.d.ts +1 -0
  33. package/dist/generate/manifest.js +1 -0
  34. package/dist/generate/types.d.ts +57 -0
  35. package/dist/generate/types.js +357 -0
  36. package/dist/generated/catalog.d.ts +2 -0
  37. package/dist/generated/catalog.js +842 -0
  38. package/dist/generated/index.d.ts +2 -0
  39. package/dist/generated/index.js +4 -0
  40. package/dist/generated/manifest.json +789 -0
  41. package/dist/generated/operations.d.ts +8407 -0
  42. package/dist/generated/operations.js +3 -0
  43. package/dist/generated/schema.json +39395 -0
  44. package/dist/index.d.ts +11 -0
  45. package/dist/index.js +11 -0
  46. package/dist/json/index.d.ts +81 -0
  47. package/dist/json/index.js +404 -0
  48. package/dist/validation/index.d.ts +19 -0
  49. package/dist/validation/index.js +149 -0
  50. package/dist/validation/json.d.ts +1 -0
  51. package/dist/validation/json.js +1 -0
  52. package/dist/validation/pattern.d.ts +56 -0
  53. package/dist/validation/pattern.js +382 -0
  54. package/dist/validation/schema.d.ts +103 -0
  55. package/dist/validation/schema.js +1300 -0
  56. package/openapi.json +40698 -0
  57. package/package.json +93 -4
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Deterministic, collision-safe TypeScript identifiers and sanitized doc text
3
+ * for generated output. Schema data is never code: a generated type name is
4
+ * derived only from a safe character class, and documentation text is
5
+ * neutralized so it can never terminate a comment or inject TypeScript.
6
+ */
7
+ /** Longest doc-comment body kept per node; longer text is truncated with an ellipsis. */
8
+ const MAX_DOC_LENGTH = 4096;
9
+ /**
10
+ * PascalCase a raw token from its alphanumeric runs. Non-alphanumeric
11
+ * characters (dots, dashes, underscores, anything else) are treated as segment
12
+ * separators, so `zo.machine.read_file` becomes `ZoMachineReadFile`. A leading
13
+ * digit run is prefixed with `_` so the result is a valid identifier head.
14
+ */
15
+ export function pascalCase(raw) {
16
+ const segments = raw.split(/[^A-Za-z0-9]+/).filter((s) => s.length > 0);
17
+ const joined = segments
18
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
19
+ .join("");
20
+ if (joined.length === 0)
21
+ return "_";
22
+ return /^[0-9]/.test(joined) ? `_${joined}` : joined;
23
+ }
24
+ /**
25
+ * Type name for an action's input or output at a version:
26
+ * `("zo.machine.read_file", 1, "Input")` → `ZoMachineReadFileInputV1`.
27
+ */
28
+ export function operationTypeName(action, version, kind) {
29
+ return `${pascalCase(action)}${kind}V${version}`;
30
+ }
31
+ /** Type name for a resource type: `("machine", 1)` → `MachineV1`. */
32
+ export function resourceTypeName(type, version) {
33
+ return `${pascalCase(type)}V${version}`;
34
+ }
35
+ /**
36
+ * Neutralize free-form documentation text so it is safe inside a `/** … *\/`
37
+ * block: drop control characters (except newline/tab), collapse the comment
38
+ * terminator `*\/`, and bound the length. The result contains no sequence that
39
+ * can close the comment or start a new syntactic construct.
40
+ */
41
+ export function sanitizeDoc(text) {
42
+ // Strip C0/C1 control characters other than newline and tab; they have no
43
+ // place in a comment and can confuse editors and tooling.
44
+ let out = "";
45
+ for (const ch of text) {
46
+ const code = ch.codePointAt(0);
47
+ if (code === 0x09 || code === 0x0a) {
48
+ out += ch;
49
+ }
50
+ else if (code < 0x20 || (code >= 0x7f && code <= 0x9f)) {
51
+ // drop
52
+ }
53
+ else {
54
+ out += ch;
55
+ }
56
+ }
57
+ // Neutralize the comment terminator anywhere it appears (including a `*` and
58
+ // `/` split across sanitization) so the doc text cannot close the block.
59
+ out = out.replace(/\*\//g, "*​/");
60
+ // Bound the length in Unicode code points, matching the server/SDK annotation
61
+ // bound. A UTF-16 `.length`/`.slice` would truncate a valid astral-character
62
+ // description early and could split a surrogate pair, emitting U+FFFD.
63
+ const points = Array.from(out);
64
+ if (points.length > MAX_DOC_LENGTH)
65
+ out = points.slice(0, MAX_DOC_LENGTH).join("") + "…";
66
+ return out;
67
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@zocomputer/system-api/generate` — the programmatic generator entry.
3
+ *
4
+ * Exposes the pure generation pipeline (parse a normalized catalog export →
5
+ * emit the five-file typed-client bundle) without the CLI's I/O. A caller that
6
+ * already has the export bytes can drive generation directly; the
7
+ * `zo-system-api` CLI is a thin wrapper over this surface.
8
+ *
9
+ * This entry pulls in json-schema-to-typescript and its dependencies, so a
10
+ * runtime consumer of the generated client never imports it — it lives behind
11
+ * this separate specifier, distinct from the dependency-light `.` and `./core`.
12
+ */
13
+ export { BUNDLE_FILE_NAMES, GENERATOR_VERSION, generateBundle, type CatalogSelection, type GenerateInput, type GeneratedBundle, } from "./generate.ts";
14
+ export { CatalogFormatError, loadNormalizedCatalog, parseNormalizedCatalog, toPlainJson, type CatalogSelectionEcho, type NormalizedCatalog, type OperationEntry, type OperationVersion, type ResourceTypeEntry, } from "./manifest.ts";
15
+ export { GeneratorError } from "./types.ts";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@zocomputer/system-api/generate` — the programmatic generator entry.
3
+ *
4
+ * Exposes the pure generation pipeline (parse a normalized catalog export →
5
+ * emit the five-file typed-client bundle) without the CLI's I/O. A caller that
6
+ * already has the export bytes can drive generation directly; the
7
+ * `zo-system-api` CLI is a thin wrapper over this surface.
8
+ *
9
+ * This entry pulls in json-schema-to-typescript and its dependencies, so a
10
+ * runtime consumer of the generated client never imports it — it lives behind
11
+ * this separate specifier, distinct from the dependency-light `.` and `./core`.
12
+ */
13
+ export { BUNDLE_FILE_NAMES, GENERATOR_VERSION, generateBundle, } from "./generate.js";
14
+ export { CatalogFormatError, loadNormalizedCatalog, parseNormalizedCatalog, toPlainJson, } from "./manifest.js";
15
+ export { GeneratorError } from "./types.js";
@@ -0,0 +1 @@
1
+ export * from "../catalog/index.ts";
@@ -0,0 +1 @@
1
+ export * from "../catalog/index.js";
@@ -0,0 +1,57 @@
1
+ import { type JsonValue } from "../validation/json.ts";
2
+ /** Thrown when a schema cannot be rendered to safe, exact TypeScript. */
3
+ export declare class GeneratorError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ /**
7
+ * A single, deterministic, collision-proof registry of every top-level
8
+ * TypeScript name the generator emits. `claim` is idempotent per source: the
9
+ * same `sourceKey` always returns the same name, and two distinct sources that
10
+ * want the same base name are separated by a content-addressed suffix derived
11
+ * from the losing source's identity.
12
+ */
13
+ export declare class NameRegistry {
14
+ private readonly used;
15
+ private readonly assigned;
16
+ constructor(reserved?: Iterable<string>);
17
+ /** True if `name` is already taken (reserved or claimed). */
18
+ has(name: string): boolean;
19
+ /**
20
+ * Return the unique name for `sourceKey`, minting it on first request.
21
+ * `desiredBase` must be a valid identifier; if it is free the source keeps it,
22
+ * otherwise the source receives `${desiredBase}_${hash}` with the shortest
23
+ * hash prefix that is free (extended, then counter-suffixed, only in the
24
+ * astronomically unlikely event of a further clash).
25
+ */
26
+ claim(sourceKey: string, desiredBase: string): string;
27
+ }
28
+ /**
29
+ * Fail if any numeric `enum`/`const` literal in the (lossless) schema would
30
+ * change value when represented as a JS `number`. Those keywords become emitted
31
+ * TypeScript literal types, so a silent round would make the generated type
32
+ * disagree with the contract; the author must string-encode such a value
33
+ * instead. Only `enum`/`const` are checked — other numeric keywords never become
34
+ * literal types.
35
+ */
36
+ export declare function assertRenderableLiterals(schema: JsonValue, where: string): void;
37
+ /** The result of rendering one schema: emitted text and whether it used JsonValue. */
38
+ export interface RenderedType {
39
+ readonly text: string;
40
+ readonly usesJsonValue: boolean;
41
+ }
42
+ /**
43
+ * Render one admitted schema to a named TypeScript declaration (plus its
44
+ * reachable `$defs` interfaces). `rootName` must already be a registry-minted
45
+ * name; `rootSourceKey` scopes this schema's `$defs` names within the registry.
46
+ */
47
+ export declare function renderSchemaType(schema: JsonValue, rootName: string, rootSourceKey: string, registry: NameRegistry, where: string): Promise<RenderedType>;
48
+ /**
49
+ * Lexically scan `text` for a bare `any` type token. This is comment- and
50
+ * string-aware: an `any` inside a line/block comment or a quoted string
51
+ * (including an `enum`/`const` value literally `"any"`) is skipped, and a `//`
52
+ * or `/*` inside a string literal is not mistaken for a comment. An `any`
53
+ * identifier immediately followed (past whitespace) by `:` or `?` is a property
54
+ * name, not the `any` type, so it is ignored; any other bare `any` token in code
55
+ * is a real `any` type.
56
+ */
57
+ export declare function containsAnyType(text: string): boolean;
@@ -0,0 +1,357 @@
1
+ /**
2
+ * The schema → TypeScript renderer: turn one admitted Zo-dialect schema into a
3
+ * named, collision-safe TypeScript declaration.
4
+ *
5
+ * Every emitted top-level name — an operation's Input/Output type, a resource
6
+ * type, and every `$defs` interface reachable from a schema — is minted through
7
+ * one shared {@link NameRegistry}, so independently compiled schemas can be
8
+ * concatenated without ever colliding or relying on TypeScript interface
9
+ * merging (which would silently union two unrelated shapes). Naming is fully
10
+ * deterministic: the first claimant of a base name in a fixed traversal order
11
+ * keeps the clean name; a distinct later claimant gets a content-addressed
12
+ * suffix.
13
+ *
14
+ * The pipeline per schema:
15
+ * 1. Admit through the SDK's own {@link compileSchema} — the authoritative
16
+ * "supported dialect" gate. A schema it rejects is rejected here too; we
17
+ * never fall back to emitting `any`.
18
+ * 2. Guard emitted literals: a numeric `enum`/`const` becomes a TypeScript
19
+ * literal type, so it must fail loudly (not silently round) when the value
20
+ * is not representable as a JS number. Ordinary bounds
21
+ * (minimum/maximum/multipleOf) are type-level metadata TypeScript ignores,
22
+ * so they may round harmlessly.
23
+ * 3. Project the lossless tree to plain JSON, then rewrite it for the
24
+ * compiler: strip every `title` (forcing anonymous inlining of nested
25
+ * objects so only registry-named declarations survive), rename `$defs`
26
+ * keys to registry names and rewrite `#/$defs/` refs, sanitize doc text,
27
+ * and rewrite the `x-zo-json-value` marker to the SDK's `JsonValue` type.
28
+ * 4. Compile with json-schema-to-typescript and post-scan the output: any
29
+ * `any` in a type position is a generator bug and throws.
30
+ */
31
+ import { createHash } from "node:crypto";
32
+ import Decimal from "decimal.js";
33
+ import { compile } from "json-schema-to-typescript";
34
+ import { JsonNumber } from "../validation/json.js";
35
+ import { SchemaDefinitionError, compileSchema } from "../validation/schema.js";
36
+ import { pascalCase, sanitizeDoc } from "./identifiers.js";
37
+ import { toPlainJson } from "./manifest.js";
38
+ /** The local-definition ref prefix, matching the validator's supported dialect. */
39
+ const REF_PREFIX = "#/$defs/";
40
+ /** Options passed to json-schema-to-typescript for every schema. */
41
+ const COMPILE_OPTIONS = {
42
+ // We prepend our own file banner in the orchestrator; none per schema.
43
+ bannerComment: "",
44
+ // Emit every reachable `$defs` interface as its own top-level declaration
45
+ // (named by the registry) rather than inlining it.
46
+ declareExternallyReferenced: true,
47
+ // Objects without an explicit `additionalProperties` are closed; our exporter
48
+ // sets it explicitly anyway.
49
+ additionalProperties: false,
50
+ // Deterministic raw output; the orchestrator owns final formatting.
51
+ format: false,
52
+ // Unknown/empty schemas become `unknown`, never `any`.
53
+ unknownAny: true,
54
+ // No `$ref` fetching: our dialect is local-only, and network/file resolution
55
+ // must never happen during generation.
56
+ $refOptions: {},
57
+ };
58
+ /** Thrown when a schema cannot be rendered to safe, exact TypeScript. */
59
+ export class GeneratorError extends Error {
60
+ constructor(message) {
61
+ super(message);
62
+ this.name = "GeneratorError";
63
+ }
64
+ }
65
+ /**
66
+ * A single, deterministic, collision-proof registry of every top-level
67
+ * TypeScript name the generator emits. `claim` is idempotent per source: the
68
+ * same `sourceKey` always returns the same name, and two distinct sources that
69
+ * want the same base name are separated by a content-addressed suffix derived
70
+ * from the losing source's identity.
71
+ */
72
+ export class NameRegistry {
73
+ used = new Set();
74
+ assigned = new Map();
75
+ constructor(reserved = []) {
76
+ for (const name of reserved)
77
+ this.used.add(name);
78
+ }
79
+ /** True if `name` is already taken (reserved or claimed). */
80
+ has(name) {
81
+ return this.used.has(name);
82
+ }
83
+ /**
84
+ * Return the unique name for `sourceKey`, minting it on first request.
85
+ * `desiredBase` must be a valid identifier; if it is free the source keeps it,
86
+ * otherwise the source receives `${desiredBase}_${hash}` with the shortest
87
+ * hash prefix that is free (extended, then counter-suffixed, only in the
88
+ * astronomically unlikely event of a further clash).
89
+ */
90
+ claim(sourceKey, desiredBase) {
91
+ const prior = this.assigned.get(sourceKey);
92
+ if (prior !== undefined)
93
+ return prior;
94
+ let name = desiredBase;
95
+ if (this.used.has(name)) {
96
+ const digest = createHash("sha256").update(sourceKey).digest("hex");
97
+ let width = 8;
98
+ name = `${desiredBase}_${digest.slice(0, width)}`;
99
+ while (this.used.has(name)) {
100
+ width += 4;
101
+ if (width > digest.length) {
102
+ let counter = 2;
103
+ while (this.used.has(`${desiredBase}_${digest}_${counter}`))
104
+ counter += 1;
105
+ name = `${desiredBase}_${digest}_${counter}`;
106
+ break;
107
+ }
108
+ name = `${desiredBase}_${digest.slice(0, width)}`;
109
+ }
110
+ }
111
+ this.used.add(name);
112
+ this.assigned.set(sourceKey, name);
113
+ return name;
114
+ }
115
+ }
116
+ function isObject(value) {
117
+ return value !== null && typeof value === "object" && !Array.isArray(value);
118
+ }
119
+ /**
120
+ * Fail if any numeric `enum`/`const` literal in the (lossless) schema would
121
+ * change value when represented as a JS `number`. Those keywords become emitted
122
+ * TypeScript literal types, so a silent round would make the generated type
123
+ * disagree with the contract; the author must string-encode such a value
124
+ * instead. Only `enum`/`const` are checked — other numeric keywords never become
125
+ * literal types.
126
+ */
127
+ export function assertRenderableLiterals(schema, where) {
128
+ const visit = (node) => {
129
+ if (Array.isArray(node)) {
130
+ for (const item of node)
131
+ visit(item);
132
+ return;
133
+ }
134
+ if (!isObjectTree(node))
135
+ return;
136
+ if ("const" in node)
137
+ assertJsSafeNumbers(node["const"], where, "const");
138
+ if ("enum" in node) {
139
+ const values = node["enum"];
140
+ if (Array.isArray(values))
141
+ for (const value of values)
142
+ assertJsSafeNumbers(value, where, "enum");
143
+ }
144
+ // Recurse only through schema-bearing positions of the supported dialect, so
145
+ // a data value under `default`/`examples` is never mistaken for a keyword.
146
+ for (const key of ["items", "additionalProperties"])
147
+ if (key in node)
148
+ visit(node[key]);
149
+ for (const key of ["anyOf", "oneOf"]) {
150
+ const branches = node[key];
151
+ if (Array.isArray(branches))
152
+ for (const branch of branches)
153
+ visit(branch);
154
+ }
155
+ for (const key of ["properties", "$defs"]) {
156
+ const holder = node[key];
157
+ if (isObjectTree(holder))
158
+ for (const child of Object.values(holder))
159
+ visit(child);
160
+ }
161
+ };
162
+ visit(schema);
163
+ }
164
+ function isObjectTree(value) {
165
+ return (value !== null &&
166
+ typeof value === "object" &&
167
+ !Array.isArray(value) &&
168
+ !(value instanceof JsonNumber));
169
+ }
170
+ /** Deep-check every number inside an `enum`/`const` value for JS-safety. */
171
+ function assertJsSafeNumbers(value, where, keyword) {
172
+ if (value instanceof JsonNumber) {
173
+ const text = value.text;
174
+ if (!new Decimal(text).equals(new Decimal(Number(text))))
175
+ throw new GeneratorError(`${where}: numeric ${keyword} value ${text} is not representable as a ` +
176
+ `JavaScript number and would change if emitted as a TypeScript literal; ` +
177
+ `encode it as a string in the schema instead`);
178
+ return;
179
+ }
180
+ if (Array.isArray(value)) {
181
+ for (const item of value)
182
+ assertJsSafeNumbers(item, where, keyword);
183
+ return;
184
+ }
185
+ if (value !== null && typeof value === "object")
186
+ for (const item of Object.values(value))
187
+ assertJsSafeNumbers(item, where, keyword);
188
+ }
189
+ /**
190
+ * Render one admitted schema to a named TypeScript declaration (plus its
191
+ * reachable `$defs` interfaces). `rootName` must already be a registry-minted
192
+ * name; `rootSourceKey` scopes this schema's `$defs` names within the registry.
193
+ */
194
+ export async function renderSchemaType(schema, rootName, rootSourceKey, registry, where) {
195
+ // 1. Admit through the authoritative dialect gate.
196
+ try {
197
+ compileSchema(schema);
198
+ }
199
+ catch (error) {
200
+ if (error instanceof SchemaDefinitionError)
201
+ throw new GeneratorError(`${where}: schema is outside the supported dialect: ${error.message}`);
202
+ throw error;
203
+ }
204
+ // 2. Guard emitted numeric literals against silent rounding.
205
+ assertRenderableLiterals(schema, where);
206
+ // 3. Project to plain JSON and rewrite for the compiler.
207
+ const plain = toPlainJson(schema);
208
+ if (!isObject(plain))
209
+ throw new GeneratorError(`${where}: schema must be a JSON object`);
210
+ const renameMap = buildDefsRenameMap(plain, rootName, rootSourceKey, registry);
211
+ const marker = { used: false };
212
+ const prepared = rewriteForCompile(plain, renameMap, marker);
213
+ // 4. Compile and post-scan for `any`.
214
+ let text;
215
+ try {
216
+ text = await compile(prepared, rootName, COMPILE_OPTIONS);
217
+ }
218
+ catch (error) {
219
+ throw new GeneratorError(`${where}: json-schema-to-typescript failed: ${error.message}`);
220
+ }
221
+ assertNoAny(text, where);
222
+ return { text: text.trim(), usesJsonValue: marker.used };
223
+ }
224
+ /** Build the `$defs` key → registry-name map for one root schema. */
225
+ function buildDefsRenameMap(plain, rootName, rootSourceKey, registry) {
226
+ const map = new Map();
227
+ const defs = plain["$defs"];
228
+ if (!isObject(defs))
229
+ return map;
230
+ // Sort keys so the first claimant of any clashing base is deterministic.
231
+ for (const key of Object.keys(defs).sort()) {
232
+ const assigned = registry.claim(`def:${rootSourceKey}:${key}`, `${rootName}${pascalCase(key)}`);
233
+ map.set(key, assigned);
234
+ }
235
+ return map;
236
+ }
237
+ /**
238
+ * Rewrite a plain schema for json-schema-to-typescript: strip titles, sanitize
239
+ * descriptions, rewrite `x-zo-json-value` to a `JsonValue` tsType, rename the
240
+ * root `$defs` keys, rewrite `#/$defs/` refs, and drop any schema-supplied
241
+ * `tsType` (never honor raw type injection from schema data) and `discriminator`
242
+ * (OpenAPI metadata the compiler ignores). Objects are built with a null
243
+ * prototype so a property literally named `__proto__` survives.
244
+ */
245
+ function rewriteForCompile(value, renameMap, marker) {
246
+ if (Array.isArray(value))
247
+ return value.map((item) => rewriteForCompile(item, renameMap, marker));
248
+ if (!isObject(value))
249
+ return value;
250
+ // The arbitrary-JSON marker: emit the SDK's exact wire type. We mint this
251
+ // tsType ourselves — a schema-supplied tsType is never trusted.
252
+ if (value["x-zo-json-value"] === true) {
253
+ marker.used = true;
254
+ const out = Object.create(null);
255
+ out["tsType"] = "JsonValue";
256
+ if (typeof value["description"] === "string")
257
+ out["description"] = sanitizeDoc(value["description"]);
258
+ return out;
259
+ }
260
+ const out = Object.create(null);
261
+ for (const [key, item] of Object.entries(value)) {
262
+ if (key === "title" || key === "tsType" || key === "discriminator")
263
+ continue;
264
+ if (key === "description" && typeof item === "string") {
265
+ out[key] = sanitizeDoc(item);
266
+ }
267
+ else if (key === "$ref" && typeof item === "string") {
268
+ out[key] = rewriteRef(item, renameMap);
269
+ }
270
+ else if (key === "$defs" && isObject(item)) {
271
+ const defs = Object.create(null);
272
+ for (const [defKey, defValue] of Object.entries(item))
273
+ defs[renameMap.get(defKey) ?? defKey] = rewriteForCompile(defValue, renameMap, marker);
274
+ out[key] = defs;
275
+ }
276
+ else {
277
+ out[key] = rewriteForCompile(item, renameMap, marker);
278
+ }
279
+ }
280
+ return out;
281
+ }
282
+ function rewriteRef(ref, renameMap) {
283
+ if (!ref.startsWith(REF_PREFIX))
284
+ return ref;
285
+ const name = ref.slice(REF_PREFIX.length);
286
+ const mapped = renameMap.get(name);
287
+ return mapped === undefined ? ref : `${REF_PREFIX}${mapped}`;
288
+ }
289
+ /**
290
+ * Fail if the compiled output contains `any` in a type position. With
291
+ * `unknownAny: true` this should never happen; it is a defense-in-depth check so
292
+ * a compiler regression can never silently widen a generated type to `any`.
293
+ */
294
+ function assertNoAny(text, where) {
295
+ if (containsAnyType(text))
296
+ throw new GeneratorError(`${where}: generated TypeScript contains 'any'; refusing to emit an unsound type`);
297
+ }
298
+ const IDENT_CHAR = /[A-Za-z0-9_$]/;
299
+ /**
300
+ * Lexically scan `text` for a bare `any` type token. This is comment- and
301
+ * string-aware: an `any` inside a line/block comment or a quoted string
302
+ * (including an `enum`/`const` value literally `"any"`) is skipped, and a `//`
303
+ * or `/*` inside a string literal is not mistaken for a comment. An `any`
304
+ * identifier immediately followed (past whitespace) by `:` or `?` is a property
305
+ * name, not the `any` type, so it is ignored; any other bare `any` token in code
306
+ * is a real `any` type.
307
+ */
308
+ export function containsAnyType(text) {
309
+ const n = text.length;
310
+ let i = 0;
311
+ while (i < n) {
312
+ const ch = text[i];
313
+ // Line comment.
314
+ if (ch === "/" && text[i + 1] === "/") {
315
+ i += 2;
316
+ while (i < n && text[i] !== "\n")
317
+ i += 1;
318
+ continue;
319
+ }
320
+ // Block comment.
321
+ if (ch === "/" && text[i + 1] === "*") {
322
+ i += 2;
323
+ while (i < n && !(text[i] === "*" && text[i + 1] === "/"))
324
+ i += 1;
325
+ i += 2;
326
+ continue;
327
+ }
328
+ // String literal (single, double, or template): skip to the matching
329
+ // delimiter, honoring backslash escapes.
330
+ if (ch === '"' || ch === "'" || ch === "`") {
331
+ const quote = ch;
332
+ i += 1;
333
+ while (i < n && text[i] !== quote)
334
+ i += text[i] === "\\" ? 2 : 1;
335
+ i += 1;
336
+ continue;
337
+ }
338
+ // Identifier run in code.
339
+ if (IDENT_CHAR.test(ch)) {
340
+ const start = i;
341
+ while (i < n && IDENT_CHAR.test(text[i]))
342
+ i += 1;
343
+ if (text.slice(start, i) === "any") {
344
+ // A property name (`any:` / `any?:`) is not the `any` type.
345
+ let j = i;
346
+ while (j < n && /\s/.test(text[j]))
347
+ j += 1;
348
+ const next = text[j];
349
+ if (next !== ":" && next !== "?")
350
+ return true;
351
+ }
352
+ continue;
353
+ }
354
+ i += 1;
355
+ }
356
+ return false;
357
+ }
@@ -0,0 +1,2 @@
1
+ import type { DefaultVersions, Operations } from "./operations.js";
2
+ export declare const catalog: import("@zocomputer/system-api/core").TypedCatalog<Operations, DefaultVersions>;