@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.
Files changed (104) hide show
  1. package/README.md +53 -1
  2. package/bin/provider-proof-composition.js +34 -0
  3. package/bin/pulsewasm-extract.js +20 -0
  4. package/package.json +60 -5
  5. package/src/artifacts-dir.js +13 -0
  6. package/src/ast-json.js +52 -0
  7. package/src/build-manifest.js +354 -0
  8. package/src/canonical-api-compiler.js +269 -0
  9. package/src/canonical-native-compiler.js +411 -0
  10. package/src/canonical-native-plan.js +1478 -0
  11. package/src/canonical-project-compiler.js +1224 -0
  12. package/src/canonical-router-compiler.js +25 -0
  13. package/src/cli-intents.js +1235 -0
  14. package/src/cli.js +1927 -0
  15. package/src/codegen/assemblyscript-compile.js +3 -0
  16. package/src/codegen/assemblyscript-core.js +3 -0
  17. package/src/codegen/assemblyscript-shape.js +3 -0
  18. package/src/codegen/assemblyscript-wasm-smoke.js +3 -0
  19. package/src/codegen/backend-capabilities.js +3 -0
  20. package/src/codegen/channel-broadcaster.js +3 -0
  21. package/src/codegen/compiled-handlers.js +3 -0
  22. package/src/codegen/compiled-wasm-runtime.js +3 -0
  23. package/src/codegen/config-references.js +248 -0
  24. package/src/codegen/dispatch-ts.js +145 -0
  25. package/src/codegen/effect-composition.js +331 -0
  26. package/src/codegen/effect-runtime.js +418 -0
  27. package/src/codegen/execution-harness-ts.js +467 -0
  28. package/src/codegen/handler-bindings-ts.js +298 -0
  29. package/src/codegen/handler-library-contracts.js +3 -0
  30. package/src/codegen/host-capabilities.js +3 -0
  31. package/src/codegen/host-runtime-kernel.js +3 -0
  32. package/src/codegen/integrated-compiled-app.js +3 -0
  33. package/src/codegen/json-body.js +3 -0
  34. package/src/codegen/library-sidecars.js +3 -0
  35. package/src/codegen/local-harness-ts.js +453 -0
  36. package/src/codegen/pulse-wrapper.js +217 -0
  37. package/src/codegen/request-result-headers.js +3 -0
  38. package/src/codegen/schema-json-compile.js +3 -0
  39. package/src/codegen/schema-json-sidecar-v2.js +3 -0
  40. package/src/codegen/schema-json-sidecar.js +3 -0
  41. package/src/codegen/streaming-passthrough.js +3 -0
  42. package/src/codegen/wasm-host-abi.js +3 -0
  43. package/src/codegen/wasm-host-bridge.js +3 -0
  44. package/src/compiled-wasm-host-runtime-kv.js +3 -0
  45. package/src/config-resolver.js +813 -0
  46. package/src/crypto-requirement-planner.js +89 -0
  47. package/src/definitions/config-schema.js +14 -0
  48. package/src/definitions/handler-roles.js +14 -0
  49. package/src/definitions/path-grammar.js +14 -0
  50. package/src/definitions/router-api.js +14 -0
  51. package/src/diagnostics/codes.js +14 -0
  52. package/src/diagnostics/reporter.js +14 -0
  53. package/src/diagnostics.js +14 -0
  54. package/src/dispatch-table.js +400 -0
  55. package/src/events/event-emit.js +265 -0
  56. package/src/events/event-topology.js +127 -0
  57. package/src/execution-plan.js +463 -0
  58. package/src/extractor.js +2816 -0
  59. package/src/handler-eval.js +1154 -0
  60. package/src/handler-table.js +326 -0
  61. package/src/index.js +19 -0
  62. package/src/javascript-application-plan.js +181 -0
  63. package/src/kv-provider.js +3 -0
  64. package/src/path-table.js +60 -0
  65. package/src/path.js +14 -0
  66. package/src/patterns/config-define.js +30 -0
  67. package/src/patterns/dependency-call.js +18 -0
  68. package/src/patterns/env-lookup.js +13 -0
  69. package/src/patterns/handler-reference.js +29 -0
  70. package/src/patterns/path-literal.js +35 -0
  71. package/src/patterns/result.js +15 -0
  72. package/src/patterns/router-chain-call.js +50 -0
  73. package/src/patterns/router-construction.js +19 -0
  74. package/src/project/package-reachability.js +782 -0
  75. package/src/project/reachable-graph-builder.js +992 -0
  76. package/src/project/reachable-graph-contract.js +54 -0
  77. package/src/project/reachable-graph-implementation.js +36 -0
  78. package/src/project/router-module-linker.js +710 -0
  79. package/src/project-config-compiler.js +222 -0
  80. package/src/project-target-support.js +500 -0
  81. package/src/provider-toolchain.js +299 -0
  82. package/src/spine/async-surface-normalizer.js +328 -0
  83. package/src/spine/canonical-handler-ir.js +336 -0
  84. package/src/spine/canonical-native-module.js +87 -0
  85. package/src/spine/canonical-native-plan.js +89 -0
  86. package/src/spine/canonical-project.js +76 -0
  87. package/src/spine/canonical-router.js +155 -0
  88. package/src/spine/canonical-source.js +165 -0
  89. package/src/spine/diagnostic-authority.js +290 -0
  90. package/src/spine/equivalence.js +262 -0
  91. package/src/spine/guest-unit-stage.js +87 -0
  92. package/src/spine/handler-ir-emitter.js +455 -0
  93. package/src/spine/handler-ir-managed.js +1598 -0
  94. package/src/spine/handler-ir.js +797 -0
  95. package/src/spine/handler-surface-authority.js +588 -0
  96. package/src/spine/package-operation-seam.js +1015 -0
  97. package/src/spine/pipeline.js +202 -0
  98. package/src/spine/plain-handler-frontend.js +545 -0
  99. package/src/spine/provider-requirement-authority.js +208 -0
  100. package/src/spine/router-control-contract.js +17 -0
  101. package/src/spine/router-handler-frontend.js +514 -0
  102. package/src/spine/router-handler-ir.js +372 -0
  103. package/src/spine/router-topology-frontend.js +638 -0
  104. package/src/stable-id.js +14 -0
