@pulse-compute/wasm-schema-json 0.0.0 → 1.0.0-beta.1

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.
@@ -0,0 +1,1675 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const { getDefaultArtifactsDir } = require('@pulse-compute/wasm-build-support/artifacts-dir');
8
+ const { spawnSync } = require('node:child_process');
9
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadContractsDiagnostics();
10
+ const { resolveAsc } = require('@pulse-compute/wasm-build-support/assemblyscript-compile');
11
+ const {
12
+ SCHEMA_JSON_SIDECAR_VERSION,
13
+ SCHEMA_JSON_PLAN_VERSION,
14
+ SCHEMA_JSON_REPORT_VERSION,
15
+ SCHEMA_JSON_SMOKE_VERSION,
16
+ SCHEMA_JSON_REGISTRY_VERSION,
17
+ SCHEMA_JSON_ABI_VERSION,
18
+ SCHEMA_JSON_BODY_POLICY_VERSION,
19
+ PHASE,
20
+ SCALAR_TYPES,
21
+ RESERVED_SCALAR_TYPES,
22
+ CONTENT_TYPE_POLICIES,
23
+ DEFAULT_SCHEMA_JSON_NAMESPACE,
24
+ DEFAULT_SCHEMA_JSON_CONTENT_TYPE_POLICY,
25
+ lowerScalar,
26
+ descriptorNeedsV2,
27
+ shouldUseSchemaJsonV2
28
+ } = loadContractsSchemaJsonV2();
29
+
30
+
31
+ function loadContractsDiagnostics() {
32
+ try {
33
+ return require('@pulse-compute/wasm-contracts/diagnostics');
34
+ } catch (error) {
35
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
36
+ return require('../../../contracts/src/diagnostics.js');
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ function loadContractsSchemaJsonV2() {
43
+ try {
44
+ return require('@pulse-compute/wasm-contracts/schema-json/v2');
45
+ } catch (error) {
46
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
47
+ return require('../../../contracts/src/schema-json/v2.js');
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ function sha256Text(text) {
54
+ return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
55
+ }
56
+ function sha256Buffer(buffer) {
57
+ return crypto.createHash('sha256').update(buffer).digest('hex');
58
+ }
59
+ function makeFile(file, text) {
60
+ return { file, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text), text };
61
+ }
62
+ function stableSlash(value) { return String(value || '').replace(/\\/g, '/'); }
63
+ function kebab(value) {
64
+ return String(value || 'schema')
65
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
66
+ .replace(/[^A-Za-z0-9]+/g, '-')
67
+ .replace(/^-+|-+$/g, '')
68
+ .toLowerCase() || 'schema';
69
+ }
70
+ function safeIdentifier(value, fallback = 'Schema') {
71
+ const raw = String(value || fallback).replace(/[^A-Za-z0-9_$]/g, '_');
72
+ const cleaned = raw.length > 0 ? raw : fallback;
73
+ return /^[A-Za-z_$]/.test(cleaned) ? cleaned : `_${cleaned}`;
74
+ }
75
+ function namespaceIdentifier(namespace) {
76
+ return safeIdentifier(String(namespace || 'app').replace(/[^A-Za-z0-9_$]/g, '_'), 'app');
77
+ }
78
+ function schemaSymbol(schema) {
79
+ return `${namespaceIdentifier(schema.namespace)}_${safeIdentifier(schema.type)}`;
80
+ }
81
+ function asString(value) { return JSON.stringify(String(value ?? '')); }
82
+ function isPlainObject(value) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }
83
+ function plainObject(value) { return isPlainObject(value) ? value : {}; }
84
+ function configArtifact(options = {}) { return options.resolvedConfig?.artifact || options.resolvedConfig || {}; }
85
+ function jsonConfig(config) {
86
+ const runtime = plainObject(config.runtime);
87
+ const payload = plainObject(runtime.payload);
88
+ return plainObject(payload.json || config.json);
89
+ }
90
+ function clone(value) { return JSON.parse(JSON.stringify(value)); }
91
+
92
+ function makeDiagnostic(code, message, hint, details) {
93
+ return normalizeDiagnostic({
94
+ severity: 'error',
95
+ phase: 'schema-json-sidecar',
96
+ code,
97
+ message,
98
+ hint,
99
+ details,
100
+ loc: { file: '<config>' }
101
+ });
102
+ }
103
+ function makeWarning(code, message, hint, details) {
104
+ return normalizeDiagnostic({
105
+ severity: 'warning',
106
+ phase: 'schema-json-sidecar',
107
+ code,
108
+ message,
109
+ hint,
110
+ details,
111
+ loc: { file: '<config>' }
112
+ });
113
+ }
114
+
115
+ function pathString(pathSegments) {
116
+ return pathSegments.length ? pathSegments.join('.') : '<root>';
117
+ }
118
+ function accessorBase(pathSegments) {
119
+ return pathSegments.map((segment) => safeIdentifier(segment)).join('__') || 'value';
120
+ }
121
+ function hasAccessorName(pathSegments) {
122
+ if (pathSegments.length <= 1) return `has_${accessorBase(pathSegments)}`;
123
+ const prefix = pathSegments.slice(0, -1).map((segment) => safeIdentifier(segment)).join('__');
124
+ return `${prefix}__has_${safeIdentifier(pathSegments[pathSegments.length - 1])}`;
125
+ }
126
+ function nullAccessorName(pathSegments) {
127
+ return `${accessorBase(pathSegments)}_is_null`;
128
+ }
129
+ function refAccessorName(pathSegments) {
130
+ return `${accessorBase(pathSegments)}_ref`;
131
+ }
132
+ function lenAccessorName(pathSegments) {
133
+ return `${accessorBase(pathSegments)}_len`;
134
+ }
135
+ function atAccessorName(pathSegments) {
136
+ return `${accessorBase(pathSegments)}_at`;
137
+ }
138
+ function upperJoin(pathSegments) {
139
+ if (!pathSegments.length) return 'Root';
140
+ return pathSegments.map((segment) => safeIdentifier(segment[0].toUpperCase() + segment.slice(1))).join('_');
141
+ }
142
+ function objectClassName(schema, pathSegments) {
143
+ return `${safeIdentifier(schema.type)}${pathSegments.length ? `_${upperJoin(pathSegments)}` : ''}Object`;
144
+ }
145
+ function objectParseFn(pathSegments) {
146
+ return safeIdentifier(`parse_object_${pathSegments.length ? pathSegments.map((segment) => kebab(segment).replace(/-/g, '_')).join('__') : 'root'}`);
147
+ }
148
+ function objectSerializeFn(pathSegments) {
149
+ return safeIdentifier(`serialize_object_${pathSegments.length ? pathSegments.map((segment) => kebab(segment).replace(/-/g, '_')).join('__') : 'root'}`);
150
+ }
151
+ function arrayParseFn(pathSegments) {
152
+ return safeIdentifier(`parse_array_${pathSegments.map((segment) => kebab(segment).replace(/-/g, '_')).join('__')}`);
153
+ }
154
+ function arraySerializeFn(pathSegments) {
155
+ return safeIdentifier(`serialize_array_${pathSegments.map((segment) => kebab(segment).replace(/-/g, '_')).join('__')}`);
156
+ }
157
+ function enumParseFn(pathSegments) {
158
+ return safeIdentifier(`parse_enum_${pathSegments.map((segment) => kebab(segment).replace(/-/g, '_')).join('__')}`);
159
+ }
160
+
161
+ function storageInfo(node) {
162
+ if (node.kind === 'scalar') {
163
+ if (node.type === 'string') return { storageType: 'string', defaultValue: '""' };
164
+ if (node.type === 'bool') return { storageType: 'bool', defaultValue: 'false' };
165
+ if (node.type === 'f64') return { storageType: 'f64', defaultValue: '0.0' };
166
+ return { storageType: node.type, defaultValue: '0' };
167
+ }
168
+ if (node.kind === 'enum') return { storageType: 'string', defaultValue: '""' };
169
+ return { storageType: 'usize', defaultValue: '0' };
170
+ }
171
+
172
+ function normalizeEnumValues(raw, diagnostics, details) {
173
+ if (!Array.isArray(raw) || raw.length === 0 || raw.some((value) => typeof value !== 'string' || !value.length)) {
174
+ diagnostics.push(makeDiagnostic(
175
+ 'PULSEWASM_SCHEMA_JSON_ENUM_INVALID',
176
+ `Invalid enum descriptor for ${details.path}.`,
177
+ 'Use { enum: ["a", "b"] } with at least one string literal value.',
178
+ details
179
+ ));
180
+ return [''];
181
+ }
182
+ return Array.from(new Set(raw.map(String)));
183
+ }
184
+
185
+ function normalizeNode(raw, diagnostics, details, options = {}) {
186
+ if (typeof raw === 'string') {
187
+ const lowered = lowerScalar(raw);
188
+ if (!SCALAR_TYPES.has(raw) && !SCALAR_TYPES.has(lowered)) {
189
+ diagnostics.push(makeDiagnostic(
190
+ RESERVED_SCALAR_TYPES.has(raw) ? 'PULSEWASM_SCHEMA_JSON_UNSUPPORTED_FIELD_TYPE' : 'PULSEWASM_SCHEMA_JSON_UNSUPPORTED_FIELD_TYPE',
191
+ `Unsupported schema field type ${JSON.stringify(raw)} at ${details.path}.`,
192
+ 'Phase 14 explicit schema v2 supports string, bool/boolean, i32, u32, f64/number, arrays, nested objects, optionals, nullable fields, and string enums.',
193
+ details
194
+ ));
195
+ return { kind: 'scalar', type: 'string' };
196
+ }
197
+ return { kind: 'scalar', type: lowered };
198
+ }
199
+ if (Array.isArray(raw)) {
200
+ if (raw.length !== 1) {
201
+ diagnostics.push(makeDiagnostic(
202
+ 'PULSEWASM_SCHEMA_JSON_ARRAY_DESCRIPTOR_INVALID',
203
+ `Array descriptor at ${details.path} must contain exactly one item descriptor.`,
204
+ 'Use ["string"] or [{ type: "object", fields: { ... } }].',
205
+ details
206
+ ));
207
+ return { kind: 'array', item: { kind: 'scalar', type: 'string' } };
208
+ }
209
+ return { kind: 'array', item: normalizeNode(raw[0], diagnostics, { ...details, path: `${details.path}[]` }, { arrayItem: true }) };
210
+ }
211
+ if (isPlainObject(raw)) {
212
+ if (Object.prototype.hasOwnProperty.call(raw, 'enum')) {
213
+ return { kind: 'enum', values: normalizeEnumValues(raw.enum, diagnostics, details) };
214
+ }
215
+ if (Object.prototype.hasOwnProperty.call(raw, 'fields') || raw.type === 'object') {
216
+ const fields = [];
217
+ const fieldMap = plainObject(raw.fields);
218
+ for (const [name, child] of Object.entries(fieldMap)) fields.push(normalizeField(name, child, diagnostics, { ...details, path: `${details.path}.${name}` }, options.schema));
219
+ return { kind: 'object', fields };
220
+ }
221
+ if (Array.isArray(raw.type)) return normalizeNode(raw.type, diagnostics, details, options);
222
+ if (isPlainObject(raw.type)) return normalizeNode(raw.type, diagnostics, details, options);
223
+ if (typeof raw.type === 'string') return normalizeNode(raw.type, diagnostics, details, options);
224
+ diagnostics.push(makeDiagnostic(
225
+ 'PULSEWASM_SCHEMA_JSON_DESCRIPTOR_INVALID',
226
+ `Unsupported schema descriptor at ${details.path}.`,
227
+ 'Use a scalar type string, [itemType], { type, required, nullable }, { fields: { ... } }, or { enum: [...] }.',
228
+ details
229
+ ));
230
+ return { kind: 'scalar', type: 'string' };
231
+ }
232
+ diagnostics.push(makeDiagnostic(
233
+ 'PULSEWASM_SCHEMA_JSON_DESCRIPTOR_INVALID',
234
+ `Unsupported schema descriptor at ${details.path}.`,
235
+ 'Use a scalar type string, [itemType], { type, required, nullable }, { fields: { ... } }, or { enum: [...] }.',
236
+ details
237
+ ));
238
+ return { kind: 'scalar', type: 'string' };
239
+ }
240
+
241
+ function normalizeField(name, raw, diagnostics, details, schema) {
242
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
243
+ diagnostics.push(makeDiagnostic(
244
+ 'PULSEWASM_SCHEMA_JSON_FIELD_NAME_UNSUPPORTED',
245
+ `Schema field ${JSON.stringify(name)} is not a supported AS identifier.`,
246
+ 'Use simple identifier field names for explicit schema v2 sidecars.',
247
+ { schema: details.schema, field: name }
248
+ ));
249
+ }
250
+ let required = true;
251
+ let nullable = false;
252
+ let descriptor = raw;
253
+ if (isPlainObject(raw)) {
254
+ if (raw.required === false) required = false;
255
+ if (raw.nullable === true) nullable = true;
256
+ if (Object.prototype.hasOwnProperty.call(raw, 'enum')) descriptor = { enum: raw.enum };
257
+ else if (Object.prototype.hasOwnProperty.call(raw, 'fields') || raw.type === 'object') descriptor = { type: 'object', fields: raw.fields };
258
+ else if (Array.isArray(raw.type)) descriptor = raw.type;
259
+ else if (plainObject(raw.type)) descriptor = raw.type;
260
+ else if (typeof raw.type === 'string') descriptor = raw.type;
261
+ }
262
+ const node = normalizeNode(descriptor, diagnostics, details, { schema });
263
+ const storage = storageInfo(node);
264
+ return {
265
+ name,
266
+ ident: safeIdentifier(name),
267
+ required,
268
+ nullable,
269
+ node,
270
+ hasFlag: !required,
271
+ nullFlag: nullable,
272
+ storageType: storage.storageType,
273
+ defaultValue: storage.defaultValue,
274
+ pathSegments: [],
275
+ baseAccessor: '',
276
+ hasAccessor: null,
277
+ nullAccessor: null,
278
+ refAccessor: null,
279
+ lenAccessor: null,
280
+ atAccessor: null
281
+ };
282
+ }
283
+
284
+ function enrichSchema(schema) {
285
+ schema.objects = [];
286
+ schema.arrays = [];
287
+ schema.enums = [];
288
+ schema.root = { kind: 'object', fields: schema.fields };
289
+
290
+ function visitNode(node, pathSegments) {
291
+ if (node.kind === 'object') {
292
+ if (node.className) return;
293
+ node.pathSegments = pathSegments.slice();
294
+ node.className = objectClassName(schema, pathSegments);
295
+ node.parseFunction = objectParseFn(pathSegments);
296
+ node.serializeFunction = objectSerializeFn(pathSegments);
297
+ schema.objects.push(node);
298
+ for (const field of node.fields) {
299
+ field.pathSegments = pathSegments.concat(field.name);
300
+ field.baseAccessor = accessorBase(field.pathSegments);
301
+ field.hasAccessor = field.hasFlag ? hasAccessorName(field.pathSegments) : null;
302
+ field.nullAccessor = field.nullFlag ? nullAccessorName(field.pathSegments) : null;
303
+ field.refAccessor = field.node.kind === 'object' || field.node.kind === 'array' ? refAccessorName(field.pathSegments) : null;
304
+ field.lenAccessor = field.node.kind === 'array' ? lenAccessorName(field.pathSegments) : null;
305
+ field.atAccessor = field.node.kind === 'array' ? atAccessorName(field.pathSegments) : null;
306
+ visitNode(field.node, field.pathSegments);
307
+ }
308
+ return;
309
+ }
310
+ if (node.kind === 'array') {
311
+ if (node.parseFunction) return;
312
+ node.pathSegments = pathSegments.slice();
313
+ node.parseFunction = arrayParseFn(pathSegments);
314
+ node.serializeFunction = arraySerializeFn(pathSegments);
315
+ schema.arrays.push(node);
316
+ visitNode(node.item, pathSegments.concat('item'));
317
+ return;
318
+ }
319
+ if (node.kind === 'enum') {
320
+ if (node.parseFunction) return;
321
+ node.pathSegments = pathSegments.slice();
322
+ node.parseFunction = enumParseFn(pathSegments);
323
+ schema.enums.push(node);
324
+ }
325
+ }
326
+
327
+ visitNode(schema.root, []);
328
+ }
329
+
330
+ function normalizeSchemasV2(config) {
331
+ const json = jsonConfig(config);
332
+ const target = typeof json.target === 'string' && json.target ? json.target : 'generic';
333
+ const schemas = Array.isArray(json.schemas) ? json.schemas : [];
334
+ const defaultNamespace = typeof json.defaultNamespace === 'string' && json.defaultNamespace ? json.defaultNamespace : DEFAULT_SCHEMA_JSON_NAMESPACE;
335
+ const contentTypePolicy = typeof json.contentTypePolicy === 'string' && json.contentTypePolicy ? json.contentTypePolicy : DEFAULT_SCHEMA_JSON_CONTENT_TYPE_POLICY;
336
+ const schemaVersion = typeof json.schemaVersion === 'string' && json.schemaVersion ? json.schemaVersion : undefined;
337
+ const diagnostics = [];
338
+ const warnings = [];
339
+ const normalized = [];
340
+ if (!CONTENT_TYPE_POLICIES.has(contentTypePolicy)) {
341
+ diagnostics.push(makeDiagnostic(
342
+ 'PULSEWASM_SCHEMA_JSON_CONTENT_TYPE_POLICY_UNSUPPORTED',
343
+ `Unsupported schema JSON contentTypePolicy ${JSON.stringify(contentTypePolicy)}.`,
344
+ 'Use "accept-json-or-missing" or "require-json". "ignore-content-type" remains reserved.',
345
+ { contentTypePolicy }
346
+ ));
347
+ }
348
+ const richDescriptor = schemas.some((schema) => Object.values(plainObject(schema.fields)).some((field) => descriptorNeedsV2(field)) || isPlainObject(schema.fields) && descriptorNeedsV2(schema.fields));
349
+ const effectiveTarget = target === 'auto' && schemas.length > 0 ? 'schema' : target;
350
+ if (effectiveTarget !== 'schema') {
351
+ warnings.push(makeWarning(
352
+ 'PULSEWASM_SCHEMA_JSON_INACTIVE',
353
+ 'Schema JSON sidecar generation is inactive because json.target is not "schema".',
354
+ 'Use json.target = "schema" with json.schemas to enable schema sidecar generation.',
355
+ { target }
356
+ ));
357
+ return { target, effectiveTarget, schemaVersion: schemaVersion || (richDescriptor ? 'v2' : undefined), defaultNamespace, contentTypePolicy, schemas: normalized, diagnostics, warnings, active: false };
358
+ }
359
+ if (schemaVersion !== 'v2') {
360
+ diagnostics.push(makeDiagnostic(
361
+ 'PULSEWASM_SCHEMA_JSON_V2_REQUIRED',
362
+ 'Explicit schema v2 features require json.schemaVersion = "v2".',
363
+ 'Set runtime.payload.json.schemaVersion = "v2" before using arrays, nested objects, optional fields, nullable fields, or enum descriptors.',
364
+ { schemaVersion, richDescriptor }
365
+ ));
366
+ return { target, effectiveTarget, schemaVersion, defaultNamespace, contentTypePolicy, schemas: normalized, diagnostics, warnings, active: true };
367
+ }
368
+ if (schemas.length === 0) {
369
+ diagnostics.push(makeDiagnostic(
370
+ 'PULSEWASM_SCHEMA_JSON_SCHEMAS_REQUIRED',
371
+ 'json.target = "schema" requires at least one schema declaration.',
372
+ 'Add json.schemas = [{ namespace, name, type, source, fields }].',
373
+ { target }
374
+ ));
375
+ }
376
+ const ids = new Set();
377
+ const namesByNamespace = new Map();
378
+ let defaultNamespaceSeen = false;
379
+ for (const [index, schema] of schemas.entries()) {
380
+ const namespace = typeof schema.namespace === 'string' && schema.namespace ? schema.namespace : defaultNamespace;
381
+ const name = typeof schema.name === 'string' && schema.name ? schema.name : schema.type;
382
+ const type = typeof schema.type === 'string' && schema.type ? schema.type : name;
383
+ const source = typeof schema.source === 'string' ? schema.source : undefined;
384
+ const codec = typeof schema.codec === 'string' && schema.codec ? schema.codec : 'json';
385
+ const mediaTypes = Array.isArray(schema.mediaTypes) && schema.mediaTypes.length ? schema.mediaTypes.map(String) : ['application/json', 'application/*+json'];
386
+ if (namespace === defaultNamespace) defaultNamespaceSeen = true;
387
+ if (!name || !type) {
388
+ diagnostics.push(makeDiagnostic(
389
+ 'PULSEWASM_SCHEMA_JSON_NAME_REQUIRED',
390
+ 'Schema declarations require a static name/type.',
391
+ 'Use { namespace: "app", name: "CreateUserBodyV2", type: "CreateUserBodyV2", fields: { ... } }.',
392
+ { index, schema }
393
+ ));
394
+ continue;
395
+ }
396
+ if (codec !== 'json') {
397
+ diagnostics.push(makeDiagnostic(
398
+ 'PULSEWASM_SCHEMA_JSON_CODEC_UNSUPPORTED',
399
+ `Schema ${JSON.stringify(name)} uses unsupported codec ${JSON.stringify(codec)}.`,
400
+ 'Phase 14 explicit schema v2 supports codec: "json" only.',
401
+ { namespace, name, codec }
402
+ ));
403
+ }
404
+ const id = `${namespace}.${name}`;
405
+ if (ids.has(id)) {
406
+ diagnostics.push(makeDiagnostic(
407
+ 'PULSEWASM_SCHEMA_JSON_DUPLICATE_ID',
408
+ `Duplicate schema id ${JSON.stringify(id)}.`,
409
+ 'Schema ids are namespace + name and must be globally unique.',
410
+ { id, index }
411
+ ));
412
+ }
413
+ ids.add(id);
414
+ const nsNames = namesByNamespace.get(namespace) || new Set();
415
+ if (nsNames.has(name)) {
416
+ diagnostics.push(makeDiagnostic(
417
+ 'PULSEWASM_SCHEMA_JSON_AMBIGUOUS_NAME',
418
+ `Schema name ${JSON.stringify(name)} appears more than once in namespace ${JSON.stringify(namespace)}.`,
419
+ 'Use unique schema names inside a namespace.',
420
+ { namespace, name }
421
+ ));
422
+ }
423
+ nsNames.add(name);
424
+ namesByNamespace.set(namespace, nsNames);
425
+ const fieldMap = plainObject(schema.fields);
426
+ const fields = [];
427
+ for (const [fieldName, fieldValue] of Object.entries(fieldMap)) fields.push(normalizeField(fieldName, fieldValue, diagnostics, { schema: id, path: fieldName }, schema));
428
+ if (fields.length === 0) {
429
+ diagnostics.push(makeDiagnostic(
430
+ 'PULSEWASM_SCHEMA_JSON_FIELDS_REQUIRED',
431
+ `Schema ${JSON.stringify(id)} requires explicit fields.`,
432
+ 'Use fields: { name: "string", tags: ["string"], profile: { type: "object", fields: { ... } } } for explicit schema v2 sidecars.',
433
+ { id }
434
+ ));
435
+ }
436
+ const normalizedSchema = {
437
+ id,
438
+ namespace,
439
+ name,
440
+ type,
441
+ source,
442
+ codec,
443
+ mediaTypes,
444
+ schemaVersion: 'v2',
445
+ fields,
446
+ functionBase: safeIdentifier(type),
447
+ fileBase: kebab(type),
448
+ namespaceBase: kebab(namespace),
449
+ moduleFile: `generated/as/schema-json/${kebab(namespace)}/${kebab(type)}.as.ts`
450
+ };
451
+ enrichSchema(normalizedSchema);
452
+ normalized.push(normalizedSchema);
453
+ }
454
+ if (schemas.length > 0 && !defaultNamespaceSeen) {
455
+ warnings.push(makeWarning(
456
+ 'PULSEWASM_SCHEMA_JSON_DEFAULT_NAMESPACE_EMPTY',
457
+ `Default namespace ${JSON.stringify(defaultNamespace)} has no schemas.`,
458
+ 'Unqualified ctx.req.parse() / schema names remain reserved; use explicit namespace.Schema ids.',
459
+ { defaultNamespace }
460
+ ));
461
+ }
462
+ return { target, schemaVersion: 'v2', defaultNamespace, contentTypePolicy, schemas: normalized, diagnostics, warnings, active: true };
463
+ }
464
+
465
+ function primitiveSample(node, nameHint = 'value') {
466
+ if (node.kind === 'scalar') {
467
+ if (node.type === 'string') return nameHint === 'name' ? 'Ada' : `${nameHint}-value`;
468
+ if (node.type === 'bool') return true;
469
+ if (node.type === 'f64') return 42.5;
470
+ return 42;
471
+ }
472
+ if (node.kind === 'enum') return node.values[0];
473
+ if (node.kind === 'array') return [primitiveSample(node.item, `${nameHint}0`), primitiveSample(node.item, `${nameHint}1`)];
474
+ if (node.kind === 'object') {
475
+ const value = {};
476
+ for (const field of node.fields) {
477
+ if (!field.required) continue;
478
+ if (field.nullable) value[field.name] = null;
479
+ else value[field.name] = primitiveSample(field.node, field.name);
480
+ }
481
+ return value;
482
+ }
483
+ return null;
484
+ }
485
+
486
+ function sampleForObject(node) {
487
+ const out = {};
488
+ for (const field of node.fields) {
489
+ if (!field.required) {
490
+ if (field.nullable) {
491
+ if (field.node.kind === 'object') out[field.name] = sampleForNode(field.node, field.name);
492
+ else out[field.name] = sampleForNode(field.node, field.name);
493
+ }
494
+ continue;
495
+ }
496
+ out[field.name] = sampleForNode(field.node, field.name);
497
+ }
498
+ return out;
499
+ }
500
+ function sampleForNode(node, nameHint = 'value') {
501
+ if (node.kind === 'object') return sampleForObject(node);
502
+ if (node.kind === 'array') {
503
+ const a = [];
504
+ if (node.item.kind === 'object') a.push(sampleForNode(node.item, nameHint));
505
+ else {
506
+ a.push(sampleForNode(node.item, `${nameHint}0`));
507
+ a.push(sampleForNode(node.item, `${nameHint}1`));
508
+ }
509
+ return a;
510
+ }
511
+ if (node.kind === 'enum') return node.values[0];
512
+ return primitiveSample(node, nameHint);
513
+ }
514
+
515
+ function deepDelete(target, pathSegments) {
516
+ if (!pathSegments.length) return;
517
+ let cursor = target;
518
+ for (let i = 0; i < pathSegments.length - 1; i += 1) {
519
+ if (!cursor || typeof cursor !== 'object') return;
520
+ cursor = cursor[pathSegments[i]];
521
+ }
522
+ if (cursor && typeof cursor === 'object') delete cursor[pathSegments[pathSegments.length - 1]];
523
+ }
524
+ function deepSet(target, pathSegments, value) {
525
+ if (!pathSegments.length) return;
526
+ let cursor = target;
527
+ for (let i = 0; i < pathSegments.length - 1; i += 1) {
528
+ const key = pathSegments[i];
529
+ if (!cursor[key] || typeof cursor[key] !== 'object') cursor[key] = {};
530
+ cursor = cursor[key];
531
+ }
532
+ cursor[pathSegments[pathSegments.length - 1]] = value;
533
+ }
534
+ function findField(schema, predicate) {
535
+ let found = null;
536
+ function visitObject(node) {
537
+ for (const field of node.fields) {
538
+ if (predicate(field)) { found = field; return; }
539
+ if (field.node.kind === 'object') visitObject(field.node);
540
+ else if (field.node.kind === 'array' && field.node.item.kind === 'object') visitObject(field.node.item);
541
+ if (found) return;
542
+ }
543
+ }
544
+ visitObject(schema.root);
545
+ return found;
546
+ }
547
+ function findNestedObjectWithScalar(schema) {
548
+ let found = null;
549
+ function visitObject(node) {
550
+ for (const field of node.fields) {
551
+ if (field.node.kind === 'object') {
552
+ const scalarField = field.node.fields.find((child) => child.node.kind === 'scalar' || child.node.kind === 'enum');
553
+ if (scalarField) { found = { parent: field, child: scalarField }; return; }
554
+ visitObject(field.node);
555
+ }
556
+ if (found) return;
557
+ }
558
+ }
559
+ visitObject(schema.root);
560
+ return found;
561
+ }
562
+
563
+ function escapeControl(code) {
564
+ let hex = code.toString(16).toUpperCase();
565
+ while (hex.length < 4) hex = `0${hex}`;
566
+ return `\\u${hex}`;
567
+ }
568
+
569
+ function emitObjectParsers(schema, lines) {
570
+ for (const objectNode of schema.objects) {
571
+ const fieldPathExpr = objectNode.pathSegments.length ? 'fieldPath' : asString(schema.id);
572
+ lines.push(`function ${objectNode.parseFunction}(parser: Parser, fieldPath: string): usize {`);
573
+ lines.push(' if (!parser.expectChar(123, "Expected { for object value.")) return 0;');
574
+ lines.push(` const value = new ${objectNode.className}();`);
575
+ for (const field of objectNode.fields) lines.push(` let seen_${field.ident}: bool = false;`);
576
+ lines.push(' parser.skipWs();');
577
+ lines.push(' if (!parser.consumeChar(125)) {');
578
+ lines.push(' while (parser.errorRef == 0) {');
579
+ lines.push(' const key = parser.parseKey();');
580
+ lines.push(' if (parser.errorRef != 0) return 0;');
581
+ lines.push(' if (!parser.expectChar(58, "Expected : after object key.")) return 0;');
582
+ lines.push(' if (false) {}');
583
+ for (const field of objectNode.fields) {
584
+ const fieldPath = objectNode.pathSegments.length ? `fieldPath + ".${field.name}"` : asString(field.name);
585
+ lines.push(` else if (key == ${asString(field.name)}) {`);
586
+ lines.push(` seen_${field.ident} = true;`);
587
+ if (field.hasFlag) lines.push(` value.__has_${field.ident} = true;`);
588
+ if (field.nullFlag) lines.push(` value.__null_${field.ident} = false;`);
589
+ if (field.nullFlag) {
590
+ lines.push(' if (parser.peek() == 110) {');
591
+ lines.push(` if (parser.consumeNull()) { value.__null_${field.ident} = true; value.${field.ident} = ${field.defaultValue}; }`);
592
+ lines.push(` else parser.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + ${fieldPath} + ".", 400);`);
593
+ lines.push(' } else {');
594
+ lines.push(` value.${field.ident} = ${parseNodeCall(field.node, 'parser', fieldPath)};`);
595
+ lines.push(' }');
596
+ } else {
597
+ lines.push(` value.${field.ident} = ${parseNodeCall(field.node, 'parser', fieldPath)};`);
598
+ }
599
+ lines.push(' }');
600
+ }
601
+ lines.push(' else {');
602
+ lines.push(' parser.skipValue();');
603
+ lines.push(' }');
604
+ lines.push(' if (parser.errorRef != 0) return 0;');
605
+ lines.push(' if (parser.consumeChar(44)) continue;');
606
+ lines.push(' if (parser.consumeChar(125)) break;');
607
+ lines.push(' parser.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Expected , or } in object value.", 400);');
608
+ lines.push(' return 0;');
609
+ lines.push(' }');
610
+ lines.push(' }');
611
+ for (const field of objectNode.fields) {
612
+ if (field.required) lines.push(` if (!seen_${field.ident}) { parser.fail("PULSEWASM_SCHEMA_JSON_MISSING_FIELD", "Missing required field ${fieldPathString(field.pathSegments)}.", 400); return 0; }`);
613
+ }
614
+ lines.push(' return changetype<usize>(value);');
615
+ lines.push('}');
616
+ lines.push('');
617
+ }
618
+ }
619
+
620
+ function fieldPathString(pathSegments) {
621
+ return pathSegments.join('.');
622
+ }
623
+
624
+ function emitEnumParsers(schema, lines) {
625
+ for (const enumNode of schema.enums) {
626
+ lines.push(`function ${enumNode.parseFunction}(parser: Parser, fieldPath: string): string {`);
627
+ lines.push(' const value = parser.parseString(fieldPath);');
628
+ lines.push(' if (parser.errorRef != 0) return "";');
629
+ let first = true;
630
+ for (const entry of enumNode.values) {
631
+ lines.push(` ${first ? 'if' : 'else if'} (value == ${asString(entry)}) return value;`);
632
+ first = false;
633
+ }
634
+ lines.push(' parser.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".", 400);');
635
+ lines.push(' return "";');
636
+ lines.push('}');
637
+ lines.push('');
638
+ }
639
+ }
640
+
641
+ function arrayStorageClass(node) {
642
+ if (node.kind === 'scalar') {
643
+ if (node.type === 'string') return 'StringArrayRef';
644
+ if (node.type === 'bool') return 'BoolArrayRef';
645
+ if (node.type === 'i32') return 'I32ArrayRef';
646
+ if (node.type === 'u32') return 'U32ArrayRef';
647
+ if (node.type === 'f64') return 'F64ArrayRef';
648
+ }
649
+ if (node.kind === 'enum') return 'StringArrayRef';
650
+ return 'RefArrayRef';
651
+ }
652
+
653
+ function emitArrayParsers(schema, lines) {
654
+ for (const arrayNode of schema.arrays) {
655
+ const storageClass = arrayStorageClass(arrayNode.item);
656
+ lines.push(`function ${arrayNode.parseFunction}(parser: Parser, fieldPath: string): usize {`);
657
+ lines.push(' if (!parser.expectChar(91, "Expected [ for array value.")) return 0;');
658
+ lines.push(` const values = new ${storageClass}();`);
659
+ lines.push(' let index: i32 = 0;');
660
+ lines.push(' parser.skipWs();');
661
+ lines.push(' if (!parser.consumeChar(93)) {');
662
+ lines.push(' while (parser.errorRef == 0) {');
663
+ lines.push(' const itemPath = fieldPath + "[" + index.toString() + "]";');
664
+ lines.push(` values.values.push(${parseNodeCall(arrayNode.item, 'parser', 'itemPath')});`);
665
+ lines.push(' if (parser.errorRef != 0) return 0;');
666
+ lines.push(' index += 1;');
667
+ lines.push(' if (parser.consumeChar(44)) continue;');
668
+ lines.push(' if (parser.consumeChar(93)) break;');
669
+ lines.push(' parser.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Expected , or ] in array value.", 400);');
670
+ lines.push(' return 0;');
671
+ lines.push(' }');
672
+ lines.push(' }');
673
+ lines.push(' return changetype<usize>(values);');
674
+ lines.push('}');
675
+ lines.push('');
676
+ }
677
+ }
678
+
679
+ function parseNodeCall(node, parserVar, fieldPathExpr) {
680
+ if (node.kind === 'scalar') {
681
+ if (node.type === 'string') return `${parserVar}.parseString(${fieldPathExpr})`;
682
+ if (node.type === 'bool') return `${parserVar}.parseBool(${fieldPathExpr})`;
683
+ if (node.type === 'u32') return `${parserVar}.parseU32(${fieldPathExpr})`;
684
+ if (node.type === 'f64') return `${parserVar}.parseF64(${fieldPathExpr})`;
685
+ return `${parserVar}.parseI32(${fieldPathExpr})`;
686
+ }
687
+ if (node.kind === 'enum') return `${node.parseFunction}(${parserVar}, ${fieldPathExpr})`;
688
+ if (node.kind === 'object' || node.kind === 'array') return `${node.parseFunction}(${parserVar}, ${fieldPathExpr})`;
689
+ return '0';
690
+ }
691
+
692
+ function serializeNodeExpr(node, expr) {
693
+ if (node.kind === 'scalar') {
694
+ if (node.type === 'string') return `"\\\"" + escapeJsonString(${expr}) + "\\\""`;
695
+ if (node.type === 'bool') return `(${expr} ? "true" : "false")`;
696
+ return `${expr}.toString()`;
697
+ }
698
+ if (node.kind === 'enum') return `"\\\"" + escapeJsonString(${expr}) + "\\\""`;
699
+ if (node.kind === 'object') return `${node.serializeFunction}(${expr})`;
700
+ if (node.kind === 'array') return `${node.serializeFunction}(${expr})`;
701
+ return '"null"';
702
+ }
703
+
704
+ function emitObjectSerializers(schema, lines) {
705
+ for (const objectNode of schema.objects) {
706
+ lines.push(`function ${objectNode.serializeFunction}(valueRef: usize): string {`);
707
+ lines.push(' if (valueRef == 0) return "null";');
708
+ lines.push(` const value = changetype<${objectNode.className}>(valueRef);`);
709
+ lines.push(' const parts = new Array<string>();');
710
+ for (const field of objectNode.fields) {
711
+ const fieldValue = `value.${field.ident}`;
712
+ const entryPrefix = asString(`"${field.name}":`);
713
+ if (field.hasFlag) {
714
+ lines.push(` if (value.__has_${field.ident}) {`);
715
+ if (field.nullFlag) {
716
+ lines.push(` if (value.__null_${field.ident}) parts.push(${entryPrefix} + "null");`);
717
+ lines.push(` else parts.push(${entryPrefix} + ${serializeNodeExpr(field.node, fieldValue)});`);
718
+ } else {
719
+ lines.push(` parts.push(${entryPrefix} + ${serializeNodeExpr(field.node, fieldValue)});`);
720
+ }
721
+ lines.push(' }');
722
+ } else if (field.nullFlag) {
723
+ lines.push(` if (value.__null_${field.ident}) parts.push(${entryPrefix} + "null");`);
724
+ lines.push(` else parts.push(${entryPrefix} + ${serializeNodeExpr(field.node, fieldValue)});`);
725
+ } else {
726
+ lines.push(` parts.push(${entryPrefix} + ${serializeNodeExpr(field.node, fieldValue)});`);
727
+ }
728
+ }
729
+ lines.push(' return "{" + parts.join(",") + "}";');
730
+ lines.push('}');
731
+ lines.push('');
732
+ }
733
+ }
734
+
735
+ function emitArraySerializers(schema, lines) {
736
+ for (const arrayNode of schema.arrays) {
737
+ const storageClass = arrayStorageClass(arrayNode.item);
738
+ lines.push(`function ${arrayNode.serializeFunction}(valueRef: usize): string {`);
739
+ lines.push(' if (valueRef == 0) return "[]";');
740
+ lines.push(` const value = changetype<${storageClass}>(valueRef);`);
741
+ lines.push(' const parts = new Array<string>();');
742
+ lines.push(' for (let i = 0; i < value.values.length; i += 1) {');
743
+ lines.push(` parts.push(${serializeNodeExpr(arrayNode.item, 'value.values[i]')});`);
744
+ lines.push(' }');
745
+ lines.push(' return "[" + parts.join(",") + "]";');
746
+ lines.push('}');
747
+ lines.push('');
748
+ }
749
+ }
750
+
751
+ function boolReturnType(node) {
752
+ return node.kind === 'scalar' && node.type === 'bool' ? 'i32' : node.kind === 'scalar' ? node.type : node.kind === 'enum' ? 'string' : 'usize';
753
+ }
754
+ function boolReturnExpr(expr, node) {
755
+ return node.kind === 'scalar' && node.type === 'bool' ? `(${expr} ? 1 : 0)` : expr;
756
+ }
757
+ function emitAccessors(schema, lines) {
758
+ for (const objectNode of schema.objects) {
759
+ lines.push(`// Accessors for ${objectNode.className}`);
760
+ for (const field of objectNode.fields) {
761
+ const valueExpr = `changetype<${objectNode.className}>(valueRef).${field.ident}`;
762
+ if (field.hasAccessor) lines.push(`export function ${field.hasAccessor}(valueRef: usize): i32 { return changetype<${objectNode.className}>(valueRef).__has_${field.ident} ? 1 : 0; }`);
763
+ if (field.nullAccessor) lines.push(`export function ${field.nullAccessor}(valueRef: usize): i32 { return changetype<${objectNode.className}>(valueRef).__null_${field.ident} ? 1 : 0; }`);
764
+ if (field.node.kind === 'object' || field.node.kind === 'array') {
765
+ lines.push(`export function ${field.refAccessor}(valueRef: usize): usize { return ${valueExpr}; }`);
766
+ if (field.node.kind === 'array') {
767
+ const storageClass = arrayStorageClass(field.node.item);
768
+ lines.push(`export function ${field.lenAccessor}(valueRef: usize): i32 { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return 0; return changetype<${storageClass}>(ref).values.length; }`);
769
+ if (field.node.item.kind === 'object' || field.node.item.kind === 'array') {
770
+ lines.push(`export function ${field.atAccessor}(valueRef: usize, index: i32): usize { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return 0; const values = changetype<${storageClass}>(ref).values; return index >= 0 && index < values.length ? values[index] : 0; }`);
771
+ } else if (field.node.item.kind === 'enum' || (field.node.item.kind === 'scalar' && field.node.item.type === 'string')) {
772
+ lines.push(`export function ${field.atAccessor}(valueRef: usize, index: i32): string { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return ""; const values = changetype<${storageClass}>(ref).values; return index >= 0 && index < values.length ? values[index] : ""; }`);
773
+ } else if (field.node.item.kind === 'scalar' && field.node.item.type === 'bool') {
774
+ lines.push(`export function ${field.atAccessor}(valueRef: usize, index: i32): i32 { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return 0; const values = changetype<${storageClass}>(ref).values; return index >= 0 && index < values.length && values[index] ? 1 : 0; }`);
775
+ } else if (field.node.item.kind === 'scalar' && field.node.item.type === 'f64') {
776
+ lines.push(`export function ${field.atAccessor}(valueRef: usize, index: i32): f64 { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return 0.0; const values = changetype<${storageClass}>(ref).values; return index >= 0 && index < values.length ? values[index] : 0.0; }`);
777
+ } else if (field.node.item.kind === 'scalar' && field.node.item.type === 'u32') {
778
+ lines.push(`export function ${field.atAccessor}(valueRef: usize, index: i32): u32 { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return 0; const values = changetype<${storageClass}>(ref).values; return index >= 0 && index < values.length ? values[index] : 0; }`);
779
+ } else {
780
+ lines.push(`export function ${field.atAccessor}(valueRef: usize, index: i32): i32 { const ref = changetype<${objectNode.className}>(valueRef).${field.ident}; if (ref == 0) return 0; const values = changetype<${storageClass}>(ref).values; return index >= 0 && index < values.length ? values[index] : 0; }`);
781
+ }
782
+ }
783
+ } else {
784
+ lines.push(`export function ${field.baseAccessor}(valueRef: usize): ${boolReturnType(field.node)} { return ${boolReturnExpr(valueExpr, field.node)}; }`);
785
+ }
786
+ lines.push('');
787
+ }
788
+ }
789
+ }
790
+
791
+ function collectExportNames(schema) {
792
+ const names = new Set(['parse', 'ok', 'value', 'error', 'serialize', 'errorCode', 'errorMessage', 'errorStatusCode', `parse${schemaSymbol(schema)}`, `${schemaSymbol(schema)}ParseOk`, `${schemaSymbol(schema)}ParseValue`, `${schemaSymbol(schema)}ParseError`, `serialize${schemaSymbol(schema)}`]);
793
+ for (const objectNode of schema.objects) {
794
+ for (const field of objectNode.fields) {
795
+ if (field.hasAccessor) names.add(field.hasAccessor);
796
+ if (field.nullAccessor) names.add(field.nullAccessor);
797
+ if (field.node.kind === 'object' || field.node.kind === 'array') {
798
+ names.add(field.refAccessor);
799
+ if (field.node.kind === 'array') {
800
+ names.add(field.lenAccessor);
801
+ names.add(field.atAccessor);
802
+ }
803
+ } else names.add(field.baseAccessor);
804
+ }
805
+ }
806
+ return Array.from(names).sort();
807
+ }
808
+
809
+ function schemaSidecarSource(schema) {
810
+ const symbol = schemaSymbol(schema);
811
+ const lines = [];
812
+ lines.push('// Generated by PulseWasm Phase 14.');
813
+ lines.push('// Deterministic schema JSON sidecar v2. Parse returns an explicit result ref.');
814
+ lines.push('');
815
+ lines.push('export class PulseSchemaJsonError {');
816
+ lines.push(' code: string = "";');
817
+ lines.push(' message: string = "";');
818
+ lines.push(' statusCode: i32 = 400;');
819
+ lines.push('}');
820
+ lines.push('');
821
+ lines.push('export class SchemaParseResult {');
822
+ lines.push(' ok: bool = false;');
823
+ lines.push(' valueRef: usize = 0;');
824
+ lines.push(' errorRef: usize = 0;');
825
+ lines.push('}');
826
+ lines.push('');
827
+ lines.push('class StringArrayRef { values: Array<string> = new Array<string>(); }');
828
+ lines.push('class BoolArrayRef { values: Array<bool> = new Array<bool>(); }');
829
+ lines.push('class I32ArrayRef { values: Array<i32> = new Array<i32>(); }');
830
+ lines.push('class U32ArrayRef { values: Array<u32> = new Array<u32>(); }');
831
+ lines.push('class F64ArrayRef { values: Array<f64> = new Array<f64>(); }');
832
+ lines.push('class RefArrayRef { values: Array<usize> = new Array<usize>(); }');
833
+ lines.push('');
834
+ lines.push('function makeError(code: string, message: string, statusCode: i32 = 400): usize {');
835
+ lines.push(' const err = new PulseSchemaJsonError();');
836
+ lines.push(' err.code = code;');
837
+ lines.push(' err.message = message;');
838
+ lines.push(' err.statusCode = statusCode;');
839
+ lines.push(' return changetype<usize>(err);');
840
+ lines.push('}');
841
+ lines.push('');
842
+ lines.push('function makeOk(valueRef: usize): usize {');
843
+ lines.push(' const result = new SchemaParseResult();');
844
+ lines.push(' result.ok = true;');
845
+ lines.push(' result.valueRef = valueRef;');
846
+ lines.push(' return changetype<usize>(result);');
847
+ lines.push('}');
848
+ lines.push('');
849
+ lines.push('function makeErr(errorRef: usize): usize {');
850
+ lines.push(' const result = new SchemaParseResult();');
851
+ lines.push(' result.ok = false;');
852
+ lines.push(' result.errorRef = errorRef;');
853
+ lines.push(' return changetype<usize>(result);');
854
+ lines.push('}');
855
+ lines.push('');
856
+
857
+ for (const objectNode of schema.objects) {
858
+ lines.push(`class ${objectNode.className} {`);
859
+ for (const field of objectNode.fields) {
860
+ if (field.hasFlag) lines.push(` __has_${field.ident}: bool = false;`);
861
+ if (field.nullFlag) lines.push(` __null_${field.ident}: bool = false;`);
862
+ lines.push(` ${field.ident}: ${field.storageType} = ${field.defaultValue};`);
863
+ }
864
+ lines.push('}');
865
+ lines.push('');
866
+ }
867
+
868
+ lines.push('class Parser {');
869
+ lines.push(' json: string;');
870
+ lines.push(' pos: i32 = 0;');
871
+ lines.push(' errorRef: usize = 0;');
872
+ lines.push(' constructor(json: string) { this.json = json; }');
873
+ lines.push('');
874
+ lines.push(' fail(code: string, message: string, statusCode: i32 = 400): void {');
875
+ lines.push(' if (this.errorRef != 0) return;');
876
+ lines.push(' this.errorRef = makeError(code, message, statusCode);');
877
+ lines.push(' }');
878
+ lines.push('');
879
+ lines.push(' skipWs(): void {');
880
+ lines.push(' while (this.pos < this.json.length) {');
881
+ lines.push(' const ch = this.json.charCodeAt(this.pos);');
882
+ lines.push(' if (ch != 32 && ch != 10 && ch != 13 && ch != 9) break;');
883
+ lines.push(' this.pos += 1;');
884
+ lines.push(' }');
885
+ lines.push(' }');
886
+ lines.push('');
887
+ lines.push(' peek(): i32 {');
888
+ lines.push(' this.skipWs();');
889
+ lines.push(' return this.pos < this.json.length ? this.json.charCodeAt(this.pos) : -1;');
890
+ lines.push(' }');
891
+ lines.push('');
892
+ lines.push(' consumeChar(code: i32): bool {');
893
+ lines.push(' this.skipWs();');
894
+ lines.push(' if (this.pos < this.json.length && this.json.charCodeAt(this.pos) == code) {');
895
+ lines.push(' this.pos += 1;');
896
+ lines.push(' return true;');
897
+ lines.push(' }');
898
+ lines.push(' return false;');
899
+ lines.push(' }');
900
+ lines.push('');
901
+ lines.push(' expectChar(code: i32, message: string): bool {');
902
+ lines.push(' if (this.consumeChar(code)) return true;');
903
+ lines.push(' this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", message, 400);');
904
+ lines.push(' return false;');
905
+ lines.push(' }');
906
+ lines.push('');
907
+ lines.push(' consumeNull(): bool {');
908
+ lines.push(' this.skipWs();');
909
+ lines.push(' if (this.json.substring(this.pos, this.pos + 4) == "null") { this.pos += 4; return true; }');
910
+ lines.push(' return false;');
911
+ lines.push(' }');
912
+ lines.push('');
913
+ lines.push(' parseKey(): string {');
914
+ lines.push(' return this.readStringInternal("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Expected string object key.");');
915
+ lines.push(' }');
916
+ lines.push('');
917
+ lines.push(' parseString(fieldPath: string): string {');
918
+ lines.push(' return this.readStringInternal("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".");');
919
+ lines.push(' }');
920
+ lines.push('');
921
+ lines.push(' readStringInternal(code: string, message: string): string {');
922
+ lines.push(' this.skipWs();');
923
+ lines.push(' if (this.pos >= this.json.length || this.json.charCodeAt(this.pos) != 34) {');
924
+ lines.push(' this.fail(code, message, 400);');
925
+ lines.push(' return "";');
926
+ lines.push(' }');
927
+ lines.push(' this.pos += 1;');
928
+ lines.push(' let out = "";');
929
+ lines.push(' while (this.pos < this.json.length) {');
930
+ lines.push(' const ch = this.json.charCodeAt(this.pos);');
931
+ lines.push(' if (ch == 34) { this.pos += 1; return out; }');
932
+ lines.push(' if (ch == 92) {');
933
+ lines.push(' this.pos += 1;');
934
+ lines.push(' if (this.pos >= this.json.length) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Unterminated escape sequence.", 400); return ""; }');
935
+ lines.push(' const escaped = this.json.charCodeAt(this.pos);');
936
+ lines.push(' if (escaped == 34) out += "\\\"";');
937
+ lines.push(' else if (escaped == 92) out += "\\\\";');
938
+ lines.push(' else if (escaped == 47) out += "/";');
939
+ lines.push(' else if (escaped == 98) out += String.fromCharCode(8);');
940
+ lines.push(' else if (escaped == 102) out += String.fromCharCode(12);');
941
+ lines.push(' else if (escaped == 110) out += String.fromCharCode(10);');
942
+ lines.push(' else if (escaped == 114) out += String.fromCharCode(13);');
943
+ lines.push(' else if (escaped == 116) out += String.fromCharCode(9);');
944
+ lines.push(' else if (escaped == 117) {');
945
+ lines.push(' if (this.pos + 4 >= this.json.length) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Incomplete unicode escape.", 400); return ""; }');
946
+ lines.push(' const hex = this.json.substring(this.pos + 1, this.pos + 5);');
947
+ lines.push(' const codePoint = I32.parseInt("0x" + hex);');
948
+ lines.push(' out += String.fromCharCode(codePoint);');
949
+ lines.push(' this.pos += 4;');
950
+ lines.push(' }');
951
+ lines.push(' else { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Unsupported escape sequence.", 400); return ""; }');
952
+ lines.push(' } else {');
953
+ lines.push(' if (ch < 32) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Control character in string literal.", 400); return ""; }');
954
+ lines.push(' out += String.fromCharCode(ch);');
955
+ lines.push(' }');
956
+ lines.push(' this.pos += 1;');
957
+ lines.push(' }');
958
+ lines.push(' this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Unterminated string literal.", 400);');
959
+ lines.push(' return "";');
960
+ lines.push(' }');
961
+ lines.push('');
962
+ lines.push(' parseBool(fieldPath: string): bool {');
963
+ lines.push(' this.skipWs();');
964
+ lines.push(' if (this.json.substring(this.pos, this.pos + 4) == "true") { this.pos += 4; return true; }');
965
+ lines.push(' if (this.json.substring(this.pos, this.pos + 5) == "false") { this.pos += 5; return false; }');
966
+ lines.push(' this.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".", 400);');
967
+ lines.push(' return false;');
968
+ lines.push(' }');
969
+ lines.push('');
970
+ lines.push(' parseNumberText(fieldPath: string, integerOnly: bool): string {');
971
+ lines.push(' this.skipWs();');
972
+ lines.push(' const start = this.pos;');
973
+ lines.push(' if (this.pos < this.json.length && this.json.charCodeAt(this.pos) == 45) this.pos += 1;');
974
+ lines.push(' if (this.pos >= this.json.length) { this.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".", 400); return ""; }');
975
+ lines.push(' const first = this.json.charCodeAt(this.pos);');
976
+ lines.push(' if (first < 48 || first > 57) { this.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".", 400); return ""; }');
977
+ lines.push(' if (first == 48) {');
978
+ lines.push(' this.pos += 1;');
979
+ lines.push(' if (this.pos < this.json.length) {');
980
+ lines.push(' const next = this.json.charCodeAt(this.pos);');
981
+ lines.push(' if (next >= 48 && next <= 57) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Malformed number literal.", 400); return ""; }');
982
+ lines.push(' }');
983
+ lines.push(' } else {');
984
+ lines.push(' while (this.pos < this.json.length) {');
985
+ lines.push(' const ch = this.json.charCodeAt(this.pos);');
986
+ lines.push(' if (ch < 48 || ch > 57) break;');
987
+ lines.push(' this.pos += 1;');
988
+ lines.push(' }');
989
+ lines.push(' }');
990
+ lines.push(' if (this.pos < this.json.length && this.json.charCodeAt(this.pos) == 46) {');
991
+ lines.push(' if (integerOnly) { this.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".", 400); return ""; }');
992
+ lines.push(' this.pos += 1;');
993
+ lines.push(' if (this.pos >= this.json.length) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Malformed number literal.", 400); return ""; }');
994
+ lines.push(' const fracStart = this.pos;');
995
+ lines.push(' while (this.pos < this.json.length) {');
996
+ lines.push(' const ch = this.json.charCodeAt(this.pos);');
997
+ lines.push(' if (ch < 48 || ch > 57) break;');
998
+ lines.push(' this.pos += 1;');
999
+ lines.push(' }');
1000
+ lines.push(' if (fracStart == this.pos) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Malformed number literal.", 400); return ""; }');
1001
+ lines.push(' }');
1002
+ lines.push(' if (this.pos < this.json.length) {');
1003
+ lines.push(' const ch = this.json.charCodeAt(this.pos);');
1004
+ lines.push(' if (ch == 101 || ch == 69) {');
1005
+ lines.push(' if (integerOnly) { this.fail("PULSEWASM_SCHEMA_JSON_TYPE_ERROR", "Wrong type for field " + fieldPath + ".", 400); return ""; }');
1006
+ lines.push(' this.pos += 1;');
1007
+ lines.push(' if (this.pos < this.json.length) {');
1008
+ lines.push(' const sign = this.json.charCodeAt(this.pos);');
1009
+ lines.push(' if (sign == 43 || sign == 45) this.pos += 1;');
1010
+ lines.push(' }');
1011
+ lines.push(' if (this.pos >= this.json.length) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Malformed number literal.", 400); return ""; }');
1012
+ lines.push(' const expStart = this.pos;');
1013
+ lines.push(' while (this.pos < this.json.length) {');
1014
+ lines.push(' const exp = this.json.charCodeAt(this.pos);');
1015
+ lines.push(' if (exp < 48 || exp > 57) break;');
1016
+ lines.push(' this.pos += 1;');
1017
+ lines.push(' }');
1018
+ lines.push(' if (expStart == this.pos) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Malformed number literal.", 400); return ""; }');
1019
+ lines.push(' }');
1020
+ lines.push(' }');
1021
+ lines.push(' return this.json.substring(start, this.pos);');
1022
+ lines.push(' }');
1023
+ lines.push('');
1024
+ lines.push(' parseI32(fieldPath: string): i32 {');
1025
+ lines.push(' const text = this.parseNumberText(fieldPath, true);');
1026
+ lines.push(' if (this.errorRef != 0) return 0;');
1027
+ lines.push(' const value = F64.parseFloat(text);');
1028
+ lines.push(' if (!isFinite(value) || value < -2147483648.0 || value > 2147483647.0) { this.fail("PULSEWASM_SCHEMA_JSON_NUMBER_RANGE_ERROR", "Number out of range for field " + fieldPath + ".", 400); return 0; }');
1029
+ lines.push(' return <i32>value;');
1030
+ lines.push(' }');
1031
+ lines.push('');
1032
+ lines.push(' parseU32(fieldPath: string): u32 {');
1033
+ lines.push(' const text = this.parseNumberText(fieldPath, true);');
1034
+ lines.push(' if (this.errorRef != 0) return 0;');
1035
+ lines.push(' const value = F64.parseFloat(text);');
1036
+ lines.push(' if (!isFinite(value) || value < 0.0 || value > 4294967295.0) { this.fail("PULSEWASM_SCHEMA_JSON_NUMBER_RANGE_ERROR", "Number out of range for field " + fieldPath + ".", 400); return 0; }');
1037
+ lines.push(' return <u32>value;');
1038
+ lines.push(' }');
1039
+ lines.push('');
1040
+ lines.push(' parseF64(fieldPath: string): f64 {');
1041
+ lines.push(' const text = this.parseNumberText(fieldPath, false);');
1042
+ lines.push(' if (this.errorRef != 0) return 0.0;');
1043
+ lines.push(' const value = F64.parseFloat(text);');
1044
+ lines.push(' if (!isFinite(value)) { this.fail("PULSEWASM_SCHEMA_JSON_NUMBER_RANGE_ERROR", "Number out of range for field " + fieldPath + ".", 400); return 0.0; }');
1045
+ lines.push(' return value;');
1046
+ lines.push(' }');
1047
+ lines.push('');
1048
+ lines.push(' skipValue(): void {');
1049
+ lines.push(' this.skipWs();');
1050
+ lines.push(' if (this.pos >= this.json.length) { this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Unexpected end of JSON input.", 400); return; }');
1051
+ lines.push(' const ch = this.json.charCodeAt(this.pos);');
1052
+ lines.push(' if (ch == 34) { this.parseKey(); return; }');
1053
+ lines.push(' if (ch == 123) {');
1054
+ lines.push(' this.pos += 1;');
1055
+ lines.push(' this.skipWs();');
1056
+ lines.push(' if (this.consumeChar(125)) return;');
1057
+ lines.push(' while (this.errorRef == 0) {');
1058
+ lines.push(' this.parseKey();');
1059
+ lines.push(' if (this.errorRef != 0) return;');
1060
+ lines.push(' if (!this.expectChar(58, "Expected : after object key.")) return;');
1061
+ lines.push(' this.skipValue();');
1062
+ lines.push(' if (this.errorRef != 0) return;');
1063
+ lines.push(' if (this.consumeChar(44)) continue;');
1064
+ lines.push(' if (this.consumeChar(125)) return;');
1065
+ lines.push(' this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Expected , or } while skipping object value.", 400);');
1066
+ lines.push(' return;');
1067
+ lines.push(' }');
1068
+ lines.push(' return;');
1069
+ lines.push(' }');
1070
+ lines.push(' if (ch == 91) {');
1071
+ lines.push(' this.pos += 1;');
1072
+ lines.push(' this.skipWs();');
1073
+ lines.push(' if (this.consumeChar(93)) return;');
1074
+ lines.push(' while (this.errorRef == 0) {');
1075
+ lines.push(' this.skipValue();');
1076
+ lines.push(' if (this.errorRef != 0) return;');
1077
+ lines.push(' if (this.consumeChar(44)) continue;');
1078
+ lines.push(' if (this.consumeChar(93)) return;');
1079
+ lines.push(' this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Expected , or ] while skipping array value.", 400);');
1080
+ lines.push(' return;');
1081
+ lines.push(' }');
1082
+ lines.push(' return;');
1083
+ lines.push(' }');
1084
+ lines.push(' if (ch == 116 || ch == 102) { this.parseBool("<skip>"); return; }');
1085
+ lines.push(' if (ch == 110) { if (!this.consumeNull()) this.fail("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Malformed null literal.", 400); return; }');
1086
+ lines.push(' this.parseNumberText("<skip>", false);');
1087
+ lines.push(' }');
1088
+ lines.push('}');
1089
+ lines.push('');
1090
+
1091
+ emitEnumParsers(schema, lines);
1092
+ emitArrayParsers(schema, lines);
1093
+ emitObjectParsers(schema, lines);
1094
+
1095
+ lines.push('function escapeJsonString(value: string): string {');
1096
+ lines.push(' let out = "";');
1097
+ lines.push(' for (let i = 0; i < value.length; i += 1) {');
1098
+ lines.push(' const ch = value.charCodeAt(i);');
1099
+ lines.push(' if (ch == 34) out += "\\\"";');
1100
+ lines.push(' else if (ch == 92) out += "\\\\";');
1101
+ lines.push(' else if (ch == 8) out += "\\b";');
1102
+ lines.push(' else if (ch == 12) out += "\\f";');
1103
+ lines.push(' else if (ch == 10) out += "\\n";');
1104
+ lines.push(' else if (ch == 13) out += "\\r";');
1105
+ lines.push(' else if (ch == 9) out += "\\t";');
1106
+ lines.push(' else if (ch < 32) {');
1107
+ lines.push(' let hex = ch.toString(16).toUpperCase();');
1108
+ lines.push(' while (hex.length < 4) hex = "0" + hex;');
1109
+ lines.push(' out += "\\\\u" + hex;');
1110
+ lines.push(' }');
1111
+ lines.push(' else out += String.fromCharCode(ch);');
1112
+ lines.push(' }');
1113
+ lines.push(' return out;');
1114
+ lines.push('}');
1115
+ lines.push('');
1116
+
1117
+ emitArraySerializers(schema, lines);
1118
+ emitObjectSerializers(schema, lines);
1119
+
1120
+ lines.push('export function parse(json: string): usize {');
1121
+ lines.push(' const parser = new Parser(json);');
1122
+ lines.push(` const valueRef = ${schema.root.parseFunction}(parser, ${asString(schema.id)});`);
1123
+ lines.push(' if (parser.errorRef != 0) return makeErr(parser.errorRef);');
1124
+ lines.push(' parser.skipWs();');
1125
+ lines.push(' if (parser.pos != json.length) return makeErr(makeError("PULSEWASM_SCHEMA_JSON_PARSE_ERROR", "Trailing content after root JSON value.", 400));');
1126
+ lines.push(' return makeOk(valueRef);');
1127
+ lines.push('}');
1128
+ lines.push('');
1129
+ lines.push('export function ok(resultRef: usize): bool { return changetype<SchemaParseResult>(resultRef).ok; }');
1130
+ lines.push('export function value(resultRef: usize): usize { return changetype<SchemaParseResult>(resultRef).valueRef; }');
1131
+ lines.push('export function error(resultRef: usize): usize { return changetype<SchemaParseResult>(resultRef).errorRef; }');
1132
+ lines.push('export function errorCode(errorRef: usize): string { return changetype<PulseSchemaJsonError>(errorRef).code; }');
1133
+ lines.push('export function errorMessage(errorRef: usize): string { return changetype<PulseSchemaJsonError>(errorRef).message; }');
1134
+ lines.push('export function errorStatusCode(errorRef: usize): i32 { return changetype<PulseSchemaJsonError>(errorRef).statusCode; }');
1135
+ lines.push('');
1136
+ lines.push(`export function parse${symbol}(json: string): usize { return parse(json); }`);
1137
+ lines.push(`export function ${symbol}ParseOk(resultRef: usize): bool { return ok(resultRef); }`);
1138
+ lines.push(`export function ${symbol}ParseValue(resultRef: usize): usize { return value(resultRef); }`);
1139
+ lines.push(`export function ${symbol}ParseError(resultRef: usize): usize { return error(resultRef); }`);
1140
+ lines.push('');
1141
+ emitAccessors(schema, lines);
1142
+ lines.push(`export function serialize(valueRef: usize): string { return ${schema.root.serializeFunction}(valueRef); }`);
1143
+ lines.push(`export function serialize${symbol}(valueRef: usize): string { return serialize(valueRef); }`);
1144
+ lines.push('');
1145
+ return `${lines.join('\n')}\n`;
1146
+ }
1147
+
1148
+ function importAlias(prefix, name) { return `${name} as ${prefix}_${name}`; }
1149
+ function smokeRunnerSource(schemas) {
1150
+ const imports = [];
1151
+ const checks = [];
1152
+ const checkNames = [];
1153
+ for (const schema of schemas) {
1154
+ const prefix = schemaSymbol(schema);
1155
+ const file = `../as/schema-json/${schema.namespaceBase}/${schema.fileBase}.as`;
1156
+ const imported = collectExportNames(schema).map((name) => importAlias(prefix, name));
1157
+ imports.push(`import { ${imported.join(', ')} } from '${file}';`);
1158
+ const fullSample = sampleForNode(schema.root, schema.name);
1159
+ const firstRootScalar = schema.root.fields.find((field) => field.node.kind === 'scalar' || field.node.kind === 'enum');
1160
+ const firstArray = findField(schema, (field) => field.node.kind === 'array');
1161
+ const firstOptional = findField(schema, (field) => !field.required);
1162
+ const firstNullable = findField(schema, (field) => field.nullable);
1163
+ const nestedScalar = findNestedObjectWithScalar(schema);
1164
+ const firstRequiredRoot = schema.root.fields.find((field) => field.required);
1165
+ const firstEnum = findField(schema, (field) => field.node.kind === 'enum');
1166
+
1167
+ const parseFn = `parse_${prefix}`;
1168
+ checks.push(`function ${parseFn}(): usize { return ${prefix}_parse(${asString(JSON.stringify(fullSample))}); }`);
1169
+ const parseOkName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_parse_ok`.replace(/-/g, '_');
1170
+ checks.push(`export function ${parseOkName}(): i32 { const r = ${parseFn}(); return ${prefix}_ok(r) && ${prefix}_value(r) != 0 ? 1 : 0; }`);
1171
+ checkNames.push(parseOkName);
1172
+
1173
+ if (firstRootScalar) {
1174
+ const scalarName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_${firstRootScalar.name}`.replace(/-/g, '_');
1175
+ const expected = sampleForNode(firstRootScalar.node, firstRootScalar.name);
1176
+ const accessor = firstRootScalar.baseAccessor;
1177
+ if (firstRootScalar.node.kind === 'scalar' && firstRootScalar.node.type === 'string' || firstRootScalar.node.kind === 'enum') {
1178
+ checks.push(`export function ${scalarName}(): i32 { const v = ${prefix}_value(${parseFn}()); return ${prefix}_${accessor}(v) == ${asString(expected)} ? 1 : 0; }`);
1179
+ } else if (firstRootScalar.node.kind === 'scalar' && firstRootScalar.node.type === 'bool') {
1180
+ checks.push(`export function ${scalarName}(): i32 { const v = ${prefix}_value(${parseFn}()); return ${prefix}_${accessor}(v) == ${expected ? 1 : 0} ? 1 : 0; }`);
1181
+ } else {
1182
+ checks.push(`export function ${scalarName}(): i32 { const v = ${prefix}_value(${parseFn}()); return ${prefix}_${accessor}(v) == ${expected} ? 1 : 0; }`);
1183
+ }
1184
+ checkNames.push(scalarName);
1185
+ }
1186
+
1187
+ if (firstArray && firstArray.lenAccessor) {
1188
+ const lenName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_array_len`.replace(/-/g, '_');
1189
+ const expectedLen = Array.isArray(deepGet(fullSample, firstArray.pathSegments)) ? deepGet(fullSample, firstArray.pathSegments).length : 0;
1190
+ checks.push(`export function ${lenName}(): i32 { const v = ${prefix}_value(${parseFn}()); return ${prefix}_${firstArray.lenAccessor}(v) == ${expectedLen} ? 1 : 0; }`);
1191
+ checkNames.push(lenName);
1192
+ }
1193
+
1194
+ if (firstOptional && firstOptional.hasAccessor) {
1195
+ const optionalPayload = clone(fullSample);
1196
+ deepDelete(optionalPayload, firstOptional.pathSegments);
1197
+ const optionalName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_optional`.replace(/-/g, '_');
1198
+ checks.push(`export function ${optionalName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(optionalPayload))}); const v = ${prefix}_value(r); return ${prefix}_ok(r) && ${prefix}_${firstOptional.hasAccessor}(v) == 0 ? 1 : 0; }`);
1199
+ checkNames.push(optionalName);
1200
+ }
1201
+
1202
+ if (firstNullable && firstNullable.nullAccessor) {
1203
+ const nullablePayload = clone(fullSample);
1204
+ deepSet(nullablePayload, firstNullable.pathSegments, null);
1205
+ const nullableName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_nullable`.replace(/-/g, '_');
1206
+ if (firstNullable.pathSegments.length === 1) {
1207
+ checks.push(`export function ${nullableName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(nullablePayload))}); const v = ${prefix}_value(r); return ${prefix}_ok(r) && ${prefix}_${firstNullable.nullAccessor}(v) == 1 ? 1 : 0; }`);
1208
+ } else {
1209
+ const parentPath = firstNullable.pathSegments.slice(0, -1);
1210
+ const parentField = findField(schema, (field) => field.pathSegments.join('.') === parentPath.join('.'));
1211
+ checks.push(`export function ${nullableName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(nullablePayload))}); const root = ${prefix}_value(r); const parent = ${prefix}_${parentField.refAccessor}(root); return ${prefix}_ok(r) && parent != 0 && ${prefix}_${firstNullable.nullAccessor}(parent) == 1 ? 1 : 0; }`);
1212
+ }
1213
+ checkNames.push(nullableName);
1214
+ }
1215
+
1216
+ if (nestedScalar) {
1217
+ const nestedName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_nested`.replace(/-/g, '_');
1218
+ const nestedValue = deepGet(fullSample, nestedScalar.parent.pathSegments.concat(nestedScalar.child.name));
1219
+ checks.push(`export function ${nestedName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(fullSample))}); const root = ${prefix}_value(r); const parent = ${prefix}_${nestedScalar.parent.refAccessor}(root); return ${prefix}_ok(r) && parent != 0 && ${compareExpression(prefix, nestedScalar.child, 'parent', nestedValue)} ? 1 : 0; }`);
1220
+ checkNames.push(nestedName);
1221
+ }
1222
+
1223
+ const serializeName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_serialize`.replace(/-/g, '_');
1224
+ checks.push(`export function ${serializeName}(): i32 { return ${prefix}_serialize(${prefix}_value(${parseFn}())).indexOf(${asString(schema.root.fields[0] ? schema.root.fields[0].name : '')}) >= 0 ? 1 : 0; }`);
1225
+ checkNames.push(serializeName);
1226
+
1227
+ if (firstRequiredRoot) {
1228
+ const missingPayload = clone(fullSample);
1229
+ deepDelete(missingPayload, [firstRequiredRoot.name]);
1230
+ const missingName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_missing_required`.replace(/-/g, '_');
1231
+ checks.push(`export function ${missingName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(missingPayload))}); const e = ${prefix}_error(r); return !${prefix}_ok(r) && ${prefix}_errorCode(e) == "PULSEWASM_SCHEMA_JSON_MISSING_FIELD" ? 1 : 0; }`);
1232
+ checkNames.push(missingName);
1233
+
1234
+ const wrongPayload = clone(fullSample);
1235
+ wrongPayload[firstRequiredRoot.name] = wrongTypeSample(firstRequiredRoot.node);
1236
+ const typeName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_type_error`.replace(/-/g, '_');
1237
+ checks.push(`export function ${typeName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(wrongPayload))}); const e = ${prefix}_error(r); return !${prefix}_ok(r) && ${prefix}_errorCode(e) == "PULSEWASM_SCHEMA_JSON_TYPE_ERROR" && ${prefix}_errorStatusCode(e) == 400 ? 1 : 0; }`);
1238
+ checkNames.push(typeName);
1239
+ }
1240
+
1241
+ if (firstEnum) {
1242
+ const enumPayload = clone(fullSample);
1243
+ deepSet(enumPayload, firstEnum.pathSegments, '__bad_enum__');
1244
+ const enumName = `smoke_${kebab(schema.namespace)}_${kebab(schema.type)}_enum_error`.replace(/-/g, '_');
1245
+ checks.push(`export function ${enumName}(): i32 { const r = ${prefix}_parse(${asString(JSON.stringify(enumPayload))}); const e = ${prefix}_error(r); return !${prefix}_ok(r) && ${prefix}_errorCode(e) == "PULSEWASM_SCHEMA_JSON_TYPE_ERROR" ? 1 : 0; }`);
1246
+ checkNames.push(enumName);
1247
+ }
1248
+ }
1249
+ return { text: `${imports.join('\n')}\n\n${checks.join('\n')}\n`, checkNames };
1250
+ }
1251
+
1252
+ function deepGet(target, pathSegments) {
1253
+ let cursor = target;
1254
+ for (const segment of pathSegments) {
1255
+ if (!cursor || typeof cursor !== 'object') return undefined;
1256
+ cursor = cursor[segment];
1257
+ }
1258
+ return cursor;
1259
+ }
1260
+ function wrongTypeSample(node) {
1261
+ if (node.kind === 'scalar') {
1262
+ if (node.type === 'string') return 1;
1263
+ if (node.type === 'bool') return 'bad';
1264
+ return 'bad';
1265
+ }
1266
+ if (node.kind === 'enum') return 1;
1267
+ if (node.kind === 'array' || node.kind === 'object') return 'bad';
1268
+ return 'bad';
1269
+ }
1270
+ function compareExpression(prefix, field, baseRef, expected) {
1271
+ const accessor = `${prefix}_${field.baseAccessor}`;
1272
+ if (field.node.kind === 'scalar' && field.node.type === 'string' || field.node.kind === 'enum') return `${accessor}(${baseRef}) == ${asString(expected)}`;
1273
+ if (field.node.kind === 'scalar' && field.node.type === 'bool') return `${accessor}(${baseRef}) == ${expected ? 1 : 0}`;
1274
+ return `${accessor}(${baseRef}) == ${expected}`;
1275
+ }
1276
+
1277
+ function stripAnsi(value) { return String(value || '').replace(/\u001b\[[0-9;]*m/g, ''); }
1278
+ function truncate(value, max = 12000) { const text = String(value || ''); return text.length <= max ? text : `${text.slice(0, max)}\n... <truncated ${text.length - max} chars>`; }
1279
+ function parseAscWarnings(stderr) {
1280
+ const text = stripAnsi(stderr);
1281
+ const warnings = [];
1282
+ const regex = /WARNING\s+(AS\d+):\s+([^\n]+)(?:.|\n)*?in ([^\n]+)\((\d+),(\d+)\)/g;
1283
+ let match;
1284
+ while ((match = regex.exec(text))) warnings.push({ code: match[1], message: match[2].trim(), file: match[3], line: Number(match[4]), column: Number(match[5]) });
1285
+ if (warnings.length === 0 && /WARNING\s+AS\d+:/m.test(text)) warnings.push({ code: 'AS_WARNING', message: 'AssemblyScript emitted one or more warnings. Inspect stderr.' });
1286
+ return warnings;
1287
+ }
1288
+ function fileRecord(filePath, cwd, kind) {
1289
+ const buffer = fs.readFileSync(filePath);
1290
+ return { file: stableSlash(path.relative(cwd, filePath)), kind, bytes: buffer.length, sha256: sha256Buffer(buffer) };
1291
+ }
1292
+
1293
+ function compileAndSmoke({ cwd, outDir, files, schemas, checkNames }) {
1294
+ const diagnostics = [];
1295
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-schema-json-v2-'));
1296
+ const generated = path.join(temp, 'generated');
1297
+ for (const file of files) {
1298
+ const target = path.join(temp, file.file);
1299
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1300
+ fs.writeFileSync(target, file.text, 'utf8');
1301
+ }
1302
+ const runner = path.join(generated, 'as-smoke', 'schema-json-smoke-runner.as.ts');
1303
+ const smokeDir = path.join(outDir, 'generated', 'schema-json');
1304
+ fs.mkdirSync(smokeDir, { recursive: true });
1305
+ const wasmFile = path.join(smokeDir, 'pulsewasm-schema-json.wasm');
1306
+ const watFile = path.join(smokeDir, 'pulsewasm-schema-json.wat');
1307
+ const asc = resolveAsc(cwd);
1308
+ const args = [runner, '--outFile', wasmFile, '--textFile', watFile, '--runtime', 'stub', '--optimize'];
1309
+ if (!asc) {
1310
+ diagnostics.push({ phase: 'schema-json-sidecar', code: 'PULSEWASM_SCHEMA_JSON_ASC_MISSING', severity: 'error', message: 'AssemblyScript compiler is not installed.', hint: 'Run npm install so the assemblyscript dev dependency is available.', loc: { file: '<schema-json-sidecar>' } });
1311
+ return { diagnostics, warnings: [], smoke: undefined, outputs: [], command: undefined, result: undefined };
1312
+ }
1313
+ const spawnArgs = asc.script ? [asc.script, ...args] : args;
1314
+ const executable = asc.executable || asc.command;
1315
+ const proc = spawnSync(executable, spawnArgs, { cwd, encoding: 'utf8', maxBuffer: 20 * 1024 * 1024, windowsHide: true });
1316
+ const warnings = parseAscWarnings(proc.stderr);
1317
+ if (proc.status !== 0) {
1318
+ diagnostics.push({ phase: 'schema-json-sidecar', code: 'PULSEWASM_SCHEMA_JSON_COMPILE_FAILED', severity: 'error', message: 'Schema JSON sidecar smoke compilation failed.', hint: 'Inspect schema-json-smoke.json stderr.', loc: { file: '<schema-json-sidecar>' }, details: { stdout: truncate(proc.stdout), stderr: truncate(proc.stderr), exitCode: proc.status } });
1319
+ }
1320
+ const outputs = [];
1321
+ if (fs.existsSync(wasmFile)) outputs.push(fileRecord(wasmFile, cwd, 'schema-json-wasm'));
1322
+ if (fs.existsSync(watFile)) outputs.push(fileRecord(watFile, cwd, 'schema-json-wat'));
1323
+ let smoke;
1324
+ if (diagnostics.length === 0 && fs.existsSync(wasmFile)) {
1325
+ const wasm = fs.readFileSync(wasmFile);
1326
+ const module = new WebAssembly.Module(wasm);
1327
+ const instance = new WebAssembly.Instance(module, { env: { abort() { throw new Error('AssemblyScript abort'); } } });
1328
+ const checks = checkNames.map((name) => {
1329
+ let actual, status = 'ok', message;
1330
+ try {
1331
+ const fn = instance.exports[name];
1332
+ if (typeof fn !== 'function') { status = 'error'; message = 'Missing smoke export.'; }
1333
+ else { actual = fn(); if (actual !== 1) { status = 'error'; message = `Expected 1, got ${actual}.`; } }
1334
+ } catch (error) { status = 'error'; message = error.message || String(error); }
1335
+ return { name, expected: 1, actual, status, message };
1336
+ });
1337
+ smoke = { imports: WebAssembly.Module.imports(module), exports: WebAssembly.Module.exports(module), checks, failedChecks: checks.filter((check) => check.status !== 'ok'), wasmBytes: wasm.length };
1338
+ if (smoke.failedChecks.length) diagnostics.push({ phase: 'schema-json-sidecar', code: 'PULSEWASM_SCHEMA_JSON_SMOKE_FAILED', severity: 'error', message: 'Schema JSON sidecar smoke checks failed.', hint: 'Inspect schema-json-smoke.json checks.', loc: { file: '<schema-json-sidecar>' } });
1339
+ }
1340
+ return { diagnostics, warnings, smoke, outputs, command: { executable: asc.display, args: args.map((arg) => path.isAbsolute(arg) ? stableSlash(path.relative(cwd, arg)) : arg) }, result: { exitCode: proc.status, stdout: truncate(proc.stdout), stderr: truncate(proc.stderr) } };
1341
+ }
1342
+
1343
+ function registryField(field) {
1344
+ if (field.node.kind === 'scalar') {
1345
+ const out = { kind: 'scalar', type: field.node.type, required: field.required, nullable: field.nullable, export: field.baseAccessor };
1346
+ if (field.hasAccessor) out.has = field.hasAccessor;
1347
+ if (field.nullAccessor) out.isNull = field.nullAccessor;
1348
+ return out;
1349
+ }
1350
+ if (field.node.kind === 'enum') {
1351
+ const out = { kind: 'enum', type: 'enum', required: field.required, nullable: field.nullable, export: field.baseAccessor, values: field.node.values };
1352
+ if (field.hasAccessor) out.has = field.hasAccessor;
1353
+ if (field.nullAccessor) out.isNull = field.nullAccessor;
1354
+ return out;
1355
+ }
1356
+ if (field.node.kind === 'array') {
1357
+ const out = { kind: 'array', type: 'array', required: field.required, nullable: field.nullable, ref: field.refAccessor, len: field.lenAccessor, at: field.atAccessor, itemKind: field.node.item.kind === 'scalar' ? 'scalar' : field.node.item.kind };
1358
+ if (field.hasAccessor) out.has = field.hasAccessor;
1359
+ if (field.nullAccessor) out.isNull = field.nullAccessor;
1360
+ return out;
1361
+ }
1362
+ const out = { kind: 'object', type: 'object', required: field.required, nullable: field.nullable, ref: field.refAccessor, fields: Object.fromEntries(field.node.fields.map((child) => [child.name, registryField(child)])) };
1363
+ if (field.hasAccessor) out.has = field.hasAccessor;
1364
+ if (field.nullAccessor) out.isNull = field.nullAccessor;
1365
+ return out;
1366
+ }
1367
+
1368
+ function schemaExports(schema) {
1369
+ const symbol = schemaSymbol(schema);
1370
+ return {
1371
+ parse: 'parse',
1372
+ ok: 'ok',
1373
+ value: 'value',
1374
+ error: 'error',
1375
+ serialize: 'serialize',
1376
+ errorCode: 'errorCode',
1377
+ errorMessage: 'errorMessage',
1378
+ errorStatusCode: 'errorStatusCode',
1379
+ aliasedParse: `parse${symbol}`,
1380
+ aliasedOk: `${symbol}ParseOk`,
1381
+ aliasedValue: `${symbol}ParseValue`,
1382
+ aliasedError: `${symbol}ParseError`,
1383
+ fields: Object.fromEntries(schema.root.fields.map((field) => [field.name, registryField(field)]))
1384
+ };
1385
+ }
1386
+
1387
+ function buildMarkdown(artifact) {
1388
+ const lines = [];
1389
+ lines.push('# PulseWasm Schema JSON Registry + Sidecar ABI v2');
1390
+ lines.push('');
1391
+ lines.push(`Generated by: \`${artifact.generatedBy}\``);
1392
+ lines.push(`Phase: \`${artifact.policy.phase}\``);
1393
+ lines.push('');
1394
+ lines.push('## Locked positions');
1395
+ lines.push('');
1396
+ for (const item of artifact.lockedPositions) lines.push(`- ${item}`);
1397
+ lines.push('');
1398
+ lines.push('## Schemas');
1399
+ lines.push('');
1400
+ if (artifact.schemas.length === 0) lines.push('- none');
1401
+ for (const schema of artifact.schemas) lines.push(`- \`${schema.id}\` codec \`${schema.codec}\` (${schema.fields.length} root fields) → \`${schema.generatedFile}\``);
1402
+ lines.push('');
1403
+ lines.push('## Summary');
1404
+ lines.push('');
1405
+ for (const [key, value] of Object.entries(artifact.summary || {})) lines.push(`- ${key}: \`${value}\``);
1406
+ lines.push('');
1407
+ return `${lines.join('\n')}\n`;
1408
+ }
1409
+
1410
+ function registryFor(selected, schemaSummaries) {
1411
+ return {
1412
+ version: SCHEMA_JSON_REGISTRY_VERSION,
1413
+ schemaVersion: 'v2',
1414
+ defaultNamespace: selected.defaultNamespace,
1415
+ contentTypePolicy: selected.contentTypePolicy,
1416
+ bodyPolicy: 'seeded-body-text-only',
1417
+ canonicalHandlerSurface: 'ctx.req.parse(schemaId)',
1418
+ schemas: schemaSummaries.map((schema) => ({
1419
+ id: schema.id,
1420
+ namespace: schema.namespace,
1421
+ name: schema.name,
1422
+ type: schema.type,
1423
+ codec: schema.codec,
1424
+ mediaTypes: schema.mediaTypes,
1425
+ sidecar: schema.generatedFile,
1426
+ exports: schema.exports
1427
+ }))
1428
+ };
1429
+ }
1430
+
1431
+ function buildSchemaJsonSidecarV2(dispatchTable, executionPlan, options = {}) {
1432
+ const cwd = options.cwd || process.cwd();
1433
+ const outDir = options.outDir || getDefaultArtifactsDir(cwd);
1434
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
1435
+ const config = configArtifact(options);
1436
+ const selected = normalizeSchemasV2(config);
1437
+ const diagnostics = [...selected.diagnostics];
1438
+ const warnings = [...selected.warnings];
1439
+ const files = [];
1440
+ let compileResult = { diagnostics: [], warnings: [], smoke: undefined, outputs: [], command: undefined, result: undefined };
1441
+ let smokeRunner;
1442
+ if (selected.active && diagnostics.length === 0) {
1443
+ for (const schema of selected.schemas) files.push(makeFile(schema.moduleFile, schemaSidecarSource(schema)));
1444
+ smokeRunner = smokeRunnerSource(selected.schemas);
1445
+ files.push(makeFile('generated/as-smoke/schema-json-smoke-runner.as.ts', smokeRunner.text));
1446
+ compileResult = compileAndSmoke({ cwd, outDir, files, schemas: selected.schemas, checkNames: smokeRunner.checkNames });
1447
+ diagnostics.push(...compileResult.diagnostics);
1448
+ warnings.push(...compileResult.warnings);
1449
+ }
1450
+ const schemaSummaries = selected.schemas.map((schema) => ({
1451
+ id: schema.id,
1452
+ namespace: schema.namespace,
1453
+ name: schema.name,
1454
+ type: schema.type,
1455
+ source: schema.source,
1456
+ codec: schema.codec,
1457
+ mediaTypes: schema.mediaTypes,
1458
+ fields: schema.root.fields.map((field) => ({ name: field.name, required: field.required, nullable: field.nullable, kind: field.node.kind, type: field.node.kind === 'scalar' ? field.node.type : field.node.kind === 'enum' ? 'enum' : field.node.kind })),
1459
+ generatedFile: schema.moduleFile,
1460
+ exports: schemaExports(schema)
1461
+ }));
1462
+ const registry = normalizeArtifact(registryFor(selected, schemaSummaries), cwd);
1463
+ const sidecarAbi = normalizeArtifact({
1464
+ version: SCHEMA_JSON_ABI_VERSION,
1465
+ generatedBy,
1466
+ status: diagnostics.length ? 'error' : 'ok',
1467
+ phase: PHASE,
1468
+ schemaVersion: 'v2',
1469
+ clientObligations: [
1470
+ 'Read schema-json-registry.json and select parsers by exact schema id, not by sidecar file path.',
1471
+ 'Treat parse result refs, value refs, and error refs as opaque.',
1472
+ 'Check ok(resultRef) before using value(resultRef).',
1473
+ 'Use field accessors; do not assume AssemblyScript class memory layout.',
1474
+ 'Do not assume generic JSON value trees or host JSON.parse.'
1475
+ ],
1476
+ parseAbi: {
1477
+ parse: '(json: string) => SchemaParseResultRef',
1478
+ ok: '(resultRef: usize) => bool',
1479
+ value: '(resultRef: usize) => SchemaValueRef',
1480
+ error: '(resultRef: usize) => PulseErrorRef'
1481
+ },
1482
+ accessorAbi: {
1483
+ scalar: '(valueRef: usize) => string | i32 | u32 | f64 | i32(bool)',
1484
+ objectRef: '(valueRef: usize) => usize',
1485
+ arrayRef: '(valueRef: usize) => usize',
1486
+ arrayLen: '(valueRef: usize) => i32',
1487
+ arrayAt: '(valueRef: usize, index: i32) => value',
1488
+ presence: '(valueRef: usize) => i32',
1489
+ nullable: '(valueRef: usize) => i32'
1490
+ },
1491
+ serializeAbi: {
1492
+ serialize: '(valueRef: usize) => string',
1493
+ fieldOrder: 'schema declaration order'
1494
+ },
1495
+ errorCodes: [
1496
+ 'PULSEWASM_SCHEMA_JSON_PARSE_ERROR',
1497
+ 'PULSEWASM_SCHEMA_JSON_MISSING_FIELD',
1498
+ 'PULSEWASM_SCHEMA_JSON_TYPE_ERROR',
1499
+ 'PULSEWASM_SCHEMA_JSON_NUMBER_RANGE_ERROR',
1500
+ 'PULSEWASM_JSON_BODY_TOO_LARGE'
1501
+ ],
1502
+ supportedFieldTypes: ['string', 'bool', 'i32', 'u32', 'f64', 'array', 'object', 'enum', 'optional', 'nullable'],
1503
+ reservedFieldTypes: ['ctx.req.parse() shorthand', 'default namespace guessing', 'compile-stream lowering for schema v2', 'discriminated unions', 'binary request parsing']
1504
+ }, cwd);
1505
+ const bodyPolicy = normalizeArtifact({
1506
+ version: SCHEMA_JSON_BODY_POLICY_VERSION,
1507
+ generatedBy,
1508
+ status: diagnostics.length ? 'error' : 'ok',
1509
+ phase: PHASE,
1510
+ canonicalHandlerSurface: 'ctx.req.parse(schemaId)',
1511
+ requestBodyAccess: {
1512
+ lazyByArchitecture: true,
1513
+ phase14Mode: 'seeded-body-text-only',
1514
+ bodyReadEffect: 'reserved',
1515
+ automaticBodyRead: false,
1516
+ automaticParseDuringRouting: false
1517
+ },
1518
+ contentTypePolicy: {
1519
+ default: selected.contentTypePolicy,
1520
+ implemented: ['accept-json-or-missing', 'require-json'],
1521
+ reserved: ['ignore-content-type'],
1522
+ acceptJsonOrMissing: {
1523
+ 'application/json': 'parse',
1524
+ 'application/*+json': 'parse',
1525
+ missing: 'parse',
1526
+ explicitWrongMediaType: '415 PULSEWASM_UNSUPPORTED_MEDIA_TYPE'
1527
+ }
1528
+ },
1529
+ errors: {
1530
+ requestBodyRequired: { code: 'PULSEWASM_REQUEST_BODY_REQUIRED', statusCode: 400 },
1531
+ bodyReadEffectUnsupported: { code: 'PULSEWASM_BODY_READ_EFFECT_UNSUPPORTED', statusCode: 500 },
1532
+ bodySeedingContractViolation: { code: 'PULSEWASM_BODY_SEEDING_CONTRACT_VIOLATION', statusCode: 500 },
1533
+ unsupportedMediaType: { code: 'PULSEWASM_UNSUPPORTED_MEDIA_TYPE', statusCode: 415 }
1534
+ }
1535
+ }, cwd);
1536
+ const summary = {
1537
+ active: selected.active,
1538
+ target: selected.target,
1539
+ schemaVersion: 'v2',
1540
+ defaultNamespace: selected.defaultNamespace,
1541
+ contentTypePolicy: selected.contentTypePolicy,
1542
+ schemas: selected.schemas.length,
1543
+ namespaces: new Set(selected.schemas.map((schema) => schema.namespace)).size,
1544
+ generatedFiles: files.length,
1545
+ compiled: Boolean(compileResult.smoke) && diagnostics.length === 0,
1546
+ smokeExecuted: Boolean(compileResult.smoke),
1547
+ smokeChecks: compileResult.smoke ? compileResult.smoke.checks.length : 0,
1548
+ failedSmokeChecks: compileResult.smoke ? compileResult.smoke.failedChecks.length : 0,
1549
+ diagnostics: diagnostics.length,
1550
+ warnings: warnings.length,
1551
+ genericJsonTree: false,
1552
+ parseResultAbi: true,
1553
+ seededBodyTextOnly: true,
1554
+ ctxReqParseSurface: true,
1555
+ compileStreamV2: false
1556
+ };
1557
+ const artifact = normalizeArtifact({
1558
+ version: SCHEMA_JSON_SIDECAR_VERSION,
1559
+ generatedBy,
1560
+ status: diagnostics.length ? 'error' : 'ok',
1561
+ policy: {
1562
+ phase: PHASE,
1563
+ sourceOfTruth: ['runtime.payload.json.target', 'runtime.payload.json.schemaVersion', 'runtime.payload.json.schemas'],
1564
+ target: selected.target,
1565
+ schemaVersion: 'v2',
1566
+ genericJsonTree: false,
1567
+ sidecarCompilation: selected.active,
1568
+ payloadFeature: true,
1569
+ parserSurface: 'ctx.req.parse(schemaId)',
1570
+ compileStreamV2: false
1571
+ },
1572
+ lockedPositions: [
1573
+ 'Explicit schema v2 widens the schema surface without changing the canonical handler surface.',
1574
+ 'Handlers still choose exact schema ids: ctx.req.parse("namespace.Schema").',
1575
+ 'The registry remains the source of truth for parser selection.',
1576
+ 'Body handling stays lazy by architecture; explicit schema v2 still parses seeded body text only.',
1577
+ 'Missing Content-Type parses by default; explicit wrong Content-Type still fails 415.',
1578
+ 'The Phase 13 compile stream remains the flat v1 frontend and does not lower schema v2 shapes yet.'
1579
+ ],
1580
+ schemas: schemaSummaries,
1581
+ registry,
1582
+ sidecarAbi,
1583
+ bodyPolicy,
1584
+ diagnostics,
1585
+ warnings,
1586
+ summary
1587
+ }, cwd);
1588
+ const plan = normalizeArtifact({
1589
+ version: SCHEMA_JSON_PLAN_VERSION,
1590
+ generatedBy,
1591
+ status: diagnostics.length ? 'error' : 'ok',
1592
+ target: selected.target,
1593
+ schemaVersion: 'v2',
1594
+ parserModel: {
1595
+ kind: 'deterministic-sidecar-registry',
1596
+ genericJsonTree: false,
1597
+ dependencyFree: true,
1598
+ parseResultAbi: true,
1599
+ supportedFieldTypes: ['string', 'i32', 'u32', 'f64', 'bool', 'arrays', 'nested objects', 'optional', 'nullable', 'enum'],
1600
+ compileStreamV2: false
1601
+ },
1602
+ defaultNamespace: selected.defaultNamespace,
1603
+ schemas: schemaSummaries,
1604
+ diagnostics,
1605
+ warnings,
1606
+ summary
1607
+ }, cwd);
1608
+ const report = normalizeArtifact({
1609
+ version: SCHEMA_JSON_REPORT_VERSION,
1610
+ generatedBy,
1611
+ status: diagnostics.length ? 'error' : 'ok',
1612
+ lowering: selected.schemas.map((schema) => ({
1613
+ id: schema.id,
1614
+ schema: schema.type,
1615
+ namespace: schema.namespace,
1616
+ source: schema.source,
1617
+ arrays: schema.root.fields.filter((field) => field.node.kind === 'array').map((field) => ({ path: fieldPathString(field.pathSegments), accessor: field.lenAccessor })),
1618
+ nestedObjects: schema.root.fields.filter((field) => field.node.kind === 'object').map((field) => fieldPathString(field.pathSegments)),
1619
+ optionalFields: schema.root.fields.filter((field) => !field.required).map((field) => fieldPathString(field.pathSegments)),
1620
+ nullableFields: schema.root.fields.filter((field) => field.nullable).map((field) => fieldPathString(field.pathSegments)),
1621
+ parser: 'parse',
1622
+ parserAlias: `parse${schemaSymbol(schema)}`,
1623
+ parseResultAccessors: ['ok', 'value', 'error'],
1624
+ serializer: 'serialize'
1625
+ })),
1626
+ command: compileResult.command,
1627
+ result: compileResult.result,
1628
+ outputFiles: compileResult.outputs || [],
1629
+ diagnostics,
1630
+ warnings,
1631
+ summary
1632
+ }, cwd);
1633
+ const smoke = normalizeArtifact({
1634
+ version: SCHEMA_JSON_SMOKE_VERSION,
1635
+ generatedBy,
1636
+ status: diagnostics.length ? 'error' : 'ok',
1637
+ policy: {
1638
+ phase: PHASE,
1639
+ wasmCompiled: Boolean(compileResult.outputs?.length),
1640
+ wasmExecuted: Boolean(compileResult.smoke),
1641
+ genericJsonParser: false,
1642
+ parseResultAbi: true,
1643
+ schemaVersion: 'v2'
1644
+ },
1645
+ command: compileResult.command,
1646
+ result: compileResult.result,
1647
+ generated: { inputFiles: files.map(({ text, ...rest }) => rest), outputFiles: compileResult.outputs || [] },
1648
+ checks: compileResult.smoke ? compileResult.smoke.checks : [],
1649
+ diagnostics,
1650
+ warnings,
1651
+ summary: {
1652
+ compiled: Boolean(compileResult.outputs?.length) && diagnostics.length === 0,
1653
+ executed: Boolean(compileResult.smoke),
1654
+ checks: compileResult.smoke ? compileResult.smoke.checks.length : 0,
1655
+ failedChecks: compileResult.smoke ? compileResult.smoke.failedChecks.length : 0,
1656
+ diagnostics: diagnostics.length,
1657
+ warnings: warnings.length
1658
+ }
1659
+ }, cwd);
1660
+ return {
1661
+ artifact,
1662
+ plan,
1663
+ report,
1664
+ smoke,
1665
+ registry,
1666
+ sidecarAbi,
1667
+ bodyPolicy,
1668
+ files: files.concat([makeFile('generated/host/schema-json-sidecar.md', buildMarkdown(artifact))]),
1669
+ outputFiles: compileResult.outputs || [],
1670
+ diagnostics,
1671
+ warnings
1672
+ };
1673
+ }
1674
+
1675
+ module.exports = { buildSchemaJsonSidecarV2, shouldUseSchemaJsonV2 };