@pulse-compute/wasm-schema-json 0.0.0 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -1
- package/package.json +35 -6
- package/src/compiler/canonical-schema-codecs.js +351 -0
- package/src/compiler/index.js +23 -0
- package/src/compiler/schema-json-compile.js +575 -0
- package/src/compiler/schema-json-generic-parser.js +539 -0
- package/src/compiler/schema-json-sidecar-v2.js +1675 -0
- package/src/compiler/schema-json-sidecar.js +889 -0
- package/src/compiler/schema-registry.js +705 -0
- package/src/index.js +8 -0
package/README.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
1
|
# @pulse-compute/wasm-schema-json
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- pulse-package-status:start -->
|
|
4
|
+
> **Support tier:** Implementation/transitive surface<br>
|
|
5
|
+
> **Audience:** Pulse schema compiler and runtime maintainers.<br>
|
|
6
|
+
> **Install directly:** No for application projects; declare schemas in the selected .pulse/config.ts profile.<br>
|
|
7
|
+
> **Supported entry points:** `None for application authors.`<br>
|
|
8
|
+
> **Stability:** Internal schema compiler interface synchronized with this release set.<br>
|
|
9
|
+
> **npm:** [`@pulse-compute/wasm-schema-json`](https://www.npmjs.com/package/@pulse-compute/wasm-schema-json)<br>
|
|
10
|
+
> **Canonical replacement:** `schemas in .pulse/config.ts through the Pulse project workflow`<br>
|
|
11
|
+
> **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.2/packages/implementation-packages/)
|
|
12
|
+
>
|
|
13
|
+
> This release-status block is generated from the synchronized `Pulse 1.0.0-beta.2` package policy.
|
|
14
|
+
<!-- pulse-package-status:end -->
|
|
15
|
+
|
|
16
|
+
JSON schema compiler, canonical codec, registry, sidecar, and body-policy implementation for Pulse.
|
|
17
|
+
|
|
18
|
+
Application projects declare schemas in `pulse.config.*`; they do not install or import this package directly.
|
|
19
|
+
|
|
20
|
+
## Responsibilities
|
|
21
|
+
|
|
22
|
+
- compile explicitly declared TypeScript schema roots;
|
|
23
|
+
- build the canonical schema registry and stable schema IDs;
|
|
24
|
+
- generate request decode and response encode codecs;
|
|
25
|
+
- enforce content-type and maximum-body policies;
|
|
26
|
+
- generate and validate schema sidecar/compiler artifacts;
|
|
27
|
+
- provide the generic parser implementation used by maintained internal tracks.
|
|
28
|
+
|
|
29
|
+
## Boundaries
|
|
30
|
+
|
|
31
|
+
- The CLI owns project configuration and user-facing diagnostics.
|
|
32
|
+
- Shared contracts own schema protocol vocabulary.
|
|
33
|
+
- The canonical handler names schemas through `ctx.req.json('namespace.Type')` and `ctx.json(value, { schema: 'namespace.Type' })`.
|
|
34
|
+
- Pulse does not scan arbitrary project types or materialize provider-specific body objects.
|
|
35
|
+
|
|
36
|
+
The exported compiler modules are synchronized implementation interfaces without an application-author compatibility promise.
|
|
37
|
+
|
|
38
|
+
See [Explicit JSON schemas](https://pulsecompute.io/v1.0.0-beta.2/guides/json-schemas/), [Project configuration](https://pulsecompute.io/v1.0.0-beta.2/reference/project-config/#json-schema-policy), and [Implementation packages](https://pulsecompute.io/v1.0.0-beta.2/packages/implementation-packages/).
|
package/package.json
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-compute/wasm-schema-json",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Inert namespace bootstrap for Pulse.",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
5
4
|
"license": "Apache-2.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": "^22.14.0 || ^24.0.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "PulseWasm schema JSON sidecar, compile, registry, and body-policy helpers.",
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"main": "./src/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.js",
|
|
13
|
+
"./compiler": "./src/compiler/index.js",
|
|
14
|
+
"./compiler/schema-json-compile": "./src/compiler/schema-json-compile.js",
|
|
15
|
+
"./compiler/schema-json-generic-parser": "./src/compiler/schema-json-generic-parser.js",
|
|
16
|
+
"./compiler/schema-json-sidecar": "./src/compiler/schema-json-sidecar.js",
|
|
17
|
+
"./compiler/schema-json-sidecar-v2": "./src/compiler/schema-json-sidecar-v2.js",
|
|
18
|
+
"./compiler/canonical-schema-codecs": "./src/compiler/canonical-schema-codecs.js",
|
|
19
|
+
"./compiler/schema-registry": "./src/compiler/schema-registry.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@pulse-compute/wasm-build-support": "1.0.0-beta.2",
|
|
23
|
+
"@pulse-compute/wasm-contracts": "1.0.0-beta.2",
|
|
24
|
+
"typescript": "5.9.3"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
6
29
|
"files": [
|
|
30
|
+
"src",
|
|
7
31
|
"README.md",
|
|
8
32
|
"LICENSE",
|
|
9
33
|
"NOTICE"
|
|
10
34
|
],
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/pulse-compute/pulse.git",
|
|
38
|
+
"directory": "wasm/packages/schema-json"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://pulsecompute.io/v1.0.0-beta.2/packages/implementation-packages/",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/pulse-compute/pulse/issues"
|
|
14
43
|
}
|
|
15
|
-
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const {
|
|
5
|
+
SCHEMA_REGISTRY_IR_VERSION,
|
|
6
|
+
normalizeSchemaRegistry
|
|
7
|
+
} = require('@pulse-compute/wasm-contracts/schema-json/registry');
|
|
8
|
+
const {
|
|
9
|
+
normalizeJsonTraceEvent,
|
|
10
|
+
semanticValueDigest
|
|
11
|
+
} = require('@pulse-compute/wasm-contracts/schema-json/semantic-trace');
|
|
12
|
+
|
|
13
|
+
const CANONICAL_SCHEMA_BUNDLE_VERSION = 'pulse.canonical-schema-bundle.v2';
|
|
14
|
+
const CANONICAL_SCHEMA_REGISTRY_VERSION = 'pulse.canonical-schema-registry.v2';
|
|
15
|
+
const CANONICAL_SCHEMA_CODECS_VERSION = 'pulse.canonical-schema-codecs.v2';
|
|
16
|
+
const JAVASCRIPT_SCHEMA_CODEC_VERSION = 'pulse.javascript-schema-codec.v1';
|
|
17
|
+
const NATIVE_SCHEMA_CODEC_VERSION = 'pulse.native-json-as-schema-codec.v1';
|
|
18
|
+
|
|
19
|
+
function stableObject(value) {
|
|
20
|
+
if (Array.isArray(value)) return value.map(stableObject);
|
|
21
|
+
if (!value || typeof value !== 'object') return value;
|
|
22
|
+
const output = {};
|
|
23
|
+
for (const key of Object.keys(value).sort()) {
|
|
24
|
+
if (value[key] !== undefined) output[key] = stableObject(value[key]);
|
|
25
|
+
}
|
|
26
|
+
return output;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stableStringify(value) {
|
|
30
|
+
return JSON.stringify(stableObject(value));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stableHash(value) {
|
|
34
|
+
return crypto.createHash('sha256').update(typeof value === 'string' ? value : stableStringify(value)).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function identifier(value) {
|
|
38
|
+
const normalized = String(value || 'schema').normalize('NFKC').replace(/[^A-Za-z0-9_$]+/g, '_');
|
|
39
|
+
return /^[A-Za-z_$]/.test(normalized) ? normalized : `_${normalized}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function deepFreeze(value) {
|
|
43
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
44
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
45
|
+
return Object.freeze(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function legacyNode(type) {
|
|
49
|
+
const kind = String(type) === 'bool' ? 'boolean' : String(type);
|
|
50
|
+
if (!['string', 'boolean', 'i32', 'u32', 'f64'].includes(kind)) {
|
|
51
|
+
throw new TypeError(`Canonical legacy schema field uses unsupported type ${String(type)}.`);
|
|
52
|
+
}
|
|
53
|
+
return Object.freeze({ kind });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function legacyRegistry(compiledSchemas) {
|
|
57
|
+
const schemas = compiledSchemas.map((schema, schemaIndex) => Object.freeze({
|
|
58
|
+
id: String(schema.id),
|
|
59
|
+
typeName: String(schema.type || schema.name || `Schema${schemaIndex + 1}`),
|
|
60
|
+
root: Object.freeze({
|
|
61
|
+
kind: 'object',
|
|
62
|
+
fields: Object.freeze((schema.fields || []).map((field, fieldIndex) => Object.freeze({
|
|
63
|
+
name: String(field.name),
|
|
64
|
+
required: true,
|
|
65
|
+
value: legacyNode(field.type),
|
|
66
|
+
source: Object.freeze({
|
|
67
|
+
file: String(schema.source || '<legacy-schema>'),
|
|
68
|
+
line: fieldIndex + 1,
|
|
69
|
+
column: 1
|
|
70
|
+
})
|
|
71
|
+
})))
|
|
72
|
+
}),
|
|
73
|
+
source: Object.freeze({
|
|
74
|
+
file: String(schema.source || '<legacy-schema>'),
|
|
75
|
+
line: schemaIndex + 1,
|
|
76
|
+
column: 1
|
|
77
|
+
})
|
|
78
|
+
}));
|
|
79
|
+
return normalizeSchemaRegistry({
|
|
80
|
+
source: Object.freeze({ file: '<legacy-schema-registry>', line: 1, column: 1 }),
|
|
81
|
+
schemas,
|
|
82
|
+
responses: Object.freeze([])
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function canonicalRegistry(input, options = {}) {
|
|
87
|
+
const registryIr = Array.isArray(input)
|
|
88
|
+
? legacyRegistry(input)
|
|
89
|
+
: normalizeSchemaRegistry(input || {
|
|
90
|
+
source: Object.freeze({ file: '<empty-schema-registry>', line: 1, column: 1 }),
|
|
91
|
+
schemas: Object.freeze([]),
|
|
92
|
+
responses: Object.freeze([])
|
|
93
|
+
});
|
|
94
|
+
const contentTypePolicy = String(options.contentTypePolicy || 'accept-json-or-missing');
|
|
95
|
+
if (!['accept-json-or-missing', 'require-json'].includes(contentTypePolicy)) {
|
|
96
|
+
throw new TypeError(`Unsupported canonical schema content-type policy ${contentTypePolicy}.`);
|
|
97
|
+
}
|
|
98
|
+
const configuredMaxBytes = Number(options.maxBytes);
|
|
99
|
+
const maxBytes = Number.isSafeInteger(configuredMaxBytes) && configuredMaxBytes > 0
|
|
100
|
+
? configuredMaxBytes
|
|
101
|
+
: 65_536;
|
|
102
|
+
const codecEntries = registryIr.schemas.map((schema, schemaIndex) => {
|
|
103
|
+
const semantic = {
|
|
104
|
+
registryVersion: registryIr.version,
|
|
105
|
+
registryHash: registryIr.registryHash,
|
|
106
|
+
id: schema.id,
|
|
107
|
+
root: schema.root,
|
|
108
|
+
policies: registryIr.policies
|
|
109
|
+
};
|
|
110
|
+
const semanticHash = stableHash(semantic);
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
id: schema.id,
|
|
113
|
+
semanticHash,
|
|
114
|
+
javascript: Object.freeze({
|
|
115
|
+
version: JAVASCRIPT_SCHEMA_CODEC_VERSION,
|
|
116
|
+
symbol: `__pulse_schema_${identifier(schema.id)}_${schemaIndex}`,
|
|
117
|
+
hash: stableHash(`${JAVASCRIPT_SCHEMA_CODEC_VERSION}:${semanticHash}`)
|
|
118
|
+
}),
|
|
119
|
+
native: Object.freeze({
|
|
120
|
+
version: NATIVE_SCHEMA_CODEC_VERSION,
|
|
121
|
+
backend: 'json-as',
|
|
122
|
+
packageVersion: '1.5.0',
|
|
123
|
+
symbol: `__Pulse_${identifier(schema.id)}_${schemaIndex}`,
|
|
124
|
+
hash: stableHash(`${NATIVE_SCHEMA_CODEC_VERSION}:json-as@1.5.0:${semanticHash}`)
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
const semantic = {
|
|
129
|
+
version: CANONICAL_SCHEMA_REGISTRY_VERSION,
|
|
130
|
+
registryIrVersion: SCHEMA_REGISTRY_IR_VERSION,
|
|
131
|
+
registryHash: registryIr.registryHash,
|
|
132
|
+
contentTypePolicy,
|
|
133
|
+
maxBytes,
|
|
134
|
+
policies: registryIr.policies,
|
|
135
|
+
schemas: registryIr.schemas,
|
|
136
|
+
responses: registryIr.responses,
|
|
137
|
+
codecs: codecEntries
|
|
138
|
+
};
|
|
139
|
+
return deepFreeze({
|
|
140
|
+
...semantic,
|
|
141
|
+
codecTableHash: stableHash(codecEntries),
|
|
142
|
+
sourceHash: stableHash(semantic)
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function schemaFunctionName(registry, schemaIndex) {
|
|
147
|
+
return registry.codecs[schemaIndex].javascript.symbol;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function renderCanonicalSchemaCodecDeclaration(registryInput, options = {}) {
|
|
151
|
+
const registry = registryInput && registryInput.version === CANONICAL_SCHEMA_REGISTRY_VERSION
|
|
152
|
+
? registryInput
|
|
153
|
+
: canonicalRegistry(registryInput, options);
|
|
154
|
+
const registryName = options.registryName || '__pulse_schema_registry';
|
|
155
|
+
const codecsName = options.codecsName || '__pulse_schema_codecs';
|
|
156
|
+
const lines = [];
|
|
157
|
+
lines.push(`const ${registryName} = Object.freeze(${JSON.stringify(registry, null, 2)});`);
|
|
158
|
+
lines.push('function __pulse_schema_kind(value) {');
|
|
159
|
+
lines.push(" if (value === null) return 'null';");
|
|
160
|
+
lines.push(" if (Array.isArray(value)) return 'array';");
|
|
161
|
+
lines.push(" if (typeof value === 'number' && !Number.isFinite(value)) return 'non-finite-number';");
|
|
162
|
+
lines.push(' return typeof value;');
|
|
163
|
+
lines.push('}');
|
|
164
|
+
lines.push('function __pulse_schema_pointer(path, segment) {');
|
|
165
|
+
lines.push(" const escaped = String(segment).replace(/~/g, '~0').replace(/\\//g, '~1');");
|
|
166
|
+
lines.push(" return (path || '') + '/' + escaped;");
|
|
167
|
+
lines.push('}');
|
|
168
|
+
lines.push('function __pulse_schema_failure(mode, code, schemaId, path, expected, actual, source, cause) {');
|
|
169
|
+
lines.push(" const encode = mode === 'encode';");
|
|
170
|
+
lines.push(" const error = new Error((encode ? 'Schema encode' : 'Schema decode') + ' failed for ' + schemaId + (path || '') + ': expected ' + expected + ', received ' + actual + '.');");
|
|
171
|
+
lines.push(" error.name = encode ? 'SchemaEncodeError' : 'SchemaDecodeError';");
|
|
172
|
+
lines.push(" error.code = code || (encode ? 'PULSE_SCHEMA_ENCODE' : 'PULSE_SCHEMA_DECODE');");
|
|
173
|
+
lines.push(' error.detail = Object.freeze({ schemaId: String(schemaId), path: path || null, expected, actualKind: actual, source: String(source || mode) });');
|
|
174
|
+
lines.push(' if (cause !== undefined) error.cause = cause;');
|
|
175
|
+
lines.push(' return error;');
|
|
176
|
+
lines.push('}');
|
|
177
|
+
lines.push('function __pulse_schema_reference_error(schemaId, source) {');
|
|
178
|
+
lines.push(" const error = new Error('Unknown compiled schema ' + String(schemaId) + '.');");
|
|
179
|
+
lines.push(" error.name = 'SchemaReferenceError';");
|
|
180
|
+
lines.push(" error.code = 'PULSE_SCHEMA_REFERENCE';");
|
|
181
|
+
lines.push(" error.detail = Object.freeze({ schemaId: String(schemaId), source: String(source || 'schema') });");
|
|
182
|
+
lines.push(' return error;');
|
|
183
|
+
lines.push('}');
|
|
184
|
+
lines.push('function __pulse_schema_data_value(value, field, mode, schemaId, path, source) {');
|
|
185
|
+
lines.push(' const descriptor = Object.getOwnPropertyDescriptor(value, field);');
|
|
186
|
+
lines.push(" if (!descriptor) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'required', 'missing', source);");
|
|
187
|
+
lines.push(" if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'data-property', 'accessor', source);");
|
|
188
|
+
lines.push(' return descriptor.value;');
|
|
189
|
+
lines.push('}');
|
|
190
|
+
lines.push('function __pulse_schema_apply_node(node, value, mode, schemaId, path, source) {');
|
|
191
|
+
lines.push(' const kind = node.kind;');
|
|
192
|
+
lines.push(" if (kind === 'nullable') {");
|
|
193
|
+
lines.push(" if (value === null) return null;");
|
|
194
|
+
lines.push(' return __pulse_schema_apply_node(node.value, value, mode, schemaId, path, source);');
|
|
195
|
+
lines.push(' }');
|
|
196
|
+
lines.push(" if (kind === 'string') {");
|
|
197
|
+
lines.push(" if (typeof value !== 'string') throw __pulse_schema_failure(mode, undefined, schemaId, path, 'string', __pulse_schema_kind(value), source);");
|
|
198
|
+
lines.push(' return value;');
|
|
199
|
+
lines.push(' }');
|
|
200
|
+
lines.push(" if (kind === 'boolean') {");
|
|
201
|
+
lines.push(" if (typeof value !== 'boolean') throw __pulse_schema_failure(mode, undefined, schemaId, path, 'boolean', __pulse_schema_kind(value), source);");
|
|
202
|
+
lines.push(' return value;');
|
|
203
|
+
lines.push(' }');
|
|
204
|
+
lines.push(" if (kind === 'i32') {");
|
|
205
|
+
lines.push(" if (!Number.isInteger(value) || value < -2147483648 || value > 2147483647) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'i32', __pulse_schema_kind(value), source);");
|
|
206
|
+
lines.push(' return value;');
|
|
207
|
+
lines.push(' }');
|
|
208
|
+
lines.push(" if (kind === 'u32') {");
|
|
209
|
+
lines.push(" if (!Number.isInteger(value) || value < 0 || value > 4294967295) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'u32', __pulse_schema_kind(value), source);");
|
|
210
|
+
lines.push(' return value;');
|
|
211
|
+
lines.push(' }');
|
|
212
|
+
lines.push(" if (kind === 'f64') {");
|
|
213
|
+
lines.push(" if (typeof value !== 'number' || !Number.isFinite(value)) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'finite-f64', __pulse_schema_kind(value), source);");
|
|
214
|
+
lines.push(' return value;');
|
|
215
|
+
lines.push(' }');
|
|
216
|
+
lines.push(" if (kind === 'string-enum') {");
|
|
217
|
+
lines.push(" if (typeof value !== 'string' || !node.values.includes(value)) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'enum(' + node.values.map(JSON.stringify).join('|') + ')', __pulse_schema_kind(value), source);");
|
|
218
|
+
lines.push(' return value;');
|
|
219
|
+
lines.push(' }');
|
|
220
|
+
lines.push(" if (kind === 'array') {");
|
|
221
|
+
lines.push(" if (!Array.isArray(value)) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'array', __pulse_schema_kind(value), source);");
|
|
222
|
+
lines.push(' return Object.freeze(value.map((entry, index) => __pulse_schema_apply_node(node.element, entry, mode, schemaId, __pulse_schema_pointer(path, index), source)));');
|
|
223
|
+
lines.push(' }');
|
|
224
|
+
lines.push(" if (kind === 'object') {");
|
|
225
|
+
lines.push(" if (!value || typeof value !== 'object' || Array.isArray(value)) throw __pulse_schema_failure(mode, undefined, schemaId, path, 'object', __pulse_schema_kind(value), source);");
|
|
226
|
+
lines.push(' const output = {};');
|
|
227
|
+
lines.push(' for (const field of node.fields) {');
|
|
228
|
+
lines.push(' const fieldPath = __pulse_schema_pointer(path, field.name);');
|
|
229
|
+
lines.push(' const fieldValue = __pulse_schema_data_value(value, field.name, mode, schemaId, fieldPath, source);');
|
|
230
|
+
lines.push(' Object.defineProperty(output, field.name, { enumerable: true, configurable: false, writable: false, value: __pulse_schema_apply_node(field.value, fieldValue, mode, schemaId, fieldPath, source) });');
|
|
231
|
+
lines.push(' }');
|
|
232
|
+
lines.push(' return Object.freeze(output);');
|
|
233
|
+
lines.push(' }');
|
|
234
|
+
lines.push(" throw __pulse_schema_failure(mode, undefined, schemaId, path, 'supported-schema-node', String(kind), source);");
|
|
235
|
+
lines.push('}');
|
|
236
|
+
|
|
237
|
+
registry.schemas.forEach((schema, index) => {
|
|
238
|
+
const fn = schemaFunctionName(registry, index);
|
|
239
|
+
lines.push(`function ${fn}(value, mode, source) {`);
|
|
240
|
+
lines.push(` return __pulse_schema_apply_node(${registryName}.schemas[${index}].root, value, mode, ${JSON.stringify(schema.id)}, '', source);`);
|
|
241
|
+
lines.push('}');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
lines.push('function __pulse_schema_apply(schemaId, value, mode, source) {');
|
|
245
|
+
lines.push(' switch (String(schemaId)) {');
|
|
246
|
+
for (let index = 0; index < registry.schemas.length; index += 1) {
|
|
247
|
+
const schema = registry.schemas[index];
|
|
248
|
+
lines.push(` case ${JSON.stringify(schema.id)}: return ${schemaFunctionName(registry, index)}(value, mode, source);`);
|
|
249
|
+
}
|
|
250
|
+
lines.push(' default: throw __pulse_schema_reference_error(schemaId, source);');
|
|
251
|
+
lines.push(' }');
|
|
252
|
+
lines.push('}');
|
|
253
|
+
lines.push(`const ${codecsName} = Object.freeze({`);
|
|
254
|
+
lines.push(` version: ${JSON.stringify(CANONICAL_SCHEMA_CODECS_VERSION)},`);
|
|
255
|
+
lines.push(` registry: ${registryName},`);
|
|
256
|
+
lines.push(` ids: Object.freeze(${JSON.stringify(registry.schemas.map((schema) => schema.id))}),`);
|
|
257
|
+
lines.push(` responseCaseIds: Object.freeze(${JSON.stringify(registry.responses.map((entry) => entry.id))}),`);
|
|
258
|
+
lines.push(` codecTableHash: ${JSON.stringify(registry.codecTableHash)},`);
|
|
259
|
+
lines.push(' has(schemaId) { return this.ids.includes(String(schemaId)); },');
|
|
260
|
+
lines.push(' schema(schemaId) { return this.registry.schemas.find((entry) => entry.id === String(schemaId)); },');
|
|
261
|
+
lines.push(' responseCase(responseCaseId) { return this.registry.responses.find((entry) => entry.id === String(responseCaseId)); },');
|
|
262
|
+
lines.push(' codec(schemaId) { return this.registry.codecs.find((entry) => entry.id === String(schemaId)); },');
|
|
263
|
+
lines.push(" decode(schemaId, value, source = 'json') { return __pulse_schema_apply(schemaId, value, 'decode', source); },");
|
|
264
|
+
lines.push(" encode(schemaId, value, source = 'response') { return __pulse_schema_apply(schemaId, value, 'encode', source); },");
|
|
265
|
+
lines.push(" decodeJsonText(schemaId, text, source = 'json') {");
|
|
266
|
+
lines.push(' let value;');
|
|
267
|
+
lines.push(' try { value = JSON.parse(String(text)); }');
|
|
268
|
+
lines.push(" catch (cause) { throw __pulse_schema_failure('decode', 'PULSE_SCHEMA_JSON_MALFORMED', String(schemaId), '', 'valid-json', 'malformed-json', source, cause); }");
|
|
269
|
+
lines.push(" return __pulse_schema_apply(schemaId, value, 'decode', source);");
|
|
270
|
+
lines.push(' },');
|
|
271
|
+
lines.push(" encodeJsonText(schemaId, value, source = 'response') {");
|
|
272
|
+
lines.push(" const normalized = __pulse_schema_apply(schemaId, value, 'encode', source);");
|
|
273
|
+
lines.push(' try { return JSON.stringify(normalized); }');
|
|
274
|
+
lines.push(" catch (cause) { throw __pulse_schema_failure('encode', 'PULSE_SCHEMA_ENCODE', String(schemaId), '', 'serializable-json', __pulse_schema_kind(normalized), source, cause); }");
|
|
275
|
+
lines.push(' }');
|
|
276
|
+
lines.push('});');
|
|
277
|
+
return `${lines.join('\n')}\n`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function createCanonicalSchemaCodecs(registryInput, options = {}) {
|
|
281
|
+
const registry = registryInput && registryInput.version === CANONICAL_SCHEMA_REGISTRY_VERSION
|
|
282
|
+
? registryInput
|
|
283
|
+
: canonicalRegistry(registryInput, options);
|
|
284
|
+
const declaration = renderCanonicalSchemaCodecDeclaration(registry, options);
|
|
285
|
+
// The source is produced exclusively from normalized, JSON-serialized schema
|
|
286
|
+
// IR. Evaluating it here keeps the in-memory JavaScript target and emitted
|
|
287
|
+
// build artifact on the exact same generated implementation.
|
|
288
|
+
const generated = Function(`${declaration}\nreturn ${options.codecsName || '__pulse_schema_codecs'};`)();
|
|
289
|
+
return Object.freeze({
|
|
290
|
+
...generated,
|
|
291
|
+
createTraceEvent(input, traceOptions = {}) {
|
|
292
|
+
const { semanticValue, ...event } = input || {};
|
|
293
|
+
return normalizeJsonTraceEvent({
|
|
294
|
+
...event,
|
|
295
|
+
valueDigest: semanticValue === undefined
|
|
296
|
+
? null
|
|
297
|
+
: semanticValueDigest(semanticValue, { redact: traceOptions.redact })
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildCanonicalSchemaBundle(registryInput = [], options = {}) {
|
|
304
|
+
const registry = canonicalRegistry(registryInput, options);
|
|
305
|
+
const declarationSource = renderCanonicalSchemaCodecDeclaration(registry, options);
|
|
306
|
+
const codecsName = options.codecsName || '__pulse_schema_codecs';
|
|
307
|
+
const moduleSource = `${declarationSource}
|
|
308
|
+
const {
|
|
309
|
+
normalizeJsonTraceEvent: __pulse_normalize_json_trace_event,
|
|
310
|
+
semanticValueDigest: __pulse_semantic_value_digest
|
|
311
|
+
} = require('@pulse-compute/wasm-contracts/schema-json/semantic-trace');
|
|
312
|
+
module.exports = Object.freeze({
|
|
313
|
+
...${codecsName},
|
|
314
|
+
createTraceEvent(input, traceOptions = {}) {
|
|
315
|
+
const { semanticValue, ...event } = input || {};
|
|
316
|
+
return __pulse_normalize_json_trace_event({
|
|
317
|
+
...event,
|
|
318
|
+
valueDigest: semanticValue === undefined
|
|
319
|
+
? null
|
|
320
|
+
: __pulse_semantic_value_digest(semanticValue, { redact: traceOptions.redact })
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
`;
|
|
325
|
+
return Object.freeze({
|
|
326
|
+
version: CANONICAL_SCHEMA_BUNDLE_VERSION,
|
|
327
|
+
registry,
|
|
328
|
+
declarationSource,
|
|
329
|
+
moduleSource,
|
|
330
|
+
active: registry.schemas.length > 0,
|
|
331
|
+
schemaIds: Object.freeze(registry.schemas.map((schema) => schema.id)),
|
|
332
|
+
responseCaseIds: Object.freeze(registry.responses.map((entry) => entry.id)),
|
|
333
|
+
codecTableHash: registry.codecTableHash,
|
|
334
|
+
sourceHash: registry.sourceHash,
|
|
335
|
+
fullCodecRealization: true
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = Object.freeze({
|
|
340
|
+
CANONICAL_SCHEMA_BUNDLE_VERSION,
|
|
341
|
+
CANONICAL_SCHEMA_REGISTRY_VERSION,
|
|
342
|
+
CANONICAL_SCHEMA_CODECS_VERSION,
|
|
343
|
+
JAVASCRIPT_SCHEMA_CODEC_VERSION,
|
|
344
|
+
NATIVE_SCHEMA_CODEC_VERSION,
|
|
345
|
+
buildCanonicalSchemaBundle,
|
|
346
|
+
canonicalRegistry,
|
|
347
|
+
createCanonicalSchemaCodecs,
|
|
348
|
+
renderCanonicalSchemaCodecDeclaration,
|
|
349
|
+
stableHash,
|
|
350
|
+
stableStringify
|
|
351
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const schemaJsonCompile = require('./schema-json-compile.js');
|
|
4
|
+
const schemaJsonSidecar = require('./schema-json-sidecar.js');
|
|
5
|
+
const schemaJsonGenericParser = require('./schema-json-generic-parser.js');
|
|
6
|
+
const schemaJsonSidecarV2 = require('./schema-json-sidecar-v2.js');
|
|
7
|
+
const canonicalSchemaCodecs = require('./canonical-schema-codecs.js');
|
|
8
|
+
const schemaRegistry = require('./schema-registry.js');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
schemaJsonCompile,
|
|
12
|
+
schemaJsonSidecar,
|
|
13
|
+
schemaJsonGenericParser,
|
|
14
|
+
schemaJsonSidecarV2,
|
|
15
|
+
canonicalSchemaCodecs,
|
|
16
|
+
schemaRegistry,
|
|
17
|
+
...schemaJsonCompile,
|
|
18
|
+
...schemaJsonSidecar,
|
|
19
|
+
...schemaJsonGenericParser,
|
|
20
|
+
...schemaJsonSidecarV2,
|
|
21
|
+
...canonicalSchemaCodecs,
|
|
22
|
+
...schemaRegistry
|
|
23
|
+
};
|