@pulse-compute/wasm-contracts 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 +35 -2
- package/package.json +124 -6
- package/src/assets/contracts.js +296 -0
- package/src/config/provider-resolution.js +54 -0
- package/src/config/runtime-provider-kv.js +73 -0
- package/src/config/symbolic-refs.js +192 -0
- package/src/crypto/contracts.js +647 -0
- package/src/definitions/config-schema.js +30 -0
- package/src/definitions/handler-roles.js +66 -0
- package/src/definitions/path-grammar.js +11 -0
- package/src/definitions/router-api.js +149 -0
- package/src/diagnostics/codes.js +42 -0
- package/src/diagnostics/reporter.js +20 -0
- package/src/diagnostics.js +139 -0
- package/src/dispatch-table.js +23 -0
- package/src/entities/catalog.d.ts +7 -0
- package/src/entities/catalog.js +87 -0
- package/src/entities/contracts.d.ts +95 -0
- package/src/entities/contracts.js +122 -0
- package/src/entities/json-rpc.d.ts +27 -0
- package/src/entities/json-rpc.js +69 -0
- package/src/entities/runtime.d.ts +67 -0
- package/src/entities/runtime.js +288 -0
- package/src/events/contracts.js +456 -0
- package/src/execution-plan.js +42 -0
- package/src/grip/contracts.js +106 -0
- package/src/handler/backend-json-request-body.js +58 -0
- package/src/handler/beta-lifecycle-golden.js +97 -0
- package/src/handler/canonical-native-plan.js +143 -0
- package/src/handler/canonical-native-runtime.js +168 -0
- package/src/handler/canonical-runtime.js +13 -0
- package/src/handler/context.js +131 -0
- package/src/handler/ctx-contract.js +71 -0
- package/src/handler/doctor-readiness-aggregator.js +67 -0
- package/src/handler/effects.js +446 -0
- package/src/handler/io-lifecycle.js +134 -0
- package/src/handler/lifecycle-authoring-alignment.js +56 -0
- package/src/handler/lifecycle-readiness-aggregate.js +93 -0
- package/src/handler/reference-node-lifecycle-golden.js +73 -0
- package/src/handler/request-json-body.js +147 -0
- package/src/handler/runtime-api.js +471 -0
- package/src/handler/schema-decode-result.js +135 -0
- package/src/handler/schema-response-codec.js +141 -0
- package/src/handler/surface-contract.js +601 -0
- package/src/handler/types-contract.js +51 -0
- package/src/handler-table.js +28 -0
- package/src/host/backend-capabilities.js +24 -0
- package/src/host/capabilities.js +273 -0
- package/src/host/channel-broadcaster.js +60 -0
- package/src/host/json-body.js +80 -0
- package/src/host/request-result-headers.js +38 -0
- package/src/host/streaming-passthrough.js +90 -0
- package/src/host/wasm-host-abi.js +154 -0
- package/src/index.js +75 -0
- package/src/jwt/contracts.js +427 -0
- package/src/library/api-surface.js +99 -0
- package/src/library/contracts.js +273 -0
- package/src/library/manifest.js +317 -0
- package/src/library/second-package-owned-lowering.js +39 -0
- package/src/library/sidecars.js +147 -0
- package/src/logging.js +89 -0
- package/src/package/package-contract.js +975 -0
- package/src/package/package-contract.schema.json +303 -0
- package/src/package/runtime-pulse-live.js +100 -0
- package/src/path-table.js +12 -0
- package/src/path.js +275 -0
- package/src/project/config-factory.js +48 -0
- package/src/project/config-plan.js +409 -0
- package/src/project/config-runtime.js +46 -0
- package/src/project/javascript-application.js +212 -0
- package/src/project/javascript-core-capabilities.js +44 -0
- package/src/project/native-optimization.js +9 -0
- package/src/project/package-ownership.js +242 -0
- package/src/project/package-product-projection.js +171 -0
- package/src/project/reachable-graph-identity.js +219 -0
- package/src/project/reachable-graph-projections.js +279 -0
- package/src/project/reachable-graph-resolver.js +303 -0
- package/src/project/reachable-graph-v2.js +430 -0
- package/src/project/reachable-graph-v2.schema.json +285 -0
- package/src/project/reachable-graph.js +199 -0
- package/src/project/reachable-graph.schema.json +222 -0
- package/src/project/target-support-evidence.js +559 -0
- package/src/provider/canonical-provider.js +239 -0
- package/src/provider/final-wasm-policy.js +87 -0
- package/src/provider/toolchain.js +771 -0
- package/src/route-plan.js +43 -0
- package/src/s3/contracts.js +12 -0
- package/src/schema-json/compile.js +63 -0
- package/src/schema-json/contracts.js +10 -0
- package/src/schema-json/generic-parser.js +62 -0
- package/src/schema-json/registry.js +359 -0
- package/src/schema-json/semantic-trace.js +241 -0
- package/src/schema-json/v1.js +65 -0
- package/src/schema-json/v2.js +110 -0
- package/src/sha256.js +104 -0
- package/src/spine.js +381 -0
- package/src/stable-id.js +142 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Executable runtime primitives used by the canonical API runtime. This module
|
|
4
|
+
// intentionally contains behavior only: no pass metadata, confidence summaries,
|
|
5
|
+
// fixture catalogs, or generated-artifact declarations.
|
|
6
|
+
|
|
7
|
+
const ERROR_KINDS = Object.freeze({
|
|
8
|
+
compile: 'CompileError',
|
|
9
|
+
lowering: 'LoweringError',
|
|
10
|
+
hostEffect: 'HostEffectError',
|
|
11
|
+
fetchNetwork: 'FetchNetworkError',
|
|
12
|
+
fetchTimeout: 'FetchTimeoutError',
|
|
13
|
+
bodyTooLarge: 'BodyTooLargeError',
|
|
14
|
+
bodyDecode: 'BodyDecodeError',
|
|
15
|
+
bodyUnavailable: 'BodyUnavailableError',
|
|
16
|
+
opaqueBodyInspection: 'OpaqueBodyInspectionError',
|
|
17
|
+
responseEncode: 'ResponseEncodeError',
|
|
18
|
+
continuationExpired: 'ContinuationExpiredError',
|
|
19
|
+
continuationDoubleResume: 'ContinuationDoubleResumeError',
|
|
20
|
+
providerCapabilityMissing: 'ProviderCapabilityMissingError'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const LIMITS = Object.freeze({
|
|
24
|
+
structuredBodyBytes: 65_536,
|
|
25
|
+
fetchTimeoutMs: 5_000
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const OPAQUE_BODY_KINDS = Object.freeze(['binary', 'stream']);
|
|
29
|
+
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && !Buffer.isBuffer(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function clonePlain(value) {
|
|
35
|
+
if (Array.isArray(value)) return value.map(clonePlain);
|
|
36
|
+
if (isRecord(value)) {
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const key of Object.keys(value)) {
|
|
39
|
+
Object.defineProperty(out, key, {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: clonePlain(value[key])
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function deepFreeze(value) {
|
|
52
|
+
if ((Array.isArray(value) || isRecord(value)) && !Object.isFrozen(value)) {
|
|
53
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
54
|
+
Object.freeze(value);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function immutableClone(value) {
|
|
60
|
+
return deepFreeze(clonePlain(value));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeHeaders(headers) {
|
|
64
|
+
if (!headers) return [];
|
|
65
|
+
if (Array.isArray(headers)) return headers.map(([name, value]) => [String(name), String(value)]);
|
|
66
|
+
if (typeof headers.entries === 'function') return Array.from(headers.entries()).map(([name, value]) => [String(name), String(value)]);
|
|
67
|
+
if (isRecord(headers)) return Object.entries(headers).map(([name, value]) => [String(name), String(value)]);
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function headerValue(headers, name) {
|
|
72
|
+
const lower = String(name || '').toLowerCase();
|
|
73
|
+
const match = normalizeHeaders(headers).find(([key]) => String(key).toLowerCase() === lower);
|
|
74
|
+
return match ? match[1] : '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeContentType(value) {
|
|
78
|
+
return String(value || '').split(';')[0].trim().toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function classifyContentType(value) {
|
|
82
|
+
const normalized = normalizeContentType(value);
|
|
83
|
+
if (!normalized) return 'unknown';
|
|
84
|
+
if (normalized === 'application/json' || normalized.endsWith('+json')) return 'json';
|
|
85
|
+
if (normalized.startsWith('text/')) return 'text';
|
|
86
|
+
if (['application/javascript', 'application/xml', 'application/xhtml+xml', 'application/x-www-form-urlencoded'].includes(normalized)) return 'text';
|
|
87
|
+
return 'opaque';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function utf8ByteLength(value) {
|
|
91
|
+
return Buffer.byteLength(String(value ?? ''), 'utf8');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runtimeValueError(kind, message, detail = {}) {
|
|
95
|
+
return deepFreeze({
|
|
96
|
+
ok: false,
|
|
97
|
+
kind: 'runtime-value-error',
|
|
98
|
+
error: kind,
|
|
99
|
+
message: String(message),
|
|
100
|
+
detail: immutableClone(detail)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readInputBody(input) {
|
|
105
|
+
const source = input || {};
|
|
106
|
+
if (!Object.prototype.hasOwnProperty.call(source, 'body')
|
|
107
|
+
&& !Object.prototype.hasOwnProperty.call(source, 'text')
|
|
108
|
+
&& !Object.prototype.hasOwnProperty.call(source, 'jsonText')) {
|
|
109
|
+
return { available: false, text: '' };
|
|
110
|
+
}
|
|
111
|
+
const value = Object.prototype.hasOwnProperty.call(source, 'body')
|
|
112
|
+
? source.body
|
|
113
|
+
: Object.prototype.hasOwnProperty.call(source, 'text')
|
|
114
|
+
? source.text
|
|
115
|
+
: source.jsonText;
|
|
116
|
+
return { available: true, text: Buffer.isBuffer(value) ? value.toString('utf8') : String(value ?? '') };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createStructuredBodyValue(input = {}, options = {}) {
|
|
120
|
+
const configuredMax = Number(options.maxBytes);
|
|
121
|
+
const maxBytes = Number.isFinite(configuredMax) && configuredMax > 0 ? configuredMax : LIMITS.structuredBodyBytes;
|
|
122
|
+
const headers = normalizeHeaders(input.headers);
|
|
123
|
+
const contentType = input.contentType || headerValue(headers, 'content-type');
|
|
124
|
+
const contentClass = classifyContentType(contentType);
|
|
125
|
+
const forceStructured = options.forceStructured === true;
|
|
126
|
+
const body = readInputBody(input);
|
|
127
|
+
const bytes = utf8ByteLength(body.text);
|
|
128
|
+
const counters = { bodyCopies: 0, textTransforms: 0, jsonTransforms: 0, errors: 0 };
|
|
129
|
+
let textMemo;
|
|
130
|
+
let jsonMemo;
|
|
131
|
+
|
|
132
|
+
function unavailableError() {
|
|
133
|
+
counters.errors += 1;
|
|
134
|
+
return runtimeValueError(ERROR_KINDS.bodyUnavailable, 'Structured body snapshot is unavailable from the provider boundary.', { contentType, contentClass });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function tooLargeError() {
|
|
138
|
+
counters.errors += 1;
|
|
139
|
+
return runtimeValueError(ERROR_KINDS.bodyTooLarge, `Structured body exceeds the ${maxBytes} byte limit.`, { bytes, maxBytes });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function inspectableError() {
|
|
143
|
+
if (!forceStructured && contentClass === 'opaque') {
|
|
144
|
+
counters.errors += 1;
|
|
145
|
+
return runtimeValueError(ERROR_KINDS.opaqueBodyInspection, 'Opaque binary and stream bodies are pass-through only.', { contentType, contentClass });
|
|
146
|
+
}
|
|
147
|
+
if (!body.available) return unavailableError();
|
|
148
|
+
if (bytes > maxBytes) return tooLargeError();
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function text() {
|
|
153
|
+
if (textMemo) return textMemo;
|
|
154
|
+
const rejected = inspectableError();
|
|
155
|
+
if (rejected) {
|
|
156
|
+
textMemo = rejected;
|
|
157
|
+
return textMemo;
|
|
158
|
+
}
|
|
159
|
+
counters.bodyCopies += 1;
|
|
160
|
+
counters.textTransforms += 1;
|
|
161
|
+
textMemo = deepFreeze({
|
|
162
|
+
ok: true,
|
|
163
|
+
kind: 'structured-text',
|
|
164
|
+
value: String(body.text),
|
|
165
|
+
bytes,
|
|
166
|
+
contentType: String(contentType || ''),
|
|
167
|
+
contentClass,
|
|
168
|
+
immutable: true,
|
|
169
|
+
memoized: true
|
|
170
|
+
});
|
|
171
|
+
return textMemo;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function json() {
|
|
175
|
+
if (jsonMemo) return jsonMemo;
|
|
176
|
+
const textResult = text();
|
|
177
|
+
if (!textResult.ok) {
|
|
178
|
+
jsonMemo = textResult;
|
|
179
|
+
return jsonMemo;
|
|
180
|
+
}
|
|
181
|
+
counters.jsonTransforms += 1;
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(textResult.value);
|
|
184
|
+
jsonMemo = deepFreeze({
|
|
185
|
+
ok: true,
|
|
186
|
+
kind: 'structured-json',
|
|
187
|
+
value: immutableClone(parsed),
|
|
188
|
+
jsonText: textResult.value,
|
|
189
|
+
bytes,
|
|
190
|
+
contentType: textResult.contentType,
|
|
191
|
+
contentClass: contentClass === 'unknown' ? 'json' : contentClass,
|
|
192
|
+
immutable: true,
|
|
193
|
+
memoized: true
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
counters.errors += 1;
|
|
197
|
+
jsonMemo = runtimeValueError(ERROR_KINDS.bodyDecode, 'Structured JSON body could not be decoded.', {
|
|
198
|
+
contentType,
|
|
199
|
+
contentClass,
|
|
200
|
+
message: error && error.message ? error.message : String(error)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return jsonMemo;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return Object.freeze({
|
|
207
|
+
kind: 'structured-body-value',
|
|
208
|
+
bodyClass: !forceStructured && contentClass === 'opaque' ? 'opaque' : 'structured',
|
|
209
|
+
contentType: String(contentType || ''),
|
|
210
|
+
contentClass,
|
|
211
|
+
maxBytes,
|
|
212
|
+
bytes,
|
|
213
|
+
text,
|
|
214
|
+
json,
|
|
215
|
+
stats() {
|
|
216
|
+
return deepFreeze({
|
|
217
|
+
...counters,
|
|
218
|
+
bodyAvailable: body.available,
|
|
219
|
+
textMemoized: Boolean(textMemo),
|
|
220
|
+
jsonMemoized: Boolean(jsonMemo)
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function defaultHeader(headers, name, value) {
|
|
227
|
+
const normalized = normalizeHeaders(headers);
|
|
228
|
+
if (!headerValue(normalized, name)) normalized.push([String(name), String(value)]);
|
|
229
|
+
return normalized;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function createStructuredResponseBuilders(defaults = {}) {
|
|
233
|
+
const configuredStatus = Number(defaults.status);
|
|
234
|
+
const defaultStatus = Number.isFinite(configuredStatus) && configuredStatus > 0 ? configuredStatus : 200;
|
|
235
|
+
function normalizeOptions(options = {}) {
|
|
236
|
+
const configured = Number(options.status);
|
|
237
|
+
return {
|
|
238
|
+
status: Number.isFinite(configured) && configured > 0 ? configured : defaultStatus,
|
|
239
|
+
headers: normalizeHeaders(options.headers)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return Object.freeze({
|
|
244
|
+
text(body, options = {}) {
|
|
245
|
+
const normalized = normalizeOptions(options);
|
|
246
|
+
const text = String(body ?? '');
|
|
247
|
+
return deepFreeze({
|
|
248
|
+
status: normalized.status,
|
|
249
|
+
kind: 'text',
|
|
250
|
+
bodyClass: 'structured',
|
|
251
|
+
body: text,
|
|
252
|
+
bytes: utf8ByteLength(text),
|
|
253
|
+
headers: defaultHeader(normalized.headers, 'content-type', 'text/plain; charset=utf-8'),
|
|
254
|
+
builder: 'ctx.text'
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
json(value, options = {}) {
|
|
258
|
+
const normalized = normalizeOptions(options);
|
|
259
|
+
let body;
|
|
260
|
+
try {
|
|
261
|
+
body = JSON.stringify(value ?? null);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return runtimeValueError(ERROR_KINDS.responseEncode, 'Structured JSON response could not be encoded.', {
|
|
264
|
+
message: error && error.message ? error.message : String(error)
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return deepFreeze({
|
|
268
|
+
status: normalized.status,
|
|
269
|
+
kind: 'json',
|
|
270
|
+
bodyClass: 'structured',
|
|
271
|
+
body,
|
|
272
|
+
value: immutableClone(value ?? null),
|
|
273
|
+
bytes: utf8ByteLength(body),
|
|
274
|
+
headers: defaultHeader(normalized.headers, 'content-type', 'application/json; charset=utf-8'),
|
|
275
|
+
builder: 'ctx.json'
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
jsonText(body, options = {}, value) {
|
|
279
|
+
const normalized = normalizeOptions(options);
|
|
280
|
+
const text = String(body);
|
|
281
|
+
try { JSON.parse(text); }
|
|
282
|
+
catch (error) {
|
|
283
|
+
return runtimeValueError(ERROR_KINDS.responseEncode, 'Structured JSON response codec returned malformed JSON.', {
|
|
284
|
+
message: error && error.message ? error.message : String(error)
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return deepFreeze({
|
|
288
|
+
status: normalized.status,
|
|
289
|
+
kind: 'json',
|
|
290
|
+
bodyClass: 'structured',
|
|
291
|
+
body: text,
|
|
292
|
+
value: immutableClone(value),
|
|
293
|
+
bytes: utf8ByteLength(text),
|
|
294
|
+
headers: defaultHeader(normalized.headers, 'content-type', 'application/json; charset=utf-8'),
|
|
295
|
+
builder: 'ctx.json'
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
response(input = {}) {
|
|
299
|
+
const body = Object.prototype.hasOwnProperty.call(input, 'body') ? input.body : '';
|
|
300
|
+
const configuredStatus = Number(input.status);
|
|
301
|
+
const status = Number.isFinite(configuredStatus) && configuredStatus > 0 ? configuredStatus : defaultStatus;
|
|
302
|
+
const headers = normalizeHeaders(input.headers);
|
|
303
|
+
const text = String(body ?? '');
|
|
304
|
+
const contentClass = classifyContentType(headerValue(headers, 'content-type'));
|
|
305
|
+
if (contentClass === 'opaque') {
|
|
306
|
+
return runtimeValueError(ERROR_KINDS.opaqueBodyInspection, 'ctx.response accepts structured bodies only; return an opaque fetch response directly.', { status });
|
|
307
|
+
}
|
|
308
|
+
return deepFreeze({
|
|
309
|
+
status,
|
|
310
|
+
kind: text.length === 0 ? 'empty' : (contentClass === 'json' ? 'json' : 'text'),
|
|
311
|
+
bodyClass: 'structured',
|
|
312
|
+
body: text,
|
|
313
|
+
bytes: utf8ByteLength(text),
|
|
314
|
+
headers,
|
|
315
|
+
builder: 'ctx.response'
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function normalizeBodyKind(value) {
|
|
322
|
+
const normalized = String(value || 'stream').trim().toLowerCase();
|
|
323
|
+
return OPAQUE_BODY_KINDS.includes(normalized) ? normalized : 'stream';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function normalizeHandleId(value) {
|
|
327
|
+
return String(value || 'opaque-body').trim().replace(/[^a-zA-Z0-9:_-]+/g, '-') || 'opaque-body';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function createOpaqueBodyHandle(input = {}) {
|
|
331
|
+
const headers = normalizeHeaders(input.headers || (input.contentType ? [['content-type', input.contentType]] : []));
|
|
332
|
+
const contentType = input.contentType || headerValue(headers, 'content-type') || 'application/octet-stream';
|
|
333
|
+
const bodyKind = normalizeBodyKind(input.bodyKind || input.kind);
|
|
334
|
+
const handleId = normalizeHandleId(input.id || input.handleId || `${bodyKind}-body`);
|
|
335
|
+
return deepFreeze({
|
|
336
|
+
ok: true,
|
|
337
|
+
kind: 'opaque-body-handle',
|
|
338
|
+
bodyClass: 'opaque',
|
|
339
|
+
bodyKind,
|
|
340
|
+
handleId,
|
|
341
|
+
source: String(input.source || 'host-response'),
|
|
342
|
+
ownership: 'host-owned',
|
|
343
|
+
model: 'capability-handle',
|
|
344
|
+
contentType: String(contentType),
|
|
345
|
+
headers,
|
|
346
|
+
status: Object.prototype.hasOwnProperty.call(input, 'status') ? Number(input.status) : undefined,
|
|
347
|
+
byteLength: Number.isFinite(Number(input.byteLength)) ? Number(input.byteLength) : undefined,
|
|
348
|
+
responseRef: Number.isFinite(Number(input.responseRef)) ? Number(input.responseRef) : undefined,
|
|
349
|
+
streamRef: Number.isFinite(Number(input.streamRef)) ? Number(input.streamRef) : undefined,
|
|
350
|
+
inspectable: false,
|
|
351
|
+
mutable: false,
|
|
352
|
+
iterable: false,
|
|
353
|
+
transformable: false,
|
|
354
|
+
passThroughOnly: true,
|
|
355
|
+
userlandBytesVisible: false,
|
|
356
|
+
bodyCopiedIntoWasm: false,
|
|
357
|
+
traceBodyContents: false,
|
|
358
|
+
providerSpecificUserland: false,
|
|
359
|
+
lifecycle: Object.freeze({
|
|
360
|
+
states: Object.freeze(['created', 'attached']),
|
|
361
|
+
finalState: 'attached',
|
|
362
|
+
detached: false,
|
|
363
|
+
userVisibleContinuationToken: false
|
|
364
|
+
})
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function opaqueError(message, detail = {}) {
|
|
369
|
+
return runtimeValueError(ERROR_KINDS.opaqueBodyInspection, message, detail);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function denyOpaqueInspection(handle, helper = 'json') {
|
|
373
|
+
const opaque = handle && handle.bodyClass === 'opaque' ? handle : createOpaqueBodyHandle({ id: 'opaque-inspection-denied' });
|
|
374
|
+
return opaqueError(`Opaque ${opaque.bodyKind} bodies cannot be inspected with ${String(helper)}().`, {
|
|
375
|
+
helper: String(helper),
|
|
376
|
+
handleId: opaque.handleId,
|
|
377
|
+
bodyKind: opaque.bodyKind,
|
|
378
|
+
bodyClass: opaque.bodyClass,
|
|
379
|
+
passThroughOnly: true,
|
|
380
|
+
userlandBytesVisible: false,
|
|
381
|
+
bodyCopiedIntoWasm: false,
|
|
382
|
+
traceBodyContents: false
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function denyOpaqueMutation(handle, operation = 'mutate') {
|
|
387
|
+
const opaque = handle && handle.bodyClass === 'opaque' ? handle : createOpaqueBodyHandle({ id: 'opaque-mutation-denied' });
|
|
388
|
+
return opaqueError(`Opaque ${opaque.bodyKind} bodies are host-owned and cannot be ${String(operation)}d in userland.`, {
|
|
389
|
+
operation: String(operation),
|
|
390
|
+
handleId: opaque.handleId,
|
|
391
|
+
bodyKind: opaque.bodyKind,
|
|
392
|
+
mutable: false,
|
|
393
|
+
passThroughOnly: true,
|
|
394
|
+
bodyCopiedIntoWasm: false
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function createOpaquePassThroughResponse(input = {}) {
|
|
399
|
+
const bodyHandle = input.bodyHandle && input.bodyHandle.bodyClass === 'opaque'
|
|
400
|
+
? input.bodyHandle
|
|
401
|
+
: createOpaqueBodyHandle({
|
|
402
|
+
id: input.handleId,
|
|
403
|
+
bodyKind: input.bodyKind,
|
|
404
|
+
contentType: input.contentType,
|
|
405
|
+
headers: input.headers,
|
|
406
|
+
status: input.status,
|
|
407
|
+
source: input.source,
|
|
408
|
+
responseRef: input.responseRef,
|
|
409
|
+
streamRef: input.streamRef
|
|
410
|
+
});
|
|
411
|
+
const status = Number(Object.prototype.hasOwnProperty.call(input, 'status') ? input.status : (bodyHandle.status || 200));
|
|
412
|
+
const headers = normalizeHeaders(input.headers || bodyHandle.headers || []);
|
|
413
|
+
return deepFreeze({
|
|
414
|
+
ok: true,
|
|
415
|
+
kind: 'opaque-pass-through-response',
|
|
416
|
+
status,
|
|
417
|
+
bodyClass: 'opaque',
|
|
418
|
+
bodyKind: bodyHandle.bodyKind,
|
|
419
|
+
bodyHandle,
|
|
420
|
+
headers,
|
|
421
|
+
contentType: headerValue(headers, 'content-type') || bodyHandle.contentType,
|
|
422
|
+
preserveStatus: true,
|
|
423
|
+
preserveHeaders: true,
|
|
424
|
+
preserveRepeatedHeaders: true,
|
|
425
|
+
preserveBodyHandle: true,
|
|
426
|
+
directFetchResponseReturn: input.directFetchResponseReturn === true,
|
|
427
|
+
requestBodyForward: input.requestBodyForward === true,
|
|
428
|
+
inspectable: false,
|
|
429
|
+
mutable: false,
|
|
430
|
+
passThroughOnly: true,
|
|
431
|
+
userlandBytesVisible: false,
|
|
432
|
+
bodyCopiedIntoWasm: false,
|
|
433
|
+
traceBodyContents: false,
|
|
434
|
+
providerSpecificUserland: false
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function detachOpaqueBodyHandle(handle, reason = 'terminal-response-handoff') {
|
|
439
|
+
const opaque = handle && handle.bodyClass === 'opaque' ? handle : createOpaqueBodyHandle({ id: 'opaque-detached' });
|
|
440
|
+
return deepFreeze({
|
|
441
|
+
...opaque,
|
|
442
|
+
lifecycle: Object.freeze({
|
|
443
|
+
states: Object.freeze(['created', 'attached', 'detached']),
|
|
444
|
+
finalState: 'detached',
|
|
445
|
+
detached: true,
|
|
446
|
+
reason: String(reason),
|
|
447
|
+
userVisibleContinuationToken: false
|
|
448
|
+
})
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
module.exports = Object.freeze({
|
|
453
|
+
errors: ERROR_KINDS,
|
|
454
|
+
limits: LIMITS,
|
|
455
|
+
body: Object.freeze({
|
|
456
|
+
deepFreeze,
|
|
457
|
+
immutableClone,
|
|
458
|
+
normalizeHeaders,
|
|
459
|
+
headerValue,
|
|
460
|
+
classifyContentType,
|
|
461
|
+
createValue: createStructuredBodyValue,
|
|
462
|
+
createResponseBuilders: createStructuredResponseBuilders
|
|
463
|
+
}),
|
|
464
|
+
opaque: Object.freeze({
|
|
465
|
+
createBodyHandle: createOpaqueBodyHandle,
|
|
466
|
+
createResponse: createOpaquePassThroughResponse,
|
|
467
|
+
denyInspection: denyOpaqueInspection,
|
|
468
|
+
denyMutation: denyOpaqueMutation,
|
|
469
|
+
detach: detachOpaqueBodyHandle
|
|
470
|
+
})
|
|
471
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SCHEMA_DECODE_RESULT_PLAN_VERSION = 'pulsewasm.schema-decode-result-plan.v1';
|
|
4
|
+
const SCHEMA_DECODE_RESULT_PLAN_PHASE = '40';
|
|
5
|
+
const SCHEMA_DECODE_RESULT_PLAN_ARTIFACT = 'schema-decode-result-plan.json';
|
|
6
|
+
const SCHEMA_DECODE_RESULT_CONTRACT_ID = 'pulse.schema-decode-result';
|
|
7
|
+
const NODE_SCHEMA_DECODE_RESULT_PROOF_VERSION = 'pulsewasm.node-schema-decode-result-proof.v1';
|
|
8
|
+
const NODE_SCHEMA_DECODE_RESULT_PROOF_PHASE = '40';
|
|
9
|
+
const NODE_SCHEMA_DECODE_RESULT_PROOF_ARTIFACT = 'node-schema-decode-result-proof.json';
|
|
10
|
+
const NODE_SCHEMA_DECODE_RESULT_RUNTIME_VERSION = 'pulsewasm.node-schema-decode-result-runtime.v1';
|
|
11
|
+
|
|
12
|
+
const SCHEMA_DECODE_RESULT_SCOPE = Object.freeze({
|
|
13
|
+
requestJsonSchemaSugar: true,
|
|
14
|
+
canonicalParseSurface: true,
|
|
15
|
+
opaqueDecodeRefs: true,
|
|
16
|
+
fieldAccessorValidation: true,
|
|
17
|
+
compiledWasmRuntimeImplemented: true,
|
|
18
|
+
nodeCompiledProofImplemented: true,
|
|
19
|
+
lazyRequestBodyRead: true,
|
|
20
|
+
perRequestDecodeCache: true,
|
|
21
|
+
automaticSchemaGeneration: false,
|
|
22
|
+
typedSchemaSpecificAccessors: false,
|
|
23
|
+
arbitraryJsObjectInference: false,
|
|
24
|
+
binaryRequestBodyParsing: false,
|
|
25
|
+
streamRequestBodyParsing: false,
|
|
26
|
+
asyncAwait: false,
|
|
27
|
+
promises: false,
|
|
28
|
+
asyncify: false
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const SCHEMA_DECODE_RESULT_SURFACE = Object.freeze({
|
|
32
|
+
canonical: 'ctx.req.parse("Schema")',
|
|
33
|
+
ergonomicSugar: 'ctx.req.json("Schema")',
|
|
34
|
+
aliases: Object.freeze(['ctx.request.json("Schema")']),
|
|
35
|
+
result: 'opaque schema decode result ref',
|
|
36
|
+
success: 'result.ok()',
|
|
37
|
+
error: 'result.errorText()',
|
|
38
|
+
fields: Object.freeze(['result.getString(path)', 'result.getI32(path)', 'result.getU32(path)', 'result.getF64(path)', 'result.getBool(path)', 'result.has(path)', 'result.jsonText()'])
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const SCHEMA_DECODE_RESULT_ACCESSORS = Object.freeze({
|
|
42
|
+
ok: { path: false, returns: 'bool', allowedFieldTypes: Object.freeze(['any']) },
|
|
43
|
+
errorText: { path: false, returns: 'string', allowedFieldTypes: Object.freeze(['any']) },
|
|
44
|
+
jsonText: { path: false, returns: 'string', allowedFieldTypes: Object.freeze(['any']) },
|
|
45
|
+
has: { path: true, returns: 'bool', allowedFieldTypes: Object.freeze(['string', 'i32', 'u32', 'f64', 'bool', 'boolean', 'number']) },
|
|
46
|
+
getString: { path: true, returns: 'string', allowedFieldTypes: Object.freeze(['string']) },
|
|
47
|
+
getI32: { path: true, returns: 'i32', allowedFieldTypes: Object.freeze(['i32', 'int', 'integer']) },
|
|
48
|
+
getU32: { path: true, returns: 'u32', allowedFieldTypes: Object.freeze(['u32', 'uint']) },
|
|
49
|
+
getF64: { path: true, returns: 'f64', allowedFieldTypes: Object.freeze(['f64', 'float', 'number']) },
|
|
50
|
+
getBool: { path: true, returns: 'bool', allowedFieldTypes: Object.freeze(['bool', 'boolean']) }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const SCHEMA_DECODE_RESULT_RESERVED_SURFACE = Object.freeze([
|
|
54
|
+
'typed schema-specific accessors such as input.email()',
|
|
55
|
+
'automatic schema generation from handler source',
|
|
56
|
+
'arbitrary JavaScript object materialization',
|
|
57
|
+
'schema-backed mutation/build APIs',
|
|
58
|
+
'binary request-body schema decode',
|
|
59
|
+
'stream request-body schema decode',
|
|
60
|
+
'throwing decode failures'
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const SCHEMA_DECODE_RESULT_DIAGNOSTICS = Object.freeze({
|
|
64
|
+
planMissing: 'PULSEWASM_SCHEMA_DECODE_RESULT_PLAN_MISSING',
|
|
65
|
+
requestJsonPlanMissing: 'PULSEWASM_SCHEMA_DECODE_REQUEST_JSON_PLAN_MISSING',
|
|
66
|
+
noSchemaRoutes: 'PULSEWASM_SCHEMA_DECODE_NO_SCHEMA_ROUTES',
|
|
67
|
+
schemaMissing: 'PULSEWASM_SCHEMA_DECODE_SCHEMA_MISSING',
|
|
68
|
+
fieldMissing: 'PULSEWASM_SCHEMA_DECODE_FIELD_MISSING',
|
|
69
|
+
accessorUnsupported: 'PULSEWASM_SCHEMA_DECODE_ACCESSOR_UNSUPPORTED',
|
|
70
|
+
accessorTypeMismatch: 'PULSEWASM_SCHEMA_DECODE_ACCESSOR_TYPE_MISMATCH',
|
|
71
|
+
pathMustBeLiteral: 'PULSEWASM_SCHEMA_DECODE_ACCESSOR_PATH_MUST_BE_LITERAL',
|
|
72
|
+
compiledSmokeFailed: 'PULSEWASM_SCHEMA_DECODE_COMPILED_SMOKE_FAILED',
|
|
73
|
+
wasmMissing: 'PULSEWASM_SCHEMA_DECODE_COMPILED_WASM_MISSING'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const SCHEMA_DECODE_RESULT_POLICY = Object.freeze({
|
|
77
|
+
phase: SCHEMA_DECODE_RESULT_PLAN_PHASE,
|
|
78
|
+
sourceOfTruth: 'runtime.payload.json.schemas + request-json-body-plan.json',
|
|
79
|
+
decodeFailure: 'inspectable result; no throw',
|
|
80
|
+
missingFields: 'result.ok() false for schema-backed decode',
|
|
81
|
+
typeMismatches: 'planned diagnostics plus compiled proof for basic scalar checks',
|
|
82
|
+
routeDemandOnly: true,
|
|
83
|
+
perRequestCache: true,
|
|
84
|
+
implicitGenericParserBundle: false
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function defaultSchemaDecodeResultPolicy() {
|
|
88
|
+
return {
|
|
89
|
+
contractId: SCHEMA_DECODE_RESULT_CONTRACT_ID,
|
|
90
|
+
artifact: SCHEMA_DECODE_RESULT_PLAN_ARTIFACT,
|
|
91
|
+
phase: SCHEMA_DECODE_RESULT_PLAN_PHASE,
|
|
92
|
+
scope: { ...SCHEMA_DECODE_RESULT_SCOPE },
|
|
93
|
+
surface: JSON.parse(JSON.stringify(SCHEMA_DECODE_RESULT_SURFACE)),
|
|
94
|
+
accessors: JSON.parse(JSON.stringify(SCHEMA_DECODE_RESULT_ACCESSORS)),
|
|
95
|
+
reserved: [...SCHEMA_DECODE_RESULT_RESERVED_SURFACE],
|
|
96
|
+
policy: { ...SCHEMA_DECODE_RESULT_POLICY },
|
|
97
|
+
diagnostics: { ...SCHEMA_DECODE_RESULT_DIAGNOSTICS }
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function defaultNodeSchemaDecodeResultProofPolicy() {
|
|
102
|
+
return {
|
|
103
|
+
...defaultSchemaDecodeResultPolicy(),
|
|
104
|
+
provider: 'node',
|
|
105
|
+
proofArtifact: NODE_SCHEMA_DECODE_RESULT_PROOF_ARTIFACT,
|
|
106
|
+
proofVersion: NODE_SCHEMA_DECODE_RESULT_PROOF_VERSION,
|
|
107
|
+
runtimeVersion: NODE_SCHEMA_DECODE_RESULT_RUNTIME_VERSION,
|
|
108
|
+
compiledWasmRuntimeImplemented: true,
|
|
109
|
+
nodeCompiledProofImplemented: true,
|
|
110
|
+
providerNetworkFetchRequired: false,
|
|
111
|
+
userAuthoredAsync: false,
|
|
112
|
+
promises: false,
|
|
113
|
+
asyncAwait: false,
|
|
114
|
+
asyncify: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
SCHEMA_DECODE_RESULT_PLAN_VERSION,
|
|
120
|
+
SCHEMA_DECODE_RESULT_PLAN_PHASE,
|
|
121
|
+
SCHEMA_DECODE_RESULT_PLAN_ARTIFACT,
|
|
122
|
+
SCHEMA_DECODE_RESULT_CONTRACT_ID,
|
|
123
|
+
NODE_SCHEMA_DECODE_RESULT_PROOF_VERSION,
|
|
124
|
+
NODE_SCHEMA_DECODE_RESULT_PROOF_PHASE,
|
|
125
|
+
NODE_SCHEMA_DECODE_RESULT_PROOF_ARTIFACT,
|
|
126
|
+
NODE_SCHEMA_DECODE_RESULT_RUNTIME_VERSION,
|
|
127
|
+
SCHEMA_DECODE_RESULT_SCOPE,
|
|
128
|
+
SCHEMA_DECODE_RESULT_SURFACE,
|
|
129
|
+
SCHEMA_DECODE_RESULT_ACCESSORS,
|
|
130
|
+
SCHEMA_DECODE_RESULT_RESERVED_SURFACE,
|
|
131
|
+
SCHEMA_DECODE_RESULT_DIAGNOSTICS,
|
|
132
|
+
SCHEMA_DECODE_RESULT_POLICY,
|
|
133
|
+
defaultSchemaDecodeResultPolicy,
|
|
134
|
+
defaultNodeSchemaDecodeResultProofPolicy
|
|
135
|
+
};
|