@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,588 @@
1
+ 'use strict';
2
+
3
+ const ts = require('typescript');
4
+
5
+ function loadHandlerSurfaceContract() {
6
+ try {
7
+ return require('@pulse-compute/wasm-contracts/handler/surface-contract');
8
+ } catch (error) {
9
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
10
+ return require('../../../contracts/src/handler/surface-contract.js');
11
+ }
12
+ throw error;
13
+ }
14
+ }
15
+
16
+ const contract = loadHandlerSurfaceContract();
17
+ const HANDLER_SURFACE_AUTHORITY_VERSION = 'pulse.compiler-handler-surface.v2';
18
+ const canonicalSourceModels = new WeakMap();
19
+ const canonicalRouterModels = new WeakMap();
20
+
21
+ function unwrapExpression(node) {
22
+ let current = node;
23
+ while (current && (
24
+ ts.isParenthesizedExpression(current)
25
+ || ts.isAsExpression(current)
26
+ || ts.isSatisfiesExpression?.(current)
27
+ || ts.isNonNullExpression(current)
28
+ || ts.isTypeAssertionExpression(current)
29
+ || ts.isPartiallyEmittedExpression?.(current)
30
+ )) current = current.expression;
31
+ return current;
32
+ }
33
+
34
+ function expressionFor(node, options = {}) {
35
+ let current = options.unwrap === true ? unwrapExpression(node) : node;
36
+ if (options.unwrapAwait === true && current && ts.isAwaitExpression(current)) {
37
+ current = options.unwrap === true ? unwrapExpression(current.expression) : current.expression;
38
+ }
39
+ return current;
40
+ }
41
+
42
+ function isIdentifierNamed(node, name, options = {}) {
43
+ const current = expressionFor(node, options);
44
+ return Boolean(current && ts.isIdentifier(current) && current.text === name);
45
+ }
46
+
47
+ function isPropertyAccessNamed(node, objectName, propertyName, options = {}) {
48
+ const current = expressionFor(node, options);
49
+ return Boolean(current && ts.isPropertyAccessExpression(current)
50
+ && isIdentifierNamed(current.expression, objectName, options)
51
+ && current.name.text === propertyName);
52
+ }
53
+
54
+ function callTargetParts(expression, options = {}) {
55
+ const current = expressionFor(expression, options);
56
+ if (!current || !ts.isCallExpression(current)) return undefined;
57
+ const target = expressionFor(current.expression, options);
58
+ if (!target || !ts.isPropertyAccessExpression(target)) return undefined;
59
+ return Object.freeze({ call: current, receiver: expressionFor(target.expression, options), method: target.name.text });
60
+ }
61
+
62
+ function staticString(node, options = {}) {
63
+ const current = expressionFor(node, options);
64
+ return current && (ts.isStringLiteral(current) || ts.isNoSubstitutionTemplateLiteral(current)) ? current.text : undefined;
65
+ }
66
+
67
+ function createRecognition(surfaceId, node, detail = {}, options = {}) {
68
+ const classified = contract.classifyHandlerSurface(surfaceId, { strict: options.strict !== false });
69
+ const definition = contract.DEFAULT_HANDLER_SURFACE_REGISTRY.get(surfaceId);
70
+ if (!definition) return undefined;
71
+ return Object.freeze({
72
+ version: HANDLER_SURFACE_AUTHORITY_VERSION,
73
+ contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
74
+ contractId: contract.HANDLER_SURFACE_CONTRACT_ID,
75
+ surfaceId,
76
+ class: definition.class,
77
+ canonicalOperation: definition.canonicalOperation,
78
+ awaitPolicy: definition.awaitPolicy,
79
+ nativeEligible: definition.targetSupport.native,
80
+ node,
81
+ detail: Object.freeze({ ...detail }),
82
+ classification: classified
83
+ });
84
+ }
85
+
86
+ function recognizeManagedHandlerWrapper(node, options = {}) {
87
+ if (!node || !ts.isFunctionLike(node)) return undefined;
88
+ const async = (node.modifiers || []).some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword);
89
+ if (!async) return undefined;
90
+ return createRecognition('handler.managed-async-wrapper', node, {
91
+ role: options.role,
92
+ functionKind: ts.SyntaxKind[node.kind]
93
+ }, options);
94
+ }
95
+
96
+ function extractFetchChain(expression, ctxName, options = {}) {
97
+ const current = expressionFor(expression, options);
98
+ if (!current) return undefined;
99
+ let decoder;
100
+ let decoderCall;
101
+ let call = current;
102
+ const outer = callTargetParts(current, options);
103
+ if (outer && ['json', 'text'].includes(outer.method)) {
104
+ decoder = outer.method;
105
+ decoderCall = outer.call;
106
+ call = outer.receiver;
107
+ }
108
+ call = expressionFor(call, options);
109
+ if (!call || !ts.isCallExpression(call)) return undefined;
110
+ if (!isPropertyAccessNamed(call.expression, ctxName, 'fetch', options)) return undefined;
111
+ return Object.freeze({
112
+ fetchCall: call,
113
+ decoder,
114
+ decoderCall,
115
+ decoderArgs: decoderCall ? Object.freeze([...decoderCall.arguments]) : Object.freeze([]),
116
+ url: call.arguments[0],
117
+ init: call.arguments[1]
118
+ });
119
+ }
120
+
121
+ function extractParallelCall(expression, ctxName, options = {}) {
122
+ const current = expressionFor(expression, options);
123
+ if (!current || !ts.isCallExpression(current)) return undefined;
124
+ if (!isPropertyAccessNamed(current.expression, ctxName, 'parallel', options)) return undefined;
125
+ return Object.freeze({
126
+ call: current,
127
+ argumentCount: current.arguments.length,
128
+ record: current.arguments[0]
129
+ });
130
+ }
131
+
132
+ function extractEventEmitCall(expression, ctxName, options = {}) {
133
+ const current = expressionFor(expression, options);
134
+ if (!current || !ts.isCallExpression(current)) return undefined;
135
+ if (!isPropertyAccessNamed(current.expression, ctxName, 'emit', options)) return undefined;
136
+ return Object.freeze({
137
+ call: current,
138
+ argumentCount: current.arguments.length,
139
+ type: current.arguments[0],
140
+ event: current.arguments[1]
141
+ });
142
+ }
143
+
144
+ function extractKvNamespaceDeclaration(statement, ctxName, options = {}) {
145
+ if (!statement || !ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return undefined;
146
+ const declaration = statement.declarationList.declarations[0];
147
+ const initializer = expressionFor(declaration.initializer, options);
148
+ if (!ts.isIdentifier(declaration.name) || !initializer || !ts.isCallExpression(initializer)) return undefined;
149
+ if (!isPropertyAccessNamed(initializer.expression, ctxName, 'kv', options) || initializer.arguments.length !== 1) return undefined;
150
+ return Object.freeze({ statement, declaration, variableName: declaration.name.text, store: initializer.arguments[0] });
151
+ }
152
+
153
+ function extractProviderCall(expression, ctxName, kvAliases = new Map(), options = {}) {
154
+ const current = expressionFor(expression, options);
155
+ if (!current || !ts.isCallExpression(current)) return undefined;
156
+ const emit = extractEventEmitCall(current, ctxName, options);
157
+ if (emit && emit.argumentCount === 2) {
158
+ return Object.freeze({
159
+ call: current,
160
+ surfaceId: 'ctx.emit',
161
+ kind: 'event.emit',
162
+ providerKind: 'event',
163
+ operation: 'emit',
164
+ capability: 'event.emit',
165
+ type: emit.type,
166
+ emission: emit.event,
167
+ args: Object.freeze([...current.arguments])
168
+ });
169
+ }
170
+ const target = expressionFor(current.expression, options);
171
+ if (!target || !ts.isPropertyAccessExpression(target)) return undefined;
172
+ const receiver = expressionFor(target.expression, options);
173
+ const method = target.name.text;
174
+
175
+ if (receiver && ts.isPropertyAccessExpression(receiver) && isIdentifierNamed(receiver.expression, ctxName, options)) {
176
+ const namespace = receiver.name.text;
177
+ if (namespace === 'config' && method === 'get' && current.arguments.length === 1) {
178
+ return Object.freeze({ call: current, surfaceId: 'ctx.config.get', kind: 'config.get', providerKind: 'config', operation: 'get', capability: 'config.get', resource: current.arguments[0], name: current.arguments[0], args: Object.freeze([...current.arguments]) });
179
+ }
180
+ if (namespace === 'secret' && method === 'get' && current.arguments.length === 1) {
181
+ return Object.freeze({ call: current, surfaceId: 'ctx.secret.get', kind: 'secret.get', providerKind: 'secret', operation: 'get', capability: 'secret.get', resource: current.arguments[0], name: current.arguments[0], args: Object.freeze([...current.arguments]) });
182
+ }
183
+ }
184
+
185
+ if (['get', 'put', 'getVersioned', 'insertIfAbsent', 'compareAndSwap'].includes(method)) {
186
+ let store;
187
+ if (receiver && ts.isCallExpression(receiver) && isPropertyAccessNamed(receiver.expression, ctxName, 'kv', options) && receiver.arguments.length === 1) {
188
+ store = receiver.arguments[0];
189
+ } else if (receiver && ts.isIdentifier(receiver) && kvAliases.has(receiver.text)) {
190
+ store = kvAliases.get(receiver.text);
191
+ }
192
+ if (store) {
193
+ if (['getVersioned', 'insertIfAbsent', 'compareAndSwap'].includes(method)
194
+ && !ts.isStringLiteral(store) && !ts.isNoSubstitutionTemplateLiteral(store)) return undefined;
195
+ const expected = method === 'compareAndSwap' ? 3 : ['get', 'getVersioned'].includes(method) ? 1 : 2;
196
+ if (current.arguments.length === expected) {
197
+ return Object.freeze({
198
+ call: current,
199
+ surfaceId: `ctx.kv.${method}`,
200
+ kind: `kv.${method}`,
201
+ providerKind: 'kv',
202
+ operation: method,
203
+ capability: `kv.${method}`,
204
+ resource: store,
205
+ store,
206
+ key: current.arguments[0],
207
+ value: current.arguments[method === 'compareAndSwap' ? 2 : 1],
208
+ generation: method === 'compareAndSwap' ? current.arguments[1] : undefined,
209
+ args: Object.freeze([...current.arguments])
210
+ });
211
+ }
212
+ }
213
+ }
214
+ return undefined;
215
+ }
216
+
217
+ function recognizeHandlerSurface(node, options = {}) {
218
+ if (!node) return undefined;
219
+ const ctxName = options.ctxName || 'ctx';
220
+ const nextName = options.nextName;
221
+ const current = expressionFor(node, options);
222
+
223
+ if (ts.isAwaitExpression(current)) {
224
+ const inner = recognizeHandlerSurface(current.expression, { ...options, awaited: true });
225
+ if (inner) return Object.freeze({ ...inner, node: current, awaited: true, innerNode: inner.node });
226
+ return createRecognition('javascript.await', current, { awaited: true }, options);
227
+ }
228
+
229
+ const wrapper = recognizeManagedHandlerWrapper(current, options);
230
+ if (wrapper) return wrapper;
231
+
232
+ if (ts.isCallExpression(current)) {
233
+ const callTarget = expressionFor(current.expression, options);
234
+ if (nextName && isIdentifierNamed(callTarget, nextName)) {
235
+ return createRecognition('router.next', current, {
236
+ argumentCount: current.arguments.length,
237
+ position: options.position
238
+ }, options);
239
+ }
240
+
241
+ const parallel = extractParallelCall(current, ctxName, options);
242
+ if (parallel) return createRecognition('ctx.parallel', current, { parallel }, options);
243
+
244
+ const emit = extractEventEmitCall(current, ctxName, options);
245
+ if (emit) return createRecognition('ctx.emit', current, { emit }, options);
246
+
247
+ if (typeof options.packageEffectForCall === 'function') {
248
+ const packageEffect = options.packageEffectForCall(current);
249
+ if (packageEffect) return createRecognition('package.operation', current, { packageEffect }, options);
250
+ }
251
+
252
+ const fetch = extractFetchChain(current, ctxName, options);
253
+ if (fetch) {
254
+ return createRecognition(fetch.decoder ? 'ctx.fetch.projected' : 'ctx.fetch.opaque-return', current, { fetch }, options);
255
+ }
256
+
257
+ const provider = extractProviderCall(current, ctxName, options.kvAliases || new Map(), options);
258
+ if (provider) return createRecognition(provider.surfaceId, current, { provider }, options);
259
+
260
+ const target = expressionFor(current.expression, options);
261
+ if (target && ts.isPropertyAccessExpression(target)) {
262
+ const receiver = expressionFor(target.expression, options);
263
+ if (isIdentifierNamed(receiver, ctxName, options)) {
264
+ const direct = Object.freeze({
265
+ param: 'ctx.param',
266
+ json: 'ctx.json',
267
+ text: 'ctx.text',
268
+ response: 'ctx.response'
269
+ });
270
+ const surfaceId = direct[target.name.text];
271
+ if (surfaceId) return createRecognition(surfaceId, current, { arguments: Object.freeze([...current.arguments]) }, options);
272
+ }
273
+ if (receiver && ts.isPropertyAccessExpression(receiver) && isIdentifierNamed(receiver.expression, ctxName, options)) {
274
+ const namespace = receiver.name.text;
275
+ const method = target.name.text;
276
+ if (namespace === 'req' && method === 'header') return createRecognition('ctx.req.header', current, { arguments: Object.freeze([...current.arguments]) }, options);
277
+ if (namespace === 'req' && method === 'text') return createRecognition('ctx.req.text', current, { arguments: Object.freeze([...current.arguments]) }, options);
278
+ if (namespace === 'req' && method === 'json') {
279
+ return createRecognition(current.arguments.length === 0 ? 'ctx.req.json.generic' : 'ctx.req.json.schema', current, { arguments: Object.freeze([...current.arguments]) }, options);
280
+ }
281
+ if (namespace === 'state' && method === 'get') return createRecognition('ctx.state.get', current, { arguments: Object.freeze([...current.arguments]) }, options);
282
+ if (namespace === 'state' && method === 'set') return createRecognition('ctx.state.set', current, { arguments: Object.freeze([...current.arguments]) }, options);
283
+ if (namespace === 'log' && ['error', 'warn', 'info', 'debug'].includes(method)) {
284
+ return createRecognition(`ctx.log.${method}`, current, {
285
+ level: method,
286
+ arguments: Object.freeze([...current.arguments])
287
+ }, options);
288
+ }
289
+ }
290
+ }
291
+ }
292
+
293
+ if (ts.isPropertyAccessExpression(current)) {
294
+ const receiver = expressionFor(current.expression, options);
295
+ if (receiver && ts.isPropertyAccessExpression(receiver) && isIdentifierNamed(receiver.expression, ctxName, options) && receiver.name.text === 'req') {
296
+ const surfaceId = Object.freeze({
297
+ method: 'ctx.req.method',
298
+ url: 'ctx.req.url',
299
+ path: 'ctx.req.path',
300
+ headers: 'ctx.req.headers'
301
+ })[current.name.text];
302
+ if (surfaceId) return createRecognition(surfaceId, current, {}, options);
303
+ }
304
+ if (receiver && ts.isPropertyAccessExpression(receiver) && isIdentifierNamed(receiver.expression, ctxName, options) && receiver.name.text === 'event') {
305
+ const surfaceId = Object.freeze({
306
+ type: 'ctx.event.type',
307
+ payload: 'ctx.event.payload'
308
+ })[current.name.text];
309
+ if (surfaceId) return createRecognition(surfaceId, current, {}, options);
310
+ }
311
+ }
312
+
313
+ return undefined;
314
+ }
315
+
316
+ function classifyRecognizedHandlerSurface(recognized, options = {}) {
317
+ if (!recognized) return undefined;
318
+ const classified = contract.classifyHandlerSurface(recognized.surfaceId, {
319
+ strict: options.strict !== false
320
+ });
321
+ return Object.freeze({
322
+ version: HANDLER_SURFACE_AUTHORITY_VERSION,
323
+ surfaceId: recognized.surfaceId,
324
+ class: recognized.class,
325
+ canonicalOperation: recognized.canonicalOperation,
326
+ awaitPolicy: recognized.awaitPolicy,
327
+ nativeEligible: recognized.nativeEligible,
328
+ classification: classified,
329
+ detail: recognized.detail
330
+ });
331
+ }
332
+
333
+ function sourcePosition(sourceFile, node) {
334
+ const offset = node && typeof node.getStart === 'function' ? node.getStart(sourceFile) : 0;
335
+ const point = sourceFile.getLineAndCharacterOfPosition(offset);
336
+ return Object.freeze({ line: point.line + 1, column: point.character + 1, offset });
337
+ }
338
+
339
+ function defaultHandlerCandidates(sourceFile) {
340
+ const handlers = [];
341
+ for (const statement of sourceFile.statements) {
342
+ if (ts.isFunctionDeclaration(statement)) {
343
+ const modifiers = statement.modifiers || [];
344
+ if (modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
345
+ && modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) handlers.push(statement);
346
+ }
347
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
348
+ const expression = unwrapExpression(statement.expression);
349
+ if (expression && (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression))) handlers.push(expression);
350
+ }
351
+ }
352
+ return handlers;
353
+ }
354
+
355
+ function inspectCanonicalSourceSurfaces(sourceText, options = {}) {
356
+ const fileName = String(options.fileName || 'app.ts').replace(/\\/g, '/');
357
+ const source = String(sourceText);
358
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.ES2022, true, fileName.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
359
+ const handlers = defaultHandlerCandidates(sourceFile);
360
+ const facts = [];
361
+ const seen = new Set();
362
+ const surfacesByNode = new WeakMap();
363
+
364
+ function record(node, handler, ctxName) {
365
+ const recognized = recognizeHandlerSurface(node, { ctxName, role: 'handler', strict: options.strict !== false });
366
+ if (!recognized) return undefined;
367
+ surfacesByNode.set(node, recognized);
368
+ const position = sourcePosition(sourceFile, node);
369
+ const key = `${recognized.surfaceId}\u0000${position.offset}\u0000${node.end}`;
370
+ if (!seen.has(key)) {
371
+ seen.add(key);
372
+ facts.push(Object.freeze({
373
+ surfaceId: recognized.surfaceId,
374
+ class: recognized.class,
375
+ canonicalOperation: recognized.canonicalOperation,
376
+ awaitPolicy: recognized.awaitPolicy,
377
+ nativeEligible: recognized.nativeEligible,
378
+ awaited: recognized.awaited === true,
379
+ position
380
+ }));
381
+ }
382
+ return recognized;
383
+ }
384
+
385
+ for (const handler of handlers) {
386
+ const ctxParameter = handler.parameters && handler.parameters[0];
387
+ const ctxName = ctxParameter && ts.isIdentifier(ctxParameter.name) ? ctxParameter.name.text : 'ctx';
388
+ record(handler, handler, ctxName);
389
+ function visit(node) {
390
+ if (typeof ts.isTypeNode === 'function' && ts.isTypeNode(node)) return;
391
+ const recognized = record(node, handler, ctxName);
392
+ if (recognized && ts.isAwaitExpression(node)) return;
393
+ if (recognized && ts.isCallExpression(node)) {
394
+ for (const argument of node.arguments) visit(argument);
395
+ return;
396
+ }
397
+ ts.forEachChild(node, visit);
398
+ }
399
+ if (handler.body) visit(handler.body);
400
+ }
401
+
402
+ facts.sort((left, right) => left.position.offset - right.position.offset || left.surfaceId.localeCompare(right.surfaceId));
403
+ const recognition = Object.freeze({
404
+ version: HANDLER_SURFACE_AUTHORITY_VERSION,
405
+ contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
406
+ file: fileName,
407
+ handlerCount: handlers.length,
408
+ facts: Object.freeze(facts),
409
+ summary: Object.freeze({
410
+ total: facts.length,
411
+ byClass: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.class))].sort().map((className) => [className, facts.filter((fact) => fact.class === className).length]))),
412
+ bySurface: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.surfaceId))].sort().map((surfaceId) => [surfaceId, facts.filter((fact) => fact.surfaceId === surfaceId).length])))
413
+ })
414
+ });
415
+ canonicalSourceModels.set(recognition, Object.freeze({
416
+ file: fileName,
417
+ source,
418
+ sourceFile,
419
+ handlers: Object.freeze([...handlers]),
420
+ surfaceForNode(node) {
421
+ return node && typeof node === 'object' ? surfacesByNode.get(node) : undefined;
422
+ }
423
+ }));
424
+ return recognition;
425
+ }
426
+
427
+ function sourceModelForCanonicalRecognition(recognition) {
428
+ return recognition && typeof recognition === 'object' ? canonicalSourceModels.get(recognition) : undefined;
429
+ }
430
+
431
+ function classifyCanonicalSourceSurfaces(recognition, options = {}) {
432
+ return Object.freeze({
433
+ version: HANDLER_SURFACE_AUTHORITY_VERSION,
434
+ contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
435
+ file: recognition.file,
436
+ handlerCount: recognition.handlerCount,
437
+ facts: Object.freeze(recognition.facts.map((fact) => Object.freeze({
438
+ ...fact,
439
+ classification: contract.classifyHandlerSurface(fact.surfaceId, { strict: options.strict !== false })
440
+ }))),
441
+ summary: recognition.summary
442
+ });
443
+ }
444
+
445
+ function routerHandlerNames(entry) {
446
+ const functionNode = entry && entry.functionNode;
447
+ const role = entry && entry.role;
448
+ const names = functionNode && functionNode.parameters
449
+ ? functionNode.parameters.map((parameter) => ts.isIdentifier(parameter.name) ? parameter.name.text : undefined)
450
+ : [];
451
+ return Object.freeze({
452
+ ctxName: role === 'error' ? names[1] : names[0],
453
+ nextName: role === 'route' && names.length === 1 ? undefined : names[role === 'error' ? 2 : 1],
454
+ errorName: role === 'error' ? names[0] : undefined
455
+ });
456
+ }
457
+
458
+ function inspectCanonicalRouterSurfaces(topology, options = {}) {
459
+ if (!topology || topology.frontend !== 'canonical-router' || !topology.sourceFile || !Array.isArray(topology.entries)) {
460
+ throw new TypeError('inspectCanonicalRouterSurfaces requires a prepared canonical Router topology.');
461
+ }
462
+ const sourceFile = topology.sourceFile;
463
+ const facts = [];
464
+ const surfacesByEntry = new Map();
465
+
466
+ function record(entry, node, names) {
467
+ const recognized = recognizeHandlerSurface(node, {
468
+ ctxName: names.ctxName || 'ctx',
469
+ nextName: names.nextName,
470
+ role: entry.role,
471
+ unwrap: true,
472
+ strict: options.strict !== false
473
+ });
474
+ if (!recognized) return undefined;
475
+ let byNode = surfacesByEntry.get(entry.entry.stableId);
476
+ if (!byNode) {
477
+ byNode = new WeakMap();
478
+ surfacesByEntry.set(entry.entry.stableId, byNode);
479
+ }
480
+ byNode.set(node, recognized);
481
+ const entrySourceFile = entry.sourceFile || sourceFile;
482
+ const position = sourcePosition(entrySourceFile, node);
483
+ facts.push(Object.freeze({
484
+ routerEntryStableId: entry.entry.stableId,
485
+ routerEntryIndex: entry.entry.index,
486
+ routerEntryKind: entry.entry.kind,
487
+ handlerId: entry.entry.handlerId,
488
+ role: entry.role,
489
+ surfaceId: recognized.surfaceId,
490
+ class: recognized.class,
491
+ canonicalOperation: recognized.canonicalOperation,
492
+ awaitPolicy: recognized.awaitPolicy,
493
+ nativeEligible: recognized.nativeEligible,
494
+ awaited: recognized.awaited === true,
495
+ position
496
+ }));
497
+ return recognized;
498
+ }
499
+
500
+ for (const entry of topology.entries) {
501
+ if (!entry.functionNode) continue;
502
+ const names = routerHandlerNames(entry);
503
+ record(entry, entry.functionNode, names);
504
+ function visit(node) {
505
+ if (typeof ts.isTypeNode === 'function' && ts.isTypeNode(node)) return;
506
+ const recognized = record(entry, node, names);
507
+ if (recognized && ts.isAwaitExpression(node)) return;
508
+ if (recognized && ts.isCallExpression(node)) {
509
+ for (const argument of node.arguments) visit(argument);
510
+ return;
511
+ }
512
+ ts.forEachChild(node, visit);
513
+ }
514
+ if (entry.functionNode.body) visit(entry.functionNode.body);
515
+ }
516
+
517
+ const recognition = Object.freeze({
518
+ version: HANDLER_SURFACE_AUTHORITY_VERSION,
519
+ contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
520
+ file: topology.fileName,
521
+ handlerCount: topology.entries.filter((entry) => Boolean(entry.functionNode)).length,
522
+ facts: Object.freeze(facts),
523
+ summary: Object.freeze({
524
+ total: facts.length,
525
+ byClass: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.class))].sort().map((className) => [className, facts.filter((fact) => fact.class === className).length]))),
526
+ bySurface: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.surfaceId))].sort().map((surfaceId) => [surfaceId, facts.filter((fact) => fact.surfaceId === surfaceId).length]))),
527
+ byEntryKind: Object.freeze(Object.fromEntries([...new Set(facts.map((fact) => fact.routerEntryKind))].sort().map((kind) => [kind, facts.filter((fact) => fact.routerEntryKind === kind).length])))
528
+ })
529
+ });
530
+ canonicalRouterModels.set(recognition, Object.freeze({
531
+ topology,
532
+ namesForEntry(entryStableId) {
533
+ const entry = topology.entries.find((candidate) => candidate.entry.stableId === entryStableId);
534
+ return entry ? routerHandlerNames(entry) : undefined;
535
+ },
536
+ surfaceForNode(entryStableId, node) {
537
+ const byNode = surfacesByEntry.get(entryStableId);
538
+ return byNode && node && typeof node === 'object' ? byNode.get(node) : undefined;
539
+ },
540
+ factsForEntry(entryStableId) {
541
+ return Object.freeze(facts.filter((fact) => fact.routerEntryStableId === entryStableId));
542
+ }
543
+ }));
544
+ return recognition;
545
+ }
546
+
547
+ function sourceModelForRouterRecognition(recognition) {
548
+ return recognition && typeof recognition === 'object' ? canonicalRouterModels.get(recognition) : undefined;
549
+ }
550
+
551
+ function classifyCanonicalRouterSurfaces(recognition, options = {}) {
552
+ return Object.freeze({
553
+ version: HANDLER_SURFACE_AUTHORITY_VERSION,
554
+ contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
555
+ file: recognition.file,
556
+ handlerCount: recognition.handlerCount,
557
+ facts: Object.freeze(recognition.facts.map((fact) => Object.freeze({
558
+ ...fact,
559
+ classification: contract.classifyHandlerSurface(fact.surfaceId, { strict: options.strict !== false })
560
+ }))),
561
+ summary: recognition.summary
562
+ });
563
+ }
564
+
565
+ module.exports = Object.freeze({
566
+ HANDLER_SURFACE_AUTHORITY_VERSION,
567
+ contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
568
+ unwrapExpression,
569
+ expressionFor,
570
+ isIdentifierNamed,
571
+ isPropertyAccessNamed,
572
+ callTargetParts,
573
+ staticString,
574
+ recognizeManagedHandlerWrapper,
575
+ recognizeHandlerSurface,
576
+ classifyRecognizedHandlerSurface,
577
+ extractFetchChain,
578
+ extractEventEmitCall,
579
+ extractParallelCall,
580
+ extractProviderCall,
581
+ extractKvNamespaceDeclaration,
582
+ inspectCanonicalSourceSurfaces,
583
+ classifyCanonicalSourceSurfaces,
584
+ sourceModelForCanonicalRecognition,
585
+ inspectCanonicalRouterSurfaces,
586
+ classifyCanonicalRouterSurfaces,
587
+ sourceModelForRouterRecognition
588
+ });