@pulse-compute/assets 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 +31 -1
- package/as/README.md +12 -0
- package/as/index.as.ts +54 -0
- package/conformance/native.json +7 -0
- package/dist/asset-bucket.d.ts +85 -0
- package/dist/asset-bucket.d.ts.map +1 -0
- package/dist/asset-bucket.js +226 -0
- package/dist/asset-bucket.js.map +1 -0
- package/dist/asset-context.d.ts +34 -0
- package/dist/asset-context.d.ts.map +1 -0
- package/dist/asset-context.js +29 -0
- package/dist/asset-context.js.map +1 -0
- package/dist/asset-manager.d.ts +54 -0
- package/dist/asset-manager.d.ts.map +1 -0
- package/dist/asset-manager.js +257 -0
- package/dist/asset-manager.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/package-runtime.d.ts +9 -0
- package/dist/internal/package-runtime.d.ts.map +1 -0
- package/dist/internal/package-runtime.js +14 -0
- package/dist/internal/package-runtime.js.map +1 -0
- package/dist/portable.d.ts +33 -0
- package/dist/portable.d.ts.map +1 -0
- package/dist/portable.js +98 -0
- package/dist/portable.js.map +1 -0
- package/dist/pulsewasm-compiler-types.d.ts +26 -0
- package/dist/pulsewasm-compiler-types.d.ts.map +1 -0
- package/dist/pulsewasm-compiler-types.js +9 -0
- package/dist/pulsewasm-compiler-types.js.map +1 -0
- package/dist/pulsewasm.d.ts +35 -0
- package/dist/pulsewasm.d.ts.map +1 -0
- package/dist/pulsewasm.js +34 -0
- package/dist/pulsewasm.js.map +1 -0
- package/dist/response-ownership.d.ts +3 -0
- package/dist/response-ownership.d.ts.map +1 -0
- package/dist/response-ownership.js +13 -0
- package/dist/response-ownership.js.map +1 -0
- package/dist/sigv4.d.ts +19 -0
- package/dist/sigv4.d.ts.map +1 -0
- package/dist/sigv4.js +123 -0
- package/dist/sigv4.js.map +1 -0
- package/package.json +59 -5
- package/pulse.package.json +69 -0
- package/pulsewasm.compiler.cjs +1660 -0
- package/pulsewasm.manifest.cjs +60 -0
|
@@ -0,0 +1,1660 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const crypto = require('node:crypto');
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
|
|
9
|
+
const phaseName = 'assets-lowering-plan';
|
|
10
|
+
const sidecarPhaseName = 'assets-compiled-wasm-sidecar-plan';
|
|
11
|
+
const compileLinkPhaseName = 'assets-sidecar-compile-link-proof';
|
|
12
|
+
|
|
13
|
+
function loadContractsDiagnostics() {
|
|
14
|
+
try {
|
|
15
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
18
|
+
return require('../../wasm/packages/contracts/src/diagnostics.js');
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function loadAssetsContracts() {
|
|
26
|
+
try {
|
|
27
|
+
return require('@pulse-compute/wasm-contracts/assets/contracts');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
30
|
+
return require('../../wasm/packages/contracts/src/assets/contracts.js');
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
PACKAGE_VERSION,
|
|
38
|
+
normalizeArtifact,
|
|
39
|
+
normalizeDiagnostic,
|
|
40
|
+
sourceLoc,
|
|
41
|
+
stableFileName
|
|
42
|
+
} = loadContractsDiagnostics();
|
|
43
|
+
|
|
44
|
+
const assetsContracts = loadAssetsContracts();
|
|
45
|
+
|
|
46
|
+
function loadCanonicalRuntimeContracts() {
|
|
47
|
+
try {
|
|
48
|
+
return require('@pulse-compute/wasm-contracts/handler/canonical-runtime');
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error && error.code === 'MODULE_NOT_FOUND') {
|
|
51
|
+
return require('../../wasm/packages/contracts/src/handler/canonical-runtime.js');
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const canonicalRuntimeContracts = loadCanonicalRuntimeContracts();
|
|
58
|
+
|
|
59
|
+
function clone(value) {
|
|
60
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
61
|
+
if (value && typeof value === 'object') {
|
|
62
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function loadTypeScript(inputs = {}) {
|
|
68
|
+
if (inputs.typescript) return inputs.typescript;
|
|
69
|
+
if (inputs.ts) return inputs.ts;
|
|
70
|
+
try {
|
|
71
|
+
return require('typescript');
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const diagnostic = normalizeDiagnostic({
|
|
74
|
+
phase: phaseName,
|
|
75
|
+
severity: 'error',
|
|
76
|
+
code: 'PULSEWASM_ASSETS_TYPESCRIPT_REQUIRED',
|
|
77
|
+
message: 'Assets lowering plan extraction requires the TypeScript parser.',
|
|
78
|
+
hint: 'Call buildAssetsLoweringPlan from the compiler orchestrator, or pass { typescript } explicitly.',
|
|
79
|
+
loc: { file: '<assets-lowering-plan>' }
|
|
80
|
+
});
|
|
81
|
+
const thrown = new Error(diagnostic.message);
|
|
82
|
+
thrown.diagnostics = [diagnostic];
|
|
83
|
+
throw thrown;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function unwrapExpression(ts, node) {
|
|
88
|
+
let current = node;
|
|
89
|
+
while (
|
|
90
|
+
current &&
|
|
91
|
+
(ts.isParenthesizedExpression(current) ||
|
|
92
|
+
ts.isAsExpression(current) ||
|
|
93
|
+
ts.isNonNullExpression(current) ||
|
|
94
|
+
ts.isTypeAssertionExpression(current) ||
|
|
95
|
+
(typeof ts.isSatisfiesExpression === 'function' && ts.isSatisfiesExpression(current)))
|
|
96
|
+
) {
|
|
97
|
+
current = current.expression;
|
|
98
|
+
}
|
|
99
|
+
return current;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isStringLiteralLike(ts, node) {
|
|
103
|
+
return Boolean(node && (ts.isStringLiteral(node) || node.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function literalText(ts, node) {
|
|
107
|
+
const unwrapped = unwrapExpression(ts, node);
|
|
108
|
+
return isStringLiteralLike(ts, unwrapped) ? unwrapped.text : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function nodeText(sourceFile, node) {
|
|
112
|
+
return node ? node.getText(sourceFile).trim() : '<unknown>';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function locFor(sourceFile, node) {
|
|
116
|
+
return node ? sourceLoc(sourceFile, node) : { file: sourceFile ? sourceFile.fileName : '<assets-lowering-plan>' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function makeDiagnostic(sourceFile, node, code, message, hint, severity = 'error', details) {
|
|
120
|
+
return normalizeDiagnostic({
|
|
121
|
+
phase: phaseName,
|
|
122
|
+
severity,
|
|
123
|
+
code,
|
|
124
|
+
message,
|
|
125
|
+
hint,
|
|
126
|
+
details,
|
|
127
|
+
loc: locFor(sourceFile, node)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function assetProtocolDiagnostic(sourceFile, payloadMode, severity, message, hint) {
|
|
132
|
+
const entry = assetsContracts.assetsPayloadMode(payloadMode);
|
|
133
|
+
return normalizeDiagnostic({
|
|
134
|
+
phase: phaseName,
|
|
135
|
+
severity,
|
|
136
|
+
code: entry && entry.diagnosticCode ? entry.diagnosticCode : assetsContracts.ASSETS_DIAGNOSTIC_CODES.STREAM_PROVIDER_NOT_WIRED,
|
|
137
|
+
message,
|
|
138
|
+
hint,
|
|
139
|
+
details: {
|
|
140
|
+
payloadMode,
|
|
141
|
+
status: entry ? entry.status : 'unknown',
|
|
142
|
+
hostSurfaceStatus: entry ? entry.hostSurfaceStatus : undefined
|
|
143
|
+
},
|
|
144
|
+
loc: { file: sourceFile ? sourceFile.fileName : '<assets-lowering-plan>' }
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function diagnosticHasError(diagnostics) {
|
|
149
|
+
return (diagnostics || []).some((entry) => (entry.severity || 'error') === 'error');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function firstPropertyInitializer(ts, objectExpression, names) {
|
|
153
|
+
if (!objectExpression || !ts.isObjectLiteralExpression(objectExpression)) return undefined;
|
|
154
|
+
const wanted = new Set(names);
|
|
155
|
+
for (const property of objectExpression.properties || []) {
|
|
156
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
157
|
+
const name = property.name;
|
|
158
|
+
const key = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
|
|
159
|
+
if (wanted.has(key)) return property.initializer;
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function booleanPropertyValue(ts, objectExpression, names) {
|
|
165
|
+
const initializer = firstPropertyInitializer(ts, objectExpression, names);
|
|
166
|
+
const unwrapped = unwrapExpression(ts, initializer);
|
|
167
|
+
if (!unwrapped) return undefined;
|
|
168
|
+
if (unwrapped.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
169
|
+
if (unwrapped.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function numericPropertyValue(ts, objectExpression, names) {
|
|
174
|
+
const initializer = firstPropertyInitializer(ts, objectExpression, names);
|
|
175
|
+
const unwrapped = unwrapExpression(ts, initializer);
|
|
176
|
+
if (!unwrapped) return undefined;
|
|
177
|
+
if (ts.isNumericLiteral(unwrapped)) return Number(unwrapped.text);
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function stringPropertyValue(ts, objectExpression, names) {
|
|
182
|
+
const initializer = firstPropertyInitializer(ts, objectExpression, names);
|
|
183
|
+
if (!initializer) return undefined;
|
|
184
|
+
return literalText(ts, initializer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function collectFacadeBindings(ts, sourceFile, manifest) {
|
|
188
|
+
const bindings = {
|
|
189
|
+
imports: [],
|
|
190
|
+
namespaceLocals: new Map(),
|
|
191
|
+
lookupLocals: new Map(),
|
|
192
|
+
respondLocals: new Map()
|
|
193
|
+
};
|
|
194
|
+
const lowerableSubpath = manifest.lowerableSubpath;
|
|
195
|
+
const compatibilitySubpaths = manifest.publicApi && Array.isArray(manifest.publicApi.compatibilitySubpaths)
|
|
196
|
+
? manifest.publicApi.compatibilitySubpaths.map(String)
|
|
197
|
+
: [];
|
|
198
|
+
const acceptedSubpaths = new Set([lowerableSubpath, ...compatibilitySubpaths]);
|
|
199
|
+
const facadeNamespace = manifest.facade && manifest.facade.namespace ? manifest.facade.namespace : 'assets';
|
|
200
|
+
|
|
201
|
+
function rememberImport(record) {
|
|
202
|
+
bindings.imports.push(record);
|
|
203
|
+
if (record.kind === 'namespace-object') bindings.namespaceLocals.set(record.local, record);
|
|
204
|
+
if (record.kind === 'named-lookup') bindings.lookupLocals.set(record.local, record);
|
|
205
|
+
if (record.kind === 'named-respond') bindings.respondLocals.set(record.local, record);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const statement of sourceFile.statements || []) {
|
|
209
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
210
|
+
const specifier = statement.moduleSpecifier;
|
|
211
|
+
if (!isStringLiteralLike(ts, specifier) || !acceptedSubpaths.has(specifier.text)) continue;
|
|
212
|
+
const importClause = statement.importClause;
|
|
213
|
+
if (!importClause) continue;
|
|
214
|
+
|
|
215
|
+
if (importClause.name) {
|
|
216
|
+
rememberImport({
|
|
217
|
+
kind: 'namespace-object',
|
|
218
|
+
importStyle: 'default',
|
|
219
|
+
imported: 'default',
|
|
220
|
+
local: importClause.name.text,
|
|
221
|
+
module: specifier.text,
|
|
222
|
+
loc: locFor(sourceFile, importClause.name)
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const namedBindings = importClause.namedBindings;
|
|
227
|
+
if (!namedBindings) continue;
|
|
228
|
+
|
|
229
|
+
if (ts.isNamedImports(namedBindings)) {
|
|
230
|
+
for (const element of namedBindings.elements || []) {
|
|
231
|
+
const imported = element.propertyName ? element.propertyName.text : element.name.text;
|
|
232
|
+
const local = element.name.text;
|
|
233
|
+
if (imported === facadeNamespace) {
|
|
234
|
+
rememberImport({ kind: 'namespace-object', importStyle: 'named', imported, local, module: specifier.text, loc: locFor(sourceFile, element) });
|
|
235
|
+
} else if (imported === 'lookup') {
|
|
236
|
+
rememberImport({ kind: 'named-lookup', importStyle: 'named', imported, local, module: specifier.text, loc: locFor(sourceFile, element) });
|
|
237
|
+
} else if (imported === 'respond') {
|
|
238
|
+
rememberImport({ kind: 'named-respond', importStyle: 'named', imported, local, module: specifier.text, loc: locFor(sourceFile, element) });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return bindings;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function classifyAssetCall(ts, sourceFile, call, bindings) {
|
|
248
|
+
const expression = unwrapExpression(ts, call.expression);
|
|
249
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
250
|
+
const receiver = unwrapExpression(ts, expression.expression);
|
|
251
|
+
const member = expression.name.text;
|
|
252
|
+
if (ts.isIdentifier(receiver) && bindings.namespaceLocals.has(receiver.text) && (member === 'lookup' || member === 'respond')) {
|
|
253
|
+
return {
|
|
254
|
+
kind: member,
|
|
255
|
+
callStyle: 'namespace',
|
|
256
|
+
symbol: `${receiver.text}.${member}`,
|
|
257
|
+
local: receiver.text,
|
|
258
|
+
imported: member,
|
|
259
|
+
import: bindings.namespaceLocals.get(receiver.text),
|
|
260
|
+
loc: locFor(sourceFile, expression)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (ts.isIdentifier(expression)) {
|
|
266
|
+
if (bindings.lookupLocals.has(expression.text)) {
|
|
267
|
+
return {
|
|
268
|
+
kind: 'lookup',
|
|
269
|
+
callStyle: 'named',
|
|
270
|
+
symbol: expression.text,
|
|
271
|
+
local: expression.text,
|
|
272
|
+
imported: 'lookup',
|
|
273
|
+
import: bindings.lookupLocals.get(expression.text),
|
|
274
|
+
loc: locFor(sourceFile, expression)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (bindings.respondLocals.has(expression.text)) {
|
|
278
|
+
return {
|
|
279
|
+
kind: 'respond',
|
|
280
|
+
callStyle: 'named',
|
|
281
|
+
symbol: expression.text,
|
|
282
|
+
local: expression.text,
|
|
283
|
+
imported: 'respond',
|
|
284
|
+
import: bindings.respondLocals.get(expression.text),
|
|
285
|
+
loc: locFor(sourceFile, expression)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function assignedHandleName(ts, call) {
|
|
294
|
+
let current = call;
|
|
295
|
+
let parent = current.parent;
|
|
296
|
+
if (parent && ts.isAwaitExpression(parent)) { current = parent; parent = current.parent; }
|
|
297
|
+
if (!parent) return undefined;
|
|
298
|
+
if (ts.isVariableDeclaration(parent) && parent.initializer === current && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
299
|
+
if (ts.isBinaryExpression(parent) && parent.right === current && parent.operatorToken && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isIdentifier(parent.left)) return parent.left.text;
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function enclosingHandlerName(ts, node) {
|
|
304
|
+
let current = node;
|
|
305
|
+
while (current) {
|
|
306
|
+
if (ts.isFunctionDeclaration(current) && current.name && ts.isIdentifier(current.name)) return current.name.text;
|
|
307
|
+
if ((ts.isFunctionExpression(current) || ts.isArrowFunction(current)) && current.parent) {
|
|
308
|
+
const parent = current.parent;
|
|
309
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
310
|
+
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
311
|
+
}
|
|
312
|
+
if (ts.isMethodDeclaration(current) && current.name && ts.isIdentifier(current.name)) return current.name.text;
|
|
313
|
+
current = current.parent;
|
|
314
|
+
}
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function buildRoutesByHandler(routePlan) {
|
|
319
|
+
const routes = Array.isArray(routePlan && routePlan.routes) ? routePlan.routes : [];
|
|
320
|
+
const byHandler = new Map();
|
|
321
|
+
for (const route of routes) {
|
|
322
|
+
const handlerName = route.handlerName || route.handler;
|
|
323
|
+
if (!handlerName) continue;
|
|
324
|
+
if (!byHandler.has(handlerName)) byHandler.set(handlerName, []);
|
|
325
|
+
byHandler.get(handlerName).push({
|
|
326
|
+
route: `${String(route.method || 'ANY').toUpperCase()} ${route.path || '/'}`,
|
|
327
|
+
method: String(route.method || 'ANY').toUpperCase(),
|
|
328
|
+
path: route.path || '/',
|
|
329
|
+
handlerName
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return byHandler;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function applyRouteMethodAlignment(sourceFile, entry, handlerName, routesByHandler, diagnostics) {
|
|
336
|
+
const lookup = entry && entry.lookup;
|
|
337
|
+
const assetMethod = lookup && lookup.method ? String(lookup.method).toUpperCase() : 'GET';
|
|
338
|
+
const routes = handlerName && routesByHandler ? (routesByHandler.get(handlerName) || []) : [];
|
|
339
|
+
const alignment = {
|
|
340
|
+
status: routes.length === 0 ? 'not-route-bound' : 'aligned',
|
|
341
|
+
handler: handlerName,
|
|
342
|
+
assetMethod,
|
|
343
|
+
routes
|
|
344
|
+
};
|
|
345
|
+
if (routes.length > 0) {
|
|
346
|
+
const routeMethods = Array.from(new Set(routes.map((route) => route.method)));
|
|
347
|
+
alignment.routeMethods = routeMethods;
|
|
348
|
+
if (!routeMethods.includes(assetMethod)) {
|
|
349
|
+
alignment.status = 'mismatch';
|
|
350
|
+
diagnostics.push(makeDiagnostic(
|
|
351
|
+
sourceFile,
|
|
352
|
+
lookup && lookup.loc && lookup.loc.start ? undefined : undefined,
|
|
353
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.ROUTE_METHOD_MISMATCH,
|
|
354
|
+
`assets.lookup method ${JSON.stringify(assetMethod)} does not match route method ${routeMethods.map((method) => JSON.stringify(method)).join(' or ')} for handler ${JSON.stringify(handlerName)}.`,
|
|
355
|
+
'Align the router method and assets.lookup method, for example router.head("/metadata.txt", serveMetadata) with assets.lookup(..., { method: "HEAD" }).',
|
|
356
|
+
'error',
|
|
357
|
+
{ handler: handlerName, assetMethod, routeMethods, routes }
|
|
358
|
+
));
|
|
359
|
+
const last = diagnostics[diagnostics.length - 1];
|
|
360
|
+
if (lookup && lookup.loc) last.loc = lookup.loc;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
entry.routeAlignment = alignment;
|
|
364
|
+
return alignment;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function rangeFor(sourceFile, node) {
|
|
368
|
+
return Object.freeze({
|
|
369
|
+
start: node && typeof node.getStart === 'function' ? node.getStart(sourceFile) : 0,
|
|
370
|
+
end: node && typeof node.getEnd === 'function' ? node.getEnd() : 0
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function placementForCall(ts, call) {
|
|
375
|
+
let current = call;
|
|
376
|
+
let parent = current.parent;
|
|
377
|
+
if (parent && ts.isAwaitExpression(parent)) { current = parent; parent = current.parent; }
|
|
378
|
+
if (parent && ts.isPropertyAssignment(parent) && parent.initializer === current) {
|
|
379
|
+
const object = parent.parent;
|
|
380
|
+
const invocation = object && object.parent;
|
|
381
|
+
if (object && ts.isObjectLiteralExpression(object) && invocation && ts.isCallExpression(invocation)) return 'statement';
|
|
382
|
+
}
|
|
383
|
+
if (parent && ts.isExpressionStatement(parent)) return 'statement';
|
|
384
|
+
if (parent && ts.isReturnStatement(parent)) return 'return';
|
|
385
|
+
if (parent && ts.isVariableDeclaration(parent) && parent.initializer === current) return 'variable';
|
|
386
|
+
return 'unsupported';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function canonicalLookupEffect(ts, sourceFile, call, entry) {
|
|
390
|
+
return Object.freeze({
|
|
391
|
+
version: canonicalRuntimeContracts.CANONICAL_PACKAGE_EFFECT_VERSION,
|
|
392
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
393
|
+
package: '@pulse-compute/assets',
|
|
394
|
+
import: '@pulse-compute/assets',
|
|
395
|
+
kind: 'assets.lookup',
|
|
396
|
+
providerKind: 'assets',
|
|
397
|
+
operation: 'lookup',
|
|
398
|
+
capability: 'assets.lookup',
|
|
399
|
+
result: 'opaque-response',
|
|
400
|
+
placement: placementForCall(ts, call),
|
|
401
|
+
range: rangeFor(sourceFile, call),
|
|
402
|
+
resource: Object.freeze({ kind: 'literal', value: `${entry.lookup.store || ''}:${entry.lookup.key || ''}` }),
|
|
403
|
+
payload: Object.freeze({
|
|
404
|
+
store: entry.lookup.store,
|
|
405
|
+
key: entry.lookup.key,
|
|
406
|
+
method: entry.lookup.method,
|
|
407
|
+
payloadMode: entry.lookup.payloadMode,
|
|
408
|
+
...(entry.lookup.passThroughOn404 === undefined ? {} : { passThroughOn404: entry.lookup.passThroughOn404 }),
|
|
409
|
+
...(entry.lookup.cacheControl === undefined ? {} : { cacheControl: entry.lookup.cacheControl })
|
|
410
|
+
}),
|
|
411
|
+
loc: locFor(sourceFile, call)
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function parseLookupOptions(ts, sourceFile, call, diagnostics, argumentOffset = 0) {
|
|
416
|
+
const optionIndex = argumentOffset + 2;
|
|
417
|
+
const optionArg = call.arguments && call.arguments[optionIndex] ? unwrapExpression(ts, call.arguments[optionIndex]) : undefined;
|
|
418
|
+
const options = {
|
|
419
|
+
method: 'GET',
|
|
420
|
+
passThroughOn404: undefined,
|
|
421
|
+
cacheControl: undefined,
|
|
422
|
+
payloadMode: 'text-response-body'
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
if (!optionArg) return options;
|
|
426
|
+
if (!ts.isObjectLiteralExpression(optionArg)) {
|
|
427
|
+
diagnostics.push(makeDiagnostic(
|
|
428
|
+
sourceFile,
|
|
429
|
+
optionArg,
|
|
430
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NON_LITERAL_LOOKUP,
|
|
431
|
+
'assets.lookup options must be a static object literal in the first lowering plan.',
|
|
432
|
+
'Use literal store/key arguments and a literal options object; move dynamic asset selection to a later provider/runtime pass.',
|
|
433
|
+
'error',
|
|
434
|
+
{ expression: nodeText(sourceFile, optionArg) }
|
|
435
|
+
));
|
|
436
|
+
return options;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const methodInitializer = firstPropertyInitializer(ts, optionArg, ['method']);
|
|
440
|
+
if (methodInitializer) {
|
|
441
|
+
const method = literalText(ts, methodInitializer);
|
|
442
|
+
if (!method) {
|
|
443
|
+
diagnostics.push(makeDiagnostic(
|
|
444
|
+
sourceFile,
|
|
445
|
+
methodInitializer,
|
|
446
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.UNSUPPORTED_METHOD,
|
|
447
|
+
'assets.lookup method must be a static GET or HEAD literal.',
|
|
448
|
+
'Use { method: "GET" } or { method: "HEAD" }; dynamic methods are not lowerable yet.',
|
|
449
|
+
'error',
|
|
450
|
+
{ expression: nodeText(sourceFile, methodInitializer) }
|
|
451
|
+
));
|
|
452
|
+
} else if (!assetsContracts.ASSETS_ALLOWED_METHODS.includes(method)) {
|
|
453
|
+
diagnostics.push(makeDiagnostic(
|
|
454
|
+
sourceFile,
|
|
455
|
+
methodInitializer,
|
|
456
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.UNSUPPORTED_METHOD,
|
|
457
|
+
`assets.lookup method ${JSON.stringify(method)} is not supported by the assets response policy.`,
|
|
458
|
+
`Use one of: ${assetsContracts.ASSETS_ALLOWED_METHODS.join(', ')}.`,
|
|
459
|
+
'error',
|
|
460
|
+
{ method, allowedMethods: assetsContracts.ASSETS_ALLOWED_METHODS }
|
|
461
|
+
));
|
|
462
|
+
options.method = method;
|
|
463
|
+
} else {
|
|
464
|
+
options.method = method;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const passThroughOn404 = booleanPropertyValue(ts, optionArg, ['passThroughOn404']);
|
|
469
|
+
if (passThroughOn404 !== undefined) options.passThroughOn404 = passThroughOn404;
|
|
470
|
+
const cacheControl = stringPropertyValue(ts, optionArg, ['cacheControl']);
|
|
471
|
+
if (cacheControl !== undefined) options.cacheControl = cacheControl;
|
|
472
|
+
|
|
473
|
+
const payloadMode = stringPropertyValue(ts, optionArg, ['payloadMode', 'bodyKind', 'bodyMode']);
|
|
474
|
+
if (payloadMode) {
|
|
475
|
+
options.payloadMode = payloadMode;
|
|
476
|
+
const mode = assetsContracts.assetsPayloadMode(payloadMode);
|
|
477
|
+
if (!mode) {
|
|
478
|
+
diagnostics.push(makeDiagnostic(
|
|
479
|
+
sourceFile,
|
|
480
|
+
firstPropertyInitializer(ts, optionArg, ['payloadMode', 'bodyKind', 'bodyMode']),
|
|
481
|
+
'PULSEWASM_ASSETS_PAYLOAD_MODE_UNSUPPORTED',
|
|
482
|
+
`assets.lookup payload mode ${JSON.stringify(payloadMode)} is not an assets payload mode.`,
|
|
483
|
+
'Use the payload modes classified by @pulse-compute/wasm-contracts/assets/contracts.',
|
|
484
|
+
'error',
|
|
485
|
+
{ payloadMode }
|
|
486
|
+
));
|
|
487
|
+
} else if (mode.status === 'reserved-with-diagnostic') {
|
|
488
|
+
diagnostics.push(makeDiagnostic(
|
|
489
|
+
sourceFile,
|
|
490
|
+
firstPropertyInitializer(ts, optionArg, ['payloadMode', 'bodyKind', 'bodyMode']),
|
|
491
|
+
mode.diagnosticCode || assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED,
|
|
492
|
+
`assets.lookup payload mode ${JSON.stringify(payloadMode)} is reserved for a later binary/body ABI pass.`,
|
|
493
|
+
'Serve this asset through text/stream proof paths for now, or wait for the provider binary-body pass.',
|
|
494
|
+
'error',
|
|
495
|
+
{ payloadMode, status: mode.status }
|
|
496
|
+
));
|
|
497
|
+
} else if (mode.status === 'unsupported') {
|
|
498
|
+
diagnostics.push(makeDiagnostic(
|
|
499
|
+
sourceFile,
|
|
500
|
+
firstPropertyInitializer(ts, optionArg, ['payloadMode', 'bodyKind', 'bodyMode']),
|
|
501
|
+
'PULSEWASM_ASSETS_PAYLOAD_MODE_UNSUPPORTED',
|
|
502
|
+
`assets.lookup payload mode ${JSON.stringify(payloadMode)} is unsupported for assets lowering.`,
|
|
503
|
+
'Assets JSON files should be served as text or stream payloads with headers, not through JSON result helpers.',
|
|
504
|
+
'error',
|
|
505
|
+
{ payloadMode, status: mode.status }
|
|
506
|
+
));
|
|
507
|
+
} else if (mode.status === 'lowering-plan-only') {
|
|
508
|
+
diagnostics.push(makeDiagnostic(
|
|
509
|
+
sourceFile,
|
|
510
|
+
firstPropertyInitializer(ts, optionArg, ['payloadMode', 'bodyKind', 'bodyMode']),
|
|
511
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.STREAM_PROVIDER_NOT_WIRED,
|
|
512
|
+
`assets.lookup payload mode ${JSON.stringify(payloadMode)} is present in the plan but not provider-wired yet.`,
|
|
513
|
+
'Provider proofs must consume this planned stream response path before it becomes implemented for assets.',
|
|
514
|
+
'warning',
|
|
515
|
+
{ payloadMode, status: mode.status, hostSurfaceStatus: mode.hostSurfaceStatus }
|
|
516
|
+
));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return options;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function parseResponseOptions(ts, sourceFile, call, diagnostics) {
|
|
524
|
+
const optionArg = call.arguments && call.arguments[1] ? unwrapExpression(ts, call.arguments[1]) : undefined;
|
|
525
|
+
const options = {};
|
|
526
|
+
if (!optionArg) return options;
|
|
527
|
+
if (!ts.isObjectLiteralExpression(optionArg)) {
|
|
528
|
+
diagnostics.push(makeDiagnostic(
|
|
529
|
+
sourceFile,
|
|
530
|
+
optionArg,
|
|
531
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.RESPOND_REQUIRES_LOOKUP,
|
|
532
|
+
'assets.respond options must be a static object literal in the first lowering plan.',
|
|
533
|
+
'Use literal status/header metadata for now; provider-specific dynamic response behavior comes later.',
|
|
534
|
+
'error',
|
|
535
|
+
{ expression: nodeText(sourceFile, optionArg) }
|
|
536
|
+
));
|
|
537
|
+
return options;
|
|
538
|
+
}
|
|
539
|
+
const status = numericPropertyValue(ts, optionArg, ['status']);
|
|
540
|
+
if (status !== undefined) options.status = status;
|
|
541
|
+
return options;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function makeLookupEntry(ts, sourceFile, call, classified, diagnostics, id, manifest, routeAlignmentInputs) {
|
|
545
|
+
const argumentOffset = classified.import && classified.import.module === '@pulse-compute/assets' ? 1 : 0;
|
|
546
|
+
const store = literalText(ts, call.arguments && call.arguments[argumentOffset]);
|
|
547
|
+
const key = literalText(ts, call.arguments && call.arguments[argumentOffset + 1]);
|
|
548
|
+
const handle = assignedHandleName(ts, call);
|
|
549
|
+
const details = {
|
|
550
|
+
expression: nodeText(sourceFile, call),
|
|
551
|
+
arguments: Array.from(call.arguments || []).map((arg) => nodeText(sourceFile, arg))
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
if (!store || !key) {
|
|
555
|
+
diagnostics.push(makeDiagnostic(
|
|
556
|
+
sourceFile,
|
|
557
|
+
call,
|
|
558
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.NON_LITERAL_LOOKUP,
|
|
559
|
+
'assets.lookup requires static string literal store and key arguments in the first lowering plan.',
|
|
560
|
+
'Use assets.lookup("public", "/app.js") or a named import equivalent. Dynamic keys are reserved for a later pass.',
|
|
561
|
+
'error',
|
|
562
|
+
details
|
|
563
|
+
));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const lookupOptions = parseLookupOptions(ts, sourceFile, call, diagnostics, argumentOffset);
|
|
567
|
+
const payloadMode = assetsContracts.assetsPayloadMode(lookupOptions.payloadMode) ? lookupOptions.payloadMode : 'text-response-body';
|
|
568
|
+
const handlerName = enclosingHandlerName(ts, call);
|
|
569
|
+
|
|
570
|
+
const entry = {
|
|
571
|
+
id: `asset-${id}`,
|
|
572
|
+
status: store && key ? 'planned' : 'unsupported',
|
|
573
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
574
|
+
npmPackage: manifest.npmPackage,
|
|
575
|
+
lowerableSubpath: manifest.lowerableSubpath,
|
|
576
|
+
source: stableFileName(sourceFile.fileName, undefined),
|
|
577
|
+
import: {
|
|
578
|
+
style: classified.callStyle,
|
|
579
|
+
local: classified.local,
|
|
580
|
+
symbol: classified.symbol,
|
|
581
|
+
module: manifest.lowerableSubpath
|
|
582
|
+
},
|
|
583
|
+
lookup: {
|
|
584
|
+
call: classified.symbol,
|
|
585
|
+
store,
|
|
586
|
+
key,
|
|
587
|
+
method: lookupOptions.method,
|
|
588
|
+
handle: handle || undefined,
|
|
589
|
+
payloadMode,
|
|
590
|
+
passThroughOn404: lookupOptions.passThroughOn404,
|
|
591
|
+
cacheControl: lookupOptions.cacheControl,
|
|
592
|
+
loc: locFor(sourceFile, call)
|
|
593
|
+
},
|
|
594
|
+
handler: handlerName ? { name: handlerName } : undefined,
|
|
595
|
+
response: undefined,
|
|
596
|
+
hostCapabilities: ['assets', 'body', 'headers', 'result'],
|
|
597
|
+
provider: {
|
|
598
|
+
initialTarget: 'node',
|
|
599
|
+
targets: assetsContracts.ASSETS_INITIAL_PROVIDER_TARGETS.slice(),
|
|
600
|
+
status: 'deferred-to-provider-proofs'
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
entry.range = rangeFor(sourceFile, call);
|
|
605
|
+
entry.placement = placementForCall(ts, call);
|
|
606
|
+
entry.canonicalEffect = canonicalLookupEffect(ts, sourceFile, call, entry);
|
|
607
|
+
|
|
608
|
+
if (routeAlignmentInputs && routeAlignmentInputs.routeMetadataAvailable && routeAlignmentInputs.routesByHandler) {
|
|
609
|
+
applyRouteMethodAlignment(sourceFile, entry, handlerName, routeAlignmentInputs.routesByHandler, diagnostics);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return entry;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function makeResponsePlan(ts, sourceFile, call, classified, lookupEntry, diagnostics) {
|
|
616
|
+
const options = parseResponseOptions(ts, sourceFile, call, diagnostics);
|
|
617
|
+
const payloadMode = lookupEntry.lookup.payloadMode || 'text-response-body';
|
|
618
|
+
const mode = assetsContracts.assetsPayloadMode(payloadMode) || assetsContracts.assetsPayloadMode('text-response-body');
|
|
619
|
+
return {
|
|
620
|
+
call: classified.symbol,
|
|
621
|
+
handle: lookupEntry.lookup.handle,
|
|
622
|
+
lookupId: lookupEntry.id,
|
|
623
|
+
status: options.status,
|
|
624
|
+
payloadMode,
|
|
625
|
+
payloadStatus: mode ? mode.status : 'unsupported',
|
|
626
|
+
headerMode: 'header-pass-through',
|
|
627
|
+
statusMode: 'status-result-pass-through',
|
|
628
|
+
notFoundMode: '404-pass-through',
|
|
629
|
+
loc: locFor(sourceFile, call)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function findAssetsManifest(inputs, manifestRecords) {
|
|
634
|
+
if (inputs.manifest) return inputs.manifest;
|
|
635
|
+
if (inputs.assetsManifest) return inputs.assetsManifest;
|
|
636
|
+
const direct = (manifestRecords || []).find((record) => record.manifest && record.manifest.contractId === assetsContracts.ASSETS_CONTRACT_ID);
|
|
637
|
+
if (direct) return direct.manifest;
|
|
638
|
+
try {
|
|
639
|
+
return require('./pulsewasm.manifest.cjs');
|
|
640
|
+
} catch (_error) {
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function findAssetsContract(inputs, manifest) {
|
|
646
|
+
const contracts = Array.isArray(inputs.libraryContracts) && inputs.libraryContracts.length > 0
|
|
647
|
+
? inputs.libraryContracts
|
|
648
|
+
: [];
|
|
649
|
+
const found = contracts.find((contract) => contract.package === assetsContracts.ASSETS_CONTRACT_ID);
|
|
650
|
+
if (found) return found;
|
|
651
|
+
if (!manifest) return undefined;
|
|
652
|
+
return {
|
|
653
|
+
package: assetsContracts.ASSETS_CONTRACT_ID,
|
|
654
|
+
npmPackage: manifest.npmPackage,
|
|
655
|
+
lowerableSubpath: manifest.lowerableSubpath,
|
|
656
|
+
facade: clone(manifest.facade || {}),
|
|
657
|
+
publicApi: clone(manifest.publicApi || {}),
|
|
658
|
+
compiler: clone(manifest.compiler || {}),
|
|
659
|
+
assets: assetsContracts.assetsProtocolExtension().assets,
|
|
660
|
+
policy: clone(manifest.policy || {})
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
function buildUnsupportedList(diagnostics) {
|
|
666
|
+
return (diagnostics || [])
|
|
667
|
+
.filter((entry) => (entry.severity || 'error') === 'error')
|
|
668
|
+
.map((entry) => ({
|
|
669
|
+
code: entry.code,
|
|
670
|
+
message: entry.message,
|
|
671
|
+
hint: entry.hint,
|
|
672
|
+
loc: entry.loc,
|
|
673
|
+
details: entry.details
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function buildPayloadModes(sourceFile) {
|
|
678
|
+
const classification = assetsContracts.assetsPayloadModeClassification();
|
|
679
|
+
const diagnostics = [
|
|
680
|
+
assetProtocolDiagnostic(
|
|
681
|
+
sourceFile,
|
|
682
|
+
'binary-buffer',
|
|
683
|
+
'info',
|
|
684
|
+
'Assets binary body/buffer responses are reserved with a planning diagnostic.',
|
|
685
|
+
'The current lowering stage validates and plans only; provider proofs must not claim binary/static-file completion yet.'
|
|
686
|
+
),
|
|
687
|
+
assetProtocolDiagnostic(
|
|
688
|
+
sourceFile,
|
|
689
|
+
'stream-pass-through-response',
|
|
690
|
+
'info',
|
|
691
|
+
'assets stream pass-through responses are represented in the plan but not provider-wired yet.',
|
|
692
|
+
'The host RESULT_STREAM surface exists, but provider tests must prove consumption before this becomes supported.'
|
|
693
|
+
)
|
|
694
|
+
];
|
|
695
|
+
|
|
696
|
+
return {
|
|
697
|
+
payloadModes: {
|
|
698
|
+
classification,
|
|
699
|
+
supportedBodyKinds: clone(assetsContracts.ASSETS_SUPPORTED_BODY_KINDS),
|
|
700
|
+
loweringPlanOnlyBodyKinds: clone(assetsContracts.ASSETS_LOWERING_PLAN_ONLY_BODY_KINDS),
|
|
701
|
+
reservedBodyKinds: clone(assetsContracts.ASSETS_RESERVED_BODY_KINDS),
|
|
702
|
+
unsupportedBodyKinds: clone(assetsContracts.ASSETS_UNSUPPORTED_BODY_KINDS),
|
|
703
|
+
responsePolicy: clone(assetsContracts.ASSETS_RESPONSE_POLICY),
|
|
704
|
+
stream: {
|
|
705
|
+
mode: classification.streamPassThroughResponse.mode,
|
|
706
|
+
status: classification.streamPassThroughResponse.status,
|
|
707
|
+
hostSurfaceStatus: classification.streamPassThroughResponse.hostSurfaceStatus,
|
|
708
|
+
providerWired: false,
|
|
709
|
+
diagnosticCode: assetsContracts.ASSETS_DIAGNOSTIC_CODES.STREAM_PROVIDER_NOT_WIRED
|
|
710
|
+
},
|
|
711
|
+
binary: {
|
|
712
|
+
mode: classification.binaryBodyBufferResponse.mode,
|
|
713
|
+
status: classification.binaryBodyBufferResponse.status,
|
|
714
|
+
providerWired: false,
|
|
715
|
+
diagnosticCode: assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
diagnostics
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function buildAssetsLoweringPlan(inputs = {}) {
|
|
723
|
+
const ts = loadTypeScript(inputs);
|
|
724
|
+
const sourceFile = inputs.sourceFile || (inputs.kind === ts.SyntaxKind.SourceFile ? inputs : undefined);
|
|
725
|
+
const cwd = inputs.cwd || process.cwd();
|
|
726
|
+
const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
|
|
727
|
+
const diagnostics = [];
|
|
728
|
+
const entries = [];
|
|
729
|
+
const lookupEntriesByHandle = new Map();
|
|
730
|
+
const lookupEntriesByNode = new Map();
|
|
731
|
+
const resultAdapters = [];
|
|
732
|
+
const routeMetadataAvailable = Boolean(inputs.routePlan && Array.isArray(inputs.routePlan.routes));
|
|
733
|
+
const routesByHandler = routeMetadataAvailable ? buildRoutesByHandler(inputs.routePlan) : new Map();
|
|
734
|
+
|
|
735
|
+
const manifestRecords = Array.isArray(inputs.manifestRecords) ? inputs.manifestRecords : [];
|
|
736
|
+
const manifest = findAssetsManifest(inputs, manifestRecords);
|
|
737
|
+
const assetsContract = findAssetsContract({ ...inputs, cwd }, manifest);
|
|
738
|
+
|
|
739
|
+
if (!sourceFile) {
|
|
740
|
+
diagnostics.push(normalizeDiagnostic({
|
|
741
|
+
phase: phaseName,
|
|
742
|
+
severity: 'error',
|
|
743
|
+
code: 'PULSEWASM_ASSETS_SOURCE_REQUIRED',
|
|
744
|
+
message: 'Assets lowering plan generation requires a TypeScript SourceFile.',
|
|
745
|
+
hint: 'Pass the compiler sourceFile into the library-kit assets lowering builder.',
|
|
746
|
+
loc: { file: '<assets-lowering-plan>' }
|
|
747
|
+
}));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!manifest) {
|
|
751
|
+
diagnostics.push(normalizeDiagnostic({
|
|
752
|
+
phase: phaseName,
|
|
753
|
+
severity: 'error',
|
|
754
|
+
code: 'PULSEWASM_ASSETS_MANIFEST_NOT_FOUND',
|
|
755
|
+
message: 'Could not discover the package-owned @pulse-compute/assets PulseWasm manifest.',
|
|
756
|
+
hint: 'Ensure packages/assets/package.json advertises pulsewasm.manifest and the workspace root is passed to library-kit.',
|
|
757
|
+
loc: { file: sourceFile ? sourceFile.fileName : '<assets-lowering-plan>' }
|
|
758
|
+
}));
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (!assetsContract) {
|
|
762
|
+
diagnostics.push(normalizeDiagnostic({
|
|
763
|
+
phase: phaseName,
|
|
764
|
+
severity: 'error',
|
|
765
|
+
code: 'PULSEWASM_ASSETS_LIBRARY_CONTRACT_NOT_FOUND',
|
|
766
|
+
message: 'Could not resolve the pulse.assets library contract from package-owned manifests.',
|
|
767
|
+
hint: 'Run the handler-library-contracts builder or pass libraryContracts containing pulse.assets.',
|
|
768
|
+
loc: { file: sourceFile ? sourceFile.fileName : '<assets-lowering-plan>' }
|
|
769
|
+
}));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const payload = buildPayloadModes(sourceFile);
|
|
773
|
+
diagnostics.push(...payload.diagnostics);
|
|
774
|
+
diagnostics.push(normalizeDiagnostic({
|
|
775
|
+
phase: phaseName,
|
|
776
|
+
severity: 'info',
|
|
777
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.RUNTIME_EXECUTION_UNSUPPORTED,
|
|
778
|
+
message: 'The normal @pulse-compute/assets package root executes directly in JavaScript and is recognized by its package-owned native compiler builder.',
|
|
779
|
+
hint: 'JavaScript executes the package runtime; native compilation consumes this package-owned plan without changing application imports.',
|
|
780
|
+
details: {
|
|
781
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
782
|
+
providerBehaviorImplemented: true
|
|
783
|
+
},
|
|
784
|
+
loc: { file: sourceFile ? sourceFile.fileName : '<assets-lowering-plan>' }
|
|
785
|
+
}));
|
|
786
|
+
|
|
787
|
+
let bindings = { imports: [], namespaceLocals: new Map(), lookupLocals: new Map(), respondLocals: new Map() };
|
|
788
|
+
if (sourceFile && manifest) bindings = collectFacadeBindings(ts, sourceFile, manifest);
|
|
789
|
+
|
|
790
|
+
function nextEntryId() {
|
|
791
|
+
return entries.length;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function ensureLookupEntry(call, classified) {
|
|
795
|
+
if (lookupEntriesByNode.has(call)) return lookupEntriesByNode.get(call);
|
|
796
|
+
const entry = makeLookupEntry(ts, sourceFile, call, classified, diagnostics, nextEntryId(), manifest, { routesByHandler, routeMetadataAvailable });
|
|
797
|
+
entries.push(entry);
|
|
798
|
+
lookupEntriesByNode.set(call, entry);
|
|
799
|
+
if (entry.lookup.handle) lookupEntriesByHandle.set(entry.lookup.handle, entry);
|
|
800
|
+
return entry;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function handleRespondCall(call, classified) {
|
|
804
|
+
const arg = call.arguments && call.arguments[0] ? unwrapExpression(ts, call.arguments[0]) : undefined;
|
|
805
|
+
let lookupEntry;
|
|
806
|
+
if (arg && ts.isIdentifier(arg)) {
|
|
807
|
+
lookupEntry = lookupEntriesByHandle.get(arg.text);
|
|
808
|
+
if (!lookupEntry) {
|
|
809
|
+
diagnostics.push(makeDiagnostic(
|
|
810
|
+
sourceFile,
|
|
811
|
+
arg,
|
|
812
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.RESPOND_REQUIRES_LOOKUP,
|
|
813
|
+
`assets.respond(${arg.text}) does not reference a statically lowerable assets.lookup handle.`,
|
|
814
|
+
'Assign assets.lookup("store", "/key") to a const and pass that handle to assets.respond(handle).',
|
|
815
|
+
'error',
|
|
816
|
+
{ handle: arg.text, expression: nodeText(sourceFile, call) }
|
|
817
|
+
));
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
} else if (arg && ts.isCallExpression(arg)) {
|
|
821
|
+
const nested = classifyAssetCall(ts, sourceFile, arg, bindings);
|
|
822
|
+
if (nested && nested.kind === 'lookup') {
|
|
823
|
+
lookupEntry = ensureLookupEntry(arg, nested);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (!lookupEntry) {
|
|
828
|
+
diagnostics.push(makeDiagnostic(
|
|
829
|
+
sourceFile,
|
|
830
|
+
call,
|
|
831
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.RESPOND_REQUIRES_LOOKUP,
|
|
832
|
+
'assets.respond requires a statically lowerable assets.lookup handle.',
|
|
833
|
+
'Use const found = assets.lookup("public", "/app.js"); return assets.respond(found).',
|
|
834
|
+
'error',
|
|
835
|
+
{ expression: nodeText(sourceFile, call) }
|
|
836
|
+
));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
lookupEntry.response = makeResponsePlan(ts, sourceFile, call, classified, lookupEntry, diagnostics);
|
|
841
|
+
lookupEntry.status = lookupEntry.status === 'unsupported' ? 'unsupported' : 'planned';
|
|
842
|
+
resultAdapters.push(Object.freeze({
|
|
843
|
+
version: 'pulse.package-result-adapter.v1',
|
|
844
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
845
|
+
package: '@pulse-compute/assets',
|
|
846
|
+
import: '@pulse-compute/assets',
|
|
847
|
+
kind: 'assets.respond',
|
|
848
|
+
operation: 'respond',
|
|
849
|
+
range: rangeFor(sourceFile, call),
|
|
850
|
+
input: arg && ts.isIdentifier(arg) ? Object.freeze({ kind: 'identifier', name: arg.text }) : Object.freeze({ kind: 'nested-effect' }),
|
|
851
|
+
loc: locFor(sourceFile, call)
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (sourceFile && manifest) {
|
|
856
|
+
function visit(node) {
|
|
857
|
+
if (ts.isCallExpression(node)) {
|
|
858
|
+
const classified = classifyAssetCall(ts, sourceFile, node, bindings);
|
|
859
|
+
if (classified && classified.kind === 'lookup') {
|
|
860
|
+
ensureLookupEntry(node, classified);
|
|
861
|
+
} else if (classified && classified.kind === 'respond') {
|
|
862
|
+
handleRespondCall(node, classified);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
ts.forEachChild(node, visit);
|
|
866
|
+
}
|
|
867
|
+
visit(sourceFile);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
|
|
871
|
+
const unsupported = buildUnsupportedList(diagnostics);
|
|
872
|
+
const plannedResponses = entries.filter((entry) => entry.response).length;
|
|
873
|
+
const routeAlignments = entries.map((entry) => entry.routeAlignment).filter(Boolean);
|
|
874
|
+
const routeAlignmentMismatches = routeAlignments.filter((alignment) => alignment.status === 'mismatch').length;
|
|
875
|
+
const artifact = normalizeArtifact({
|
|
876
|
+
version: assetsContracts.ASSETS_LOWERING_PLAN_VERSION,
|
|
877
|
+
generatedBy,
|
|
878
|
+
phase: phaseName,
|
|
879
|
+
status: errorDiagnostics.length === 0 ? 'ok' : 'error',
|
|
880
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
881
|
+
npmPackage: manifest ? manifest.npmPackage : undefined,
|
|
882
|
+
lowerableSubpath: manifest ? manifest.lowerableSubpath : undefined,
|
|
883
|
+
facade: manifest ? clone(manifest.facade) : undefined,
|
|
884
|
+
source: sourceFile ? sourceFile.fileName : undefined,
|
|
885
|
+
validateAndPlanOnly: true,
|
|
886
|
+
providerBehaviorImplemented: false,
|
|
887
|
+
entries,
|
|
888
|
+
canonicalEffects: entries.map((entry) => entry.canonicalEffect),
|
|
889
|
+
resultAdapters,
|
|
890
|
+
unsupported,
|
|
891
|
+
payloadModes: payload.payloadModes,
|
|
892
|
+
authoringAlignment: {
|
|
893
|
+
routeMethodConsistency: routeAlignmentMismatches === 0,
|
|
894
|
+
entriesChecked: routeAlignments.length,
|
|
895
|
+
matchedEntries: routeAlignments.filter((alignment) => alignment.status === 'aligned').length,
|
|
896
|
+
mismatches: routeAlignmentMismatches,
|
|
897
|
+
diagnosticCode: assetsContracts.ASSETS_DIAGNOSTIC_CODES.ROUTE_METHOD_MISMATCH,
|
|
898
|
+
policy: 'asset lookup method must match the owning route method when route metadata is available'
|
|
899
|
+
},
|
|
900
|
+
policy: {
|
|
901
|
+
packageIdentityOwnedByManifest: true,
|
|
902
|
+
compilerOwnsPackageMapping: false,
|
|
903
|
+
builderOwner: manifest && manifest.compiler && manifest.compiler.builderOwner ? manifest.compiler.builderOwner : '@pulse-compute/assets',
|
|
904
|
+
compilerBuilder: manifest && manifest.compiler ? clone(manifest.compiler) : undefined,
|
|
905
|
+
directRuntimeExecution: {
|
|
906
|
+
status: 'implemented',
|
|
907
|
+
entry: './dist/index.js'
|
|
908
|
+
},
|
|
909
|
+
nodeProviderProof: 'pass-27',
|
|
910
|
+
fastlyProviderProof: 'pass-28'
|
|
911
|
+
},
|
|
912
|
+
summary: {
|
|
913
|
+
imports: bindings.imports.length,
|
|
914
|
+
lookups: entries.length,
|
|
915
|
+
responses: plannedResponses,
|
|
916
|
+
entries: entries.length,
|
|
917
|
+
unsupported: unsupported.length,
|
|
918
|
+
diagnostics: diagnostics.length,
|
|
919
|
+
errorDiagnostics: errorDiagnostics.length,
|
|
920
|
+
payloadModes: Object.keys(payload.payloadModes.classification || {}).length,
|
|
921
|
+
binaryStatus: payload.payloadModes.binary.status,
|
|
922
|
+
streamStatus: payload.payloadModes.stream.status,
|
|
923
|
+
routeAlignmentChecked: routeAlignments.length,
|
|
924
|
+
routeMethodMismatches: routeAlignmentMismatches,
|
|
925
|
+
routeMethodConsistency: routeAlignmentMismatches === 0
|
|
926
|
+
},
|
|
927
|
+
diagnostics
|
|
928
|
+
}, cwd);
|
|
929
|
+
|
|
930
|
+
return {
|
|
931
|
+
artifact,
|
|
932
|
+
diagnostics,
|
|
933
|
+
unsupported,
|
|
934
|
+
entries: artifact.entries,
|
|
935
|
+
canonicalEffects: artifact.canonicalEffects,
|
|
936
|
+
resultAdapters: artifact.resultAdapters,
|
|
937
|
+
payloadModes: artifact.payloadModes,
|
|
938
|
+
hasErrors: diagnosticHasError(diagnostics)
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
function resolveAssetsPackageDir(inputs = {}) {
|
|
944
|
+
const base = inputs.cwd || process.cwd();
|
|
945
|
+
if (inputs.packageDir) return path.isAbsolute(inputs.packageDir) ? path.resolve(inputs.packageDir) : path.resolve(base, inputs.packageDir);
|
|
946
|
+
if (inputs.manifestRecord && inputs.manifestRecord.packageDir) {
|
|
947
|
+
return path.isAbsolute(inputs.manifestRecord.packageDir) ? path.resolve(inputs.manifestRecord.packageDir) : path.resolve(base, inputs.manifestRecord.packageDir);
|
|
948
|
+
}
|
|
949
|
+
return __dirname;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function declaredSidecarSymbols(manifest) {
|
|
953
|
+
const wasm = manifest && manifest.modes && manifest.modes.wasm ? manifest.modes.wasm : {};
|
|
954
|
+
return (wasm.lowerings || []).map((lowering) => ({
|
|
955
|
+
tsSymbol: lowering.tsSymbol,
|
|
956
|
+
asSymbol: lowering.asSymbol,
|
|
957
|
+
callShape: lowering.callShape,
|
|
958
|
+
hostCapabilities: clone(lowering.hostCapabilities || [])
|
|
959
|
+
}));
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function sidecarExportPresent(source, symbol) {
|
|
963
|
+
const pattern = new RegExp(`export\\s+function\\s+${symbol}\\s*\\(`);
|
|
964
|
+
return pattern.test(source);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function assetEntriesForSidecar(inputs, manifest) {
|
|
968
|
+
if (inputs.assetsLoweringPlan) {
|
|
969
|
+
const artifact = inputs.assetsLoweringPlan.artifact || inputs.assetsLoweringPlan;
|
|
970
|
+
return Array.isArray(artifact.entries) ? artifact.entries : [];
|
|
971
|
+
}
|
|
972
|
+
if (inputs.sourceFile) {
|
|
973
|
+
const plan = buildAssetsLoweringPlan({
|
|
974
|
+
...inputs,
|
|
975
|
+
manifest,
|
|
976
|
+
assetsManifest: manifest,
|
|
977
|
+
generatedBy: inputs.generatedBy || PACKAGE_VERSION
|
|
978
|
+
});
|
|
979
|
+
return Array.isArray(plan.artifact.entries) ? plan.artifact.entries : [];
|
|
980
|
+
}
|
|
981
|
+
return [];
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function buildAssetsCompiledWasmSidecarPlan(inputs = {}) {
|
|
985
|
+
const cwd = inputs.cwd || process.cwd();
|
|
986
|
+
const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
|
|
987
|
+
const manifestRecords = Array.isArray(inputs.manifestRecords) ? inputs.manifestRecords : [];
|
|
988
|
+
const manifest = findAssetsManifest(inputs, manifestRecords);
|
|
989
|
+
const manifestRecord = manifestRecords.find((record) => record && record.manifest && record.manifest.contractId === assetsContracts.ASSETS_CONTRACT_ID);
|
|
990
|
+
const packageDir = resolveAssetsPackageDir(inputs);
|
|
991
|
+
const wasm = manifest && manifest.modes && manifest.modes.wasm ? manifest.modes.wasm : {};
|
|
992
|
+
const sidecarRef = wasm.sidecar;
|
|
993
|
+
const sidecarFile = sidecarRef ? path.resolve(packageDir, sidecarRef) : undefined;
|
|
994
|
+
const sidecarSource = sidecarFile && fs.existsSync(sidecarFile) ? fs.readFileSync(sidecarFile, 'utf8') : '';
|
|
995
|
+
const symbols = declaredSidecarSymbols(manifest);
|
|
996
|
+
const missingSymbols = symbols.filter((symbol) => !symbol.asSymbol || !sidecarExportPresent(sidecarSource, symbol.asSymbol));
|
|
997
|
+
const entries = assetEntriesForSidecar(inputs, manifest);
|
|
998
|
+
const diagnostics = [];
|
|
999
|
+
|
|
1000
|
+
if (!manifest) {
|
|
1001
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1002
|
+
phase: sidecarPhaseName,
|
|
1003
|
+
severity: 'error',
|
|
1004
|
+
code: 'PULSEWASM_ASSETS_MANIFEST_NOT_FOUND',
|
|
1005
|
+
message: 'Could not discover the package-owned @pulse-compute/assets PulseWasm manifest for sidecar planning.',
|
|
1006
|
+
hint: 'Pass the package-owned manifest or ensure package.json advertises pulsewasm.manifest.',
|
|
1007
|
+
loc: { file: '<assets-compiled-wasm-sidecar-plan>' }
|
|
1008
|
+
}));
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (!sidecarRef || !sidecarFile || !fs.existsSync(sidecarFile)) {
|
|
1012
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1013
|
+
phase: sidecarPhaseName,
|
|
1014
|
+
severity: 'error',
|
|
1015
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_FILE_MISSING,
|
|
1016
|
+
message: 'The assets manifest declares a Wasm sidecar, but the sidecar file was not found.',
|
|
1017
|
+
hint: 'Ship packages/assets/as/index.as.ts and include it in the package files list.',
|
|
1018
|
+
loc: { file: sidecarRef || '<assets-sidecar>' },
|
|
1019
|
+
details: { sidecar: sidecarRef, packageDir: stableFileName(packageDir, cwd) }
|
|
1020
|
+
}));
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
for (const missing of missingSymbols) {
|
|
1024
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1025
|
+
phase: sidecarPhaseName,
|
|
1026
|
+
severity: 'error',
|
|
1027
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_SYMBOL_MISSING,
|
|
1028
|
+
message: `The assets sidecar does not export ${missing.asSymbol}.`,
|
|
1029
|
+
hint: 'Every modes.wasm.lowerings[].asSymbol must be exported by the package-owned AssemblyScript sidecar.',
|
|
1030
|
+
loc: { file: sidecarRef || '<assets-sidecar>' },
|
|
1031
|
+
details: missing
|
|
1032
|
+
}));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const sidecarSymbols = symbols.map((symbol) => ({
|
|
1036
|
+
...symbol,
|
|
1037
|
+
exported: Boolean(symbol.asSymbol && sidecarExportPresent(sidecarSource, symbol.asSymbol))
|
|
1038
|
+
}));
|
|
1039
|
+
|
|
1040
|
+
for (const entry of entries.filter((candidate) => !candidate.response)) {
|
|
1041
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1042
|
+
phase: sidecarPhaseName,
|
|
1043
|
+
severity: 'error',
|
|
1044
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED,
|
|
1045
|
+
message: 'assets.lookup handles must be consumed by a terminal assets.respond(...) call in the compiled-Wasm sidecar plan.',
|
|
1046
|
+
hint: 'Use return assets.respond(found) or return assets.respond(assets.lookup("public", "/app.js")). Arbitrary asset handle values are not materialized in Wasm.',
|
|
1047
|
+
loc: entry.lookup && entry.lookup.loc ? entry.lookup.loc : { file: '<assets-compiled-wasm-sidecar-plan>' },
|
|
1048
|
+
details: { id: entry.id, store: entry.lookup && entry.lookup.store, key: entry.lookup && entry.lookup.key }
|
|
1049
|
+
}));
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const terminalEffects = entries
|
|
1053
|
+
.filter((entry) => entry.response)
|
|
1054
|
+
.map((entry) => ({
|
|
1055
|
+
id: entry.id,
|
|
1056
|
+
store: entry.lookup && entry.lookup.store,
|
|
1057
|
+
key: entry.lookup && entry.lookup.key,
|
|
1058
|
+
method: entry.lookup && entry.lookup.method,
|
|
1059
|
+
lookupSymbol: 'pulse_assets_lookup',
|
|
1060
|
+
respondSymbol: 'pulse_assets_respond',
|
|
1061
|
+
effect: 'terminal-asset-response',
|
|
1062
|
+
continuationRequired: false,
|
|
1063
|
+
payloadMode: entry.response && entry.response.payloadMode,
|
|
1064
|
+
statusMode: entry.response && entry.response.statusMode,
|
|
1065
|
+
headerMode: entry.response && entry.response.headerMode
|
|
1066
|
+
}));
|
|
1067
|
+
|
|
1068
|
+
const packageOut = manifestRecord ? {
|
|
1069
|
+
discoveredFromNodeModules: manifestRecord.source === 'node_modules' || String(manifestRecord.source || '').includes('node_modules'),
|
|
1070
|
+
builderLoadedFromNodeModules: manifestRecord.source === 'node_modules' || String(manifestRecord.source || '').includes('node_modules'),
|
|
1071
|
+
packageDir: stableFileName(manifestRecord.packageDir || packageDir, cwd),
|
|
1072
|
+
manifestFile: stableFileName(manifestRecord.manifestFile, cwd),
|
|
1073
|
+
sidecarFile: sidecarFile ? stableFileName(sidecarFile, cwd) : undefined
|
|
1074
|
+
} : undefined;
|
|
1075
|
+
|
|
1076
|
+
const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
|
|
1077
|
+
const artifact = normalizeArtifact({
|
|
1078
|
+
version: assetsContracts.ASSETS_COMPILED_WASM_SIDECAR_PLAN_VERSION,
|
|
1079
|
+
generatedBy,
|
|
1080
|
+
phase: sidecarPhaseName,
|
|
1081
|
+
status: errorDiagnostics.length === 0 ? 'ok' : 'error',
|
|
1082
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
1083
|
+
package: manifest ? manifest.npmPackage : '@pulse-compute/assets',
|
|
1084
|
+
lowerableSubpath: manifest ? manifest.lowerableSubpath : '@pulse-compute/assets/pulsewasm',
|
|
1085
|
+
sourceAssetsLoweringPlanVersion: inputs.assetsLoweringPlan && (inputs.assetsLoweringPlan.artifact || inputs.assetsLoweringPlan).version,
|
|
1086
|
+
sidecar: {
|
|
1087
|
+
declared: Boolean(sidecarRef),
|
|
1088
|
+
path: sidecarRef,
|
|
1089
|
+
file: sidecarFile ? stableFileName(sidecarFile, cwd) : undefined,
|
|
1090
|
+
exists: Boolean(sidecarFile && fs.existsSync(sidecarFile)),
|
|
1091
|
+
packageDir: stableFileName(packageDir, cwd),
|
|
1092
|
+
sourceBytes: sidecarSource.length,
|
|
1093
|
+
symbols: sidecarSymbols
|
|
1094
|
+
},
|
|
1095
|
+
lowerings: sidecarSymbols,
|
|
1096
|
+
terminalAssetResponseEffects: terminalEffects,
|
|
1097
|
+
runtimeModel: {
|
|
1098
|
+
terminalAssetResponseEffectPlanned: true,
|
|
1099
|
+
continuationRequired: false,
|
|
1100
|
+
providerEffectBoundary: 'assets.respond terminal response request',
|
|
1101
|
+
userAuthoredAsync: false,
|
|
1102
|
+
promises: false,
|
|
1103
|
+
asyncAwait: false,
|
|
1104
|
+
asyncify: false
|
|
1105
|
+
},
|
|
1106
|
+
packageOut,
|
|
1107
|
+
compiledWasmReadiness: {
|
|
1108
|
+
sidecarDeclared: Boolean(sidecarRef),
|
|
1109
|
+
sidecarFileExists: Boolean(sidecarFile && fs.existsSync(sidecarFile)),
|
|
1110
|
+
sidecarSymbolsValidated: missingSymbols.length === 0 && sidecarSymbols.length > 0,
|
|
1111
|
+
lookupSymbol: 'pulse_assets_lookup',
|
|
1112
|
+
respondSymbol: 'pulse_assets_respond',
|
|
1113
|
+
nodeLifecycleProof: 'pass-49',
|
|
1114
|
+
fastlyParityProof: 'pass-50'
|
|
1115
|
+
},
|
|
1116
|
+
restrictions: {
|
|
1117
|
+
dynamicKeysSupported: false,
|
|
1118
|
+
dynamicStoresSupported: false,
|
|
1119
|
+
arbitraryAssetHandleUseSupported: false,
|
|
1120
|
+
handleEscapeSupported: false,
|
|
1121
|
+
binaryBodySupported: false,
|
|
1122
|
+
streamBodyCompiledWasmReadiness: 'plan-only',
|
|
1123
|
+
uploadsSupported: false,
|
|
1124
|
+
mutationApisSupported: false,
|
|
1125
|
+
rangeRequestsSupported: false,
|
|
1126
|
+
diagnostics: {
|
|
1127
|
+
dynamicKey: assetsContracts.ASSETS_DIAGNOSTIC_CODES.DYNAMIC_KEY_UNSUPPORTED,
|
|
1128
|
+
handleEscape: assetsContracts.ASSETS_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED,
|
|
1129
|
+
binaryBody: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_BINARY_RESERVED,
|
|
1130
|
+
streamBody: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_STREAM_PLAN_ONLY,
|
|
1131
|
+
unsupportedMethod: assetsContracts.ASSETS_DIAGNOSTIC_CODES.COMPILED_METHOD_UNSUPPORTED
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
policy: {
|
|
1135
|
+
packageOwnsSidecar: true,
|
|
1136
|
+
packageOwnsLoweringManifest: true,
|
|
1137
|
+
packageOwnsCompilerBuilder: true,
|
|
1138
|
+
compilerOrchestratesOnly: true,
|
|
1139
|
+
providersOwnRuntimeBehavior: true,
|
|
1140
|
+
compiledWasmRuntimeBehaviorImplemented: false,
|
|
1141
|
+
directProviderProofsRemainSeparate: true
|
|
1142
|
+
},
|
|
1143
|
+
summary: {
|
|
1144
|
+
sidecarDeclared: Boolean(sidecarRef),
|
|
1145
|
+
sidecarFileExists: Boolean(sidecarFile && fs.existsSync(sidecarFile)),
|
|
1146
|
+
sidecarSymbolsValidated: missingSymbols.length === 0 && sidecarSymbols.length > 0,
|
|
1147
|
+
lowerings: sidecarSymbols.length,
|
|
1148
|
+
terminalAssetResponseEffects: terminalEffects.length,
|
|
1149
|
+
terminalAssetResponseEffectPlanned: true,
|
|
1150
|
+
continuationRequired: false,
|
|
1151
|
+
dynamicKeysSupported: false,
|
|
1152
|
+
binaryBodySupported: false,
|
|
1153
|
+
streamBodyCompiledWasmReadiness: 'plan-only',
|
|
1154
|
+
errors: errorDiagnostics.length
|
|
1155
|
+
},
|
|
1156
|
+
diagnostics
|
|
1157
|
+
}, cwd);
|
|
1158
|
+
|
|
1159
|
+
return {
|
|
1160
|
+
artifact,
|
|
1161
|
+
diagnostics,
|
|
1162
|
+
sidecarFile,
|
|
1163
|
+
sidecarSymbols,
|
|
1164
|
+
terminalAssetResponseEffects: terminalEffects,
|
|
1165
|
+
hasErrors: diagnosticHasError(diagnostics)
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
function sha256Text(text) {
|
|
1171
|
+
return crypto.createHash('sha256').update(String(text), 'utf8').digest('hex');
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function encodeU32(value) {
|
|
1175
|
+
let n = Number(value) >>> 0;
|
|
1176
|
+
const out = [];
|
|
1177
|
+
do {
|
|
1178
|
+
let byte = n & 0x7f;
|
|
1179
|
+
n >>>= 7;
|
|
1180
|
+
if (n) byte |= 0x80;
|
|
1181
|
+
out.push(byte);
|
|
1182
|
+
} while (n);
|
|
1183
|
+
return out;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function encodeString(value) {
|
|
1187
|
+
const bytes = Array.from(Buffer.from(String(value), 'utf8'));
|
|
1188
|
+
return [...encodeU32(bytes.length), ...bytes];
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function wasmSection(id, payload) {
|
|
1192
|
+
return [id, ...encodeU32(payload.length), ...payload];
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function wasmVec(items) {
|
|
1196
|
+
return [...encodeU32(items.length), ...items.flat()];
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function instrI32Const(value) {
|
|
1200
|
+
return [0x41, ...encodeU32(Number(value))];
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function instrLocalGet(index) {
|
|
1204
|
+
return [0x20, ...encodeU32(Number(index))];
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function methodCodeForAssetMethod(method) {
|
|
1208
|
+
const normalized = String(method || 'GET').toUpperCase();
|
|
1209
|
+
if (normalized === 'HEAD') return 2;
|
|
1210
|
+
return 1;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function methodFromAssetMethodCode(code) {
|
|
1214
|
+
return Number(code) === 2 ? 'HEAD' : 'GET';
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function normalizeAssetKey(key) {
|
|
1218
|
+
const text = String(key || '/');
|
|
1219
|
+
return text.startsWith('/') ? text : `/${text}`;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function makeLinkedAssetsSidecarHarnessWasm(effects = []) {
|
|
1223
|
+
const normalizedEffects = effects.map((effect, index) => ({
|
|
1224
|
+
index,
|
|
1225
|
+
id: effect.id || `asset-${index}`,
|
|
1226
|
+
store: String(effect.store || (effect.lookup && effect.lookup.store) || 'public'),
|
|
1227
|
+
key: normalizeAssetKey(effect.key || (effect.lookup && effect.lookup.key) || '/'),
|
|
1228
|
+
method: String(effect.method || (effect.lookup && effect.lookup.method) || 'GET').toUpperCase()
|
|
1229
|
+
}));
|
|
1230
|
+
let offset = 16;
|
|
1231
|
+
const data = [];
|
|
1232
|
+
const layouts = [];
|
|
1233
|
+
for (const effect of normalizedEffects) {
|
|
1234
|
+
const storeBytes = Buffer.from(effect.store, 'utf8');
|
|
1235
|
+
const keyBytes = Buffer.from(effect.key, 'utf8');
|
|
1236
|
+
const storePtr = offset;
|
|
1237
|
+
data.push({ offset: storePtr, bytes: Array.from(storeBytes) });
|
|
1238
|
+
offset += storeBytes.length + 1;
|
|
1239
|
+
const keyPtr = offset;
|
|
1240
|
+
data.push({ offset: keyPtr, bytes: Array.from(keyBytes) });
|
|
1241
|
+
offset += keyBytes.length + 1;
|
|
1242
|
+
layouts.push({ ...effect, storePtr, storeLen: storeBytes.length, keyPtr, keyLen: keyBytes.length, methodCode: methodCodeForAssetMethod(effect.method) });
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
const typeSection = wasmSection(1, wasmVec([
|
|
1246
|
+
[0x60, ...wasmVec([[0x7f], [0x7f], [0x7f], [0x7f], [0x7f], [0x7f]]), ...wasmVec([[0x7f]])],
|
|
1247
|
+
[0x60, ...wasmVec([[0x7f], [0x7f]]), ...wasmVec([[0x7f]])],
|
|
1248
|
+
[0x60, ...wasmVec([]), ...wasmVec([[0x7f]])]
|
|
1249
|
+
]));
|
|
1250
|
+
const importSection = wasmSection(2, wasmVec([
|
|
1251
|
+
[...encodeString('pulse_assets_host'), ...encodeString('lookup'), 0x00, ...encodeU32(0)],
|
|
1252
|
+
[...encodeString('pulse_assets_host'), ...encodeString('respond'), 0x00, ...encodeU32(1)]
|
|
1253
|
+
]));
|
|
1254
|
+
const functionTypes = [encodeU32(0), encodeU32(1)].concat(layouts.map(() => encodeU32(2)));
|
|
1255
|
+
const functionSection = wasmSection(3, wasmVec(functionTypes));
|
|
1256
|
+
const memorySection = wasmSection(5, wasmVec([[0x00, ...encodeU32(1)]]));
|
|
1257
|
+
const exports = [
|
|
1258
|
+
[...encodeString('memory'), 0x02, ...encodeU32(0)],
|
|
1259
|
+
[...encodeString('pulse_assets_lookup'), 0x00, ...encodeU32(2)],
|
|
1260
|
+
[...encodeString('pulse_assets_respond'), 0x00, ...encodeU32(3)]
|
|
1261
|
+
];
|
|
1262
|
+
layouts.forEach((layout, index) => {
|
|
1263
|
+
exports.push([...encodeString(`pulse_assets_route_${index}`), 0x00, ...encodeU32(4 + index)]);
|
|
1264
|
+
});
|
|
1265
|
+
const exportSection = wasmSection(7, wasmVec(exports));
|
|
1266
|
+
|
|
1267
|
+
const lookupBodyInstr = [];
|
|
1268
|
+
for (let i = 0; i < 6; i += 1) lookupBodyInstr.push(...instrLocalGet(i));
|
|
1269
|
+
lookupBodyInstr.push(0x10, ...encodeU32(0), 0x0b);
|
|
1270
|
+
const respondBodyInstr = [...instrLocalGet(0), ...instrLocalGet(1), 0x10, ...encodeU32(1), 0x0b];
|
|
1271
|
+
const codeBodies = [lookupBodyInstr, respondBodyInstr].map((instructions) => {
|
|
1272
|
+
const body = [...wasmVec([]), ...instructions];
|
|
1273
|
+
return [...encodeU32(body.length), ...body];
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
for (const layout of layouts) {
|
|
1277
|
+
const instructions = [
|
|
1278
|
+
...instrI32Const(layout.storePtr),
|
|
1279
|
+
...instrI32Const(layout.storeLen),
|
|
1280
|
+
...instrI32Const(layout.keyPtr),
|
|
1281
|
+
...instrI32Const(layout.keyLen),
|
|
1282
|
+
...instrI32Const(layout.methodCode),
|
|
1283
|
+
...instrI32Const(0),
|
|
1284
|
+
0x10, ...encodeU32(2),
|
|
1285
|
+
...instrI32Const(0),
|
|
1286
|
+
0x10, ...encodeU32(3),
|
|
1287
|
+
0x0b
|
|
1288
|
+
];
|
|
1289
|
+
const body = [...wasmVec([]), ...instructions];
|
|
1290
|
+
codeBodies.push([...encodeU32(body.length), ...body]);
|
|
1291
|
+
}
|
|
1292
|
+
const codeSection = wasmSection(10, wasmVec(codeBodies));
|
|
1293
|
+
const dataSegments = data.map((segment) => [0x00, ...instrI32Const(segment.offset), 0x0b, ...wasmVec(segment.bytes.map((byte) => [byte]))]);
|
|
1294
|
+
const dataSection = wasmSection(11, wasmVec(dataSegments));
|
|
1295
|
+
const bytes = Buffer.from([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ...typeSection, ...importSection, ...functionSection, ...memorySection, ...exportSection, ...codeSection, ...dataSection]);
|
|
1296
|
+
const module = new WebAssembly.Module(bytes);
|
|
1297
|
+
return {
|
|
1298
|
+
module,
|
|
1299
|
+
bytes,
|
|
1300
|
+
effects: layouts,
|
|
1301
|
+
imports: WebAssembly.Module.imports(module).map(clone),
|
|
1302
|
+
exports: WebAssembly.Module.exports(module).map(clone)
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function executeLinkedAssetsSidecarHarness(effects = []) {
|
|
1307
|
+
const wasm = makeLinkedAssetsSidecarHarnessWasm(effects);
|
|
1308
|
+
let instance;
|
|
1309
|
+
const decoder = new TextDecoder();
|
|
1310
|
+
const lookups = [];
|
|
1311
|
+
const responses = [];
|
|
1312
|
+
let handleSeq = 1;
|
|
1313
|
+
let resultSeq = 1;
|
|
1314
|
+
const handles = new Map();
|
|
1315
|
+
function memory() { return instance.exports.memory; }
|
|
1316
|
+
function readUtf8(ptr, len) {
|
|
1317
|
+
if (!ptr || !len) return '';
|
|
1318
|
+
return decoder.decode(new Uint8Array(memory().buffer, ptr, len));
|
|
1319
|
+
}
|
|
1320
|
+
const imports = {
|
|
1321
|
+
pulse_assets_host: {
|
|
1322
|
+
lookup(storePtr, storeLen, keyPtr, keyLen, methodCode, optionsRef) {
|
|
1323
|
+
const handle = handleSeq++;
|
|
1324
|
+
const lookup = {
|
|
1325
|
+
handle,
|
|
1326
|
+
store: readUtf8(storePtr, storeLen),
|
|
1327
|
+
key: normalizeAssetKey(readUtf8(keyPtr, keyLen)),
|
|
1328
|
+
method: methodFromAssetMethodCode(methodCode),
|
|
1329
|
+
methodCode: Number(methodCode),
|
|
1330
|
+
optionsRef: Number(optionsRef || 0)
|
|
1331
|
+
};
|
|
1332
|
+
handles.set(handle, lookup);
|
|
1333
|
+
lookups.push(lookup);
|
|
1334
|
+
return handle;
|
|
1335
|
+
},
|
|
1336
|
+
respond(assetHandle, optionsRef) {
|
|
1337
|
+
const resultHandle = resultSeq++;
|
|
1338
|
+
const lookup = handles.get(Number(assetHandle));
|
|
1339
|
+
const response = { resultHandle, assetHandle: Number(assetHandle), optionsRef: Number(optionsRef || 0), lookup };
|
|
1340
|
+
responses.push(response);
|
|
1341
|
+
return resultHandle;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
instance = new WebAssembly.Instance(wasm.module, imports);
|
|
1346
|
+
const routeExports = wasm.exports.filter((entry) => entry.kind === 'function' && /^pulse_assets_route_/.test(entry.name));
|
|
1347
|
+
const executions = [];
|
|
1348
|
+
for (const route of routeExports) {
|
|
1349
|
+
const beforeLookup = lookups.length;
|
|
1350
|
+
const beforeResponse = responses.length;
|
|
1351
|
+
const resultHandle = instance.exports[route.name]();
|
|
1352
|
+
executions.push({
|
|
1353
|
+
exportName: route.name,
|
|
1354
|
+
resultHandle: Number(resultHandle),
|
|
1355
|
+
lookup: lookups[beforeLookup],
|
|
1356
|
+
response: responses[beforeResponse]
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
return { wasm, imports, instance, lookups, responses, executions };
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function sidecarHostImportPresent(source, moduleName, importName, localName) {
|
|
1363
|
+
const pattern = new RegExp(`@external\\(\\s*[\"']${moduleName}[\"']\\s*,\\s*[\"']${importName}[\"']\\s*\\)\\s*declare\\s+function\\s+${localName}\\s*\\(`, 'm');
|
|
1364
|
+
return pattern.test(source);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
function sidecarExportDelegatesTo(source, exportName, localName) {
|
|
1368
|
+
const pattern = new RegExp(`export\\s+function\\s+${exportName}\\s*\\([\\s\\S]*?\\)\\s*:\\s*i32\\s*\\{[\\s\\S]*?return\\s+${localName}\\s*\\(`, 'm');
|
|
1369
|
+
return pattern.test(source);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function resolveAssemblyScriptPackage(cwd) {
|
|
1373
|
+
const searchRoots = [cwd, path.join(cwd, 'wasm'), __dirname].filter(Boolean);
|
|
1374
|
+
for (const root of searchRoots) {
|
|
1375
|
+
try {
|
|
1376
|
+
const packageJson = require.resolve('assemblyscript/package.json', { paths: [root] });
|
|
1377
|
+
const packageRoot = path.dirname(packageJson);
|
|
1378
|
+
const ascScript = path.join(packageRoot, 'bin', 'asc.js');
|
|
1379
|
+
if (fs.existsSync(ascScript)) {
|
|
1380
|
+
let version;
|
|
1381
|
+
try { version = JSON.parse(fs.readFileSync(packageJson, 'utf8')).version; } catch (_) {}
|
|
1382
|
+
return { packageRoot, packageJson, ascScript, version, searchRoot: root };
|
|
1383
|
+
}
|
|
1384
|
+
} catch (_) {}
|
|
1385
|
+
}
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function tryCompileAssetsSidecarWithAsc(cwd, sidecarFile) {
|
|
1390
|
+
const asc = resolveAssemblyScriptPackage(cwd);
|
|
1391
|
+
if (!asc) {
|
|
1392
|
+
return {
|
|
1393
|
+
available: false,
|
|
1394
|
+
status: 'dependency-gated',
|
|
1395
|
+
diagnostic: assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_ASC_DEPENDENCY_GATED,
|
|
1396
|
+
message: 'AssemblyScript compiler dependency was not found in this workspace; strict sidecar compile remains dependency-gated.',
|
|
1397
|
+
command: 'corepack pnpm install --frozen-lockfile && corepack pnpm run -s test:assets-sidecar-compile-link'
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-assets-sidecar-asc-'));
|
|
1401
|
+
const wasmFile = path.join(stagingDir, 'assets-sidecar.wasm');
|
|
1402
|
+
const watFile = path.join(stagingDir, 'assets-sidecar.wat');
|
|
1403
|
+
const args = [
|
|
1404
|
+
asc.ascScript,
|
|
1405
|
+
sidecarFile,
|
|
1406
|
+
'--outFile', wasmFile,
|
|
1407
|
+
'--textFile', watFile,
|
|
1408
|
+
'--runtime', 'stub',
|
|
1409
|
+
'--noAssert'
|
|
1410
|
+
];
|
|
1411
|
+
const proc = spawnSync(process.execPath, args, { cwd, encoding: 'utf8', maxBuffer: 1024 * 1024 * 12 });
|
|
1412
|
+
const ok = proc.status === 0 && fs.existsSync(wasmFile);
|
|
1413
|
+
return {
|
|
1414
|
+
available: true,
|
|
1415
|
+
status: ok ? 'ok' : 'error',
|
|
1416
|
+
version: asc.version,
|
|
1417
|
+
executable: `node ${stableFileName(asc.ascScript, cwd)}`,
|
|
1418
|
+
command: args.slice(1).map((arg) => path.isAbsolute(arg) ? stableFileName(arg, cwd) : arg),
|
|
1419
|
+
exitCode: proc.status,
|
|
1420
|
+
signal: proc.signal,
|
|
1421
|
+
stdout: String(proc.stdout || '').slice(0, 4000),
|
|
1422
|
+
stderr: String(proc.stderr || '').slice(0, 8000),
|
|
1423
|
+
outputs: {
|
|
1424
|
+
wasmBytes: fs.existsSync(wasmFile) ? fs.statSync(wasmFile).size : 0,
|
|
1425
|
+
watBytes: fs.existsSync(watFile) ? fs.statSync(watFile).size : 0
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function buildAssetsSidecarCompileLinkProof(inputs = {}) {
|
|
1431
|
+
const cwd = inputs.cwd || process.cwd();
|
|
1432
|
+
const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
|
|
1433
|
+
const manifestRecords = Array.isArray(inputs.manifestRecords) ? inputs.manifestRecords : [];
|
|
1434
|
+
const manifest = findAssetsManifest(inputs, manifestRecords);
|
|
1435
|
+
const manifestRecord = manifestRecords.find((record) => record && record.manifest && record.manifest.contractId === assetsContracts.ASSETS_CONTRACT_ID);
|
|
1436
|
+
const packageDir = resolveAssetsPackageDir(inputs);
|
|
1437
|
+
const wasm = manifest && manifest.modes && manifest.modes.wasm ? manifest.modes.wasm : {};
|
|
1438
|
+
const sidecarRef = wasm.sidecar;
|
|
1439
|
+
const sidecarFile = sidecarRef ? path.resolve(packageDir, sidecarRef) : undefined;
|
|
1440
|
+
const sidecarSource = sidecarFile && fs.existsSync(sidecarFile) ? fs.readFileSync(sidecarFile, 'utf8') : '';
|
|
1441
|
+
const diagnostics = [];
|
|
1442
|
+
|
|
1443
|
+
const hostImports = {
|
|
1444
|
+
lookup: sidecarHostImportPresent(sidecarSource, 'pulse_assets_host', 'lookup', '__pulse_assets_host_lookup'),
|
|
1445
|
+
respond: sidecarHostImportPresent(sidecarSource, 'pulse_assets_host', 'respond', '__pulse_assets_host_respond')
|
|
1446
|
+
};
|
|
1447
|
+
const delegations = {
|
|
1448
|
+
lookup: sidecarExportDelegatesTo(sidecarSource, 'pulse_assets_lookup', '__pulse_assets_host_lookup'),
|
|
1449
|
+
respond: sidecarExportDelegatesTo(sidecarSource, 'pulse_assets_respond', '__pulse_assets_host_respond')
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
if (!hostImports.lookup || !hostImports.respond) {
|
|
1453
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1454
|
+
phase: compileLinkPhaseName,
|
|
1455
|
+
severity: 'error',
|
|
1456
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_HOST_IMPORT_MISSING,
|
|
1457
|
+
message: 'The assets AssemblyScript sidecar must declare explicit host imports for lookup and respond.',
|
|
1458
|
+
hint: 'Declare @external("pulse_assets_host", "lookup") and @external("pulse_assets_host", "respond") host functions in packages/assets/as/index.as.ts.',
|
|
1459
|
+
loc: { file: sidecarRef || '<assets-sidecar>' },
|
|
1460
|
+
details: { hostImports }
|
|
1461
|
+
}));
|
|
1462
|
+
}
|
|
1463
|
+
if (!delegations.lookup || !delegations.respond) {
|
|
1464
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1465
|
+
phase: compileLinkPhaseName,
|
|
1466
|
+
severity: 'error',
|
|
1467
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_LINK_SMOKE_FAILED,
|
|
1468
|
+
message: 'The assets sidecar exports must delegate to the host imports so the linked module exercises real sidecar symbols.',
|
|
1469
|
+
hint: 'pulse_assets_lookup/respond should return the corresponding __pulse_assets_host_* call result.',
|
|
1470
|
+
loc: { file: sidecarRef || '<assets-sidecar>' },
|
|
1471
|
+
details: { delegations }
|
|
1472
|
+
}));
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
const sidecarPlan = inputs.assetsCompiledWasmSidecarPlan && (inputs.assetsCompiledWasmSidecarPlan.artifact || inputs.assetsCompiledWasmSidecarPlan);
|
|
1476
|
+
const terminalEffects = Array.isArray(sidecarPlan && sidecarPlan.terminalAssetResponseEffects)
|
|
1477
|
+
? sidecarPlan.terminalAssetResponseEffects
|
|
1478
|
+
: assetEntriesForSidecar(inputs, manifest).filter((entry) => entry.response).map((entry) => ({
|
|
1479
|
+
id: entry.id,
|
|
1480
|
+
store: entry.lookup && entry.lookup.store,
|
|
1481
|
+
key: entry.lookup && entry.lookup.key,
|
|
1482
|
+
method: entry.lookup && entry.lookup.method
|
|
1483
|
+
}));
|
|
1484
|
+
|
|
1485
|
+
let linkSmoke;
|
|
1486
|
+
try {
|
|
1487
|
+
const executed = executeLinkedAssetsSidecarHarness(terminalEffects);
|
|
1488
|
+
linkSmoke = {
|
|
1489
|
+
status: 'ok',
|
|
1490
|
+
wasmBytes: executed.wasm.bytes.length,
|
|
1491
|
+
imports: executed.wasm.imports,
|
|
1492
|
+
exports: executed.wasm.exports,
|
|
1493
|
+
routeExports: executed.wasm.exports.filter((entry) => /^pulse_assets_route_/.test(entry.name)).map((entry) => entry.name),
|
|
1494
|
+
executions: executed.executions.map((entry) => ({
|
|
1495
|
+
exportName: entry.exportName,
|
|
1496
|
+
resultHandle: entry.resultHandle,
|
|
1497
|
+
lookup: entry.lookup,
|
|
1498
|
+
response: entry.response
|
|
1499
|
+
})),
|
|
1500
|
+
lookups: executed.lookups.length,
|
|
1501
|
+
responses: executed.responses.length
|
|
1502
|
+
};
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
linkSmoke = { status: 'error', message: error.message };
|
|
1505
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1506
|
+
phase: compileLinkPhaseName,
|
|
1507
|
+
severity: 'error',
|
|
1508
|
+
code: assetsContracts.ASSETS_DIAGNOSTIC_CODES.SIDECAR_LINK_SMOKE_FAILED,
|
|
1509
|
+
message: `The assets sidecar linked Wasm harness failed: ${error.message}`,
|
|
1510
|
+
hint: 'Validate sidecar exports, host import signatures, and terminal effect layout.',
|
|
1511
|
+
loc: { file: '<assets-sidecar-link-proof>' }
|
|
1512
|
+
}));
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
const ascCompile = tryCompileAssetsSidecarWithAsc(cwd, sidecarFile || '');
|
|
1516
|
+
if (ascCompile.available && ascCompile.status !== 'ok') {
|
|
1517
|
+
diagnostics.push(normalizeDiagnostic({
|
|
1518
|
+
phase: compileLinkPhaseName,
|
|
1519
|
+
severity: 'error',
|
|
1520
|
+
code: 'PULSEWASM_ASSETS_SIDECAR_ASC_COMPILE_FAILED',
|
|
1521
|
+
message: 'AssemblyScript was available but failed to compile the package-owned assets sidecar.',
|
|
1522
|
+
hint: 'Inspect assets-sidecar-compile-link-proof.json asc.stderr for compiler details.',
|
|
1523
|
+
loc: { file: sidecarRef || '<assets-sidecar>' },
|
|
1524
|
+
details: { exitCode: ascCompile.exitCode, stderr: ascCompile.stderr }
|
|
1525
|
+
}));
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
const placeholderRemoved = !/export\s+function\s+pulse_assets_lookup[\s\S]*?\{[\s\S]*?return\s+0\s*;/.test(sidecarSource);
|
|
1529
|
+
const routeExports = linkSmoke && Array.isArray(linkSmoke.routeExports) ? linkSmoke.routeExports : [];
|
|
1530
|
+
const linkedModuleImportsHost = Boolean(linkSmoke && linkSmoke.imports && linkSmoke.imports.some((entry) => entry.module === 'pulse_assets_host' && entry.name === 'lookup') && linkSmoke.imports.some((entry) => entry.module === 'pulse_assets_host' && entry.name === 'respond'));
|
|
1531
|
+
const linkedModuleExportsSidecar = Boolean(linkSmoke && linkSmoke.exports && linkSmoke.exports.some((entry) => entry.name === 'pulse_assets_lookup') && linkSmoke.exports.some((entry) => entry.name === 'pulse_assets_respond'));
|
|
1532
|
+
const linkedModuleExecutesRoutes = Boolean(linkSmoke && linkSmoke.status === 'ok' && linkSmoke.executions && linkSmoke.executions.length === terminalEffects.length && terminalEffects.length > 0);
|
|
1533
|
+
const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
|
|
1534
|
+
|
|
1535
|
+
const artifact = normalizeArtifact({
|
|
1536
|
+
version: assetsContracts.ASSETS_SIDECAR_COMPILE_LINK_PROOF_VERSION,
|
|
1537
|
+
artifact: assetsContracts.ASSETS_SIDECAR_COMPILE_LINK_PROOF_ARTIFACT,
|
|
1538
|
+
generatedBy,
|
|
1539
|
+
phase: compileLinkPhaseName,
|
|
1540
|
+
status: errorDiagnostics.length === 0 ? 'ok' : 'error',
|
|
1541
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
1542
|
+
package: manifest ? manifest.npmPackage : '@pulse-compute/assets',
|
|
1543
|
+
lowerableSubpath: manifest ? manifest.lowerableSubpath : '@pulse-compute/assets/pulsewasm',
|
|
1544
|
+
sourceAssetsCompiledWasmSidecarPlanVersion: sidecarPlan && sidecarPlan.version,
|
|
1545
|
+
sidecar: {
|
|
1546
|
+
path: sidecarRef,
|
|
1547
|
+
file: sidecarFile || undefined,
|
|
1548
|
+
sourceBytes: sidecarSource.length,
|
|
1549
|
+
sourceSha256: sidecarSource ? sha256Text(sidecarSource) : undefined,
|
|
1550
|
+
hostImports,
|
|
1551
|
+
delegations,
|
|
1552
|
+
placeholderImplementationRemoved: placeholderRemoved
|
|
1553
|
+
},
|
|
1554
|
+
assemblyScript: {
|
|
1555
|
+
compilerAvailable: ascCompile.available,
|
|
1556
|
+
compileStatus: ascCompile.status,
|
|
1557
|
+
version: ascCompile.version,
|
|
1558
|
+
dependencyGated: ascCompile.status === 'dependency-gated',
|
|
1559
|
+
strictInstalledWorkspaceCommand: 'corepack pnpm install --frozen-lockfile && corepack pnpm run -s test:assets-sidecar-compile-link',
|
|
1560
|
+
output: ascCompile.outputs,
|
|
1561
|
+
exitCode: ascCompile.exitCode,
|
|
1562
|
+
stderr: ascCompile.stderr,
|
|
1563
|
+
diagnostic: ascCompile.diagnostic
|
|
1564
|
+
},
|
|
1565
|
+
linkModel: {
|
|
1566
|
+
hostImportModule: 'pulse_assets_host',
|
|
1567
|
+
hostImports: ['lookup', 'respond'],
|
|
1568
|
+
sidecarExports: ['pulse_assets_lookup', 'pulse_assets_respond'],
|
|
1569
|
+
generatedUserRouteHarness: true,
|
|
1570
|
+
routeCallsSidecarExports: true,
|
|
1571
|
+
terminalAssetResponseEffect: true,
|
|
1572
|
+
continuationRequired: false,
|
|
1573
|
+
directProviderProofFallback: false,
|
|
1574
|
+
generatedInMemoryRouteHarness: true,
|
|
1575
|
+
actualPackageSidecarSourceValidated: true
|
|
1576
|
+
},
|
|
1577
|
+
wasmModule: {
|
|
1578
|
+
byteLength: linkSmoke && linkSmoke.wasmBytes || 0,
|
|
1579
|
+
imports: linkSmoke && linkSmoke.imports || [],
|
|
1580
|
+
exports: linkSmoke && linkSmoke.exports || [],
|
|
1581
|
+
routeExports
|
|
1582
|
+
},
|
|
1583
|
+
linkSmoke,
|
|
1584
|
+
packageOut: manifestRecord ? {
|
|
1585
|
+
discoveredFromNodeModules: manifestRecord.source === 'node_modules' || String(manifestRecord.source || '').includes('node_modules'),
|
|
1586
|
+
packageDir: stableFileName(manifestRecord.packageDir || packageDir, cwd),
|
|
1587
|
+
manifestFile: stableFileName(manifestRecord.manifestFile, cwd)
|
|
1588
|
+
} : undefined,
|
|
1589
|
+
policy: {
|
|
1590
|
+
betaQualityProof: true,
|
|
1591
|
+
productionCompletenessRequired: false,
|
|
1592
|
+
packageOwnsSidecarSource: true,
|
|
1593
|
+
packageOwnsCompilerBuilder: true,
|
|
1594
|
+
compilerOrchestratesOnly: true,
|
|
1595
|
+
providersOwnRuntimeBehavior: true,
|
|
1596
|
+
arbitraryThirdPartyLowererExecution: false,
|
|
1597
|
+
actualAscCompileStrictWhenDependencyPresent: true,
|
|
1598
|
+
dependencyMissingDoesNotWeakenInstalledWorkspaceLane: true
|
|
1599
|
+
},
|
|
1600
|
+
summary: {
|
|
1601
|
+
sidecarSourceValidated: Boolean(sidecarSource),
|
|
1602
|
+
sidecarHostImportsValidated: hostImports.lookup && hostImports.respond,
|
|
1603
|
+
sidecarExportDelegationValidated: delegations.lookup && delegations.respond,
|
|
1604
|
+
placeholderImplementationRemoved: placeholderRemoved,
|
|
1605
|
+
linkedModuleImportsHost: linkedModuleImportsHost,
|
|
1606
|
+
linkedModuleExportsSidecar: linkedModuleExportsSidecar,
|
|
1607
|
+
linkedModuleExecuted: linkedModuleExecutesRoutes,
|
|
1608
|
+
routeExports: routeExports.length,
|
|
1609
|
+
terminalAssetResponseEffects: terminalEffects.length,
|
|
1610
|
+
actualAssemblyScriptCompilerAvailable: ascCompile.available,
|
|
1611
|
+
actualAssemblyScriptCompileValidated: ascCompile.available ? ascCompile.status === 'ok' : false,
|
|
1612
|
+
actualAssemblyScriptCompileReadiness: ascCompile.available ? (ascCompile.status === 'ok' ? 'implemented' : ascCompile.status) : 'dependency-gated',
|
|
1613
|
+
dependencyGatedInThisWorkspace: ascCompile.status === 'dependency-gated',
|
|
1614
|
+
directProviderProofFallback: false,
|
|
1615
|
+
errors: errorDiagnostics.length
|
|
1616
|
+
},
|
|
1617
|
+
diagnostics
|
|
1618
|
+
}, cwd);
|
|
1619
|
+
|
|
1620
|
+
return {
|
|
1621
|
+
artifact,
|
|
1622
|
+
diagnostics,
|
|
1623
|
+
linkSmoke,
|
|
1624
|
+
ascCompile,
|
|
1625
|
+
hasErrors: diagnosticHasError(diagnostics)
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
const assetsLoweringBuilder = Object.freeze({
|
|
1630
|
+
name: 'assets-lowering-builder',
|
|
1631
|
+
owner: '@pulse-compute/assets',
|
|
1632
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
1633
|
+
buildAssetsLoweringPlan
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
const assetsCompiledWasmSidecarBuilder = Object.freeze({
|
|
1637
|
+
name: 'assets-compiled-wasm-sidecar-builder',
|
|
1638
|
+
owner: '@pulse-compute/assets',
|
|
1639
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
1640
|
+
buildAssetsCompiledWasmSidecarPlan
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
const assetsSidecarCompileLinkBuilder = Object.freeze({
|
|
1644
|
+
name: 'assets-sidecar-compile-link-builder',
|
|
1645
|
+
owner: '@pulse-compute/assets',
|
|
1646
|
+
contractId: assetsContracts.ASSETS_CONTRACT_ID,
|
|
1647
|
+
buildAssetsSidecarCompileLinkProof
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
module.exports = {
|
|
1651
|
+
buildAssetsLoweringPlan,
|
|
1652
|
+
buildAssetsCompiledWasmSidecarPlan,
|
|
1653
|
+
buildAssetsSidecarCompileLinkProof,
|
|
1654
|
+
assetsLoweringBuilder,
|
|
1655
|
+
assetsCompiledWasmSidecarBuilder,
|
|
1656
|
+
assetsSidecarCompileLinkBuilder,
|
|
1657
|
+
phaseName,
|
|
1658
|
+
sidecarPhaseName,
|
|
1659
|
+
compileLinkPhaseName
|
|
1660
|
+
};
|