@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.
Files changed (104) hide show
  1. package/README.md +52 -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 +245 -0
  9. package/src/canonical-native-compiler.js +411 -0
  10. package/src/canonical-native-plan.js +1477 -0
  11. package/src/canonical-project-compiler.js +1218 -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 +312 -0
  83. package/src/spine/canonical-handler-ir.js +335 -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 +454 -0
  93. package/src/spine/handler-ir-managed.js +1597 -0
  94. package/src/spine/handler-ir.js +797 -0
  95. package/src/spine/handler-surface-authority.js +585 -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 +537 -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 +513 -0
  102. package/src/spine/router-handler-ir.js +372 -0
  103. package/src/spine/router-topology-frontend.js +635 -0
  104. package/src/stable-id.js +14 -0
@@ -0,0 +1,513 @@
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
+ handlerAuthoring: topology.options && topology.options.handlerAuthoring,
197
+ requireAsync: topology.options && topology.options.requireAsync === true,
198
+ requireEffectAwait: topology.options && topology.options.requireEffectAwait === true,
199
+ packageEffectForCall: topology.options && topology.options.packageEffectForCall
200
+ || ((call) => packageEffectsByStart.get(call.getStart(sourceFile)))
201
+ });
202
+ diagnostics.push(...normalized.diagnostics);
203
+ functionNode = normalized.functionNode;
204
+ const params = new Set(route && route.params || []);
205
+ const transferFlag = `__pulse_router_transferred_${entry.index}`;
206
+
207
+ function scan(node, root = false, parentNode) {
208
+ if (!root && ts.isFunctionLike(node)) {
209
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.nested-function.unsupported', sourceFile, node }));
210
+ return;
211
+ }
212
+ if (ts.isThrowStatement(node)) diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.throw.unsupported', sourceFile, node }));
213
+ const nextSurface = recognizeHandlerSurface(node, { nextName, unwrap: true });
214
+ if (nextSurface && nextSurface.surfaceId === 'router.next') {
215
+ const parent = node.parent || parentNode;
216
+ const expressionBodyReturn = ts.isArrowFunction(functionNode) && unwrapExpression(functionNode.body) === node;
217
+ if ((!ts.isReturnStatement(parent) || unwrapExpression(parent.expression) !== node) && !expressionBodyReturn) {
218
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'router.next.not-terminal', sourceFile, node }));
219
+ }
220
+ if (node.arguments.length > 1) diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'router.next.arity', sourceFile, node }));
221
+ } else if (nextName && ts.isIdentifier(node) && node.text === nextName && isReferenceIdentifier(node)) {
222
+ diagnostics.push(createHandlerDiagnostic({
223
+ frontend: 'canonical-router',
224
+ issue: 'router.next.not-terminal',
225
+ sourceFile,
226
+ node,
227
+ values: { reference: true }
228
+ }));
229
+ }
230
+ const contextSurface = recognizeHandlerSurface(node, { ctxName, unwrap: true });
231
+ if (contextSurface) {
232
+ const httpOnly = contextSurface.surfaceId === 'ctx.param'
233
+ || contextSurface.surfaceId.startsWith('ctx.req.')
234
+ || ['ctx.json', 'ctx.text', 'ctx.response'].includes(contextSurface.surfaceId);
235
+ const eventOnly = contextSurface.surfaceId.startsWith('ctx.event.');
236
+ if (role === 'event' && httpOnly) diagnostics.push(diagnostic(
237
+ sourceFile,
238
+ node,
239
+ 'PULSE_EVENT_CONTEXT_HTTP_SURFACE_UNSUPPORTED',
240
+ `Event handlers cannot use HTTP-only surface ${contextSurface.surfaceId}.`,
241
+ { role, surfaceId: contextSurface.surfaceId }
242
+ ));
243
+ if (role !== 'event' && eventOnly) diagnostics.push(diagnostic(
244
+ sourceFile,
245
+ node,
246
+ 'PULSE_HTTP_CONTEXT_EVENT_SURFACE_UNSUPPORTED',
247
+ `HTTP handlers cannot use event-only surface ${contextSurface.surfaceId}.`,
248
+ { role, surfaceId: contextSurface.surfaceId }
249
+ ));
250
+ }
251
+ if (errorName && ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === errorName) {
252
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'router.error.shadowed', sourceFile, node, values: { errorName } }));
253
+ }
254
+ ts.forEachChild(node, (child) => scan(child, false, node));
255
+ }
256
+ scan(functionNode.body, true, functionNode);
257
+
258
+ const rewriteTransformer = (context) => {
259
+ const visit = (node) => {
260
+ if (errorName && ts.isShorthandPropertyAssignment(node) && node.name.text === errorName) {
261
+ return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(errorName), ts.factory.createIdentifier(ERROR));
262
+ }
263
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
264
+ const target = node.expression;
265
+ const surface = recognizeHandlerSurface(node, { ctxName });
266
+ if (surface && surface.surfaceId === 'ctx.param') {
267
+ if (role !== 'route') {
268
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.param.outside-route', sourceFile, node }));
269
+ return node;
270
+ }
271
+ if (node.arguments.length !== 1) {
272
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.param.arity', sourceFile, node }));
273
+ return node;
274
+ }
275
+ const name = staticString(node.arguments[0]);
276
+ if (name === undefined) {
277
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.param.dynamic', sourceFile, node }));
278
+ return node;
279
+ }
280
+ if (!params.has(name)) diagnostics.push(createHandlerDiagnostic({
281
+ frontend: 'canonical-router',
282
+ issue: 'context.param.unknown',
283
+ sourceFile,
284
+ node,
285
+ values: { route: route.path, name },
286
+ detail: { route: route.path, parameter: name, available: [...params] }
287
+ }));
288
+ return ts.factory.createCallExpression(ts.factory.createIdentifier('__pulse_router_param'), undefined, [
289
+ ts.factory.createPropertyAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('ctx'), 'req'), 'path'),
290
+ ts.factory.createStringLiteral(route.path),
291
+ ts.factory.createStringLiteral(name)
292
+ ]);
293
+ }
294
+ if (ts.isIdentifier(target.expression) && target.expression.text === ctxName && target.name.text === 'resolve') {
295
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.resolve.retired', sourceFile, node }));
296
+ }
297
+ }
298
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === ctxName && node.name.text === 'resolved') {
299
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'context.resolved.retired', sourceFile, node }));
300
+ }
301
+ if (role === 'event'
302
+ && ts.isPropertyAccessExpression(node)
303
+ && ts.isPropertyAccessExpression(node.expression)
304
+ && ts.isIdentifier(node.expression.expression)
305
+ && node.expression.expression.text === ctxName
306
+ && node.expression.name.text === 'event'
307
+ && node.name.text === 'type') {
308
+ return ts.factory.createStringLiteral(String(descriptor.event && descriptor.event.type || entry.eventType));
309
+ }
310
+ if (errorName && ts.isIdentifier(node) && node.text === errorName && isReferenceIdentifier(node)) return ts.factory.createIdentifier(ERROR);
311
+ return ts.visitEachChild(node, visit, context);
312
+ };
313
+ return (root) => ts.visitNode(root, visit);
314
+ };
315
+
316
+ function rewrite(node) {
317
+ const result = ts.transform(node, [rewriteTransformer]);
318
+ const transformed = result.transformed[0];
319
+ result.dispose();
320
+ return transformed;
321
+ }
322
+
323
+ function transferOperation(call) {
324
+ return createHandlerOperation('router-transfer', {
325
+ call,
326
+ role,
327
+ nextIndex: entry.nextIndex,
328
+ transferFlag,
329
+ clearNormalMode: call.arguments.length === 0 && role !== 'error',
330
+ errorExpression: call.arguments.length > 0 ? rewrite(call.arguments[0]) : undefined
331
+ });
332
+ }
333
+
334
+ function transformList(statements) {
335
+ const operations = [];
336
+ let mayTransfer = false;
337
+ for (let index = 0; index < statements.length; index += 1) {
338
+ const statement = statements[index];
339
+ const result = transformStatement(statement);
340
+ operations.push(...result.operations);
341
+ mayTransfer = mayTransfer || result.mayTransfer;
342
+ if (result.mustTerminate) {
343
+ for (const unreachable of statements.slice(index + 1)) {
344
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.unreachable-after-terminal', sourceFile, node: unreachable }));
345
+ }
346
+ return { operations, mayTransfer, mustTerminate: true };
347
+ }
348
+ if (result.mayTransfer) {
349
+ const remainder = transformList(statements.slice(index + 1));
350
+ if (remainder.operations.length > 0) {
351
+ operations.push(createHandlerOperation('router-guard', {
352
+ transferFlag,
353
+ body: createHandlerOperation('block', {
354
+ statement: ts.factory.createBlock([], true),
355
+ statements: Object.freeze(remainder.operations)
356
+ })
357
+ }));
358
+ }
359
+ return { operations, mayTransfer: true, mustTerminate: remainder.mustTerminate };
360
+ }
361
+ }
362
+ return { operations, mayTransfer, mustTerminate: false };
363
+ }
364
+
365
+ function transformStatement(statement) {
366
+ const call = nextName ? directNextCall(statement, nextName) : undefined;
367
+ if (call) return { operations: [transferOperation(call)], mayTransfer: true, mustTerminate: true };
368
+ if (ts.isReturnStatement(statement)) {
369
+ const returned = statement.expression && unwrapExpression(statement.expression);
370
+ const returnsVoid = !returned
371
+ || (ts.isIdentifier(returned) && returned.text === 'undefined')
372
+ || ts.isVoidExpression(returned);
373
+ if (role === 'event' && !returnsVoid) {
374
+ diagnostics.push(diagnostic(
375
+ sourceFile,
376
+ statement,
377
+ 'PULSE_EVENT_HANDLER_RESULT_UNSUPPORTED',
378
+ 'Event handlers complete with void and cannot return a result value.',
379
+ { role }
380
+ ));
381
+ } else if (role !== 'event' && !statement.expression) {
382
+ diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-router', issue: 'handler.return-value.required', sourceFile, node: statement }));
383
+ }
384
+ return { operations: [createHandlerOperation('source-statement', { statement: rewrite(statement), role: 'router-return' })], mayTransfer: false, mustTerminate: true };
385
+ }
386
+ if (ts.isBlock(statement)) {
387
+ const nested = transformList(statement.statements);
388
+ return {
389
+ operations: [createHandlerOperation('block', { statement, statements: Object.freeze(nested.operations) })],
390
+ mayTransfer: nested.mayTransfer,
391
+ mustTerminate: nested.mustTerminate
392
+ };
393
+ }
394
+ if (ts.isIfStatement(statement)) {
395
+ const thenResult = ts.isBlock(statement.thenStatement)
396
+ ? transformList(statement.thenStatement.statements)
397
+ : transformList([statement.thenStatement]);
398
+ const elseResult = statement.elseStatement
399
+ ? (ts.isBlock(statement.elseStatement) ? transformList(statement.elseStatement.statements) : transformList([statement.elseStatement]))
400
+ : { operations: [], mayTransfer: false, mustTerminate: false };
401
+ return {
402
+ operations: [createHandlerOperation('if', {
403
+ statement,
404
+ expression: rewrite(statement.expression),
405
+ thenOperation: createHandlerOperation('block', { statement: ts.factory.createBlock([], true), statements: Object.freeze(thenResult.operations) }),
406
+ elseOperation: statement.elseStatement ? createHandlerOperation('block', { statement: ts.factory.createBlock([], true), statements: Object.freeze(elseResult.operations) }) : undefined
407
+ })],
408
+ mayTransfer: thenResult.mayTransfer || elseResult.mayTransfer,
409
+ mustTerminate: Boolean(statement.elseStatement) && thenResult.mustTerminate && elseResult.mustTerminate
410
+ };
411
+ }
412
+ return { operations: [createHandlerOperation('source-statement', { statement: rewrite(statement), role: 'router-source' })], mayTransfer: false, mustTerminate: false };
413
+ }
414
+
415
+ const originalBody = ts.isBlock(functionNode.body)
416
+ ? functionNode.body
417
+ : ts.factory.createBlock([ts.factory.createReturnStatement(functionNode.body)], true);
418
+ const transformed = transformList(originalBody.statements);
419
+ if (role !== 'event' && !transformed.mustTerminate) diagnostics.push(createHandlerDiagnostic({
420
+ frontend: 'canonical-router',
421
+ issue: 'handler.fallthrough',
422
+ sourceFile,
423
+ node: functionNode,
424
+ values: { role },
425
+ detail: { role }
426
+ }));
427
+
428
+ const surfaceFacts = entrySurfaceFacts(recognition, classification, entry.stableId);
429
+ const wave2Normalization = normalized.summary.changed || normalized.summary.userAuthoredAsync || normalized.summary.awaitCount > 0;
430
+ const normalization = wave2Normalization ? normalized.summary : undefined;
431
+ return Object.freeze({
432
+ version: ROUTER_HANDLER_FRONTEND_VERSION,
433
+ frontend: 'canonical-router-handler',
434
+ handlerIrVersion: HANDLER_IR_VERSION,
435
+ handlerIrKind: ROUTER_HANDLER_IR_KIND,
436
+ fileName,
437
+ sourceFile,
438
+ sourceText: authoredFunctionNode.getText(sourceFile),
439
+ functionNode,
440
+ authoredFunctionNode,
441
+ warnings: normalized.warnings,
442
+ normalization,
443
+ descriptor,
444
+ role,
445
+ entry,
446
+ route,
447
+ signature: Object.freeze({
448
+ ctxName: signature.ctxName || 'ctx',
449
+ nextName,
450
+ errorName
451
+ }),
452
+ transferFlag,
453
+ body: createHandlerOperation('block', {
454
+ statement: originalBody,
455
+ statements: Object.freeze(transformed.operations)
456
+ }),
457
+ analysis: analysisForSurfaceFacts(surfaceFacts),
458
+ schemaBundle: emptySchemaBundle(fileName),
459
+ flow: Object.freeze({ mayTransfer: transformed.mayTransfer, mustTerminate: transformed.mustTerminate }),
460
+ surfaceFacts: Object.freeze({
461
+ ...surfaceFacts,
462
+ normalization: wave2Normalization
463
+ ? Object.freeze({
464
+ authority: 'async-surface-normalizer',
465
+ frontendVersion: ROUTER_HANDLER_FRONTEND_VERSION,
466
+ ...normalized.summary,
467
+ behaviorChangeAllowed: true
468
+ })
469
+ : Object.freeze({
470
+ authority: 'router-handler-frontend',
471
+ frontendVersion: ROUTER_HANDLER_FRONTEND_VERSION,
472
+ changed: true,
473
+ behaviorChangeAllowed: false
474
+ })
475
+ })
476
+ });
477
+ }
478
+
479
+ function prepareCanonicalRouterHandlers(topology, recognition, classification) {
480
+ if (!topology || topology.frontend !== 'canonical-router') throw new TypeError('prepareCanonicalRouterHandlers requires canonical Router topology.');
481
+ const model = sourceModelForRouterRecognition(recognition);
482
+ if (!model || model.topology !== topology) throw new TypeError('Router handler recognition does not belong to the supplied topology.');
483
+ const diagnostics = [];
484
+ const handlers = topology.entries
485
+ .filter((descriptor) => ['use', 'route', 'error', 'event'].includes(descriptor.entry.kind))
486
+ .map((descriptor) => normalizeRouterHandler(topology, descriptor, recognition, classification, diagnostics));
487
+ const validation = Object.freeze({
488
+ authority: 'router-handler-frontend',
489
+ frontendVersion: ROUTER_HANDLER_FRONTEND_VERSION,
490
+ handlerCount: handlers.length,
491
+ behaviorChangeAllowed: false
492
+ });
493
+ if (diagnostics.some((entry) => entry.severity === 'error')) {
494
+ throw new CanonicalRouterCompileError(`Canonical Router lowering failed for ${topology.fileName}.`, diagnostics);
495
+ }
496
+ return Object.freeze({
497
+ version: ROUTER_HANDLER_FRONTEND_VERSION,
498
+ topology,
499
+ recognition,
500
+ classification,
501
+ validation,
502
+ handlers: Object.freeze(handlers.map((handler) => Object.freeze({
503
+ ...handler,
504
+ surfaceFacts: Object.freeze({ ...handler.surfaceFacts, validation })
505
+ })))
506
+ });
507
+ }
508
+
509
+ module.exports = Object.freeze({
510
+ ROUTER_HANDLER_FRONTEND_VERSION,
511
+ validateFunctionSignature,
512
+ prepareCanonicalRouterHandlers
513
+ });