@pulse-compute/wasm-compiler 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 +53 -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 +269 -0
- package/src/canonical-native-compiler.js +411 -0
- package/src/canonical-native-plan.js +1478 -0
- package/src/canonical-project-compiler.js +1224 -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 +328 -0
- package/src/spine/canonical-handler-ir.js +336 -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 +455 -0
- package/src/spine/handler-ir-managed.js +1598 -0
- package/src/spine/handler-ir.js +797 -0
- package/src/spine/handler-surface-authority.js +588 -0
- package/src/spine/package-operation-seam.js +1015 -0
- package/src/spine/pipeline.js +202 -0
- package/src/spine/plain-handler-frontend.js +545 -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 +514 -0
- package/src/spine/router-handler-ir.js +372 -0
- package/src/spine/router-topology-frontend.js +638 -0
- package/src/stable-id.js +14 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const ts = require('typescript');
|
|
6
|
+
const {
|
|
7
|
+
createCanonicalDiagnostic,
|
|
8
|
+
adaptRouterDiagnostic
|
|
9
|
+
} = require('./diagnostic-authority.js');
|
|
10
|
+
const {
|
|
11
|
+
buildRouterIR,
|
|
12
|
+
normalizePathIR,
|
|
13
|
+
resolveRouterTree,
|
|
14
|
+
createRoutePlanResult
|
|
15
|
+
} = require('../extractor.js');
|
|
16
|
+
const { buildHandlerTable } = require('../handler-table.js');
|
|
17
|
+
const { buildPathTable } = require('../path-table.js');
|
|
18
|
+
const { buildDispatchTable } = require('../dispatch-table.js');
|
|
19
|
+
const { buildExecutionPlan } = require('../execution-plan.js');
|
|
20
|
+
const { buildEventTopology } = require('../events/event-topology.js');
|
|
21
|
+
const { analyzeEventEmitHandlers } = require('../events/event-emit.js');
|
|
22
|
+
const { ROUTER_API_DEFINITIONS } = require('../definitions/router-api.js');
|
|
23
|
+
|
|
24
|
+
const ROUTER_TOPOLOGY_FRONTEND_VERSION = 'pulse.router-topology-frontend.v1';
|
|
25
|
+
const CANONICAL_ROUTER_COMPILER_VERSION = 'pulse.canonical-router-compiler.v2';
|
|
26
|
+
const CANONICAL_ROUTER_AUTHORING_VERSION = 'pulse.router-authoring.v2';
|
|
27
|
+
const CANONICAL_ROUTER_EXECUTION_VERSION = 'pulse.router-terminal-execution.v1';
|
|
28
|
+
const ROUTER_IMPORT = '@pulse-compute/runtime';
|
|
29
|
+
const ROUTER_CLASS = 'Router';
|
|
30
|
+
const PULSE_IMPORT = '@pulse-compute/pulse';
|
|
31
|
+
const PULSE_CLASS = 'Pulse';
|
|
32
|
+
const SUPPORTED_ROUTE_METHODS = new Set(ROUTER_API_DEFINITIONS
|
|
33
|
+
.filter((definition) => definition.kind === 'route')
|
|
34
|
+
.map((definition) => definition.method));
|
|
35
|
+
const DISALLOWED_OPERATION_KINDS = new Map([
|
|
36
|
+
['lifecycle', 'realtime lifecycle handlers'],
|
|
37
|
+
['channel', 'realtime channel registration'],
|
|
38
|
+
['timeout', 'router timeout scopes']
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
class CanonicalRouterCompileError extends Error {
|
|
42
|
+
constructor(message, diagnostics = []) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'CanonicalRouterCompileError';
|
|
45
|
+
this.code = 'PULSE_CANONICAL_ROUTER_COMPILE_FAILED';
|
|
46
|
+
this.diagnostics = Object.freeze([...diagnostics]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function diagnostic(sourceFile, node, code, message, detail = {}) {
|
|
51
|
+
return createCanonicalDiagnostic({
|
|
52
|
+
frontend: 'canonical-router',
|
|
53
|
+
sourceFile,
|
|
54
|
+
node,
|
|
55
|
+
code,
|
|
56
|
+
message,
|
|
57
|
+
detail
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function extractorDiagnostic(sourceFile, entry) {
|
|
62
|
+
return adaptRouterDiagnostic(sourceFile, entry);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseRouterSource(sourceText, fileName) {
|
|
66
|
+
return ts.createSourceFile(fileName, String(sourceText), ts.ScriptTarget.ES2022, true, fileName.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function applicationImports(sourceFile) {
|
|
70
|
+
const found = [];
|
|
71
|
+
for (const statement of sourceFile.statements) {
|
|
72
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
73
|
+
const moduleName = statement.moduleSpecifier.text;
|
|
74
|
+
const expected = moduleName === ROUTER_IMPORT ? ROUTER_CLASS : (moduleName === PULSE_IMPORT ? PULSE_CLASS : undefined);
|
|
75
|
+
if (!expected) continue;
|
|
76
|
+
const bindings = statement.importClause && statement.importClause.namedBindings;
|
|
77
|
+
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
78
|
+
const item = bindings.elements.find((element) => (element.propertyName ? element.propertyName.text : element.name.text) === expected && element.name.text === expected);
|
|
79
|
+
if (item) found.push(Object.freeze({ statement, item, moduleName, className: expected, kind: expected === PULSE_CLASS ? 'pulse' : 'router' }));
|
|
80
|
+
}
|
|
81
|
+
return Object.freeze(found);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function directDefaultImport(sourceFile, localName) {
|
|
85
|
+
if (!localName) return undefined;
|
|
86
|
+
for (const statement of sourceFile.statements) {
|
|
87
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
88
|
+
const clause = statement.importClause;
|
|
89
|
+
if (clause && clause.name && clause.name.text === localName) {
|
|
90
|
+
return Object.freeze({ statement, moduleName: statement.moduleSpecifier.text, localName });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function stripModuleExtension(value) {
|
|
97
|
+
return String(value).replace(/\.(?:[cm]?[jt]sx?)$/i, '');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function configImportMatches(importRecord, options, fileName) {
|
|
101
|
+
if (!importRecord || !importRecord.moduleName.startsWith('.')) return false;
|
|
102
|
+
if (!options.configFile) return /(?:^|\/)\.pulse\/config(?:\.[cm]?[jt]sx?)?$/.test(importRecord.moduleName.replace(/\\/g, '/'));
|
|
103
|
+
const sourceFile = path.resolve(options.rootDir || process.cwd(), fileName);
|
|
104
|
+
const imported = stripModuleExtension(path.resolve(path.dirname(sourceFile), importRecord.moduleName));
|
|
105
|
+
const configured = stripModuleExtension(path.resolve(options.configFile));
|
|
106
|
+
return imported === configured;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function rootApplicationDeclaration(sourceFile, rootName) {
|
|
110
|
+
if (!rootName) return undefined;
|
|
111
|
+
for (const statement of sourceFile.statements) {
|
|
112
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
113
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
114
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== rootName || !declaration.initializer) continue;
|
|
115
|
+
let current = declaration.initializer;
|
|
116
|
+
while (current && (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression?.(current) || ts.isNonNullExpression(current) || ts.isTypeAssertionExpression(current))) current = current.expression;
|
|
117
|
+
if (current && ts.isNewExpression(current) && ts.isIdentifier(current.expression) && [ROUTER_CLASS, PULSE_CLASS].includes(current.expression.text)) {
|
|
118
|
+
return Object.freeze({ statement, declaration, expression: current, className: current.expression.text, kind: current.expression.text === PULSE_CLASS ? 'pulse' : 'router' });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function rootRouterName(sourceFile) {
|
|
126
|
+
for (const statement of sourceFile.statements) {
|
|
127
|
+
if (ts.isExportAssignment(statement) && !statement.isExportEquals && ts.isIdentifier(statement.expression)) return statement.expression.text;
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function detectCanonicalRouterSource(sourceText, options = {}) {
|
|
133
|
+
const sourceFile = parseRouterSource(sourceText, String(options.fileName || 'app.ts'));
|
|
134
|
+
return Boolean(applicationImports(sourceFile).length > 0 || sourceFile.statements.some((statement) => {
|
|
135
|
+
if (!ts.isVariableStatement(statement)) return false;
|
|
136
|
+
return statement.declarationList.declarations.some((declaration) => {
|
|
137
|
+
let initializer = declaration.initializer;
|
|
138
|
+
while (initializer && (ts.isParenthesizedExpression(initializer) || ts.isAsExpression(initializer) || ts.isSatisfiesExpression?.(initializer) || ts.isNonNullExpression(initializer) || ts.isTypeAssertionExpression(initializer))) initializer = initializer.expression;
|
|
139
|
+
return initializer && ts.isNewExpression(initializer) && ts.isIdentifier(initializer.expression) && [ROUTER_CLASS, PULSE_CLASS].includes(initializer.expression.text);
|
|
140
|
+
});
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function functionForNamedHandler(sourceFile, name) {
|
|
145
|
+
for (const statement of sourceFile.statements) {
|
|
146
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.name.text === name) return statement;
|
|
147
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
148
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
149
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name || !declaration.initializer) continue;
|
|
150
|
+
let value = declaration.initializer;
|
|
151
|
+
while (value && (ts.isParenthesizedExpression(value) || ts.isAsExpression(value) || ts.isSatisfiesExpression?.(value) || ts.isNonNullExpression(value) || ts.isTypeAssertionExpression(value))) value = value.expression;
|
|
152
|
+
if (value && (ts.isArrowFunction(value) || ts.isFunctionExpression(value))) return value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function functionAtOffset(sourceFile, offset) {
|
|
159
|
+
let match;
|
|
160
|
+
function visit(node) {
|
|
161
|
+
if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.getStart(sourceFile) === offset) match = node;
|
|
162
|
+
if (!match) ts.forEachChild(node, visit);
|
|
163
|
+
}
|
|
164
|
+
visit(sourceFile);
|
|
165
|
+
return match;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handlerNode(sourceFile, handler) {
|
|
169
|
+
if (handler && handler.localName) return functionForNamedHandler(sourceFile, handler.localName);
|
|
170
|
+
const offset = handler && handler.loc && handler.loc.start && handler.loc.start.offset;
|
|
171
|
+
return Number.isInteger(offset) ? functionAtOffset(sourceFile, offset) : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function expectedSignature(role) {
|
|
175
|
+
if (role === 'event') return { counts: [1], names: [['ctx']], display: '(ctx)' };
|
|
176
|
+
if (role === 'route') return { counts: [1, 2], names: [['ctx'], ['ctx', 'next']], display: '(ctx) or (ctx, next)' };
|
|
177
|
+
if (role === 'middleware') return { counts: [2], names: [['ctx', 'next']], display: '(ctx, next)' };
|
|
178
|
+
if (role === 'error') return { counts: [3], names: [['error', 'ctx', 'next']], display: '(error, ctx, next)' };
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function stableEntryId(entry) {
|
|
183
|
+
const input = JSON.stringify({
|
|
184
|
+
index: entry.index,
|
|
185
|
+
kind: entry.kind,
|
|
186
|
+
order: entry.order,
|
|
187
|
+
routerPath: entry.routerPath,
|
|
188
|
+
method: entry.method,
|
|
189
|
+
path: entry.path,
|
|
190
|
+
handlerId: entry.handler && (entry.handler.id || entry.handler.handlerId),
|
|
191
|
+
childRouter: entry.childRouter
|
|
192
|
+
});
|
|
193
|
+
return `router_entry_${crypto.createHash('sha256').update(input).digest('hex').slice(0, 24)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function fakeHandlerEval(handlerTable) {
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
version: 'pulse.canonical-router-static-handler-eval.v1',
|
|
199
|
+
handlers: Object.freeze((handlerTable.handlers || []).map((handler) => Object.freeze({
|
|
200
|
+
handlerId: handler.id,
|
|
201
|
+
status: 'ok',
|
|
202
|
+
captures: Object.freeze([]),
|
|
203
|
+
calls: Object.freeze([]),
|
|
204
|
+
deps: Object.freeze([]),
|
|
205
|
+
dependencyChain: Object.freeze([]),
|
|
206
|
+
globals: Object.freeze([]),
|
|
207
|
+
imports: Object.freeze([]),
|
|
208
|
+
unsupported: Object.freeze([])
|
|
209
|
+
})))
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function httpHandlerTable(handlerTable) {
|
|
214
|
+
const handlers = (handlerTable.handlers || []).filter((handler) => (handler.roles || []).some((role) => role !== 'event'));
|
|
215
|
+
if (handlers.length === handlerTable.handlers.length) return handlerTable;
|
|
216
|
+
return Object.freeze({ ...handlerTable, handlers: Object.freeze(handlers) });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function cleanExecutionEntry(entry) {
|
|
220
|
+
return Object.freeze({
|
|
221
|
+
stableId: stableEntryId(entry),
|
|
222
|
+
index: entry.index,
|
|
223
|
+
nextIndex: entry.nextIndex,
|
|
224
|
+
kind: entry.kind,
|
|
225
|
+
order: entry.order,
|
|
226
|
+
router: entry.router,
|
|
227
|
+
routerPath: Object.freeze([...(entry.routerPath || [])]),
|
|
228
|
+
path: entry.path || undefined,
|
|
229
|
+
scoped: entry.scoped === true,
|
|
230
|
+
pattern: entry.pattern && entry.pattern.normalized || undefined,
|
|
231
|
+
method: entry.method || undefined,
|
|
232
|
+
routeStableId: entry.stableId || undefined,
|
|
233
|
+
routeRuntimeId: Number.isInteger(entry.runtimeId) ? entry.runtimeId : undefined,
|
|
234
|
+
handlerId: entry.handler && (entry.handler.id || entry.handler.handlerId) || undefined,
|
|
235
|
+
childRouter: entry.childRouter || undefined,
|
|
236
|
+
childStartIndex: Number.isInteger(entry.childStartIndex) ? entry.childStartIndex : undefined,
|
|
237
|
+
parentContinueIndex: Number.isInteger(entry.parentContinueIndex) ? entry.parentContinueIndex : undefined
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function retainedDeclarations(sourceFile, options = {}) {
|
|
242
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
243
|
+
const out = [];
|
|
244
|
+
for (const statement of sourceFile.statements) {
|
|
245
|
+
if (statement === options.configImportStatement) continue;
|
|
246
|
+
if (ts.isImportDeclaration(statement)) {
|
|
247
|
+
if (ts.isStringLiteral(statement.moduleSpecifier) && [ROUTER_IMPORT, PULSE_IMPORT].includes(statement.moduleSpecifier.text)) {
|
|
248
|
+
const clause = statement.importClause;
|
|
249
|
+
const bindings = clause && clause.namedBindings;
|
|
250
|
+
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
251
|
+
const removedClass = statement.moduleSpecifier.text === PULSE_IMPORT ? PULSE_CLASS : ROUTER_CLASS;
|
|
252
|
+
const kept = bindings.elements.filter((element) => (element.propertyName ? element.propertyName.text : element.name.text) !== removedClass);
|
|
253
|
+
if (kept.length === 0 && !clause.name) continue;
|
|
254
|
+
const allTypeOnly = kept.length > 0 && kept.every((element) => element.isTypeOnly);
|
|
255
|
+
const elements = kept.map((element) => ts.factory.updateImportSpecifier(element, false, element.propertyName, element.name));
|
|
256
|
+
const nextClause = ts.factory.updateImportClause(clause, clause.isTypeOnly || allTypeOnly, clause.name, ts.factory.updateNamedImports(bindings, elements));
|
|
257
|
+
out.push(printer.printNode(ts.EmitHint.Unspecified, ts.factory.updateImportDeclaration(statement, statement.modifiers, nextClause, statement.moduleSpecifier, statement.attributes), sourceFile));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
out.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile));
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) out.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile));
|
|
264
|
+
}
|
|
265
|
+
return out.join('\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function validatePulseProfileTokenUses(sourceFile, rootName, diagnostics) {
|
|
269
|
+
if (!rootName) return;
|
|
270
|
+
function visit(node) {
|
|
271
|
+
if (ts.isCallExpression(node)
|
|
272
|
+
&& ts.isPropertyAccessExpression(node.expression)
|
|
273
|
+
&& ts.isIdentifier(node.expression.expression)
|
|
274
|
+
&& node.expression.expression.text === rootName
|
|
275
|
+
&& node.expression.name.text === 'profile') {
|
|
276
|
+
let current = node;
|
|
277
|
+
let parent = current.parent;
|
|
278
|
+
while (parent && (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isSatisfiesExpression?.(parent) || ts.isNonNullExpression(parent) || ts.isTypeAssertionExpression(parent))) {
|
|
279
|
+
current = parent;
|
|
280
|
+
parent = current.parent;
|
|
281
|
+
}
|
|
282
|
+
const directArgument = Boolean(parent && ts.isCallExpression(parent) && parent.expression !== current && parent.arguments.some((argument) => argument === current));
|
|
283
|
+
if (node.arguments.length !== 0 || !directArgument) {
|
|
284
|
+
diagnostics.push(diagnostic(
|
|
285
|
+
sourceFile,
|
|
286
|
+
node,
|
|
287
|
+
'PULSE_PROFILE_TOKEN_DIRECT_USE_REQUIRED',
|
|
288
|
+
'app.profile() is an opaque composition token and may only be passed directly to one composition helper. Package ownership is resolved by the reachable module graph in a later implementation wave.',
|
|
289
|
+
{ argumentCount: node.arguments.length, directArgument }
|
|
290
|
+
));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
ts.forEachChild(node, visit);
|
|
294
|
+
}
|
|
295
|
+
visit(sourceFile);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function routerPreludeSource(eventReachable = false) {
|
|
299
|
+
const eventPrelude = eventReachable
|
|
300
|
+
? '\nfunction __pulse_event_runtime_id() { return -1; }'
|
|
301
|
+
: '';
|
|
302
|
+
return `
|
|
303
|
+
function __pulse_router_segments(value) {
|
|
304
|
+
const text = String(value || '/');
|
|
305
|
+
if (text === '/') return [];
|
|
306
|
+
return text.replace(/^\\/+|\\/+$/g, '').split('/');
|
|
307
|
+
}
|
|
308
|
+
function __pulse_router_match(path, pattern) {
|
|
309
|
+
const actual = __pulse_router_segments(path);
|
|
310
|
+
const expected = __pulse_router_segments(pattern);
|
|
311
|
+
const wildcard = expected.length > 0 && expected[expected.length - 1] === '*';
|
|
312
|
+
if ((!wildcard && actual.length !== expected.length) || (wildcard && actual.length < expected.length - 1)) return false;
|
|
313
|
+
const limit = wildcard ? expected.length - 1 : expected.length;
|
|
314
|
+
for (let index = 0; index < limit; index += 1) {
|
|
315
|
+
const part = expected[index];
|
|
316
|
+
if (part.startsWith(':')) { if (!actual[index]) return false; continue; }
|
|
317
|
+
if (actual[index] !== part) return false;
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
function __pulse_router_param(path, pattern, name) {
|
|
322
|
+
const actual = __pulse_router_segments(path);
|
|
323
|
+
const expected = __pulse_router_segments(pattern);
|
|
324
|
+
for (let index = 0; index < expected.length; index += 1) {
|
|
325
|
+
if (expected[index] === ':' + name) return actual[index];
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
}${eventPrelude}
|
|
329
|
+
`.trim();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function prepareCanonicalRouterTopology(sourceText, options = {}) {
|
|
333
|
+
const fileName = String(options.fileName || 'app.ts').replace(/\\/g, '/');
|
|
334
|
+
const linkedProjectModules = options.linkedProjectModules;
|
|
335
|
+
const sourceFile = linkedProjectModules ? linkedProjectModules.rootSourceFile : parseRouterSource(sourceText, fileName);
|
|
336
|
+
const rootFileName = sourceFile.fileName.replace(/\\/g, '/');
|
|
337
|
+
const diagnostics = [...(linkedProjectModules && linkedProjectModules.diagnostics || [])];
|
|
338
|
+
const imports = applicationImports(sourceFile);
|
|
339
|
+
if (imports.length === 0) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_IMPORT_REQUIRED', `Canonical application authoring requires a direct named Router import from ${ROUTER_IMPORT} or Pulse import from ${PULSE_IMPORT}.`));
|
|
340
|
+
const rootLocalName = linkedProjectModules ? linkedProjectModules.rootLocalName : rootRouterName(sourceFile);
|
|
341
|
+
const rootName = linkedProjectModules ? linkedProjectModules.rootGlobalName : rootLocalName;
|
|
342
|
+
const rootApplication = rootApplicationDeclaration(sourceFile, rootLocalName);
|
|
343
|
+
if (!rootName) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_DEFAULT_EXPORT_REQUIRED', 'Canonical Router authoring requires export default <routerIdentifier>.'));
|
|
344
|
+
|
|
345
|
+
if (rootName && !rootApplication) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_APPLICATION_ROOT_REQUIRED', 'The default export must reference a direct top-level new Router() or new Pulse(...) declaration.'));
|
|
346
|
+
if (rootApplication) {
|
|
347
|
+
const expectedImport = rootApplication.kind === 'pulse' ? PULSE_CLASS : ROUTER_CLASS;
|
|
348
|
+
if (!imports.some((entry) => entry.className === expectedImport)) {
|
|
349
|
+
diagnostics.push(diagnostic(sourceFile, rootApplication.expression, 'PULSE_CANONICAL_APPLICATION_IMPORT_REQUIRED', `Application root ${expectedImport} requires a direct unaliased named import from ${rootApplication.kind === 'pulse' ? PULSE_IMPORT : ROUTER_IMPORT}.`, { className: expectedImport }));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const statement of sourceFile.statements) {
|
|
353
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
354
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
355
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
356
|
+
let value = declaration.initializer;
|
|
357
|
+
while (value && (ts.isParenthesizedExpression(value) || ts.isAsExpression(value) || ts.isSatisfiesExpression?.(value) || ts.isNonNullExpression(value) || ts.isTypeAssertionExpression(value))) value = value.expression;
|
|
358
|
+
if (value && ts.isNewExpression(value) && ts.isIdentifier(value.expression) && value.expression.text === PULSE_CLASS && declaration.name.text !== rootLocalName) {
|
|
359
|
+
diagnostics.push(diagnostic(sourceFile, value, 'PULSE_CANONICAL_PULSE_ROOT_ONLY', 'Pulse may be constructed only for the default application root. Mounted and child applications use Router.', { declaration: declaration.name.text }));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
let applicationConstructionMode = rootApplication && rootApplication.kind === 'pulse' ? 'invalid' : 'router';
|
|
364
|
+
let applicationConfigImport;
|
|
365
|
+
if (rootApplication && rootApplication.kind === 'pulse') {
|
|
366
|
+
const args = rootApplication.expression.arguments || [];
|
|
367
|
+
const auto = args.length === 1 && ts.isObjectLiteralExpression(args[0])
|
|
368
|
+
&& args[0].properties.length === 1
|
|
369
|
+
&& ts.isPropertyAssignment(args[0].properties[0])
|
|
370
|
+
&& ((ts.isIdentifier(args[0].properties[0].name) && args[0].properties[0].name.text === 'auto') || (ts.isStringLiteral(args[0].properties[0].name) && args[0].properties[0].name.text === 'auto'))
|
|
371
|
+
&& args[0].properties[0].initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
372
|
+
const explicitIdentifier = args.length === 1 && ts.isIdentifier(args[0]) ? args[0] : undefined;
|
|
373
|
+
const explicitImport = explicitIdentifier ? directDefaultImport(sourceFile, explicitIdentifier.text) : undefined;
|
|
374
|
+
const explicit = Boolean(explicitImport && configImportMatches(explicitImport, options, rootFileName));
|
|
375
|
+
applicationConstructionMode = auto ? 'auto' : (explicit ? 'explicit' : 'invalid');
|
|
376
|
+
applicationConfigImport = explicit ? explicitImport : undefined;
|
|
377
|
+
if (!auto && !explicit) diagnostics.push(diagnostic(
|
|
378
|
+
sourceFile,
|
|
379
|
+
rootApplication.expression,
|
|
380
|
+
'PULSE_CANONICAL_PULSE_CONSTRUCTOR_UNSUPPORTED',
|
|
381
|
+
'Pulse construction must use new Pulse({ auto: true }) or one direct default import of the configured .pulse/config module.',
|
|
382
|
+
{
|
|
383
|
+
argumentCount: args.length,
|
|
384
|
+
argumentKind: explicitIdentifier ? 'identifier' : (args[0] && ts.SyntaxKind[args[0].kind]),
|
|
385
|
+
configuredFile: options.configFile || null
|
|
386
|
+
}
|
|
387
|
+
));
|
|
388
|
+
if (!options.applicationProjectMetadata) diagnostics.push(diagnostic(sourceFile, rootApplication.expression, 'PULSE_CANONICAL_PULSE_PROJECT_CONTEXT_REQUIRED', 'Pulse application roots require a normalized project/profile plan supplied by project tooling. Deployed application code does not discover configuration at runtime.', { constructionMode: applicationConstructionMode }));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (rootApplication && rootApplication.kind === 'pulse') validatePulseProfileTokenUses(sourceFile, rootLocalName, diagnostics);
|
|
392
|
+
|
|
393
|
+
const rawIR = linkedProjectModules
|
|
394
|
+
? linkedProjectModules.ir
|
|
395
|
+
: buildRouterIR(sourceFile, { cwd: options.rootDir || process.cwd() });
|
|
396
|
+
diagnostics.push(...(rawIR.diagnostics || []).map((entry) => extractorDiagnostic(sourceFile, entry)));
|
|
397
|
+
const normalized = normalizePathIR(rawIR, { cwd: options.rootDir || process.cwd() });
|
|
398
|
+
diagnostics.push(...(normalized.diagnostics || []).map((entry) => extractorDiagnostic(sourceFile, entry)));
|
|
399
|
+
|
|
400
|
+
const eventIR = [];
|
|
401
|
+
const httpStatementOrders = new Map();
|
|
402
|
+
let httpOperationOrder = 0;
|
|
403
|
+
let httpStatementOrder = 0;
|
|
404
|
+
const httpRouterIR = (normalized.ir.routerIR || []).map((router) => {
|
|
405
|
+
const ops = [];
|
|
406
|
+
for (const op of router.ops || []) {
|
|
407
|
+
if (op.kind === 'event') {
|
|
408
|
+
const rootOwned = Boolean(rootApplication && rootApplication.kind === 'pulse' && router.name === rootName);
|
|
409
|
+
if (!rootOwned) {
|
|
410
|
+
diagnostics.push(extractorDiagnostic(sourceFile, {
|
|
411
|
+
code: 'PULSEWASM_EVENTS_ROOT_ONLY',
|
|
412
|
+
message: 'Pulse.on event registrations are allowed only on the resolved Pulse application root; Router and mounted registrations are unsupported.',
|
|
413
|
+
loc: op.loc,
|
|
414
|
+
phase: 'event-topology'
|
|
415
|
+
}));
|
|
416
|
+
} else if (!op.unsupported) {
|
|
417
|
+
eventIR.push(Object.freeze({ ...op, router: router.name }));
|
|
418
|
+
}
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (DISALLOWED_OPERATION_KINDS.has(op.kind)) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_OPERATION_RETIRED', `Router ${DISALLOWED_OPERATION_KINDS.get(op.kind)} are not part of canonical Router v2.`, { router: router.name, operation: op.kind }));
|
|
422
|
+
if (op.route && !SUPPORTED_ROUTE_METHODS.has(String(op.method))) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_METHOD_UNSUPPORTED', `Router method ${op.method} is outside canonical Router v2.`, { method: op.method }));
|
|
423
|
+
const statementKey = `${router.name}\u0000${op.statementOrder}`;
|
|
424
|
+
if (!httpStatementOrders.has(statementKey)) httpStatementOrders.set(statementKey, httpStatementOrder++);
|
|
425
|
+
ops.push(Object.freeze({ ...op, order: httpOperationOrder++, statementOrder: httpStatementOrders.get(statementKey) }));
|
|
426
|
+
}
|
|
427
|
+
return Object.freeze({ ...router, ops: Object.freeze(ops) });
|
|
428
|
+
});
|
|
429
|
+
eventIR.sort((left, right) => left.order - right.order);
|
|
430
|
+
const eventTypes = new Map();
|
|
431
|
+
const declaredSchemaIds = new Set((options.eventSchemaIds || []).map(String));
|
|
432
|
+
for (const event of eventIR) {
|
|
433
|
+
if (eventTypes.has(event.type)) diagnostics.push(extractorDiagnostic(sourceFile, {
|
|
434
|
+
code: 'PULSEWASM_EVENTS_TYPE_DUPLICATE',
|
|
435
|
+
message: `Pulse event type ${JSON.stringify(event.type)} is registered more than once.`,
|
|
436
|
+
loc: event.loc,
|
|
437
|
+
phase: 'event-topology'
|
|
438
|
+
}));
|
|
439
|
+
else eventTypes.set(event.type, event.order);
|
|
440
|
+
const schemaId = event.declaration && event.declaration.schemaId;
|
|
441
|
+
if (schemaId !== null && !declaredSchemaIds.has(schemaId)) diagnostics.push(extractorDiagnostic(sourceFile, {
|
|
442
|
+
code: 'PULSEWASM_EVENTS_SCHEMA_UNRESOLVED',
|
|
443
|
+
message: `Pulse event schema ${JSON.stringify(schemaId)} is not present in the compiled project schema registry.`,
|
|
444
|
+
loc: event.loc,
|
|
445
|
+
phase: 'event-topology'
|
|
446
|
+
}));
|
|
447
|
+
}
|
|
448
|
+
const normalizedIr = Object.freeze({
|
|
449
|
+
...normalized.ir,
|
|
450
|
+
routerIR: Object.freeze(httpRouterIR),
|
|
451
|
+
...(eventIR.length > 0 ? { eventIR: Object.freeze(eventIR) } : {})
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const handlerResult = buildHandlerTable(normalizedIr, {
|
|
455
|
+
cwd: options.rootDir || process.cwd(),
|
|
456
|
+
expectedSignatureForRole(role) {
|
|
457
|
+
if (role === 'event') return { params: [1], display: '(ctx) => Promise<void>' };
|
|
458
|
+
const expected = expectedSignature(role);
|
|
459
|
+
return expected ? { params: expected.counts, display: expected.display } : undefined;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
diagnostics.push(...(handlerResult.diagnostics || []).map((entry) => extractorDiagnostic(sourceFile, entry)));
|
|
463
|
+
const functionNodeForHandler = (handler) => linkedProjectModules
|
|
464
|
+
? linkedProjectModules.functionNodeForHandler(handler)
|
|
465
|
+
: handlerNode(sourceFile, handler);
|
|
466
|
+
const sourceFileForHandler = (handler) => linkedProjectModules
|
|
467
|
+
? linkedProjectModules.sourceFileForPath(handler && handler.file) || sourceFile
|
|
468
|
+
: sourceFile;
|
|
469
|
+
const eventEmitAnalysis = analyzeEventEmitHandlers(handlerResult.handlerTable, {
|
|
470
|
+
cwd: options.rootDir || process.cwd(),
|
|
471
|
+
schemaIds: options.eventSchemaIds || [],
|
|
472
|
+
functionNodeForHandler,
|
|
473
|
+
sourceFileForHandler
|
|
474
|
+
});
|
|
475
|
+
diagnostics.push(...eventEmitAnalysis.diagnostics);
|
|
476
|
+
|
|
477
|
+
let routerTree;
|
|
478
|
+
let routePlan;
|
|
479
|
+
let eventTopology;
|
|
480
|
+
if (rootName && handlerResult.resolvedIR.routerIR.some((router) => router.name === rootName)) {
|
|
481
|
+
try {
|
|
482
|
+
routerTree = resolveRouterTree(handlerResult.resolvedIR, rootName, { cwd: options.rootDir || process.cwd() });
|
|
483
|
+
const routeResult = createRoutePlanResult(handlerResult.resolvedIR, routerTree, rootName, sourceFile, { cwd: options.rootDir || process.cwd() });
|
|
484
|
+
routePlan = routeResult.routePlan;
|
|
485
|
+
diagnostics.push(...(routeResult.diagnostics || []).map((entry) => extractorDiagnostic(sourceFile, entry)));
|
|
486
|
+
} catch (error) {
|
|
487
|
+
diagnostics.push(diagnostic(sourceFile, sourceFile, error.code || 'PULSE_CANONICAL_ROUTER_TREE_FAILED', error.message, { rootName }));
|
|
488
|
+
}
|
|
489
|
+
} else if (rootName) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_ROOT_UNKNOWN', `Default export ${rootName} is not a directly declared Router.`));
|
|
490
|
+
|
|
491
|
+
if (routePlan) {
|
|
492
|
+
const registrations = new Map();
|
|
493
|
+
for (const route of routePlan.routes || []) {
|
|
494
|
+
const key = `${route.method}\u0000${route.path}\u0000${route.handler}`;
|
|
495
|
+
if (registrations.has(key)) {
|
|
496
|
+
diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_DUPLICATE_ROUTE_HANDLER', `Canonical Router v2 does not allow the same handler to be registered more than once for ${route.method} ${route.path}. Distinct handlers may share a method/path when an earlier handler can return next().`, {
|
|
497
|
+
method: route.method,
|
|
498
|
+
path: route.path,
|
|
499
|
+
handler: route.handler,
|
|
500
|
+
firstOrder: registrations.get(key),
|
|
501
|
+
duplicateOrder: route.order
|
|
502
|
+
}));
|
|
503
|
+
} else registrations.set(key, route.order);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if ((eventIR.length > 0 || eventEmitAnalysis.callsites.length > 0) && !diagnostics.some((entry) => entry.severity === 'error')) {
|
|
508
|
+
try {
|
|
509
|
+
eventTopology = buildEventTopology(handlerResult.resolvedIR.eventIR, handlerResult.handlerTable, {
|
|
510
|
+
cwd: options.rootDir || process.cwd(),
|
|
511
|
+
emitAnalysis: eventEmitAnalysis
|
|
512
|
+
});
|
|
513
|
+
} catch (error) {
|
|
514
|
+
diagnostics.push(diagnostic(sourceFile, sourceFile, error.code || 'PULSEWASM_EVENTS_CATALOG_INVALID', error.message, { automaticFallback: false }));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let executionPlan;
|
|
519
|
+
if (routePlan && routerTree && !diagnostics.some((entry) => entry.severity === 'error')) {
|
|
520
|
+
const pathTable = buildPathTable(routePlan, { cwd: options.rootDir || process.cwd() });
|
|
521
|
+
const dispatchHandlers = httpHandlerTable(handlerResult.handlerTable);
|
|
522
|
+
const dispatchResult = buildDispatchTable(routePlan, dispatchHandlers, fakeHandlerEval(dispatchHandlers), pathTable, { cwd: options.rootDir || process.cwd() });
|
|
523
|
+
diagnostics.push(...(dispatchResult.diagnostics || []).map((entry) => extractorDiagnostic(sourceFile, entry)));
|
|
524
|
+
const executionResult = buildExecutionPlan(routerTree, dispatchResult.dispatchTable, { cwd: options.rootDir || process.cwd() });
|
|
525
|
+
diagnostics.push(...(executionResult.diagnostics || []).map((entry) => extractorDiagnostic(sourceFile, entry)));
|
|
526
|
+
executionPlan = executionResult.executionPlan;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (!routePlan || (routePlan.routes.length === 0 && !(rootApplication && rootApplication.kind === 'pulse' && eventIR.length > 0))) diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_EMPTY', 'Canonical Router authoring requires at least one supported HTTP route or one statically declared Pulse event.'));
|
|
530
|
+
if (diagnostics.some((entry) => entry.severity === 'error')) throw new CanonicalRouterCompileError(`Canonical Router lowering failed for ${fileName}.`, diagnostics);
|
|
531
|
+
|
|
532
|
+
const handlers = new Map(handlerResult.handlerTable.handlers.map((handler) => [handler.id, handler]));
|
|
533
|
+
const routesByStableId = new Map((routePlan.routes || []).map((route) => [route.stableId, route]));
|
|
534
|
+
const httpEntries = (executionPlan.entries || []).map((rawEntry) => {
|
|
535
|
+
const entry = cleanExecutionEntry(rawEntry);
|
|
536
|
+
const route = entry.kind === 'route' ? routesByStableId.get(entry.routeStableId) : undefined;
|
|
537
|
+
const handler = ['use', 'route', 'error'].includes(entry.kind) ? handlers.get(entry.handlerId) : undefined;
|
|
538
|
+
const handlerSourceFile = handler && linkedProjectModules
|
|
539
|
+
? linkedProjectModules.sourceFileForPath(handler.file)
|
|
540
|
+
: sourceFile;
|
|
541
|
+
const functionNode = handler
|
|
542
|
+
? (linkedProjectModules ? linkedProjectModules.functionNodeForHandler(handler) : handlerNode(sourceFile, handler))
|
|
543
|
+
: undefined;
|
|
544
|
+
const role = entry.kind === 'use' ? 'middleware' : entry.kind;
|
|
545
|
+
return Object.freeze({ rawEntry, entry, route, handler, functionNode, sourceFile: handlerSourceFile || sourceFile, fileName: handler && handler.file || fileName, role });
|
|
546
|
+
});
|
|
547
|
+
const eventEntries = eventTopology ? eventTopology.catalog.events.map((event) => {
|
|
548
|
+
const registration = eventIR[event.runtimeId];
|
|
549
|
+
const handler = handlers.get(event.handlerStableId);
|
|
550
|
+
const handlerSourceFile = handler && linkedProjectModules
|
|
551
|
+
? linkedProjectModules.sourceFileForPath(handler.file)
|
|
552
|
+
: sourceFile;
|
|
553
|
+
const functionNode = handler
|
|
554
|
+
? (linkedProjectModules ? linkedProjectModules.functionNodeForHandler(handler) : handlerNode(sourceFile, handler))
|
|
555
|
+
: undefined;
|
|
556
|
+
const entry = Object.freeze({
|
|
557
|
+
stableId: event.stableId,
|
|
558
|
+
index: httpEntries.length + event.runtimeId,
|
|
559
|
+
kind: 'event',
|
|
560
|
+
order: registration && registration.order,
|
|
561
|
+
router: rootName,
|
|
562
|
+
routerPath: Object.freeze([rootName]),
|
|
563
|
+
handlerId: event.handlerStableId,
|
|
564
|
+
eventStableId: event.stableId,
|
|
565
|
+
eventRuntimeId: event.runtimeId,
|
|
566
|
+
eventType: event.type,
|
|
567
|
+
eventSchemaId: event.schemaId
|
|
568
|
+
});
|
|
569
|
+
return Object.freeze({
|
|
570
|
+
rawEntry: registration,
|
|
571
|
+
entry,
|
|
572
|
+
event,
|
|
573
|
+
handler,
|
|
574
|
+
functionNode,
|
|
575
|
+
sourceFile: handlerSourceFile || sourceFile,
|
|
576
|
+
fileName: handler && handler.file || fileName,
|
|
577
|
+
role: 'event'
|
|
578
|
+
});
|
|
579
|
+
}) : [];
|
|
580
|
+
const entries = Object.freeze([...httpEntries, ...eventEntries]);
|
|
581
|
+
|
|
582
|
+
return Object.freeze({
|
|
583
|
+
version: ROUTER_TOPOLOGY_FRONTEND_VERSION,
|
|
584
|
+
frontend: 'canonical-router',
|
|
585
|
+
sourceText: String(sourceText),
|
|
586
|
+
fileName,
|
|
587
|
+
sourceFile,
|
|
588
|
+
options: Object.freeze({ ...options }),
|
|
589
|
+
rootName,
|
|
590
|
+
applicationKind: rootApplication ? rootApplication.kind : 'router',
|
|
591
|
+
applicationConstructionMode,
|
|
592
|
+
routePlan,
|
|
593
|
+
handlerTable: handlerResult.handlerTable,
|
|
594
|
+
...(eventTopology ? {
|
|
595
|
+
eventTopology,
|
|
596
|
+
eventCatalog: eventTopology.catalog,
|
|
597
|
+
eventOutboundRequirements: eventTopology.outboundRequirements
|
|
598
|
+
} : {}),
|
|
599
|
+
routerTree,
|
|
600
|
+
executionPlan,
|
|
601
|
+
entries,
|
|
602
|
+
retainedDeclarations: linkedProjectModules
|
|
603
|
+
? linkedProjectModules.retainedDeclarations
|
|
604
|
+
: retainedDeclarations(sourceFile, { configImportStatement: applicationConfigImport && applicationConfigImport.statement }),
|
|
605
|
+
applicationConfigImport: applicationConfigImport ? Object.freeze({ moduleName: applicationConfigImport.moduleName, localName: applicationConfigImport.localName }) : undefined,
|
|
606
|
+
compilerPrelude: routerPreludeSource(Boolean(eventTopology)),
|
|
607
|
+
compilerOwnedCalls: Object.freeze([
|
|
608
|
+
'__pulse_router_match',
|
|
609
|
+
'__pulse_router_param',
|
|
610
|
+
...(eventTopology ? ['__pulse_event_runtime_id'] : [])
|
|
611
|
+
]),
|
|
612
|
+
...(linkedProjectModules ? { reachableGraph: linkedProjectModules.graph, projectModuleLink: Object.freeze({
|
|
613
|
+
version: linkedProjectModules.version,
|
|
614
|
+
projectFiles: linkedProjectModules.projectFiles,
|
|
615
|
+
runtimeProjectFiles: linkedProjectModules.runtimeProjectFiles,
|
|
616
|
+
summary: linkedProjectModules.summary
|
|
617
|
+
}) } : {})
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
module.exports = Object.freeze({
|
|
622
|
+
ROUTER_TOPOLOGY_FRONTEND_VERSION,
|
|
623
|
+
CANONICAL_ROUTER_COMPILER_VERSION,
|
|
624
|
+
CANONICAL_ROUTER_AUTHORING_VERSION,
|
|
625
|
+
CANONICAL_ROUTER_EXECUTION_VERSION,
|
|
626
|
+
ROUTER_IMPORT,
|
|
627
|
+
ROUTER_CLASS,
|
|
628
|
+
PULSE_IMPORT,
|
|
629
|
+
PULSE_CLASS,
|
|
630
|
+
CanonicalRouterCompileError,
|
|
631
|
+
parseRouterSource,
|
|
632
|
+
detectCanonicalRouterSource,
|
|
633
|
+
expectedSignature,
|
|
634
|
+
diagnostic,
|
|
635
|
+
cleanExecutionEntry,
|
|
636
|
+
routerPreludeSource,
|
|
637
|
+
prepareCanonicalRouterTopology
|
|
638
|
+
});
|
package/src/stable-id.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function loadContractsStableId() {
|
|
4
|
+
try {
|
|
5
|
+
return require('@pulse-compute/wasm-contracts/stable-id');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
8
|
+
return require('../../contracts/src/stable-id.js');
|
|
9
|
+
}
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = loadContractsStableId();
|