@pulse-compute/provider-node 0.0.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -1
- package/package.json +61 -6
- package/src/capabilities.js +56 -0
- package/src/compiler/assets-provider-proof.js +434 -0
- package/src/compiler/backend-json-request-body-proof.js +90 -0
- package/src/compiler/index.js +38 -0
- package/src/compiler/node-adapter.js +452 -0
- package/src/compiler/node-compiled-wasm-assets-lifecycle-proof.js +435 -0
- package/src/compiler/request-json-body-proof.js +181 -0
- package/src/compiler/route-handler-compiled-wasm-bridge.js +323 -0
- package/src/compiler/route-handler-effect-proof.js +215 -0
- package/src/compiler/route-handler-live-origin-proof.js +372 -0
- package/src/compiler/schema-decode-result-proof.js +180 -0
- package/src/compiler/schema-response-codec-proof.js +149 -0
- package/src/events/reference-adapter.js +145 -0
- package/src/index.js +57 -0
- package/src/javascript/assets-adapter.js +68 -0
- package/src/javascript/bindings-adapter.js +120 -0
- package/src/javascript/fetch-adapter.js +187 -0
- package/src/javascript/grip-broadcast.js +243 -0
- package/src/javascript/jwt-verifier.js +137 -0
- package/src/javascript/lifecycle.js +69 -0
- package/src/javascript/node-adapter.js +281 -0
- package/src/javascript/package-effects.js +78 -0
- package/src/javascript/runtime-host.js +165 -0
- package/src/javascript/source-package.js +439 -0
- package/src/javascript/support.js +205 -0
- package/src/javascript/target-support-policy.js +134 -0
- package/src/javascript/target.js +37 -0
- package/src/javascript/test-runtime.js +111 -0
- package/src/native/target.js +78 -0
- package/src/runtime/assets-provider.js +776 -0
- package/src/runtime/canonical-api-runtime.js +632 -0
- package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
- package/src/runtime/index.js +17 -0
- package/src/runtime/jwt-verifier.js +264 -0
- package/src/runtime/route-handler-effects.js +757 -0
- package/src/toolchain.js +258 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function loadAssetsContracts() {
|
|
4
|
+
try {
|
|
5
|
+
return require('@pulse-compute/wasm-contracts/assets/contracts');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
8
|
+
return require('../../../../wasm/packages/contracts/src/assets/contracts.js');
|
|
9
|
+
}
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadDiagnostics() {
|
|
15
|
+
try {
|
|
16
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
19
|
+
return require('../../../../wasm/packages/contracts/src/diagnostics.js');
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const assetsContracts = loadAssetsContracts();
|
|
26
|
+
const { normalizeDiagnostic } = loadDiagnostics();
|
|
27
|
+
|
|
28
|
+
function clone(value) {
|
|
29
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
30
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeHeaders(headers) {
|
|
35
|
+
if (!headers) return [];
|
|
36
|
+
if (Array.isArray(headers)) {
|
|
37
|
+
return headers
|
|
38
|
+
.filter((pair) => pair && pair[0] !== undefined && pair[1] !== undefined)
|
|
39
|
+
.map((pair) => [String(pair[0]), String(pair[1])]);
|
|
40
|
+
}
|
|
41
|
+
if (typeof headers[Symbol.iterator] === 'function' && typeof headers !== 'string') {
|
|
42
|
+
return Array.from(headers, ([name, value]) => [String(name), String(value)]);
|
|
43
|
+
}
|
|
44
|
+
return Object.entries(headers).map(([name, value]) => [String(name), String(value)]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function headerValue(headers, name) {
|
|
48
|
+
const lower = String(name).toLowerCase();
|
|
49
|
+
const found = normalizeHeaders(headers).find(([key]) => String(key).toLowerCase() === lower);
|
|
50
|
+
return found ? found[1] : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function setHeader(headers, name, value) {
|
|
54
|
+
const out = normalizeHeaders(headers).filter(([key]) => String(key).toLowerCase() !== String(name).toLowerCase());
|
|
55
|
+
if (value !== undefined && value !== null) out.push([String(name), String(value)]);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function appendMissingHeader(headers, name, value) {
|
|
60
|
+
if (headerValue(headers, name) !== undefined) return headers;
|
|
61
|
+
return headers.concat([[String(name), String(value)]]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeKey(key) {
|
|
65
|
+
const text = String(key || '');
|
|
66
|
+
return text.startsWith('/') ? text : `/${text}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeAssetRecord(value) {
|
|
70
|
+
if (value === undefined || value === null) return undefined;
|
|
71
|
+
if (typeof value === 'string' || Buffer.isBuffer(value)) {
|
|
72
|
+
return { status: 200, body: value, headers: [] };
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === 'object') {
|
|
75
|
+
return {
|
|
76
|
+
status: value.status || 200,
|
|
77
|
+
body: value.body !== undefined ? value.body : value.text !== undefined ? value.text : value.content,
|
|
78
|
+
headers: normalizeHeaders(value.headers),
|
|
79
|
+
cacheControl: value.cacheControl,
|
|
80
|
+
contentType: value.contentType,
|
|
81
|
+
payloadMode: value.payloadMode
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { status: 200, body: String(value), headers: [] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeStores(stores) {
|
|
88
|
+
const out = new Map();
|
|
89
|
+
for (const [storeName, records] of Object.entries(stores || {})) {
|
|
90
|
+
const byKey = new Map();
|
|
91
|
+
for (const [key, value] of Object.entries(records || {})) {
|
|
92
|
+
byKey.set(normalizeKey(key), normalizeAssetRecord(value));
|
|
93
|
+
}
|
|
94
|
+
out.set(String(storeName), byKey);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizePlan(input) {
|
|
100
|
+
const artifact = input && input.artifact ? input.artifact : input;
|
|
101
|
+
if (!artifact || typeof artifact !== 'object') return undefined;
|
|
102
|
+
return {
|
|
103
|
+
...artifact,
|
|
104
|
+
entries: Array.isArray(artifact.entries) ? artifact.entries.map(clone) : [],
|
|
105
|
+
payloadModes: artifact.payloadModes ? clone(artifact.payloadModes) : {
|
|
106
|
+
classification: assetsContracts.assetsPayloadModeClassification(),
|
|
107
|
+
responsePolicy: clone(assetsContracts.ASSETS_RESPONSE_POLICY)
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function entriesFromPlan(plan) {
|
|
113
|
+
return (plan && Array.isArray(plan.entries) ? plan.entries : [])
|
|
114
|
+
.filter((entry) => entry && entry.status !== 'unsupported' && entry.lookup && entry.lookup.store && entry.lookup.key)
|
|
115
|
+
.map(clone);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createDefaultStoresFromPlan(plan) {
|
|
119
|
+
const stores = {};
|
|
120
|
+
for (const entry of entriesFromPlan(plan)) {
|
|
121
|
+
const lookup = entry.lookup;
|
|
122
|
+
const store = lookup.store;
|
|
123
|
+
const key = normalizeKey(lookup.key);
|
|
124
|
+
if (!stores[store]) stores[store] = {};
|
|
125
|
+
if (!stores[store][key]) {
|
|
126
|
+
stores[store][key] = {
|
|
127
|
+
status: entry.response && entry.response.status ? entry.response.status : 200,
|
|
128
|
+
body: `asset:${store}:${key}`,
|
|
129
|
+
headers: [
|
|
130
|
+
['Content-Type', 'text/plain; charset=utf-8'],
|
|
131
|
+
['X-PulseWasm-Asset-Store', store],
|
|
132
|
+
['X-PulseWasm-Asset-Key', key]
|
|
133
|
+
],
|
|
134
|
+
cacheControl: lookup.cacheControl
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return stores;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function makeDiagnostic(code, message, severity, details) {
|
|
142
|
+
return normalizeDiagnostic({
|
|
143
|
+
phase: 'node-assets-provider',
|
|
144
|
+
severity: severity || 'error',
|
|
145
|
+
code,
|
|
146
|
+
message,
|
|
147
|
+
hint: details && details.hint,
|
|
148
|
+
details,
|
|
149
|
+
loc: { file: '<node-assets-provider>' }
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function unsupportedMethodResult(method) {
|
|
154
|
+
return {
|
|
155
|
+
status: 405,
|
|
156
|
+
kind: 'empty',
|
|
157
|
+
headers: [['Allow', assetsContracts.ASSETS_ALLOWED_METHODS.join(', ')]],
|
|
158
|
+
body: undefined,
|
|
159
|
+
diagnostics: [makeDiagnostic(
|
|
160
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.UNSUPPORTED_METHOD,
|
|
161
|
+
`Node assets provider proof only accepts ${assetsContracts.ASSETS_ALLOWED_METHODS.join(' / ')} requests.`,
|
|
162
|
+
'error',
|
|
163
|
+
{ method, allowedMethods: assetsContracts.ASSETS_ALLOWED_METHODS }
|
|
164
|
+
)]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function reservedBinaryResult(entry) {
|
|
169
|
+
return {
|
|
170
|
+
status: 501,
|
|
171
|
+
kind: 'empty',
|
|
172
|
+
headers: [['Content-Type', 'text/plain; charset=utf-8']],
|
|
173
|
+
body: undefined,
|
|
174
|
+
entryId: entry && entry.id,
|
|
175
|
+
diagnostics: [makeDiagnostic(
|
|
176
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED,
|
|
177
|
+
'Node assets provider proof explicitly rejects binary-buffer assets until the binary body ABI is implemented.',
|
|
178
|
+
'error',
|
|
179
|
+
{ payloadMode: 'binary-buffer', status: 'reserved-with-diagnostic' }
|
|
180
|
+
)]
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function unsupportedPayloadResult(entry, payloadMode) {
|
|
185
|
+
return {
|
|
186
|
+
status: 415,
|
|
187
|
+
kind: 'empty',
|
|
188
|
+
headers: [['Content-Type', 'text/plain; charset=utf-8']],
|
|
189
|
+
body: undefined,
|
|
190
|
+
entryId: entry && entry.id,
|
|
191
|
+
diagnostics: [makeDiagnostic(
|
|
192
|
+
'PULSEWASM_ASSETS_PAYLOAD_MODE_UNSUPPORTED',
|
|
193
|
+
`Node assets provider proof cannot serve assets payload mode ${JSON.stringify(payloadMode)}.`,
|
|
194
|
+
'error',
|
|
195
|
+
{ payloadMode }
|
|
196
|
+
)]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function notFoundResult(entry, request) {
|
|
201
|
+
const lookup = entry && entry.lookup ? entry.lookup : {};
|
|
202
|
+
const passThrough = lookup.passThroughOn404 !== false;
|
|
203
|
+
const headers = [
|
|
204
|
+
['Content-Type', 'text/plain; charset=utf-8'],
|
|
205
|
+
['X-PulseWasm-Asset-Miss', '1']
|
|
206
|
+
];
|
|
207
|
+
const body = passThrough || String(request.method || 'GET').toUpperCase() === 'HEAD'
|
|
208
|
+
? undefined
|
|
209
|
+
: `asset not found:${lookup.store || '<unknown>'}:${normalizeKey(lookup.key || request.path || '/')}`;
|
|
210
|
+
return {
|
|
211
|
+
status: 404,
|
|
212
|
+
kind: body === undefined ? 'empty' : 'text',
|
|
213
|
+
headers,
|
|
214
|
+
body,
|
|
215
|
+
passThroughOn404: passThrough,
|
|
216
|
+
entryId: entry && entry.id,
|
|
217
|
+
diagnostics: [makeDiagnostic(
|
|
218
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NODE_PROVIDER_ASSET_NOT_FOUND,
|
|
219
|
+
'Node assets provider proof could not resolve the static store/key from the lowering plan.',
|
|
220
|
+
'info',
|
|
221
|
+
{ store: lookup.store, key: lookup.key, path: request.path, passThroughOn404: passThrough }
|
|
222
|
+
)]
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function resolveAsset(stores, store, key) {
|
|
227
|
+
const byKey = stores.get(String(store));
|
|
228
|
+
if (!byKey) return undefined;
|
|
229
|
+
return byKey.get(normalizeKey(key)) || byKey.get(String(key));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function responseStatus(entry, asset) {
|
|
233
|
+
if (entry && entry.response && entry.response.status) return entry.response.status;
|
|
234
|
+
return asset && asset.status ? asset.status : 200;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function assetHeaders(entry, asset) {
|
|
238
|
+
const lookup = entry.lookup || {};
|
|
239
|
+
let headers = normalizeHeaders(asset && asset.headers);
|
|
240
|
+
headers = appendMissingHeader(headers, 'Content-Type', (asset && asset.contentType) || 'text/plain; charset=utf-8');
|
|
241
|
+
headers = setHeader(headers, 'X-PulseWasm-Asset-Store', lookup.store);
|
|
242
|
+
headers = setHeader(headers, 'X-PulseWasm-Asset-Key', normalizeKey(lookup.key));
|
|
243
|
+
const cacheControl = lookup.cacheControl || (asset && asset.cacheControl);
|
|
244
|
+
if (cacheControl) headers = setHeader(headers, 'Cache-Control', cacheControl);
|
|
245
|
+
return headers;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function bodyText(asset) {
|
|
249
|
+
const body = asset && asset.body !== undefined ? asset.body : '';
|
|
250
|
+
return Buffer.isBuffer(body) ? body.toString('utf8') : String(body);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function streamFromText(text) {
|
|
254
|
+
return { chunks: [String(text)] };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function payloadModeForEntry(entry, asset) {
|
|
258
|
+
return (entry && entry.response && entry.response.payloadMode) ||
|
|
259
|
+
(entry && entry.lookup && entry.lookup.payloadMode) ||
|
|
260
|
+
(asset && asset.payloadMode) ||
|
|
261
|
+
'text-response-body';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function matchEntry(entries, request) {
|
|
265
|
+
const method = String(request.method || 'GET').toUpperCase();
|
|
266
|
+
const path = normalizeKey(request.path || '/');
|
|
267
|
+
const candidates = entries.filter((entry) => normalizeKey(entry.lookup.key) === path);
|
|
268
|
+
return candidates.find((entry) => String(entry.lookup.method || 'GET').toUpperCase() === method) ||
|
|
269
|
+
(method === 'HEAD' ? candidates.find((entry) => String(entry.lookup.method || 'GET').toUpperCase() === 'GET') : undefined) ||
|
|
270
|
+
candidates[0];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createNodeAssetsProvider(options = {}) {
|
|
274
|
+
const plan = normalizePlan(options.plan || options.assetsLoweringPlan);
|
|
275
|
+
const diagnostics = [];
|
|
276
|
+
if (!plan) {
|
|
277
|
+
diagnostics.push(makeDiagnostic(
|
|
278
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NODE_PROVIDER_PLAN_REQUIRED,
|
|
279
|
+
'Node assets provider proof requires an assets-lowering-plan artifact.',
|
|
280
|
+
'error',
|
|
281
|
+
{ hint: 'Emit --assets-lowering-plan before invoking the Node assets provider proof.' }
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
const entries = entriesFromPlan(plan);
|
|
285
|
+
const stores = normalizeStores(options.stores || createDefaultStoresFromPlan(plan));
|
|
286
|
+
|
|
287
|
+
function executeRequest(input = {}) {
|
|
288
|
+
const request = {
|
|
289
|
+
method: String(input.method || 'GET').toUpperCase(),
|
|
290
|
+
path: normalizeKey(input.path || input.key || '/')
|
|
291
|
+
};
|
|
292
|
+
if (!assetsContracts.ASSETS_ALLOWED_METHODS.includes(request.method)) return unsupportedMethodResult(request.method);
|
|
293
|
+
const entry = matchEntry(entries, request);
|
|
294
|
+
if (!entry) return notFoundResult({ lookup: { key: request.path, store: '<none>', passThroughOn404: true } }, request);
|
|
295
|
+
const asset = resolveAsset(stores, entry.lookup.store, entry.lookup.key);
|
|
296
|
+
if (!asset) return notFoundResult(entry, request);
|
|
297
|
+
const payloadMode = payloadModeForEntry(entry, asset);
|
|
298
|
+
if (payloadMode === 'binary-buffer') return reservedBinaryResult(entry);
|
|
299
|
+
const mode = assetsContracts.assetsPayloadMode(payloadMode);
|
|
300
|
+
if (mode && mode.status === 'unsupported') return unsupportedPayloadResult(entry, payloadMode);
|
|
301
|
+
const headers = assetHeaders(entry, asset);
|
|
302
|
+
const status = responseStatus(entry, asset);
|
|
303
|
+
if (request.method === 'HEAD') {
|
|
304
|
+
return { status, kind: 'empty', headers, body: undefined, entryId: entry.id, lookup: clone(entry.lookup), diagnostics: [] };
|
|
305
|
+
}
|
|
306
|
+
if (payloadMode === 'stream-pass-through-response') {
|
|
307
|
+
return {
|
|
308
|
+
status,
|
|
309
|
+
kind: 'stream',
|
|
310
|
+
headers,
|
|
311
|
+
bodyStream: streamFromText(bodyText(asset)),
|
|
312
|
+
entryId: entry.id,
|
|
313
|
+
lookup: clone(entry.lookup),
|
|
314
|
+
payloadMode,
|
|
315
|
+
providerStreamWired: true,
|
|
316
|
+
diagnostics: []
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
status,
|
|
321
|
+
kind: 'text',
|
|
322
|
+
headers,
|
|
323
|
+
body: bodyText(asset),
|
|
324
|
+
entryId: entry.id,
|
|
325
|
+
lookup: clone(entry.lookup),
|
|
326
|
+
payloadMode,
|
|
327
|
+
diagnostics: []
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
version: assetsContracts.ASSETS_NODE_PROVIDER_RUNTIME_VERSION,
|
|
333
|
+
provider: 'node',
|
|
334
|
+
mode: options.mode || 'local-static-fixture',
|
|
335
|
+
plan,
|
|
336
|
+
entries: entries.map(clone),
|
|
337
|
+
diagnostics,
|
|
338
|
+
executeRequest,
|
|
339
|
+
lookup(store, key) { return resolveAsset(stores, store, key); },
|
|
340
|
+
stores
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
function stripLeadingSlash(value) {
|
|
346
|
+
return normalizeKey(value).replace(/^\/+/, '');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function contentTypeForAssetPath(file) {
|
|
350
|
+
const ext = String(file || '').split('.').pop().toLowerCase();
|
|
351
|
+
if (ext === 'html') return 'text/html; charset=utf-8';
|
|
352
|
+
if (ext === 'js' || ext === 'mjs' || ext === 'cjs') return 'application/javascript; charset=utf-8';
|
|
353
|
+
if (ext === 'css') return 'text/css; charset=utf-8';
|
|
354
|
+
if (ext === 'json') return 'application/json; charset=utf-8';
|
|
355
|
+
if (ext === 'txt' || ext === 'md') return 'text/plain; charset=utf-8';
|
|
356
|
+
return 'text/plain; charset=utf-8';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function assetConfigFromOptions(options = {}) {
|
|
360
|
+
const resolved = options.resolvedConfig && (options.resolvedConfig.artifact || options.resolvedConfig);
|
|
361
|
+
return options.assetConfig ||
|
|
362
|
+
options.assets ||
|
|
363
|
+
(options.config && options.config.assets) ||
|
|
364
|
+
(options.config && options.config.runtime && options.config.runtime.assets) ||
|
|
365
|
+
(resolved && resolved.runtime && resolved.runtime.assets) ||
|
|
366
|
+
(resolved && resolved.config && resolved.config.runtime && resolved.config.runtime.assets);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function createStoresFromAssetConfig(options = {}) {
|
|
370
|
+
const plan = normalizePlan(options.plan || options.assetsLoweringPlan);
|
|
371
|
+
const entries = entriesFromPlan(plan);
|
|
372
|
+
const assetConfig = assetConfigFromOptions(options);
|
|
373
|
+
const configRoot = options.configRoot || options.cwd || process.cwd();
|
|
374
|
+
const diagnostics = [];
|
|
375
|
+
const stores = {};
|
|
376
|
+
const configuredStores = [];
|
|
377
|
+
|
|
378
|
+
if (!assetConfig || !assetConfig.stores || typeof assetConfig.stores !== 'object') {
|
|
379
|
+
diagnostics.push(makeDiagnostic(
|
|
380
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_REQUIRED,
|
|
381
|
+
'Node compiled-Wasm assets lifecycle requires an explicit runtime.assets.stores configuration.',
|
|
382
|
+
'error',
|
|
383
|
+
{ hint: 'Configure runtime.assets.stores.public with mode "local", dir, methods, and responseBody.' }
|
|
384
|
+
));
|
|
385
|
+
return { stores, diagnostics, configuredStores, assetConfig };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const neededStores = Array.from(new Set(entries.map((entry) => entry.lookup && entry.lookup.store).filter(Boolean)));
|
|
389
|
+
for (const storeName of neededStores) {
|
|
390
|
+
const storeConfig = assetConfig.stores[storeName];
|
|
391
|
+
if (!storeConfig) {
|
|
392
|
+
diagnostics.push(makeDiagnostic(
|
|
393
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_NOT_CONFIGURED,
|
|
394
|
+
`Node compiled-Wasm assets lifecycle needs store ${JSON.stringify(storeName)} but it is not configured.`,
|
|
395
|
+
'error',
|
|
396
|
+
{ store: storeName, configuredStores: Object.keys(assetConfig.stores || {}) }
|
|
397
|
+
));
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (storeConfig.mode && storeConfig.mode !== 'local') {
|
|
401
|
+
diagnostics.push(makeDiagnostic(
|
|
402
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_INVALID,
|
|
403
|
+
`Node compiled-Wasm assets lifecycle only supports local asset stores in this proof, got ${JSON.stringify(storeConfig.mode)}.`,
|
|
404
|
+
'error',
|
|
405
|
+
{ store: storeName, mode: storeConfig.mode }
|
|
406
|
+
));
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (!storeConfig.dir) {
|
|
410
|
+
diagnostics.push(makeDiagnostic(
|
|
411
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_INVALID,
|
|
412
|
+
`Node compiled-Wasm assets lifecycle local store ${JSON.stringify(storeName)} must declare dir.`,
|
|
413
|
+
'error',
|
|
414
|
+
{ store: storeName }
|
|
415
|
+
));
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const methods = Array.isArray(storeConfig.methods) ? storeConfig.methods.map((method) => String(method).toUpperCase()) : assetsContracts.ASSETS_ALLOWED_METHODS.slice();
|
|
419
|
+
const responseBody = Array.isArray(storeConfig.responseBody) ? storeConfig.responseBody.map(String) : ['text'];
|
|
420
|
+
const storeDir = require('node:path').resolve(configRoot, storeConfig.dir);
|
|
421
|
+
configuredStores.push({ store: storeName, mode: storeConfig.mode || 'local', dir: storeDir, methods, responseBody, cacheControl: storeConfig.cacheControl });
|
|
422
|
+
if (!stores[storeName]) stores[storeName] = {};
|
|
423
|
+
|
|
424
|
+
for (const entry of entries.filter((candidate) => candidate.lookup && candidate.lookup.store === storeName)) {
|
|
425
|
+
const lookup = entry.lookup || {};
|
|
426
|
+
const method = String(lookup.method || 'GET').toUpperCase();
|
|
427
|
+
if (!methods.includes(method)) {
|
|
428
|
+
diagnostics.push(makeDiagnostic(
|
|
429
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_METHOD_UNSUPPORTED,
|
|
430
|
+
`Node compiled-Wasm assets lifecycle store ${JSON.stringify(storeName)} does not allow ${method}.`,
|
|
431
|
+
'error',
|
|
432
|
+
{ store: storeName, method, allowedMethods: methods }
|
|
433
|
+
));
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (!responseBody.includes('text')) {
|
|
437
|
+
diagnostics.push(makeDiagnostic(
|
|
438
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_INVALID,
|
|
439
|
+
`Node compiled-Wasm assets lifecycle store ${JSON.stringify(storeName)} must allow text response bodies for Pass 49.`,
|
|
440
|
+
'error',
|
|
441
|
+
{ store: storeName, responseBody }
|
|
442
|
+
));
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const rel = stripLeadingSlash(lookup.key);
|
|
446
|
+
const file = require('node:path').resolve(storeDir, rel);
|
|
447
|
+
if (!file.startsWith(storeDir + require('node:path').sep) && file !== storeDir) {
|
|
448
|
+
diagnostics.push(makeDiagnostic(
|
|
449
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_CONFIG_INVALID,
|
|
450
|
+
`Node compiled-Wasm assets lifecycle rejected asset key ${JSON.stringify(lookup.key)} because it escapes the configured store directory.`,
|
|
451
|
+
'error',
|
|
452
|
+
{ store: storeName, key: lookup.key, dir: storeDir }
|
|
453
|
+
));
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (!require('node:fs').existsSync(file) || !require('node:fs').statSync(file).isFile()) {
|
|
457
|
+
diagnostics.push(makeDiagnostic(
|
|
458
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STORE_ASSET_FILE_NOT_FOUND,
|
|
459
|
+
`Node compiled-Wasm assets lifecycle could not find ${JSON.stringify(lookup.key)} in configured store ${JSON.stringify(storeName)}.`,
|
|
460
|
+
'error',
|
|
461
|
+
{ store: storeName, key: lookup.key, file }
|
|
462
|
+
));
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const body = require('node:fs').readFileSync(file, 'utf8');
|
|
466
|
+
stores[storeName][normalizeKey(lookup.key)] = {
|
|
467
|
+
status: entry.response && entry.response.status ? entry.response.status : 200,
|
|
468
|
+
body,
|
|
469
|
+
cacheControl: lookup.cacheControl || storeConfig.cacheControl,
|
|
470
|
+
headers: [
|
|
471
|
+
['Content-Type', contentTypeForAssetPath(file)],
|
|
472
|
+
['X-PulseWasm-Compiled-Assets', '1']
|
|
473
|
+
],
|
|
474
|
+
payloadMode: 'text-response-body'
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return { stores, diagnostics, configuredStores, assetConfig };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function methodCodeForAssetMethod(method) {
|
|
483
|
+
const upper = String(method || 'GET').toUpperCase();
|
|
484
|
+
return upper === 'HEAD' ? 2 : 1;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function methodFromAssetMethodCode(code) {
|
|
488
|
+
return Number(code) === 2 ? 'HEAD' : 'GET';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function encodeU32(value) {
|
|
492
|
+
let n = Number(value >>> 0);
|
|
493
|
+
const out = [];
|
|
494
|
+
do {
|
|
495
|
+
let byte = n & 0x7f;
|
|
496
|
+
n >>>= 7;
|
|
497
|
+
if (n !== 0) byte |= 0x80;
|
|
498
|
+
out.push(byte);
|
|
499
|
+
} while (n !== 0);
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function encodeString(value) {
|
|
504
|
+
const bytes = Array.from(Buffer.from(String(value), 'utf8'));
|
|
505
|
+
return [...encodeU32(bytes.length), ...bytes];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function section(id, payload) {
|
|
509
|
+
return [id, ...encodeU32(payload.length), ...payload];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function vec(items) {
|
|
513
|
+
return [...encodeU32(items.length), ...items.flat()];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function instrI32Const(value) {
|
|
517
|
+
return [0x41, ...encodeU32(Number(value))];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function makeTerminalAssetsWasm(effects = []) {
|
|
521
|
+
const normalizedEffects = effects.map((effect, index) => ({
|
|
522
|
+
index,
|
|
523
|
+
id: effect.id || `asset-${index}`,
|
|
524
|
+
store: String(effect.store || (effect.lookup && effect.lookup.store) || 'public'),
|
|
525
|
+
key: normalizeKey(effect.key || (effect.lookup && effect.lookup.key) || '/'),
|
|
526
|
+
method: String(effect.method || (effect.lookup && effect.lookup.method) || 'GET').toUpperCase()
|
|
527
|
+
}));
|
|
528
|
+
let offset = 16;
|
|
529
|
+
const data = [];
|
|
530
|
+
const layouts = [];
|
|
531
|
+
for (const effect of normalizedEffects) {
|
|
532
|
+
const storeBytes = Buffer.from(effect.store, 'utf8');
|
|
533
|
+
const keyBytes = Buffer.from(effect.key, 'utf8');
|
|
534
|
+
const storePtr = offset;
|
|
535
|
+
data.push({ offset: storePtr, bytes: Array.from(storeBytes) });
|
|
536
|
+
offset += storeBytes.length + 1;
|
|
537
|
+
const keyPtr = offset;
|
|
538
|
+
data.push({ offset: keyPtr, bytes: Array.from(keyBytes) });
|
|
539
|
+
offset += keyBytes.length + 1;
|
|
540
|
+
layouts.push({ ...effect, storePtr, storeLen: storeBytes.length, keyPtr, keyLen: keyBytes.length, methodCode: methodCodeForAssetMethod(effect.method) });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const typeSection = section(1, vec([
|
|
544
|
+
[0x60, ...vec([[0x7f], [0x7f], [0x7f], [0x7f], [0x7f], [0x7f]]), ...vec([[0x7f]])],
|
|
545
|
+
[0x60, ...vec([[0x7f], [0x7f]]), ...vec([[0x7f]])],
|
|
546
|
+
[0x60, ...vec([]), ...vec([[0x7f]])]
|
|
547
|
+
]));
|
|
548
|
+
const importSection = section(2, vec([
|
|
549
|
+
[...encodeString('pulse_assets'), ...encodeString('pulse_assets_lookup'), 0x00, ...encodeU32(0)],
|
|
550
|
+
[...encodeString('pulse_assets'), ...encodeString('pulse_assets_respond'), 0x00, ...encodeU32(1)]
|
|
551
|
+
]));
|
|
552
|
+
const functionSection = section(3, vec(layouts.map(() => encodeU32(2))));
|
|
553
|
+
const memorySection = section(5, vec([[0x00, ...encodeU32(1)]]));
|
|
554
|
+
const exports = [[...encodeString('memory'), 0x02, ...encodeU32(0)]];
|
|
555
|
+
layouts.forEach((layout, index) => {
|
|
556
|
+
exports.push([...encodeString(`pulse_assets_route_${index}`), 0x00, ...encodeU32(2 + index)]);
|
|
557
|
+
});
|
|
558
|
+
const exportSection = section(7, vec(exports));
|
|
559
|
+
const codeBodies = layouts.map((layout) => {
|
|
560
|
+
const instructions = [
|
|
561
|
+
...instrI32Const(layout.storePtr),
|
|
562
|
+
...instrI32Const(layout.storeLen),
|
|
563
|
+
...instrI32Const(layout.keyPtr),
|
|
564
|
+
...instrI32Const(layout.keyLen),
|
|
565
|
+
...instrI32Const(layout.methodCode),
|
|
566
|
+
...instrI32Const(0),
|
|
567
|
+
0x10, ...encodeU32(0),
|
|
568
|
+
...instrI32Const(0),
|
|
569
|
+
0x10, ...encodeU32(1),
|
|
570
|
+
0x0b
|
|
571
|
+
];
|
|
572
|
+
const body = [...vec([]), ...instructions];
|
|
573
|
+
return [...encodeU32(body.length), ...body];
|
|
574
|
+
});
|
|
575
|
+
const codeSection = section(10, vec(codeBodies));
|
|
576
|
+
const dataSegments = data.map((segment) => [0x00, ...instrI32Const(segment.offset), 0x0b, ...vec(segment.bytes.map((byte) => [byte]))]);
|
|
577
|
+
const dataSection = section(11, vec(dataSegments));
|
|
578
|
+
const bytes = Buffer.from([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ...typeSection, ...importSection, ...functionSection, ...memorySection, ...exportSection, ...codeSection, ...dataSection]);
|
|
579
|
+
const module = new WebAssembly.Module(bytes);
|
|
580
|
+
return {
|
|
581
|
+
module,
|
|
582
|
+
bytes,
|
|
583
|
+
effects: layouts,
|
|
584
|
+
imports: WebAssembly.Module.imports(module).map(clone),
|
|
585
|
+
exports: WebAssembly.Module.exports(module).map(clone)
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function createNodeCompiledWasmAssetsLifecycleRuntime(options = {}) {
|
|
590
|
+
const plan = normalizePlan(options.assetsLoweringPlan || options.plan);
|
|
591
|
+
const sidecarPlan = options.assetsCompiledWasmSidecarPlan && (options.assetsCompiledWasmSidecarPlan.artifact || options.assetsCompiledWasmSidecarPlan);
|
|
592
|
+
const baseEffects = Array.isArray(sidecarPlan && sidecarPlan.terminalAssetResponseEffects)
|
|
593
|
+
? sidecarPlan.terminalAssetResponseEffects
|
|
594
|
+
: entriesFromPlan(plan).filter((entry) => entry.response).map((entry) => ({
|
|
595
|
+
id: entry.id,
|
|
596
|
+
store: entry.lookup.store,
|
|
597
|
+
key: entry.lookup.key,
|
|
598
|
+
method: entry.lookup.method || 'GET'
|
|
599
|
+
}));
|
|
600
|
+
const terminalEffects = baseEffects.concat(Array.isArray(options.extraTerminalEffects) ? options.extraTerminalEffects : []);
|
|
601
|
+
const storeBuild = createStoresFromAssetConfig({
|
|
602
|
+
plan,
|
|
603
|
+
assetConfig: options.assetConfig,
|
|
604
|
+
assets: options.assets,
|
|
605
|
+
config: options.config,
|
|
606
|
+
resolvedConfig: options.resolvedConfig,
|
|
607
|
+
cwd: options.cwd,
|
|
608
|
+
configRoot: options.configRoot
|
|
609
|
+
});
|
|
610
|
+
const provider = createNodeAssetsProvider({
|
|
611
|
+
plan,
|
|
612
|
+
stores: storeBuild.stores,
|
|
613
|
+
mode: options.mode || 'compiled-wasm-assets-lifecycle'
|
|
614
|
+
});
|
|
615
|
+
const diagnostics = [...storeBuild.diagnostics, ...(provider.diagnostics || [])];
|
|
616
|
+
const wasm = makeTerminalAssetsWasm(terminalEffects);
|
|
617
|
+
const handles = new Map();
|
|
618
|
+
const results = new Map();
|
|
619
|
+
let handleSeq = 1;
|
|
620
|
+
let resultSeq = 1;
|
|
621
|
+
let instance;
|
|
622
|
+
const decoder = new TextDecoder();
|
|
623
|
+
function memory() { return instance.exports.memory; }
|
|
624
|
+
function readUtf8(ptr, len) {
|
|
625
|
+
if (!ptr || !len) return '';
|
|
626
|
+
return decoder.decode(new Uint8Array(memory().buffer, ptr, len));
|
|
627
|
+
}
|
|
628
|
+
const imports = {
|
|
629
|
+
pulse_assets: {
|
|
630
|
+
pulse_assets_lookup(storePtr, storeLen, keyPtr, keyLen, methodCode, optionsRef) {
|
|
631
|
+
const handle = handleSeq++;
|
|
632
|
+
const lookup = {
|
|
633
|
+
handle,
|
|
634
|
+
store: readUtf8(storePtr, storeLen),
|
|
635
|
+
key: normalizeKey(readUtf8(keyPtr, keyLen)),
|
|
636
|
+
method: methodFromAssetMethodCode(methodCode),
|
|
637
|
+
methodCode: Number(methodCode),
|
|
638
|
+
optionsRef: Number(optionsRef || 0)
|
|
639
|
+
};
|
|
640
|
+
handles.set(handle, lookup);
|
|
641
|
+
return handle;
|
|
642
|
+
},
|
|
643
|
+
pulse_assets_respond(assetHandle, optionsRef) {
|
|
644
|
+
const lookup = handles.get(Number(assetHandle));
|
|
645
|
+
const resultHandle = resultSeq++;
|
|
646
|
+
const response = lookup
|
|
647
|
+
? provider.executeRequest({ method: lookup.method, path: lookup.key })
|
|
648
|
+
: { status: 500, kind: 'empty', headers: [], diagnostics: [makeDiagnostic(assetsContracts.ASSETS_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED, 'Unknown compiled-Wasm asset handle.', 'error', { assetHandle })] };
|
|
649
|
+
results.set(resultHandle, { resultHandle, assetHandle: Number(assetHandle), optionsRef: Number(optionsRef || 0), lookup, response });
|
|
650
|
+
return resultHandle;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
instance = new WebAssembly.Instance(wasm.module, imports);
|
|
655
|
+
|
|
656
|
+
function executeEffect(index) {
|
|
657
|
+
const exportName = `pulse_assets_route_${index}`;
|
|
658
|
+
const fn = instance.exports[exportName];
|
|
659
|
+
if (typeof fn !== 'function') throw new Error(`Missing compiled assets export ${exportName}.`);
|
|
660
|
+
const resultHandle = fn();
|
|
661
|
+
const execution = results.get(Number(resultHandle));
|
|
662
|
+
return {
|
|
663
|
+
index,
|
|
664
|
+
exportName,
|
|
665
|
+
resultHandle: Number(resultHandle),
|
|
666
|
+
effect: wasm.effects[index],
|
|
667
|
+
lookup: execution && execution.lookup,
|
|
668
|
+
response: execution && execution.response,
|
|
669
|
+
diagnostics: execution && execution.response ? execution.response.diagnostics || [] : []
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function executeAll() {
|
|
674
|
+
return wasm.effects.map((_, index) => executeEffect(index));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
version: assetsContracts.ASSETS_NODE_COMPILED_WASM_LIFECYCLE_RUNTIME_VERSION,
|
|
679
|
+
provider: 'node',
|
|
680
|
+
mode: options.mode || 'compiled-wasm-assets-lifecycle',
|
|
681
|
+
plan,
|
|
682
|
+
sidecarPlan,
|
|
683
|
+
providerRuntime: provider,
|
|
684
|
+
storeConfig: storeBuild,
|
|
685
|
+
diagnostics,
|
|
686
|
+
wasm: {
|
|
687
|
+
bytes: wasm.bytes,
|
|
688
|
+
byteLength: wasm.bytes.length,
|
|
689
|
+
imports: wasm.imports,
|
|
690
|
+
exports: wasm.exports,
|
|
691
|
+
effects: wasm.effects
|
|
692
|
+
},
|
|
693
|
+
instance,
|
|
694
|
+
executeEffect,
|
|
695
|
+
executeAll
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function groupHeaders(headers) {
|
|
700
|
+
const order = [];
|
|
701
|
+
const groups = new Map();
|
|
702
|
+
for (const [name, value] of normalizeHeaders(headers)) {
|
|
703
|
+
const display = String(name);
|
|
704
|
+
const lower = display.toLowerCase();
|
|
705
|
+
if (!groups.has(lower)) {
|
|
706
|
+
groups.set(lower, { name: display, values: [] });
|
|
707
|
+
order.push(lower);
|
|
708
|
+
}
|
|
709
|
+
groups.get(lower).values.push(String(value));
|
|
710
|
+
}
|
|
711
|
+
return order.map((key) => groups.get(key));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function writeNodeResponse(res, hostResult) {
|
|
715
|
+
const result = hostResult || { status: 500, kind: 'empty', headers: [] };
|
|
716
|
+
res.statusCode = result.status || 500;
|
|
717
|
+
for (const group of groupHeaders(result.headers || [])) {
|
|
718
|
+
res.setHeader(group.name, group.values.length === 1 ? group.values[0] : group.values.slice());
|
|
719
|
+
}
|
|
720
|
+
if (result.kind === 'stream' && result.bodyStream) {
|
|
721
|
+
if (typeof result.bodyStream.pipe === 'function') {
|
|
722
|
+
result.bodyStream.pipe(res);
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
if (Array.isArray(result.bodyStream.chunks)) {
|
|
726
|
+
if (typeof res.write === 'function') {
|
|
727
|
+
for (const chunk of result.bodyStream.chunks) {
|
|
728
|
+
res.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
729
|
+
}
|
|
730
|
+
res.end();
|
|
731
|
+
} else {
|
|
732
|
+
res.end(Buffer.concat(result.bodyStream.chunks.map((chunk) => Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)))));
|
|
733
|
+
}
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
if (typeof result.bodyStream === 'string' || Buffer.isBuffer(result.bodyStream)) {
|
|
737
|
+
res.end(result.bodyStream);
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const body = result.body === undefined || result.body === null ? '' : String(result.body);
|
|
742
|
+
res.end(body);
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function normalizeNodePath(req) {
|
|
747
|
+
const rawUrl = req && req.url ? String(req.url) : '/';
|
|
748
|
+
try {
|
|
749
|
+
return new URL(rawUrl, 'http://pulsewasm.local').pathname || '/';
|
|
750
|
+
} catch (_) {
|
|
751
|
+
return rawUrl.split('?')[0] || '/';
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function createNodeAssetsHandler(provider) {
|
|
756
|
+
if (!provider || typeof provider.executeRequest !== 'function') {
|
|
757
|
+
throw new Error('createNodeAssetsHandler requires a Node assets provider.');
|
|
758
|
+
}
|
|
759
|
+
return function pulseWasmNodeAssetsHandler(req, res) {
|
|
760
|
+
const result = provider.executeRequest({ method: req && req.method, path: normalizeNodePath(req) });
|
|
761
|
+
writeNodeResponse(res, result);
|
|
762
|
+
return result;
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
module.exports = {
|
|
767
|
+
createNodeAssetsProvider,
|
|
768
|
+
createDefaultStoresFromPlan,
|
|
769
|
+
createStoresFromAssetConfig,
|
|
770
|
+
createNodeCompiledWasmAssetsLifecycleRuntime,
|
|
771
|
+
makeTerminalAssetsWasm,
|
|
772
|
+
createNodeAssetsHandler,
|
|
773
|
+
writeNodeResponse,
|
|
774
|
+
normalizeHeaders,
|
|
775
|
+
normalizeNodePath
|
|
776
|
+
};
|