@pulse-compute/wasm-host-runtime 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.
@@ -0,0 +1,984 @@
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 { spawnSync } = require('node:child_process');
9
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = require('@pulse-compute/wasm-contracts/diagnostics');
10
+
11
+ function diagnosticFromParts(parts) {
12
+ return normalizeDiagnostic({ severity: 'error', loc: { file: '<generated>' }, ...parts });
13
+ }
14
+
15
+ const WASM_BRIDGE_VERSION = 'pulsewasm.wasm-host-bridge.v3';
16
+ const WASM_BRIDGE_PHASE = '12D';
17
+ const BRIDGE_RUNNER_FILE = 'generated/host-bridge/host-bridge-runner.as.ts';
18
+ const BRIDGE_WASM_REL = 'generated/wasm-bridge/pulsewasm-host-bridge.wasm';
19
+ const BRIDGE_WAT_REL = 'generated/wasm-bridge/pulsewasm-host-bridge.wat';
20
+
21
+ function sha256Buffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); }
22
+ function sha256Text(text) { return crypto.createHash('sha256').update(text, 'utf8').digest('hex'); }
23
+ function stripAnsi(value) { return String(value || '').replace(/\u001b\[[0-9;]*m/g, ''); }
24
+
25
+ function parseAscWarnings(stderr) {
26
+ const text = stripAnsi(stderr);
27
+ const warnings = [];
28
+ const regex = /WARNING\s+(AS\d+):\s+([^\n]+)(?:.|\n)*?in ([^\n]+)\((\d+),(\d+)\)/g;
29
+ let match;
30
+ while ((match = regex.exec(text))) {
31
+ warnings.push({ code: match[1], message: match[2].trim(), file: match[3], line: Number(match[4]), column: Number(match[5]) });
32
+ }
33
+ if (warnings.length === 0 && /WARNING\s+AS\d+:/m.test(text)) {
34
+ warnings.push({ code: 'AS_WARNING', message: 'AssemblyScript emitted one or more warnings. Inspect result.stderr for details.' });
35
+ }
36
+ return warnings;
37
+ }
38
+
39
+ function assemblyScriptVersion(cwd) {
40
+ try {
41
+ const packageJson = require.resolve('assemblyscript/package.json', { paths: [cwd] });
42
+ return JSON.parse(fs.readFileSync(packageJson, 'utf8')).version;
43
+ } catch (_) {
44
+ return undefined;
45
+ }
46
+ }
47
+
48
+ function fileRecord(filePath, cwd, kind) {
49
+ const buffer = fs.readFileSync(filePath);
50
+ return { file: path.relative(cwd, filePath).replace(/\\/g, '/'), kind, bytes: buffer.length, sha256: sha256Buffer(buffer) };
51
+ }
52
+
53
+ function writeStagedFiles(stagingDir, shape, core, bridgeRunnerText) {
54
+ const allFiles = [];
55
+ if (shape && Array.isArray(shape.files)) allFiles.push(...shape.files);
56
+ if (core && Array.isArray(core.files)) allFiles.push(...core.files);
57
+ const byFile = new Map();
58
+ for (const file of allFiles) byFile.set(file.file, file.text);
59
+ byFile.set(BRIDGE_RUNNER_FILE, bridgeRunnerText);
60
+ for (const [file, text] of byFile.entries()) {
61
+ const abs = path.join(stagingDir, file);
62
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
63
+ fs.writeFileSync(abs, text, 'utf8');
64
+ }
65
+ return Array.from(byFile.entries()).map(([file, text]) => ({ file, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text) }));
66
+ }
67
+
68
+ function buildBridgeRunnerSource() {
69
+ return `/*
70
+ * Generated by PulseWasm Phase 12D.
71
+ * Do not edit by hand.
72
+ *
73
+ * This file adapts the generated AssemblyScript routing core to the locked
74
+ * Wasm Host ABI v4 so Node can drive the compiled Wasm through ptr+byteLen
75
+ * strings, ordered repeated headers, and exported continuation/result helpers.
76
+ */
77
+ import {
78
+ METHOD_GET,
79
+ METHOD_POST,
80
+ CHANNEL_HANDLER
81
+ } from '../as/types.as'
82
+
83
+ import {
84
+ EXECUTION_ENTRY_COUNT,
85
+ ROUTE_COUNT
86
+ } from '../as/route-table.as'
87
+
88
+ import {
89
+ HANDLER_BINDING_COUNT
90
+ } from '../as/handler-slots.as'
91
+
92
+ import {
93
+ PulseWasmDispatchState,
94
+ PulseWasmErrorRef,
95
+ createDispatchState,
96
+ createPulseWasmError,
97
+ stepDispatchState,
98
+ continueNormal,
99
+ continueError,
100
+ pulse_set_result,
101
+ defaultStatusForStepResult,
102
+ STEP_INVOKE,
103
+ STEP_NOT_FOUND,
104
+ STEP_UNHANDLED_ERROR,
105
+ STEP_CONTRACT_VIOLATION,
106
+ NEXT_NORMAL,
107
+ NEXT_ERROR,
108
+ ENTRY_ROUTE,
109
+ invokeCurrentHandler,
110
+ currentParamCount,
111
+ currentParamNameAt,
112
+ currentParamValueAt,
113
+ scopeChannelKind,
114
+ scopeChannelSlot,
115
+ scopeConnectLength,
116
+ scopeConnectSlotAt,
117
+ scopeDisconnectLength,
118
+ scopeDisconnectSlotAt,
119
+ invokeChannelHandler,
120
+ invokeLifecycleHandler
121
+ } from '../as/runtime-core.as'
122
+
123
+ @external('pulsewasm_broadcaster', 'broadcast')
124
+ declare function __pulsewasm_broadcast(channelsRef: usize, ctxRef: usize): i32
125
+
126
+ export const RESULT_NONE: i32 = 0
127
+ export const RESULT_TEXT: i32 = 1
128
+ export const RESULT_JSON: i32 = 2
129
+ export const RESULT_BINARY: i32 = 3
130
+ export const RESULT_EMPTY: i32 = 4
131
+ export const RESULT_STREAM: i32 = 5
132
+
133
+ export const HOST_BRIDGE_OK: i32 = 0
134
+ export const HOST_BRIDGE_NOT_FOUND: i32 = 404
135
+ export const HOST_BRIDGE_INTERNAL_ERROR: i32 = 500
136
+ export const HOST_BRIDGE_BROADCASTER_ERROR: i32 = 590
137
+
138
+ let __currentState: PulseWasmDispatchState | null = null
139
+ let __resultStatus: i32 = 0
140
+ let __resultKind: i32 = RESULT_NONE
141
+ let __resultBodyPtr: usize = 0
142
+ let __resultBodyLen: i32 = 0
143
+ let __resultHeadersRef: usize = 0
144
+ let __resultErrorRef: usize = 0
145
+ let __resultStreamRef: usize = 0
146
+ let __resultResponseRef: usize = 0
147
+ let __lastStringBuffer: ArrayBuffer | null = null
148
+ let __lastHeaderBuffer: ArrayBuffer | null = null
149
+ let __lastChannelsRef: usize = 0
150
+ let __lastBroadcastResult: i32 = 0
151
+ let __lastInvokedHandlers: i32 = 0
152
+
153
+
154
+ let __requestHeaderNames = new Array<string>()
155
+ let __requestHeaderLowerNames = new Array<string>()
156
+ let __requestHeaderValues = new Array<string>()
157
+ let __responseHeaderNames = new Array<string>()
158
+ let __responseHeaderLowerNames = new Array<string>()
159
+ let __responseHeaderValues = new Array<string>()
160
+ let __responseHeaderModes = new Array<i32>()
161
+ let __responseHeaderDeletedNames = new Array<string>()
162
+ let __responseHeaderDeletedLowerNames = new Array<string>()
163
+
164
+ function decodeUtf8(ptr: usize, len: i32): string {
165
+ if (ptr == 0 || len <= 0) return ''
166
+ return String.UTF8.decodeUnsafe(ptr, len, true)
167
+ }
168
+
169
+ function stringRef(value: string): usize { return changetype<usize>(value) }
170
+
171
+ function lowerAscii(value: string): string {
172
+ let out = ''
173
+ for (let i: i32 = 0; i < value.length; i += 1) {
174
+ let code = value.charCodeAt(i)
175
+ if (code >= 65 && code <= 90) code += 32
176
+ out += String.fromCharCode(code)
177
+ }
178
+ return out
179
+ }
180
+
181
+ function clearRequestHeaders(): void {
182
+ __requestHeaderNames.length = 0
183
+ __requestHeaderLowerNames.length = 0
184
+ __requestHeaderValues.length = 0
185
+ }
186
+
187
+ function clearResponseHeaders(): void {
188
+ __responseHeaderNames.length = 0
189
+ __responseHeaderLowerNames.length = 0
190
+ __responseHeaderValues.length = 0
191
+ __responseHeaderModes.length = 0
192
+ __responseHeaderDeletedNames.length = 0
193
+ __responseHeaderDeletedLowerNames.length = 0
194
+ __resultHeadersRef = 0
195
+ }
196
+
197
+ function appendHeader(names: Array<string>, lowerNames: Array<string>, values: Array<string>, name: string, value: string): void {
198
+ names.push(name)
199
+ lowerNames.push(lowerAscii(name))
200
+ values.push(value)
201
+ }
202
+
203
+ function appendResponseHeader(name: string, value: string, mode: i32): void {
204
+ __responseHeaderNames.push(name)
205
+ __responseHeaderLowerNames.push(lowerAscii(name))
206
+ __responseHeaderValues.push(value)
207
+ __responseHeaderModes.push(mode)
208
+ __resultHeadersRef = 1
209
+ }
210
+
211
+ function headerCount(lowerNames: Array<string>, lowerName: string): i32 {
212
+ let count: i32 = 0
213
+ for (let i: i32 = 0; i < lowerNames.length; i += 1) {
214
+ if (lowerNames[i] == lowerName) count += 1
215
+ }
216
+ return count
217
+ }
218
+
219
+ function matchingHeaderIndex(lowerNames: Array<string>, lowerName: string, ordinal: i32): i32 {
220
+ let seen: i32 = 0
221
+ for (let i: i32 = 0; i < lowerNames.length; i += 1) {
222
+ if (lowerNames[i] == lowerName) {
223
+ if (seen == ordinal) return i
224
+ seen += 1
225
+ }
226
+ }
227
+ return -1
228
+ }
229
+
230
+ function deleteResponseHeadersByLower(lowerName: string): void {
231
+ const names = new Array<string>()
232
+ const lowerNames = new Array<string>()
233
+ const values = new Array<string>()
234
+ const modes = new Array<i32>()
235
+ for (let i: i32 = 0; i < __responseHeaderNames.length; i += 1) {
236
+ if (__responseHeaderLowerNames[i] != lowerName) {
237
+ names.push(__responseHeaderNames[i])
238
+ lowerNames.push(__responseHeaderLowerNames[i])
239
+ values.push(__responseHeaderValues[i])
240
+ modes.push(__responseHeaderModes[i])
241
+ }
242
+ }
243
+ __responseHeaderNames = names
244
+ __responseHeaderLowerNames = lowerNames
245
+ __responseHeaderValues = values
246
+ __responseHeaderModes = modes
247
+ __resultHeadersRef = __responseHeaderNames.length > 0 ? 1 : 0
248
+ }
249
+
250
+ function headerStringPtr(value: string): i32 {
251
+ const encoded = String.UTF8.encode(value, true)
252
+ __lastHeaderBuffer = encoded
253
+ return changetype<i32>(encoded)
254
+ }
255
+
256
+ function resetResult(): void {
257
+ __resultStatus = 0
258
+ __resultKind = RESULT_NONE
259
+ __resultBodyPtr = 0
260
+ __resultBodyLen = 0
261
+ __resultErrorRef = 0
262
+ __resultStreamRef = 0
263
+ __resultResponseRef = 0
264
+ __lastStringBuffer = null
265
+ __lastHeaderBuffer = null
266
+ clearResponseHeaders()
267
+ __lastChannelsRef = 0
268
+ __lastBroadcastResult = 0
269
+ __lastInvokedHandlers = 0
270
+ }
271
+
272
+ function setEmptyResult(statusCode: i32): void {
273
+ __resultStatus = statusCode
274
+ __resultKind = RESULT_EMPTY
275
+ __resultBodyPtr = 0
276
+ __resultBodyLen = 0
277
+ }
278
+
279
+ function setErrorResult(statusCode: i32, errorRef: usize): void {
280
+ __resultStatus = statusCode
281
+ __resultKind = RESULT_TEXT
282
+ __resultErrorRef = errorRef
283
+ }
284
+
285
+ function currentStateFromCtx(ctxRef: usize): PulseWasmDispatchState {
286
+ return changetype<PulseWasmDispatchState>(ctxRef)
287
+ }
288
+
289
+ function maybeBroadcastForState(state: PulseWasmDispatchState): void {
290
+ const scopeId = state.currentScopeId
291
+ if (scopeId < 0) return
292
+ if (scopeChannelKind(scopeId) != CHANNEL_HANDLER) return
293
+ const slot = scopeChannelSlot(scopeId)
294
+ if (slot < 0) return
295
+ const channelsRef = invokeChannelHandler(slot, changetype<usize>(state))
296
+ if (channelsRef == 0) return
297
+ __lastChannelsRef = channelsRef
298
+ const result = __pulsewasm_broadcast(channelsRef, changetype<usize>(state))
299
+ __lastBroadcastResult = result
300
+ if (result != 0) setErrorResult(HOST_BRIDGE_INTERNAL_ERROR, result)
301
+ }
302
+
303
+ function executeState(state: PulseWasmDispatchState): i32 {
304
+ __currentState = state
305
+ let guard: i32 = 0
306
+ while (guard < EXECUTION_ENTRY_COUNT + 16) {
307
+ guard += 1
308
+ const step = stepDispatchState(state)
309
+ if (step == STEP_INVOKE) {
310
+ __lastInvokedHandlers += 1
311
+ const invoked = invokeCurrentHandler(state)
312
+ if (!invoked) {
313
+ setErrorResult(HOST_BRIDGE_INTERNAL_ERROR, changetype<usize>(createPulseWasmError('PULSEWASM_HANDLER_NOT_BOUND', 'Handler slot could not be invoked.', HOST_BRIDGE_INTERNAL_ERROR)))
314
+ return __resultStatus
315
+ }
316
+ if (__resultStatus != 0 || __resultKind != RESULT_NONE) {
317
+ maybeBroadcastForState(state)
318
+ return __resultStatus
319
+ }
320
+ if (state.nextOutcome == NEXT_NORMAL || state.nextOutcome == NEXT_ERROR) continue
321
+ setErrorResult(HOST_BRIDGE_INTERNAL_ERROR, changetype<usize>(createPulseWasmError('PULSEWASM_NO_RESULT', 'Handler halted without result or continuation.', HOST_BRIDGE_INTERNAL_ERROR)))
322
+ return __resultStatus
323
+ }
324
+ if (step == STEP_NOT_FOUND) { setEmptyResult(404); return __resultStatus }
325
+ if (step == STEP_UNHANDLED_ERROR) { setErrorResult(500, state.activeError); return __resultStatus }
326
+ if (step == STEP_CONTRACT_VIOLATION) {
327
+ setErrorResult(500, changetype<usize>(createPulseWasmError('PULSEWASM_NO_RESULT', 'Handler halted without result or continuation.', 500)))
328
+ return __resultStatus
329
+ }
330
+ const fallback = defaultStatusForStepResult(step)
331
+ if (fallback != 0) { setEmptyResult(fallback); return __resultStatus }
332
+ setErrorResult(500, changetype<usize>(createPulseWasmError('PULSEWASM_INVALID_STEP', 'Invalid routing step result.', 500)))
333
+ return __resultStatus
334
+ }
335
+ setErrorResult(500, changetype<usize>(createPulseWasmError('PULSEWASM_DISPATCH_GUARD_EXHAUSTED', 'Dispatch guard exhausted.', 500)))
336
+ return __resultStatus
337
+ }
338
+
339
+ function findScopeForPath(methodCode: i32, requestPath: string): PulseWasmDispatchState {
340
+ const state = createDispatchState(methodCode, requestPath)
341
+ let guard: i32 = 0
342
+ while (guard < EXECUTION_ENTRY_COUNT + 16) {
343
+ guard += 1
344
+ const step = stepDispatchState(state)
345
+ if (step != STEP_INVOKE) return state
346
+ if (state.currentKind == ENTRY_ROUTE) return state
347
+ continueNormal(state)
348
+ }
349
+ return state
350
+ }
351
+
352
+ function executeLifecycleForScope(state: PulseWasmDispatchState, eventKind: i32): i32 {
353
+ __currentState = state
354
+ let invoked: i32 = 0
355
+ const scopeId = state.currentScopeId
356
+ const length = eventKind == 1 ? scopeConnectLength(scopeId) : scopeDisconnectLength(scopeId)
357
+ for (let i: i32 = 0; i < length; i += 1) {
358
+ const slot = eventKind == 1 ? scopeConnectSlotAt(scopeId, i) : scopeDisconnectSlotAt(scopeId, i)
359
+ if (slot >= 0 && invokeLifecycleHandler(slot, changetype<usize>(state), changetype<usize>(state))) invoked += 1
360
+ }
361
+ return invoked
362
+ }
363
+
364
+ export function pulse_alloc(byteLength: i32): i32 {
365
+ if (byteLength <= 0) return 0
366
+ const buffer = new ArrayBuffer(byteLength)
367
+ return changetype<i32>(buffer)
368
+ }
369
+
370
+ export function pulse_free(ptr: i32, byteLength: i32): void {}
371
+ export function pulse_init(): void { pulse_reset() }
372
+
373
+ export function pulse_reset(): void {
374
+ __currentState = null
375
+ clearRequestHeaders()
376
+ resetResult()
377
+ }
378
+
379
+ export function pulse_execute_request(methodCode: i32, pathPtr: i32, pathLen: i32): i32 {
380
+ resetResult()
381
+ const state = createDispatchState(methodCode, decodeUtf8(pathPtr, pathLen))
382
+ return executeState(state)
383
+ }
384
+
385
+ export function pulse_execute_connect(methodCode: i32, pathPtr: i32, pathLen: i32): i32 {
386
+ resetResult()
387
+ const state = findScopeForPath(methodCode, decodeUtf8(pathPtr, pathLen))
388
+ return executeLifecycleForScope(state, 1)
389
+ }
390
+
391
+ export function pulse_execute_disconnect(methodCode: i32, pathPtr: i32, pathLen: i32): i32 {
392
+ resetResult()
393
+ const state = findScopeForPath(methodCode, decodeUtf8(pathPtr, pathLen))
394
+ return executeLifecycleForScope(state, 2)
395
+ }
396
+
397
+ export function pulse_result_status(): i32 { return __resultStatus }
398
+ export function pulse_result_kind(): i32 { return __resultKind }
399
+ export function pulse_result_body_ref(): i32 { return __resultBodyPtr == 0 ? 0 : 1 }
400
+ export function pulse_result_body_ptr(): i32 { return __resultBodyPtr as i32 }
401
+ export function pulse_result_body_len(): i32 { return __resultBodyLen }
402
+ export function pulse_result_headers_ref(): i32 { return __resultHeadersRef as i32 }
403
+ export function pulse_result_error_ref(): i32 { return __resultErrorRef as i32 }
404
+ export function pulse_result_stream_ref(): i32 { return __resultStreamRef as i32 }
405
+ export function pulse_result_response_ref(): i32 { return __resultResponseRef as i32 }
406
+
407
+ export function pulse_request_header_first(ctxRef: i32, namePtr: i32, nameLen: i32): i32 {
408
+ const lowerName = lowerAscii(decodeUtf8(namePtr as usize, nameLen))
409
+ const index = matchingHeaderIndex(__requestHeaderLowerNames, lowerName, 0)
410
+ return index < 0 ? 0 : stringRef(__requestHeaderValues[index]) as i32
411
+ }
412
+
413
+ export function pulse_request_header_count(ctxRef: i32, namePtr: i32, nameLen: i32): i32 {
414
+ return headerCount(__requestHeaderLowerNames, lowerAscii(decodeUtf8(namePtr as usize, nameLen)))
415
+ }
416
+
417
+ export function pulse_request_header_at(ctxRef: i32, namePtr: i32, nameLen: i32, index: i32): i32 {
418
+ const lowerName = lowerAscii(decodeUtf8(namePtr as usize, nameLen))
419
+ const actual = matchingHeaderIndex(__requestHeaderLowerNames, lowerName, index)
420
+ return actual < 0 ? 0 : stringRef(__requestHeaderValues[actual]) as i32
421
+ }
422
+
423
+ export function pulse_response_header_set(ctxRef: i32, namePtr: i32, nameLen: i32, valuePtr: i32, valueLen: i32): void {
424
+ const name = decodeUtf8(namePtr as usize, nameLen)
425
+ const value = decodeUtf8(valuePtr as usize, valueLen)
426
+ deleteResponseHeadersByLower(lowerAscii(name))
427
+ appendResponseHeader(name, value, 1)
428
+ }
429
+
430
+ export function pulse_response_header_append(ctxRef: i32, namePtr: i32, nameLen: i32, valuePtr: i32, valueLen: i32): void {
431
+ appendResponseHeader(decodeUtf8(namePtr as usize, nameLen), decodeUtf8(valuePtr as usize, valueLen), 2)
432
+ }
433
+
434
+ export function pulse_response_header_delete(ctxRef: i32, namePtr: i32, nameLen: i32): void {
435
+ const name = decodeUtf8(namePtr as usize, nameLen)
436
+ const lower = lowerAscii(name)
437
+ __responseHeaderDeletedNames.push(name)
438
+ __responseHeaderDeletedLowerNames.push(lower)
439
+ deleteResponseHeadersByLower(lower)
440
+ }
441
+
442
+ export function pulse_result_header_count(): i32 { return __responseHeaderNames.length }
443
+ export function pulse_result_header_name_ptr(index: i32): i32 { return index < 0 || index >= __responseHeaderNames.length ? 0 : headerStringPtr(__responseHeaderNames[index]) }
444
+ export function pulse_result_header_name_len(index: i32): i32 { return index < 0 || index >= __responseHeaderNames.length ? 0 : String.UTF8.byteLength(__responseHeaderNames[index]) }
445
+ export function pulse_result_header_value_ptr(index: i32): i32 { return index < 0 || index >= __responseHeaderValues.length ? 0 : headerStringPtr(__responseHeaderValues[index]) }
446
+ export function pulse_result_header_value_len(index: i32): i32 { return index < 0 || index >= __responseHeaderValues.length ? 0 : String.UTF8.byteLength(__responseHeaderValues[index]) }
447
+ export function pulse_result_header_mode(index: i32): i32 { return index < 0 || index >= __responseHeaderModes.length ? 2 : __responseHeaderModes[index] }
448
+ export function pulse_result_header_delete_count(): i32 { return __responseHeaderDeletedNames.length }
449
+ export function pulse_result_header_delete_name_ptr(index: i32): i32 { return index < 0 || index >= __responseHeaderDeletedNames.length ? 0 : headerStringPtr(__responseHeaderDeletedNames[index]) }
450
+ export function pulse_result_header_delete_name_len(index: i32): i32 { return index < 0 || index >= __responseHeaderDeletedNames.length ? 0 : String.UTF8.byteLength(__responseHeaderDeletedNames[index]) }
451
+
452
+ export function pulse_string_ptr(stringRef: i32): i32 {
453
+ if (stringRef == 0) return 0
454
+ const value = changetype<string>(stringRef)
455
+ const encoded = String.UTF8.encode(value, true)
456
+ __lastStringBuffer = encoded
457
+ return changetype<i32>(encoded)
458
+ }
459
+ export function pulse_string_len(stringRef: i32): i32 { return stringRef == 0 ? 0 : String.UTF8.byteLength(changetype<string>(stringRef)) }
460
+ export function pulse_string_is_null(stringRef: i32): i32 { return stringRef == 0 ? 1 : 0 }
461
+
462
+ export function pulse_ctx_param(ctxRef: i32, namePtr: i32, nameLen: i32): i32 {
463
+ const state = currentStateFromCtx(ctxRef as usize)
464
+ const name = decodeUtf8(namePtr as usize, nameLen)
465
+ const count = currentParamCount(state)
466
+ for (let i: i32 = 0; i < count; i += 1) {
467
+ if (currentParamNameAt(state, i) == name) return stringRef(currentParamValueAt(state, i)) as i32
468
+ }
469
+ return 0
470
+ }
471
+
472
+ export function pulse_ctx_state_get(ctxRef: i32, keyPtr: i32, keyLen: i32): i32 {
473
+ const state = currentStateFromCtx(ctxRef as usize)
474
+ const key = decodeUtf8(keyPtr as usize, keyLen)
475
+ for (let i: i32 = 0; i < state.stateKeys.length; i += 1) if (state.stateKeys[i] == key) return stringRef(state.stateValues[i]) as i32
476
+ return 0
477
+ }
478
+
479
+ export function pulse_ctx_state_set(ctxRef: i32, keyPtr: i32, keyLen: i32, valuePtr: i32, valueLen: i32): void {
480
+ const state = currentStateFromCtx(ctxRef as usize)
481
+ const key = decodeUtf8(keyPtr as usize, keyLen)
482
+ const value = decodeUtf8(valuePtr as usize, valueLen)
483
+ for (let i: i32 = 0; i < state.stateKeys.length; i += 1) {
484
+ if (state.stateKeys[i] == key) { state.stateValues[i] = value; return }
485
+ }
486
+ state.stateKeys.push(key)
487
+ state.stateValues.push(value)
488
+ }
489
+
490
+ export function pulse_result_text(ctxRef: i32, statusCode: i32, bodyPtr: i32, bodyLen: i32): void {
491
+ __resultStatus = statusCode
492
+ __resultKind = RESULT_TEXT
493
+ __resultBodyPtr = bodyPtr as usize
494
+ __resultBodyLen = bodyLen
495
+ pulse_set_result(currentStateFromCtx(ctxRef as usize), 1)
496
+ }
497
+
498
+ export function pulse_result_empty(ctxRef: i32, statusCode: i32): void {
499
+ __resultStatus = statusCode
500
+ __resultKind = RESULT_EMPTY
501
+ __resultBodyPtr = 0
502
+ __resultBodyLen = 0
503
+ pulse_set_result(currentStateFromCtx(ctxRef as usize), 1)
504
+ }
505
+
506
+ export function pulse_result_stream(ctxRef: i32, statusCode: i32, streamRef: i32): void {
507
+ __resultStatus = statusCode
508
+ __resultKind = RESULT_STREAM
509
+ __resultStreamRef = streamRef as usize
510
+ __resultResponseRef = 0
511
+ __resultBodyPtr = 0
512
+ __resultBodyLen = 0
513
+ pulse_set_result(currentStateFromCtx(ctxRef as usize), 1)
514
+ }
515
+
516
+ export function pulse_result_from_response(ctxRef: i32, responseRef: i32): void {
517
+ __resultStatus = 0
518
+ __resultKind = RESULT_STREAM
519
+ __resultResponseRef = responseRef as usize
520
+ __resultStreamRef = 0
521
+ __resultBodyPtr = 0
522
+ __resultBodyLen = 0
523
+ pulse_set_result(currentStateFromCtx(ctxRef as usize), 1)
524
+ }
525
+
526
+ export function pulse_next(ctxRef: i32): void { continueNormal(currentStateFromCtx(ctxRef as usize)) }
527
+ export function pulse_next_error(ctxRef: i32, errorRef: i32): void { continueError(currentStateFromCtx(ctxRef as usize), errorRef as usize) }
528
+
529
+ export function pulse_error(codePtr: i32, codeLen: i32, messagePtr: i32, messageLen: i32, statusCode: i32): i32 {
530
+ const error = createPulseWasmError(decodeUtf8(codePtr as usize, codeLen), decodeUtf8(messagePtr as usize, messageLen), statusCode)
531
+ return changetype<i32>(error)
532
+ }
533
+
534
+ export function pulse_error_with_cause(codePtr: i32, codeLen: i32, messagePtr: i32, messageLen: i32, statusCode: i32, causeRef: i32): i32 {
535
+ const error = createPulseWasmError(decodeUtf8(codePtr as usize, codeLen), decodeUtf8(messagePtr as usize, messageLen), statusCode, causeRef as usize)
536
+ return changetype<i32>(error)
537
+ }
538
+
539
+ export function pulse_error_code_ref(errorRef: i32): i32 { return errorRef == 0 ? 0 : stringRef(changetype<PulseWasmErrorRef>(errorRef).code) as i32 }
540
+ export function pulse_error_message_ref(errorRef: i32): i32 { return errorRef == 0 ? 0 : stringRef(changetype<PulseWasmErrorRef>(errorRef).message) as i32 }
541
+ export function pulse_error_status_code(errorRef: i32): i32 { return errorRef == 0 ? 0 : changetype<PulseWasmErrorRef>(errorRef).statusCode }
542
+ export function pulse_error_cause_ref(errorRef: i32): i32 { return errorRef == 0 ? 0 : changetype<PulseWasmErrorRef>(errorRef).cause as i32 }
543
+
544
+ export function pulse_channel_count(channelsRef: i32): i32 { return channelsRef == 0 ? 0 : 1 }
545
+ export function pulse_channel_ref_at(channelsRef: i32, index: i32): i32 { return index == 0 ? channelsRef : 0 }
546
+
547
+ export function pulse_bridge_request_headers_clear(): void { clearRequestHeaders() }
548
+ export function pulse_bridge_request_header_append(namePtr: i32, nameLen: i32, valuePtr: i32, valueLen: i32): void {
549
+ appendHeader(__requestHeaderNames, __requestHeaderLowerNames, __requestHeaderValues, decodeUtf8(namePtr as usize, nameLen), decodeUtf8(valuePtr as usize, valueLen))
550
+ }
551
+ export function pulse_bridge_request_header_total(): i32 { return __requestHeaderNames.length }
552
+
553
+ export function pulse_bridge_route_count(): i32 { return ROUTE_COUNT }
554
+ export function pulse_bridge_execution_entry_count(): i32 { return EXECUTION_ENTRY_COUNT }
555
+ export function pulse_bridge_handler_binding_count(): i32 { return HANDLER_BINDING_COUNT }
556
+ export function pulse_bridge_last_channels_ref(): i32 { return __lastChannelsRef as i32 }
557
+ export function pulse_bridge_last_broadcast_result(): i32 { return __lastBroadcastResult }
558
+ export function pulse_bridge_last_invoked_handlers(): i32 { return __lastInvokedHandlers }
559
+ export function pulse_bridge_method_get(): i32 { return METHOD_GET }
560
+ export function pulse_bridge_method_post(): i32 { return METHOD_POST }
561
+ `;
562
+ }
563
+
564
+ function ascExecutable(cwd) {
565
+ try {
566
+ const ascPath = require.resolve('assemblyscript/bin/asc.js', { paths: [cwd] });
567
+ return { command: process.execPath, argsPrefix: [ascPath], display: `node ${path.relative(cwd, ascPath).replace(/\\/g, '/')}` };
568
+ } catch (_) {
569
+ return undefined;
570
+ }
571
+ }
572
+
573
+ function compileBridge(stagingDir, cwd, options = {}) {
574
+ const asc = ascExecutable(cwd);
575
+ if (!asc) {
576
+ return {
577
+ asc,
578
+ result: { status: 1, stdout: '', stderr: 'assemblyscript package is not installed.' },
579
+ diagnostics: [diagnosticFromParts({
580
+ phase: 'wasm-host-bridge',
581
+ code: 'PULSEWASM_ASSEMBLYSCRIPT_NOT_INSTALLED',
582
+ message: 'AssemblyScript is required for Phase 10D Wasm host bridge compilation.',
583
+ hint: 'Run npm install before requesting --emit-wasm-host-bridge or --emit-request-result-headers.'
584
+ })]
585
+ };
586
+ }
587
+
588
+ const outDir = options.outDir ? path.resolve(cwd, options.outDir) : getDefaultArtifactsDir(cwd);
589
+ const wasmFile = path.join(outDir, BRIDGE_WASM_REL);
590
+ const watFile = path.join(outDir, BRIDGE_WAT_REL);
591
+ fs.mkdirSync(path.dirname(wasmFile), { recursive: true });
592
+ fs.mkdirSync(path.dirname(watFile), { recursive: true });
593
+ const input = path.join(stagingDir, BRIDGE_RUNNER_FILE);
594
+ const args = [...asc.argsPrefix, input, '--outFile', wasmFile, '--textFile', watFile, '--runtime', 'stub', '--noAssert'];
595
+ const started = Date.now();
596
+ const result = spawnSync(asc.command, args, { cwd, encoding: 'utf8', maxBuffer: 1024 * 1024 * 20 });
597
+ const durationMs = Date.now() - started;
598
+ const warnings = parseAscWarnings(result.stderr || '');
599
+ const diagnostics = [];
600
+ if (result.error || result.status !== 0) {
601
+ diagnostics.push(diagnosticFromParts({
602
+ phase: 'wasm-host-bridge',
603
+ code: 'PULSEWASM_WASM_HOST_BRIDGE_COMPILE_FAILED',
604
+ message: 'Phase 10D Wasm host bridge compile failed.',
605
+ hint: 'Inspect wasm-host-bridge.json result.stderr for AssemblyScript diagnostics.',
606
+ details: { exitCode: result.status, stderr: result.stderr || '', stdout: result.stdout || '', error: result.error ? result.error.message : undefined }
607
+ }));
608
+ }
609
+ if (warnings.length > 0) {
610
+ diagnostics.push(diagnosticFromParts({
611
+ phase: 'wasm-host-bridge',
612
+ code: 'PULSEWASM_WASM_HOST_BRIDGE_WARNINGS',
613
+ message: 'Phase 10D Wasm host bridge emitted AssemblyScript warnings.',
614
+ hint: 'Warnings are treated as failures at this ABI boundary.',
615
+ details: { warnings }
616
+ }));
617
+ }
618
+ return { asc, args, wasmFile, watFile, durationMs, warnings, result, diagnostics };
619
+ }
620
+
621
+ function instantiateWithoutBroadcaster(wasmFile) {
622
+ try {
623
+ const wasm = fs.readFileSync(wasmFile);
624
+ const module = new WebAssembly.Module(wasm);
625
+ const importObject = { env: { abort() { throw new Error('abort'); } }, pulsewasm_handlers: {} };
626
+ for (const imp of WebAssembly.Module.imports(module)) if (imp.module === 'pulsewasm_handlers') importObject.pulsewasm_handlers[imp.name] = () => 0;
627
+ new WebAssembly.Instance(module, importObject);
628
+ return { missingBroadcasterRejected: false, error: undefined };
629
+ } catch (error) {
630
+ return { missingBroadcasterRejected: true, error: error && error.message ? error.message : String(error) };
631
+ }
632
+ }
633
+
634
+ function instantiateAndRunBridge(wasmFile) {
635
+ const wasm = fs.readFileSync(wasmFile);
636
+ const module = new WebAssembly.Module(wasm);
637
+ const imports = WebAssembly.Module.imports(module);
638
+ const exports = WebAssembly.Module.exports(module);
639
+ let instance;
640
+ const encoder = new TextEncoder();
641
+ const decoder = new TextDecoder();
642
+ const handlerCalls = {};
643
+ const observedParams = {};
644
+ const observedState = {};
645
+ const observedHeaders = {};
646
+ const broadcasterCalls = [];
647
+
648
+ function memory() { return instance.exports.memory; }
649
+ function allocBytes(text) {
650
+ const bytes = encoder.encode(text);
651
+ const ptr = instance.exports.pulse_alloc(bytes.length || 1);
652
+ new Uint8Array(memory().buffer, ptr, bytes.length).set(bytes);
653
+ return { ptr, len: bytes.length, text };
654
+ }
655
+ function readBytes(ptr, len) { return (!ptr || !len) ? '' : decoder.decode(new Uint8Array(memory().buffer, ptr, len)); }
656
+ function readStringRef(ref) {
657
+ if (!ref) return undefined;
658
+ return readBytes(instance.exports.pulse_string_ptr(ref), instance.exports.pulse_string_len(ref));
659
+ }
660
+ function setResultText(ctx, status, body) {
661
+ const encoded = allocBytes(body);
662
+ instance.exports.pulse_result_text(ctx, status, encoded.ptr, encoded.len);
663
+ }
664
+ function getParam(ctx, name) {
665
+ const encoded = allocBytes(name);
666
+ return readStringRef(instance.exports.pulse_ctx_param(ctx, encoded.ptr, encoded.len));
667
+ }
668
+ function stateSet(ctx, key, value) {
669
+ const k = allocBytes(key); const v = allocBytes(value);
670
+ instance.exports.pulse_ctx_state_set(ctx, k.ptr, k.len, v.ptr, v.len);
671
+ }
672
+ function stateGet(ctx, key) {
673
+ const k = allocBytes(key);
674
+ return readStringRef(instance.exports.pulse_ctx_state_get(ctx, k.ptr, k.len));
675
+ }
676
+ function requestHeaderFirst(ctx, name) {
677
+ const n = allocBytes(name);
678
+ return readStringRef(instance.exports.pulse_request_header_first(ctx, n.ptr, n.len));
679
+ }
680
+ function requestHeaderCount(ctx, name) {
681
+ const n = allocBytes(name);
682
+ return instance.exports.pulse_request_header_count(ctx, n.ptr, n.len);
683
+ }
684
+ function requestHeaderAt(ctx, name, index) {
685
+ const n = allocBytes(name);
686
+ return readStringRef(instance.exports.pulse_request_header_at(ctx, n.ptr, n.len, index));
687
+ }
688
+ function responseHeaderSet(ctx, name, value) {
689
+ const n = allocBytes(name); const v = allocBytes(value);
690
+ instance.exports.pulse_response_header_set(ctx, n.ptr, n.len, v.ptr, v.len);
691
+ }
692
+ function responseHeaderAppend(ctx, name, value) {
693
+ const n = allocBytes(name); const v = allocBytes(value);
694
+ instance.exports.pulse_response_header_append(ctx, n.ptr, n.len, v.ptr, v.len);
695
+ }
696
+ function responseHeaderDelete(ctx, name) {
697
+ const n = allocBytes(name);
698
+ instance.exports.pulse_response_header_delete(ctx, n.ptr, n.len);
699
+ }
700
+ function requestHeadersClear() { instance.exports.pulse_bridge_request_headers_clear(); }
701
+ function requestHeaderAppend(name, value) {
702
+ const n = allocBytes(name); const v = allocBytes(value);
703
+ instance.exports.pulse_bridge_request_header_append(n.ptr, n.len, v.ptr, v.len);
704
+ }
705
+ function readResultHeaders() {
706
+ const headers = [];
707
+ const count = instance.exports.pulse_result_header_count();
708
+ for (let i = 0; i < count; i += 1) {
709
+ headers.push({
710
+ name: readBytes(instance.exports.pulse_result_header_name_ptr(i), instance.exports.pulse_result_header_name_len(i)),
711
+ value: readBytes(instance.exports.pulse_result_header_value_ptr(i), instance.exports.pulse_result_header_value_len(i))
712
+ });
713
+ }
714
+ return headers;
715
+ }
716
+ function headerValues(headers, name) {
717
+ const lower = String(name).toLowerCase();
718
+ return headers.filter((header) => String(header.name).toLowerCase() === lower).map((header) => header.value);
719
+ }
720
+ function makeError(code, message, status) {
721
+ const c = allocBytes(code); const m = allocBytes(message);
722
+ return instance.exports.pulse_error(c.ptr, c.len, m.ptr, m.len, status);
723
+ }
724
+
725
+ const importObject = {
726
+ env: { abort(message, file, line, column) { throw new Error(`AssemblyScript abort(${message}, ${file}, ${line}, ${column})`); } },
727
+ pulsewasm_handlers: {},
728
+ pulsewasm_broadcaster: { broadcast(channelsRef, ctxRef) { broadcasterCalls.push({ channelsRef, ctxRef }); return 0; } }
729
+ };
730
+
731
+ for (const imp of imports) {
732
+ if (imp.module === 'pulsewasm_handlers') {
733
+ importObject.pulsewasm_handlers[imp.name] = (a, b, c) => {
734
+ handlerCalls[imp.name] = (handlerCalls[imp.name] || 0) + 1;
735
+ if (imp.name === 'auth') {
736
+ const first = requestHeaderFirst(a, 'X-Pulse-Test');
737
+ const multiCount = requestHeaderCount(a, 'X-Pulse-Multi');
738
+ const multiSecond = requestHeaderAt(a, 'X-Pulse-Multi', 1);
739
+ if (first !== undefined && observedHeaders.requestFirst === undefined) observedHeaders.requestFirst = first;
740
+ if (multiCount > (observedHeaders.requestMultiCount || 0)) observedHeaders.requestMultiCount = multiCount;
741
+ if (multiSecond !== undefined && observedHeaders.requestMultiSecond === undefined) observedHeaders.requestMultiSecond = multiSecond;
742
+ stateSet(a, 'auth', '1');
743
+ instance.exports.pulse_next(a);
744
+ return 0;
745
+ }
746
+ if (imp.name === 'gateway') {
747
+ observedState.gatewaySawAuth = stateGet(a, 'auth');
748
+ instance.exports.pulse_next(a);
749
+ return 0;
750
+ }
751
+ if (imp.name === 'computeHandler') {
752
+ const id = getParam(a, 'id');
753
+ observedParams.computeId = id;
754
+ responseHeaderAppend(a, 'Temp-Header', 'remove-me');
755
+ responseHeaderDelete(a, 'Temp-Header');
756
+ responseHeaderSet(a, 'Content-Type', 'text/plain');
757
+ setResultText(a, 201, `compute:${id}`);
758
+ return 0;
759
+ }
760
+ if (imp.name === 'realtimeHandler') {
761
+ const id = getParam(a, 'id');
762
+ observedParams.realtimeId = id;
763
+ responseHeaderSet(a, 'Content-Type', 'text/plain');
764
+ responseHeaderSet(a, 'Grip-Hold', 'stream');
765
+ responseHeaderAppend(a, 'Grip-Channel', 'realtime');
766
+ responseHeaderAppend(a, 'Grip-Channel', `realtime:${id}`);
767
+ setResultText(a, 200, `realtime:${id}`);
768
+ return 0;
769
+ }
770
+ if (imp.name === 'bundleChannel') return 0xC0DE;
771
+ if (imp.name === 'onConnect' || imp.name === 'onDisconnect') return 0;
772
+ if (imp.name.toLowerCase().includes('error')) { instance.exports.pulse_next(a || b || c || 0); return 0; }
773
+ return 0;
774
+ };
775
+ }
776
+ }
777
+
778
+ instance = new WebAssembly.Instance(module, importObject);
779
+
780
+ function executeRequest(methodCode, requestPath, headers = []) {
781
+ requestHeadersClear();
782
+ for (const header of headers) requestHeaderAppend(header.name, header.value);
783
+ const pathBytes = allocBytes(requestPath);
784
+ const status = instance.exports.pulse_execute_request(methodCode, pathBytes.ptr, pathBytes.len);
785
+ const resultHeaders = readResultHeaders();
786
+ return {
787
+ status,
788
+ resultStatus: instance.exports.pulse_result_status(),
789
+ resultKind: instance.exports.pulse_result_kind(),
790
+ body: readBytes(instance.exports.pulse_result_body_ptr(), instance.exports.pulse_result_body_len()),
791
+ headers: resultHeaders,
792
+ headerCount: resultHeaders.length,
793
+ gripChannels: headerValues(resultHeaders, 'Grip-Channel'),
794
+ errorRef: instance.exports.pulse_result_error_ref(),
795
+ streamRef: instance.exports.pulse_result_stream_ref ? instance.exports.pulse_result_stream_ref() : 0,
796
+ responseRef: instance.exports.pulse_result_response_ref ? instance.exports.pulse_result_response_ref() : 0,
797
+ lastChannelsRef: instance.exports.pulse_bridge_last_channels_ref(),
798
+ lastBroadcastResult: instance.exports.pulse_bridge_last_broadcast_result(),
799
+ invokedHandlers: instance.exports.pulse_bridge_last_invoked_handlers()
800
+ };
801
+ }
802
+
803
+ function executeLifecycle(kind, methodCode, requestPath) {
804
+ const pathBytes = allocBytes(requestPath);
805
+ const fn = kind === 'connect' ? instance.exports.pulse_execute_connect : instance.exports.pulse_execute_disconnect;
806
+ return fn(methodCode, pathBytes.ptr, pathBytes.len);
807
+ }
808
+
809
+ const methodGet = instance.exports.pulse_bridge_method_get();
810
+ const methodPost = instance.exports.pulse_bridge_method_post();
811
+ const realtime = executeRequest(methodGet, '/realtime/abc', [
812
+ { name: 'X-Pulse-Test', value: 'edge' },
813
+ { name: 'X-Pulse-Multi', value: 'one' },
814
+ { name: 'x-pulse-multi', value: 'two' }
815
+ ]);
816
+ const compute = executeRequest(methodPost, '/compute/42', [{ name: 'X-Pulse-Test', value: 'edge' }]);
817
+ const missing = executeRequest(methodGet, '/missing');
818
+ const connectInvoked = executeLifecycle('connect', methodGet, '/realtime/abc');
819
+ const disconnectInvoked = executeLifecycle('disconnect', methodGet, '/realtime/abc');
820
+ const error = makeError('PULSEWASM_TEST', 'host bridge error', 599);
821
+ const errorCode = readStringRef(instance.exports.pulse_error_code_ref(error));
822
+ const errorMessage = readStringRef(instance.exports.pulse_error_message_ref(error));
823
+
824
+ const checks = [
825
+ { name: 'route_count', expected: 2, actual: instance.exports.pulse_bridge_route_count() },
826
+ { name: 'execution_entry_count', expected: 8, actual: instance.exports.pulse_bridge_execution_entry_count() },
827
+ { name: 'handler_binding_count', expected: 7, actual: instance.exports.pulse_bridge_handler_binding_count() },
828
+ { name: 'request_realtime_status', expected: 200, actual: realtime.status },
829
+ { name: 'request_realtime_body', expected: 'realtime:abc', actual: realtime.body },
830
+ { name: 'request_realtime_param', expected: 'abc', actual: observedParams.realtimeId },
831
+ { name: 'request_realtime_broadcaster', expected: 1, actual: broadcasterCalls.length >= 1 ? 1 : 0 },
832
+ { name: 'request_header_first', expected: 'edge', actual: observedHeaders.requestFirst },
833
+ { name: 'request_header_multi_count', expected: 2, actual: observedHeaders.requestMultiCount },
834
+ { name: 'request_header_multi_second', expected: 'two', actual: observedHeaders.requestMultiSecond },
835
+ { name: 'response_header_count', expected: 4, actual: realtime.headerCount },
836
+ { name: 'response_header_grip_channel_count', expected: 2, actual: realtime.gripChannels.length },
837
+ { name: 'response_header_grip_channel_second', expected: 'realtime:abc', actual: realtime.gripChannels[1] },
838
+ { name: 'request_compute_status', expected: 201, actual: compute.status },
839
+ { name: 'request_compute_body', expected: 'compute:42', actual: compute.body },
840
+ { name: 'response_header_delete', expected: 0, actual: headerValues(compute.headers, 'Temp-Header').length },
841
+ { name: 'request_compute_param', expected: '42', actual: observedParams.computeId },
842
+ { name: 'ctx_state_map_bridge', expected: '1', actual: observedState.gatewaySawAuth },
843
+ { name: 'missing_fallthrough_status', expected: 404, actual: missing.status },
844
+ { name: 'connect_lifecycle_invoked', expected: 1, actual: connectInvoked },
845
+ { name: 'disconnect_lifecycle_invoked', expected: 1, actual: disconnectInvoked },
846
+ { name: 'error_code_ref', expected: 'PULSEWASM_TEST', actual: errorCode },
847
+ { name: 'error_message_ref', expected: 'host bridge error', actual: errorMessage },
848
+ { name: 'handler_auth_called', expected: 1, actual: handlerCalls.auth >= 1 ? 1 : 0 },
849
+ { name: 'handler_gateway_called', expected: 1, actual: handlerCalls.gateway >= 1 ? 1 : 0 },
850
+ { name: 'handler_realtime_called', expected: 1, actual: handlerCalls.realtimeHandler >= 1 ? 1 : 0 },
851
+ { name: 'handler_compute_called', expected: 1, actual: handlerCalls.computeHandler >= 1 ? 1 : 0 },
852
+ { name: 'handler_channel_called', expected: 1, actual: handlerCalls.bundleChannel >= 1 ? 1 : 0 }
853
+ ].map((check) => ({ ...check, status: check.actual === check.expected ? 'ok' : 'error', message: check.actual === check.expected ? undefined : `Expected ${check.expected}, got ${check.actual}.` }));
854
+
855
+ return {
856
+ imports,
857
+ exports,
858
+ checks,
859
+ handlerCalls,
860
+ broadcasterCalls,
861
+ observedParams,
862
+ observedState,
863
+ observedHeaders,
864
+ results: { realtime, compute, missing, connectInvoked, disconnectInvoked, errorCode, errorMessage },
865
+ memoryExported: Boolean(instance.exports.memory),
866
+ instanceExports: Object.keys(instance.exports)
867
+ };
868
+ }
869
+
870
+ function buildArtifact(options = {}) {
871
+ const cwd = options.cwd || process.cwd();
872
+ const diagnostics = options.diagnostics || [];
873
+ const checks = options.bridge?.checks || [];
874
+ const failedChecks = checks.filter((check) => check.status !== 'ok');
875
+ return normalizeArtifact({
876
+ version: WASM_BRIDGE_VERSION,
877
+ generatedBy: options.generatedBy || PACKAGE_VERSION,
878
+ status: diagnostics.length || failedChecks.length ? 'error' : 'ok',
879
+ sourceWasmHostAbiVersion: options.wasmHostAbi?.artifact?.version,
880
+ sourceAssemblyScriptCoreVersion: options.core?.artifact?.version,
881
+ policy: {
882
+ phase: WASM_BRIDGE_PHASE,
883
+ sourceOfTruth: 'wasm-host-abi.json + request-result-headers.json + generated/as/*.as.ts',
884
+ reReadsSource: false,
885
+ reInfersRouteSemantics: false,
886
+ compiler: 'assemblyscript asc',
887
+ wasmCompiled: true,
888
+ wasmInstantiated: Boolean(options.bridge),
889
+ wasmExecutedThroughHostAbi: Boolean(options.bridge),
890
+ hostBridgeImplemented: true,
891
+ platformBindings: false,
892
+ witBindings: false,
893
+ wasiHttp: false,
894
+ stringAbi: 'host writes UTF-8 through pulse_alloc; wasm decodes ptr + byteLen',
895
+ handlers: 'host imports call exported continuation/result/ctx/header operations',
896
+ headers: 'ordered repeated request/response headers implemented; result headers exported by iteration',
897
+ bodyPolicy: 'request body deferred; RESULT_TEXT/JSON/EMPTY implemented; RESULT_STREAM implemented for host-owned pass-through; RESULT_BINARY reserved',
898
+ broadcaster: 'host import required; missing broadcaster rejected during instantiation'
899
+ },
900
+ compiler: {
901
+ package: 'assemblyscript',
902
+ version: options.assemblyScriptVersion,
903
+ executable: options.asc?.display
904
+ },
905
+ command: options.command,
906
+ result: options.result ? { exitCode: options.result.status, signal: options.result.signal, durationMs: options.durationMs, stdout: String(options.result.stdout || ''), stderr: String(options.result.stderr || '') } : undefined,
907
+ generated: { inputFiles: options.inputFiles || [], outputFiles: options.outputFiles || [] },
908
+ wasmModule: options.bridge ? { imports: options.bridge.imports, exports: options.bridge.exports, memoryExported: options.bridge.memoryExported, instanceExports: options.bridge.instanceExports } : undefined,
909
+ bridge: options.bridge ? {
910
+ checks: options.bridge.checks,
911
+ failedChecks,
912
+ handlerCalls: options.bridge.handlerCalls,
913
+ broadcasterCalls: options.bridge.broadcasterCalls.length,
914
+ observedParams: options.bridge.observedParams,
915
+ observedState: options.bridge.observedState,
916
+ observedHeaders: options.bridge.observedHeaders,
917
+ results: options.bridge.results
918
+ } : undefined,
919
+ missingBroadcaster: options.missingBroadcaster,
920
+ diagnostics,
921
+ warnings: options.warnings || [],
922
+ summary: {
923
+ compiled: Boolean(options.outputFiles && options.outputFiles.length),
924
+ instantiated: Boolean(options.bridge),
925
+ executed: Boolean(options.bridge),
926
+ hostBridge: Boolean(options.bridge),
927
+ checks: checks.length,
928
+ failedChecks: failedChecks.length,
929
+ handlerCalls: options.bridge ? Object.values(options.bridge.handlerCalls).reduce((sum, value) => sum + Number(value || 0), 0) : 0,
930
+ broadcasterCalls: options.bridge ? options.bridge.broadcasterCalls.length : 0,
931
+ headersAdapted: Boolean(options.bridge),
932
+ requestHeaderChecks: checks.filter((check) => check.name.startsWith('request_header_')).length,
933
+ responseHeaderChecks: checks.filter((check) => check.name.startsWith('response_header_')).length,
934
+ repeatedHeadersPreserved: Boolean(options.bridge?.results?.realtime?.gripChannels?.length === 2),
935
+ missingBroadcasterRejected: Boolean(options.missingBroadcaster?.missingBroadcasterRejected),
936
+ wasmBytes: options.outputFiles?.find((file) => file.kind === 'wasm-host-bridge-output')?.bytes || 0,
937
+ watBytes: options.outputFiles?.find((file) => file.kind === 'wat-host-bridge-output')?.bytes || 0,
938
+ diagnostics: diagnostics.length,
939
+ warnings: (options.warnings || []).length,
940
+ platformBindings: false,
941
+ witBindings: false
942
+ }
943
+ }, cwd);
944
+ }
945
+
946
+ function buildWasmHostBridge(shape, core, wasmHostAbi, options = {}) {
947
+ const cwd = options.cwd || process.cwd();
948
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
949
+ const diagnostics = [];
950
+ const bridgeRunnerText = buildBridgeRunnerSource();
951
+ const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-host-bridge-'));
952
+ const inputFiles = writeStagedFiles(stagingDir, shape, core, bridgeRunnerText);
953
+ const compile = compileBridge(stagingDir, cwd, options);
954
+ diagnostics.push(...(compile.diagnostics || []));
955
+
956
+ let outputFiles = [];
957
+ let bridge;
958
+ let missingBroadcaster;
959
+ if (diagnostics.length === 0 && compile.wasmFile && fs.existsSync(compile.wasmFile)) {
960
+ outputFiles = [fileRecord(compile.wasmFile, cwd, 'wasm-host-bridge-output'), fileRecord(compile.watFile, cwd, 'wat-host-bridge-output')];
961
+ try {
962
+ missingBroadcaster = instantiateWithoutBroadcaster(compile.wasmFile);
963
+ bridge = instantiateAndRunBridge(compile.wasmFile);
964
+ const failed = bridge.checks.filter((check) => check.status !== 'ok');
965
+ if (failed.length > 0) diagnostics.push(diagnosticFromParts({ phase: 'wasm-host-bridge', code: 'PULSEWASM_WASM_HOST_BRIDGE_CHECK_FAILED', message: 'Phase 10D Wasm host bridge checks failed.', hint: 'Inspect wasm-host-bridge.json bridge.failedChecks for details.', details: { failed } }));
966
+ if (!missingBroadcaster.missingBroadcasterRejected) diagnostics.push(diagnosticFromParts({ phase: 'wasm-host-bridge', code: 'PULSEWASM_WASM_HOST_BRIDGE_BROADCASTER_NOT_REQUIRED', message: 'The Phase 10D bridge did not reject a missing broadcaster import.', hint: 'Channel-resolving builds must require the broadcaster import at this boundary.', details: missingBroadcaster }));
967
+ } catch (error) {
968
+ diagnostics.push(diagnosticFromParts({ phase: 'wasm-host-bridge', code: 'PULSEWASM_WASM_HOST_BRIDGE_EXECUTION_FAILED', message: 'Phase 10D Wasm host bridge execution failed.', hint: 'Inspect details for the host-side execution error.', details: { message: error && error.message ? error.message : String(error), stack: error && error.stack ? error.stack : undefined } }));
969
+ }
970
+ }
971
+
972
+ const command = compile.asc ? {
973
+ executable: compile.asc.display,
974
+ args: [path.relative(cwd, path.join(stagingDir, BRIDGE_RUNNER_FILE)).replace(/\\/g, '/'), '--outFile', path.join(options.outDir ? path.relative(cwd, path.resolve(cwd, options.outDir)) : 'artifacts', BRIDGE_WASM_REL).replace(/\\/g, '/'), '--textFile', path.join(options.outDir ? path.relative(cwd, path.resolve(cwd, options.outDir)) : 'artifacts', BRIDGE_WAT_REL).replace(/\\/g, '/'), '--runtime', 'stub', '--noAssert']
975
+ } : undefined;
976
+
977
+ const artifact = buildArtifact({ cwd, generatedBy, shape, core, wasmHostAbi, diagnostics, warnings: compile.warnings || [], asc: compile.asc, command, result: compile.result, durationMs: compile.durationMs, inputFiles, outputFiles, bridge, missingBroadcaster, assemblyScriptVersion: assemblyScriptVersion(cwd) });
978
+ return { artifact, files: [{ file: BRIDGE_RUNNER_FILE, text: bridgeRunnerText }], outputFiles, diagnostics };
979
+ }
980
+
981
+ module.exports = {
982
+ WASM_BRIDGE_VERSION,
983
+ buildWasmHostBridge
984
+ };