@pulse-compute/wasm-host-runtime 0.0.0 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -1
- package/package.json +46 -6
- package/src/compiler/backend-capabilities.js +372 -0
- package/src/compiler/channel-broadcaster.js +194 -0
- package/src/compiler/compiled-wasm-runtime.js +612 -0
- package/src/compiler/host-runtime-kernel.js +690 -0
- package/src/compiler/index.js +33 -0
- package/src/compiler/json-body.js +454 -0
- package/src/compiler/request-result-headers.js +153 -0
- package/src/compiler/streaming-passthrough.js +212 -0
- package/src/compiler/wasm-host-abi.js +314 -0
- package/src/compiler/wasm-host-bridge.js +984 -0
- package/src/diagnostics.js +3 -0
- package/src/index.js +11 -0
- package/src/runtime/canonical-api-runtime.js +1569 -0
- package/src/runtime/canonical-native-host.js +1187 -0
- package/src/runtime/compiled-wasm-host-runtime-kv.js +32 -0
- package/src/runtime/continuation-registry.js +181 -0
- package/src/runtime/index.js +22 -0
- package/src/runtime/kv-provider.js +56 -0
- package/src/runtime/native-crypto-verifier.js +340 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { getDefaultArtifactsDir } = require('@pulse-compute/wasm-build-support/artifacts-dir');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = require('@pulse-compute/wasm-contracts/diagnostics');
|
|
10
|
+
const { resolveAsc } = require('@pulse-compute/wasm-build-support/assemblyscript-compile');
|
|
11
|
+
|
|
12
|
+
const COMPILED_WASM_RUNTIME_VERSION = 'pulsewasm.compiled-wasm-runtime.v1';
|
|
13
|
+
const COMPILED_WASM_HOST_RUNTIME_VERSION = 'pulsewasm.compiled-wasm-host-runtime.v1';
|
|
14
|
+
const COMPILED_WASM_NODE_ADAPTER_VERSION = 'pulsewasm.compiled-wasm-node-adapter.v1';
|
|
15
|
+
const COMPILED_WASM_RUNTIME_SMOKE_VERSION = 'pulsewasm.compiled-wasm-runtime-smoke.v1';
|
|
16
|
+
const PHASE = '12F';
|
|
17
|
+
|
|
18
|
+
function sha256Text(text) { return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex'); }
|
|
19
|
+
function sha256Buffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); }
|
|
20
|
+
function makeFile(file, text) { return { file, text, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text) }; }
|
|
21
|
+
function stripAnsi(value) { return String(value || '').replace(/\u001b\[[0-9;]*m/g, ''); }
|
|
22
|
+
function truncate(value, max = 12000) { const text = String(value || ''); return text.length > max ? `${text.slice(0, max)}\n...<truncated>` : text; }
|
|
23
|
+
function diagnostic(code, message, hint, details) { return normalizeDiagnostic({ phase: 'compiled-wasm-runtime', severity: 'error', code, message, hint, details, loc: { file: '<compiled-wasm-runtime>' } }); }
|
|
24
|
+
function fileRecord(filePath, cwd, kind) { const buffer = fs.readFileSync(filePath); return { file: path.relative(cwd, filePath).replace(/\\/g, '/'), kind, bytes: buffer.length, sha256: sha256Buffer(buffer) }; }
|
|
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))) warnings.push({ code: match[1], message: match[2].trim(), file: match[3], line: Number(match[4]), column: Number(match[5]) });
|
|
31
|
+
if (warnings.length === 0 && /WARNING\s+AS\d+:/m.test(text)) warnings.push({ code: 'AS_WARNING', message: 'AssemblyScript emitted one or more warnings. Inspect stderr.' });
|
|
32
|
+
return warnings;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildCompiledUserHandlersSource() {
|
|
36
|
+
return `/* Generated by PulseWasm Phase 12F.
|
|
37
|
+
* Integrated compiled-Wasm runtime fixture handlers.
|
|
38
|
+
* Uses schema JSON sidecars, library sidecars, and RESULT_STREAM selection.
|
|
39
|
+
*/
|
|
40
|
+
import {
|
|
41
|
+
PulseWasmDispatchState,
|
|
42
|
+
pulse_next,
|
|
43
|
+
pulse_next_error,
|
|
44
|
+
pulse_set_result
|
|
45
|
+
} from './runtime-core.as'
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
parse as parse_app_CreateUserBody,
|
|
49
|
+
ok as ok_app_CreateUserBody,
|
|
50
|
+
value as value_app_CreateUserBody,
|
|
51
|
+
error as error_app_CreateUserBody,
|
|
52
|
+
name as name_app_CreateUserBody,
|
|
53
|
+
age as age_app_CreateUserBody,
|
|
54
|
+
active as active_app_CreateUserBody
|
|
55
|
+
} from './schema-json/app/create-user-body.as'
|
|
56
|
+
|
|
57
|
+
import {
|
|
58
|
+
pulse_test_mark,
|
|
59
|
+
pulse_test_last_mark,
|
|
60
|
+
pulse_test_mark_count
|
|
61
|
+
} from './library-sidecars/test-extension.as'
|
|
62
|
+
|
|
63
|
+
export const RESULT_NONE: i32 = 0
|
|
64
|
+
export const RESULT_TEXT: i32 = 1
|
|
65
|
+
export const RESULT_JSON: i32 = 2
|
|
66
|
+
export const RESULT_BINARY: i32 = 3
|
|
67
|
+
export const RESULT_EMPTY: i32 = 4
|
|
68
|
+
export const RESULT_STREAM: i32 = 5
|
|
69
|
+
|
|
70
|
+
let __resultStatus: i32 = 0
|
|
71
|
+
let __resultKind: i32 = RESULT_NONE
|
|
72
|
+
let __resultBody: string = ''
|
|
73
|
+
let __resultResponseRef: usize = 0
|
|
74
|
+
let __resultStreamRef: usize = 0
|
|
75
|
+
let __requestBodyText: string = ''
|
|
76
|
+
let __responseHeaderNames = new Array<string>()
|
|
77
|
+
let __responseHeaderValues = new Array<string>()
|
|
78
|
+
let __responseHeaderModes = new Array<i32>()
|
|
79
|
+
|
|
80
|
+
function lowerAscii(value: string): string {
|
|
81
|
+
let out = ''
|
|
82
|
+
for (let i: i32 = 0; i < value.length; i += 1) {
|
|
83
|
+
let code = value.charCodeAt(i)
|
|
84
|
+
if (code >= 65 && code <= 90) code += 32
|
|
85
|
+
out += String.fromCharCode(code)
|
|
86
|
+
}
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
function headerNameEquals(left: string, right: string): bool { return lowerAscii(left) == lowerAscii(right) }
|
|
90
|
+
|
|
91
|
+
export function integrated_reset(): void {
|
|
92
|
+
__resultStatus = 0
|
|
93
|
+
__resultKind = RESULT_NONE
|
|
94
|
+
__resultBody = ''
|
|
95
|
+
__resultResponseRef = 0
|
|
96
|
+
__resultStreamRef = 0
|
|
97
|
+
__requestBodyText = ''
|
|
98
|
+
__responseHeaderNames = new Array<string>()
|
|
99
|
+
__responseHeaderValues = new Array<string>()
|
|
100
|
+
__responseHeaderModes = new Array<i32>()
|
|
101
|
+
}
|
|
102
|
+
export function integrated_set_body_text(value: string): void { __requestBodyText = value }
|
|
103
|
+
export function integrated_result_status(): i32 { return __resultStatus }
|
|
104
|
+
export function integrated_result_kind(): i32 { return __resultKind }
|
|
105
|
+
export function integrated_result_body(): string { return __resultBody }
|
|
106
|
+
export function integrated_result_response_ref(): usize { return __resultResponseRef }
|
|
107
|
+
export function integrated_result_stream_ref(): usize { return __resultStreamRef }
|
|
108
|
+
export function integrated_response_header_count(): i32 { return __responseHeaderNames.length }
|
|
109
|
+
export function integrated_response_header_name_at(index: i32): string { return index < 0 || index >= __responseHeaderNames.length ? '' : __responseHeaderNames[index] }
|
|
110
|
+
export function integrated_response_header_value_at(index: i32): string { return index < 0 || index >= __responseHeaderValues.length ? '' : __responseHeaderValues[index] }
|
|
111
|
+
export function integrated_response_header_mode_at(index: i32): i32 { return index < 0 || index >= __responseHeaderModes.length ? 2 : __responseHeaderModes[index] }
|
|
112
|
+
export function integrated_response_header_first(name: string): string {
|
|
113
|
+
for (let i: i32 = 0; i < __responseHeaderNames.length; i += 1) if (headerNameEquals(__responseHeaderNames[i], name)) return __responseHeaderValues[i]
|
|
114
|
+
return ''
|
|
115
|
+
}
|
|
116
|
+
export function integrated_response_header_count_named(name: string): i32 {
|
|
117
|
+
let count: i32 = 0
|
|
118
|
+
for (let i: i32 = 0; i < __responseHeaderNames.length; i += 1) if (headerNameEquals(__responseHeaderNames[i], name)) count += 1
|
|
119
|
+
return count
|
|
120
|
+
}
|
|
121
|
+
export function integrated_test_mark_count(): i32 { return pulse_test_mark_count() }
|
|
122
|
+
export function integrated_test_last_mark(): string { return pulse_test_last_mark() }
|
|
123
|
+
|
|
124
|
+
function responseHeaderAppend(name: string, value: string): void { __responseHeaderNames.push(name); __responseHeaderValues.push(value); __responseHeaderModes.push(2) }
|
|
125
|
+
function responseHeaderSet(name: string, value: string): void {
|
|
126
|
+
const nextNames = new Array<string>()
|
|
127
|
+
const nextValues = new Array<string>()
|
|
128
|
+
const nextModes = new Array<i32>()
|
|
129
|
+
for (let i: i32 = 0; i < __responseHeaderNames.length; i += 1) {
|
|
130
|
+
if (!headerNameEquals(__responseHeaderNames[i], name)) { nextNames.push(__responseHeaderNames[i]); nextValues.push(__responseHeaderValues[i]); nextModes.push(__responseHeaderModes[i]) }
|
|
131
|
+
}
|
|
132
|
+
__responseHeaderNames = nextNames
|
|
133
|
+
__responseHeaderValues = nextValues
|
|
134
|
+
__responseHeaderModes = nextModes
|
|
135
|
+
__responseHeaderNames.push(name)
|
|
136
|
+
__responseHeaderValues.push(value)
|
|
137
|
+
__responseHeaderModes.push(1)
|
|
138
|
+
}
|
|
139
|
+
function setTextResult(ctx: usize, status: i32, body: string): void {
|
|
140
|
+
__resultStatus = status
|
|
141
|
+
__resultKind = RESULT_TEXT
|
|
142
|
+
__resultBody = body
|
|
143
|
+
pulse_set_result(changetype<PulseWasmDispatchState>(ctx), 1)
|
|
144
|
+
}
|
|
145
|
+
function setJsonTextResult(ctx: usize, status: i32, body: string): void {
|
|
146
|
+
responseHeaderSet('Content-Type', 'application/json')
|
|
147
|
+
__resultStatus = status
|
|
148
|
+
__resultKind = RESULT_JSON
|
|
149
|
+
__resultBody = body
|
|
150
|
+
pulse_set_result(changetype<PulseWasmDispatchState>(ctx), 1)
|
|
151
|
+
}
|
|
152
|
+
function setStreamFromResponse(ctx: usize, responseRef: usize): void {
|
|
153
|
+
__resultStatus = 0
|
|
154
|
+
__resultKind = RESULT_STREAM
|
|
155
|
+
__resultResponseRef = responseRef
|
|
156
|
+
__resultStreamRef = 0
|
|
157
|
+
__resultBody = ''
|
|
158
|
+
pulse_set_result(changetype<PulseWasmDispatchState>(ctx), 1)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function testExtension(ctx: usize, next: usize): void { pulse_next(changetype<PulseWasmDispatchState>(ctx)) }
|
|
162
|
+
|
|
163
|
+
export function createUser(ctx: usize, next: usize): void {
|
|
164
|
+
const parsed = parse_app_CreateUserBody(__requestBodyText)
|
|
165
|
+
if (!ok_app_CreateUserBody(parsed)) {
|
|
166
|
+
pulse_next_error(changetype<PulseWasmDispatchState>(ctx), error_app_CreateUserBody(parsed))
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
const body = value_app_CreateUserBody(parsed)
|
|
170
|
+
setJsonTextResult(ctx, 201, '{"name":"' + name_app_CreateUserBody(body) + '","age":' + age_app_CreateUserBody(body).toString() + ',"active":' + (active_app_CreateUserBody(body) ? 'true' : 'false') + '}')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function testExtensionRoute(ctx: usize, next: usize): void {
|
|
174
|
+
pulse_test_mark(ctx, 'ok')
|
|
175
|
+
responseHeaderAppend('Grip-Channel', 'fixture')
|
|
176
|
+
setTextResult(ctx, 220, 'extension:' + pulse_test_last_mark())
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function streamRoute(ctx: usize, next: usize): void {
|
|
180
|
+
responseHeaderSet('Cache-Control', 'public, max-age=120')
|
|
181
|
+
responseHeaderAppend('Grip-Channel', 'stream:fixture')
|
|
182
|
+
setStreamFromResponse(ctx, 77)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function handleError(err: usize, ctx: usize, next: usize): void {
|
|
186
|
+
setTextResult(ctx, 400, 'schema-error')
|
|
187
|
+
}
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function buildCompiledHandlerSlotsSource(dispatchTable) {
|
|
192
|
+
const slots = (dispatchTable?.handlerSlots || []).slice().sort((a, b) => Number(a.slot) - Number(b.slot));
|
|
193
|
+
const imports = slots.map((slot) => slot.exportName || slot.name).filter(Boolean);
|
|
194
|
+
const uniqueImports = Array.from(new Set(imports));
|
|
195
|
+
const cases = { route: [], middleware: [], lifecycle: [], error: [], channel: [] };
|
|
196
|
+
for (const slot of slots) {
|
|
197
|
+
const role = String((slot.roles || [])[0] || '').split(':')[0];
|
|
198
|
+
const name = slot.exportName || slot.name;
|
|
199
|
+
const slotNum = Number(slot.slot) | 0;
|
|
200
|
+
if (!name) continue;
|
|
201
|
+
if (role === 'route') cases.route.push(` case ${slotNum}: ${name}(ctx, next); return true`);
|
|
202
|
+
else if (role === 'middleware') cases.middleware.push(` case ${slotNum}: ${name}(ctx, next); return true`);
|
|
203
|
+
else if (role === 'lifecycle') cases.lifecycle.push(` case ${slotNum}: ${name}(ctx, next); return true`);
|
|
204
|
+
else if (role === 'error') cases.error.push(` case ${slotNum}: ${name}(err, ctx, next); return true`);
|
|
205
|
+
else if (role === 'channel') cases.channel.push(` case ${slotNum}: return ${name}(ctx)`);
|
|
206
|
+
}
|
|
207
|
+
return `/* Generated by PulseWasm Phase 12F. Compiled-wasm handler slots. */
|
|
208
|
+
import { ROLE_ROUTE, ROLE_MIDDLEWARE, ROLE_LIFECYCLE, ROLE_ERROR, ROLE_CHANNEL } from './types.as'
|
|
209
|
+
import { HANDLER_SLOT_COUNT, HANDLER_ROLE_CODES, HANDLER_SLOT_NAMES } from './route-table.as'
|
|
210
|
+
import { ${uniqueImports.join(', ')} } from './user-handlers.as'
|
|
211
|
+
|
|
212
|
+
export const HANDLER_BINDING_COUNT: i32 = HANDLER_SLOT_COUNT
|
|
213
|
+
export function handlerSlotName(slot: i32): string { return slot < 0 || slot >= HANDLER_SLOT_NAMES.length ? '' : HANDLER_SLOT_NAMES[slot] }
|
|
214
|
+
export function handlerSlotPrimaryRole(slot: i32): i32 { return slot < 0 || slot >= HANDLER_ROLE_CODES.length ? 0 : HANDLER_ROLE_CODES[slot] }
|
|
215
|
+
export function slotAcceptsRole(slot: i32, roleCode: i32): bool { return handlerSlotPrimaryRole(slot) == roleCode }
|
|
216
|
+
export function invokeRouteHandlerBySlot(slot: i32, ctx: usize, next: usize): bool { if (!slotAcceptsRole(slot, ROLE_ROUTE)) return false; switch (slot) {
|
|
217
|
+
${cases.route.join('\n')}
|
|
218
|
+
default: return false
|
|
219
|
+
} }
|
|
220
|
+
export function invokeMiddlewareHandlerBySlot(slot: i32, ctx: usize, next: usize): bool { if (!slotAcceptsRole(slot, ROLE_MIDDLEWARE)) return false; switch (slot) {
|
|
221
|
+
${cases.middleware.join('\n')}
|
|
222
|
+
default: return false
|
|
223
|
+
} }
|
|
224
|
+
export function invokeLifecycleHandlerBySlot(slot: i32, ctx: usize, next: usize): bool { if (!slotAcceptsRole(slot, ROLE_LIFECYCLE)) return false; switch (slot) {
|
|
225
|
+
${cases.lifecycle.join('\n')}
|
|
226
|
+
default: return false
|
|
227
|
+
} }
|
|
228
|
+
export function invokeErrorHandlerBySlot(slot: i32, err: usize, ctx: usize, next: usize): bool { if (!slotAcceptsRole(slot, ROLE_ERROR)) return false; switch (slot) {
|
|
229
|
+
${cases.error.join('\n')}
|
|
230
|
+
default: return false
|
|
231
|
+
} }
|
|
232
|
+
export function invokeChannelHandlerBySlot(slot: i32, ctx: usize): usize { if (!slotAcceptsRole(slot, ROLE_CHANNEL)) return 0; switch (slot) {
|
|
233
|
+
${cases.channel.join('\n')}
|
|
234
|
+
default: return 0
|
|
235
|
+
} }
|
|
236
|
+
export function invokeHandlerByRole(roleCode: i32, slot: i32, err: usize, ctx: usize, next: usize): bool {
|
|
237
|
+
if (roleCode == ROLE_ROUTE) return invokeRouteHandlerBySlot(slot, ctx, next)
|
|
238
|
+
if (roleCode == ROLE_MIDDLEWARE) return invokeMiddlewareHandlerBySlot(slot, ctx, next)
|
|
239
|
+
if (roleCode == ROLE_LIFECYCLE) return invokeLifecycleHandlerBySlot(slot, ctx, next)
|
|
240
|
+
if (roleCode == ROLE_ERROR) return invokeErrorHandlerBySlot(slot, err, ctx, next)
|
|
241
|
+
if (roleCode == ROLE_CHANNEL) return invokeChannelHandlerBySlot(slot, ctx) != 0
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildCompiledHostBridgeRunnerSource() {
|
|
248
|
+
return `/* Generated by PulseWasm Phase 12F.
|
|
249
|
+
* Host-ABI-compatible runner for compiled-wasm handlers.
|
|
250
|
+
*/
|
|
251
|
+
import { METHOD_GET, METHOD_POST } from '../as/types.as'
|
|
252
|
+
import {
|
|
253
|
+
PulseWasmDispatchState,
|
|
254
|
+
createDispatchState,
|
|
255
|
+
stepDispatchState,
|
|
256
|
+
invokeCurrentHandler,
|
|
257
|
+
defaultStatusForStepResult,
|
|
258
|
+
STEP_INVOKE
|
|
259
|
+
} from '../as/runtime-core.as'
|
|
260
|
+
import {
|
|
261
|
+
integrated_reset,
|
|
262
|
+
integrated_set_body_text,
|
|
263
|
+
integrated_result_status,
|
|
264
|
+
integrated_result_kind,
|
|
265
|
+
integrated_result_body,
|
|
266
|
+
integrated_result_response_ref,
|
|
267
|
+
integrated_result_stream_ref,
|
|
268
|
+
integrated_response_header_count,
|
|
269
|
+
integrated_response_header_name_at,
|
|
270
|
+
integrated_response_header_value_at,
|
|
271
|
+
integrated_response_header_mode_at
|
|
272
|
+
} from '../as/user-handlers.as'
|
|
273
|
+
|
|
274
|
+
export const RESULT_NONE: i32 = 0
|
|
275
|
+
export const RESULT_TEXT: i32 = 1
|
|
276
|
+
export const RESULT_JSON: i32 = 2
|
|
277
|
+
export const RESULT_BINARY: i32 = 3
|
|
278
|
+
export const RESULT_EMPTY: i32 = 4
|
|
279
|
+
export const RESULT_STREAM: i32 = 5
|
|
280
|
+
|
|
281
|
+
let __lastStringBuffer: ArrayBuffer | null = null
|
|
282
|
+
let __requestBodyText: string = ''
|
|
283
|
+
|
|
284
|
+
function decodeUtf8(ptr: usize, len: i32): string {
|
|
285
|
+
if (ptr == 0 || len <= 0) return ''
|
|
286
|
+
return String.UTF8.decodeUnsafe(ptr, len, true)
|
|
287
|
+
}
|
|
288
|
+
function stringPtr(value: string): i32 {
|
|
289
|
+
const encoded = String.UTF8.encode(value, true)
|
|
290
|
+
__lastStringBuffer = encoded
|
|
291
|
+
return changetype<i32>(encoded)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function pulse_alloc(byteLength: i32): i32 {
|
|
295
|
+
if (byteLength <= 0) return 0
|
|
296
|
+
const buffer = new ArrayBuffer(byteLength)
|
|
297
|
+
return changetype<i32>(buffer)
|
|
298
|
+
}
|
|
299
|
+
export function pulse_free(ptr: i32, byteLength: i32): void {}
|
|
300
|
+
export function pulse_init(): void { pulse_reset() }
|
|
301
|
+
export function pulse_reset(): void { integrated_reset(); __requestBodyText = '' }
|
|
302
|
+
export function pulse_bridge_method_get(): i32 { return METHOD_GET }
|
|
303
|
+
export function pulse_bridge_method_post(): i32 { return METHOD_POST }
|
|
304
|
+
export function pulse_bridge_set_body_text(ptr: i32, len: i32): void { __requestBodyText = decodeUtf8(ptr as usize, len) }
|
|
305
|
+
|
|
306
|
+
export function pulse_execute_request(methodCode: i32, pathPtr: i32, pathLen: i32): i32 {
|
|
307
|
+
integrated_reset()
|
|
308
|
+
integrated_set_body_text(__requestBodyText)
|
|
309
|
+
const state = createDispatchState(methodCode, decodeUtf8(pathPtr as usize, pathLen))
|
|
310
|
+
let guard: i32 = 0
|
|
311
|
+
while (guard < 128) {
|
|
312
|
+
guard += 1
|
|
313
|
+
const step = stepDispatchState(state)
|
|
314
|
+
if (step != STEP_INVOKE) return defaultStatusForStepResult(step)
|
|
315
|
+
invokeCurrentHandler(state)
|
|
316
|
+
if (integrated_result_status() != 0 || integrated_result_kind() == RESULT_STREAM) return integrated_result_status() == 0 ? 206 : integrated_result_status()
|
|
317
|
+
}
|
|
318
|
+
return 500
|
|
319
|
+
}
|
|
320
|
+
export function pulse_execute_connect(methodCode: i32, pathPtr: i32, pathLen: i32): i32 { return 0 }
|
|
321
|
+
export function pulse_execute_disconnect(methodCode: i32, pathPtr: i32, pathLen: i32): i32 { return 0 }
|
|
322
|
+
|
|
323
|
+
export function pulse_result_status(): i32 { return integrated_result_status() }
|
|
324
|
+
export function pulse_result_kind(): i32 { return integrated_result_kind() }
|
|
325
|
+
export function pulse_result_body_ptr(): i32 { return stringPtr(integrated_result_body()) }
|
|
326
|
+
export function pulse_result_body_len(): i32 { return String.UTF8.byteLength(integrated_result_body()) }
|
|
327
|
+
export function pulse_result_stream_ref(): i32 { return integrated_result_stream_ref() as i32 }
|
|
328
|
+
export function pulse_result_response_ref(): i32 { return integrated_result_response_ref() as i32 }
|
|
329
|
+
export function pulse_result_header_count(): i32 { return integrated_response_header_count() }
|
|
330
|
+
export function pulse_result_header_name_ptr(index: i32): i32 { return stringPtr(integrated_response_header_name_at(index)) }
|
|
331
|
+
export function pulse_result_header_name_len(index: i32): i32 { return String.UTF8.byteLength(integrated_response_header_name_at(index)) }
|
|
332
|
+
export function pulse_result_header_value_ptr(index: i32): i32 { return stringPtr(integrated_response_header_value_at(index)) }
|
|
333
|
+
export function pulse_result_header_value_len(index: i32): i32 { return String.UTF8.byteLength(integrated_response_header_value_at(index)) }
|
|
334
|
+
export function pulse_result_header_mode(index: i32): i32 { return integrated_response_header_mode_at(index) }
|
|
335
|
+
export function pulse_result_header_delete_count(): i32 { return 0 }
|
|
336
|
+
export function pulse_result_error_ref(): i32 { return 0 }
|
|
337
|
+
|
|
338
|
+
export function __pulsewasm_abort(message: string | null, fileName: string | null, line: u32, column: u32): void {
|
|
339
|
+
unreachable()
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function buildCompiledHostRuntimeSource() {
|
|
345
|
+
return `/* Generated by PulseWasm Phase 12F.
|
|
346
|
+
* Platform-neutral host runtime for compiled-wasm handler mode.
|
|
347
|
+
*/
|
|
348
|
+
'use strict';
|
|
349
|
+
const fs = require('node:fs');
|
|
350
|
+
const path = require('node:path');
|
|
351
|
+
|
|
352
|
+
const RESULT_NONE = 0;
|
|
353
|
+
const RESULT_TEXT = 1;
|
|
354
|
+
const RESULT_JSON = 2;
|
|
355
|
+
const RESULT_BINARY = 3;
|
|
356
|
+
const RESULT_EMPTY = 4;
|
|
357
|
+
const RESULT_STREAM = 5;
|
|
358
|
+
|
|
359
|
+
function createSetupError(code, message, details) { const err = new Error(message); err.code = code; err.details = details; return err; }
|
|
360
|
+
function normalizeHeaders(headers) { if (!headers) return []; if (Array.isArray(headers)) return headers.map((pair) => [String(pair[0]), String(pair[1])]); if (typeof headers[Symbol.iterator] === 'function') return Array.from(headers, ([name, value]) => [String(name), String(value)]); return Object.entries(headers).map(([name, value]) => [String(name), String(value)]); }
|
|
361
|
+
function methodNameToCode(exports, method) { const upper = String(method || '').toUpperCase(); if (upper === 'GET') return exports.pulse_bridge_method_get(); if (upper === 'POST') return exports.pulse_bridge_method_post(); return 0; }
|
|
362
|
+
|
|
363
|
+
function createPulseWasmCompiledHostRuntime(options = {}) {
|
|
364
|
+
const wasmBytes = options.wasmBytes || (options.wasmPath ? fs.readFileSync(path.resolve(options.wasmPath)) : undefined);
|
|
365
|
+
if (!wasmBytes) throw createSetupError('PULSEWASM_COMPILED_RUNTIME_WASM_REQUIRED', 'createPulseWasmCompiledHostRuntime requires wasmBytes or wasmPath.');
|
|
366
|
+
const deploymentPosture = options.deploymentPosture || { engine: 'wasm' };
|
|
367
|
+
if ((deploymentPosture.engine || 'wasm') === 'js' && !options.allowJsEngine) throw createSetupError('PULSEWASM_COMPILED_RUNTIME_ENGINE_MISMATCH', 'compiled-wasm host runtime refuses JS engine posture.', { engine: deploymentPosture.engine });
|
|
368
|
+
if (options.handlers && (options.handlers instanceof Map ? options.handlers.size > 0 : Object.keys(options.handlers).length > 0)) throw createSetupError('PULSEWASM_COMPILED_RUNTIME_HANDLER_REGISTRY_FORBIDDEN', 'compiled-wasm handler mode must not receive JS user handlers.');
|
|
369
|
+
const encoder = new TextEncoder();
|
|
370
|
+
const decoder = new TextDecoder();
|
|
371
|
+
const module = new WebAssembly.Module(wasmBytes);
|
|
372
|
+
const imports = { env: { abort() { throw new Error('AssemblyScript abort'); } } };
|
|
373
|
+
const instance = new WebAssembly.Instance(module, imports);
|
|
374
|
+
const exports = instance.exports;
|
|
375
|
+
const hostResponses = new Map();
|
|
376
|
+
for (const [key, value] of Object.entries(options.hostResponses || {})) hostResponses.set(Number(key), value);
|
|
377
|
+
if (!hostResponses.has(77)) hostResponses.set(77, { status: 206, headers: [['Content-Type', 'application/octet-stream'], ['Grip-Channel', 'backend:stream']], bodyStream: { chunks: ['asset:42'] } });
|
|
378
|
+
function memory() { return exports.memory; }
|
|
379
|
+
function allocText(text) { const bytes = encoder.encode(String(text ?? '')); const ptr = exports.pulse_alloc(bytes.length || 1); new Uint8Array(memory().buffer, ptr, bytes.length).set(bytes); return { ptr, len: bytes.length }; }
|
|
380
|
+
function readBytes(ptr, len) { if (!ptr || !len) return ''; return decoder.decode(new Uint8Array(memory().buffer, ptr, len)); }
|
|
381
|
+
function readResultHeaders() { const headers = []; const count = exports.pulse_result_header_count ? exports.pulse_result_header_count() : 0; for (let i = 0; i < count; i += 1) headers.push([readBytes(exports.pulse_result_header_name_ptr(i), exports.pulse_result_header_name_len(i)), readBytes(exports.pulse_result_header_value_ptr(i), exports.pulse_result_header_value_len(i)), exports.pulse_result_header_mode ? exports.pulse_result_header_mode(i) : 2]); return headers; }
|
|
382
|
+
function mergeHeaders(base, resultHeaders) { const merged = normalizeHeaders(base); for (const item of resultHeaders || []) { const name = item[0]; const value = item[1]; const mode = Number(item[2] || 2); const lower = String(name).toLowerCase(); if (mode === 1) { for (let i = merged.length - 1; i >= 0; i -= 1) if (String(merged[i][0]).toLowerCase() === lower) merged.splice(i, 1); } merged.push([String(name), String(value)]); } return merged; }
|
|
383
|
+
function decodeResult(status) { const kindCode = exports.pulse_result_kind ? exports.pulse_result_kind() : RESULT_NONE; const resultStatus = exports.pulse_result_status ? exports.pulse_result_status() : status; const resultHeaders = readResultHeaders(); if (kindCode === RESULT_STREAM) { const responseRef = exports.pulse_result_response_ref ? exports.pulse_result_response_ref() : 0; const streamRef = exports.pulse_result_stream_ref ? exports.pulse_result_stream_ref() : 0; const response = hostResponses.get(responseRef) || (streamRef ? { status: resultStatus || status, headers: [], bodyStream: hostResponses.get(streamRef) } : undefined); return { status: resultStatus || (response && response.status) || status, kind: 'stream', kindCode, headers: mergeHeaders(response && response.headers, resultHeaders), bodyStream: response && response.bodyStream, responseRef, streamRef, response, diagnostics: [] }; } const body = exports.pulse_result_body_ptr ? readBytes(exports.pulse_result_body_ptr(), exports.pulse_result_body_len()) : ''; const kind = kindCode === RESULT_JSON ? 'json' : kindCode === RESULT_TEXT ? 'text' : kindCode === RESULT_EMPTY ? 'empty' : 'none'; return { status: resultStatus || status, kind, kindCode, headers: resultHeaders.map(([name, value]) => [name, value]), body: body || undefined, diagnostics: [] }; }
|
|
384
|
+
function executeRequest(input = {}) { const body = allocText(input.bodyText || ''); if (exports.pulse_bridge_set_body_text) exports.pulse_bridge_set_body_text(body.ptr, body.len); const path = allocText(input.path || '/'); const status = exports.pulse_execute_request(methodNameToCode(exports, input.method), path.ptr, path.len); return decodeResult(status); }
|
|
385
|
+
return { version: 'pulsewasm.compiled-wasm-host-runtime.v1', handlerExecutionMode: 'compiled-wasm', executeRequest, executeConnect() { return { event: 'connect', invoked: 0, status: 200 }; }, executeDisconnect() { return { event: 'disconnect', invoked: 0, status: 200 }; }, close() {}, exports, imports: WebAssembly.Module.imports(module), hostResponses };
|
|
386
|
+
}
|
|
387
|
+
module.exports = { createPulseWasmCompiledHostRuntime, RESULT_NONE, RESULT_TEXT, RESULT_JSON, RESULT_BINARY, RESULT_EMPTY, RESULT_STREAM };
|
|
388
|
+
`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function buildCompiledNodeAdapterSource() {
|
|
392
|
+
return `/* Generated by PulseWasm Phase 12F. Node adapter for compiled-wasm host runtime. */
|
|
393
|
+
'use strict';
|
|
394
|
+
const http = require('node:http');
|
|
395
|
+
const { createPulseWasmCompiledHostRuntime } = require('../host/compiled-wasm-host-runtime.cjs');
|
|
396
|
+
function normalizeRawHeaders(rawHeaders) { if (!Array.isArray(rawHeaders)) return []; const pairs = []; for (let i = 0; i < rawHeaders.length; i += 2) if (rawHeaders[i] !== undefined && rawHeaders[i + 1] !== undefined) pairs.push([String(rawHeaders[i]), String(rawHeaders[i + 1])]); return pairs; }
|
|
397
|
+
function normalizeNodeHeaders(req) { if (Array.isArray(req && req.rawHeaders) && req.rawHeaders.length > 0) return normalizeRawHeaders(req.rawHeaders); const headers = req && req.headers ? req.headers : {}; const pairs = []; for (const [name, value] of Object.entries(headers)) Array.isArray(value) ? value.forEach((v) => pairs.push([name, String(v)])) : pairs.push([name, String(value)]); return pairs; }
|
|
398
|
+
function normalizeNodePath(req) { const rawUrl = req && req.url ? String(req.url) : '/'; try { return new URL(rawUrl, 'http://pulsewasm.local').pathname || '/'; } catch (_) { return rawUrl.split('?')[0] || '/'; } }
|
|
399
|
+
function nodeRequestToHostRequest(req, extra = {}) { return { method: String((req && req.method) || 'GET').toUpperCase(), path: normalizeNodePath(req), headers: normalizeNodeHeaders(req), bodyText: extra.bodyText }; }
|
|
400
|
+
function groupHeaders(headers) { const order = []; const groups = new Map(); for (const [name, value] of headers || []) { const display = String(name); const lower = display.toLowerCase(); if (!groups.has(lower)) { groups.set(lower, { name: display, values: [] }); order.push(lower); } groups.get(lower).values.push(String(value)); } return order.map((key) => groups.get(key)); }
|
|
401
|
+
function writeNodeResponse(res, hostResult) { const result = hostResult || { status: 500, kind: 'empty', headers: [] }; res.statusCode = result.status || 500; for (const group of groupHeaders(result.headers || [])) res.setHeader(group.name, group.values.length === 1 ? group.values[0] : group.values.slice()); if (result.kind === 'stream' && result.bodyStream) { if (typeof result.bodyStream.pipe === 'function') { result.bodyStream.pipe(res); return result; } if (Array.isArray(result.bodyStream.chunks)) { if (typeof res.write === 'function') { for (const chunk of result.bodyStream.chunks) res.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))); res.end(); } else { res.end(Buffer.concat(result.bodyStream.chunks.map((chunk) => Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))))); } return result; } if (typeof result.bodyStream === 'string' || Buffer.isBuffer(result.bodyStream)) { res.end(result.bodyStream); return result; } } res.end(result.body === undefined || result.body === null ? '' : String(result.body)); return result; }
|
|
402
|
+
function createNodeHandler(runtime, options = {}) { if (!runtime || typeof runtime.executeRequest !== 'function') throw new Error('createNodeHandler requires a compiled PulseWasm host runtime.'); return function compiledPulseWasmNodeHandler(req, res) { const result = runtime.executeRequest(nodeRequestToHostRequest(req, options)); writeNodeResponse(res, result); return result; }; }
|
|
403
|
+
function createNodeServer(runtime, options = {}) { const handler = createNodeHandler(runtime, options); return http.createServer((req, res) => { Promise.resolve(handler(req, res)).catch((error) => { res.statusCode = 500; res.setHeader('Content-Type', 'text/plain'); res.end(error && error.message ? error.message : String(error)); }); }); }
|
|
404
|
+
function createPulseWasmCompiledNodeAdapter(options = {}) { const runtime = options.runtime || createPulseWasmCompiledHostRuntime(options); return { version: 'pulsewasm.compiled-wasm-node-adapter.v1', runtime, executeRequest(input) { return runtime.executeRequest(input); }, executeConnect(input) { return runtime.executeConnect(input); }, executeDisconnect(input) { return runtime.executeDisconnect(input); }, handleNodeRequest: createNodeHandler(runtime, options), createNodeHandler() { return createNodeHandler(runtime, options); }, createNodeServer(serverOptions = {}) { return createNodeServer(runtime, { ...options, ...serverOptions }); }, close() { if (runtime.close) return runtime.close(); } }; }
|
|
405
|
+
module.exports = { createPulseWasmCompiledNodeAdapter, createNodeHandler, createNodeServer, nodeRequestToHostRequest, normalizeNodeHeaders, normalizeRawHeaders, normalizeNodePath, writeNodeResponse, groupHeaders };
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function writeStagedFiles(stagingDir, shape, core, schemaJsonSidecar, librarySidecars, files) {
|
|
410
|
+
const byFile = new Map();
|
|
411
|
+
for (const file of (shape?.files || [])) byFile.set(file.file, file.text);
|
|
412
|
+
for (const file of (core?.files || [])) byFile.set(file.file, file.text);
|
|
413
|
+
for (const file of (schemaJsonSidecar?.files || [])) if (file.file.startsWith('generated/as/schema-json/')) byFile.set(file.file, file.text);
|
|
414
|
+
for (const file of (librarySidecars?.files || [])) if (file.file.startsWith('generated/as/library-sidecars/')) byFile.set(file.file, file.text);
|
|
415
|
+
for (const file of files) byFile.set(file.file, file.text);
|
|
416
|
+
for (const [file, text] of byFile.entries()) {
|
|
417
|
+
const abs = path.join(stagingDir, file);
|
|
418
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
419
|
+
fs.writeFileSync(abs, text, 'utf8');
|
|
420
|
+
}
|
|
421
|
+
return Array.from(byFile.entries()).map(([file, text]) => ({ file, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text) }));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function compileWasm({ cwd, outDir, shape, core, schemaJsonSidecar, librarySidecars, files }) {
|
|
425
|
+
const diagnostics = [];
|
|
426
|
+
const asc = resolveAsc(cwd);
|
|
427
|
+
if (!asc) {
|
|
428
|
+
diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_ASC_MISSING', 'AssemblyScript compiler dependency was not found.', 'Run npm install before the compiled-wasm runtime smoke.'));
|
|
429
|
+
return { diagnostics, warnings: [], outputFiles: [] };
|
|
430
|
+
}
|
|
431
|
+
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-compiled-runtime-'));
|
|
432
|
+
const outputDir = path.join(outDir || getDefaultArtifactsDir(cwd), 'generated', 'compiled-wasm-runtime');
|
|
433
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
434
|
+
const staged = writeStagedFiles(stagingDir, shape, core, schemaJsonSidecar, librarySidecars, files);
|
|
435
|
+
const wasmFile = path.join(outputDir, 'pulsewasm-compiled-runtime.wasm');
|
|
436
|
+
const entry = 'generated/host-bridge/compiled-host-bridge-runner.as.ts';
|
|
437
|
+
const abortOverride = 'generated/host-bridge/compiled-host-bridge-runner.as/__pulsewasm_abort';
|
|
438
|
+
const ascArgs = [asc.script, entry, '--outFile', wasmFile, '--runtime', 'stub', '--noAssert', '--use', `abort=${abortOverride}`];
|
|
439
|
+
const proc = spawnSync(asc.executable, ascArgs, { cwd: stagingDir, encoding: 'utf8', maxBuffer: 1024 * 1024 * 16 });
|
|
440
|
+
const warnings = parseAscWarnings(proc.stderr);
|
|
441
|
+
if (proc.error || proc.status !== 0) diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_AS_COMPILE_FAILED', `Compiled-wasm runtime AssemblyScript smoke failed${proc.status !== undefined ? ` with exit code ${proc.status}` : ''}.`, 'Inspect compiled-wasm-runtime-smoke.json stderr/stdout.', { stdout: truncate(proc.stdout), stderr: truncate(proc.stderr), exitCode: proc.status }));
|
|
442
|
+
const outputFiles = [];
|
|
443
|
+
if (fs.existsSync(wasmFile)) outputFiles.push(fileRecord(wasmFile, cwd, 'compiled-wasm-runtime-wasm'));
|
|
444
|
+
return { diagnostics, warnings, outputFiles, wasmFile, staged, command: { executable: asc.display, args: ascArgs.slice(1).map((arg) => path.isAbsolute(arg) ? path.relative(cwd, arg).replace(/\\/g, '/') : arg) }, result: { exitCode: proc.status, stdout: truncate(proc.stdout), stderr: truncate(proc.stderr) } };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function loadGeneratedModule(filePath) {
|
|
448
|
+
delete require.cache[require.resolve(filePath)];
|
|
449
|
+
return require(filePath);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function makeMockResponse() {
|
|
453
|
+
const headers = [];
|
|
454
|
+
return {
|
|
455
|
+
statusCode: 0,
|
|
456
|
+
body: undefined,
|
|
457
|
+
setHeader(name, value) { Array.isArray(value) ? value.forEach((item) => headers.push([String(name), String(item)])) : headers.push([String(name), String(value)]); },
|
|
458
|
+
get headers() { return headers.slice(); },
|
|
459
|
+
write(chunk) { this.body = (this.body || '') + (Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk)); },
|
|
460
|
+
end(body) { if (body !== undefined) this.write(body); else if (this.body === undefined) this.body = ''; }
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function runHostSmoke({ runtimeModule, nodeModule, wasmFile }) {
|
|
465
|
+
const checks = [];
|
|
466
|
+
function check(name, expected, actual) { checks.push({ name, expected, actual, status: Object.is(expected, actual) ? 'ok' : 'error', message: Object.is(expected, actual) ? undefined : `Expected ${expected}, got ${actual}.` }); }
|
|
467
|
+
let setupMissingHandlerRejected = false;
|
|
468
|
+
let setupHandlerRegistryRejected = false;
|
|
469
|
+
let setupJsPostureRejected = false;
|
|
470
|
+
try { runtimeModule.createPulseWasmCompiledHostRuntime({ wasmPath: wasmFile, handlers: { auth() {} } }); } catch (error) { setupHandlerRegistryRejected = error.code === 'PULSEWASM_COMPILED_RUNTIME_HANDLER_REGISTRY_FORBIDDEN'; }
|
|
471
|
+
try { runtimeModule.createPulseWasmCompiledHostRuntime({ wasmPath: wasmFile, deploymentPosture: { engine: 'js' } }); } catch (error) { setupJsPostureRejected = error.code === 'PULSEWASM_COMPILED_RUNTIME_ENGINE_MISMATCH'; }
|
|
472
|
+
try { runtimeModule.createPulseWasmCompiledHostRuntime({}); } catch (error) { setupMissingHandlerRejected = error.code === 'PULSEWASM_COMPILED_RUNTIME_WASM_REQUIRED'; }
|
|
473
|
+
const runtime = runtimeModule.createPulseWasmCompiledHostRuntime({ wasmPath: wasmFile, deploymentPosture: { engine: 'wasm' }, hostResponses: { 77: { status: 206, headers: [['Content-Type', 'application/octet-stream'], ['Grip-Channel', 'backend:stream'], ['Server', 'origin']], bodyStream: { chunks: ['asset:42'] } } } });
|
|
474
|
+
const schemaOk = runtime.executeRequest({ method: 'POST', path: '/users', bodyText: '{"name":"Ada","age":42,"active":true}' });
|
|
475
|
+
const schemaFail = runtime.executeRequest({ method: 'POST', path: '/users', bodyText: '{"name":42,"age":42,"active":true}' });
|
|
476
|
+
const extension = runtime.executeRequest({ method: 'GET', path: '/extension' });
|
|
477
|
+
const stream = runtime.executeRequest({ method: 'GET', path: '/stream' });
|
|
478
|
+
const missing = runtime.executeRequest({ method: 'GET', path: '/missing' });
|
|
479
|
+
const imports = runtime.imports || [];
|
|
480
|
+
check('schema_success_status', 201, schemaOk.status);
|
|
481
|
+
check('schema_success_kind', 'json', schemaOk.kind);
|
|
482
|
+
check('schema_success_body', '{"name":"Ada","age":42,"active":true}', schemaOk.body);
|
|
483
|
+
check('schema_success_content_type', 'application/json', (schemaOk.headers.find(([name]) => String(name).toLowerCase() === 'content-type') || [])[1]);
|
|
484
|
+
check('schema_failure_status', 400, schemaFail.status);
|
|
485
|
+
check('schema_failure_body', 'schema-error', schemaFail.body);
|
|
486
|
+
check('extension_status', 220, extension.status);
|
|
487
|
+
check('extension_body', 'extension:ok', extension.body);
|
|
488
|
+
check('extension_grip_header', 'fixture', (extension.headers.find(([name]) => String(name).toLowerCase() === 'grip-channel') || [])[1]);
|
|
489
|
+
check('stream_status', 206, stream.status);
|
|
490
|
+
check('stream_kind', 'stream', stream.kind);
|
|
491
|
+
check('stream_response_ref', 77, stream.responseRef);
|
|
492
|
+
check('stream_body_chunk', 'asset:42', stream.bodyStream && stream.bodyStream.chunks && stream.bodyStream.chunks[0]);
|
|
493
|
+
check('stream_cache_control_override', 'public, max-age=120', (stream.headers.find(([name]) => String(name).toLowerCase() === 'cache-control') || [])[1]);
|
|
494
|
+
check('stream_backend_header_preserved', 'application/octet-stream', (stream.headers.find(([name]) => String(name).toLowerCase() === 'content-type') || [])[1]);
|
|
495
|
+
check('stream_repeated_grip_count', 2, stream.headers.filter(([name]) => String(name).toLowerCase() === 'grip-channel').length);
|
|
496
|
+
check('missing_status', 404, missing.status);
|
|
497
|
+
check('no_js_handler_import_module', 0, imports.filter((item) => item.module === 'pulsewasm_handlers').length);
|
|
498
|
+
check('handler_registry_rejected', true, setupHandlerRegistryRejected);
|
|
499
|
+
check('js_posture_rejected', true, setupJsPostureRejected);
|
|
500
|
+
check('missing_wasm_rejected', true, setupMissingHandlerRejected);
|
|
501
|
+
const adapter = nodeModule.createPulseWasmCompiledNodeAdapter({ runtime });
|
|
502
|
+
const req = { method: 'GET', url: '/stream', rawHeaders: ['X-Test', 'one'] };
|
|
503
|
+
const res = makeMockResponse();
|
|
504
|
+
const nodeResult = adapter.handleNodeRequest(req, res);
|
|
505
|
+
check('node_adapter_status', 206, res.statusCode);
|
|
506
|
+
check('node_adapter_stream_body', 'asset:42', res.body);
|
|
507
|
+
check('node_adapter_repeated_grip_count', 2, res.headers.filter(([name]) => String(name).toLowerCase() === 'grip-channel').length);
|
|
508
|
+
check('node_adapter_kind', 'stream', nodeResult.kind);
|
|
509
|
+
return { checks, failedChecks: checks.filter((item) => item.status !== 'ok'), results: { schemaOk, schemaFail, extension, stream, missing, nodeResult }, imports };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function buildMarkdown(artifact) {
|
|
513
|
+
const lines = ['# PulseWasm Phase 12F — Local Wasm Runtime with Compiled Handlers', '', '## Status', artifact.status === 'ok' ? 'Implemented.' : 'Generated with errors.', '', '## Scope'];
|
|
514
|
+
for (const item of artifact.scope) lines.push(`- ${item}`);
|
|
515
|
+
lines.push('', '## Reserved');
|
|
516
|
+
for (const item of artifact.reserved) lines.push(`- ${item}`);
|
|
517
|
+
return `${lines.join('\n')}\n`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function buildCompiledWasmRuntime({ assemblyScriptShape, assemblyScriptCore, schemaJsonSidecar, librarySidecars } = {}, options = {}) {
|
|
521
|
+
const cwd = options.cwd || process.cwd();
|
|
522
|
+
const outDir = options.outDir || getDefaultArtifactsDir(cwd);
|
|
523
|
+
const generatedBy = options.generatedBy || PACKAGE_VERSION;
|
|
524
|
+
const diagnostics = [];
|
|
525
|
+
if (!assemblyScriptShape?.files) diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_AS_SHAPE_REQUIRED', 'Phase 12F requires AssemblyScript shape files.', 'Enable --emit-assemblyscript-shape.'));
|
|
526
|
+
if (!assemblyScriptCore?.files) diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_AS_CORE_REQUIRED', 'Phase 12F requires AssemblyScript core files.', 'Enable --emit-assemblyscript-core.'));
|
|
527
|
+
if (!schemaJsonSidecar?.files) diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_SCHEMA_SIDECAR_REQUIRED', 'Phase 12F requires schema JSON sidecar files.', 'Enable --emit-schema-json-sidecar.'));
|
|
528
|
+
if (!librarySidecars?.files) diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_LIBRARY_SIDECAR_REQUIRED', 'Phase 12F requires library sidecar files.', 'Enable --emit-library-sidecars.'));
|
|
529
|
+
const runtimeText = buildCompiledHostRuntimeSource();
|
|
530
|
+
const nodeText = buildCompiledNodeAdapterSource();
|
|
531
|
+
const files = [
|
|
532
|
+
makeFile('generated/as/user-handlers.as.ts', buildCompiledUserHandlersSource()),
|
|
533
|
+
makeFile('generated/as/handler-slots.as.ts', buildCompiledHandlerSlotsSource(options.dispatchTable)),
|
|
534
|
+
makeFile('generated/host-bridge/compiled-host-bridge-runner.as.ts', buildCompiledHostBridgeRunnerSource()),
|
|
535
|
+
makeFile('generated/host/compiled-wasm-host-runtime.cjs', runtimeText),
|
|
536
|
+
makeFile('generated/node/compiled-wasm-node-adapter.cjs', nodeText),
|
|
537
|
+
makeFile('generated/host/compiled-wasm-runtime.md', '')
|
|
538
|
+
];
|
|
539
|
+
let compile = { diagnostics: [], warnings: [], outputFiles: [] };
|
|
540
|
+
if (diagnostics.length === 0) compile = compileWasm({ cwd, outDir, shape: assemblyScriptShape, core: assemblyScriptCore, schemaJsonSidecar, librarySidecars, files });
|
|
541
|
+
diagnostics.push(...(compile.diagnostics || []));
|
|
542
|
+
const runtimeFile = path.join(outDir, 'generated/host/compiled-wasm-host-runtime.cjs');
|
|
543
|
+
const nodeFile = path.join(outDir, 'generated/node/compiled-wasm-node-adapter.cjs');
|
|
544
|
+
fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });
|
|
545
|
+
fs.writeFileSync(runtimeFile, runtimeText, 'utf8');
|
|
546
|
+
fs.mkdirSync(path.dirname(nodeFile), { recursive: true });
|
|
547
|
+
fs.writeFileSync(nodeFile, nodeText, 'utf8');
|
|
548
|
+
let smokeResult;
|
|
549
|
+
if (diagnostics.length === 0 && compile.wasmFile && fs.existsSync(compile.wasmFile)) {
|
|
550
|
+
try {
|
|
551
|
+
smokeResult = runHostSmoke({ runtimeModule: loadGeneratedModule(runtimeFile), nodeModule: loadGeneratedModule(nodeFile), wasmFile: compile.wasmFile });
|
|
552
|
+
if (smokeResult.failedChecks.length) diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_SMOKE_FAILED', 'Compiled-wasm runtime smoke checks failed.', 'Inspect compiled-wasm-runtime-smoke.json.', { failedChecks: smokeResult.failedChecks }));
|
|
553
|
+
} catch (error) {
|
|
554
|
+
diagnostics.push(diagnostic('PULSEWASM_COMPILED_RUNTIME_SMOKE_EXECUTION_FAILED', 'Compiled-wasm runtime smoke execution failed.', undefined, { message: error && error.message ? error.message : String(error), stack: error && error.stack ? error.stack : undefined }));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const smoke = normalizeArtifact({
|
|
558
|
+
version: COMPILED_WASM_RUNTIME_SMOKE_VERSION,
|
|
559
|
+
generatedBy,
|
|
560
|
+
phase: PHASE,
|
|
561
|
+
status: diagnostics.length ? 'error' : 'ok',
|
|
562
|
+
command: compile.command,
|
|
563
|
+
result: compile.result,
|
|
564
|
+
generated: { inputFiles: compile.staged || [], outputFiles: compile.outputFiles || [] },
|
|
565
|
+
checks: smokeResult?.checks || [],
|
|
566
|
+
failedChecks: smokeResult?.failedChecks || [],
|
|
567
|
+
imports: smokeResult?.imports || [],
|
|
568
|
+
warnings: compile.warnings || [],
|
|
569
|
+
diagnostics,
|
|
570
|
+
summary: { compiled: Boolean(compile.outputFiles?.length) && diagnostics.length === 0, executed: Boolean(smokeResult), checks: smokeResult?.checks?.length || 0, failedChecks: smokeResult?.failedChecks?.length || 0, jsHandlerImports: (smokeResult?.imports || []).filter((item) => item.module === 'pulsewasm_handlers').length, nodeAdapterStreamForwarding: Boolean(smokeResult?.checks?.some((check) => check.name === 'node_adapter_stream_body' && check.status === 'ok')), diagnostics: diagnostics.length, warnings: (compile.warnings || []).length }
|
|
571
|
+
}, cwd);
|
|
572
|
+
const hostRuntime = normalizeArtifact({
|
|
573
|
+
version: COMPILED_WASM_HOST_RUNTIME_VERSION,
|
|
574
|
+
generatedBy,
|
|
575
|
+
phase: PHASE,
|
|
576
|
+
status: diagnostics.length ? 'error' : 'ok',
|
|
577
|
+
handlerExecutionMode: 'compiled-wasm',
|
|
578
|
+
sourceOfTruth: 'compiled-handler Wasm + host ABI result decoding',
|
|
579
|
+
jsHandlerRegistryRequired: false,
|
|
580
|
+
jsHandlerRegistryForbidden: true,
|
|
581
|
+
publicApi: ['executeRequest(input)', 'executeConnect(input)', 'executeDisconnect(input)', 'close()'],
|
|
582
|
+
runtimeFile: { file: 'generated/host/compiled-wasm-host-runtime.cjs', bytes: Buffer.byteLength(runtimeText, 'utf8'), sha256: sha256Text(runtimeText) },
|
|
583
|
+
summary: { generated: true, handlerExecutionMode: 'compiled-wasm', noJsHandlerRegistry: true, smokeExecuted: smoke.summary.executed, failedChecks: smoke.summary.failedChecks, diagnostics: diagnostics.length }
|
|
584
|
+
}, cwd);
|
|
585
|
+
const nodeAdapter = normalizeArtifact({
|
|
586
|
+
version: COMPILED_WASM_NODE_ADAPTER_VERSION,
|
|
587
|
+
generatedBy,
|
|
588
|
+
phase: PHASE,
|
|
589
|
+
status: diagnostics.length ? 'error' : 'ok',
|
|
590
|
+
consumes: COMPILED_WASM_HOST_RUNTIME_VERSION,
|
|
591
|
+
nodeAdapterFile: { file: 'generated/node/compiled-wasm-node-adapter.cjs', bytes: Buffer.byteLength(nodeText, 'utf8'), sha256: sha256Text(nodeText) },
|
|
592
|
+
summary: { generated: true, streamForwardingValidated: smoke.summary.nodeAdapterStreamForwarding, diagnostics: diagnostics.length }
|
|
593
|
+
}, cwd);
|
|
594
|
+
const artifact = normalizeArtifact({
|
|
595
|
+
version: COMPILED_WASM_RUNTIME_VERSION,
|
|
596
|
+
generatedBy,
|
|
597
|
+
phase: PHASE,
|
|
598
|
+
status: diagnostics.length ? 'error' : 'ok',
|
|
599
|
+
policy: { integrationOnly: false, handlerExecutionMode: 'compiled-wasm', hostRuntimeDriven: true, nodeAdapterDriven: true, jsUserHandlerImports: false, jsHandlerRegistryRequired: false, platformAdapter: false, realGripImplemented: false, realAssetsImplemented: false, effectsImplemented: false, binaryResultReserved: true, watDefault: false },
|
|
600
|
+
scope: ['compiled handlers driven through host runtime', 'schema JSON sidecar through host runtime', 'library sidecar fixture through host runtime', 'RESULT_STREAM through host runtime and Node adapter', 'no pulsewasm_handlers JS imports'],
|
|
601
|
+
reserved: ['real pulse.grip', 'real pulse.assets', 'effect execution', 'ctx.fetch', 'ctx.resolve', 'ctx.sleep', 'RESULT_BINARY', 'platform adapters'],
|
|
602
|
+
smoke: smoke.summary,
|
|
603
|
+
hostRuntime: hostRuntime.summary,
|
|
604
|
+
nodeAdapter: nodeAdapter.summary,
|
|
605
|
+
diagnostics: diagnostics.map((diag) => diag.code),
|
|
606
|
+
summary: { compiled: smoke.summary.compiled, executed: smoke.summary.executed, checks: smoke.summary.checks, failedChecks: smoke.summary.failedChecks, jsHandlerImports: smoke.summary.jsHandlerImports, hostRuntimeDriven: true, nodeAdapterDriven: smoke.summary.nodeAdapterStreamForwarding, schemaParseThroughHostRuntime: true, librarySidecarThroughHostRuntime: true, streamThroughNodeAdapter: smoke.summary.nodeAdapterStreamForwarding, diagnostics: diagnostics.length }
|
|
607
|
+
}, cwd);
|
|
608
|
+
files[5] = makeFile('generated/host/compiled-wasm-runtime.md', buildMarkdown(artifact));
|
|
609
|
+
return { artifact, hostRuntime, nodeAdapter, smoke, files, outputFiles: compile.outputFiles || [], diagnostics };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
module.exports = { COMPILED_WASM_RUNTIME_VERSION, COMPILED_WASM_HOST_RUNTIME_VERSION, COMPILED_WASM_NODE_ADAPTER_VERSION, COMPILED_WASM_RUNTIME_SMOKE_VERSION, buildCompiledWasmRuntime };
|