@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.
- package/README.md +52 -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 +245 -0
- package/src/canonical-native-compiler.js +411 -0
- package/src/canonical-native-plan.js +1477 -0
- package/src/canonical-project-compiler.js +1218 -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 +312 -0
- package/src/spine/canonical-handler-ir.js +335 -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 +454 -0
- package/src/spine/handler-ir-managed.js +1597 -0
- package/src/spine/handler-ir.js +797 -0
- package/src/spine/handler-surface-authority.js +585 -0
- package/src/spine/package-operation-seam.js +1015 -0
- package/src/spine/pipeline.js +202 -0
- package/src/spine/plain-handler-frontend.js +537 -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 +513 -0
- package/src/spine/router-handler-ir.js +372 -0
- package/src/spine/router-topology-frontend.js +635 -0
- package/src/stable-id.js +14 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ts = require('typescript');
|
|
4
|
+
const {
|
|
5
|
+
extractFetchChain,
|
|
6
|
+
extractKvNamespaceDeclaration,
|
|
7
|
+
extractParallelCall,
|
|
8
|
+
extractProviderCall,
|
|
9
|
+
recognizeHandlerSurface
|
|
10
|
+
} = require('./handler-surface-authority.js');
|
|
11
|
+
const {
|
|
12
|
+
positionFor,
|
|
13
|
+
createCanonicalDiagnostic,
|
|
14
|
+
createHandlerDiagnostic
|
|
15
|
+
} = require('./diagnostic-authority.js');
|
|
16
|
+
|
|
17
|
+
function loadCanonicalRuntimeContract() {
|
|
18
|
+
try { return require('@pulse-compute/wasm-contracts/handler/canonical-runtime'); }
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/handler/canonical-runtime.js');
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const canonicalRuntimeContract = loadCanonicalRuntimeContract();
|
|
26
|
+
const HANDLER_IR_VERSION = 'pulse.canonical-handler-ir.v1';
|
|
27
|
+
const HANDLER_IR_KIND = 'plain-handler';
|
|
28
|
+
const ROUTER_HANDLER_IR_KIND = 'router-handler';
|
|
29
|
+
const HANDLER_IR_OPERATION_KINDS = Object.freeze([
|
|
30
|
+
'source-statement',
|
|
31
|
+
'block',
|
|
32
|
+
'if',
|
|
33
|
+
'fetch-single',
|
|
34
|
+
'fetch-group',
|
|
35
|
+
'provider-variable',
|
|
36
|
+
'provider-expression',
|
|
37
|
+
'package-effect',
|
|
38
|
+
'package-variable',
|
|
39
|
+
'package-result-adapter',
|
|
40
|
+
'opaque-fetch-return'
|
|
41
|
+
]);
|
|
42
|
+
const HANDLER_IR_EXTENSION_OPERATION_KINDS = Object.freeze([
|
|
43
|
+
'parallel-group'
|
|
44
|
+
]);
|
|
45
|
+
const ROUTER_HANDLER_IR_OPERATION_KINDS = Object.freeze([
|
|
46
|
+
'router-transfer',
|
|
47
|
+
'router-guard'
|
|
48
|
+
]);
|
|
49
|
+
const ALL_HANDLER_IR_OPERATION_KINDS = Object.freeze([
|
|
50
|
+
...HANDLER_IR_OPERATION_KINDS,
|
|
51
|
+
...HANDLER_IR_EXTENSION_OPERATION_KINDS,
|
|
52
|
+
...ROUTER_HANDLER_IR_OPERATION_KINDS
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
function staticResource(node) {
|
|
56
|
+
if (!node) return Object.freeze({ kind: 'none' });
|
|
57
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
58
|
+
const value = node.text;
|
|
59
|
+
let origin;
|
|
60
|
+
try { origin = new URL(value).origin; } catch (_) { origin = undefined; }
|
|
61
|
+
return Object.freeze({ kind: 'literal', value, origin });
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze({ kind: 'dynamic' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function diagnostic(sourceFile, node, code, message, detail = {}) {
|
|
67
|
+
return createCanonicalDiagnostic({
|
|
68
|
+
frontend: 'canonical-source',
|
|
69
|
+
sourceFile,
|
|
70
|
+
node,
|
|
71
|
+
code,
|
|
72
|
+
message,
|
|
73
|
+
detail
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function collectIdentifiers(node, out = new Set()) {
|
|
78
|
+
if (!node) return out;
|
|
79
|
+
function visit(current) {
|
|
80
|
+
if (ts.isIdentifier(current)) out.add(current.text);
|
|
81
|
+
ts.forEachChild(current, visit);
|
|
82
|
+
}
|
|
83
|
+
visit(node);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function fetchVariableStatement(statement, ctxName) {
|
|
88
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return undefined;
|
|
89
|
+
const declaration = statement.declarationList.declarations[0];
|
|
90
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) return undefined;
|
|
91
|
+
const chain = extractFetchChain(declaration.initializer, ctxName);
|
|
92
|
+
if (!chain || !chain.url) return undefined;
|
|
93
|
+
return Object.freeze({ statement, declaration, variableName: declaration.name.text, chain });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function providerVariableStatement(statement, ctxName, kvAliases) {
|
|
97
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return undefined;
|
|
98
|
+
const declaration = statement.declarationList.declarations[0];
|
|
99
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) return undefined;
|
|
100
|
+
const operation = extractProviderCall(declaration.initializer, ctxName, kvAliases);
|
|
101
|
+
if (!operation) return undefined;
|
|
102
|
+
return Object.freeze({ statement, declaration, variableName: declaration.name.text, operation });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function providerExpressionStatement(statement, ctxName, kvAliases) {
|
|
106
|
+
if (!ts.isExpressionStatement(statement)) return undefined;
|
|
107
|
+
const operation = extractProviderCall(statement.expression, ctxName, kvAliases);
|
|
108
|
+
return operation ? Object.freeze({ statement, operation }) : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parallelInvocationStatement(statement, ctxName) {
|
|
112
|
+
if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
|
|
113
|
+
const declaration = statement.declarationList.declarations[0];
|
|
114
|
+
const parallel = extractParallelCall(declaration.initializer, ctxName, { unwrap: true });
|
|
115
|
+
if (parallel) return Object.freeze({ statement, declaration, call: parallel.call, record: parallel.record, discard: false });
|
|
116
|
+
}
|
|
117
|
+
if (ts.isExpressionStatement(statement)) {
|
|
118
|
+
const parallel = extractParallelCall(statement.expression, ctxName, { unwrap: true });
|
|
119
|
+
if (parallel) return Object.freeze({ statement, call: parallel.call, record: parallel.record, discard: true });
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function portableParallelKey(name) {
|
|
125
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isArrayIndexKey(key) {
|
|
130
|
+
if (!/^(0|[1-9]\d*)$/.test(key)) return false;
|
|
131
|
+
const value = Number(key);
|
|
132
|
+
return Number.isSafeInteger(value) && value >= 0 && value < 0xffffffff && String(value) === key;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function groupIsIndependent(candidates) {
|
|
136
|
+
const names = new Set(candidates.map((candidate) => candidate.variableName));
|
|
137
|
+
for (const candidate of candidates) {
|
|
138
|
+
const identifiers = new Set();
|
|
139
|
+
collectIdentifiers(candidate.chain.url, identifiers);
|
|
140
|
+
collectIdentifiers(candidate.chain.init, identifiers);
|
|
141
|
+
for (const name of names) if (identifiers.has(name)) return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function createHandlerOperation(kind, fields = {}) {
|
|
147
|
+
if (!ALL_HANDLER_IR_OPERATION_KINDS.includes(kind)) throw new TypeError(`Unsupported canonical Handler IR operation kind: ${kind}`);
|
|
148
|
+
return Object.freeze({ kind, ...fields });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function summarizeHandlerOperation(body) {
|
|
152
|
+
const counts = {};
|
|
153
|
+
function countOperation(entry) {
|
|
154
|
+
if (!entry) return;
|
|
155
|
+
counts[entry.kind] = (counts[entry.kind] || 0) + 1;
|
|
156
|
+
if (entry.kind === 'block') for (const child of entry.statements || []) countOperation(child);
|
|
157
|
+
if (entry.kind === 'if') {
|
|
158
|
+
countOperation(entry.thenOperation);
|
|
159
|
+
if (entry.elseOperation) countOperation(entry.elseOperation);
|
|
160
|
+
}
|
|
161
|
+
if (entry.kind === 'router-guard') countOperation(entry.body);
|
|
162
|
+
}
|
|
163
|
+
countOperation(body);
|
|
164
|
+
return Object.freeze({
|
|
165
|
+
operationCount: Object.values(counts).reduce((sum, count) => sum + count, 0),
|
|
166
|
+
operationKinds: Object.freeze(Object.fromEntries(Object.entries(counts).sort(([left], [right]) => left.localeCompare(right))))
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildPlainHandlerIr(frontend, options = {}) {
|
|
171
|
+
if (!frontend || frontend.frontend !== 'canonical-source') throw new TypeError('buildPlainHandlerIr requires a prepared canonical-source frontend.');
|
|
172
|
+
const { sourceFile, handler, analysis, schemaBundle, diagnostics } = frontend;
|
|
173
|
+
if (!handler) return undefined;
|
|
174
|
+
|
|
175
|
+
const ctxParameter = handler.parameters[0];
|
|
176
|
+
const ctxName = ctxParameter && ts.isIdentifier(ctxParameter.name) ? ctxParameter.name.text : 'ctx';
|
|
177
|
+
if (!ctxParameter || !ts.isIdentifier(ctxParameter.name)) diagnostics.push(createHandlerDiagnostic({
|
|
178
|
+
frontend: 'canonical-source',
|
|
179
|
+
issue: 'handler.context-identifier.required',
|
|
180
|
+
sourceFile,
|
|
181
|
+
node: handler
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
const effectSites = [];
|
|
185
|
+
const continuationSites = [];
|
|
186
|
+
const effectCounters = new Map();
|
|
187
|
+
let continuationIndex = 0;
|
|
188
|
+
const packageEffectsByStart = new Map();
|
|
189
|
+
for (const effectInput of options.packageEffects || []) {
|
|
190
|
+
if (!effectInput || effectInput.version !== canonicalRuntimeContract.CANONICAL_PACKAGE_EFFECT_VERSION || !effectInput.range) continue;
|
|
191
|
+
const start = Number(effectInput.range.start);
|
|
192
|
+
if (!Number.isSafeInteger(start) || packageEffectsByStart.has(start)) {
|
|
193
|
+
diagnostics.push(diagnostic(sourceFile, handler, 'PULSE_CANONICAL_PACKAGE_EFFECT_RANGE_INVALID', 'Package-owned canonical effects require unique source ranges.', { effect: effectInput }));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
packageEffectsByStart.set(start, effectInput);
|
|
197
|
+
}
|
|
198
|
+
const packageIntrinsicsByStart = new Map();
|
|
199
|
+
const linkedPackageIntrinsicsByStart = new Map();
|
|
200
|
+
for (const intrinsicInput of options.packageIntrinsics || []) {
|
|
201
|
+
const start = Number(intrinsicInput && intrinsicInput.range && intrinsicInput.range.start);
|
|
202
|
+
if (!Number.isSafeInteger(start) || packageIntrinsicsByStart.has(start)) {
|
|
203
|
+
diagnostics.push(diagnostic(sourceFile, handler, 'PULSE_CANONICAL_PACKAGE_INTRINSIC_RANGE_INVALID', 'Package-owned intrinsics require unique source ranges.', { intrinsic: intrinsicInput }));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
packageIntrinsicsByStart.set(start, intrinsicInput);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function unwrapPackageCall(expression) {
|
|
210
|
+
let current = expression;
|
|
211
|
+
while (current && (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isNonNullExpression(current) || ts.isTypeAssertionExpression(current))) current = current.expression;
|
|
212
|
+
if (current && ts.isVoidExpression(current)) current = current.expression;
|
|
213
|
+
return current && ts.isCallExpression(current) ? current : undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function packageEffectForCall(call) {
|
|
217
|
+
if (!call) return undefined;
|
|
218
|
+
if (typeof options.packageEffectForCall === 'function') {
|
|
219
|
+
const linked = options.packageEffectForCall(call);
|
|
220
|
+
if (linked) {
|
|
221
|
+
linkPackageIntrinsicsWithin(call);
|
|
222
|
+
return linked;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const effect = packageEffectsByStart.get(call.getStart(sourceFile));
|
|
226
|
+
if (effect) linkPackageIntrinsicsWithin(call);
|
|
227
|
+
return effect;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function packageIntrinsicForCall(call) {
|
|
231
|
+
if (!call) return undefined;
|
|
232
|
+
if (typeof options.packageIntrinsicForCall === 'function') {
|
|
233
|
+
const linked = options.packageIntrinsicForCall(call);
|
|
234
|
+
if (linked) {
|
|
235
|
+
const start = call.getStart(sourceFile);
|
|
236
|
+
if (!linkedPackageIntrinsicsByStart.has(start)) {
|
|
237
|
+
linkedPackageIntrinsicsByStart.set(start, Object.freeze({
|
|
238
|
+
...linked,
|
|
239
|
+
range: Object.freeze({ start, end: call.getEnd() })
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
return linked;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const start = call.getStart(sourceFile);
|
|
246
|
+
const intrinsic = packageIntrinsicsByStart.get(start);
|
|
247
|
+
if (intrinsic && !linkedPackageIntrinsicsByStart.has(start)) linkedPackageIntrinsicsByStart.set(start, intrinsic);
|
|
248
|
+
return intrinsic;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function linkPackageIntrinsicsWithin(node) {
|
|
252
|
+
if (!node) return;
|
|
253
|
+
if (ts.isCallExpression(node) && packageIntrinsicForCall(node)) return;
|
|
254
|
+
ts.forEachChild(node, linkPackageIntrinsicsWithin);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const packageResultAdaptersByStart = new Map();
|
|
258
|
+
for (const adapter of options.packageResultAdapters || []) {
|
|
259
|
+
const start = Number(adapter && adapter.range && adapter.range.start);
|
|
260
|
+
if (Number.isSafeInteger(start) && !packageResultAdaptersByStart.has(start)) packageResultAdaptersByStart.set(start, adapter);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function packageResultAdapterForCall(call) {
|
|
264
|
+
if (!call) return undefined;
|
|
265
|
+
if (typeof options.packageResultAdapterForCall === 'function') {
|
|
266
|
+
const linked = options.packageResultAdapterForCall(call);
|
|
267
|
+
if (linked) return linked;
|
|
268
|
+
}
|
|
269
|
+
return packageResultAdaptersByStart.get(call.getStart(sourceFile));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function packageVariableStatement(statement) {
|
|
273
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return undefined;
|
|
274
|
+
const declaration = statement.declarationList.declarations[0];
|
|
275
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) return undefined;
|
|
276
|
+
const call = unwrapPackageCall(declaration.initializer);
|
|
277
|
+
const effect = packageEffectForCall(call);
|
|
278
|
+
if (!effect) return undefined;
|
|
279
|
+
return Object.freeze({ statement, declaration, call, effect, variableName: declaration.name.text });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function nextEffectId(prefix) {
|
|
283
|
+
const next = (effectCounters.get(prefix) || 0) + 1;
|
|
284
|
+
effectCounters.set(prefix, next);
|
|
285
|
+
return `${prefix}-${next}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function nextFetchEffectSite(chain, grouped, groupKey) {
|
|
289
|
+
const site = Object.freeze({
|
|
290
|
+
id: nextEffectId('fetch'),
|
|
291
|
+
kind: 'fetch',
|
|
292
|
+
providerKind: 'fetch',
|
|
293
|
+
operation: 'dispatch',
|
|
294
|
+
decoder: chain.decoder || null,
|
|
295
|
+
grouped: Boolean(grouped),
|
|
296
|
+
...(groupKey === undefined ? {} : { groupKey }),
|
|
297
|
+
capability: 'fetch',
|
|
298
|
+
resource: staticResource(chain.url),
|
|
299
|
+
position: positionFor(sourceFile, chain.fetchCall)
|
|
300
|
+
});
|
|
301
|
+
effectSites.push(site);
|
|
302
|
+
return site;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function nextProviderEffectSite(providerOperation, grouped = false, groupKey) {
|
|
306
|
+
const prefix = providerOperation.providerKind === 'kv' ? `kv-${providerOperation.operation}` : providerOperation.providerKind;
|
|
307
|
+
const site = Object.freeze({
|
|
308
|
+
id: nextEffectId(prefix),
|
|
309
|
+
kind: providerOperation.kind,
|
|
310
|
+
providerKind: providerOperation.providerKind,
|
|
311
|
+
operation: providerOperation.operation,
|
|
312
|
+
grouped: Boolean(grouped),
|
|
313
|
+
...(groupKey === undefined ? {} : { groupKey }),
|
|
314
|
+
capability: providerOperation.capability,
|
|
315
|
+
resource: staticResource(providerOperation.resource),
|
|
316
|
+
position: positionFor(sourceFile, providerOperation.call)
|
|
317
|
+
});
|
|
318
|
+
effectSites.push(site);
|
|
319
|
+
return site;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function packageEffectPosition(effect, call) {
|
|
323
|
+
const start = effect && effect.loc && effect.loc.start;
|
|
324
|
+
const file = effect && effect.loc && effect.loc.file;
|
|
325
|
+
if (file && start && String(file).replace(/\\/g, '/') !== String(sourceFile.fileName).replace(/\\/g, '/')) {
|
|
326
|
+
return Object.freeze({
|
|
327
|
+
file: String(file).replace(/\\/g, '/'),
|
|
328
|
+
line: Number(start.line || 1),
|
|
329
|
+
column: Number(start.column || 1)
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return positionFor(sourceFile, call);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function nextPackageEffectSite(effect, call, grouped = false, groupKey) {
|
|
336
|
+
const prefix = String(effect.kind || 'package-effect').replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase();
|
|
337
|
+
const site = Object.freeze({
|
|
338
|
+
id: nextEffectId(prefix || 'package-effect'),
|
|
339
|
+
kind: String(effect.kind),
|
|
340
|
+
providerKind: String(effect.providerKind || 'package'),
|
|
341
|
+
operation: String(effect.operation || ''),
|
|
342
|
+
grouped: Boolean(grouped),
|
|
343
|
+
...(groupKey === undefined ? {} : { groupKey }),
|
|
344
|
+
capability: String(effect.capability || effect.kind),
|
|
345
|
+
package: String(effect.package || ''),
|
|
346
|
+
contractId: String(effect.contractId || ''),
|
|
347
|
+
payload: Object.freeze({ ...(effect.payload || {}) }),
|
|
348
|
+
result: String(effect.result || 'value'),
|
|
349
|
+
resource: effect.resource || Object.freeze({ kind: 'none' }),
|
|
350
|
+
position: packageEffectPosition(effect, call)
|
|
351
|
+
});
|
|
352
|
+
effectSites.push(site);
|
|
353
|
+
return site;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function nextContinuationSite(effectIds, kind, node) {
|
|
357
|
+
continuationIndex += 1;
|
|
358
|
+
const site = Object.freeze({
|
|
359
|
+
id: `continuation-${continuationIndex}`,
|
|
360
|
+
kind,
|
|
361
|
+
effectIds: Object.freeze(Array.from(effectIds)),
|
|
362
|
+
position: positionFor(sourceFile, node)
|
|
363
|
+
});
|
|
364
|
+
continuationSites.push(site);
|
|
365
|
+
return site;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function parallelCandidate(invocation, aliases) {
|
|
369
|
+
const { call, record } = invocation;
|
|
370
|
+
let valid = true;
|
|
371
|
+
if (call.arguments.length !== 1 || !record || !ts.isObjectLiteralExpression(record)) {
|
|
372
|
+
diagnostics.push(diagnostic(
|
|
373
|
+
sourceFile,
|
|
374
|
+
call,
|
|
375
|
+
'PULSE_PARALLEL_OBJECT_LITERAL_REQUIRED',
|
|
376
|
+
'ctx.parallel requires exactly one inline object literal of Pulse effects.'
|
|
377
|
+
));
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
if (record.properties.length === 0) {
|
|
381
|
+
diagnostics.push(diagnostic(
|
|
382
|
+
sourceFile,
|
|
383
|
+
record,
|
|
384
|
+
'PULSE_PARALLEL_EMPTY',
|
|
385
|
+
'ctx.parallel requires at least one keyed Pulse effect.'
|
|
386
|
+
));
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
const members = [];
|
|
390
|
+
const keys = new Set();
|
|
391
|
+
for (const property of record.properties) {
|
|
392
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
393
|
+
diagnostics.push(diagnostic(
|
|
394
|
+
sourceFile,
|
|
395
|
+
property,
|
|
396
|
+
'PULSE_PARALLEL_PROPERTY_STATIC_REQUIRED',
|
|
397
|
+
'ctx.parallel accepts only ordinary key: effect property assignments; spreads, shorthand properties, methods, getters, and setters are not portable.'
|
|
398
|
+
));
|
|
399
|
+
valid = false;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const key = portableParallelKey(property.name);
|
|
403
|
+
if (key === undefined) {
|
|
404
|
+
diagnostics.push(diagnostic(
|
|
405
|
+
sourceFile,
|
|
406
|
+
property.name,
|
|
407
|
+
'PULSE_PARALLEL_KEY_STATIC_REQUIRED',
|
|
408
|
+
'ctx.parallel keys must be static identifier or string-literal keys.'
|
|
409
|
+
));
|
|
410
|
+
valid = false;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (key === '__proto__' || isArrayIndexKey(key)) {
|
|
414
|
+
diagnostics.push(diagnostic(
|
|
415
|
+
sourceFile,
|
|
416
|
+
property.name,
|
|
417
|
+
'PULSE_PARALLEL_KEY_UNSUPPORTED',
|
|
418
|
+
`ctx.parallel key ${JSON.stringify(key)} is not portable; use a non-index string key.`
|
|
419
|
+
));
|
|
420
|
+
valid = false;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (keys.has(key)) {
|
|
424
|
+
diagnostics.push(diagnostic(
|
|
425
|
+
sourceFile,
|
|
426
|
+
property.name,
|
|
427
|
+
'PULSE_PARALLEL_KEY_DUPLICATE',
|
|
428
|
+
`ctx.parallel key ${JSON.stringify(key)} is duplicated.`
|
|
429
|
+
));
|
|
430
|
+
valid = false;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
keys.add(key);
|
|
434
|
+
const fetch = extractFetchChain(property.initializer, ctxName, { unwrap: true });
|
|
435
|
+
if (fetch && fetch.url) {
|
|
436
|
+
members.push(Object.freeze({ key, kind: 'fetch', property, chain: fetch }));
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const provider = extractProviderCall(property.initializer, ctxName, aliases, { unwrap: true });
|
|
440
|
+
if (provider) {
|
|
441
|
+
members.push(Object.freeze({ key, kind: 'provider', property, operation: provider }));
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const packageCall = unwrapPackageCall(property.initializer);
|
|
445
|
+
const packageEffect = packageEffectForCall(packageCall);
|
|
446
|
+
if (packageEffect && packageEffect.placement === 'statement') {
|
|
447
|
+
members.push(Object.freeze({ key, kind: 'package', property, effect: packageEffect, call: packageCall }));
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
diagnostics.push(diagnostic(
|
|
451
|
+
sourceFile,
|
|
452
|
+
property.initializer,
|
|
453
|
+
'PULSE_PARALLEL_EFFECT_REQUIRED',
|
|
454
|
+
`ctx.parallel property ${JSON.stringify(key)} must contain a directly recognizable Pulse effect expression.`
|
|
455
|
+
));
|
|
456
|
+
valid = false;
|
|
457
|
+
}
|
|
458
|
+
if (!valid || members.length !== record.properties.length) return undefined;
|
|
459
|
+
if (!invocation.discard) {
|
|
460
|
+
const binding = invocation.declaration.name;
|
|
461
|
+
if (ts.isObjectBindingPattern(binding)) {
|
|
462
|
+
const memberKeys = new Set(members.map((member) => member.key));
|
|
463
|
+
for (const element of binding.elements) {
|
|
464
|
+
const keyNode = element.propertyName || element.name;
|
|
465
|
+
const key = portableParallelKey(keyNode);
|
|
466
|
+
if (element.dotDotDotToken || element.initializer || !ts.isIdentifier(element.name) || key === undefined || !memberKeys.has(key)) {
|
|
467
|
+
diagnostics.push(diagnostic(
|
|
468
|
+
sourceFile,
|
|
469
|
+
element,
|
|
470
|
+
'PULSE_PARALLEL_BINDING_UNSUPPORTED',
|
|
471
|
+
'ctx.parallel result destructuring accepts only simple identifiers or static key aliases without defaults or rest elements.'
|
|
472
|
+
));
|
|
473
|
+
valid = false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} else if (!ts.isIdentifier(binding)) {
|
|
477
|
+
diagnostics.push(diagnostic(
|
|
478
|
+
sourceFile,
|
|
479
|
+
binding,
|
|
480
|
+
'PULSE_PARALLEL_BINDING_UNSUPPORTED',
|
|
481
|
+
'ctx.parallel results must bind to one identifier or a simple object binding pattern.'
|
|
482
|
+
));
|
|
483
|
+
valid = false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!valid) return undefined;
|
|
487
|
+
return Object.freeze({ ...invocation, members: Object.freeze(members) });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function buildStatementList(statements, inheritedAliases = new Map()) {
|
|
491
|
+
const aliases = new Map(inheritedAliases);
|
|
492
|
+
const out = [];
|
|
493
|
+
for (let index = 0; index < statements.length;) {
|
|
494
|
+
const statement = statements[index];
|
|
495
|
+
const parallelInvocation = parallelInvocationStatement(statement, ctxName);
|
|
496
|
+
if (parallelInvocation) {
|
|
497
|
+
const candidate = parallelCandidate(parallelInvocation, aliases);
|
|
498
|
+
if (!candidate) {
|
|
499
|
+
out.push(createHandlerOperation('source-statement', { statement, role: 'parallel-rejected' }));
|
|
500
|
+
index += 1;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const sites = Object.freeze(candidate.members.map((member) => {
|
|
504
|
+
if (member.kind === 'fetch') return nextFetchEffectSite(member.chain, true, member.key);
|
|
505
|
+
if (member.kind === 'provider') return nextProviderEffectSite(member.operation, true, member.key);
|
|
506
|
+
return nextPackageEffectSite(member.effect, member.call, true, member.key);
|
|
507
|
+
}));
|
|
508
|
+
const continuation = nextContinuationSite(sites.map((site) => site.id), 'parallel-group', candidate.call);
|
|
509
|
+
out.push(createHandlerOperation('parallel-group', { candidate, sites, continuation }));
|
|
510
|
+
index += 1;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (ts.isExpressionStatement(statement)) {
|
|
514
|
+
const packageCall = unwrapPackageCall(statement.expression);
|
|
515
|
+
const packageEffect = packageEffectForCall(packageCall);
|
|
516
|
+
if (packageEffect) {
|
|
517
|
+
if (packageEffect.placement !== 'statement') diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-source', issue: 'effect.package.position', sourceFile, node: packageCall, values: { kind: packageEffect.kind, placement: 'statement' } }));
|
|
518
|
+
const site = nextPackageEffectSite(packageEffect, packageCall);
|
|
519
|
+
const continuation = nextContinuationSite([site.id], site.kind, statement);
|
|
520
|
+
out.push(createHandlerOperation('package-effect', { statement, call: packageCall, effect: packageEffect, site, continuation, returnResult: false }));
|
|
521
|
+
index += 1;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const namespace = extractKvNamespaceDeclaration(statement, ctxName);
|
|
526
|
+
if (namespace) {
|
|
527
|
+
aliases.set(namespace.variableName, namespace.store);
|
|
528
|
+
out.push(createHandlerOperation('source-statement', { statement, role: 'kv-namespace' }));
|
|
529
|
+
index += 1;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const packageVariable = packageVariableStatement(statement);
|
|
534
|
+
if (packageVariable) {
|
|
535
|
+
if (packageVariable.effect.placement !== 'variable') diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-source', issue: 'effect.package.position', sourceFile, node: packageVariable.call, values: { kind: packageVariable.effect.kind, placement: 'variable' } }));
|
|
536
|
+
const site = nextPackageEffectSite(packageVariable.effect, packageVariable.call);
|
|
537
|
+
const continuation = nextContinuationSite([site.id], site.kind, packageVariable.statement);
|
|
538
|
+
out.push(createHandlerOperation('package-variable', { candidate: packageVariable, site, continuation }));
|
|
539
|
+
index += 1;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const firstFetch = fetchVariableStatement(statement, ctxName);
|
|
544
|
+
if (firstFetch) {
|
|
545
|
+
const candidates = [firstFetch];
|
|
546
|
+
let next = index + 1;
|
|
547
|
+
while (next < statements.length) {
|
|
548
|
+
const candidate = fetchVariableStatement(statements[next], ctxName);
|
|
549
|
+
if (!candidate) break;
|
|
550
|
+
candidates.push(candidate);
|
|
551
|
+
next += 1;
|
|
552
|
+
}
|
|
553
|
+
if (candidates.length > 1 && groupIsIndependent(candidates)) {
|
|
554
|
+
const sites = Object.freeze(candidates.map((candidate) => nextFetchEffectSite(candidate.chain, true)));
|
|
555
|
+
const continuation = nextContinuationSite(sites.map((site) => site.id), 'fetch-group', candidates[0].statement);
|
|
556
|
+
out.push(createHandlerOperation('fetch-group', { candidates: Object.freeze(candidates), sites, continuation }));
|
|
557
|
+
} else {
|
|
558
|
+
for (const candidate of candidates) {
|
|
559
|
+
const site = nextFetchEffectSite(candidate.chain, false);
|
|
560
|
+
const continuation = nextContinuationSite([site.id], 'single-fetch', candidate.statement);
|
|
561
|
+
out.push(createHandlerOperation('fetch-single', { candidate, site, continuation }));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
index = next;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const providerDeclaration = providerVariableStatement(statement, ctxName, aliases);
|
|
569
|
+
if (providerDeclaration) {
|
|
570
|
+
const site = nextProviderEffectSite(providerDeclaration.operation);
|
|
571
|
+
const continuation = nextContinuationSite([site.id], site.kind, providerDeclaration.statement);
|
|
572
|
+
out.push(createHandlerOperation('provider-variable', { candidate: providerDeclaration, site, continuation }));
|
|
573
|
+
index += 1;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const providerStatement = providerExpressionStatement(statement, ctxName, aliases);
|
|
577
|
+
if (providerStatement) {
|
|
578
|
+
const site = nextProviderEffectSite(providerStatement.operation);
|
|
579
|
+
const continuation = nextContinuationSite([site.id], site.kind, providerStatement.statement);
|
|
580
|
+
out.push(createHandlerOperation('provider-expression', { candidate: providerStatement, site, continuation }));
|
|
581
|
+
index += 1;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
out.push(buildStatement(statement, aliases));
|
|
585
|
+
index += 1;
|
|
586
|
+
}
|
|
587
|
+
return Object.freeze(out.flat().filter(Boolean));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function buildStatement(statement, aliases) {
|
|
591
|
+
if (ts.isBlock(statement)) return createHandlerOperation('block', { statement, statements: buildStatementList(statement.statements, aliases) });
|
|
592
|
+
if (ts.isIfStatement(statement)) {
|
|
593
|
+
const thenOperation = buildStatement(statement.thenStatement, new Map(aliases));
|
|
594
|
+
const elseOperation = statement.elseStatement ? buildStatement(statement.elseStatement, new Map(aliases)) : undefined;
|
|
595
|
+
return createHandlerOperation('if', { statement, expression: statement.expression, thenOperation, elseOperation });
|
|
596
|
+
}
|
|
597
|
+
if (ts.isReturnStatement(statement) && statement.expression) {
|
|
598
|
+
const packageCall = unwrapPackageCall(statement.expression);
|
|
599
|
+
const resultAdapter = packageResultAdapterForCall(packageCall);
|
|
600
|
+
if (resultAdapter) {
|
|
601
|
+
const argument = packageCall.arguments && packageCall.arguments[0];
|
|
602
|
+
if (!argument || !ts.isIdentifier(argument)) {
|
|
603
|
+
diagnostics.push(diagnostic(sourceFile, packageCall, 'PULSE_PACKAGE_RESULT_ADAPTER_INPUT_UNSUPPORTED', 'Package result adapters currently require a directly bound effect result identifier.'));
|
|
604
|
+
return createHandlerOperation('source-statement', { statement, role: 'package-result-adapter-rejected' });
|
|
605
|
+
}
|
|
606
|
+
return createHandlerOperation('package-result-adapter', { statement, call: packageCall, adapter: resultAdapter, resultExpression: argument });
|
|
607
|
+
}
|
|
608
|
+
const packageEffect = packageEffectForCall(packageCall);
|
|
609
|
+
if (packageEffect) {
|
|
610
|
+
if (packageEffect.placement !== 'return') diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-source', issue: 'effect.package.position', sourceFile, node: packageCall, values: { kind: packageEffect.kind, placement: 'return' } }));
|
|
611
|
+
const site = nextPackageEffectSite(packageEffect, packageCall);
|
|
612
|
+
const continuation = nextContinuationSite([site.id], site.kind, statement);
|
|
613
|
+
return createHandlerOperation('package-effect', { statement, call: packageCall, effect: packageEffect, site, continuation, returnResult: true });
|
|
614
|
+
}
|
|
615
|
+
const chain = extractFetchChain(statement.expression, ctxName);
|
|
616
|
+
if (chain) {
|
|
617
|
+
if (chain.decoder) {
|
|
618
|
+
diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-source', issue: 'effect.fetch.value-return', sourceFile, node: statement }));
|
|
619
|
+
return createHandlerOperation('source-statement', { statement, role: 'fetch-value-return-rejected' });
|
|
620
|
+
}
|
|
621
|
+
const site = nextFetchEffectSite(chain, false);
|
|
622
|
+
const continuation = nextContinuationSite([site.id], 'opaque-fetch-return', statement);
|
|
623
|
+
return createHandlerOperation('opaque-fetch-return', { statement, chain, site, continuation });
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (ts.isTryStatement(statement) || ts.isForStatement(statement) || ts.isForOfStatement(statement) || ts.isWhileStatement(statement) || ts.isDoStatement(statement) || ts.isSwitchStatement(statement)) {
|
|
627
|
+
diagnostics.push(diagnostic(sourceFile, statement, 'PULSE_CANONICAL_CONTROL_FLOW_UNSUPPORTED', 'The current canonical lowering supports straight-line code and if/else branching; loops, switch, and try/catch are reserved.'));
|
|
628
|
+
}
|
|
629
|
+
return createHandlerOperation('source-statement', { statement, role: 'preserved-source' });
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const originalBody = ts.isBlock(handler.body) ? handler.body : ts.factory.createBlock([ts.factory.createReturnStatement(handler.body)], true);
|
|
633
|
+
const body = createHandlerOperation('block', { statement: originalBody, statements: buildStatementList(originalBody.statements) });
|
|
634
|
+
|
|
635
|
+
function scanUnsupportedCalls(node, aliases) {
|
|
636
|
+
if (ts.isCallExpression(node) && packageIntrinsicForCall(node)) return;
|
|
637
|
+
const surface = recognizeHandlerSurface(node, {
|
|
638
|
+
ctxName,
|
|
639
|
+
kvAliases: aliases,
|
|
640
|
+
packageEffectForCall
|
|
641
|
+
});
|
|
642
|
+
if (surface && surface.surfaceId === 'package.operation') return;
|
|
643
|
+
if (surface && surface.surfaceId === 'ctx.parallel') {
|
|
644
|
+
diagnostics.push(diagnostic(
|
|
645
|
+
sourceFile,
|
|
646
|
+
node,
|
|
647
|
+
'PULSE_PARALLEL_POSITION_UNSUPPORTED',
|
|
648
|
+
'ctx.parallel must be awaited as a standalone variable declaration or expression statement.'
|
|
649
|
+
));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (surface && ['ctx.fetch.opaque-return', 'ctx.fetch.projected'].includes(surface.surfaceId)
|
|
653
|
+
&& surface.detail.fetch.fetchCall === node) {
|
|
654
|
+
diagnostics.push(createHandlerDiagnostic({ frontend: 'canonical-source', issue: 'effect.fetch.position', sourceFile, node }));
|
|
655
|
+
}
|
|
656
|
+
if (surface && surface.detail.provider) {
|
|
657
|
+
diagnostics.push(createHandlerDiagnostic({
|
|
658
|
+
frontend: 'canonical-source',
|
|
659
|
+
issue: 'effect.provider.position',
|
|
660
|
+
sourceFile,
|
|
661
|
+
node,
|
|
662
|
+
values: { kind: surface.detail.provider.kind }
|
|
663
|
+
}));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
ts.forEachChild(node, (child) => scanUnsupportedCalls(child, aliases));
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function scanOriginalStatements(statements, inheritedAliases = new Map()) {
|
|
670
|
+
const aliases = new Map(inheritedAliases);
|
|
671
|
+
for (let index = 0; index < statements.length; index += 1) {
|
|
672
|
+
const statement = statements[index];
|
|
673
|
+
const namespace = extractKvNamespaceDeclaration(statement, ctxName);
|
|
674
|
+
if (namespace) { aliases.set(namespace.variableName, namespace.store); continue; }
|
|
675
|
+
if (parallelInvocationStatement(statement, ctxName)) continue;
|
|
676
|
+
if (packageVariableStatement(statement)) continue;
|
|
677
|
+
if (fetchVariableStatement(statement, ctxName)) continue;
|
|
678
|
+
if (providerVariableStatement(statement, ctxName, aliases)) continue;
|
|
679
|
+
if (providerExpressionStatement(statement, ctxName, aliases)) continue;
|
|
680
|
+
if (ts.isExpressionStatement(statement) && packageEffectForCall(unwrapPackageCall(statement.expression))) continue;
|
|
681
|
+
if (ts.isReturnStatement(statement) && packageResultAdapterForCall(unwrapPackageCall(statement.expression))) continue;
|
|
682
|
+
if (ts.isReturnStatement(statement) && packageEffectForCall(unwrapPackageCall(statement.expression))) continue;
|
|
683
|
+
if (ts.isReturnStatement(statement) && extractFetchChain(statement.expression, ctxName)) continue;
|
|
684
|
+
if (ts.isBlock(statement)) { scanOriginalStatements(statement.statements, aliases); continue; }
|
|
685
|
+
if (ts.isIfStatement(statement)) {
|
|
686
|
+
scanOriginalStatements(ts.isBlock(statement.thenStatement) ? statement.thenStatement.statements : [statement.thenStatement], aliases);
|
|
687
|
+
if (statement.elseStatement) scanOriginalStatements(ts.isBlock(statement.elseStatement) ? statement.elseStatement.statements : [statement.elseStatement], aliases);
|
|
688
|
+
scanUnsupportedCalls(statement.expression, aliases);
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
scanUnsupportedCalls(statement, aliases);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
scanOriginalStatements(originalBody.statements);
|
|
695
|
+
|
|
696
|
+
const operationSummary = summarizeHandlerOperation(body);
|
|
697
|
+
|
|
698
|
+
return Object.freeze({
|
|
699
|
+
version: HANDLER_IR_VERSION,
|
|
700
|
+
kind: HANDLER_IR_KIND,
|
|
701
|
+
frontend: frontend.frontend,
|
|
702
|
+
file: frontend.fileName,
|
|
703
|
+
sourceFile,
|
|
704
|
+
handler,
|
|
705
|
+
ctxName,
|
|
706
|
+
body,
|
|
707
|
+
analysis,
|
|
708
|
+
schemaBundle,
|
|
709
|
+
effectSites: Object.freeze(effectSites),
|
|
710
|
+
continuationSites: Object.freeze(continuationSites),
|
|
711
|
+
packageEffects: Object.freeze([...(options.packageEffects || [])]),
|
|
712
|
+
packageIntrinsics: Object.freeze(
|
|
713
|
+
typeof options.packageIntrinsicForCall === 'function'
|
|
714
|
+
? [...linkedPackageIntrinsicsByStart.values()]
|
|
715
|
+
: [...(options.packageIntrinsics || [])]
|
|
716
|
+
),
|
|
717
|
+
packageResultAdapters: Object.freeze([...(options.packageResultAdapters || [])]),
|
|
718
|
+
summary: Object.freeze({
|
|
719
|
+
operationCount: operationSummary.operationCount,
|
|
720
|
+
operationKinds: operationSummary.operationKinds,
|
|
721
|
+
effectCount: effectSites.length,
|
|
722
|
+
continuationCount: continuationSites.length
|
|
723
|
+
})
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function buildRouterHandlerIr(frontend) {
|
|
728
|
+
if (!frontend || frontend.frontend !== 'canonical-router-handler') {
|
|
729
|
+
throw new TypeError('buildRouterHandlerIr requires a prepared canonical-router-handler frontend.');
|
|
730
|
+
}
|
|
731
|
+
const operationSummary = summarizeHandlerOperation(frontend.body);
|
|
732
|
+
return Object.freeze({
|
|
733
|
+
version: HANDLER_IR_VERSION,
|
|
734
|
+
kind: ROUTER_HANDLER_IR_KIND,
|
|
735
|
+
frontend: frontend.frontend,
|
|
736
|
+
file: frontend.fileName,
|
|
737
|
+
sourceFile: frontend.sourceFile,
|
|
738
|
+
handler: frontend.functionNode,
|
|
739
|
+
ctxName: frontend.signature.ctxName || 'ctx',
|
|
740
|
+
nextName: frontend.signature.nextName,
|
|
741
|
+
errorName: frontend.signature.errorName,
|
|
742
|
+
role: frontend.role,
|
|
743
|
+
transferFlag: frontend.transferFlag,
|
|
744
|
+
routerEntry: frontend.entry,
|
|
745
|
+
route: frontend.route,
|
|
746
|
+
body: frontend.body,
|
|
747
|
+
analysis: frontend.analysis,
|
|
748
|
+
schemaBundle: frontend.schemaBundle,
|
|
749
|
+
effectSites: Object.freeze([]),
|
|
750
|
+
continuationSites: Object.freeze([]),
|
|
751
|
+
packageEffects: Object.freeze([]),
|
|
752
|
+
packageResultAdapters: Object.freeze([]),
|
|
753
|
+
summary: Object.freeze({
|
|
754
|
+
operationCount: operationSummary.operationCount,
|
|
755
|
+
operationKinds: operationSummary.operationKinds,
|
|
756
|
+
effectCount: 0,
|
|
757
|
+
continuationCount: 0,
|
|
758
|
+
flow: frontend.flow
|
|
759
|
+
})
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function handlerIrSnapshot(ir) {
|
|
764
|
+
if (!ir || ir.version !== HANDLER_IR_VERSION) return undefined;
|
|
765
|
+
return Object.freeze({
|
|
766
|
+
version: ir.version,
|
|
767
|
+
kind: ir.kind,
|
|
768
|
+
frontend: ir.frontend,
|
|
769
|
+
file: ir.file,
|
|
770
|
+
ctxName: ir.ctxName,
|
|
771
|
+
summary: ir.summary,
|
|
772
|
+
analysis: Object.freeze({
|
|
773
|
+
capabilities: ir.analysis.capabilities,
|
|
774
|
+
fetchCount: ir.analysis.fetchCount,
|
|
775
|
+
schemaReferences: ir.analysis.schemaReferences,
|
|
776
|
+
providerOperations: ir.analysis.providerOperations
|
|
777
|
+
}),
|
|
778
|
+
effectSites: ir.effectSites,
|
|
779
|
+
continuationSites: ir.continuationSites
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
module.exports = Object.freeze({
|
|
784
|
+
HANDLER_IR_VERSION,
|
|
785
|
+
HANDLER_IR_KIND,
|
|
786
|
+
ROUTER_HANDLER_IR_KIND,
|
|
787
|
+
HANDLER_IR_OPERATION_KINDS,
|
|
788
|
+
HANDLER_IR_EXTENSION_OPERATION_KINDS,
|
|
789
|
+
ROUTER_HANDLER_IR_OPERATION_KINDS,
|
|
790
|
+
ALL_HANDLER_IR_OPERATION_KINDS,
|
|
791
|
+
staticResource,
|
|
792
|
+
createHandlerOperation,
|
|
793
|
+
summarizeHandlerOperation,
|
|
794
|
+
buildPlainHandlerIr,
|
|
795
|
+
buildRouterHandlerIr,
|
|
796
|
+
handlerIrSnapshot
|
|
797
|
+
});
|