@pulse-compute/provider-node 0.0.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -1
- package/package.json +61 -6
- package/src/capabilities.js +56 -0
- package/src/compiler/assets-provider-proof.js +434 -0
- package/src/compiler/backend-json-request-body-proof.js +90 -0
- package/src/compiler/index.js +38 -0
- package/src/compiler/node-adapter.js +452 -0
- package/src/compiler/node-compiled-wasm-assets-lifecycle-proof.js +435 -0
- package/src/compiler/request-json-body-proof.js +181 -0
- package/src/compiler/route-handler-compiled-wasm-bridge.js +323 -0
- package/src/compiler/route-handler-effect-proof.js +215 -0
- package/src/compiler/route-handler-live-origin-proof.js +372 -0
- package/src/compiler/schema-decode-result-proof.js +180 -0
- package/src/compiler/schema-response-codec-proof.js +149 -0
- package/src/events/reference-adapter.js +145 -0
- package/src/index.js +57 -0
- package/src/javascript/assets-adapter.js +68 -0
- package/src/javascript/bindings-adapter.js +120 -0
- package/src/javascript/fetch-adapter.js +187 -0
- package/src/javascript/grip-broadcast.js +243 -0
- package/src/javascript/jwt-verifier.js +137 -0
- package/src/javascript/lifecycle.js +69 -0
- package/src/javascript/node-adapter.js +281 -0
- package/src/javascript/package-effects.js +78 -0
- package/src/javascript/runtime-host.js +165 -0
- package/src/javascript/source-package.js +439 -0
- package/src/javascript/support.js +205 -0
- package/src/javascript/target-support-policy.js +134 -0
- package/src/javascript/target.js +37 -0
- package/src/javascript/test-runtime.js +111 -0
- package/src/native/target.js +78 -0
- package/src/runtime/assets-provider.js +776 -0
- package/src/runtime/canonical-api-runtime.js +632 -0
- package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
- package/src/runtime/index.js +17 -0
- package/src/runtime/jwt-verifier.js +264 -0
- package/src/runtime/route-handler-effects.js +757 -0
- package/src/toolchain.js +258 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function loadAssetsContracts() {
|
|
4
|
+
try {
|
|
5
|
+
return require('@pulse-compute/wasm-contracts/assets/contracts');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
8
|
+
return require('../../../../wasm/packages/contracts/src/assets/contracts.js');
|
|
9
|
+
}
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadDiagnostics() {
|
|
15
|
+
try {
|
|
16
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
19
|
+
return require('../../../../wasm/packages/contracts/src/diagnostics.js');
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const assetsContracts = loadAssetsContracts();
|
|
26
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic, stableFileName } = loadDiagnostics();
|
|
27
|
+
const {
|
|
28
|
+
createNodeCompiledWasmAssetsLifecycleRuntime,
|
|
29
|
+
normalizeHeaders
|
|
30
|
+
} = require('../runtime/assets-provider.js');
|
|
31
|
+
|
|
32
|
+
const phaseName = 'node-compiled-wasm-assets-lifecycle-proof';
|
|
33
|
+
|
|
34
|
+
function clone(value) {
|
|
35
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
36
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizePlan(input) {
|
|
41
|
+
const plan = input && input.artifact ? input.artifact : input;
|
|
42
|
+
if (!plan || typeof plan !== 'object') return undefined;
|
|
43
|
+
return {
|
|
44
|
+
...plan,
|
|
45
|
+
entries: Array.isArray(plan.entries) ? plan.entries.map(clone) : []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeSidecarPlan(input) {
|
|
50
|
+
const plan = input && input.artifact ? input.artifact : input;
|
|
51
|
+
if (!plan || typeof plan !== 'object') return undefined;
|
|
52
|
+
return {
|
|
53
|
+
...plan,
|
|
54
|
+
terminalAssetResponseEffects: Array.isArray(plan.terminalAssetResponseEffects) ? plan.terminalAssetResponseEffects.map(clone) : []
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeDiagnostic(code, message, severity, details, hint) {
|
|
59
|
+
return normalizeDiagnostic({
|
|
60
|
+
phase: phaseName,
|
|
61
|
+
severity: severity || 'error',
|
|
62
|
+
code,
|
|
63
|
+
message,
|
|
64
|
+
hint: hint || (details && details.hint),
|
|
65
|
+
details,
|
|
66
|
+
loc: { file: '<node-compiled-wasm-assets-lifecycle-proof>' }
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function check(name, passed, details, failures, diagnostics) {
|
|
71
|
+
const entry = { name, status: passed ? 'ok' : 'error', details: details || {} };
|
|
72
|
+
if (!passed) {
|
|
73
|
+
failures.push(entry);
|
|
74
|
+
diagnostics.push(makeDiagnostic(
|
|
75
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NODE_COMPILED_LIFECYCLE_SMOKE_FAILED,
|
|
76
|
+
`Node compiled-Wasm assets lifecycle check failed: ${name}.`,
|
|
77
|
+
'error',
|
|
78
|
+
{ check: name, details: entry.details }
|
|
79
|
+
));
|
|
80
|
+
}
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function headerValue(headers, name) {
|
|
85
|
+
const lower = String(name).toLowerCase();
|
|
86
|
+
const found = normalizeHeaders(headers).find(([key]) => String(key).toLowerCase() === lower);
|
|
87
|
+
return found ? found[1] : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resultBody(result) {
|
|
91
|
+
if (!result) return undefined;
|
|
92
|
+
if (result.body !== undefined) return String(result.body);
|
|
93
|
+
if (result.kind === 'stream' && result.bodyStream && Array.isArray(result.bodyStream.chunks)) {
|
|
94
|
+
return result.bodyStream.chunks.map((chunk) => Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk)).join('');
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sanitizeResult(result) {
|
|
100
|
+
if (!result) return undefined;
|
|
101
|
+
return {
|
|
102
|
+
status: result.status,
|
|
103
|
+
kind: result.kind,
|
|
104
|
+
body: resultBody(result),
|
|
105
|
+
bodyLength: resultBody(result) ? resultBody(result).length : 0,
|
|
106
|
+
headers: normalizeHeaders(result.headers),
|
|
107
|
+
diagnostics: (result.diagnostics || []).map((entry) => ({ code: entry.code, severity: entry.severity, message: entry.message }))
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function findEffect(effects, method) {
|
|
112
|
+
const upper = String(method || 'GET').toUpperCase();
|
|
113
|
+
return effects.find((entry) => String(entry.method || 'GET').toUpperCase() === upper) || effects[0];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function codeSet(result) {
|
|
117
|
+
return new Set((result && result.diagnostics || []).map((entry) => entry.code));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function negativeCaseFlag(negativeCases, name, fallback) {
|
|
121
|
+
if (negativeCases && Object.prototype.hasOwnProperty.call(negativeCases, name)) return negativeCases[name] === true;
|
|
122
|
+
return Boolean(fallback);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildNodeCompiledWasmAssetsLifecycleProof(inputs = {}) {
|
|
126
|
+
const cwd = inputs.cwd || process.cwd();
|
|
127
|
+
const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
|
|
128
|
+
const plan = normalizePlan(inputs.assetsLoweringPlan || inputs.plan);
|
|
129
|
+
const sidecarPlan = normalizeSidecarPlan(inputs.assetsCompiledWasmSidecarPlan || inputs.sidecarPlan);
|
|
130
|
+
const diagnostics = [];
|
|
131
|
+
const checks = [];
|
|
132
|
+
const failures = [];
|
|
133
|
+
const negativeCases = inputs.negativeCases || {};
|
|
134
|
+
|
|
135
|
+
if (!plan) {
|
|
136
|
+
diagnostics.push(makeDiagnostic(
|
|
137
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NODE_PROVIDER_PLAN_REQUIRED,
|
|
138
|
+
'Node compiled-Wasm assets lifecycle proof requires an assets-lowering-plan artifact.',
|
|
139
|
+
'error',
|
|
140
|
+
{ sourceArtifact: assetsContracts.ASSETS_LOWERING_PLAN_VERSION }
|
|
141
|
+
));
|
|
142
|
+
}
|
|
143
|
+
if (!sidecarPlan) {
|
|
144
|
+
diagnostics.push(makeDiagnostic(
|
|
145
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_FILE_MISSING,
|
|
146
|
+
'Node compiled-Wasm assets lifecycle proof requires an assets-compiled-wasm-sidecar-plan artifact.',
|
|
147
|
+
'error',
|
|
148
|
+
{ sourceArtifact: assetsContracts.ASSETS_COMPILED_WASM_SIDECAR_PLAN_ARTIFACT }
|
|
149
|
+
));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const terminalEffects = sidecarPlan && sidecarPlan.terminalAssetResponseEffects || [];
|
|
153
|
+
const getEffect = findEffect(terminalEffects, 'GET');
|
|
154
|
+
const headEffect = findEffect(terminalEffects, 'HEAD');
|
|
155
|
+
const missingEffect = getEffect ? {
|
|
156
|
+
...clone(getEffect),
|
|
157
|
+
id: `${getEffect.id || 'asset'}-missing`,
|
|
158
|
+
key: '/__pulsewasm-missing-asset__',
|
|
159
|
+
method: 'GET'
|
|
160
|
+
} : undefined;
|
|
161
|
+
|
|
162
|
+
let runtime;
|
|
163
|
+
let executions = [];
|
|
164
|
+
let storeConfigProbeCodes = new Set();
|
|
165
|
+
try {
|
|
166
|
+
if (plan && sidecarPlan) {
|
|
167
|
+
runtime = createNodeCompiledWasmAssetsLifecycleRuntime({
|
|
168
|
+
cwd,
|
|
169
|
+
configRoot: inputs.configRoot,
|
|
170
|
+
assetConfig: inputs.assetConfig,
|
|
171
|
+
resolvedConfig: inputs.resolvedConfig,
|
|
172
|
+
assetsLoweringPlan: plan,
|
|
173
|
+
assetsCompiledWasmSidecarPlan: sidecarPlan,
|
|
174
|
+
extraTerminalEffects: missingEffect ? [missingEffect] : [],
|
|
175
|
+
mode: inputs.mode || 'compiled-wasm-assets-lifecycle'
|
|
176
|
+
});
|
|
177
|
+
diagnostics.push(...(runtime.diagnostics || []));
|
|
178
|
+
executions = runtime.executeAll();
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
diagnostics.push(makeDiagnostic(
|
|
182
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NODE_COMPILED_LIFECYCLE_SMOKE_FAILED,
|
|
183
|
+
'Node compiled-Wasm assets lifecycle smoke execution failed.',
|
|
184
|
+
'error',
|
|
185
|
+
{ message: error && error.message ? error.message : String(error), stack: error && error.stack ? error.stack : undefined }
|
|
186
|
+
));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (plan && sidecarPlan) {
|
|
190
|
+
try {
|
|
191
|
+
const probe = createNodeCompiledWasmAssetsLifecycleRuntime({
|
|
192
|
+
cwd,
|
|
193
|
+
configRoot: inputs.configRoot,
|
|
194
|
+
assetsLoweringPlan: plan,
|
|
195
|
+
assetsCompiledWasmSidecarPlan: sidecarPlan,
|
|
196
|
+
mode: 'compiled-wasm-assets-lifecycle-missing-store-config-probe'
|
|
197
|
+
});
|
|
198
|
+
storeConfigProbeCodes = codeSet(probe);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error && Array.isArray(error.diagnostics)) storeConfigProbeCodes = new Set(error.diagnostics.map((entry) => entry.code));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const byId = new Map(executions.map((entry) => [entry.effect && entry.effect.id, entry]));
|
|
205
|
+
const getExecution = getEffect ? byId.get(getEffect.id) : undefined;
|
|
206
|
+
const headExecution = headEffect ? byId.get(headEffect.id) : undefined;
|
|
207
|
+
const missingExecution = missingEffect ? byId.get(missingEffect.id) : undefined;
|
|
208
|
+
const getResult = getExecution && getExecution.response;
|
|
209
|
+
const headResult = headExecution && headExecution.response;
|
|
210
|
+
const missingResult = missingExecution && missingExecution.response;
|
|
211
|
+
|
|
212
|
+
checks.push(check(
|
|
213
|
+
'compiled_wasm_module_imports_assets_hostcalls',
|
|
214
|
+
Boolean(runtime && runtime.wasm.imports.some((entry) => entry.module === 'pulse_assets' && entry.name === 'pulse_assets_lookup') && runtime.wasm.imports.some((entry) => entry.module === 'pulse_assets' && entry.name === 'pulse_assets_respond')),
|
|
215
|
+
{ imports: runtime && runtime.wasm.imports },
|
|
216
|
+
failures,
|
|
217
|
+
diagnostics
|
|
218
|
+
));
|
|
219
|
+
checks.push(check(
|
|
220
|
+
'terminal_asset_response_effect_validated',
|
|
221
|
+
executions.length >= terminalEffects.length && executions.every((entry) => entry.resultHandle > 0 && entry.lookup && entry.response),
|
|
222
|
+
{ executions: executions.map((entry) => ({ id: entry.effect && entry.effect.id, resultHandle: entry.resultHandle, lookup: entry.lookup && { store: entry.lookup.store, key: entry.lookup.key, method: entry.lookup.method }, response: sanitizeResult(entry.response) })) },
|
|
223
|
+
failures,
|
|
224
|
+
diagnostics
|
|
225
|
+
));
|
|
226
|
+
checks.push(check(
|
|
227
|
+
'get_text_asset_validated',
|
|
228
|
+
Boolean(getResult && getResult.status === 200 && getResult.kind === 'text' && resultBody(getResult) && resultBody(getResult).length > 0),
|
|
229
|
+
{ response: sanitizeResult(getResult) },
|
|
230
|
+
failures,
|
|
231
|
+
diagnostics
|
|
232
|
+
));
|
|
233
|
+
checks.push(check(
|
|
234
|
+
'head_asset_bodyless_validated',
|
|
235
|
+
Boolean(headResult && headResult.status === 200 && headResult.kind === 'empty' && headResult.body === undefined),
|
|
236
|
+
{ response: sanitizeResult(headResult) },
|
|
237
|
+
failures,
|
|
238
|
+
diagnostics
|
|
239
|
+
));
|
|
240
|
+
checks.push(check(
|
|
241
|
+
'headers_validated',
|
|
242
|
+
Boolean(getResult && headerValue(getResult.headers, 'content-type') && headerValue(getResult.headers, 'x-pulsewasm-asset-key') === (getEffect && getEffect.key)),
|
|
243
|
+
{ contentType: getResult && headerValue(getResult.headers, 'content-type'), assetKey: getResult && headerValue(getResult.headers, 'x-pulsewasm-asset-key') },
|
|
244
|
+
failures,
|
|
245
|
+
diagnostics
|
|
246
|
+
));
|
|
247
|
+
checks.push(check(
|
|
248
|
+
'cache_control_validated',
|
|
249
|
+
Boolean(getResult && headerValue(getResult.headers, 'cache-control') === (getEffect && getEffect.cacheControl || 'public, max-age=60')),
|
|
250
|
+
{ expected: getEffect && getEffect.cacheControl || 'public, max-age=60', actual: getResult && headerValue(getResult.headers, 'cache-control') },
|
|
251
|
+
failures,
|
|
252
|
+
diagnostics
|
|
253
|
+
));
|
|
254
|
+
checks.push(check(
|
|
255
|
+
'missing_asset_validated',
|
|
256
|
+
Boolean(missingResult && missingResult.status === 404 && missingResult.kind === 'empty'),
|
|
257
|
+
{ response: sanitizeResult(missingResult) },
|
|
258
|
+
failures,
|
|
259
|
+
diagnostics
|
|
260
|
+
));
|
|
261
|
+
checks.push(check(
|
|
262
|
+
'explicit_store_config_required',
|
|
263
|
+
storeConfigProbeCodes.has(assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_REQUIRED),
|
|
264
|
+
{ diagnosticCodes: Array.from(storeConfigProbeCodes).sort() },
|
|
265
|
+
failures,
|
|
266
|
+
diagnostics
|
|
267
|
+
));
|
|
268
|
+
|
|
269
|
+
const sidecarRestrictions = sidecarPlan && sidecarPlan.restrictions || {};
|
|
270
|
+
const dynamicKeysRejected = negativeCaseFlag(negativeCases, 'dynamicKeysRejected', sidecarRestrictions.dynamicKeysSupported === false);
|
|
271
|
+
const handleEscapeRejected = negativeCaseFlag(negativeCases, 'handleEscapeRejected', sidecarRestrictions.handleEscapeSupported === false);
|
|
272
|
+
const binaryBodyReserved = negativeCaseFlag(negativeCases, 'binaryBodyReserved', sidecarRestrictions.binaryBodySupported === false);
|
|
273
|
+
const streamPlanOnly = negativeCaseFlag(negativeCases, 'streamBodyPlanOnly', sidecarRestrictions.streamBodyCompiledWasmReadiness === 'plan-only');
|
|
274
|
+
|
|
275
|
+
checks.push(check('dynamic_keys_rejected', dynamicKeysRejected, { diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.DYNAMIC_KEY_UNSUPPORTED }, failures, diagnostics));
|
|
276
|
+
checks.push(check('asset_handle_escape_rejected', handleEscapeRejected, { diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED }, failures, diagnostics));
|
|
277
|
+
checks.push(check('compiled_binary_body_reserved', binaryBodyReserved, { diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_BINARY_RESERVED }, failures, diagnostics));
|
|
278
|
+
checks.push(check('compiled_stream_body_plan_only', streamPlanOnly, { diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_STREAM_PLAN_ONLY }, failures, diagnostics));
|
|
279
|
+
|
|
280
|
+
const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
|
|
281
|
+
const artifact = normalizeArtifact({
|
|
282
|
+
version: assetsContracts.ASSETS_NODE_COMPILED_WASM_LIFECYCLE_PROOF_VERSION,
|
|
283
|
+
generatedBy,
|
|
284
|
+
phase: phaseName,
|
|
285
|
+
artifact: assetsContracts.ASSETS_NODE_COMPILED_WASM_LIFECYCLE_PROOF_ARTIFACT,
|
|
286
|
+
provider: 'node',
|
|
287
|
+
providerPackage: '@pulse-compute/provider-node',
|
|
288
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
289
|
+
status: errorDiagnostics.length === 0 ? 'ok' : 'error',
|
|
290
|
+
sourceAssetsLoweringPlanVersion: plan && plan.version,
|
|
291
|
+
sourceAssetsCompiledWasmSidecarPlanVersion: sidecarPlan && sidecarPlan.version,
|
|
292
|
+
loweringPlanConnected: Boolean(plan),
|
|
293
|
+
sidecarPlanConnected: Boolean(sidecarPlan),
|
|
294
|
+
compiledWasmLowering: true,
|
|
295
|
+
compiledWasmRuntimeBehaviorImplemented: errorDiagnostics.length === 0,
|
|
296
|
+
terminalAssetResponseEffectValidated: checks.some((entry) => entry.name === 'terminal_asset_response_effect_validated' && entry.status === 'ok'),
|
|
297
|
+
getTextAssetValidated: checks.some((entry) => entry.name === 'get_text_asset_validated' && entry.status === 'ok'),
|
|
298
|
+
headAssetValidated: checks.some((entry) => entry.name === 'head_asset_bodyless_validated' && entry.status === 'ok'),
|
|
299
|
+
headersValidated: checks.some((entry) => entry.name === 'headers_validated' && entry.status === 'ok'),
|
|
300
|
+
cacheControlValidated: checks.some((entry) => entry.name === 'cache_control_validated' && entry.status === 'ok'),
|
|
301
|
+
missingAssetValidated: checks.some((entry) => entry.name === 'missing_asset_validated' && entry.status === 'ok'),
|
|
302
|
+
storeConfigRequired: checks.some((entry) => entry.name === 'explicit_store_config_required' && entry.status === 'ok'),
|
|
303
|
+
dynamicKeysRejected,
|
|
304
|
+
handleEscapeRejected,
|
|
305
|
+
binaryBodyReserved,
|
|
306
|
+
streamBodyReadiness: 'plan-only',
|
|
307
|
+
userAuthoredAsync: false,
|
|
308
|
+
promises: false,
|
|
309
|
+
asyncAwait: false,
|
|
310
|
+
asyncify: false,
|
|
311
|
+
runtime: {
|
|
312
|
+
version: assetsContracts.ASSETS_NODE_COMPILED_WASM_LIFECYCLE_RUNTIME_VERSION,
|
|
313
|
+
package: '@pulse-compute/provider-node/runtime/assets-provider',
|
|
314
|
+
mode: inputs.mode || 'compiled-wasm-assets-lifecycle',
|
|
315
|
+
actualAssemblyScriptCompilerRequired: false,
|
|
316
|
+
generatedInMemoryWasmModule: true
|
|
317
|
+
},
|
|
318
|
+
wasmModule: runtime ? {
|
|
319
|
+
byteLength: runtime.wasm.byteLength,
|
|
320
|
+
imports: runtime.wasm.imports,
|
|
321
|
+
exports: runtime.wasm.exports,
|
|
322
|
+
routeExports: runtime.wasm.exports.filter((entry) => entry.kind === 'function' && String(entry.name).startsWith('pulse_assets_route_')).map((entry) => entry.name)
|
|
323
|
+
} : undefined,
|
|
324
|
+
storeConfig: runtime ? {
|
|
325
|
+
explicit: true,
|
|
326
|
+
stores: runtime.storeConfig.configuredStores.map((entry) => ({
|
|
327
|
+
store: entry.store,
|
|
328
|
+
mode: entry.mode,
|
|
329
|
+
methods: entry.methods,
|
|
330
|
+
responseBody: entry.responseBody,
|
|
331
|
+
cacheControl: entry.cacheControl,
|
|
332
|
+
dir: stableFileName(entry.dir, cwd)
|
|
333
|
+
}))
|
|
334
|
+
} : undefined,
|
|
335
|
+
entriesConsumed: terminalEffects.map(clone),
|
|
336
|
+
syntheticEffects: missingEffect ? [{ id: missingEffect.id, key: missingEffect.key, method: missingEffect.method, purpose: 'missing asset deterministic 404 proof' }] : [],
|
|
337
|
+
smoke: {
|
|
338
|
+
checks,
|
|
339
|
+
failedChecks: failures,
|
|
340
|
+
executions: executions.map((entry) => ({
|
|
341
|
+
index: entry.index,
|
|
342
|
+
exportName: entry.exportName,
|
|
343
|
+
resultHandle: entry.resultHandle,
|
|
344
|
+
effect: entry.effect,
|
|
345
|
+
lookup: entry.lookup,
|
|
346
|
+
response: sanitizeResult(entry.response)
|
|
347
|
+
}))
|
|
348
|
+
},
|
|
349
|
+
negativeCases: {
|
|
350
|
+
storeConfigRequired: {
|
|
351
|
+
rejected: storeConfigProbeCodes.has(assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_REQUIRED),
|
|
352
|
+
diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_REQUIRED
|
|
353
|
+
},
|
|
354
|
+
dynamicKey: {
|
|
355
|
+
rejected: dynamicKeysRejected,
|
|
356
|
+
diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.DYNAMIC_KEY_UNSUPPORTED,
|
|
357
|
+
loweringDiagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.NON_LITERAL_LOOKUP
|
|
358
|
+
},
|
|
359
|
+
handleEscape: {
|
|
360
|
+
rejected: handleEscapeRejected,
|
|
361
|
+
diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED
|
|
362
|
+
},
|
|
363
|
+
binaryBody: {
|
|
364
|
+
reserved: binaryBodyReserved,
|
|
365
|
+
diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_BINARY_RESERVED,
|
|
366
|
+
loweringDiagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED
|
|
367
|
+
},
|
|
368
|
+
streamBody: {
|
|
369
|
+
readiness: 'plan-only',
|
|
370
|
+
diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_STREAM_PLAN_ONLY,
|
|
371
|
+
loweringDiagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.STREAM_PROVIDER_NOT_WIRED
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
policy: {
|
|
375
|
+
providerOwnsRuntimeBehavior: true,
|
|
376
|
+
compilerOrchestratesOnly: true,
|
|
377
|
+
packageOwnsSidecarAndLowerer: true,
|
|
378
|
+
sidecarPlanRequired: true,
|
|
379
|
+
storeConfigRequired: true,
|
|
380
|
+
continuationRequired: false,
|
|
381
|
+
directProviderProofFallback: false,
|
|
382
|
+
compiledWasmModuleExecuted: Boolean(runtime && executions.length > 0),
|
|
383
|
+
assemblyScriptInstallRequiredForProof: false
|
|
384
|
+
},
|
|
385
|
+
scope: {
|
|
386
|
+
methods: clone(assetsContracts.ASSETS_ALLOWED_METHODS),
|
|
387
|
+
payloadModes: ['text-response-body'],
|
|
388
|
+
localStoreMode: true,
|
|
389
|
+
uploads: false,
|
|
390
|
+
mutationApis: false,
|
|
391
|
+
binaryBody: false,
|
|
392
|
+
streamBodyReadiness: 'plan-only',
|
|
393
|
+
dynamicKeys: false,
|
|
394
|
+
handleEscape: false
|
|
395
|
+
},
|
|
396
|
+
summary: {
|
|
397
|
+
compiledWasmLowering: true,
|
|
398
|
+
compiledWasmRuntimeBehaviorImplemented: errorDiagnostics.length === 0,
|
|
399
|
+
terminalAssetResponseEffectValidated: checks.some((entry) => entry.name === 'terminal_asset_response_effect_validated' && entry.status === 'ok'),
|
|
400
|
+
getTextAssetValidated: checks.some((entry) => entry.name === 'get_text_asset_validated' && entry.status === 'ok'),
|
|
401
|
+
headAssetValidated: checks.some((entry) => entry.name === 'head_asset_bodyless_validated' && entry.status === 'ok'),
|
|
402
|
+
headersValidated: checks.some((entry) => entry.name === 'headers_validated' && entry.status === 'ok'),
|
|
403
|
+
cacheControlValidated: checks.some((entry) => entry.name === 'cache_control_validated' && entry.status === 'ok'),
|
|
404
|
+
missingAssetValidated: checks.some((entry) => entry.name === 'missing_asset_validated' && entry.status === 'ok'),
|
|
405
|
+
storeConfigRequired: checks.some((entry) => entry.name === 'explicit_store_config_required' && entry.status === 'ok'),
|
|
406
|
+
dynamicKeysRejected,
|
|
407
|
+
handleEscapeRejected,
|
|
408
|
+
binaryBodyReserved,
|
|
409
|
+
streamBodyReadiness: 'plan-only',
|
|
410
|
+
userAuthoredAsync: false,
|
|
411
|
+
promises: false,
|
|
412
|
+
asyncAwait: false,
|
|
413
|
+
asyncify: false,
|
|
414
|
+
checks: checks.length,
|
|
415
|
+
failedChecks: failures.length,
|
|
416
|
+
diagnostics: diagnostics.length,
|
|
417
|
+
errorDiagnostics: errorDiagnostics.length,
|
|
418
|
+
errors: errorDiagnostics.length
|
|
419
|
+
},
|
|
420
|
+
diagnostics
|
|
421
|
+
}, cwd);
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
artifact,
|
|
425
|
+
diagnostics,
|
|
426
|
+
runtime,
|
|
427
|
+
smoke: artifact.smoke,
|
|
428
|
+
hasErrors: errorDiagnostics.length > 0
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
module.exports = {
|
|
433
|
+
phaseName,
|
|
434
|
+
buildNodeCompiledWasmAssetsLifecycleProof
|
|
435
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
function loadRequestJsonContracts() {
|
|
7
|
+
const local = path.resolve(__dirname, '../../../../wasm/packages/contracts/src/handler/request-json-body.js');
|
|
8
|
+
if (fs.existsSync(local)) return require(local);
|
|
9
|
+
return require('@pulse-compute/wasm-contracts/handler/request-json-body');
|
|
10
|
+
}
|
|
11
|
+
function loadDiagnostics() {
|
|
12
|
+
const local = path.resolve(__dirname, '../../../../wasm/packages/contracts/src/diagnostics.js');
|
|
13
|
+
if (fs.existsSync(local)) return require(local);
|
|
14
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
15
|
+
}
|
|
16
|
+
function loadArtifactsDir() {
|
|
17
|
+
const local = path.resolve(__dirname, '../../../../wasm/packages/build-support/src/artifacts-dir.js');
|
|
18
|
+
if (fs.existsSync(local)) return require(local);
|
|
19
|
+
return require('@pulse-compute/wasm-build-support/artifacts-dir');
|
|
20
|
+
}
|
|
21
|
+
const requestJson = loadRequestJsonContracts();
|
|
22
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadDiagnostics();
|
|
23
|
+
const { getDefaultArtifactsDir } = loadArtifactsDir();
|
|
24
|
+
const { createCompiledHandlerBridgeRuntime, findCompiledHandlerWasm } = require('./route-handler-compiled-wasm-bridge.js');
|
|
25
|
+
|
|
26
|
+
const phaseName = 'node-request-json-body-proof';
|
|
27
|
+
|
|
28
|
+
function clone(value) {
|
|
29
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
30
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function normalizePlan(input) {
|
|
34
|
+
const plan = input && input.artifact && typeof input.artifact === 'object' ? input.artifact : input;
|
|
35
|
+
if (!plan || typeof plan !== 'object') return undefined;
|
|
36
|
+
return { ...plan, routes: Array.isArray(plan.routes) ? plan.routes.map(clone) : [] };
|
|
37
|
+
}
|
|
38
|
+
function requestJsonRoutes(plan) {
|
|
39
|
+
return (Array.isArray(plan && plan.routes) ? plan.routes : []).filter((route) => route && route.required);
|
|
40
|
+
}
|
|
41
|
+
function makeDiagnostic(code, message, severity, details, hint) {
|
|
42
|
+
return normalizeDiagnostic({ phase: phaseName, severity: severity || 'error', code, message, hint, details, loc: { file: '<node-request-json-body-proof>' } });
|
|
43
|
+
}
|
|
44
|
+
function check(name, ok, details, failures, diagnostics) {
|
|
45
|
+
const entry = { name, status: ok ? 'ok' : 'error', details: sanitize(details || {}) };
|
|
46
|
+
if (!ok) {
|
|
47
|
+
failures.push(entry);
|
|
48
|
+
diagnostics.push(makeDiagnostic(requestJson.REQUEST_JSON_BODY_DIAGNOSTICS.compiledSmokeFailed, `Node request JSON body proof check failed: ${name}.`, 'error', entry.details));
|
|
49
|
+
}
|
|
50
|
+
return entry;
|
|
51
|
+
}
|
|
52
|
+
function sanitize(value) {
|
|
53
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
54
|
+
if (!value || typeof value !== 'object') return value;
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
57
|
+
if (key === 'body') { out.body = String(entry || ''); out.bodyPreview = String(entry || '').slice(0, 240); }
|
|
58
|
+
else out[key] = sanitize(entry);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function runRoute(runtime, index, variant) {
|
|
63
|
+
const fnName = variant === 'invalid' ? `bridge_run_request_json_route_${index}_invalid` : `bridge_run_request_json_route_${index}`;
|
|
64
|
+
const fn = runtime.exports[fnName];
|
|
65
|
+
if (typeof fn !== 'function') throw new Error(`Missing request JSON bridge export ${fnName}.`);
|
|
66
|
+
const status = fn();
|
|
67
|
+
return { status, result: runtime.readResult(), bodyReadCount: runtime.requestBodyReadCount(), parseCount: runtime.requestJsonParseCount() };
|
|
68
|
+
}
|
|
69
|
+
function runNoBodyEffectRoute(runtime) {
|
|
70
|
+
const fn = runtime.exports.bridge_run_effect_route_0;
|
|
71
|
+
if (typeof fn !== 'function') return undefined;
|
|
72
|
+
const kind = fn();
|
|
73
|
+
return { kind, bodyReadCount: runtime.requestBodyReadCount(), parseCount: runtime.requestJsonParseCount(), result: runtime.readResult() };
|
|
74
|
+
}
|
|
75
|
+
function buildNodeRequestJsonBodyProof(options = {}) {
|
|
76
|
+
const cwd = options.cwd || process.cwd();
|
|
77
|
+
const outDir = options.outDir || getDefaultArtifactsDir(cwd);
|
|
78
|
+
const generatedBy = options.generatedBy || PACKAGE_VERSION;
|
|
79
|
+
const diagnostics = [];
|
|
80
|
+
const failures = [];
|
|
81
|
+
const checks = [];
|
|
82
|
+
const plan = normalizePlan(options.requestJsonBodyPlan || options.plan);
|
|
83
|
+
if (!plan) diagnostics.push(makeDiagnostic(requestJson.REQUEST_JSON_BODY_DIAGNOSTICS.planMissing, 'Node request JSON body proof requires request-json-body-plan.json.', 'error', { hint: 'Enable request-json-body-plan before requesting the Node proof.' }));
|
|
84
|
+
const routes = requestJsonRoutes(plan || {});
|
|
85
|
+
if (plan && routes.length === 0) diagnostics.push(makeDiagnostic(requestJson.REQUEST_JSON_BODY_DIAGNOSTICS.planMissing, 'Node request JSON body proof requires at least one route using ctx.req.json/parse.', 'error', { routes: (plan.routes || []).length }));
|
|
86
|
+
const wasmFile = findCompiledHandlerWasm(options.compiledHandlers, cwd, outDir, options.compiledHandlersCwd || options.sourceCwd);
|
|
87
|
+
if (!wasmFile) diagnostics.push(makeDiagnostic(requestJson.REQUEST_JSON_BODY_DIAGNOSTICS.wasmMissing, 'Node request JSON body proof requires the compiled-handler Wasm artifact.', 'error', { hint: 'Enable compiled handlers together with the request JSON body proof.' }));
|
|
88
|
+
|
|
89
|
+
let runtime;
|
|
90
|
+
const smoke = { results: {}, checks, failedChecks: failures };
|
|
91
|
+
if (wasmFile && diagnostics.filter((entry) => (entry.severity || 'error') === 'error').length === 0) {
|
|
92
|
+
try {
|
|
93
|
+
runtime = createCompiledHandlerBridgeRuntime(wasmFile);
|
|
94
|
+
const noBody = runNoBodyEffectRoute(runtime);
|
|
95
|
+
if (noBody) {
|
|
96
|
+
smoke.results.noBodyRoute = sanitize(noBody);
|
|
97
|
+
checks.push(check('request_json_not_read_on_unrelated_effect_route', noBody.bodyReadCount === 0 && noBody.parseCount === 0, noBody, failures, diagnostics));
|
|
98
|
+
}
|
|
99
|
+
for (let index = 0; index < routes.length; index += 1) {
|
|
100
|
+
const route = routes[index];
|
|
101
|
+
const valid = runRoute(runtime, index, 'valid');
|
|
102
|
+
smoke.results[`route${index}`] = sanitize({ route: { method: route.method, path: route.path, handlerName: route.handlerName }, valid });
|
|
103
|
+
const validReturnedResolveBoundary = valid.result && valid.result.kind === 'resolve';
|
|
104
|
+
checks.push(check(`request_json_route_${index}_valid_status`, (valid.status >= 200 && valid.status < 300) || (validReturnedResolveBoundary && valid.status === 0), valid, failures, diagnostics));
|
|
105
|
+
checks.push(check(`request_json_route_${index}_lazy_read_once`, valid.bodyReadCount === 1, valid, failures, diagnostics));
|
|
106
|
+
checks.push(check(`request_json_route_${index}_parse_once`, valid.parseCount === 1, valid, failures, diagnostics));
|
|
107
|
+
if (validReturnedResolveBoundary) {
|
|
108
|
+
checks.push(check(`request_json_route_${index}_effect_boundary_returned`, true, valid.result, failures, diagnostics));
|
|
109
|
+
} else {
|
|
110
|
+
checks.push(check(`request_json_route_${index}_body_visible`, /body-123/.test(valid.result.body || ''), valid.result, failures, diagnostics));
|
|
111
|
+
}
|
|
112
|
+
const invalid = runRoute(runtime, index, 'invalid');
|
|
113
|
+
smoke.results[`route${index}Invalid`] = sanitize({ route: { method: route.method, path: route.path, handlerName: route.handlerName }, invalid });
|
|
114
|
+
checks.push(check(`request_json_route_${index}_invalid_json_status`, invalid.status === 400, invalid, failures, diagnostics));
|
|
115
|
+
checks.push(check(`request_json_route_${index}_invalid_json_error`, /invalid_json/.test(invalid.result.body || ''), invalid.result, failures, diagnostics));
|
|
116
|
+
}
|
|
117
|
+
checks.push(check('request_json_module_has_no_js_handler_imports', runtime.imports.filter((entry) => entry.module === 'pulsewasm_handlers').length === 0, { imports: runtime.imports }, failures, diagnostics));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
diagnostics.push(makeDiagnostic(requestJson.REQUEST_JSON_BODY_DIAGNOSTICS.compiledSmokeFailed, 'Node request JSON body proof smoke execution failed.', 'error', { message: error && error.message ? error.message : String(error), stack: error && error.stack ? error.stack : undefined }));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
|
|
123
|
+
const compiledHandlerFiles = Array.isArray(options.compiledHandlers && options.compiledHandlers.files) ? options.compiledHandlers.files : [];
|
|
124
|
+
const compiledHandlerOutputs = Array.isArray(options.compiledHandlers && options.compiledHandlers.outputFiles) ? options.compiledHandlers.outputFiles : [];
|
|
125
|
+
const artifact = normalizeArtifact({
|
|
126
|
+
version: requestJson.NODE_REQUEST_JSON_BODY_PROOF_VERSION,
|
|
127
|
+
generatedBy,
|
|
128
|
+
phase: requestJson.NODE_REQUEST_JSON_BODY_PROOF_PHASE,
|
|
129
|
+
artifact: requestJson.NODE_REQUEST_JSON_BODY_PROOF_ARTIFACT,
|
|
130
|
+
contractId: requestJson.REQUEST_JSON_BODY_CONTRACT_ID,
|
|
131
|
+
provider: 'node',
|
|
132
|
+
status: errorDiagnostics.length === 0 ? 'ok' : 'error',
|
|
133
|
+
sourcePlanVersion: plan && plan.version,
|
|
134
|
+
sourcePlanArtifact: plan && plan.artifact,
|
|
135
|
+
requestJsonBodyPlanConnected: Boolean(plan),
|
|
136
|
+
compiledHandlersConnected: Boolean(options.compiledHandlers),
|
|
137
|
+
compiledHandlerPlanVersion: options.compiledHandlers && options.compiledHandlers.artifact && options.compiledHandlers.artifact.version,
|
|
138
|
+
runtime: { version: requestJson.NODE_REQUEST_JSON_BODY_RUNTIME_VERSION, package: '@pulse-compute/wasm-runtime-core-as/compiler/compiled-handlers', wasm: wasmFile ? path.relative(cwd, wasmFile).replace(/\\/g, '/') : undefined },
|
|
139
|
+
scope: requestJson.defaultNodeRequestJsonBodyProofPolicy(),
|
|
140
|
+
lazyEvaluation: JSON.parse(JSON.stringify(requestJson.REQUEST_JSON_BODY_LAZY_POLICY)),
|
|
141
|
+
surface: JSON.parse(JSON.stringify(requestJson.REQUEST_JSON_BODY_SURFACE)),
|
|
142
|
+
accessors: [...requestJson.REQUEST_JSON_BODY_ACCESSORS],
|
|
143
|
+
reservedAccessors: [...requestJson.REQUEST_JSON_BODY_RESERVED_ACCESSORS],
|
|
144
|
+
compiledWasmRuntimeImplemented: errorDiagnostics.length === 0,
|
|
145
|
+
nodeCompiledProofImplemented: errorDiagnostics.length === 0,
|
|
146
|
+
providerNetworkFetchRequired: false,
|
|
147
|
+
backendJsonRequestBodyImplemented: false,
|
|
148
|
+
binaryRequestBodyParsing: false,
|
|
149
|
+
streamRequestBodyParsing: false,
|
|
150
|
+
asyncAwait: false,
|
|
151
|
+
promises: false,
|
|
152
|
+
asyncify: false,
|
|
153
|
+
routesConsumed: routes.map((route, index) => ({ index, routeId: route.routeId, method: route.method, path: route.path, handlerName: route.handlerName, reads: route.reads, accessors: route.accessors })),
|
|
154
|
+
smoke,
|
|
155
|
+
summary: {
|
|
156
|
+
routesWithJsonBodyReads: routes.length,
|
|
157
|
+
compiled: Boolean(wasmFile) && errorDiagnostics.length === 0,
|
|
158
|
+
executed: checks.length > 0 && errorDiagnostics.length === 0,
|
|
159
|
+
checks: checks.length,
|
|
160
|
+
failedChecks: failures.length,
|
|
161
|
+
lazyReadValidated: checks.some((entry) => entry.name.includes('lazy_read_once') && entry.status === 'ok'),
|
|
162
|
+
parseCacheValidated: checks.some((entry) => entry.name.includes('parse_once') && entry.status === 'ok'),
|
|
163
|
+
invalidJsonValidated: checks.some((entry) => entry.name.includes('invalid_json_status') && entry.status === 'ok'),
|
|
164
|
+
noEagerReadValidated: checks.some((entry) => entry.name === 'request_json_not_read_on_unrelated_effect_route' && entry.status === 'ok'),
|
|
165
|
+
noJsHandlerImports: checks.some((entry) => entry.name === 'request_json_module_has_no_js_handler_imports' && entry.status === 'ok'),
|
|
166
|
+
providerBehaviorImplemented: false,
|
|
167
|
+
compiledWasmRuntimeImplemented: errorDiagnostics.length === 0,
|
|
168
|
+
binaryRequestBodyParsing: false,
|
|
169
|
+
streamRequestBodyParsing: false,
|
|
170
|
+
diagnostics: diagnostics.length,
|
|
171
|
+
errorDiagnostics: errorDiagnostics.length,
|
|
172
|
+
errors: errorDiagnostics.length
|
|
173
|
+
},
|
|
174
|
+
generatedFiles: compiledHandlerFiles.map((file) => ({ file: file.file, bytes: file.bytes, sha256: file.sha256 })),
|
|
175
|
+
generatedOutputs: compiledHandlerOutputs.map((file) => ({ file: file.file, kind: file.kind, bytes: file.bytes, sha256: file.sha256 })),
|
|
176
|
+
diagnostics
|
|
177
|
+
}, cwd);
|
|
178
|
+
return { artifact, diagnostics, failures, files: compiledHandlerFiles, outputFiles: compiledHandlerOutputs.length ? compiledHandlerOutputs : (wasmFile ? [{ file: path.relative(cwd, wasmFile).replace(/\\/g, '/'), kind: 'compiled-handler-wasm', bytes: fs.statSync(wasmFile).size }] : []), wasmFile };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { phaseName, buildNodeRequestJsonBodyProof };
|