@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.
Files changed (38) hide show
  1. package/README.md +60 -1
  2. package/package.json +61 -6
  3. package/src/capabilities.js +56 -0
  4. package/src/compiler/assets-provider-proof.js +434 -0
  5. package/src/compiler/backend-json-request-body-proof.js +90 -0
  6. package/src/compiler/index.js +38 -0
  7. package/src/compiler/node-adapter.js +452 -0
  8. package/src/compiler/node-compiled-wasm-assets-lifecycle-proof.js +435 -0
  9. package/src/compiler/request-json-body-proof.js +181 -0
  10. package/src/compiler/route-handler-compiled-wasm-bridge.js +323 -0
  11. package/src/compiler/route-handler-effect-proof.js +215 -0
  12. package/src/compiler/route-handler-live-origin-proof.js +372 -0
  13. package/src/compiler/schema-decode-result-proof.js +180 -0
  14. package/src/compiler/schema-response-codec-proof.js +149 -0
  15. package/src/events/reference-adapter.js +145 -0
  16. package/src/index.js +57 -0
  17. package/src/javascript/assets-adapter.js +68 -0
  18. package/src/javascript/bindings-adapter.js +120 -0
  19. package/src/javascript/fetch-adapter.js +187 -0
  20. package/src/javascript/grip-broadcast.js +243 -0
  21. package/src/javascript/jwt-verifier.js +137 -0
  22. package/src/javascript/lifecycle.js +69 -0
  23. package/src/javascript/node-adapter.js +281 -0
  24. package/src/javascript/package-effects.js +78 -0
  25. package/src/javascript/runtime-host.js +165 -0
  26. package/src/javascript/source-package.js +439 -0
  27. package/src/javascript/support.js +205 -0
  28. package/src/javascript/target-support-policy.js +134 -0
  29. package/src/javascript/target.js +37 -0
  30. package/src/javascript/test-runtime.js +111 -0
  31. package/src/native/target.js +78 -0
  32. package/src/runtime/assets-provider.js +776 -0
  33. package/src/runtime/canonical-api-runtime.js +632 -0
  34. package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
  35. package/src/runtime/index.js +17 -0
  36. package/src/runtime/jwt-verifier.js +264 -0
  37. package/src/runtime/route-handler-effects.js +757 -0
  38. package/src/toolchain.js +258 -0
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ function loadHandlerEffectContracts() {
7
+ const local = path.resolve(__dirname, '../../../../wasm/packages/contracts/src/handler/effects.js');
8
+ if (fs.existsSync(local)) return require(local);
9
+ return require('@pulse-compute/wasm-contracts/handler/effects');
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
+ function loadRouteEffectsRuntime() {
22
+ try { return require('@pulse-compute/provider-node/runtime/route-handler-effects'); }
23
+ catch (error) {
24
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/provider-node')) return require('../runtime/route-handler-effects.js');
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ const handlerEffects = loadHandlerEffectContracts();
30
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadDiagnostics();
31
+ const { getDefaultArtifactsDir } = loadArtifactsDir();
32
+ const {
33
+ createNodeLiveOriginRouteHandlerEffectProvider,
34
+ headerValue,
35
+ defaultParamsForRoute = (route) => {
36
+ const params = {};
37
+ for (const match of String(route && route.path || '').matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) params[match[1]] = 'abc';
38
+ return params;
39
+ }
40
+ } = loadRouteEffectsRuntime();
41
+
42
+ const phaseName = 'node-compiled-wasm-route-handler-effect-bridge';
43
+ const COMPILED_RESULT_RESOLVE = 6;
44
+
45
+ function clone(value) {
46
+ if (Array.isArray(value)) return value.map(clone);
47
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
48
+ return value;
49
+ }
50
+ function stableSlash(value) { return String(value || '').replace(/\\/g, '/'); }
51
+ function normalizePlan(input) {
52
+ const plan = input && input.artifact && typeof input.artifact === 'object' ? input.artifact : input;
53
+ if (!plan || typeof plan !== 'object') return undefined;
54
+ return { ...plan, routes: Array.isArray(plan.routes) ? plan.routes.map(clone) : [], continuations: Array.isArray(plan.continuations) ? plan.continuations.map(clone) : [] };
55
+ }
56
+ function plannedEntries(plan) {
57
+ const entries = [];
58
+ for (const route of plan && plan.routes || []) {
59
+ for (const resolve of route.resolves || []) {
60
+ if (resolve && Array.isArray(resolve.effects) && resolve.effects.some((effect) => effect && effect.kind === 'backend-fetch')) entries.push({ route, resolve });
61
+ }
62
+ }
63
+ return entries;
64
+ }
65
+ function makeDiagnostic(code, message, severity, details, hint) {
66
+ return normalizeDiagnostic({ phase: phaseName, severity: severity || 'error', code, message, hint, details, loc: { file: '<node-compiled-wasm-route-handler-effect-bridge>' } });
67
+ }
68
+ function check(name, ok, details, failures, diagnostics) {
69
+ const entry = { name, status: ok ? 'ok' : 'error', details: sanitize(details || {}) };
70
+ if (!ok) {
71
+ failures.push(entry);
72
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeCompiledBridgeSmokeFailed, `Node compiled-Wasm route-effect bridge check failed: ${name}.`, 'error', entry.details));
73
+ }
74
+ return entry;
75
+ }
76
+ function stableHeaders(headers) {
77
+ const volatile = new Set(['date', 'connection', 'keep-alive', 'transfer-encoding']);
78
+ const entries = Array.isArray(headers) ? headers : Object.entries(headers || {});
79
+ return entries.map((entry) => {
80
+ if (Array.isArray(entry)) return [entry[0], entry[1]];
81
+ if (entry && typeof entry === 'object') return [entry.name || entry.key || '', entry.value !== undefined ? entry.value : ''];
82
+ return ['', ''];
83
+ }).map(([name, value]) => [String(name).toLowerCase(), String(value)]).filter(([name]) => name && !volatile.has(name)).sort(([a], [b]) => a.localeCompare(b));
84
+ }
85
+ function sanitize(value) {
86
+ if (Array.isArray(value)) return value.map(sanitize);
87
+ if (!value || typeof value !== 'object') return value;
88
+ const out = {};
89
+ for (const [key, entry] of Object.entries(value)) {
90
+ if (key === 'headers') out.headers = stableHeaders(entry);
91
+ else if (key === 'body') { out.body = String(entry || ''); out.bodyPreview = String(entry || '').slice(0, 240); }
92
+ else out[key] = sanitize(entry);
93
+ }
94
+ return out;
95
+ }
96
+ function findCompiledHandlerWasm(compiledHandlers, cwd, outDir, compiledHandlersCwd) {
97
+ const candidates = [];
98
+ for (const file of compiledHandlers && compiledHandlers.outputFiles || []) {
99
+ if (file && file.kind === 'compiled-handler-wasm' && file.file) candidates.push(file.file);
100
+ }
101
+ for (const file of candidates) {
102
+ const bases = [compiledHandlersCwd, cwd].filter(Boolean);
103
+ for (const base of bases) {
104
+ const abs = path.isAbsolute(file) ? file : path.resolve(base, file);
105
+ if (fs.existsSync(abs)) return abs;
106
+ }
107
+ }
108
+ const fallback = path.join(outDir || getDefaultArtifactsDir(cwd), 'generated', 'compiled-handlers', 'pulsewasm-compiled-handlers.wasm');
109
+ if (fs.existsSync(fallback)) return fallback;
110
+ return undefined;
111
+ }
112
+ function instantiateCompiledBridge(wasmFile) {
113
+ const wasmBytes = fs.readFileSync(wasmFile);
114
+ const module = new WebAssembly.Module(wasmBytes);
115
+ const imports = WebAssembly.Module.imports(module);
116
+ const importObject = { env: { abort() { throw new Error('AssemblyScript abort'); } } };
117
+ for (const imp of imports) {
118
+ if (!importObject[imp.module]) importObject[imp.module] = {};
119
+ if (!importObject[imp.module][imp.name]) importObject[imp.module][imp.name] = () => 0;
120
+ }
121
+ const instance = new WebAssembly.Instance(module, importObject);
122
+ const exports = instance.exports;
123
+ const encoder = new TextEncoder();
124
+ const decoder = new TextDecoder();
125
+ function memory() {
126
+ if (!exports.memory) throw new Error('Compiled handler bridge did not export memory.');
127
+ return exports.memory;
128
+ }
129
+ function writeUtf8(value) {
130
+ const bytes = encoder.encode(String(value ?? ''));
131
+ const ptr = exports.bridge_alloc_utf8(bytes.length || 1);
132
+ new Uint8Array(memory().buffer, ptr, bytes.length).set(bytes);
133
+ return { ptr, len: bytes.length };
134
+ }
135
+ function readUtf8(ptr, len) {
136
+ if (!ptr || !len) return '';
137
+ return decoder.decode(new Uint8Array(memory().buffer, ptr, len));
138
+ }
139
+ function setResolvedResponse(name, response) {
140
+ const kind = response.kind || (String(headerValue(response.headers, 'content-type') || '').includes('json') ? 'json-text' : 'text');
141
+ const n = writeUtf8(name);
142
+ const k = writeUtf8(kind);
143
+ const b = writeUtf8(response.body || '');
144
+ exports.bridge_set_resolved_response_utf8(n.ptr, n.len, Number(response.status || 0), k.ptr, k.len, b.ptr, b.len);
145
+ for (const [headerName, headerValueText] of stableHeaders(response.headers || [])) {
146
+ const hn = writeUtf8(headerName);
147
+ const hv = writeUtf8(headerValueText);
148
+ exports.bridge_set_resolved_header_utf8(n.ptr, n.len, hn.ptr, hn.len, hv.ptr, hv.len);
149
+ }
150
+ }
151
+ function readHeaderText(kind, index) {
152
+ const lenName = `bridge_result_header_${kind}_utf8_len`;
153
+ const ptrName = `bridge_result_header_${kind}_utf8_ptr`;
154
+ const len = typeof exports[lenName] === 'function' ? exports[lenName](index) : 0;
155
+ const ptr = typeof exports[ptrName] === 'function' ? exports[ptrName](index) : 0;
156
+ return readUtf8(ptr, len);
157
+ }
158
+ function readResultHeaders() {
159
+ const count = typeof exports.bridge_result_header_count === 'function' ? exports.bridge_result_header_count() : 0;
160
+ const headers = [];
161
+ for (let index = 0; index < count; index += 1) {
162
+ const name = readHeaderText('name', index);
163
+ const value = readHeaderText('value', index);
164
+ if (name) headers.push([name, value]);
165
+ }
166
+ return headers;
167
+ }
168
+ function setRequestBody(body) {
169
+ if (typeof exports.bridge_set_request_body_utf8 !== 'function') return false;
170
+ const b = writeUtf8(body || '');
171
+ exports.bridge_set_request_body_utf8(b.ptr, b.len);
172
+ return true;
173
+ }
174
+ function requestBodyReadCount() { return typeof exports.bridge_request_body_read_count === 'function' ? exports.bridge_request_body_read_count() : 0; }
175
+ function requestJsonParseCount() { return typeof exports.bridge_request_json_parse_count === 'function' ? exports.bridge_request_json_parse_count() : 0; }
176
+ function readResult() {
177
+ const len = exports.bridge_result_body_utf8_len ? exports.bridge_result_body_utf8_len() : 0;
178
+ const ptr = exports.bridge_result_body_utf8_ptr ? exports.bridge_result_body_utf8_ptr() : 0;
179
+ const kindCode = exports.bridge_result_kind ? exports.bridge_result_kind() : 0;
180
+ return { status: exports.bridge_result_status ? exports.bridge_result_status() : 0, kindCode, kind: kindCode === 2 ? 'json-text' : kindCode === 1 ? 'text' : kindCode === 4 ? 'empty' : kindCode === COMPILED_RESULT_RESOLVE ? 'resolve' : 'none', headers: readResultHeaders(), body: readUtf8(ptr, len) };
181
+ }
182
+ return { module, instance, exports, imports, wasmBytes, writeUtf8, setRequestBody, requestBodyReadCount, requestJsonParseCount, setResolvedResponse, readResult };
183
+ }
184
+ async function executeCompiledBoundary({ runtime, provider, entry, routeIndex, params, variant }) {
185
+ const route = entry.route;
186
+ const resolve = entry.resolve;
187
+ const runName = variant === 'missing' ? `bridge_run_effect_route_${routeIndex}_missing` : `bridge_run_effect_route_${routeIndex}`;
188
+ const run = runtime.exports[runName];
189
+ if (typeof run !== 'function') throw new Error(`Missing compiled bridge export ${runName}.`);
190
+ const pendingKind = run();
191
+ const boundaryIndex = typeof runtime.exports[`bridge_route_${routeIndex}_boundary_index`] === 'function' ? runtime.exports[`bridge_route_${routeIndex}_boundary_index`]() : 0;
192
+ const runtimeId = typeof runtime.exports[`bridge_route_${routeIndex}_runtime_id`] === 'function' ? runtime.exports[`bridge_route_${routeIndex}_runtime_id`]() : Number(route.runtimeId || 0);
193
+ if (runtime.exports.bridge_clear_resolved) runtime.exports.bridge_clear_resolved();
194
+ const mergedParams = { ...defaultParamsForRoute(route), ...params };
195
+ const resolved = {};
196
+ const diagnostics = [];
197
+ for (const effect of resolve.effects || []) {
198
+ if (!effect || effect.kind !== 'backend-fetch') continue;
199
+ const requestJsonText = sampleJsonBodyForEffect(effect);
200
+ const handle = await provider.executeFetch(effect, { route, params: mergedParams, requestJsonText });
201
+ const snapshot = handle.toJSON();
202
+ const name = effect.name || '$default';
203
+ diagnostics.push(...(snapshot.diagnostics || []));
204
+ resolved[name] = { effect: clone(effect), request: snapshot.request, response: { status: snapshot.status, ok: snapshot.ok, kind: snapshot.kind, headers: snapshot.headers, body: snapshot.body, diagnostics: snapshot.diagnostics || [] } };
205
+ runtime.setResolvedResponse(name, resolved[name].response);
206
+ }
207
+ const resumeStatus = runtime.exports.bridge_resume(runtimeId, boundaryIndex);
208
+ const finalResult = runtime.readResult();
209
+ return { pendingKind, boundaryIndex, runtimeId, resumeStatus, routeIndex, variant: variant || 'default', route: { routeId: route.routeId, method: route.method, path: route.path, handlerName: route.handlerName }, groupShape: resolve.groupShape, continuation: resolve.continuation && resolve.continuation.name, resolved, finalResult, diagnostics, lifecycle: { compiledWasmHandlerReturnedResolveRequest: pendingKind === COMPILED_RESULT_RESOLVE, nodeProviderExecutedLiveOriginFetch: true, hostWroteResolvedHandlesToWasm: true, compiledWasmContinuationReentered: true, finalResponseReadFromWasm: true, userAuthoredAsync: false, promises: false, asyncAwait: false, asyncify: false } };
210
+ }
211
+ function serializeExecution(execution) {
212
+ if (!execution) return undefined;
213
+ return { pendingKind: execution.pendingKind, boundaryIndex: execution.boundaryIndex, runtimeId: execution.runtimeId, resumeStatus: execution.resumeStatus, routeIndex: execution.routeIndex, variant: execution.variant, route: execution.route, groupShape: execution.groupShape, continuation: execution.continuation, resolved: Object.fromEntries(Object.entries(execution.resolved || {}).map(([name, value]) => [name, sanitize(value)])), finalResult: sanitize(execution.finalResult), lifecycle: execution.lifecycle, diagnostics: (execution.diagnostics || []).map((entry) => ({ code: entry.code, severity: entry.severity })) };
214
+ }
215
+ function sampleJsonBodyForEffect(effect) {
216
+ if (!effect || !effect.request || !(effect.request.bodyMode === 'json' || effect.request.json)) return undefined;
217
+ return '{"id":"compiled-created","name":"Compiled Created User","active":true}';
218
+ }
219
+ async function buildNodeCompiledRouteHandlerEffectBridge(options = {}) {
220
+ const cwd = options.cwd || process.cwd();
221
+ const outDir = options.outDir || getDefaultArtifactsDir(cwd);
222
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
223
+ const diagnostics = [];
224
+ const failures = [];
225
+ const checks = [];
226
+ const plan = normalizePlan(options.routeHandlerEffectPlan || options.plan || options.effectPlan);
227
+ if (!plan) diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeCompiledBridgePlanRequired, 'Node compiled-Wasm route-effect bridge requires route-handler-effect-plan.json.', 'error', { hint: 'Emit route-handler-effect-plan.json before requesting the compiled bridge proof.' }));
228
+ const entries = plannedEntries(plan || {});
229
+ if (plan && entries.length === 0) diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeCompiledBridgeNoPlannedEffects, 'Node compiled-Wasm route-effect bridge requires at least one planned ctx.resolve boundary.', 'error', { routes: (plan.routes || []).length }));
230
+ const wasmFile = findCompiledHandlerWasm(options.compiledHandlers, cwd, outDir, options.compiledHandlersCwd || options.sourceCwd);
231
+ if (!wasmFile) diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeCompiledBridgeAscMissing, 'Node compiled-Wasm route-effect bridge requires the compiled-handler Wasm artifact.', 'error', { hint: 'Enable emitCompiledHandlers together with the node compiled bridge proof.' }));
232
+
233
+ let runtime;
234
+ let originSummary = [];
235
+ const smoke = { results: {}, checks, failedChecks: failures };
236
+ if (wasmFile && diagnostics.filter((entry) => (entry.severity || 'error') === 'error').length === 0) {
237
+ try {
238
+ runtime = instantiateCompiledBridge(wasmFile);
239
+ const provider = createNodeLiveOriginRouteHandlerEffectProvider({
240
+ plan,
241
+ resolvedConfig: options.resolvedConfig,
242
+ liveOrigins: options.liveOrigins || options.origins,
243
+ defaultParams: options.params || { id: 'abc' },
244
+ mode: options.mode || 'compiled-wasm-live-origin-bridge',
245
+ redactOriginUrls: options.redactOriginUrls !== false
246
+ });
247
+ originSummary = Array.isArray(provider.originSummary) ? provider.originSummary.map(clone) : [];
248
+ diagnostics.push(...(provider.diagnostics || []));
249
+ for (let index = 0; index < entries.length; index += 1) {
250
+ const entry = entries[index];
251
+ const result = await executeCompiledBoundary({ runtime, provider, entry, routeIndex: index, params: options.params || { id: 'abc' } });
252
+ smoke.results[`route${index}`] = serializeExecution(result);
253
+ checks.push(check(`compiled_wasm_route_${index}_returned_resolve`, result.pendingKind === COMPILED_RESULT_RESOLVE && result.lifecycle.compiledWasmHandlerReturnedResolveRequest, { pendingKind: result.pendingKind, route: entry.route.path }, failures, diagnostics));
254
+ checks.push(check(`compiled_wasm_route_${index}_resumed_final_result`, result.finalResult.status >= 200 && result.finalResult.status < 300 && String(result.finalResult.body || '').length > 0, { finalResult: result.finalResult, route: entry.route.path }, failures, diagnostics));
255
+ checks.push(check(`compiled_wasm_route_${index}_final_response_headers`, Boolean(headerValue(result.finalResult.headers, 'content-type')), { finalResult: result.finalResult, route: entry.route.path }, failures, diagnostics));
256
+ const head = Object.values(result.resolved).find((value) => String(value.request && value.request.method).toUpperCase() === 'HEAD');
257
+ if (head) checks.push(check(`compiled_wasm_route_${index}_head_bodyless`, head.response.body === '' && Boolean(headerValue(head.response.headers, 'x-count')), { head: head.response }, failures, diagnostics));
258
+ }
259
+ const singleIndex = entries.findIndex((entry) => entry.resolve.groupShape === 'single');
260
+ if (singleIndex >= 0) {
261
+ const missing = await executeCompiledBoundary({ runtime, provider, entry: entries[singleIndex], routeIndex: singleIndex, params: options.missingParams || { id: 'missing' }, variant: 'missing' });
262
+ smoke.results.missing = serializeExecution(missing);
263
+ checks.push(check('compiled_wasm_missing_origin_404', missing.finalResult.status === 404, { finalResult: missing.finalResult }, failures, diagnostics));
264
+ }
265
+ checks.push(check('compiled_wasm_module_has_no_js_handler_imports', runtime.imports.filter((entry) => entry.module === 'pulsewasm_handlers').length === 0, { imports: runtime.imports }, failures, diagnostics));
266
+ } catch (error) {
267
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeCompiledBridgeSmokeFailed, 'Node compiled-Wasm route-effect bridge smoke execution failed.', 'error', { message: error && error.message ? error.message : String(error), stack: error && error.stack ? error.stack : undefined }));
268
+ }
269
+ }
270
+ const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
271
+ const singleValidated = entries.some((entry, index) => entry.resolve && entry.resolve.groupShape === 'single' && checks.some((check) => check.name === `compiled_wasm_route_${index}_resumed_final_result` && check.status === 'ok'));
272
+ const namedObjectValidated = entries.some((entry, index) => entry.resolve && entry.resolve.groupShape === 'named-object' && checks.some((check) => check.name === `compiled_wasm_route_${index}_resumed_final_result` && check.status === 'ok'));
273
+ const policy = handlerEffects.defaultRouteHandlerNodeCompiledEffectBridgePolicy();
274
+ const compiledHandlerFiles = Array.isArray(options.compiledHandlers && options.compiledHandlers.files) ? options.compiledHandlers.files : [];
275
+ const compiledHandlerOutputs = Array.isArray(options.compiledHandlers && options.compiledHandlers.outputFiles) ? options.compiledHandlers.outputFiles : [];
276
+ const artifact = normalizeArtifact({
277
+ version: handlerEffects.ROUTE_HANDLER_NODE_COMPILED_EFFECT_BRIDGE_VERSION,
278
+ generatedBy,
279
+ phase: handlerEffects.ROUTE_HANDLER_NODE_COMPILED_EFFECT_BRIDGE_PHASE,
280
+ artifact: handlerEffects.ROUTE_HANDLER_NODE_COMPILED_EFFECT_BRIDGE_ARTIFACT,
281
+ contractId: handlerEffects.ROUTE_HANDLER_EFFECT_CONTRACT_ID,
282
+ provider: 'node',
283
+ status: errorDiagnostics.length === 0 ? 'ok' : 'error',
284
+ sourcePlanVersion: plan && plan.version,
285
+ sourcePlanArtifact: plan && plan.artifact,
286
+ routeEffectPlanConnected: Boolean(plan),
287
+ compiledHandlersConnected: Boolean(options.compiledHandlers),
288
+ compiledHandlerPlanVersion: options.compiledHandlers && options.compiledHandlers.artifact && options.compiledHandlers.artifact.version,
289
+ providerBehaviorImplemented: true,
290
+ nodeProviderLiveOriginFetchImplemented: true,
291
+ compiledWasmEffectExecutionImplemented: errorDiagnostics.length === 0,
292
+ compiledWasmSuspendResumeBridgeImplemented: errorDiagnostics.length === 0,
293
+ compiledWasmContinuationReentryImplemented: errorDiagnostics.length === 0,
294
+ userAuthoredAsync: false,
295
+ promises: false,
296
+ asyncAwait: false,
297
+ asyncify: false,
298
+ scope: policy,
299
+ runtime: { version: handlerEffects.ROUTE_HANDLER_NODE_COMPILED_EFFECT_BRIDGE_RUNTIME_VERSION, package: '@pulse-compute/wasm-runtime-core-as/compiler/compiled-handlers', wasm: wasmFile ? stableSlash(path.relative(cwd, wasmFile)) : undefined },
300
+ proof: { package: '@pulse-compute/provider-node/compiler/route-handler-compiled-wasm-bridge', mode: options.mode || 'compiled-wasm-live-origin-bridge', actualNetworkFetch: true, compiledWasmBridgeModule: true, singleEffect: entries.some((entry) => entry.resolve && entry.resolve.groupShape === 'single'), namedObjectEffectGroup: entries.some((entry) => entry.resolve && entry.resolve.groupShape === 'named-object'), arrayEffectGroup: false, fixtureBackends: false, noJsHandlerImports: runtime ? runtime.imports.filter((entry) => entry.module === 'pulsewasm_handlers').length === 0 : false },
301
+ lifecycle: { ...handlerEffects.ROUTE_HANDLER_EFFECT_LIFECYCLE, compiledWasmHandlerReturnedResolveRequest: checks.some((entry) => entry.name.includes('returned_resolve') && entry.status === 'ok'), nodeProviderExecutedLiveOriginFetch: true, hostWroteResolvedHandlesToWasm: errorDiagnostics.length === 0, compiledWasmContinuationReentered: errorDiagnostics.length === 0, finalResponseReadFromWasm: errorDiagnostics.length === 0, languageLevelAsync: false, promises: false, asyncAwait: false, asyncify: false },
302
+ methods: [...handlerEffects.ROUTE_HANDLER_EFFECT_ALLOWED_METHODS],
303
+ groupShapes: clone(handlerEffects.ROUTE_HANDLER_EFFECT_GROUP_SHAPES),
304
+ responseHandleSurface: [...handlerEffects.ROUTE_HANDLER_CONTEXT_SURFACE.resolvedFetchResponse],
305
+ reservedResponseHandleSurface: [...handlerEffects.ROUTE_HANDLER_CONTEXT_SURFACE.reservedResolvedFetchResponse],
306
+ origins: originSummary,
307
+ entriesConsumed: entries.map((entry, index) => ({ routeIndex: index, routeId: entry.route.routeId, method: entry.route.method, path: entry.route.path, handlerName: entry.route.handlerName, groupShape: entry.resolve.groupShape, continuation: entry.resolve.continuation && entry.resolve.continuation.name, effects: (entry.resolve.effects || []).map((effect) => ({ name: effect.name || '$default', kind: effect.kind, backend: effect.backend, method: effect.request && effect.request.method })) })),
308
+ smoke,
309
+ summary: { routesWithEffects: entries.length, compiled: Boolean(wasmFile) && errorDiagnostics.length === 0, executed: checks.length > 0 && errorDiagnostics.length === 0, checks: checks.length, failedChecks: failures.length, singleValidated: entries.some((entry, index) => entry.resolve.groupShape === 'single' && checks.some((checkEntry) => checkEntry.name === `compiled_wasm_route_${index}_resumed_final_result` && checkEntry.status === 'ok')), namedObjectValidated: entries.some((entry, index) => entry.resolve.groupShape === 'named-object' && checks.some((checkEntry) => checkEntry.name === `compiled_wasm_route_${index}_resumed_final_result` && checkEntry.status === 'ok')), liveOriginNetworkFetchValidated: checks.some((entry) => entry.name.includes('resumed_final_result') && entry.status === 'ok'), liveOrigin404Validated: checks.some((entry) => entry.name === 'compiled_wasm_missing_origin_404' && entry.status === 'ok'), headValidated: checks.some((entry) => entry.name.includes('head_bodyless') && entry.status === 'ok'), finalResponseHeadersValidated: checks.some((entry) => entry.name.includes('final_response_headers') && entry.status === 'ok'), continuationResumeValidated: checks.some((entry) => entry.name.includes('resumed_final_result') && entry.status === 'ok'), noJsHandlerImports: checks.some((entry) => entry.name === 'compiled_wasm_module_has_no_js_handler_imports' && entry.status === 'ok'), providerBehaviorImplemented: true, compiledWasmEffectExecutionImplemented: errorDiagnostics.length === 0, compiledWasmSuspendResumeBridgeImplemented: errorDiagnostics.length === 0, diagnostics: diagnostics.length, errorDiagnostics: errorDiagnostics.length, errors: errorDiagnostics.length },
310
+ generatedFiles: compiledHandlerFiles.map((file) => ({ file: file.file, bytes: file.bytes, sha256: file.sha256 })),
311
+ generatedOutputs: compiledHandlerOutputs.map((file) => ({ file: file.file, kind: file.kind, bytes: file.bytes, sha256: file.sha256 })),
312
+ diagnostics
313
+ }, cwd);
314
+ return { artifact, diagnostics, failures, files: compiledHandlerFiles, outputFiles: compiledHandlerOutputs.length ? compiledHandlerOutputs : (wasmFile ? [{ file: stableSlash(path.relative(cwd, wasmFile)), kind: 'compiled-handler-wasm', bytes: fs.statSync(wasmFile).size }] : []), wasmFile };
315
+ }
316
+
317
+ module.exports = {
318
+ phaseName,
319
+ buildNodeCompiledRouteHandlerEffectBridge,
320
+ buildNodeCompiledWasmRouteHandlerEffectBridge: buildNodeCompiledRouteHandlerEffectBridge,
321
+ createCompiledHandlerBridgeRuntime: instantiateCompiledBridge,
322
+ findCompiledHandlerWasm
323
+ };
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ function loadHandlerEffectContracts() {
4
+ try {
5
+ return require('@pulse-compute/wasm-contracts/handler/effects');
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/handler/effects.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 handlerEffects = loadHandlerEffectContracts();
26
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadDiagnostics();
27
+ const { createNodeRouteHandlerEffectProvider, createDefaultBackendsFromPlan, headerValue } = require('../runtime/route-handler-effects.js');
28
+
29
+ const phaseName = 'node-route-handler-effect-proof';
30
+
31
+ function clone(value) {
32
+ if (Array.isArray(value)) return value.map(clone);
33
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
34
+ return value;
35
+ }
36
+
37
+ function normalizePlan(input) {
38
+ const plan = input && input.artifact && typeof input.artifact === 'object' ? input.artifact : input;
39
+ if (!plan || typeof plan !== 'object') return undefined;
40
+ return {
41
+ ...plan,
42
+ routes: Array.isArray(plan.routes) ? plan.routes.map(clone) : [],
43
+ continuations: Array.isArray(plan.continuations) ? plan.continuations.map(clone) : []
44
+ };
45
+ }
46
+
47
+ function plannedResolveEntries(plan) {
48
+ const entries = [];
49
+ for (const route of plan && plan.routes || []) {
50
+ for (const resolve of route.resolves || []) {
51
+ if (resolve && Array.isArray(resolve.effects) && resolve.effects.length > 0) entries.push({ route, resolve });
52
+ }
53
+ }
54
+ return entries;
55
+ }
56
+
57
+ function firstEntryByShape(entries, shape) {
58
+ return entries.find((entry) => entry.resolve && entry.resolve.groupShape === shape);
59
+ }
60
+
61
+ function makeDiagnostic(code, message, severity, details) {
62
+ return normalizeDiagnostic({
63
+ phase: phaseName,
64
+ severity: severity || 'error',
65
+ code,
66
+ message,
67
+ hint: details && details.hint,
68
+ details,
69
+ loc: { file: '<node-route-handler-effect-proof>' }
70
+ });
71
+ }
72
+
73
+ function check(name, condition, details, failures, diagnostics) {
74
+ const entry = { name, status: condition ? 'ok' : 'error', details: details || {} };
75
+ if (!condition) {
76
+ failures.push(entry);
77
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectSmokeFailed, `Node route-handler effect proof check failed: ${name}.`, 'error', { check: name, details }));
78
+ }
79
+ return entry;
80
+ }
81
+
82
+ function cloneEffectWithMethod(effect, method) {
83
+ const next = clone(effect);
84
+ next.request = { ...(next.request || {}), method };
85
+ return next;
86
+ }
87
+
88
+ function serializeExecution(execution) {
89
+ if (!execution) return undefined;
90
+ return {
91
+ status: execution.status,
92
+ routeId: execution.routeId,
93
+ route: clone(execution.route),
94
+ groupShape: execution.groupShape,
95
+ continuation: execution.continuation,
96
+ continuationPlanned: execution.continuationPlanned,
97
+ effectCount: execution.effectCount,
98
+ effects: clone(execution.effects || []),
99
+ resolved: clone(execution.resolved || {}),
100
+ finalResult: clone(execution.finalResult),
101
+ responseHandleSurface: clone(execution.responseHandleSurface),
102
+ continuationResultAccesses: clone(execution.continuationResultAccesses),
103
+ continuationResponseMethodCalls: clone(execution.continuationResponseMethodCalls),
104
+ lifecycle: clone(execution.lifecycle),
105
+ diagnostics: clone(execution.diagnostics || [])
106
+ };
107
+ }
108
+
109
+ function buildNodeRouteHandlerEffectProof(inputs = {}) {
110
+ const cwd = inputs.cwd || process.cwd();
111
+ const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
112
+ const plan = normalizePlan(inputs.routeHandlerEffectPlan || inputs.plan);
113
+ const diagnostics = [];
114
+ const failures = [];
115
+ const checks = [];
116
+
117
+ if (!plan) {
118
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectPlanRequired, 'Node route-handler effect proof requires a route-handler-effect-plan artifact.', 'error', { hint: 'Emit --route-handler-effects before requesting --node-route-handler-effect-proof.' }));
119
+ }
120
+
121
+ const entries = plannedResolveEntries(plan);
122
+ if (plan && entries.length === 0) {
123
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectNoPlannedEffects, 'Node route-handler effect proof requires at least one ctx.resolve(ctx.fetch(...), continuation) boundary.', 'error', { routes: Array.isArray(plan.routes) ? plan.routes.length : 0, hint: 'Add a lowerable ctx.resolve(ctx.fetch("backend", requestSpec), afterFetch) route handler.' }));
124
+ }
125
+
126
+ const backends = inputs.backends || createDefaultBackendsFromPlan(plan || {}, inputs.defaultParams || {});
127
+ const continuations = inputs.continuations || {
128
+ renderUser(ctx) {
129
+ const user = ctx.resolved();
130
+ if (!user.ok()) return ctx.result.text(user.status(), user.text());
131
+ return ctx.result.text(200, user.text(), { headers: { 'X-PulseWasm-Continuation': 'renderUser' } });
132
+ },
133
+ renderDashboard(ctx) {
134
+ const user = ctx.resolved('user');
135
+ const posts = ctx.resolved('posts');
136
+ return ctx.result.text(200, user.text() + posts.header('x-count'), { headers: { 'X-PulseWasm-Continuation': 'renderDashboard' } });
137
+ }
138
+ };
139
+ const provider = createNodeRouteHandlerEffectProvider({ plan, backends, continuations, mode: inputs.mode || 'local-route-effect-fixture' });
140
+ diagnostics.push(...(provider.diagnostics || []));
141
+
142
+ let singleExecution;
143
+ let namedExecution;
144
+ let unsupportedResult;
145
+ let missingBackendResult;
146
+
147
+ if (entries.length > 0) {
148
+ const single = firstEntryByShape(entries, 'single') || entries[0];
149
+ singleExecution = provider.executeResolve(single.route, single.resolve, inputs.params || { id: 'abc' });
150
+ const singleResolved = singleExecution.resolved && (singleExecution.resolved.$default || singleExecution.resolved[Object.keys(singleExecution.resolved || {})[0]]);
151
+ checks.push(check('single ctx.resolve(ctx.fetch(...), continuation) resolves one backend fetch handle', singleExecution.status === 'resolved' && Boolean(singleResolved) && singleResolved.status >= 200 && singleResolved.status < 300, { status: singleExecution.status, resolvedNames: Object.keys(singleExecution.resolved || {}) }, failures, diagnostics));
152
+ checks.push(check('continuation reads single fetch through ctx.resolved()', singleExecution.continuationPlanned === true && singleExecution.continuationResultAccesses.some((access) => access.name === '$default'), { continuation: singleExecution.continuation, resultAccesses: singleExecution.continuationResultAccesses }, failures, diagnostics));
153
+ checks.push(check('resolved fetch response exposes status ok header text jsonText json surface', singleExecution.responseHandleSurface.includes('status') && singleExecution.responseHandleSurface.includes('ok') && singleExecution.responseHandleSurface.includes('header') && singleExecution.responseHandleSurface.includes('text') && singleExecution.responseHandleSurface.includes('jsonText') && singleExecution.responseHandleSurface.includes('json'), { responseHandleSurface: singleExecution.responseHandleSurface }, failures, diagnostics));
154
+
155
+ const named = firstEntryByShape(entries, 'named-object');
156
+ if (named) {
157
+ namedExecution = provider.executeResolve(named.route, named.resolve, inputs.params || { id: 'abc' });
158
+ const names = Object.keys(namedExecution.resolved || {}).sort();
159
+ checks.push(check('named ctx.resolve({ name: ctx.fetch(...) }, continuation) resolves all named fetch handles', namedExecution.status === 'resolved' && names.includes('user') && names.includes('posts'), { status: namedExecution.status, names }, failures, diagnostics));
160
+ const head = Object.values(namedExecution.resolved || {}).find((entry) => entry.request && entry.request.method === 'HEAD');
161
+ checks.push(check('HEAD backend fetch resolves headers/status with empty body text', Boolean(head) && head.status >= 200 && head.status < 300 && head.body === '', { head }, failures, diagnostics));
162
+ } else {
163
+ checks.push(check('named object effect group is present in proof fixture', false, { entries: entries.map((entry) => ({ routeId: entry.route.routeId, groupShape: entry.resolve.groupShape })) }, failures, diagnostics));
164
+ }
165
+
166
+ const firstEffect = single.resolve.effects[0];
167
+ unsupportedResult = provider.executeFetch(cloneEffectWithMethod(firstEffect, 'PUT'), { route: single.route, params: { id: 'abc' } }).toJSON();
168
+ checks.push(check('unsupported backend fetch methods are diagnosed', unsupportedResult.status === 405 && unsupportedResult.diagnostics.some((entry) => entry.code === handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectUnsupportedMethod), { status: unsupportedResult.status, diagnostics: unsupportedResult.diagnostics.map((entry) => entry.code) }, failures, diagnostics));
169
+
170
+ const missingEffect = clone(firstEffect);
171
+ missingEffect.backend = 'missingBackend';
172
+ missingBackendResult = provider.executeFetch(missingEffect, { route: single.route, params: { id: 'abc' } }).toJSON();
173
+ checks.push(check('missing backend fixtures are diagnosed', missingBackendResult.status === 502 && missingBackendResult.diagnostics.some((entry) => entry.code === handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectBackendMissing), { status: missingBackendResult.status, diagnostics: missingBackendResult.diagnostics.map((entry) => entry.code) }, failures, diagnostics));
174
+
175
+ checks.push(check('resolved header passthrough is available to continuations', singleResolved && headerValue(singleResolved.headers, 'x-pulsewasm-backend') === firstEffect.backend, { headers: singleResolved && singleResolved.headers }, failures, diagnostics));
176
+ }
177
+
178
+ const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
179
+ const policy = handlerEffects.defaultRouteHandlerNodeEffectProofPolicy();
180
+ const effects = entries.flatMap((entry) => entry.resolve.effects || []);
181
+ const artifact = normalizeArtifact({
182
+ version: handlerEffects.ROUTE_HANDLER_NODE_EFFECT_PROOF_VERSION,
183
+ generatedBy,
184
+ phase: handlerEffects.ROUTE_HANDLER_NODE_EFFECT_PROOF_PHASE,
185
+ artifact: handlerEffects.ROUTE_HANDLER_NODE_EFFECT_PROOF_ARTIFACT,
186
+ contractId: handlerEffects.ROUTE_HANDLER_EFFECT_CONTRACT_ID,
187
+ provider: 'node',
188
+ status: errorDiagnostics.length === 0 ? 'ok' : 'error',
189
+ routeEffectPlanConnected: Boolean(plan),
190
+ loweringPlanConnected: Boolean(plan),
191
+ sourcePlanVersion: plan && plan.version,
192
+ sourcePlanArtifact: plan && plan.artifact,
193
+ providerBehaviorImplemented: true,
194
+ nodeProviderEffectExecutionImplemented: true,
195
+ compiledWasmEffectExecutionImplemented: false,
196
+ compiledWasmSuspendResumeBridgeImplemented: false,
197
+ scope: policy,
198
+ runtime: { version: handlerEffects.ROUTE_HANDLER_NODE_EFFECT_RUNTIME_VERSION, package: '@pulse-compute/provider-node/runtime/route-handler-effects' },
199
+ proof: { package: '@pulse-compute/provider-node/compiler/route-handler-effect-proof', mode: inputs.mode || 'local-route-effect-fixture', singleEffect: true, namedObjectEffectGroup: true, arrayEffectGroup: false, fixtureBackends: true, actualNetworkFetch: false },
200
+ lifecycle: { ...handlerEffects.ROUTE_HANDLER_EFFECT_LIFECYCLE, nodeProofHostEffectExecution: entries.length > 0, nodeProofContinuationReentryPlanned: entries.length > 0, languageLevelAsync: false, promises: false, asyncAwait: false, asyncify: false },
201
+ methods: [...handlerEffects.ROUTE_HANDLER_EFFECT_ALLOWED_METHODS],
202
+ groupShapes: clone(handlerEffects.ROUTE_HANDLER_EFFECT_GROUP_SHAPES),
203
+ responseHandleSurface: [...handlerEffects.ROUTE_HANDLER_CONTEXT_SURFACE.resolvedFetchResponse],
204
+ reservedResponseHandleSurface: [...handlerEffects.ROUTE_HANDLER_CONTEXT_SURFACE.reservedResolvedFetchResponse],
205
+ policy: { compilerOwnsProviderBehavior: false, compilerOrchestrationOnly: true, providerOwner: '@pulse-compute/provider-node', directProviderSmoke: true, localFixtureFetch: true, actualNetworkFetch: false, languageLevelAsync: false, promises: false, asyncAwait: false, asyncify: false, compiledWasmSuspendResumeBridge: false },
206
+ entriesConsumed: entries.map(({ route, resolve }) => ({ routeId: route.routeId, method: route.method, path: route.path, handlerName: route.handlerName, groupShape: resolve.groupShape, continuation: resolve.continuation && resolve.continuation.name, effects: (resolve.effects || []).map((effect) => ({ name: effect.name || '$default', kind: effect.kind, backend: effect.backend, method: effect.request && effect.request.method })) })),
207
+ backendFixtures: Object.keys(backends || {}).sort().map((backend) => ({ backend, responses: Object.keys(backends[backend] || {}).sort() })),
208
+ smoke: { checks, failedChecks: failures, results: { single: serializeExecution(singleExecution), namedObject: serializeExecution(namedExecution), unsupportedMethod: unsupportedResult, missingBackend: missingBackendResult } },
209
+ summary: { routesWithEffects: entries.length, effects: effects.length, backendFetchEffects: effects.filter((effect) => effect.kind === 'backend-fetch').length, checks: checks.length, failedChecks: failures.length, singleResolveValidated: checks.some((entry) => entry.name.startsWith('single ctx.resolve') && entry.status === 'ok'), singleValidated: checks.some((entry) => entry.name.startsWith('single ctx.resolve') && entry.status === 'ok'), namedObjectValidated: checks.some((entry) => entry.name.startsWith('named ctx.resolve') && entry.status === 'ok'), headValidated: checks.some((entry) => entry.name.startsWith('HEAD backend fetch') && entry.status === 'ok'), continuationReentryValidated: checks.some((entry) => entry.name.includes('continuation reads') && entry.status === 'ok'), unsupportedMethodValidated: checks.some((entry) => entry.name.startsWith('unsupported backend fetch methods') && entry.status === 'ok'), unsupportedMethodDiagnosticValidated: checks.some((entry) => entry.name.startsWith('unsupported backend fetch methods') && entry.status === 'ok'), missingBackendValidated: checks.some((entry) => entry.name.startsWith('missing backend fixtures') && entry.status === 'ok'), missingBackendDiagnosticValidated: checks.some((entry) => entry.name.startsWith('missing backend fixtures') && entry.status === 'ok'), responseHandleSurfaceValidated: checks.some((entry) => entry.name.startsWith('resolved fetch response exposes') && entry.status === 'ok'), providerBehaviorImplemented: true, compiledWasmEffectExecutionImplemented: false, diagnostics: diagnostics.length, errorDiagnostics: errorDiagnostics.length, errors: errorDiagnostics.length },
210
+ diagnostics
211
+ }, cwd);
212
+ return { artifact, diagnostics, failures, provider };
213
+ }
214
+
215
+ module.exports = { buildNodeRouteHandlerEffectProof, phaseName };