@@ -0,0 +1,514 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const ts = require('typescript');
5
+ const {
6
+ unwrapExpression,
7
+ staticString,
8
+ recognizeManagedHandlerWrapper,
9
+ recognizeHandlerSurface,
10
+ sourceModelForRouterRecognition
11
+ } = require('./handler-surface-authority.js');
12
+ const { normalizeManagedHandler } = require('./async-surface-normalizer.js');
13
+ const {
14
+ createHandlerDiagnostic
15
+ } = require('./diagnostic-authority.js');
16
+ const {
17
+ HANDLER_IR_VERSION,
18
+ ROUTER_HANDLER_IR_KIND,
19
+ createHandlerOperation
20
+ } = require('./handler-ir.js');
21
+ const {
22
+ diagnostic,
23
+ expectedSignature,
24
+ CanonicalRouterCompileError
25
+ } = require('./router-topology-frontend.js');
26
+
27
+ const {
28
+ ROUTER_CURSOR_IDENTIFIER: CURSOR,
29
+ ROUTER_MODE_IDENTIFIER: MODE,
30
+ ROUTER_ERROR_IDENTIFIER: ERROR
31
+ } = require('./router-control-contract.js');
32
+
33
+ const ROUTER_HANDLER_FRONTEND_VERSION = 'pulse.router-handler-frontend.v1';
34
+
35
+ function directNextCall(statement, nextName) {
36
+ if (!ts.isReturnStatement(statement) || !statement.expression) return undefined;
37
+ const expression = unwrapExpression(statement.expression);
38
+ const surface = recognizeHandlerSurface(expression, { nextName, position: 'return', unwrap: true });
39
+ return surface && surface.surfaceId === 'router.next' ? expression : undefined;
40
+ }
41
+
42
+ function isReferenceIdentifier(node) {
43
+ const parent = node.parent;
44
+ if (!parent) return true;
45
+ if (ts.isCallExpression(parent) && unwrapExpression(parent.expression) === node) return false;
46
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) return false;
47
+ if (ts.isPropertyAssignment(parent) && parent.name === node && !ts.isComputedPropertyName(parent.name)) return false;
48
+ if (ts.isMethodDeclaration(parent) && parent.name === node) return false;
49
+ if (ts.isVariableDeclaration(parent) && parent.name === node) return false;
50
+ if (ts.isParameter(parent) && parent.name === node) return false;
51
+ if (ts.isFunctionDeclaration(parent) && parent.name === node) return false;
52
+ if (ts.isPropertySignature(parent) && parent.name === node) return false;
53
+ if (ts.isTypeReferenceNode(parent) && parent.typeName === node) return false;
54
+ if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent)) return false;
55
+ if (ts.isLabeledStatement(parent) && parent.label === node) return false;
56
+ if ((ts.isBreakStatement(parent) || ts.isContinueStatement(parent)) && parent.label === node) return false;
57
+ return true;
58
+ }
59
+
60
+ function validateFunctionSignature(sourceFile, functionNode, role, diagnostics) {
61
+ const expected = expectedSignature(role);
62
+ if (!functionNode) return undefined;
63
+ if (functionNode.asteriskToken) diagnostics.push(createHandlerDiagnostic({
64
+ frontend: 'canonical-router',
65
+ issue: 'handler.generator.unsupported',
66
+ sourceFile,
67
+ node: functionNode
68
+ }));
69
+ const names = functionNode.parameters.map((parameter) => ts.isIdentifier(parameter.name) ? parameter.name.text : undefined);
70
+ const matches = expected && expected.names.some((candidate) => candidate.length === names.length && candidate.every((name, index) => names[index] === name));
71
+ if (!matches) diagnostics.push(createHandlerDiagnostic({
72
+ frontend: 'canonical-router',
73
+ issue: 'handler.signature.invalid',
74
+ sourceFile,
75
+ node: functionNode,
76
+ values: { role, expected: expected.display },
77
+ detail: { role, expected: expected.display, actual: names }
78
+ }));
79
+ return Object.freeze({
80
+ ctxName: role === 'error' ? names[1] : names[0],
81
+ nextName: role === 'route' && names.length === 1 ? undefined : names[role === 'error' ? 2 : 1],
82
+ errorName: role === 'error' ? names[0] : undefined
83
+ });
84
+ }
85
+
86
+ function emptySchemaBundle(fileName) {
87
+ const sourceHash = crypto.createHash('sha256').update(`router-handler:${fileName}`).digest('hex');
88
+ return Object.freeze({
89
+ active: false,
90
+ registry: Object.freeze([]),
91
+ schemaIds: Object.freeze([]),
92
+ sourceHash,
93
+ declarationSource: '',
94
+ moduleSource: ''
95
+ });
96
+ }
97
+
98
+ function entrySurfaceFacts(recognition, classification, entryStableId) {
99
+ const recognizedFacts = recognition.facts.filter((fact) => fact.routerEntryStableId === entryStableId);
100
+ const classifiedFacts = classification.facts.filter((fact) => fact.routerEntryStableId === entryStableId);
101
+ function summary(facts) {
102
+ return Object.freeze({
103
+ total: facts.length,
104
+ byClass: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.class))].sort().map((className) => [className, facts.filter((fact) => fact.class === className).length]))),
105
+ bySurface: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.surfaceId))].sort().map((surfaceId) => [surfaceId, facts.filter((fact) => fact.surfaceId === surfaceId).length])))
106
+ });
107
+ }
108
+ return Object.freeze({
109
+ recognition: Object.freeze({
110
+ version: recognition.version,
111
+ contractVersion: recognition.contractVersion,
112
+ file: recognition.file,
113
+ handlerCount: 1,
114
+ facts: Object.freeze(recognizedFacts),
115
+ summary: summary(recognizedFacts)
116
+ }),
117
+ classification: Object.freeze({
118
+ version: classification.version,
119
+ contractVersion: classification.contractVersion,
120
+ file: classification.file,
121
+ handlerCount: 1,
122
+ facts: Object.freeze(classifiedFacts),
123
+ summary: summary(classifiedFacts)
124
+ })
125
+ });
126
+ }
127
+
128
+ function analysisForSurfaceFacts(surfaceFacts) {
129
+ const facts = surfaceFacts.classification.facts;
130
+ const capabilities = new Set();
131
+ let fetchCount = 0;
132
+ for (const fact of facts) {
133
+ if (fact.surfaceId.startsWith('ctx.fetch.')) { capabilities.add('fetch'); fetchCount += 1; continue; }
134
+ if (fact.surfaceId.startsWith('ctx.req.')) capabilities.add(fact.surfaceId.slice('ctx.'.length));
135
+ else if (fact.surfaceId.startsWith('ctx.config.')) capabilities.add('config.get');
136
+ else if (fact.surfaceId.startsWith('ctx.secret.')) capabilities.add('secret.get');
137
+ else if (fact.surfaceId.startsWith('ctx.kv.')) capabilities.add(fact.surfaceId.slice('ctx.'.length));
138
+ else if (fact.surfaceId.startsWith('ctx.state.')) capabilities.add(fact.surfaceId.slice('ctx.'.length));
139
+ else if (fact.surfaceId === 'ctx.emit') capabilities.add('event.emit');
140
+ else if (fact.surfaceId.startsWith('ctx.log.')) capabilities.add('logging');
141
+ else if (['ctx.json', 'ctx.text', 'ctx.response'].includes(fact.surfaceId)) capabilities.add(`response.${fact.surfaceId.slice('ctx.'.length)}`);
142
+ else if (fact.surfaceId === 'ctx.param') capabilities.add('route.param');
143
+ }
144
+ return Object.freeze({
145
+ capabilities: Object.freeze([...capabilities].sort()),
146
+ providerOperations: Object.freeze([]),
147
+ fetchCount,
148
+ schemaReferences: Object.freeze([])
149
+ });
150
+ }
151
+
152
+ function normalizeRouterHandler(topology, descriptor, recognition, classification, diagnostics) {
153
+ const sourceFile = descriptor.sourceFile || topology.sourceFile;
154
+ const fileName = descriptor.fileName || sourceFile.fileName || topology.fileName;
155
+ const { functionNode: authoredFunctionNode, role, entry, route } = descriptor;
156
+ let functionNode = authoredFunctionNode;
157
+ if (!functionNode) {
158
+ diagnostics.push(diagnostic(sourceFile, sourceFile, 'PULSE_CANONICAL_ROUTER_HANDLER_MISSING', `Unable to recover ${role} handler ${descriptor.handler && descriptor.handler.name || '<unknown>'} from the retained handler table.`, { entry: entry.index }));
159
+ return Object.freeze({
160
+ version: ROUTER_HANDLER_FRONTEND_VERSION,
161
+ frontend: 'canonical-router-handler',
162
+ fileName,
163
+ sourceFile,
164
+ sourceText: '',
165
+ functionNode,
166
+ descriptor,
167
+ role,
168
+ entry,
169
+ route,
170
+ signature: Object.freeze({ ctxName: 'ctx', nextName: undefined, errorName: undefined }),
171
+ transferFlag: `__pulse_router_transferred_${entry.index}`,
172
+ body: createHandlerOperation('block', { statement: ts.factory.createBlock([], true), statements: Object.freeze([]) }),
173
+ analysis: Object.freeze({ capabilities: Object.freeze([]), providerOperations: Object.freeze([]), fetchCount: 0, schemaReferences: Object.freeze([]) }),
174
+ schemaBundle: emptySchemaBundle(fileName),
175
+ flow: Object.freeze({ mayTransfer: false, mustTerminate: false }),
176
+ surfaceFacts: entrySurfaceFacts(recognition, classification, entry.stableId)
177
+ });
178
+ }
179
+
180
+ const signature = validateFunctionSignature(sourceFile, functionNode, role, diagnostics) || {};
181
+ const ctxName = signature.ctxName || 'ctx';
182
+ const nextName = signature.nextName;
183
+ const errorName = signature.errorName;
184
+ const packageEffectsByStart = new Map();
185
+ for (const effect of topology.options && topology.options.packageEffects || []) {
186
+ const start = effect && effect.range && Number(effect.range.start);
187
+ if (Number.isSafeInteger(start) && !packageEffectsByStart.has(start)) packageEffectsByStart.set(start, effect);
188
+ }
189
+ const normalized = normalizeManagedHandler(authoredFunctionNode, {
190
+ sourceFile,
191
+ ctxName,
192
+ nextName,
193
+ role,
194
+ strict: Boolean(topology.options && topology.options.strict === true),
195
+ frontend: 'canonical-router',
196
+ target: topology.options && topology.options.target,
197
+ handlerAuthoring: topology.options && topology.options.handlerAuthoring,
198
+ requireAsync: topology.options && topology.options.requireAsync === true,
199
+ requireEffectAwait: topology.options && topology.options.requireEffectAwait === true,
200
+ packageEffectForCall: topology.options && topology.options.packageEffectForCall
201
+ || ((call) => packageEffectsByStart.get(call.getStart(sourceFile)))
202
+ });
203
+ diagnostics.push(...normalized.diagnostics);
204
+ functionNode = normalized.functionNode;
205
+ const params = new Set(route && route.params || []);
206
+ const transferFlag = `__pulse_router_transferred_${entry.index}`;
207
+
208
+ function scan(node, root = false, parentNode) {
209
+ if (!root && ts.isFunctionLike(node)) {
210
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.nested-function.unsupported', sourceFile, node }));
211
+ return;
212
+ }
213
+ if (ts.isThrowStatement(node)) diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.throw.unsupported', sourceFile, node }));
214
+ const nextSurface = recognizeHandlerSurface(node, { nextName, unwrap: true });
215
+ if (nextSurface && nextSurface.surfaceId === 'router.next') {
216
+ const parent = node.parent || parentNode;
217
+ const expressionBodyReturn = ts.isArrowFunction(functionNode) && unwrapExpression(functionNode.body) === node;
218
+ if ((!ts.isReturnStatement(parent) || unwrapExpression(parent.expression) !== node) && !expressionBodyReturn) {
219
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'router.next.not-terminal', sourceFile, node }));
220
+ }
221
+ if (node.arguments.length > 1) diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'router.next.arity', sourceFile, node }));
222
+ } else if (nextName && ts.isIdentifier(node) && node.text === nextName && isReferenceIdentifier(node)) {
223
+ diagnostics.push(createHandlerDiagnostic({
224
+ frontend: 'canonical-router',
225
+ issue: 'router.next.not-terminal',
226
+ sourceFile,
227
+ node,
228
+ values: { reference: true }
229
+ }));
230
+ }
231
+ const contextSurface = recognizeHandlerSurface(node, { ctxName, unwrap: true });
232
+ if (contextSurface) {
233
+ const httpOnly = contextSurface.surfaceId === 'ctx.param'
234
+ || contextSurface.surfaceId.startsWith('ctx.req.')
235
+ || ['ctx.json', 'ctx.text', 'ctx.response'].includes(contextSurface.surfaceId);
236
+ const eventOnly = contextSurface.surfaceId.startsWith('ctx.event.');
237
+ if (role === 'event' && httpOnly) diagnostics.push(diagnostic(
238
+ sourceFile,
239
+ node,
240
+ 'PULSE_EVENT_CONTEXT_HTTP_SURFACE_UNSUPPORTED',
241
+ `Event handlers cannot use HTTP-only surface ${contextSurface.surfaceId}.`,
242
+ { role, surfaceId: contextSurface.surfaceId }
243
+ ));
244
+ if (role !== 'event' && eventOnly) diagnostics.push(diagnostic(
245
+ sourceFile,
246
+ node,
247
+ 'PULSE_HTTP_CONTEXT_EVENT_SURFACE_UNSUPPORTED',
248
+ `HTTP handlers cannot use event-only surface ${contextSurface.surfaceId}.`,
249
+ { role, surfaceId: contextSurface.surfaceId }
250
+ ));
251
+ }
252
+ if (errorName && ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === errorName) {
253
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'router.error.shadowed', sourceFile, node, values: { errorName } }));
254
+ }
255
+ ts.forEachChild(node, (child) => scan(child, false, node));
256
+ }
257
+ scan(functionNode.body, true, functionNode);
258
+
259
+ const rewriteTransformer = (context) => {
260
+ const visit = (node) => {
261
+ if (errorName && ts.isShorthandPropertyAssignment(node) && node.name.text === errorName) {
262
+ return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(errorName), ts.factory.createIdentifier(ERROR));
263
+ }
264
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
265
+ const target = node.expression;
266
+ const surface = recognizeHandlerSurface(node, { ctxName });
267
+ if (surface && surface.surfaceId === 'ctx.param') {
268
+ if (role !== 'route') {
269
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.param.outside-route', sourceFile, node }));
270
+ return node;
271
+ }
272
+ if (node.arguments.length !== 1) {
273
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.param.arity', sourceFile, node }));
274
+ return node;
275
+ }
276
+ const name = staticString(node.arguments[0]);
277
+ if (name === undefined) {
278
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.param.dynamic', sourceFile, node }));
279
+ return node;
280
+ }
281
+ if (!params.has(name)) diagnostics.push(createHandlerDiagnostic({
282
+ frontend: 'canonical-router',
283
+ issue: 'context.param.unknown',
284
+ sourceFile,
285
+ node,
286
+ values: { route: route.path, name },
287
+ detail: { route: route.path, parameter: name, available: [...params] }
288
+ }));
289
+ return ts.factory.createCallExpression(ts.factory.createIdentifier('__pulse_router_param'), undefined, [
290
+ ts.factory.createPropertyAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('ctx'), 'req'), 'path'),
291
+ ts.factory.createStringLiteral(route.path),
292
+ ts.factory.createStringLiteral(name)
293
+ ]);
294
+ }
295
+ if (ts.isIdentifier(target.expression) && target.expression.text === ctxName && target.name.text === 'resolve') {
296
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.resolve.retired', sourceFile, node }));
297
+ }
298
+ }
299
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === ctxName && node.name.text === 'resolved') {
300
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.resolved.retired', sourceFile, node }));
301
+ }
302
+ if (role === 'event'
303
+ && ts.isPropertyAccessExpression(node)
304
+ && ts.isPropertyAccessExpression(node.expression)
305
+ && ts.isIdentifier(node.expression.expression)
306
+ && node.expression.expression.text === ctxName
307
+ && node.expression.name.text === 'event'
308
+ && node.name.text === 'type') {
309
+ return ts.factory.createStringLiteral(String(descriptor.event && descriptor.event.type || entry.eventType));
310
+ }
311
+ if (errorName && ts.isIdentifier(node) && node.text === errorName && isReferenceIdentifier(node)) return ts.factory.createIdentifier(ERROR);
312
+ return ts.visitEachChild(node, visit, context);
313
+ };
314
+ return (root) => ts.visitNode(root, visit);
315
+ };
316
+
317
+ function rewrite(node) {
318
+ const result = ts.transform(node, [rewriteTransformer]);
319
+ const transformed = result.transformed[0];
320
+ result.dispose();
321
+ return transformed;
322
+ }
323
+
324
+ function transferOperation(call) {
325
+ return createHandlerOperation('router-transfer', {
326
+ call,
327
+ role,
328
+ nextIndex: entry.nextIndex,
329
+ transferFlag,
330
+ clearNormalMode: call.arguments.length === 0 && role !== 'error',
331
+ errorExpression: call.arguments.length > 0 ? rewrite(call.arguments[0]) : undefined
332
+ });
333
+ }
334
+
335
+ function transformList(statements) {
336
+ const operations = [];
337
+ let mayTransfer = false;
338
+ for (let index = 0; index < statements.length; index += 1) {
339
+ const statement = statements[index];
340
+ const result = transformStatement(statement);
341
+ operations.push(...result.operations);
342
+ mayTransfer = mayTransfer || result.mayTransfer;
343
+ if (result.mustTerminate) {
344
+ for (const unreachable of statements.slice(index + 1)) {
345
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.unreachable-after-terminal', sourceFile, node: unreachable }));
346
+ }
347
+ return { operations, mayTransfer, mustTerminate: true };
348
+ }
349
+ if (result.mayTransfer) {
350
+ const remainder = transformList(statements.slice(index + 1));
351
+ if (remainder.operations.length > 0) {
352
+ operations.push(createHandlerOperation('router-guard', {
353
+ transferFlag,
354
+ body: createHandlerOperation('block', {
355
+ statement: ts.factory.createBlock([], true),
356
+ statements: Object.freeze(remainder.operations)
357
+ })
358
+ }));
359
+ }
360
+ return { operations, mayTransfer: true, mustTerminate: remainder.mustTerminate };
361
+ }
362
+ }
363
+ return { operations, mayTransfer, mustTerminate: false };
364
+ }
365
+
366
+ function transformStatement(statement) {
367
+ const call = nextName ? directNextCall(statement, nextName) : undefined;
368
+ if (call) return { operations: [transferOperation(call)], mayTransfer: true, mustTerminate: true };
369
+ if (ts.isReturnStatement(statement)) {
370
+ const returned = statement.expression && unwrapExpression(statement.expression);
371
+ const returnsVoid = !returned
372
+ || (ts.isIdentifier(returned) && returned.text === 'undefined')
373
+ || ts.isVoidExpression(returned);
374
+ if (role === 'event' && !returnsVoid) {
375
+ diagnostics.push(diagnostic(
376
+ sourceFile,
377
+ statement,
378
+ 'PULSE_EVENT_HANDLER_RESULT_UNSUPPORTED',
379
+ 'Event handlers complete with void and cannot return a result value.',
380
+ { role }
381
+ ));
382
+ } else if (role !== 'event' && !statement.expression) {
383
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.return-value.required', sourceFile, node: statement }));
384
+ }
385
+ return { operations: [createHandlerOperation('source-statement', { statement: rewrite(statement), role: 'router-return' })], mayTransfer: false, mustTerminate: true };
386
+ }
387
+ if (ts.isBlock(statement)) {
388
+ const nested = transformList(statement.statements);
389
+ return {
390
+ operations: [createHandlerOperation('block', { statement, statements: Object.freeze(nested.operations) })],
391
+ mayTransfer: nested.mayTransfer,
392
+ mustTerminate: nested.mustTerminate
393
+ };
394
+ }
395
+ if (ts.isIfStatement(statement)) {
396
+ const thenResult = ts.isBlock(statement.thenStatement)
397
+ ? transformList(statement.thenStatement.statements)
398
+ : transformList([statement.thenStatement]);
399
+ const elseResult = statement.elseStatement
400
+ ? (ts.isBlock(statement.elseStatement) ? transformList(statement.elseStatement.statements) : transformList([statement.elseStatement]))
401
+ : { operations: [], mayTransfer: false, mustTerminate: false };
402
+ return {
403
+ operations: [createHandlerOperation('if', {
404
+ statement,
405
+ expression: rewrite(statement.expression),
406
+ thenOperation: createHandlerOperation('block', { statement: ts.factory.createBlock([], true), statements: Object.freeze(thenResult.operations) }),
407
+ elseOperation: statement.elseStatement ? createHandlerOperation('block', { statement: ts.factory.createBlock([], true), statements: Object.freeze(elseResult.operations) }) : undefined
408
+ })],
409
+ mayTransfer: thenResult.mayTransfer || elseResult.mayTransfer,
410
+ mustTerminate: Boolean(statement.elseStatement) && thenResult.mustTerminate && elseResult.mustTerminate
411
+ };
412
+ }
413
+ return { operations: [createHandlerOperation('source-statement', { statement: rewrite(statement), role: 'router-source' })], mayTransfer: false, mustTerminate: false };
414
+ }
415
+
416
+ const originalBody = ts.isBlock(functionNode.body)
417
+ ? functionNode.body
418
+ : ts.factory.createBlock([ts.factory.createReturnStatement(functionNode.body)], true);
419
+ const transformed = transformList(originalBody.statements);
420
+ if (role !== 'event' && !transformed.mustTerminate) diagnostics.push(createHandlerDiagnostic({
421
+ frontend: 'canonical-router',
422
+ issue: 'handler.fallthrough',
423
+ sourceFile,
424
+ node: functionNode,
425
+ values: { role },
426
+ detail: { role }
427
+ }));
428
+
429
+ const surfaceFacts = entrySurfaceFacts(recognition, classification, entry.stableId);
430
+ const wave2Normalization = normalized.summary.changed || normalized.summary.userAuthoredAsync || normalized.summary.awaitCount > 0;
431
+ const normalization = wave2Normalization ? normalized.summary : undefined;
432
+ return Object.freeze({
433
+ version: ROUTER_HANDLER_FRONTEND_VERSION,
434
+ frontend: 'canonical-router-handler',
435
+ handlerIrVersion: HANDLER_IR_VERSION,
436
+ handlerIrKind: ROUTER_HANDLER_IR_KIND,
437
+ fileName,
438
+ sourceFile,
439
+ sourceText: authoredFunctionNode.getText(sourceFile),
440
+ functionNode,
441
+ authoredFunctionNode,
442
+ warnings: normalized.warnings,
443
+ normalization,
444
+ descriptor,
445
+ role,
446
+ entry,
447
+ route,
448
+ signature: Object.freeze({
449
+ ctxName: signature.ctxName || 'ctx',
450
+ nextName,
451
+ errorName
452
+ }),
453
+ transferFlag,
454
+ body: createHandlerOperation('block', {
455
+ statement: originalBody,
456
+ statements: Object.freeze(transformed.operations)
457
+ }),
458
+ analysis: analysisForSurfaceFacts(surfaceFacts),
459
+ schemaBundle: emptySchemaBundle(fileName),
460
+ flow: Object.freeze({ mayTransfer: transformed.mayTransfer, mustTerminate: transformed.mustTerminate }),
461
+ surfaceFacts: Object.freeze({
462
+ ...surfaceFacts,
463
+ normalization: wave2Normalization
464
+ ? Object.freeze({
465
+ authority: 'async-surface-normalizer',
466
+ frontendVersion: ROUTER_HANDLER_FRONTEND_VERSION,
467
+ ...normalized.summary,
468
+ behaviorChangeAllowed: true
469
+ })
470
+ : Object.freeze({
471
+ authority: 'router-handler-frontend',
472
+ frontendVersion: ROUTER_HANDLER_FRONTEND_VERSION,
473
+ changed: true,
474
+ behaviorChangeAllowed: false
475
+ })
476
+ })
477
+ });
478
+ }
479
+
480
+ function prepareCanonicalRouterHandlers(topology, recognition, classification) {
481
+ if (!topology || topology.frontend !== 'canonical-router') throw new TypeError('prepareCanonicalRouterHandlers requires canonical Router topology.');
482
+ const model = sourceModelForRouterRecognition(recognition);
483
+ if (!model || model.topology !== topology) throw new TypeError('Router handler recognition does not belong to the supplied topology.');
484
+ const diagnostics = [];
485
+ const handlers = topology.entries
486
+ .filter((descriptor) => ['use', 'route', 'error', 'event'].includes(descriptor.entry.kind))
487
+ .map((descriptor) => normalizeRouterHandler(topology, descriptor, recognition, classification, diagnostics));
488
+ const validation = Object.freeze({
489
+ authority: 'router-handler-frontend',
490
+ frontendVersion: ROUTER_HANDLER_FRONTEND_VERSION,
491
+ handlerCount: handlers.length,
492
+ behaviorChangeAllowed: false
493
+ });
494
+ if (diagnostics.some((entry) => entry.severity === 'error')) {
495
+ throw new CanonicalRouterCompileError(`Canonical Router lowering failed for ${topology.fileName}.`, diagnostics);
496
+ }
497
+ return Object.freeze({
498
+ version: ROUTER_HANDLER_FRONTEND_VERSION,
499
+ topology,
500
+ recognition,
501
+ classification,
502
+ validation,
503
+ handlers: Object.freeze(handlers.map((handler) => Object.freeze({
504
+ ...handler,
505
+ surfaceFacts: Object.freeze({ ...handler.surfaceFacts, validation })
506
+ })))
507
+ });
508
+ }
509
+
510
+ module.exports = Object.freeze({
511
+ ROUTER_HANDLER_FRONTEND_VERSION,
512
+ validateFunctionSignature,
513
+ prepareCanonicalRouterHandlers
514
+ });