@pulse-compute/wasm-contracts 1.0.0-beta.4 → 1.0.0-beta.6
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 +2 -2
- package/package.json +2 -2
- package/src/assets/contracts.js +7 -1
- package/src/assets/embedded.as.ts +48 -0
- package/src/assets/embedded.js +63 -0
- package/src/crypto/contracts.js +11 -3
- package/src/definitions/router-api.js +3 -2
- package/src/diagnostics/codes.js +1 -0
- package/src/diagnostics.js +1 -1
- package/src/entities/contracts.d.ts +1 -0
- package/src/entities/contracts.js +21 -0
- package/src/handler/canonical-native-plan.js +61 -2
- package/src/handler/canonical-native-runtime.js +59 -3
- package/src/handler/canonical-runtime.js +10 -1
- package/src/handler/ctx-contract.js +3 -3
- package/src/handler/runtime-api.js +13 -3
- package/src/handler/surface-contract.js +46 -0
- package/src/handler/types-contract.js +1 -1
- package/src/jwt/contracts.js +42 -1
- package/src/library/manifest.js +11 -0
- package/src/project/native-optimization.js +3 -1
- package/src/provider/canonical-provider.js +7 -1
- package/src/provider/toolchain.js +8 -0
- package/src/s3/contracts.js +4 -4
- package/src/schema-json/admission-text.js +212 -0
- package/src/schema-json/admission.js +99 -0
- package/src/schema-json/contracts.js +1 -0
- package/src/schema-json/registry.js +119 -18
- package/src/schema-json/semantic-trace.js +4 -2
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { sha256Hex, stableStringify } = require('../stable-id.js');
|
|
4
|
+
const { JSON_LIMIT_FIELDS, normalizeJsonAdmissionLimits } = require('./admission.js');
|
|
4
5
|
|
|
5
|
-
const SCHEMA_REGISTRY_IR_VERSION = 'pulse.schema-registry-ir.
|
|
6
|
-
const SCHEMA_CODEC_INPUTS_VERSION = 'pulse.schema-codec-inputs.
|
|
7
|
-
const SCHEMA_AUTHORING_VERSION = 'pulse.schema-authoring.
|
|
6
|
+
const SCHEMA_REGISTRY_IR_VERSION = 'pulse.schema-registry-ir.v5';
|
|
7
|
+
const SCHEMA_CODEC_INPUTS_VERSION = 'pulse.schema-codec-inputs.v5';
|
|
8
|
+
const SCHEMA_AUTHORING_VERSION = 'pulse.schema-authoring.v3';
|
|
8
9
|
const SCHEMA_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)+$/;
|
|
9
10
|
const RESPONSE_CASE_ID_PATTERN = SCHEMA_ID_PATTERN;
|
|
10
11
|
const SCALAR_KINDS = Object.freeze(['string', 'boolean', 'i32', 'u32', 'f64']);
|
|
11
|
-
const NODE_KINDS = Object.freeze([...SCALAR_KINDS, 'object', 'array', 'string-enum', 'nullable']);
|
|
12
|
+
const NODE_KINDS = Object.freeze([...SCALAR_KINDS, 'object', 'array', 'string-enum', 'nullable', 'scalar-record', 'json-value', 'json-object']);
|
|
13
|
+
const SCALAR_RECORD_LIMITS = Object.freeze({ maxKeys: 32, maxKeyLength: 64, maxStringLength: 1024, maxBytes: 8192, numberBytes: 24 });
|
|
14
|
+
const JSON_SCHEMA_DEFAULT_LIMITS = Object.freeze({ maxTextBytes: 65536, maxDepth: 32, maxNodes: 4096,
|
|
15
|
+
maxObjectMembers: 256, maxArrayItems: 1024, maxKeyLength: 256, maxStringLength: 16384, maxJsonBytes: 65536 });
|
|
16
|
+
|
|
17
|
+
function normalizeJsonSchemaLimits(input = {}) {
|
|
18
|
+
if (!plainObject(input)) throw schemaContractError('PULSE_SCHEMA_JSON_LIMITS_INVALID', 'Schema JSON limits must be a static object.');
|
|
19
|
+
const values = { ...JSON_SCHEMA_DEFAULT_LIMITS };
|
|
20
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
21
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
22
|
+
if (!JSON_LIMIT_FIELDS.includes(key) || !descriptor || !Object.hasOwn(descriptor, 'value')) {
|
|
23
|
+
throw schemaContractError('PULSE_SCHEMA_JSON_LIMITS_INVALID', 'Unknown JSON limit or non-data property.');
|
|
24
|
+
}
|
|
25
|
+
values[key] = descriptor.value;
|
|
26
|
+
}
|
|
27
|
+
try { return normalizeJsonAdmissionLimits(values); }
|
|
28
|
+
catch { throw schemaContractError('PULSE_SCHEMA_JSON_LIMITS_INVALID', 'JSON limits must be positive i32 integers.'); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Shared conservative UTF-8 JSON string sizing. Native code generation reuses
|
|
32
|
+
// this pure function body; keep it independent of host APIs and mutable state.
|
|
33
|
+
function scalarRecordStringBytes(text) {
|
|
34
|
+
let bytes = 2;
|
|
35
|
+
for (let i = 0; i < text.length; i++) {
|
|
36
|
+
const code = text.charCodeAt(i);
|
|
37
|
+
if (code < 32) bytes += 6;
|
|
38
|
+
else if (code === 34 || code === 92) bytes += 2;
|
|
39
|
+
else if (code < 128) bytes += 1;
|
|
40
|
+
else if (code < 2048) bytes += 2;
|
|
41
|
+
else if (code >= 0xd800 && code <= 0xdbff && i + 1 < text.length && text.charCodeAt(i + 1) >= 0xdc00 && text.charCodeAt(i + 1) <= 0xdfff) { bytes += 4; i++; }
|
|
42
|
+
else if (code >= 0xd800 && code <= 0xdfff) bytes += 6;
|
|
43
|
+
else bytes += 3;
|
|
44
|
+
}
|
|
45
|
+
return bytes;
|
|
46
|
+
}
|
|
12
47
|
|
|
13
48
|
function schemaContractError(code, message, details = {}) {
|
|
14
49
|
const error = new TypeError(message);
|
|
@@ -73,6 +108,14 @@ function normalizeSchemaNode(input, field = 'schema.root', stack = []) {
|
|
|
73
108
|
throw schemaContractError('PULSE_SCHEMA_IR_NODE_KIND_UNSUPPORTED', `${field}.kind is unsupported: ${kind}.`, { field, kind });
|
|
74
109
|
}
|
|
75
110
|
if (SCALAR_KINDS.includes(kind)) return Object.freeze({ kind });
|
|
111
|
+
if (kind === 'json-value' || kind === 'json-object') return Object.freeze({ kind });
|
|
112
|
+
if (kind === 'scalar-record') {
|
|
113
|
+
if (input.limits !== undefined && (!plainObject(input.limits)
|
|
114
|
+
|| stableStringify(input.limits) !== stableStringify(SCALAR_RECORD_LIMITS))) {
|
|
115
|
+
throw schemaContractError('PULSE_SCHEMA_IR_NODE_KIND_UNSUPPORTED', `${field} must use the fixed ScalarRecord limits.`, { field, kind });
|
|
116
|
+
}
|
|
117
|
+
return Object.freeze({ kind, limits: SCALAR_RECORD_LIMITS });
|
|
118
|
+
}
|
|
76
119
|
if (kind === 'array') {
|
|
77
120
|
return Object.freeze({ kind, element: normalizeSchemaNode(input.element, `${field}.element`, stack) });
|
|
78
121
|
}
|
|
@@ -106,20 +149,26 @@ function normalizeSchemaNode(input, field = 'schema.root', stack = []) {
|
|
|
106
149
|
throw schemaContractError('PULSE_SCHEMA_IR_FIELD_DUPLICATE', `${field} declares field ${name} more than once.`, { field, name });
|
|
107
150
|
}
|
|
108
151
|
names.add(name);
|
|
109
|
-
if (entry.required !==
|
|
110
|
-
throw schemaContractError('
|
|
152
|
+
if (typeof entry.required !== 'boolean') {
|
|
153
|
+
throw schemaContractError('PULSE_SCHEMA_IR_FIELD_REQUIRED', `${field}.${name}.required must be a boolean.`, {
|
|
111
154
|
field,
|
|
112
155
|
name
|
|
113
156
|
});
|
|
114
157
|
}
|
|
115
158
|
return Object.freeze({
|
|
116
159
|
name,
|
|
117
|
-
required:
|
|
160
|
+
required: entry.required,
|
|
118
161
|
value: normalizeSchemaNode(entry.value, `${field}.fields[${index}].value`, [...stack, name]),
|
|
119
162
|
source: entry.source ? normalizeSource(entry.source, `${field}.fields[${index}].source`) : undefined
|
|
120
163
|
});
|
|
121
164
|
});
|
|
122
|
-
|
|
165
|
+
if (input.additionalProperties !== undefined
|
|
166
|
+
&& (!plainObject(input.additionalProperties) || input.additionalProperties.kind !== 'json-value'
|
|
167
|
+
|| Object.keys(input.additionalProperties).length !== 1)) {
|
|
168
|
+
throw schemaContractError('PULSE_SCHEMA_IR_NODE_KIND_UNSUPPORTED', `${field}.additionalProperties must be a JSON value policy.`, { field, kind });
|
|
169
|
+
}
|
|
170
|
+
return Object.freeze({ kind, fields: Object.freeze(fields),
|
|
171
|
+
...(input.additionalProperties === undefined ? {} : { additionalProperties: Object.freeze({ kind: 'json-value' }) }) });
|
|
123
172
|
}
|
|
124
173
|
|
|
125
174
|
function normalizeSchema(input, index) {
|
|
@@ -135,6 +184,8 @@ function normalizeSchema(input, index) {
|
|
|
135
184
|
id,
|
|
136
185
|
typeName: requiredString(input.typeName, `schemas[${index}].typeName`),
|
|
137
186
|
root,
|
|
187
|
+
...(input.jsonLimits !== undefined || schemaHasNestedJson(root)
|
|
188
|
+
? { jsonLimits: normalizeJsonSchemaLimits(input.jsonLimits) } : {}),
|
|
138
189
|
source: normalizeSource(input.source, `schemas[${index}].source`)
|
|
139
190
|
});
|
|
140
191
|
}
|
|
@@ -193,10 +244,19 @@ function normalizeSchemaRegistry(input) {
|
|
|
193
244
|
responses,
|
|
194
245
|
policies: deepFreeze({
|
|
195
246
|
objectRoots: true,
|
|
196
|
-
requiredFieldsOnly:
|
|
197
|
-
|
|
198
|
-
|
|
247
|
+
requiredFieldsOnly: false,
|
|
248
|
+
optionalFields: 'omit-absent-own-properties',
|
|
249
|
+
presentUndefined: 'reject',
|
|
250
|
+
unknownInputFields: 'drop-unless-open',
|
|
251
|
+
outputFields: 'declared-and-admitted-open-properties',
|
|
199
252
|
outputFieldOrder: 'declaration',
|
|
253
|
+
scalarRecords: 'bounded-own-scalar-properties',
|
|
254
|
+
scalarRecordDuplicateKeys: 'reject-after-unescaping',
|
|
255
|
+
nestedJson: 'bounded-recursive-json-values',
|
|
256
|
+
nestedJsonDuplicateKeys: 'reject-after-unescaping',
|
|
257
|
+
openObjects: 'known-fields-first-then-bounded-json-extras',
|
|
258
|
+
openObjectDuplicateKeys: 'reject-all-names-after-unescaping',
|
|
259
|
+
jsonAdmission: 'whole-document-before-materialization',
|
|
200
260
|
numericPolicy: 'finite-rfc-json',
|
|
201
261
|
parity: 'semantic',
|
|
202
262
|
dynamicIds: false,
|
|
@@ -248,7 +308,7 @@ function collectNativeClasses(schema, node, symbols, classes, pathParts = []) {
|
|
|
248
308
|
fields: Object.freeze(node.fields.map((field) => Object.freeze({
|
|
249
309
|
name: field.name,
|
|
250
310
|
type: assemblyScriptType(field.value, symbols, [...pathParts, field.name]),
|
|
251
|
-
required:
|
|
311
|
+
required: field.required,
|
|
252
312
|
constraints: field.value.kind === 'string-enum'
|
|
253
313
|
? Object.freeze({ enum: field.value.values })
|
|
254
314
|
: Object.freeze({})
|
|
@@ -256,18 +316,45 @@ function collectNativeClasses(schema, node, symbols, classes, pathParts = []) {
|
|
|
256
316
|
}));
|
|
257
317
|
}
|
|
258
318
|
|
|
319
|
+
function schemaHasOptionalProperties(node) {
|
|
320
|
+
if (node.kind === 'nullable') return schemaHasOptionalProperties(node.value);
|
|
321
|
+
if (node.kind === 'array') return schemaHasOptionalProperties(node.element);
|
|
322
|
+
return node.kind === 'object' && node.fields.some(field => !field.required || schemaHasOptionalProperties(field.value));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function schemaHasScalarRecord(node) {
|
|
326
|
+
if (node.kind === 'scalar-record') return true;
|
|
327
|
+
if (node.kind === 'nullable') return schemaHasScalarRecord(node.value);
|
|
328
|
+
if (node.kind === 'array') return schemaHasScalarRecord(node.element);
|
|
329
|
+
return node.kind === 'object' && node.fields.some(field => schemaHasScalarRecord(field.value));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function schemaNeedsValueProjection(node) {
|
|
333
|
+
return schemaHasOptionalProperties(node) || schemaHasScalarRecord(node) || schemaHasNestedJson(node);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function schemaHasNestedJson(node) {
|
|
337
|
+
if (node.kind === 'json-value' || node.kind === 'json-object') return true;
|
|
338
|
+
if (node.kind === 'nullable') return schemaHasNestedJson(node.value);
|
|
339
|
+
if (node.kind === 'array') return schemaHasNestedJson(node.element);
|
|
340
|
+
return node.kind === 'object' && (Boolean(node.additionalProperties) || node.fields.some(field => schemaHasNestedJson(field.value)));
|
|
341
|
+
}
|
|
342
|
+
|
|
259
343
|
function codecInputsForRegistry(registryInput) {
|
|
260
344
|
const registry = normalizeSchemaRegistry(registryInput);
|
|
261
345
|
const nativeSchemas = registry.schemas.map((schema) => {
|
|
262
346
|
const schemaClasses = [];
|
|
263
347
|
const symbols = new Map();
|
|
264
|
-
|
|
348
|
+
const presence = schemaNeedsValueProjection(schema.root) || Boolean(schema.jsonLimits);
|
|
349
|
+
if (!presence) collectNativeClasses(schema, schema.root, symbols, schemaClasses);
|
|
350
|
+
const rootClass = presence ? 'JSON.Value' : symbols.get('');
|
|
265
351
|
return Object.freeze({
|
|
266
352
|
id: schema.id,
|
|
267
|
-
rootClass
|
|
353
|
+
rootClass,
|
|
354
|
+
representation: presence ? 'schema-projected-json-value' : 'required-struct',
|
|
268
355
|
classes: Object.freeze(schemaClasses),
|
|
269
|
-
parse: `JSON.parse<${
|
|
270
|
-
stringify: `JSON.stringify<${
|
|
356
|
+
parse: `JSON.parse<${rootClass}>`,
|
|
357
|
+
stringify: `JSON.stringify<${rootClass}>`
|
|
271
358
|
});
|
|
272
359
|
});
|
|
273
360
|
return deepFreeze({
|
|
@@ -325,7 +412,9 @@ function defaultSchemaRegistryContract() {
|
|
|
325
412
|
scalarKinds: SCALAR_KINDS,
|
|
326
413
|
nodeKinds: NODE_KINDS,
|
|
327
414
|
helpers: ['defineSchemaRegistry', 'schema', 'response'],
|
|
328
|
-
markerTypes: ['Int32', 'Uint32'],
|
|
415
|
+
markerTypes: ['Int32', 'Uint32', 'ScalarRecord', 'JsonValue', 'JsonObject', 'OpenObject'],
|
|
416
|
+
scalarRecordLimits: SCALAR_RECORD_LIMITS,
|
|
417
|
+
jsonDefaultLimits: JSON_SCHEMA_DEFAULT_LIMITS,
|
|
329
418
|
policies: {
|
|
330
419
|
canonicalEntrypoint: 'pulse.schema',
|
|
331
420
|
defaultExportRequired: true,
|
|
@@ -333,7 +422,11 @@ function defaultSchemaRegistryContract() {
|
|
|
333
422
|
stableStringIds: true,
|
|
334
423
|
exactLiteralIdsAtBoundaries: true,
|
|
335
424
|
oldSchemasJsonSupported: false,
|
|
336
|
-
optionalPropertiesSupported:
|
|
425
|
+
optionalPropertiesSupported: true,
|
|
426
|
+
scalarRecordsSupported: true,
|
|
427
|
+
nestedJsonSupported: true,
|
|
428
|
+
typedOpenObjectsSupported: true,
|
|
429
|
+
staticJsonLimitsSupported: true,
|
|
337
430
|
recursiveSchemasSupported: false,
|
|
338
431
|
publicJsonAsImportsSupported: false,
|
|
339
432
|
automaticFallback: false
|
|
@@ -350,10 +443,18 @@ module.exports = Object.freeze({
|
|
|
350
443
|
RESPONSE_CASE_ID_PATTERN,
|
|
351
444
|
SCALAR_KINDS,
|
|
352
445
|
NODE_KINDS,
|
|
446
|
+
SCALAR_RECORD_LIMITS,
|
|
447
|
+
JSON_SCHEMA_DEFAULT_LIMITS,
|
|
448
|
+
normalizeJsonSchemaLimits,
|
|
449
|
+
scalarRecordStringBytes,
|
|
353
450
|
schemaContractError,
|
|
354
451
|
normalizeSchemaNode,
|
|
355
452
|
normalizeSchemaRegistry,
|
|
356
453
|
codecInputsForRegistry,
|
|
454
|
+
schemaHasOptionalProperties,
|
|
455
|
+
schemaHasScalarRecord,
|
|
456
|
+
schemaHasNestedJson,
|
|
457
|
+
schemaNeedsValueProjection,
|
|
357
458
|
defaultSchemaBoundaryPolicy,
|
|
358
459
|
defaultSchemaRegistryContract
|
|
359
460
|
});
|
|
@@ -7,13 +7,14 @@ const JSON_SEMANTIC_TRACE_VERSION = 'pulse.json-semantic-trace.v1';
|
|
|
7
7
|
const JSON_TRACE_EVENT_KINDS = Object.freeze([
|
|
8
8
|
'json.decode.request',
|
|
9
9
|
'json.decode.fetch',
|
|
10
|
+
'json.decode.text',
|
|
10
11
|
'json.encode.response',
|
|
11
12
|
'json.encode.fetch',
|
|
12
13
|
'json.encode.value',
|
|
13
14
|
'json.decode.error',
|
|
14
15
|
'json.encode.error'
|
|
15
16
|
]);
|
|
16
|
-
const JSON_BOUNDARIES = Object.freeze(['request', 'fetch-response', 'application-response', 'fetch-request', 'application-value']);
|
|
17
|
+
const JSON_BOUNDARIES = Object.freeze(['request', 'fetch-response', 'application-response', 'fetch-request', 'application-value', 'application-text']);
|
|
17
18
|
const JSON_BODY_OWNERSHIP = Object.freeze([
|
|
18
19
|
'request-snapshot',
|
|
19
20
|
'fetched-response-snapshot',
|
|
@@ -23,10 +24,11 @@ const JSON_BODY_OWNERSHIP = Object.freeze([
|
|
|
23
24
|
const JSON_EVENT_BOUNDARIES = Object.freeze({
|
|
24
25
|
'json.decode.request': Object.freeze(['request']),
|
|
25
26
|
'json.decode.fetch': Object.freeze(['fetch-response']),
|
|
27
|
+
'json.decode.text': Object.freeze(['application-text']),
|
|
26
28
|
'json.encode.response': Object.freeze(['application-response']),
|
|
27
29
|
'json.encode.fetch': Object.freeze(['fetch-request']),
|
|
28
30
|
'json.encode.value': Object.freeze(['application-value']),
|
|
29
|
-
'json.decode.error': Object.freeze(['request', 'fetch-response']),
|
|
31
|
+
'json.decode.error': Object.freeze(['request', 'fetch-response', 'application-text']),
|
|
30
32
|
'json.encode.error': Object.freeze(['application-response', 'fetch-request', 'application-value'])
|
|
31
33
|
});
|
|
32
34
|
|