@pulse-compute/wasm-compiler 0.0.0 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -1
- package/bin/provider-proof-composition.js +34 -0
- package/bin/pulsewasm-extract.js +20 -0
- package/package.json +60 -5
- package/src/artifacts-dir.js +13 -0
- package/src/ast-json.js +52 -0
- package/src/build-manifest.js +354 -0
- package/src/canonical-api-compiler.js +269 -0
- package/src/canonical-native-compiler.js +411 -0
- package/src/canonical-native-plan.js +1478 -0
- package/src/canonical-project-compiler.js +1224 -0
- package/src/canonical-router-compiler.js +25 -0
- package/src/cli-intents.js +1235 -0
- package/src/cli.js +1927 -0
- package/src/codegen/assemblyscript-compile.js +3 -0
- package/src/codegen/assemblyscript-core.js +3 -0
- package/src/codegen/assemblyscript-shape.js +3 -0
- package/src/codegen/assemblyscript-wasm-smoke.js +3 -0
- package/src/codegen/backend-capabilities.js +3 -0
- package/src/codegen/channel-broadcaster.js +3 -0
- package/src/codegen/compiled-handlers.js +3 -0
- package/src/codegen/compiled-wasm-runtime.js +3 -0
- package/src/codegen/config-references.js +248 -0
- package/src/codegen/dispatch-ts.js +145 -0
- package/src/codegen/effect-composition.js +331 -0
- package/src/codegen/effect-runtime.js +418 -0
- package/src/codegen/execution-harness-ts.js +467 -0
- package/src/codegen/handler-bindings-ts.js +298 -0
- package/src/codegen/handler-library-contracts.js +3 -0
- package/src/codegen/host-capabilities.js +3 -0
- package/src/codegen/host-runtime-kernel.js +3 -0
- package/src/codegen/integrated-compiled-app.js +3 -0
- package/src/codegen/json-body.js +3 -0
- package/src/codegen/library-sidecars.js +3 -0
- package/src/codegen/local-harness-ts.js +453 -0
- package/src/codegen/pulse-wrapper.js +217 -0
- package/src/codegen/request-result-headers.js +3 -0
- package/src/codegen/schema-json-compile.js +3 -0
- package/src/codegen/schema-json-sidecar-v2.js +3 -0
- package/src/codegen/schema-json-sidecar.js +3 -0
- package/src/codegen/streaming-passthrough.js +3 -0
- package/src/codegen/wasm-host-abi.js +3 -0
- package/src/codegen/wasm-host-bridge.js +3 -0
- package/src/compiled-wasm-host-runtime-kv.js +3 -0
- package/src/config-resolver.js +813 -0
- package/src/crypto-requirement-planner.js +89 -0
- package/src/definitions/config-schema.js +14 -0
- package/src/definitions/handler-roles.js +14 -0
- package/src/definitions/path-grammar.js +14 -0
- package/src/definitions/router-api.js +14 -0
- package/src/diagnostics/codes.js +14 -0
- package/src/diagnostics/reporter.js +14 -0
- package/src/diagnostics.js +14 -0
- package/src/dispatch-table.js +400 -0
- package/src/events/event-emit.js +265 -0
- package/src/events/event-topology.js +127 -0
- package/src/execution-plan.js +463 -0
- package/src/extractor.js +2816 -0
- package/src/handler-eval.js +1154 -0
- package/src/handler-table.js +326 -0
- package/src/index.js +19 -0
- package/src/javascript-application-plan.js +181 -0
- package/src/kv-provider.js +3 -0
- package/src/path-table.js +60 -0
- package/src/path.js +14 -0
- package/src/patterns/config-define.js +30 -0
- package/src/patterns/dependency-call.js +18 -0
- package/src/patterns/env-lookup.js +13 -0
- package/src/patterns/handler-reference.js +29 -0
- package/src/patterns/path-literal.js +35 -0
- package/src/patterns/result.js +15 -0
- package/src/patterns/router-chain-call.js +50 -0
- package/src/patterns/router-construction.js +19 -0
- package/src/project/package-reachability.js +782 -0
- package/src/project/reachable-graph-builder.js +992 -0
- package/src/project/reachable-graph-contract.js +54 -0
- package/src/project/reachable-graph-implementation.js +36 -0
- package/src/project/router-module-linker.js +710 -0
- package/src/project-config-compiler.js +222 -0
- package/src/project-target-support.js +500 -0
- package/src/provider-toolchain.js +299 -0
- package/src/spine/async-surface-normalizer.js +328 -0
- package/src/spine/canonical-handler-ir.js +336 -0
- package/src/spine/canonical-native-module.js +87 -0
- package/src/spine/canonical-native-plan.js +89 -0
- package/src/spine/canonical-project.js +76 -0
- package/src/spine/canonical-router.js +155 -0
- package/src/spine/canonical-source.js +165 -0
- package/src/spine/diagnostic-authority.js +290 -0
- package/src/spine/equivalence.js +262 -0
- package/src/spine/guest-unit-stage.js +87 -0
- package/src/spine/handler-ir-emitter.js +455 -0
- package/src/spine/handler-ir-managed.js +1598 -0
- package/src/spine/handler-ir.js +797 -0
- package/src/spine/handler-surface-authority.js +588 -0
- package/src/spine/package-operation-seam.js +1015 -0
- package/src/spine/pipeline.js +202 -0
- package/src/spine/plain-handler-frontend.js +545 -0
- package/src/spine/provider-requirement-authority.js +208 -0
- package/src/spine/router-control-contract.js +17 -0
- package/src/spine/router-handler-frontend.js +514 -0
- package/src/spine/router-handler-ir.js +372 -0
- package/src/spine/router-topology-frontend.js +638 -0
- package/src/stable-id.js +14 -0
|
@@ -0,0 +1,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
|
+
});
|