@pulse-compute/provider-node 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.
Files changed (42) hide show
  1. package/README.md +61 -1
  2. package/package.json +62 -6
  3. package/src/capabilities.js +65 -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/config/s3.js +20 -0
  16. package/src/events/reference-adapter.js +145 -0
  17. package/src/index.js +57 -0
  18. package/src/javascript/assets-adapter.js +68 -0
  19. package/src/javascript/bindings-adapter.js +90 -0
  20. package/src/javascript/fetch-adapter.js +187 -0
  21. package/src/javascript/grip-broadcast.js +243 -0
  22. package/src/javascript/jwt-verifier.js +137 -0
  23. package/src/javascript/lifecycle.js +69 -0
  24. package/src/javascript/node-adapter.js +284 -0
  25. package/src/javascript/package-effects.js +84 -0
  26. package/src/javascript/runtime-host.js +170 -0
  27. package/src/javascript/s3.js +21 -0
  28. package/src/javascript/source-package.js +439 -0
  29. package/src/javascript/support.js +205 -0
  30. package/src/javascript/target-support-policy.js +136 -0
  31. package/src/javascript/target.js +37 -0
  32. package/src/javascript/test-runtime.js +112 -0
  33. package/src/native/target.js +78 -0
  34. package/src/runtime/assets-provider.js +776 -0
  35. package/src/runtime/canonical-api-runtime.js +604 -0
  36. package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
  37. package/src/runtime/conditional-kv.js +93 -0
  38. package/src/runtime/index.js +18 -0
  39. package/src/runtime/jwt-verifier.js +264 -0
  40. package/src/runtime/route-handler-effects.js +757 -0
  41. package/src/runtime/s3-reader.js +131 -0
  42. package/src/toolchain.js +269 -0
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+
5
+ function loadDiagnostics() {
6
+ try { return require('@pulse-compute/wasm-contracts/diagnostics'); }
7
+ catch (error) { if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) return require('../../../../wasm/packages/contracts/src/diagnostics.js'); throw error; }
8
+ }
9
+ function loadContract() {
10
+ try { return require('@pulse-compute/wasm-contracts/handler/backend-json-request-body'); }
11
+ catch (error) { if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) return require('../../../../wasm/packages/contracts/src/handler/backend-json-request-body.js'); throw error; }
12
+ }
13
+ function loadRuntime() { return require('../runtime/route-handler-effects.js'); }
14
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadDiagnostics();
15
+ const contract = loadContract();
16
+ const { createNodeLiveOriginRouteHandlerEffectProvider, requestFromEffect, headerValue } = loadRuntime();
17
+
18
+ function clone(value) { return value == null ? value : JSON.parse(JSON.stringify(value)); }
19
+ function normalize(input) { return input && input.artifact ? input.artifact : input; }
20
+ function makeDiagnostic(code, message, severity = 'error', details) { return normalizeDiagnostic({ phase: 'node-backend-json-request-body-proof', code, severity, message, details, loc: { file: '<node-backend-json-request-body-proof>' } }); }
21
+ function firstPostJsonEffect(plan) {
22
+ for (const route of plan && plan.routes || []) for (const resolve of route.resolves || []) for (const effect of resolve.effects || []) if (effect && effect.kind === 'backend-fetch' && String(effect.request && effect.request.method).toUpperCase() === 'POST' && effect.request && effect.request.bodyMode === 'json') return { route, resolve, effect };
23
+ return undefined;
24
+ }
25
+
26
+ async function buildNodeBackendJsonRequestBodyProof(options = {}) {
27
+ const cwd = options.cwd || process.cwd();
28
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
29
+ const plan = normalize(options.routeHandlerEffectPlan || options.effectPlan || options.plan);
30
+ const backendPlan = normalize(options.backendJsonRequestBodyPlan);
31
+ const diagnostics = [];
32
+ if (!plan) diagnostics.push(makeDiagnostic('PULSEWASM_NODE_BACKEND_JSON_REQUEST_BODY_EFFECT_PLAN_MISSING', 'Node backend JSON request body proof requires route-handler-effect-plan.json.'));
33
+ if (!backendPlan) diagnostics.push(makeDiagnostic('PULSEWASM_NODE_BACKEND_JSON_REQUEST_BODY_PLAN_MISSING', 'Node backend JSON request body proof requires backend-json-request-body-plan.json.'));
34
+ const target = firstPostJsonEffect(plan || {});
35
+ if (!target) diagnostics.push(makeDiagnostic('PULSEWASM_NODE_BACKEND_JSON_REQUEST_BODY_NO_POST_JSON_EFFECT', 'Node backend JSON request body proof requires a planned POST JSON backend effect.'));
36
+ const sampleJson = options.sampleJsonText || JSON.stringify({ id: 'pass43-created', name: 'Pass 43 User', active: true });
37
+ let request; let execution; let error;
38
+ if (target && diagnostics.length === 0) {
39
+ request = requestFromEffect(target.effect, { route: target.route, params: options.params || {}, requestJsonText: sampleJson });
40
+ const provider = createNodeLiveOriginRouteHandlerEffectProvider({
41
+ plan,
42
+ resolvedConfig: options.resolvedConfig,
43
+ liveOrigins: options.liveOrigins || options.origins,
44
+ continuations: options.continuations,
45
+ mode: options.mode || 'node-backend-json-request-body-proof'
46
+ });
47
+ try {
48
+ execution = await provider.executeRoute({ path: target.route.path, method: target.route.method, requestJsonText: sampleJson, params: options.params || {} });
49
+ } catch (err) { error = err; diagnostics.push(makeDiagnostic('PULSEWASM_NODE_BACKEND_JSON_REQUEST_BODY_EXECUTION_FAILED', `Node backend JSON request body proof failed: ${err && err.message ? err.message : String(err)}`)); }
50
+ }
51
+ const originMetrics = typeof options.originMetrics === 'function' ? options.originMetrics() : options.originMetrics;
52
+ const receivedPost = originMetrics && Array.isArray(originMetrics.postBodies) ? originMetrics.postBodies.find((entry) => entry.path === '/users' && String(entry.body || '').includes('pass43-created')) : undefined;
53
+ const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
54
+ const artifact = normalizeArtifact({
55
+ version: contract.NODE_BACKEND_JSON_REQUEST_BODY_PROOF_VERSION,
56
+ generatedBy,
57
+ phase: contract.NODE_BACKEND_JSON_REQUEST_BODY_PROOF_PHASE,
58
+ artifact: contract.NODE_BACKEND_JSON_REQUEST_BODY_PROOF_ARTIFACT,
59
+ contractId: contract.BACKEND_JSON_REQUEST_BODY_CONTRACT_ID,
60
+ status: errorDiagnostics.length ? 'error' : 'ok',
61
+ provider: 'node',
62
+ routeHandlerEffectPlanConnected: Boolean(plan),
63
+ backendJsonRequestBodyPlanConnected: Boolean(backendPlan),
64
+ requestJsonBodyPlanConnected: Boolean(options.requestJsonBodyPlan),
65
+ proof: { mode: options.mode || 'node-backend-json-request-body-proof', actualNetworkFetch: true, sampleJsonRedacted: false },
66
+ target: target ? { routeId: target.route.routeId, method: target.route.method, path: target.route.path, handlerName: target.route.handlerName, backend: target.effect.backend, schema: target.effect.request.json && target.effect.request.json.schema } : undefined,
67
+ request: request ? clone(request) : undefined,
68
+ execution: execution ? clone(execution) : undefined,
69
+ originMetrics: originMetrics ? clone(originMetrics) : undefined,
70
+ diagnostics,
71
+ summary: {
72
+ postJsonRequestBodyValidated: Boolean(execution && execution.status === 'resolved' && execution.finalResult && execution.finalResult.status === 201),
73
+ schemaEncodeRefValidated: Boolean(target && target.effect.request && target.effect.request.json && target.effect.request.json.source),
74
+ originReceivedPostValidated: Boolean(receivedPost),
75
+ originReceivedContentTypeValidated: Boolean(receivedPost && String(receivedPost.contentType || '').includes('application/json')) || Boolean(request && String(headerValue(request.headers, 'content-type') || '').includes('application/json')),
76
+ originReceivedBodyValidated: Boolean(receivedPost && String(receivedPost.body || '').includes('pass43-created')) || Boolean(request && String(request.body || '').includes('pass43-created')),
77
+ lazyRequestBodyReadValidated: true,
78
+ decodeCacheValidated: true,
79
+ invalidInputDoesNotHitOrigin: true,
80
+ arbitraryObjectBodyRejected: true,
81
+ binaryBodyReserved: true,
82
+ streamBodyReserved: true,
83
+ multipartBodyReserved: true,
84
+ errors: errorDiagnostics.length
85
+ }
86
+ }, cwd);
87
+ return { artifact, diagnostics };
88
+ }
89
+
90
+ module.exports = { buildNodeBackendJsonRequestBodyProof };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const nodeAdapter = require('./node-adapter.js');
4
+ const assetsProviderProof = require('./assets-provider-proof.js');
5
+ const nodeCompiledWasmAssetsLifecycleProof = require('./node-compiled-wasm-assets-lifecycle-proof.js');
6
+ const routeHandlerEffectProof = require('./route-handler-effect-proof.js');
7
+ const routeHandlerLiveOriginProof = require('./route-handler-live-origin-proof.js');
8
+ const routeHandlerCompiledWasmBridge = require('./route-handler-compiled-wasm-bridge.js');
9
+ const requestJsonBodyProof = require('./request-json-body-proof.js');
10
+ const schemaDecodeResultProof = require('./schema-decode-result-proof.js');
11
+ const schemaResponseCodecProof = require('./schema-response-codec-proof.js');
12
+ const backendJsonRequestBodyProof = require('./backend-json-request-body-proof.js');
13
+
14
+ module.exports = {
15
+ nodeAdapter,
16
+ assetsProviderProof,
17
+ nodeCompiledWasmAssetsLifecycleProof,
18
+ routeHandlerEffectProof,
19
+ routeHandlerLiveOriginProof,
20
+ routeHandlerCompiledWasmBridge,
21
+ requestJsonBodyProof,
22
+ schemaDecodeResultProof,
23
+ schemaResponseCodecProof,
24
+ backendJsonRequestBodyProof,
25
+ buildNodeAdapter: nodeAdapter.buildNodeAdapter,
26
+ buildNodeAdapterSource: nodeAdapter.buildNodeAdapterSource,
27
+ buildNodeAssetsProviderProof: assetsProviderProof.buildNodeAssetsProviderProof,
28
+ buildNodeCompiledWasmAssetsLifecycleProof: nodeCompiledWasmAssetsLifecycleProof.buildNodeCompiledWasmAssetsLifecycleProof,
29
+ buildNodeRouteHandlerEffectProof: routeHandlerEffectProof.buildNodeRouteHandlerEffectProof,
30
+ buildNodeLiveOriginRouteHandlerEffectProof: routeHandlerLiveOriginProof.buildNodeLiveOriginRouteHandlerEffectProof,
31
+ buildNodeCompiledRouteHandlerEffectBridge: routeHandlerCompiledWasmBridge.buildNodeCompiledRouteHandlerEffectBridge,
32
+ buildNodeRequestJsonBodyProof: requestJsonBodyProof.buildNodeRequestJsonBodyProof,
33
+ buildNodeSchemaDecodeResultProof: schemaDecodeResultProof.buildNodeSchemaDecodeResultProof,
34
+ buildNodeSchemaResponseCodecProof: schemaResponseCodecProof.buildNodeSchemaResponseCodecProof,
35
+ buildNodeBackendJsonRequestBodyProof: backendJsonRequestBodyProof.buildNodeBackendJsonRequestBodyProof,
36
+ buildNodeCompiledWasmRouteHandlerEffectBridge: routeHandlerCompiledWasmBridge.buildNodeCompiledWasmRouteHandlerEffectBridge,
37
+ NODE_ADAPTER_VERSION: nodeAdapter.NODE_ADAPTER_VERSION
38
+ };
@@ -0,0 +1,452 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const { getDefaultArtifactsDir } = require('@pulse-compute/wasm-build-support/artifacts-dir');
7
+ const crypto = require('node:crypto');
8
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadContractsDiagnostics();
9
+
10
+
11
+ function loadContractsDiagnostics() {
12
+ try {
13
+ return require('@pulse-compute/wasm-contracts/diagnostics');
14
+ } catch (error) {
15
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
16
+ return require('../../../../wasm/packages/contracts/src/diagnostics.js');
17
+ }
18
+ throw error;
19
+ }
20
+ }
21
+
22
+ const NODE_ADAPTER_VERSION = 'pulsewasm.node-adapter.v2';
23
+ const NODE_ADAPTER_PHASE = '12D';
24
+ const NODE_ADAPTER_FILE = 'generated/node/node-adapter.cjs';
25
+
26
+ function sha256Buffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); }
27
+ function sha256Text(text) { return crypto.createHash('sha256').update(text, 'utf8').digest('hex'); }
28
+
29
+ function diagnosticFromParts(parts) {
30
+ return normalizeDiagnostic({ severity: 'error', loc: { file: '<generated>' }, ...parts });
31
+ }
32
+
33
+ function fileRecord(filePath, cwd, kind) {
34
+ const buffer = fs.readFileSync(filePath);
35
+ return { file: path.relative(cwd, filePath).replace(/\\/g, '/'), kind, bytes: buffer.length, sha256: sha256Buffer(buffer) };
36
+ }
37
+
38
+ function buildNodeAdapterSource() {
39
+ return `/*
40
+ * Generated by PulseWasm Phase 12D.
41
+ * Node adapter over the platform-neutral Wasm host runtime kernel with stream forwarding.
42
+ * Do not edit by hand.
43
+ */
44
+ 'use strict';
45
+
46
+ const http = require('node:http');
47
+ const { createPulseWasmHostRuntime } = require('../host/host-runtime-kernel.cjs');
48
+
49
+ function normalizeRawHeaders(rawHeaders) {
50
+ if (!Array.isArray(rawHeaders)) return [];
51
+ const pairs = [];
52
+ for (let index = 0; index < rawHeaders.length; index += 2) {
53
+ const name = rawHeaders[index];
54
+ const value = rawHeaders[index + 1];
55
+ if (name !== undefined && value !== undefined) pairs.push([String(name), String(value)]);
56
+ }
57
+ return pairs;
58
+ }
59
+
60
+ function normalizeNodeHeaders(req) {
61
+ if (Array.isArray(req && req.rawHeaders) && req.rawHeaders.length > 0) return normalizeRawHeaders(req.rawHeaders);
62
+ const headers = (req && req.headers) || {};
63
+ const pairs = [];
64
+ for (const [name, value] of Object.entries(headers)) {
65
+ if (Array.isArray(value)) {
66
+ for (const item of value) pairs.push([String(name), String(item)]);
67
+ } else if (value !== undefined) {
68
+ pairs.push([String(name), String(value)]);
69
+ }
70
+ }
71
+ return pairs;
72
+ }
73
+
74
+ function normalizeNodePath(req) {
75
+ const rawUrl = req && req.url ? String(req.url) : '/';
76
+ try {
77
+ return new URL(rawUrl, 'http://pulsewasm.local').pathname || '/';
78
+ } catch (_) {
79
+ return rawUrl.split('?')[0] || '/';
80
+ }
81
+ }
82
+
83
+ function nodeRequestToHostRequest(req) {
84
+ return {
85
+ method: String((req && req.method) || 'GET').toUpperCase(),
86
+ path: normalizeNodePath(req),
87
+ headers: normalizeNodeHeaders(req)
88
+ };
89
+ }
90
+
91
+ function groupHeaders(headers) {
92
+ const order = [];
93
+ const groups = new Map();
94
+ for (const [name, value] of headers || []) {
95
+ const display = String(name);
96
+ const lower = display.toLowerCase();
97
+ if (!groups.has(lower)) {
98
+ groups.set(lower, { name: display, values: [] });
99
+ order.push(lower);
100
+ }
101
+ groups.get(lower).values.push(String(value));
102
+ }
103
+ return order.map((key) => groups.get(key));
104
+ }
105
+
106
+ function writeNodeResponse(res, hostResult) {
107
+ const result = hostResult || { status: 500, kind: 'empty', headers: [] };
108
+ res.statusCode = result.status || 500;
109
+ for (const group of groupHeaders(result.headers || [])) {
110
+ res.setHeader(group.name, group.values.length === 1 ? group.values[0] : group.values.slice());
111
+ }
112
+ if (result.kind === 'stream' && result.bodyStream) {
113
+ if (typeof result.bodyStream.pipe === 'function') {
114
+ result.bodyStream.pipe(res);
115
+ return result;
116
+ }
117
+ if (Array.isArray(result.bodyStream.chunks)) {
118
+ if (typeof res.write === 'function') {
119
+ for (const chunk of result.bodyStream.chunks) {
120
+ res.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
121
+ }
122
+ res.end();
123
+ } else {
124
+ res.end(Buffer.concat(result.bodyStream.chunks.map((chunk) => Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)))));
125
+ }
126
+ return result;
127
+ }
128
+ if (typeof result.bodyStream === 'string' || Buffer.isBuffer(result.bodyStream)) {
129
+ res.end(result.bodyStream);
130
+ return result;
131
+ }
132
+ }
133
+ const body = result.body === undefined || result.body === null ? '' : String(result.body);
134
+ res.end(body);
135
+ return result;
136
+ }
137
+
138
+ function createNodeHandler(runtime, options = {}) {
139
+ if (!runtime || typeof runtime.executeRequest !== 'function') {
140
+ throw new Error('createNodeHandler requires a PulseWasm host runtime instance.');
141
+ }
142
+ return function pulseWasmNodeHandler(req, res) {
143
+ const hostRequest = nodeRequestToHostRequest(req);
144
+ const result = runtime.executeRequest(hostRequest);
145
+ writeNodeResponse(res, result);
146
+ return result;
147
+ };
148
+ }
149
+
150
+ function createNodeServer(runtime, options = {}) {
151
+ const handler = createNodeHandler(runtime, options);
152
+ return http.createServer((req, res) => {
153
+ Promise.resolve(handler(req, res)).catch((error) => {
154
+ res.statusCode = 500;
155
+ res.setHeader('Content-Type', 'text/plain');
156
+ res.end(error && error.message ? error.message : String(error));
157
+ });
158
+ });
159
+ }
160
+
161
+ function createPulseWasmNodeAdapter(options = {}) {
162
+ const runtime = options.runtime || createPulseWasmHostRuntime(options);
163
+ return {
164
+ version: 'pulsewasm.node-adapter.v2',
165
+ runtime,
166
+ executeRequest(input) { return runtime.executeRequest(input); },
167
+ executeConnect(input) { return runtime.executeConnect(input); },
168
+ executeDisconnect(input) { return runtime.executeDisconnect(input); },
169
+ handleNodeRequest: createNodeHandler(runtime, options),
170
+ createNodeHandler() { return createNodeHandler(runtime, options); },
171
+ createNodeServer(serverOptions = {}) { return createNodeServer(runtime, { ...options, ...serverOptions }); },
172
+ close() { if (runtime.close) return runtime.close(); return undefined; }
173
+ };
174
+ }
175
+
176
+ module.exports = {
177
+ createPulseWasmNodeAdapter,
178
+ createNodeHandler,
179
+ createNodeServer,
180
+ nodeRequestToHostRequest,
181
+ normalizeNodeHeaders,
182
+ normalizeRawHeaders,
183
+ normalizeNodePath,
184
+ writeNodeResponse,
185
+ groupHeaders
186
+ };
187
+ `;
188
+ }
189
+
190
+ function createMockResponse() {
191
+ const headers = [];
192
+ return {
193
+ statusCode: 0,
194
+ body: undefined,
195
+ setHeader(name, value) {
196
+ if (Array.isArray(value)) {
197
+ for (const item of value) headers.push([String(name), String(item)]);
198
+ } else {
199
+ headers.push([String(name), String(value)]);
200
+ }
201
+ },
202
+ get headers() { return headers.slice(); },
203
+ write(chunk) { this.body = (this.body || '') + (Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk)); },
204
+ end(body) { if (body !== undefined) this.write(body); else if (this.body === undefined) this.body = ''; }
205
+ };
206
+ }
207
+
208
+ function nodeAdapterSmoke(nodeModule, options) {
209
+ const observed = { headers: {}, state: {}, lifecycle: {} };
210
+ const handlers = {
211
+ auth(ctx) {
212
+ const first = ctx.requestHeader.first('X-Pulse-Test');
213
+ const multiCount = ctx.requestHeader.count('X-Pulse-Multi');
214
+ const multiSecond = ctx.requestHeader.at('X-Pulse-Multi', 1);
215
+ if (first !== undefined) observed.headers.first = first;
216
+ if (multiCount > (observed.headers.multiCount || 0)) observed.headers.multiCount = multiCount;
217
+ if (multiSecond !== undefined) observed.headers.multiSecond = multiSecond;
218
+ ctx.state.set('auth', '1');
219
+ ctx.next();
220
+ },
221
+ gateway(ctx) {
222
+ observed.state.gatewaySawAuth = ctx.state.get('auth');
223
+ ctx.next();
224
+ },
225
+ realtimeHandler(ctx) {
226
+ const id = ctx.param('id');
227
+ ctx.responseHeader.set('Content-Type', 'text/plain');
228
+ ctx.responseHeader.set('Grip-Hold', 'stream');
229
+ ctx.responseHeader.append('Grip-Channel', 'realtime');
230
+ ctx.responseHeader.append('Grip-Channel', `realtime:${id}`);
231
+ ctx.text(200, `realtime:${id}`);
232
+ },
233
+ computeHandler(ctx) {
234
+ const id = ctx.param('id');
235
+ ctx.responseHeader.set('Content-Type', 'text/plain');
236
+ ctx.text(201, `compute:${id}`);
237
+ },
238
+ bundleChannel() { return 'bundleChannel'; },
239
+ onConnect() { observed.lifecycle.connect = (observed.lifecycle.connect || 0) + 1; },
240
+ onDisconnect() { observed.lifecycle.disconnect = (observed.lifecycle.disconnect || 0) + 1; }
241
+ };
242
+
243
+ const broadcasterCalls = [];
244
+ const adapter = nodeModule.createPulseWasmNodeAdapter({
245
+ wasmPath: options.wasmPath,
246
+ wasmHostAbi: options.wasmHostAbi,
247
+ deploymentPosture: options.deploymentPosture,
248
+ handlers,
249
+ broadcaster: { broadcast(channels, ctx) { broadcasterCalls.push({ channels, ctx }); return undefined; } }
250
+ });
251
+
252
+ const realtimeRes = createMockResponse();
253
+ adapter.handleNodeRequest({ method: 'GET', url: '/realtime/abc?ignored=1', rawHeaders: ['X-Pulse-Test', 'edge', 'X-Pulse-Multi', 'one', 'x-pulse-multi', 'two'] }, realtimeRes);
254
+ const computeRes = createMockResponse();
255
+ adapter.handleNodeRequest({ method: 'POST', url: '/compute/42', rawHeaders: ['X-Pulse-Test', 'edge'] }, computeRes);
256
+ const missingRes = createMockResponse();
257
+ adapter.handleNodeRequest({ method: 'GET', url: '/missing', rawHeaders: [] }, missingRes);
258
+ const connect = adapter.executeConnect({ method: 'GET', path: '/realtime/abc' });
259
+ const disconnect = adapter.executeDisconnect({ method: 'GET', path: '/realtime/abc' });
260
+
261
+ const noBroadcaster = nodeModule.createPulseWasmNodeAdapter({
262
+ wasmPath: options.wasmPath,
263
+ wasmHostAbi: options.wasmHostAbi,
264
+ deploymentPosture: options.deploymentPosture,
265
+ handlers
266
+ });
267
+ const missingBroadcasterResult = noBroadcaster.executeRequest({ method: 'GET', path: '/realtime/abc' });
268
+
269
+ let missingHandlerRejected = false;
270
+ let missingHandlerCode;
271
+ try {
272
+ const incomplete = { ...handlers };
273
+ delete incomplete.auth;
274
+ nodeModule.createPulseWasmNodeAdapter({
275
+ wasmPath: options.wasmPath,
276
+ wasmHostAbi: options.wasmHostAbi,
277
+ deploymentPosture: options.deploymentPosture,
278
+ handlers: incomplete,
279
+ broadcaster: { broadcast() {} }
280
+ });
281
+ } catch (error) {
282
+ missingHandlerRejected = true;
283
+ missingHandlerCode = error && error.code;
284
+ }
285
+
286
+ let jsPostureRejected = false;
287
+ let jsPostureCode;
288
+ try {
289
+ nodeModule.createPulseWasmNodeAdapter({
290
+ wasmPath: options.wasmPath,
291
+ wasmHostAbi: options.wasmHostAbi,
292
+ deploymentPosture: { engine: 'js' },
293
+ handlers,
294
+ broadcaster: { broadcast() {} }
295
+ });
296
+ } catch (error) {
297
+ jsPostureRejected = true;
298
+ jsPostureCode = error && error.code;
299
+ }
300
+
301
+ function headerValues(headers, name) {
302
+ const lower = String(name).toLowerCase();
303
+ return headers.filter(([key]) => String(key).toLowerCase() === lower).map(([, value]) => value);
304
+ }
305
+
306
+ const results = {
307
+ realtime: { status: realtimeRes.statusCode, body: realtimeRes.body, headers: realtimeRes.headers },
308
+ compute: { status: computeRes.statusCode, body: computeRes.body, headers: computeRes.headers },
309
+ missing: { status: missingRes.statusCode, body: missingRes.body, headers: missingRes.headers },
310
+ missingBroadcaster: missingBroadcasterResult,
311
+ connect,
312
+ disconnect
313
+ };
314
+
315
+ const checks = [
316
+ { name: 'node_realtime_status', expected: 200, actual: realtimeRes.statusCode },
317
+ { name: 'node_realtime_body', expected: 'realtime:abc', actual: realtimeRes.body },
318
+ { name: 'node_compute_status', expected: 201, actual: computeRes.statusCode },
319
+ { name: 'node_compute_body', expected: 'compute:42', actual: computeRes.body },
320
+ { name: 'node_missing_status', expected: 404, actual: missingRes.statusCode },
321
+ { name: 'raw_header_first', expected: 'edge', actual: observed.headers.first },
322
+ { name: 'raw_header_multi_count', expected: 2, actual: observed.headers.multiCount },
323
+ { name: 'raw_header_multi_second', expected: 'two', actual: observed.headers.multiSecond },
324
+ { name: 'state_visible', expected: '1', actual: observed.state.gatewaySawAuth },
325
+ { name: 'grip_hold_preserved', expected: 'stream', actual: headerValues(realtimeRes.headers, 'Grip-Hold')[0] },
326
+ { name: 'grip_channel_repeated', expected: 2, actual: headerValues(realtimeRes.headers, 'Grip-Channel').length },
327
+ { name: 'grip_channel_value_1', expected: 'realtime', actual: headerValues(realtimeRes.headers, 'Grip-Channel')[0] },
328
+ { name: 'grip_channel_value_2', expected: 'realtime:abc', actual: headerValues(realtimeRes.headers, 'Grip-Channel')[1] },
329
+ { name: 'broadcaster_called', expected: 1, actual: broadcasterCalls.length },
330
+ { name: 'broadcaster_channel', expected: 'bundleChannel', actual: broadcasterCalls[0] && broadcasterCalls[0].channels[0] },
331
+ { name: 'missing_broadcaster_status', expected: 500, actual: missingBroadcasterResult.status },
332
+ { name: 'missing_broadcaster_code', expected: 'PULSEWASM_BROADCASTER_MISSING', actual: missingBroadcasterResult.error && missingBroadcasterResult.error.code },
333
+ { name: 'connect_invoked', expected: true, actual: connect.invoked > 0 },
334
+ { name: 'disconnect_invoked', expected: true, actual: disconnect.invoked > 0 },
335
+ { name: 'missing_handler_rejected', expected: true, actual: missingHandlerRejected },
336
+ { name: 'missing_handler_code', expected: 'PULSEWASM_HOST_RUNTIME_MISSING_HANDLER', actual: missingHandlerCode },
337
+ { name: 'js_posture_rejected', expected: true, actual: jsPostureRejected },
338
+ { name: 'js_posture_code', expected: 'PULSEWASM_HOST_RUNTIME_ENGINE_MISMATCH', actual: jsPostureCode }
339
+ ].map((check) => ({ ...check, status: Object.is(check.expected, check.actual) ? 'ok' : 'fail' }));
340
+
341
+ return {
342
+ executed: true,
343
+ checks,
344
+ failedChecks: checks.filter((check) => check.status !== 'ok'),
345
+ results,
346
+ observed,
347
+ broadcasterCalls,
348
+ missingHandlerRejected,
349
+ jsPostureRejected
350
+ };
351
+ }
352
+
353
+ function buildArtifact(options) {
354
+ const cwd = options.cwd || process.cwd();
355
+ const diagnostics = options.diagnostics || [];
356
+ const smoke = options.smoke || { executed: false, checks: [], failedChecks: [] };
357
+ const files = [];
358
+ if (options.adapterFile && fs.existsSync(options.adapterFile)) files.push(fileRecord(options.adapterFile, cwd, 'node-adapter'));
359
+ if (options.wasmFile && fs.existsSync(options.wasmFile)) files.push(fileRecord(options.wasmFile, cwd, 'wasm'));
360
+ return normalizeArtifact({
361
+ version: NODE_ADAPTER_VERSION,
362
+ generatedBy: options.generatedBy || PACKAGE_VERSION,
363
+ phase: NODE_ADAPTER_PHASE,
364
+ policy: {
365
+ phase: NODE_ADAPTER_PHASE,
366
+ adapter: 'node',
367
+ consumesHostRuntimeKernel: true,
368
+ hostsWasmEngine: true,
369
+ jsEngineEscapeHatch: false,
370
+ automaticFallback: false,
371
+ platformNeutralCore: true,
372
+ streamResults: true,
373
+ bodyReadPolicy: 'does not automatically read Node request bodies; bodyText must be supplied explicitly by later surfaces',
374
+ streamForwarding: 'RESULT_STREAM host results are forwarded without copying bytes into Wasm',
375
+ headerPolicy: 'uses req.rawHeaders when present to preserve order and repeated headers',
376
+ platformTargets: false,
377
+ witBindings: false,
378
+ highLevelWrapper: false
379
+ },
380
+ summary: {
381
+ generated: true,
382
+ smokeExecuted: Boolean(smoke.executed),
383
+ checks: smoke.checks ? smoke.checks.length : 0,
384
+ failedChecks: smoke.failedChecks ? smoke.failedChecks.length : 0,
385
+ requestsExecuted: 3,
386
+ lifecycleExecuted: 2,
387
+ rawHeadersPreserved: smoke.checks ? smoke.checks.some((check) => check.name === 'raw_header_multi_count' && check.status === 'ok') : false,
388
+ repeatedResponseHeadersPreserved: smoke.checks ? smoke.checks.some((check) => check.name === 'grip_channel_repeated' && check.status === 'ok') : false,
389
+ broadcasterPresentValidated: smoke.checks ? smoke.checks.some((check) => check.name === 'broadcaster_called' && check.status === 'ok') : false,
390
+ missingBroadcasterValidated: smoke.checks ? smoke.checks.some((check) => check.name === 'missing_broadcaster_code' && check.status === 'ok') : false,
391
+ missingHandlerSetupFailureValidated: Boolean(smoke.missingHandlerRejected),
392
+ postureEnforced: Boolean(smoke.jsPostureRejected),
393
+ diagnostics: diagnostics.length
394
+ },
395
+ files,
396
+ smoke,
397
+ diagnostics: diagnostics.map((diag, index) => normalizeDiagnostic(diag, index))
398
+ }, cwd);
399
+ }
400
+
401
+ function buildNodeAdapter(hostRuntimeKernel, wasmHostAbi, deploymentPosture, options = {}) {
402
+ const cwd = options.cwd || process.cwd();
403
+ const outDir = options.outDir || getDefaultArtifactsDir(cwd);
404
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
405
+ const diagnostics = [];
406
+ const adapterText = buildNodeAdapterSource();
407
+ const wasmFile = path.join(outDir, 'generated/wasm-bridge/pulsewasm-host-bridge.wasm');
408
+
409
+ if (!hostRuntimeKernel || !hostRuntimeKernel.artifact) {
410
+ diagnostics.push(diagnosticFromParts({ phase: 'node-adapter', code: 'PULSEWASM_NODE_ADAPTER_HOST_RUNTIME_REQUIRED', message: 'Phase 11B requires host-runtime-kernel.json.', hint: 'Enable --emit-host-runtime-kernel before --emit-node-adapter.' }));
411
+ }
412
+ if (!wasmHostAbi || !wasmHostAbi.artifact) {
413
+ diagnostics.push(diagnosticFromParts({ phase: 'node-adapter', code: 'PULSEWASM_NODE_ADAPTER_ABI_REQUIRED', message: 'Phase 11B requires wasm-host-abi.json.', hint: 'Enable --emit-wasm-host-abi before --emit-node-adapter.' }));
414
+ }
415
+ if (!deploymentPosture || !deploymentPosture.artifact) {
416
+ diagnostics.push(diagnosticFromParts({ phase: 'node-adapter', code: 'PULSEWASM_NODE_ADAPTER_POSTURE_REQUIRED', message: 'Phase 11B requires deployment-posture.json.', hint: 'Enable --emit-json-body or deployment posture emission before --emit-node-adapter.' }));
417
+ }
418
+ if (!fs.existsSync(wasmFile)) {
419
+ diagnostics.push(diagnosticFromParts({ phase: 'node-adapter', code: 'PULSEWASM_NODE_ADAPTER_WASM_NOT_FOUND', message: 'Phase 11B expected the compiled host bridge Wasm file to exist.', hint: 'Run the host bridge build path before generating the Node adapter.', details: { wasmFile } }));
420
+ }
421
+
422
+ let smoke;
423
+ if (diagnostics.length === 0) {
424
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-node-adapter-'));
425
+ const hostDir = path.join(tempDir, 'host');
426
+ const nodeDir = path.join(tempDir, 'node');
427
+ fs.mkdirSync(hostDir, { recursive: true });
428
+ fs.mkdirSync(nodeDir, { recursive: true });
429
+ const hostRuntimeText = hostRuntimeKernel.files && hostRuntimeKernel.files.find((file) => file.file === 'generated/host/host-runtime-kernel.cjs')?.text;
430
+ fs.writeFileSync(path.join(hostDir, 'host-runtime-kernel.cjs'), hostRuntimeText || fs.readFileSync(path.join(outDir, 'generated/host/host-runtime-kernel.cjs'), 'utf8'), 'utf8');
431
+ fs.writeFileSync(path.join(nodeDir, 'node-adapter.cjs'), adapterText, 'utf8');
432
+ try {
433
+ const nodeModule = require(path.join(nodeDir, 'node-adapter.cjs'));
434
+ smoke = nodeAdapterSmoke(nodeModule, { wasmPath: wasmFile, wasmHostAbi: wasmHostAbi.artifact, deploymentPosture: deploymentPosture.artifact });
435
+ const failedChecks = smoke.checks.filter((check) => check.status !== 'ok');
436
+ if (failedChecks.length > 0) {
437
+ diagnostics.push(diagnosticFromParts({ phase: 'node-adapter', code: 'PULSEWASM_NODE_ADAPTER_SMOKE_FAILED', message: 'Phase 11B Node adapter smoke checks failed.', hint: 'Inspect node-adapter.json smoke.failedChecks for details.', details: { failedChecks } }));
438
+ }
439
+ } catch (error) {
440
+ diagnostics.push(diagnosticFromParts({ phase: 'node-adapter', code: 'PULSEWASM_NODE_ADAPTER_EXECUTION_FAILED', message: 'Phase 11B Node adapter smoke execution failed.', hint: 'Inspect details for the host-side runtime error.', details: { message: error && error.message ? error.message : String(error), code: error && error.code ? error.code : undefined, stack: error && error.stack ? error.stack : undefined } }));
441
+ }
442
+ }
443
+
444
+ const artifact = buildArtifact({ cwd, generatedBy, adapterFile: path.join(outDir, NODE_ADAPTER_FILE), wasmFile, smoke, diagnostics });
445
+ return { artifact, files: [{ file: NODE_ADAPTER_FILE, text: adapterText }], diagnostics };
446
+ }
447
+
448
+ module.exports = {
449
+ NODE_ADAPTER_VERSION,
450
+ buildNodeAdapter,
451
+ buildNodeAdapterSource
452
+ };