@pulse-compute/wasm-compiler 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 +52 -1
- package/bin/provider-proof-composition.js +34 -0
- package/bin/pulsewasm-extract.js +20 -0
- package/package.json +60 -5
- package/src/artifacts-dir.js +13 -0
- package/src/ast-json.js +52 -0
- package/src/build-manifest.js +354 -0
- package/src/canonical-api-compiler.js +245 -0
- package/src/canonical-native-compiler.js +411 -0
- package/src/canonical-native-plan.js +1477 -0
- package/src/canonical-project-compiler.js +1218 -0
- package/src/canonical-router-compiler.js +25 -0
- package/src/cli-intents.js +1235 -0
- package/src/cli.js +1927 -0
- package/src/codegen/assemblyscript-compile.js +3 -0
- package/src/codegen/assemblyscript-core.js +3 -0
- package/src/codegen/assemblyscript-shape.js +3 -0
- package/src/codegen/assemblyscript-wasm-smoke.js +3 -0
- package/src/codegen/backend-capabilities.js +3 -0
- package/src/codegen/channel-broadcaster.js +3 -0
- package/src/codegen/compiled-handlers.js +3 -0
- package/src/codegen/compiled-wasm-runtime.js +3 -0
- package/src/codegen/config-references.js +248 -0
- package/src/codegen/dispatch-ts.js +145 -0
- package/src/codegen/effect-composition.js +331 -0
- package/src/codegen/effect-runtime.js +418 -0
- package/src/codegen/execution-harness-ts.js +467 -0
- package/src/codegen/handler-bindings-ts.js +298 -0
- package/src/codegen/handler-library-contracts.js +3 -0
- package/src/codegen/host-capabilities.js +3 -0
- package/src/codegen/host-runtime-kernel.js +3 -0
- package/src/codegen/integrated-compiled-app.js +3 -0
- package/src/codegen/json-body.js +3 -0
- package/src/codegen/library-sidecars.js +3 -0
- package/src/codegen/local-harness-ts.js +453 -0
- package/src/codegen/pulse-wrapper.js +217 -0
- package/src/codegen/request-result-headers.js +3 -0
- package/src/codegen/schema-json-compile.js +3 -0
- package/src/codegen/schema-json-sidecar-v2.js +3 -0
- package/src/codegen/schema-json-sidecar.js +3 -0
- package/src/codegen/streaming-passthrough.js +3 -0
- package/src/codegen/wasm-host-abi.js +3 -0
- package/src/codegen/wasm-host-bridge.js +3 -0
- package/src/compiled-wasm-host-runtime-kv.js +3 -0
- package/src/config-resolver.js +813 -0
- package/src/crypto-requirement-planner.js +89 -0
- package/src/definitions/config-schema.js +14 -0
- package/src/definitions/handler-roles.js +14 -0
- package/src/definitions/path-grammar.js +14 -0
- package/src/definitions/router-api.js +14 -0
- package/src/diagnostics/codes.js +14 -0
- package/src/diagnostics/reporter.js +14 -0
- package/src/diagnostics.js +14 -0
- package/src/dispatch-table.js +400 -0
- package/src/events/event-emit.js +265 -0
- package/src/events/event-topology.js +127 -0
- package/src/execution-plan.js +463 -0
- package/src/extractor.js +2816 -0
- package/src/handler-eval.js +1154 -0
- package/src/handler-table.js +326 -0
- package/src/index.js +19 -0
- package/src/javascript-application-plan.js +181 -0
- package/src/kv-provider.js +3 -0
- package/src/path-table.js +60 -0
- package/src/path.js +14 -0
- package/src/patterns/config-define.js +30 -0
- package/src/patterns/dependency-call.js +18 -0
- package/src/patterns/env-lookup.js +13 -0
- package/src/patterns/handler-reference.js +29 -0
- package/src/patterns/path-literal.js +35 -0
- package/src/patterns/result.js +15 -0
- package/src/patterns/router-chain-call.js +50 -0
- package/src/patterns/router-construction.js +19 -0
- package/src/project/package-reachability.js +782 -0
- package/src/project/reachable-graph-builder.js +992 -0
- package/src/project/reachable-graph-contract.js +54 -0
- package/src/project/reachable-graph-implementation.js +36 -0
- package/src/project/router-module-linker.js +710 -0
- package/src/project-config-compiler.js +222 -0
- package/src/project-target-support.js +500 -0
- package/src/provider-toolchain.js +299 -0
- package/src/spine/async-surface-normalizer.js +312 -0
- package/src/spine/canonical-handler-ir.js +335 -0
- package/src/spine/canonical-native-module.js +87 -0
- package/src/spine/canonical-native-plan.js +89 -0
- package/src/spine/canonical-project.js +76 -0
- package/src/spine/canonical-router.js +155 -0
- package/src/spine/canonical-source.js +165 -0
- package/src/spine/diagnostic-authority.js +290 -0
- package/src/spine/equivalence.js +262 -0
- package/src/spine/guest-unit-stage.js +87 -0
- package/src/spine/handler-ir-emitter.js +454 -0
- package/src/spine/handler-ir-managed.js +1597 -0
- package/src/spine/handler-ir.js +797 -0
- package/src/spine/handler-surface-authority.js +585 -0
- package/src/spine/package-operation-seam.js +1015 -0
- package/src/spine/pipeline.js +202 -0
- package/src/spine/plain-handler-frontend.js +537 -0
- package/src/spine/provider-requirement-authority.js +208 -0
- package/src/spine/router-control-contract.js +17 -0
- package/src/spine/router-handler-frontend.js +513 -0
- package/src/spine/router-handler-ir.js +372 -0
- package/src/spine/router-topology-frontend.js +635 -0
- package/src/stable-id.js +14 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PACKAGE_VERSION, normalizeArtifact, stableFileName } = require('./diagnostics.js');
|
|
4
|
+
const { createHandlerStableId, createHandlerStableInput } = require('./stable-id.js');
|
|
5
|
+
const { DEFAULT_HANDLER_ROLE_REGISTRY, ROLE_SIGNATURES, roleForOperation } = require('./definitions/handler-roles.js');
|
|
6
|
+
const { HANDLER_ID_POLICY, HANDLER_TABLE_VERSION } = loadContractsHandlerTable();
|
|
7
|
+
const EVENT_ROLE_SIGNATURE = Object.freeze({ params: 1, display: '(ctx) => Promise<void>' });
|
|
8
|
+
|
|
9
|
+
function loadContractsHandlerTable() {
|
|
10
|
+
try {
|
|
11
|
+
return require('@pulse-compute/wasm-contracts/handler-table');
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
14
|
+
return require('../../contracts/src/handler-table.js');
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function clone(value) {
|
|
21
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function uniqueSorted(values) {
|
|
25
|
+
return Array.from(new Set(values)).sort();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handlerDisplayName(entry) {
|
|
29
|
+
return entry.localName || entry.exportName || entry.inlineName || entry.id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createUse(router, op, role, ref) {
|
|
33
|
+
const use = {
|
|
34
|
+
role,
|
|
35
|
+
router: router.name,
|
|
36
|
+
opKind: op.kind,
|
|
37
|
+
order: op.order,
|
|
38
|
+
statementOrder: op.statementOrder,
|
|
39
|
+
loc: ref?.loc || op.loc
|
|
40
|
+
};
|
|
41
|
+
if (op.route || op.kind === 'get' || op.kind === 'post') {
|
|
42
|
+
use.method = op.method || op.kind.toUpperCase();
|
|
43
|
+
use.path = op.path;
|
|
44
|
+
}
|
|
45
|
+
if (op.kind === 'use' && op.path) use.path = op.path;
|
|
46
|
+
if (op.kind === 'lifecycle') use.event = op.event;
|
|
47
|
+
if (role === 'event') {
|
|
48
|
+
use.event = op.type;
|
|
49
|
+
use.schemaId = op.declaration && op.declaration.schemaId;
|
|
50
|
+
}
|
|
51
|
+
return use;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function expectedSignatureForRole(role, registry = DEFAULT_HANDLER_ROLE_REGISTRY) {
|
|
55
|
+
const definition = registry.get(role);
|
|
56
|
+
return definition ? { params: definition.params, display: definition.display } : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateEntryForRole(entry, role, use, diagnostics, options = {}) {
|
|
60
|
+
entry._validatedRoleKeys ||= new Set();
|
|
61
|
+
const key = `${entry.id}:${role}`;
|
|
62
|
+
if (entry._validatedRoleKeys.has(key)) return;
|
|
63
|
+
entry._validatedRoleKeys.add(key);
|
|
64
|
+
|
|
65
|
+
const expected = typeof options.expectedSignatureForRole === 'function'
|
|
66
|
+
? options.expectedSignatureForRole(role, entry, use)
|
|
67
|
+
: expectedSignatureForRole(role);
|
|
68
|
+
const signature = entry.signature || {};
|
|
69
|
+
const loc = entry.loc || use?.loc || { file: entry.file || '<unknown>' };
|
|
70
|
+
|
|
71
|
+
if (!expected) return;
|
|
72
|
+
|
|
73
|
+
// Sprint 1 Wave 2: managed Router handlers are async-shaped authoring functions.
|
|
74
|
+
// Promise runtime semantics remain rejected by the shared handler-surface normalizer.
|
|
75
|
+
|
|
76
|
+
if ((signature.nonIdentifierParams || []).length > 0) {
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
pass: 'handler-table',
|
|
79
|
+
code: 'PULSEWASM_UNSUPPORTED_HANDLER_PARAMETER',
|
|
80
|
+
severity: 'error',
|
|
81
|
+
message: `Handler "${handlerDisplayName(entry)}" uses destructured or non-identifier parameters.`,
|
|
82
|
+
hint: `Use the explicit ${expected.display} role signature.`,
|
|
83
|
+
loc
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const expectedParams = Array.isArray(expected.params) ? expected.params : [expected.params];
|
|
88
|
+
if (!expectedParams.includes(signature.paramCount)) {
|
|
89
|
+
diagnostics.push({
|
|
90
|
+
pass: 'handler-table',
|
|
91
|
+
code: 'PULSEWASM_ROLE_SIGNATURE_MISMATCH',
|
|
92
|
+
severity: 'error',
|
|
93
|
+
message: `Handler "${handlerDisplayName(entry)}" is used as ${role} but has ${signature.paramCount ?? 0} parameter(s).`,
|
|
94
|
+
hint: `Expected ${expected.display}.`,
|
|
95
|
+
loc
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cleanEntry(entry) {
|
|
101
|
+
const out = { ...entry };
|
|
102
|
+
delete out._validatedRoleKeys;
|
|
103
|
+
out.roles = uniqueSorted(out.roles || []);
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildHandlerTable(ir, options = {}) {
|
|
108
|
+
const cwd = options.cwd || process.cwd();
|
|
109
|
+
const diagnostics = [];
|
|
110
|
+
const declarationsByName = new Map();
|
|
111
|
+
const entriesBySymbolName = new Map();
|
|
112
|
+
const entriesById = new Map();
|
|
113
|
+
const handlers = [];
|
|
114
|
+
let nextOrder = 0;
|
|
115
|
+
|
|
116
|
+
for (const decl of ir.handlerIR || []) {
|
|
117
|
+
declarationsByName.set(decl.symbolId || decl.name, decl);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function addEntry(entry) {
|
|
121
|
+
const existing = entriesById.get(entry.id);
|
|
122
|
+
if (existing) return existing;
|
|
123
|
+
entry.order = nextOrder++;
|
|
124
|
+
entry.roles ||= [];
|
|
125
|
+
entry.uses ||= [];
|
|
126
|
+
entriesById.set(entry.id, entry);
|
|
127
|
+
handlers.push(entry);
|
|
128
|
+
if (entry.symbolId) entriesBySymbolName.set(entry.symbolId, entry);
|
|
129
|
+
else if (entry.localName) entriesBySymbolName.set(entry.localName, entry);
|
|
130
|
+
return entry;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function entryFromDeclaration(decl) {
|
|
134
|
+
const localName = decl.localName || decl.name;
|
|
135
|
+
const symbolId = decl.symbolId && decl.symbolId !== localName ? decl.symbolId : undefined;
|
|
136
|
+
const lookupName = symbolId || localName;
|
|
137
|
+
const existing = entriesBySymbolName.get(lookupName);
|
|
138
|
+
if (existing) return existing;
|
|
139
|
+
const stableInput = createHandlerStableInput({
|
|
140
|
+
file: decl.file,
|
|
141
|
+
kind: decl.kind,
|
|
142
|
+
localName,
|
|
143
|
+
exportName: decl.exportName,
|
|
144
|
+
inline: false,
|
|
145
|
+
sourceTextHash: decl.sourceTextHash
|
|
146
|
+
});
|
|
147
|
+
const id = createHandlerStableId({
|
|
148
|
+
file: decl.file,
|
|
149
|
+
kind: decl.kind,
|
|
150
|
+
localName,
|
|
151
|
+
exportName: decl.exportName,
|
|
152
|
+
inline: false,
|
|
153
|
+
sourceTextHash: decl.sourceTextHash
|
|
154
|
+
});
|
|
155
|
+
return addEntry({
|
|
156
|
+
id,
|
|
157
|
+
stableInput,
|
|
158
|
+
kind: decl.kind,
|
|
159
|
+
...(symbolId ? { symbolId } : {}),
|
|
160
|
+
localName,
|
|
161
|
+
exportName: decl.exportName,
|
|
162
|
+
file: decl.file,
|
|
163
|
+
loc: decl.loc,
|
|
164
|
+
sourceTextHash: decl.sourceTextHash,
|
|
165
|
+
declaration: {
|
|
166
|
+
topLevel: true,
|
|
167
|
+
exported: Boolean(decl.exportName)
|
|
168
|
+
},
|
|
169
|
+
signature: decl.signature
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function entryFromInline(ref, role, router, op) {
|
|
174
|
+
const inlineName = `<inline:${role}:${router.name}:${op.order}>`;
|
|
175
|
+
const file = stableFileName(ref.loc?.file || ir.source, cwd);
|
|
176
|
+
const stableLoc = ref.loc
|
|
177
|
+
? {
|
|
178
|
+
file,
|
|
179
|
+
start: ref.loc.start,
|
|
180
|
+
end: ref.loc.end
|
|
181
|
+
}
|
|
182
|
+
: undefined;
|
|
183
|
+
const stableInput = createHandlerStableInput({
|
|
184
|
+
file,
|
|
185
|
+
kind: ref.inlineKind || 'inline-function',
|
|
186
|
+
localName: null,
|
|
187
|
+
exportName: null,
|
|
188
|
+
inline: true,
|
|
189
|
+
loc: stableLoc,
|
|
190
|
+
sourceTextHash: ref.sourceTextHash
|
|
191
|
+
});
|
|
192
|
+
const id = createHandlerStableId({
|
|
193
|
+
file,
|
|
194
|
+
kind: ref.inlineKind || 'inline-function',
|
|
195
|
+
localName: null,
|
|
196
|
+
exportName: null,
|
|
197
|
+
inline: true,
|
|
198
|
+
loc: stableLoc,
|
|
199
|
+
sourceTextHash: ref.sourceTextHash
|
|
200
|
+
});
|
|
201
|
+
return addEntry({
|
|
202
|
+
id,
|
|
203
|
+
stableInput,
|
|
204
|
+
kind: ref.inlineKind || 'inline-function',
|
|
205
|
+
inlineName,
|
|
206
|
+
file,
|
|
207
|
+
loc: ref.loc,
|
|
208
|
+
sourceTextHash: ref.sourceTextHash,
|
|
209
|
+
declaration: {
|
|
210
|
+
topLevel: false,
|
|
211
|
+
exported: false
|
|
212
|
+
},
|
|
213
|
+
signature: ref.signature
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveRef(ref, role, router, op) {
|
|
218
|
+
if (!ref) return ref;
|
|
219
|
+
if (ref.kind === 'static') return clone(ref);
|
|
220
|
+
|
|
221
|
+
let entry;
|
|
222
|
+
if (ref.kind === 'identifier' || ref.kind === 'ref') {
|
|
223
|
+
const decl = declarationsByName.get(ref.value);
|
|
224
|
+
if (!decl) {
|
|
225
|
+
diagnostics.push({
|
|
226
|
+
pass: 'handler-table',
|
|
227
|
+
code: 'PULSEWASM_UNRESOLVED_HANDLER_REFERENCE',
|
|
228
|
+
severity: 'error',
|
|
229
|
+
message: `Unable to resolve ${role} handler "${ref.value}" as a top-level handler declaration.`,
|
|
230
|
+
hint: 'Use a top-level function declaration, const arrow function, or const function expression in the same entry file.',
|
|
231
|
+
loc: ref.loc || op.loc
|
|
232
|
+
});
|
|
233
|
+
return { kind: 'unresolved', name: ref.value, role, loc: ref.loc || op.loc };
|
|
234
|
+
}
|
|
235
|
+
entry = entryFromDeclaration(decl);
|
|
236
|
+
} else if (ref.kind === 'inline') {
|
|
237
|
+
entry = entryFromInline(ref, role, router, op);
|
|
238
|
+
} else {
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
pass: 'handler-table',
|
|
241
|
+
code: 'PULSEWASM_UNSUPPORTED_HANDLER_REFERENCE',
|
|
242
|
+
severity: 'error',
|
|
243
|
+
message: `Unsupported ${role} handler reference kind "${ref.kind}".`,
|
|
244
|
+
hint: 'Use a direct handler identifier or an inline function expression.',
|
|
245
|
+
loc: ref.loc || op.loc
|
|
246
|
+
});
|
|
247
|
+
return { kind: 'unsupported', name: ref.value, role, loc: ref.loc || op.loc };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const use = createUse(router, op, role, ref);
|
|
251
|
+
entry.roles.push(role);
|
|
252
|
+
entry.uses.push(use);
|
|
253
|
+
validateEntryForRole(entry, role, use, diagnostics, options);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
kind: 'handler',
|
|
257
|
+
id: entry.id,
|
|
258
|
+
name: entry.localName || entry.inlineName,
|
|
259
|
+
role,
|
|
260
|
+
loc: ref.loc || op.loc
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const resolvedRouterIR = (ir.routerIR || []).map((router) => ({
|
|
265
|
+
...router,
|
|
266
|
+
ops: (router.ops || []).map((op) => {
|
|
267
|
+
const next = clone(op);
|
|
268
|
+
const role = roleForOperation(next);
|
|
269
|
+
if (!role) return next;
|
|
270
|
+
if (next.kind === 'channel') {
|
|
271
|
+
if (next.value && next.value.kind !== 'static') {
|
|
272
|
+
next.value = resolveRef(next.value, role, router, next);
|
|
273
|
+
}
|
|
274
|
+
return next;
|
|
275
|
+
}
|
|
276
|
+
if (next.handler) next.handler = resolveRef(next.handler, role, router, next);
|
|
277
|
+
return next;
|
|
278
|
+
})
|
|
279
|
+
}));
|
|
280
|
+
|
|
281
|
+
const resolvedEventIR = (ir.eventIR || []).map((op) => {
|
|
282
|
+
const next = clone(op);
|
|
283
|
+
if (next.handler) next.handler = resolveRef(next.handler, 'event', { name: next.router }, next);
|
|
284
|
+
return next;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const artifactHandlers = handlers.map(cleanEntry);
|
|
288
|
+
const eventActive = resolvedEventIR.length > 0;
|
|
289
|
+
const artifact = normalizeArtifact({
|
|
290
|
+
version: HANDLER_TABLE_VERSION,
|
|
291
|
+
generatedBy: PACKAGE_VERSION,
|
|
292
|
+
source: ir.source,
|
|
293
|
+
idPolicy: HANDLER_ID_POLICY,
|
|
294
|
+
roleSignatures: eventActive ? { ...ROLE_SIGNATURES, event: EVENT_ROLE_SIGNATURE } : ROLE_SIGNATURES,
|
|
295
|
+
handlers: artifactHandlers,
|
|
296
|
+
summary: {
|
|
297
|
+
handlers: artifactHandlers.length,
|
|
298
|
+
declarationHandlers: artifactHandlers.filter((entry) => entry.declaration?.topLevel).length,
|
|
299
|
+
inlineHandlers: artifactHandlers.filter((entry) => !entry.declaration?.topLevel).length,
|
|
300
|
+
roles: uniqueSorted(artifactHandlers.flatMap((entry) => entry.roles || [])),
|
|
301
|
+
...(eventActive ? { eventHandlers: artifactHandlers.filter((entry) => entry.roles.includes('event')).length } : {}),
|
|
302
|
+
diagnostics: diagnostics.length
|
|
303
|
+
}
|
|
304
|
+
}, cwd);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
handlerTable: artifact,
|
|
308
|
+
diagnostics,
|
|
309
|
+
resolvedIR: {
|
|
310
|
+
...ir,
|
|
311
|
+
routerIR: resolvedRouterIR,
|
|
312
|
+
...(eventActive ? { eventIR: resolvedEventIR } : {}),
|
|
313
|
+
handlerIR: artifact.handlers
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = {
|
|
319
|
+
HANDLER_ID_POLICY,
|
|
320
|
+
HANDLER_TABLE_VERSION,
|
|
321
|
+
ROLE_SIGNATURES,
|
|
322
|
+
EVENT_ROLE_SIGNATURE,
|
|
323
|
+
buildHandlerTable,
|
|
324
|
+
expectedSignatureForRole,
|
|
325
|
+
roleForOperation
|
|
326
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
astJson: require('./ast-json.js'),
|
|
5
|
+
buildManifest: require('./build-manifest.js'),
|
|
6
|
+
cli: require('./cli.js'),
|
|
7
|
+
configResolver: require('./config-resolver.js'),
|
|
8
|
+
projectConfigCompiler: require('./project-config-compiler.js'),
|
|
9
|
+
diagnostics: require('./diagnostics.js'),
|
|
10
|
+
canonicalApiCompiler: require('./canonical-api-compiler.js'),
|
|
11
|
+
canonicalNativePlan: require('./canonical-native-plan.js'),
|
|
12
|
+
canonicalNativeCompiler: require('./canonical-native-compiler.js'),
|
|
13
|
+
dispatchTable: require('./dispatch-table.js'),
|
|
14
|
+
executionPlan: require('./execution-plan.js'),
|
|
15
|
+
extractor: require('./extractor.js'),
|
|
16
|
+
handlerEval: require('./handler-eval.js'),
|
|
17
|
+
handlerTable: require('./handler-table.js'),
|
|
18
|
+
pathTable: require('./path-table.js')
|
|
19
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const {
|
|
5
|
+
buildReachableProjectGraph,
|
|
6
|
+
ReachableProjectGraphError
|
|
7
|
+
} = require('./project/reachable-graph-builder.js');
|
|
8
|
+
const {
|
|
9
|
+
JAVASCRIPT_APPLICATION_PLAN_VERSION,
|
|
10
|
+
normalizeJavascriptApplicationPlan
|
|
11
|
+
} = require('@pulse-compute/wasm-contracts/project/javascript-application');
|
|
12
|
+
|
|
13
|
+
const JAVASCRIPT_APPLICATION_PLANNER_VERSION = 'pulse.javascript-application-planner.v1';
|
|
14
|
+
const CORE_APPLICATION_PACKAGES = new Set(['@pulse-compute/runtime', '@pulse-compute/pulse']);
|
|
15
|
+
|
|
16
|
+
class JavascriptApplicationPlanError extends Error {
|
|
17
|
+
constructor(code, message, detail = {}, diagnostics = []) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'JavascriptApplicationPlanError';
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.detail = Object.freeze({ ...detail });
|
|
22
|
+
this.diagnostics = Object.freeze([...diagnostics]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function moduleSources(graph, moduleId) {
|
|
27
|
+
return Object.freeze(graph.edges
|
|
28
|
+
.filter((edge) => edge.runtime && edge.to === moduleId && edge.source)
|
|
29
|
+
.map((edge) => edge.source)
|
|
30
|
+
.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function packageStatus(module, graphBuild) {
|
|
34
|
+
if (CORE_APPLICATION_PACKAGES.has(module.packageName)) {
|
|
35
|
+
return Object.freeze({ status: 'supplied-core', runtimeEntry: module.packageName, reasonCode: null });
|
|
36
|
+
}
|
|
37
|
+
const product = graphBuild.packageProduct && graphBuild.packageProduct.contracts
|
|
38
|
+
.find((entry) => entry.packageName === module.packageName);
|
|
39
|
+
if (product) {
|
|
40
|
+
const target = product.javascriptTarget;
|
|
41
|
+
if (target && target.status === 'supported') {
|
|
42
|
+
return Object.freeze({ status: 'package-runtime', runtimeEntry: target.entry, reasonCode: null });
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
status: 'unavailable',
|
|
46
|
+
runtimeEntry: null,
|
|
47
|
+
reasonCode: target && target.reasonCode || 'PULSE_JAVASCRIPT_PACKAGE_REALIZATION_UNAVAILABLE'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (module.packageContract) {
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
status: 'unavailable',
|
|
53
|
+
runtimeEntry: null,
|
|
54
|
+
reasonCode: 'PULSE_JAVASCRIPT_PACKAGE_REALIZATION_UNAVAILABLE'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return Object.freeze({ status: 'node-require', runtimeEntry: module.packageName, reasonCode: null });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createPackageEntries(graphBuild) {
|
|
61
|
+
return graphBuild.graph.modules
|
|
62
|
+
.filter((module) => module.runtime && module.kind === 'package')
|
|
63
|
+
.map((module) => {
|
|
64
|
+
const status = packageStatus(module, graphBuild);
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
moduleId: module.id,
|
|
67
|
+
packageName: module.packageName,
|
|
68
|
+
packageSubpath: module.packageSubpath,
|
|
69
|
+
contractId: module.packageContract,
|
|
70
|
+
status: status.status,
|
|
71
|
+
runtimeEntry: status.runtimeEntry,
|
|
72
|
+
reasonCode: status.reasonCode,
|
|
73
|
+
sources: moduleSources(graphBuild.graph, module.id)
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createBlockers(graphBuild, packages) {
|
|
79
|
+
const blockers = [];
|
|
80
|
+
if (graphBuild.entrySafety && graphBuild.entrySafety.importSafe === false) {
|
|
81
|
+
const first = graphBuild.nativeEligibility && graphBuild.nativeEligibility.blockers
|
|
82
|
+
.find((entry) => entry.kind === 'application-entry-lifecycle-side-effect');
|
|
83
|
+
blockers.push(Object.freeze({
|
|
84
|
+
code: 'PULSE_APPLICATION_ENTRY_LIFECYCLE_SIDE_EFFECT',
|
|
85
|
+
kind: 'application-entry-lifecycle-side-effect',
|
|
86
|
+
message: 'The configured Pulse application entry starts a known provider lifecycle during module evaluation.',
|
|
87
|
+
moduleId: first && first.moduleId || graphBuild.graph.entry,
|
|
88
|
+
source: first && first.source || { file: graphBuild.entryKey, line: 1, column: 1 }
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
for (const entry of packages.filter((item) => item.status === 'unavailable')) {
|
|
92
|
+
const source = entry.sources[0] || { file: graphBuild.entryKey, line: 1, column: 1 };
|
|
93
|
+
blockers.push(Object.freeze({
|
|
94
|
+
code: entry.reasonCode || 'PULSE_JAVASCRIPT_PACKAGE_REALIZATION_UNAVAILABLE',
|
|
95
|
+
kind: 'javascript-package-realization-unavailable',
|
|
96
|
+
message: `Package ${entry.packageName}${entry.packageSubpath && entry.packageSubpath !== '.' ? entry.packageSubpath.slice(1) : ''} has no JavaScript realization for the selected target.`,
|
|
97
|
+
moduleId: entry.moduleId,
|
|
98
|
+
packageName: entry.packageName,
|
|
99
|
+
packageSubpath: entry.packageSubpath,
|
|
100
|
+
contractId: entry.contractId,
|
|
101
|
+
source
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
return blockers;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildJavascriptApplicationPlan(entryFile, options = {}) {
|
|
108
|
+
const absoluteEntry = path.resolve(entryFile);
|
|
109
|
+
let graphBuild;
|
|
110
|
+
try {
|
|
111
|
+
graphBuild = buildReachableProjectGraph(absoluteEntry, {
|
|
112
|
+
rootDir: options.rootDir,
|
|
113
|
+
workspaceRoot: options.workspaceRoot,
|
|
114
|
+
configFile: options.configFile,
|
|
115
|
+
projectFragments: options.projectFragments,
|
|
116
|
+
tsconfig: options.tsconfig
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error instanceof ReachableProjectGraphError || error && error.name === 'ReachableProjectGraphError') {
|
|
120
|
+
throw new JavascriptApplicationPlanError(
|
|
121
|
+
error.code || 'PULSE_JAVASCRIPT_APPLICATION_GRAPH_FAILED',
|
|
122
|
+
error.message,
|
|
123
|
+
error.detail,
|
|
124
|
+
error.diagnostics
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
const entryModule = graphBuild.graph.modules.find((module) => module.id === graphBuild.graph.entry);
|
|
130
|
+
if (!entryModule || entryModule.kind !== 'project' || !entryModule.path) {
|
|
131
|
+
throw new JavascriptApplicationPlanError(
|
|
132
|
+
'PULSE_JAVASCRIPT_APPLICATION_ENTRY_INVALID',
|
|
133
|
+
'JavaScript application plan entry must resolve to a project module.',
|
|
134
|
+
{ entryFile: absoluteEntry, graphEntry: graphBuild.graph.entry }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const packages = createPackageEntries(graphBuild);
|
|
138
|
+
const blockers = createBlockers(graphBuild, packages);
|
|
139
|
+
const requestedEnvelope = options.capabilityEnvelope || {};
|
|
140
|
+
const capabilityEnvelope = Object.freeze({
|
|
141
|
+
version: requestedEnvelope.version || 'pulse.javascript-capability-envelope.v1',
|
|
142
|
+
provider: requestedEnvelope.provider || options.provider || 'node',
|
|
143
|
+
status: requestedEnvelope.status || 'provider-adapters-deferred',
|
|
144
|
+
core: requestedEnvelope.core || Object.freeze({ request: true, response: true, state: true }),
|
|
145
|
+
effects: requestedEnvelope.effects || Object.freeze({ fetch: false, config: false, secret: false, kv: false }),
|
|
146
|
+
packages: Object.freeze(packages.map((entry) => Object.freeze({
|
|
147
|
+
contractId: entry.contractId,
|
|
148
|
+
packageName: entry.packageName,
|
|
149
|
+
status: entry.status
|
|
150
|
+
})))
|
|
151
|
+
});
|
|
152
|
+
const plan = normalizeJavascriptApplicationPlan({
|
|
153
|
+
provider: options.provider || 'node',
|
|
154
|
+
targetId: options.targetId || 'node-javascript',
|
|
155
|
+
entryPath: entryModule.path,
|
|
156
|
+
graphVersion: graphBuild.graph.version,
|
|
157
|
+
graphHash: graphBuild.graph.graphHash,
|
|
158
|
+
resolverHash: graphBuild.graph.resolver.resolverHash,
|
|
159
|
+
entryModuleId: graphBuild.graph.entry,
|
|
160
|
+
modules: graphBuild.graph.modules,
|
|
161
|
+
edges: graphBuild.graph.edges,
|
|
162
|
+
packages,
|
|
163
|
+
blockers,
|
|
164
|
+
packageProduct: graphBuild.packageProduct,
|
|
165
|
+
nativeEligibility: graphBuild.nativeEligibility,
|
|
166
|
+
entrySafety: graphBuild.entrySafety,
|
|
167
|
+
capabilityEnvelope,
|
|
168
|
+
providerLifecycleDeferred: options.providerLifecycleDeferred !== false
|
|
169
|
+
});
|
|
170
|
+
return Object.freeze({
|
|
171
|
+
...plan,
|
|
172
|
+
plannerVersion: JAVASCRIPT_APPLICATION_PLANNER_VERSION
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = Object.freeze({
|
|
177
|
+
JAVASCRIPT_APPLICATION_PLANNER_VERSION,
|
|
178
|
+
JAVASCRIPT_APPLICATION_PLAN_VERSION,
|
|
179
|
+
JavascriptApplicationPlanError,
|
|
180
|
+
buildJavascriptApplicationPlan
|
|
181
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PACKAGE_VERSION, normalizeArtifact } = require('./diagnostics.js');
|
|
4
|
+
const { PATH_POLICY, compileRoutePath, publicPathPattern } = require('./path.js');
|
|
5
|
+
const { PATH_TABLE_VERSION } = loadContractsPathTable();
|
|
6
|
+
|
|
7
|
+
function loadContractsPathTable() {
|
|
8
|
+
try {
|
|
9
|
+
return require('@pulse-compute/wasm-contracts/path-table');
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
12
|
+
return require('../../contracts/src/path-table.js');
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildPathTable(routePlan, options = {}) {
|
|
19
|
+
const cwd = options.cwd || process.cwd();
|
|
20
|
+
const routes = (routePlan.routes || []).map((route) => {
|
|
21
|
+
const compiled = route.pathPattern || publicPathPattern(compileRoutePath(route.path, { allowWildcard: true }));
|
|
22
|
+
return {
|
|
23
|
+
stableId: route.stableId,
|
|
24
|
+
runtimeId: route.runtimeId ?? route.id,
|
|
25
|
+
method: route.method,
|
|
26
|
+
path: route.path,
|
|
27
|
+
params: route.params || [],
|
|
28
|
+
pattern: compiled,
|
|
29
|
+
scopedMiddleware: route.scopedMiddleware || []
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const scopedMiddlewareMatches = routes.reduce((sum, route) => sum + (route.scopedMiddleware || []).length, 0);
|
|
34
|
+
const wildcardRoutes = routes.filter((route) => route.pattern && route.pattern.wildcard).length;
|
|
35
|
+
const paramNames = new Set();
|
|
36
|
+
for (const route of routes) {
|
|
37
|
+
for (const param of route.params || []) paramNames.add(param);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return normalizeArtifact({
|
|
41
|
+
version: PATH_TABLE_VERSION,
|
|
42
|
+
generatedBy: options.generatedBy || routePlan.generatedBy || PACKAGE_VERSION,
|
|
43
|
+
source: routePlan.source,
|
|
44
|
+
entryRouter: routePlan.entryRouter,
|
|
45
|
+
policy: PATH_POLICY,
|
|
46
|
+
routes,
|
|
47
|
+
summary: {
|
|
48
|
+
routes: routes.length,
|
|
49
|
+
wildcardRoutes,
|
|
50
|
+
scopedMiddlewareMatches,
|
|
51
|
+
uniqueParams: paramNames.size,
|
|
52
|
+
paramNames: Array.from(paramNames).sort()
|
|
53
|
+
}
|
|
54
|
+
}, cwd);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
PATH_TABLE_VERSION,
|
|
59
|
+
buildPathTable
|
|
60
|
+
};
|
package/src/path.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function loadContractsPath() {
|
|
4
|
+
try {
|
|
5
|
+
return require('@pulse-compute/wasm-contracts/path');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
8
|
+
return require('../../contracts/src/path.js');
|
|
9
|
+
}
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = loadContractsPath();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isTopLevelDefineConfigCall(ts, sourceFile, call) {
|
|
4
|
+
const parent = call.parent;
|
|
5
|
+
if (!parent) return false;
|
|
6
|
+
if (ts.isExportAssignment(parent)) return true;
|
|
7
|
+
if (ts.isVariableDeclaration(parent)) {
|
|
8
|
+
const declList = parent.parent;
|
|
9
|
+
const varStmt = declList && declList.parent;
|
|
10
|
+
return Boolean(varStmt && ts.isVariableStatement(varStmt) && varStmt.parent === sourceFile);
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collectDefineConfigCalls(ts, sourceFile) {
|
|
16
|
+
const calls = [];
|
|
17
|
+
function walk(node) {
|
|
18
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'defineConfig') {
|
|
19
|
+
calls.push(node);
|
|
20
|
+
}
|
|
21
|
+
ts.forEachChild(node, walk);
|
|
22
|
+
}
|
|
23
|
+
walk(sourceFile);
|
|
24
|
+
return calls;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
collectDefineConfigCalls,
|
|
29
|
+
isTopLevelDefineConfigCall
|
|
30
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function classifyCallExpression(ts, call, scope) {
|
|
4
|
+
const expr = call.expression;
|
|
5
|
+
if (ts.isIdentifier(expr)) {
|
|
6
|
+
if (expr.text === 'next') return { kind: 'continuation', name: 'next', resolved: true };
|
|
7
|
+
if (scope && scope.localFunctions && scope.localFunctions.has(expr.text)) {
|
|
8
|
+
return { kind: 'local-function', name: expr.text, resolved: true };
|
|
9
|
+
}
|
|
10
|
+
return { kind: 'identifier', name: expr.text, resolved: false };
|
|
11
|
+
}
|
|
12
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
13
|
+
return { kind: 'member', name: expr.name.text, resolved: false };
|
|
14
|
+
}
|
|
15
|
+
return { kind: 'dynamic', name: call.getText ? call.getText() : '<dynamic>', resolved: false };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { classifyCallExpression };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isEnvCall(ts, expr, envParamName) {
|
|
4
|
+
if (ts.isIdentifier(expr) && expr.text === envParamName) {
|
|
5
|
+
return { method: 'string' };
|
|
6
|
+
}
|
|
7
|
+
if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === envParamName) {
|
|
8
|
+
return { method: expr.name.text, nameNode: expr.name };
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { isEnvCall };
|