@pulse-compute/wasm-contracts 1.0.0-beta.3 → 1.0.0-beta.5

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 CHANGED
@@ -7,9 +7,9 @@
7
7
  > **Supported entry points:** `@pulse-compute/wasm-contracts/provider/toolchain`<br>
8
8
  > **Stability:** The provider toolchain contract is versioned and supported for the Beta; other protocol and proof interfaces remain internal.<br>
9
9
  > **npm:** [`@pulse-compute/wasm-contracts`](https://www.npmjs.com/package/@pulse-compute/wasm-contracts)<br>
10
- > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.3/packages/implementation-packages/)
10
+ > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.5/packages/implementation-packages/)
11
11
  >
12
- > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.3` package policy.
12
+ > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.5` package policy.
13
13
  <!-- pulse-package-status:end -->
14
14
 
15
15
  This package owns stable data contracts shared by the compiler, runtime, and providers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-compute/wasm-contracts",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": "^22.14.0 || ^24.0.0"
@@ -126,7 +126,7 @@
126
126
  "url": "git+https://github.com/pulse-compute/pulse.git",
127
127
  "directory": "wasm/packages/contracts"
128
128
  },
129
- "homepage": "https://pulsecompute.io/v1.0.0-beta.3/packages/implementation-packages/",
129
+ "homepage": "https://pulsecompute.io/v1.0.0-beta.5/packages/implementation-packages/",
130
130
  "bugs": {
131
131
  "url": "https://github.com/pulse-compute/pulse/issues"
132
132
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const path = require('node:path');
4
4
 
5
- const PACKAGE_VERSION = 'pulsewasm-route-extractor@1.0.0-beta.3';
5
+ const PACKAGE_VERSION = 'pulsewasm-route-extractor@1.0.0-beta.5';
6
6
  const DIAGNOSTICS_VERSION = 'pulsewasm.diagnostics.v1';
7
7
 
8
8
  function stableFileName(fileName, cwd) {
@@ -3,12 +3,16 @@
3
3
  const CANONICAL_NATIVE_PLAN_VERSION = 'pulse.canonical-native-plan.v2';
4
4
  const CANONICAL_NATIVE_PLAN_HASH_ALGORITHM = 'sha256';
5
5
  const CANONICAL_NATIVE_PLAN_OWNERSHIP_VERSION = 'pulse.canonical-native-ownership.v1';
6
+ const CANONICAL_PURE_LOOP_LIMITS = Object.freeze({ maxIterations: 1024, maxNestedIterations: 65536 });
6
7
 
7
8
  const CANONICAL_NATIVE_STATEMENT_KINDS = Object.freeze([
8
9
  'local',
9
10
  'effect',
10
11
  'effect-group',
11
12
  'if',
13
+ 'pure-loop',
14
+ 'break',
15
+ 'continue',
12
16
  'return',
13
17
  'expression'
14
18
  ]);
@@ -46,6 +50,8 @@ const CANONICAL_NATIVE_INTRINSICS = Object.freeze([
46
50
  'request.text',
47
51
  'request.json',
48
52
  'response.json',
53
+ 'schema.encode.text',
54
+ 'schema.decode.text',
49
55
  'response.text',
50
56
  'response.custom',
51
57
  'kv.namespace',
@@ -118,7 +124,7 @@ const CANONICAL_NATIVE_PLAN_POLICY = Object.freeze({
118
124
  javascriptRuntime: false,
119
125
  promiseSemantics: false,
120
126
  asyncify: false,
121
- controlFlow: 'structured straight-line statements plus if/else branches',
127
+ controlFlow: 'structured statements, if/else, and literal-capped pure for loops; no effect iteration',
122
128
  suspension: 'explicit effect and effect-group statements with stable continuation IDs',
123
129
  values: 'versioned JSON expression tree with stable local identities',
124
130
  logging: 'compile-time threshold pruning plus synchronous provider-adapter emission',
@@ -129,6 +135,7 @@ const CANONICAL_NATIVE_PLAN_POLICY = Object.freeze({
129
135
 
130
136
  module.exports = Object.freeze({
131
137
  CANONICAL_NATIVE_PLAN_VERSION,
138
+ CANONICAL_PURE_LOOP_LIMITS,
132
139
  CANONICAL_NATIVE_PLAN_HASH_ALGORITHM,
133
140
  CANONICAL_NATIVE_PLAN_OWNERSHIP_VERSION,
134
141
  CANONICAL_NATIVE_STATEMENT_KINDS,
@@ -63,6 +63,7 @@ const CANONICAL_NATIVE_IMPORTS = Object.freeze([
63
63
  ['value_unary', ['i32', 'i32'], ['i32']],
64
64
  ['value_truthy', ['i32'], ['i32']],
65
65
  ['value_nullish', ['i32'], ['i32']],
66
+ ['value_string_trim', ['i32'], ['i32']],
66
67
  ['log', ['i32', 'i32'], []],
67
68
  ['request_method', [], ['i32']],
68
69
  ['request_url', [], ['i32']],
@@ -73,7 +74,11 @@ const CANONICAL_NATIVE_IMPORTS = Object.freeze([
73
74
  ['request_json', ['i32'], ['i32']],
74
75
  ['router_match', ['i32', 'i32'], ['i32']],
75
76
  ['router_param', ['i32', 'i32', 'i32'], ['i32']],
77
+ // 0: no failure; positive: application error handle; negative: terminal failure.
78
+ ['router_error_take', [], ['i32']],
76
79
  ['response_json', ['i32', 'i32'], ['i32']],
80
+ ['schema_encode', ['i32', 'i32'], ['i32']],
81
+ ['schema_decode', ['i32', 'i32'], ['i32']],
77
82
  ['response_text', ['i32', 'i32'], ['i32']],
78
83
  ['response_custom', ['i32'], ['i32']],
79
84
  ['grip_is_websocket', [], ['i32']],
@@ -6,8 +6,8 @@ const CTX_CONTRACT_VERSION = 'pulse.ctx-contract.v4';
6
6
  const CTX_CONTRACT_ID = 'pulse.ctx';
7
7
  const CTX_SURFACE_IDS = Object.freeze([
8
8
  'ctx.param','ctx.req.method','ctx.req.url','ctx.req.path','ctx.req.header','ctx.req.headers',
9
- 'ctx.state.get','ctx.state.set','ctx.json','ctx.text','ctx.response','ctx.req.text','ctx.req.json.schema',
10
- 'ctx.req.json.generic','ctx.fetch.projected','ctx.fetch.opaque-return','ctx.config.get','ctx.secret.get',
9
+ 'ctx.state.get','ctx.state.set','ctx.encodeJson','ctx.decodeJson','ctx.json','ctx.text','ctx.response','ctx.req.text','ctx.req.json.schema',
10
+ 'ctx.req.json.generic','ctx.fetch.projected','ctx.fetch.opaque-return','ctx.time.now','ctx.config.get','ctx.secret.get',
11
11
  'ctx.kv.get','ctx.kv.put','ctx.kv.getVersioned','ctx.kv.insertIfAbsent','ctx.kv.compareAndSwap','ctx.emit','package.operation'
12
12
  ]);
13
13
  const surfaceById = new Map(HANDLER_SURFACE_DEFINITIONS.map((entry) => [entry.id, entry]));
@@ -33,7 +33,7 @@ function defaultCtxContract() {
33
33
  handlerSurfaceVersion: HANDLER_SURFACE_CONTRACT_VERSION,
34
34
  surfaces: CTX_SURFACES,
35
35
  planes: Object.freeze({
36
- sharedAuthoring: Object.freeze(['state', 'log', 'fetch', 'parallel', 'config', 'secret', 'kv', 'emit']),
36
+ sharedAuthoring: Object.freeze(['state', 'log', 'time', 'fetch', 'parallel', 'config', 'secret', 'kv', 'emit', 'encodeJson', 'decodeJson']),
37
37
  httpAuthoring: Object.freeze(['req', 'json', 'text', 'response']),
38
38
  routeAuthoring: Object.freeze(['param']),
39
39
  eventAuthoring: Object.freeze(['event.type', 'event.payload']),
@@ -12,6 +12,7 @@ const ERROR_KINDS = Object.freeze({
12
12
  fetchTimeout: 'FetchTimeoutError',
13
13
  bodyTooLarge: 'BodyTooLargeError',
14
14
  bodyDecode: 'BodyDecodeError',
15
+ requestBodyInvalidUtf8: 'RequestBodyInvalidUtf8Error',
15
16
  bodyUnavailable: 'BodyUnavailableError',
16
17
  opaqueBodyInspection: 'OpaqueBodyInspectionError',
17
18
  responseEncode: 'ResponseEncodeError',
@@ -113,7 +114,7 @@ function readInputBody(input) {
113
114
  : Object.prototype.hasOwnProperty.call(source, 'text')
114
115
  ? source.text
115
116
  : source.jsonText;
116
- return { available: true, text: Buffer.isBuffer(value) ? value.toString('utf8') : String(value ?? '') };
117
+ return { available: true, value };
117
118
  }
118
119
 
119
120
  function createStructuredBodyValue(input = {}, options = {}) {
@@ -124,7 +125,8 @@ function createStructuredBodyValue(input = {}, options = {}) {
124
125
  const contentClass = classifyContentType(contentType);
125
126
  const forceStructured = options.forceStructured === true;
126
127
  const body = readInputBody(input);
127
- const bytes = utf8ByteLength(body.text);
128
+ const raw = body.value instanceof Uint8Array ? body.value : undefined;
129
+ const bytes = raw ? raw.byteLength : utf8ByteLength(body.value);
128
130
  const counters = { bodyCopies: 0, textTransforms: 0, jsonTransforms: 0, errors: 0 };
129
131
  let textMemo;
130
132
  let jsonMemo;
@@ -156,12 +158,20 @@ function createStructuredBodyValue(input = {}, options = {}) {
156
158
  textMemo = rejected;
157
159
  return textMemo;
158
160
  }
161
+ let value;
162
+ try {
163
+ value = raw ? new TextDecoder('utf-8', { fatal: options.strictUtf8 === true, ignoreBOM: true }).decode(raw) : String(body.value ?? '');
164
+ } catch (_) {
165
+ counters.errors += 1;
166
+ textMemo = runtimeValueError(ERROR_KINDS.requestBodyInvalidUtf8, 'Pulse request text must be well-formed UTF-8.');
167
+ return textMemo;
168
+ }
159
169
  counters.bodyCopies += 1;
160
170
  counters.textTransforms += 1;
161
171
  textMemo = deepFreeze({
162
172
  ok: true,
163
173
  kind: 'structured-text',
164
- value: String(body.text),
174
+ value,
165
175
  bytes,
166
176
  contentType: String(contentType || ''),
167
177
  contentClass,
@@ -212,6 +212,30 @@ const HANDLER_SURFACE_DEFINITIONS = Object.freeze([
212
212
  nativeBehavior: 'construct-canonical-result',
213
213
  status: 'existing-alias-normalization'
214
214
  }),
215
+ surface({
216
+ id: 'ctx.decodeJson',
217
+ class: 'sync',
218
+ canonicalOperation: 'schema.decode.text',
219
+ publicForms: ["ctx.decodeJson<T>(text, 'schema.id')"],
220
+ awaitPolicy: 'optional-redundant-warning',
221
+ validPositions: ['expression', 'return', 'condition'],
222
+ targetSupport: { javascript: true, native: true },
223
+ nativeBehavior: 'decode-bounded-schema-text',
224
+ schemaPolicy: 'literal-schema-required',
225
+ status: 'supported'
226
+ }),
227
+ surface({
228
+ id: 'ctx.encodeJson',
229
+ class: 'sync',
230
+ canonicalOperation: 'schema.encode.text',
231
+ publicForms: ["ctx.encodeJson(value, 'schema.id')"],
232
+ awaitPolicy: 'optional-redundant-warning',
233
+ validPositions: ['expression', 'statement', 'return'],
234
+ targetSupport: { javascript: true, native: true },
235
+ nativeBehavior: 'schema-validated-bounded-json-text',
236
+ schemaPolicy: 'literal-schema-required',
237
+ status: 'supported'
238
+ }),
215
239
  surface({
216
240
  id: 'ctx.text',
217
241
  class: 'sync',
@@ -303,6 +327,17 @@ const HANDLER_SURFACE_DEFINITIONS = Object.freeze([
303
327
  nativeBehavior: 'emit-terminal-fetch-effect',
304
328
  status: 'supported'
305
329
  }),
330
+ surface({
331
+ id: 'ctx.time.now',
332
+ class: 'effect',
333
+ canonicalOperation: 'effect.time.now',
334
+ publicForms: ['ctx.time.now()'],
335
+ awaitPolicy: 'required-when-consumed',
336
+ validPositions: ['await-expression', 'parallel-member'],
337
+ targetSupport: { javascript: true, native: true },
338
+ nativeBehavior: 'emit-provider-wall-clock-effect-and-continuation',
339
+ status: 'supported'
340
+ }),
306
341
  surface({
307
342
  id: 'ctx.config.get',
308
343
  class: 'effect',
@@ -20,7 +20,7 @@ function defaultHandlerTypesContract() {
20
20
  set: 'void'
21
21
  }),
22
22
  executionContexts: Object.freeze({
23
- shared: Object.freeze(['state', 'log', 'fetch', 'parallel', 'config', 'secret', 'kv', 'emit']),
23
+ shared: Object.freeze(['state', 'log', 'time', 'fetch', 'parallel', 'config', 'secret', 'kv', 'emit', 'encodeJson', 'decodeJson']),
24
24
  http: Object.freeze(['req', 'json', 'text', 'response']),
25
25
  route: Object.freeze(['param']),
26
26
  event: Object.freeze(['event.type', 'event.payload']),
@@ -9,6 +9,8 @@ const CANONICAL_PROVIDER_CAPABILITIES = Object.freeze([
9
9
  'response.text',
10
10
  'response.custom',
11
11
  'fetch',
12
+ 'time.wall-clock',
13
+ 'crypto.digestText',
12
14
  'config.get',
13
15
  'secret.get',
14
16
  'kv.get',
@@ -3,7 +3,7 @@
3
3
  const S3_CONTRACT_ID = 'pulse.s3';
4
4
  const S3_PACKAGE = '@pulse-compute/s3';
5
5
  const S3_LOWERING_PLAN_VERSION = 'pulse.s3-lowering-plan.v1';
6
- const S3_LIMITS = Object.freeze({ keyBytes: 1024, textBytes: 32768, metadataBytes: 1024, headerBytes: 16384, envelopeBytes: 262144 });
6
+ const S3_LIMITS = Object.freeze({ keyBytes: 1024, textBytes: 2097152, defaultTextBytes: 32768, metadataBytes: 1024, headerBytes: 16384, envelopeBytes: 12648448 });
7
7
  const S3_OPERATIONS = Object.freeze(Object.fromEntries(['head', 'getText', 'putText'].map((name) => [name, Object.freeze({
8
8
  kind: `s3.${name}`, capability: `s3.${name}`, result: name === 'head' ? 's3-head-result' : name === 'getText' ? 's3-get-text-result' : 's3-put-text-result'
9
9
  })])));
@@ -2,8 +2,8 @@
2
2
 
3
3
  const { sha256Hex, stableStringify } = require('../stable-id.js');
4
4
 
5
- const SCHEMA_REGISTRY_IR_VERSION = 'pulse.schema-registry-ir.v1';
6
- const SCHEMA_CODEC_INPUTS_VERSION = 'pulse.schema-codec-inputs.v1';
5
+ const SCHEMA_REGISTRY_IR_VERSION = 'pulse.schema-registry-ir.v2';
6
+ const SCHEMA_CODEC_INPUTS_VERSION = 'pulse.schema-codec-inputs.v2';
7
7
  const SCHEMA_AUTHORING_VERSION = 'pulse.schema-authoring.v1';
8
8
  const SCHEMA_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)+$/;
9
9
  const RESPONSE_CASE_ID_PATTERN = SCHEMA_ID_PATTERN;
@@ -106,15 +106,15 @@ function normalizeSchemaNode(input, field = 'schema.root', stack = []) {
106
106
  throw schemaContractError('PULSE_SCHEMA_IR_FIELD_DUPLICATE', `${field} declares field ${name} more than once.`, { field, name });
107
107
  }
108
108
  names.add(name);
109
- if (entry.required !== true) {
110
- throw schemaContractError('PULSE_SCHEMA_OPTIONAL_FIELD_RESERVED', `${field}.${name} must be required in schema IR v1.`, {
109
+ if (typeof entry.required !== 'boolean') {
110
+ throw schemaContractError('PULSE_SCHEMA_IR_FIELD_REQUIRED', `${field}.${name}.required must be a boolean.`, {
111
111
  field,
112
112
  name
113
113
  });
114
114
  }
115
115
  return Object.freeze({
116
116
  name,
117
- required: true,
117
+ required: entry.required,
118
118
  value: normalizeSchemaNode(entry.value, `${field}.fields[${index}].value`, [...stack, name]),
119
119
  source: entry.source ? normalizeSource(entry.source, `${field}.fields[${index}].source`) : undefined
120
120
  });
@@ -193,7 +193,9 @@ function normalizeSchemaRegistry(input) {
193
193
  responses,
194
194
  policies: deepFreeze({
195
195
  objectRoots: true,
196
- requiredFieldsOnly: true,
196
+ requiredFieldsOnly: false,
197
+ optionalFields: 'omit-absent-own-properties',
198
+ presentUndefined: 'reject',
197
199
  unknownInputFields: 'drop',
198
200
  outputFields: 'declared-only',
199
201
  outputFieldOrder: 'declaration',
@@ -248,7 +250,7 @@ function collectNativeClasses(schema, node, symbols, classes, pathParts = []) {
248
250
  fields: Object.freeze(node.fields.map((field) => Object.freeze({
249
251
  name: field.name,
250
252
  type: assemblyScriptType(field.value, symbols, [...pathParts, field.name]),
251
- required: true,
253
+ required: field.required,
252
254
  constraints: field.value.kind === 'string-enum'
253
255
  ? Object.freeze({ enum: field.value.values })
254
256
  : Object.freeze({})
@@ -256,18 +258,27 @@ function collectNativeClasses(schema, node, symbols, classes, pathParts = []) {
256
258
  }));
257
259
  }
258
260
 
261
+ function schemaHasOptionalProperties(node) {
262
+ if (node.kind === 'nullable') return schemaHasOptionalProperties(node.value);
263
+ if (node.kind === 'array') return schemaHasOptionalProperties(node.element);
264
+ return node.kind === 'object' && node.fields.some(field => !field.required || schemaHasOptionalProperties(field.value));
265
+ }
266
+
259
267
  function codecInputsForRegistry(registryInput) {
260
268
  const registry = normalizeSchemaRegistry(registryInput);
261
269
  const nativeSchemas = registry.schemas.map((schema) => {
262
270
  const schemaClasses = [];
263
271
  const symbols = new Map();
264
- collectNativeClasses(schema, schema.root, symbols, schemaClasses);
272
+ const presence = schemaHasOptionalProperties(schema.root);
273
+ if (!presence) collectNativeClasses(schema, schema.root, symbols, schemaClasses);
274
+ const rootClass = presence ? 'JSON.Value' : symbols.get('');
265
275
  return Object.freeze({
266
276
  id: schema.id,
267
- rootClass: symbols.get(''),
277
+ rootClass,
278
+ representation: presence ? 'schema-projected-json-value' : 'required-struct',
268
279
  classes: Object.freeze(schemaClasses),
269
- parse: `JSON.parse<${symbols.get('')}>`,
270
- stringify: `JSON.stringify<${symbols.get('')}>`
280
+ parse: `JSON.parse<${rootClass}>`,
281
+ stringify: `JSON.stringify<${rootClass}>`
271
282
  });
272
283
  });
273
284
  return deepFreeze({
@@ -333,7 +344,7 @@ function defaultSchemaRegistryContract() {
333
344
  stableStringIds: true,
334
345
  exactLiteralIdsAtBoundaries: true,
335
346
  oldSchemasJsonSupported: false,
336
- optionalPropertiesSupported: false,
347
+ optionalPropertiesSupported: true,
337
348
  recursiveSchemasSupported: false,
338
349
  publicJsonAsImportsSupported: false,
339
350
  automaticFallback: false
@@ -354,6 +365,7 @@ module.exports = Object.freeze({
354
365
  normalizeSchemaNode,
355
366
  normalizeSchemaRegistry,
356
367
  codecInputsForRegistry,
368
+ schemaHasOptionalProperties,
357
369
  defaultSchemaBoundaryPolicy,
358
370
  defaultSchemaRegistryContract
359
371
  });
@@ -7,12 +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',
13
+ 'json.encode.value',
12
14
  'json.decode.error',
13
15
  'json.encode.error'
14
16
  ]);
15
- const JSON_BOUNDARIES = Object.freeze(['request', 'fetch-response', 'application-response', 'fetch-request']);
17
+ const JSON_BOUNDARIES = Object.freeze(['request', 'fetch-response', 'application-response', 'fetch-request', 'application-value', 'application-text']);
16
18
  const JSON_BODY_OWNERSHIP = Object.freeze([
17
19
  'request-snapshot',
18
20
  'fetched-response-snapshot',
@@ -22,10 +24,12 @@ const JSON_BODY_OWNERSHIP = Object.freeze([
22
24
  const JSON_EVENT_BOUNDARIES = Object.freeze({
23
25
  'json.decode.request': Object.freeze(['request']),
24
26
  'json.decode.fetch': Object.freeze(['fetch-response']),
27
+ 'json.decode.text': Object.freeze(['application-text']),
25
28
  'json.encode.response': Object.freeze(['application-response']),
26
29
  'json.encode.fetch': Object.freeze(['fetch-request']),
27
- 'json.decode.error': Object.freeze(['request', 'fetch-response']),
28
- 'json.encode.error': Object.freeze(['application-response', 'fetch-request'])
30
+ 'json.encode.value': Object.freeze(['application-value']),
31
+ 'json.decode.error': Object.freeze(['request', 'fetch-response', 'application-text']),
32
+ 'json.encode.error': Object.freeze(['application-response', 'fetch-request', 'application-value'])
29
33
  });
30
34
 
31
35
  function traceError(code, message, details = {}) {