@seljs/runtime 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  3. package/dist/analysis/call-collector.cjs +233 -0
  4. package/dist/analysis/call-collector.mjs +232 -0
  5. package/dist/analysis/dependency-analyzer.cjs +68 -0
  6. package/dist/analysis/dependency-analyzer.mjs +68 -0
  7. package/dist/analysis/round-planner.cjs +65 -0
  8. package/dist/analysis/round-planner.mjs +65 -0
  9. package/dist/analysis/types.d.cts +115 -0
  10. package/dist/analysis/types.d.mts +115 -0
  11. package/dist/debug.cjs +17 -0
  12. package/dist/debug.mjs +15 -0
  13. package/dist/environment/context.cjs +11 -0
  14. package/dist/environment/context.mjs +11 -0
  15. package/dist/environment/contract-caller.cjs +81 -0
  16. package/dist/environment/contract-caller.mjs +77 -0
  17. package/dist/environment/environment.cjs +254 -0
  18. package/dist/environment/environment.d.cts +84 -0
  19. package/dist/environment/environment.d.mts +84 -0
  20. package/dist/environment/environment.mjs +252 -0
  21. package/dist/environment/error-wrapper.cjs +29 -0
  22. package/dist/environment/error-wrapper.mjs +27 -0
  23. package/dist/environment/index.cjs +1 -0
  24. package/dist/environment/index.d.mts +2 -0
  25. package/dist/environment/index.mjs +2 -0
  26. package/dist/environment/replay-cache.cjs +48 -0
  27. package/dist/environment/replay-cache.mjs +47 -0
  28. package/dist/environment/types.d.cts +60 -0
  29. package/dist/environment/types.d.mts +60 -0
  30. package/dist/errors/errors.cjs +68 -0
  31. package/dist/errors/errors.d.cts +64 -0
  32. package/dist/errors/errors.d.mts +64 -0
  33. package/dist/errors/errors.mjs +63 -0
  34. package/dist/errors/index.cjs +3 -0
  35. package/dist/errors/index.d.mts +1 -0
  36. package/dist/errors/index.mjs +2 -0
  37. package/dist/execution/multi-round-executor.cjs +45 -0
  38. package/dist/execution/multi-round-executor.mjs +44 -0
  39. package/dist/execution/multicall-batcher.cjs +51 -0
  40. package/dist/execution/multicall-batcher.mjs +50 -0
  41. package/dist/execution/multicall.cjs +39 -0
  42. package/dist/execution/multicall.mjs +38 -0
  43. package/dist/execution/result-cache.cjs +63 -0
  44. package/dist/execution/result-cache.mjs +63 -0
  45. package/dist/execution/round-executor.cjs +81 -0
  46. package/dist/execution/round-executor.mjs +80 -0
  47. package/dist/execution/types.d.cts +58 -0
  48. package/dist/execution/types.d.mts +58 -0
  49. package/dist/factory.cjs +6 -0
  50. package/dist/factory.d.cts +7 -0
  51. package/dist/factory.d.mts +6 -0
  52. package/dist/factory.mjs +6 -0
  53. package/dist/index.cjs +18 -0
  54. package/dist/index.d.cts +7 -0
  55. package/dist/index.d.mts +7 -0
  56. package/dist/index.mjs +6 -0
  57. package/package.json +26 -19
  58. package/dist/analysis/call-collector.d.ts +0 -20
  59. package/dist/analysis/call-collector.d.ts.map +0 -1
  60. package/dist/analysis/call-collector.js +0 -272
  61. package/dist/analysis/dependency-analyzer.d.ts +0 -14
  62. package/dist/analysis/dependency-analyzer.d.ts.map +0 -1
  63. package/dist/analysis/dependency-analyzer.js +0 -76
  64. package/dist/analysis/index.d.ts +0 -2
  65. package/dist/analysis/index.d.ts.map +0 -1
  66. package/dist/analysis/index.js +0 -1
  67. package/dist/analysis/round-planner.d.ts +0 -32
  68. package/dist/analysis/round-planner.d.ts.map +0 -1
  69. package/dist/analysis/round-planner.js +0 -69
  70. package/dist/analysis/types.d.ts +0 -113
  71. package/dist/analysis/types.d.ts.map +0 -1
  72. package/dist/analysis/types.js +0 -1
  73. package/dist/debug.d.ts +0 -13
  74. package/dist/debug.d.ts.map +0 -1
  75. package/dist/debug.js +0 -12
  76. package/dist/environment/context.d.ts +0 -3
  77. package/dist/environment/context.d.ts.map +0 -1
  78. package/dist/environment/context.js +0 -8
  79. package/dist/environment/contract-caller.d.ts +0 -25
  80. package/dist/environment/contract-caller.d.ts.map +0 -1
  81. package/dist/environment/contract-caller.js +0 -85
  82. package/dist/environment/environment.d.ts +0 -81
  83. package/dist/environment/environment.d.ts.map +0 -1
  84. package/dist/environment/environment.js +0 -279
  85. package/dist/environment/error-wrapper.d.ts +0 -11
  86. package/dist/environment/error-wrapper.d.ts.map +0 -1
  87. package/dist/environment/error-wrapper.js +0 -33
  88. package/dist/environment/index.d.ts +0 -3
  89. package/dist/environment/index.d.ts.map +0 -1
  90. package/dist/environment/index.js +0 -2
  91. package/dist/environment/replay-cache.d.ts +0 -23
  92. package/dist/environment/replay-cache.d.ts.map +0 -1
  93. package/dist/environment/replay-cache.js +0 -51
  94. package/dist/environment/types.d.ts +0 -57
  95. package/dist/environment/types.d.ts.map +0 -1
  96. package/dist/environment/types.js +0 -1
  97. package/dist/errors/errors.d.ts +0 -63
  98. package/dist/errors/errors.d.ts.map +0 -1
  99. package/dist/errors/errors.js +0 -63
  100. package/dist/errors/index.d.ts +0 -2
  101. package/dist/errors/index.d.ts.map +0 -1
  102. package/dist/errors/index.js +0 -1
  103. package/dist/execution/index.d.ts +0 -2
  104. package/dist/execution/index.d.ts.map +0 -1
  105. package/dist/execution/index.js +0 -1
  106. package/dist/execution/multi-round-executor.d.ts +0 -17
  107. package/dist/execution/multi-round-executor.d.ts.map +0 -1
  108. package/dist/execution/multi-round-executor.js +0 -47
  109. package/dist/execution/multicall-batcher.d.ts +0 -14
  110. package/dist/execution/multicall-batcher.d.ts.map +0 -1
  111. package/dist/execution/multicall-batcher.js +0 -53
  112. package/dist/execution/multicall.d.ts +0 -42
  113. package/dist/execution/multicall.d.ts.map +0 -1
  114. package/dist/execution/multicall.js +0 -29
  115. package/dist/execution/result-cache.d.ts +0 -47
  116. package/dist/execution/result-cache.d.ts.map +0 -1
  117. package/dist/execution/result-cache.js +0 -65
  118. package/dist/execution/round-executor.d.ts +0 -18
  119. package/dist/execution/round-executor.d.ts.map +0 -1
  120. package/dist/execution/round-executor.js +0 -95
  121. package/dist/execution/types.d.ts +0 -55
  122. package/dist/execution/types.d.ts.map +0 -1
  123. package/dist/execution/types.js +0 -1
  124. package/dist/factory.d.ts +0 -3
  125. package/dist/factory.d.ts.map +0 -1
  126. package/dist/factory.js +0 -2
  127. package/dist/index.d.ts +0 -10
  128. package/dist/index.d.ts.map +0 -1
  129. package/dist/index.js +0 -7
package/package.json CHANGED
@@ -1,30 +1,36 @@
1
1
  {
2
2
  "name": "@seljs/runtime",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
+ "repository": {
5
+ "url": "https://github.com/abinnovision/seljs"
6
+ },
4
7
  "license": "Apache-2.0",
5
8
  "author": {
6
9
  "name": "abi group GmbH",
7
10
  "email": "info@abigroup.io",
8
11
  "url": "https://abigroup.io/"
9
12
  },
10
- "repository": {
11
- "url": "https://github.com/abinnovision/seljs"
12
- },
13
13
  "type": "module",
14
14
  "exports": {
15
15
  ".": {
16
- "import": "./dist/index.js",
17
- "types": "./dist/index.d.ts"
16
+ "import": {
17
+ "types": "./dist/index.d.mts",
18
+ "default": "./dist/index.mjs"
19
+ },
20
+ "require": {
21
+ "types": "./dist/index.d.cts",
22
+ "default": "./dist/index.cjs"
23
+ }
18
24
  }
19
25
  },
20
- "main": "./dist/index.js",
21
- "types": "./dist/index.d.ts",
26
+ "main": "./dist/index.cjs",
27
+ "types": "./dist/index.d.cts",
22
28
  "files": [
23
29
  "dist",
24
30
  "LICENSE.md"
25
31
  ],
26
32
  "scripts": {
27
- "build": "tsc -p tsconfig.build.json",
33
+ "build": "tsdown",
28
34
  "format:check": "prettier --check '{{src,test}/**/*,*}.{{t,j}s{,x},json{,5},md,y{,a}ml}'",
29
35
  "format:fix": "prettier --write '{{src,test}/**/*,*}.{{t,j}s{,x},json{,5},md,y{,a}ml}'",
30
36
  "lint:check": "eslint '{{src,test}/**/*,*}.{t,j}s{,x}'",
@@ -34,16 +40,6 @@
34
40
  "test-unit:watch": "vitest watch",
35
41
  "typecheck": "tsc --noEmit"
36
42
  },
37
- "dependencies": {
38
- "@marcbachmann/cel-js": "^7.5.2",
39
- "@seljs/checker": "1.0.0",
40
- "@seljs/common": "1.0.0",
41
- "@seljs/env": "1.0.0",
42
- "@seljs/schema": "1.0.0",
43
- "@seljs/types": "1.0.0",
44
- "debug": "^4.4.3",
45
- "viem": "^2.47.2"
46
- },
47
43
  "lint-staged": {
48
44
  "{{src,test}/**/*,*}.{{t,j}s{,x},json{,5},md,y{,a}ml}": [
49
45
  "prettier --write"
@@ -52,6 +48,16 @@
52
48
  "eslint --fix"
53
49
  ]
54
50
  },
51
+ "dependencies": {
52
+ "@marcbachmann/cel-js": "^7.5.3",
53
+ "@seljs/checker": "1.0.1",
54
+ "@seljs/common": "1.0.1",
55
+ "@seljs/env": "1.0.1",
56
+ "@seljs/schema": "1.0.1",
57
+ "@seljs/types": "1.0.1",
58
+ "debug": "^4.4.3",
59
+ "viem": "^2.47.2"
60
+ },
55
61
  "devDependencies": {
56
62
  "@abinnovision/eslint-config-base": "^3.2.0",
57
63
  "@abinnovision/prettier-config": "^2.1.5",
@@ -61,6 +67,7 @@
61
67
  "concurrently": "^9.2.1",
62
68
  "eslint": "^9.39.4",
63
69
  "prettier": "^3.8.1",
70
+ "tsdown": "^0.21.3",
64
71
  "typescript": "^5.9.3",
65
72
  "vitest": "^4.0.18",
66
73
  "zod": "^4.3.6"
@@ -1,20 +0,0 @@
1
- import type { CollectedCall } from "./types.js";
2
- import type { ASTNode } from "@marcbachmann/cel-js";
3
- /** Minimal interface required by collectCalls for contract lookup. */
4
- export interface ContractLookup {
5
- get: (name: string) => unknown;
6
- }
7
- /**
8
- * Collects contract calls from a CEL AST by traversing the tree.
9
- *
10
- * Walks the AST looking for `rcall` nodes whose receiver matches a registered
11
- * contract. For each matched call, classifies arguments as literals, variable
12
- * references, or results of other contract calls (creating dependency edges).
13
- * Skips `cel.bind()` calls while still collecting any nested contract calls.
14
- *
15
- * @param ast The root AST node from a parsed CEL expression
16
- * @param registry Contract lookup to validate receiver names against
17
- * @returns Array of collected calls with dependency information
18
- */
19
- export declare const collectCalls: (ast: ASTNode, registry: ContractLookup) => CollectedCall[];
20
- //# sourceMappingURL=call-collector.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"call-collector.d.ts","sourceRoot":"","sources":["../../src/analysis/call-collector.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/B;AAwHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GACxB,KAAK,OAAO,EACZ,UAAU,cAAc,KACtB,aAAa,EA2Nf,CAAC"}
@@ -1,272 +0,0 @@
1
- import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "@seljs/checker";
2
- import { isAstNode } from "@seljs/common";
3
- import { createLogger } from "../debug.js";
4
- const debug = createLogger("analysis:collect");
5
- /**
6
- * Generates a deterministic call identifier from contract name, method, and arguments.
7
- *
8
- * Format: "contract:method:arg1,arg2,..."
9
- * Used for deduplication and dependency tracking between calls.
10
- */
11
- const generateCallId = (contract, method, args) => {
12
- const argKey = args
13
- .map((arg) => {
14
- if (arg.variableName !== undefined) {
15
- return arg.variableName;
16
- }
17
- if (arg.value !== undefined) {
18
- const v = arg.value;
19
- return typeof v === "string" ||
20
- typeof v === "number" ||
21
- typeof v === "boolean" ||
22
- typeof v === "bigint"
23
- ? String(v)
24
- : "?";
25
- }
26
- if (arg.dependsOnCallId !== undefined) {
27
- return arg.dependsOnCallId;
28
- }
29
- return "?";
30
- })
31
- .join(",");
32
- return `${contract}:${method}:${argKey}`;
33
- };
34
- /**
35
- * Attempts to extract a scalar argument from a Solidity cast/wrapper call
36
- * (e.g. `uint256(42)`, `solAddress("0x...")`, `int256(tokenId)`).
37
- *
38
- * Handles both literal values and variable references.
39
- * Returns undefined if the node is not a recognized scalar wrapper.
40
- */
41
- const collectScalarArgument = (argNode) => {
42
- if (!isAstNode(argNode) || argNode.op !== "call") {
43
- return undefined;
44
- }
45
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
46
- if (!Array.isArray(argNode.args) || argNode.args.length !== 2) {
47
- return undefined;
48
- }
49
- const fnName = argNode.args[0];
50
- const fnArgs = argNode.args[1];
51
- if (typeof fnName !== "string" ||
52
- !Array.isArray(fnArgs) ||
53
- fnArgs.length !== 1 ||
54
- !SCALAR_WRAPPER_FUNCTIONS.has(fnName)) {
55
- return undefined;
56
- }
57
- const inner = fnArgs[0];
58
- if (!isAstNode(inner)) {
59
- return undefined;
60
- }
61
- if (inner.op === "value") {
62
- return { type: "literal", value: inner.args };
63
- }
64
- if (inner.op === "id" && typeof inner.args === "string") {
65
- return { type: "variable", variableName: inner.args };
66
- }
67
- return undefined;
68
- };
69
- /**
70
- * Extracts the method name, receiver identifier, and argument nodes from an rcall AST node.
71
- *
72
- * An rcall node has the structure: `["methodName", receiverNode, [argNodes...]]`
73
- */
74
- const getRCallArgs = (node) => {
75
- if (!Array.isArray(node.args) || node.args.length < 3) {
76
- return undefined;
77
- }
78
- const methodNode = node.args[0];
79
- const receiverNode = node.args[1];
80
- const argNodes = node.args[2];
81
- if (typeof methodNode !== "string" || !Array.isArray(argNodes)) {
82
- return undefined;
83
- }
84
- let receiverName;
85
- if (isAstNode(receiverNode) &&
86
- receiverNode.op === "id" &&
87
- typeof receiverNode.args === "string") {
88
- receiverName = receiverNode.args;
89
- }
90
- return { method: methodNode, receiverName, args: argNodes };
91
- };
92
- /**
93
- * Collects contract calls from a CEL AST by traversing the tree.
94
- *
95
- * Walks the AST looking for `rcall` nodes whose receiver matches a registered
96
- * contract. For each matched call, classifies arguments as literals, variable
97
- * references, or results of other contract calls (creating dependency edges).
98
- * Skips `cel.bind()` calls while still collecting any nested contract calls.
99
- *
100
- * @param ast The root AST node from a parsed CEL expression
101
- * @param registry Contract lookup to validate receiver names against
102
- * @returns Array of collected calls with dependency information
103
- */
104
- export const collectCalls = (ast, registry) => {
105
- const calls = [];
106
- /** Variable names that are scoped (comprehension iteration vars, cel.bind vars) — NOT user context */
107
- const scopedVars = new Set();
108
- /** rcall AST nodes skipped because they have unresolvable args */
109
- const deferredNodes = new WeakSet();
110
- const traverse = (node) => {
111
- if (Array.isArray(node)) {
112
- for (const item of node) {
113
- traverse(item);
114
- }
115
- return;
116
- }
117
- if (!isAstNode(node)) {
118
- return;
119
- }
120
- if (node.op === "rcall") {
121
- collectRCall(node);
122
- return;
123
- }
124
- traverse(node.args);
125
- };
126
- /**
127
- * Handles cel.bind() — registers the bind variable name for the body scope,
128
- * traverses the initializer and body, then removes the scoped var.
129
- */
130
- const handleCelBind = (args) => {
131
- if (args.length >= 3) {
132
- const nameNode = args[0];
133
- if (isAstNode(nameNode) &&
134
- nameNode.op === "id" &&
135
- typeof nameNode.args === "string") {
136
- traverse(args[1]);
137
- scopedVars.add(nameNode.args);
138
- traverse(args[2]);
139
- scopedVars.delete(nameNode.args);
140
- return;
141
- }
142
- }
143
- for (const argNode of args) {
144
- traverse(argNode);
145
- }
146
- };
147
- /**
148
- * Handles rcall nodes whose receiver is NOT a registered contract.
149
- * Traverses the receiver node and args, registering comprehension
150
- * iteration variables for their body scope.
151
- */
152
- const handleNonContractRCall = (node, method, args) => {
153
- // Always traverse the receiver to collect contract calls within it
154
- if (Array.isArray(node.args) && node.args.length >= 2) {
155
- traverse(node.args[1]);
156
- }
157
- // Comprehension macros: register iteration variable for body scope
158
- if (COMPREHENSION_MACROS.has(method) && args.length >= 2) {
159
- const iterVarNode = args[0];
160
- if (isAstNode(iterVarNode) &&
161
- iterVarNode.op === "id" &&
162
- typeof iterVarNode.args === "string") {
163
- scopedVars.add(iterVarNode.args);
164
- for (let i = 1; i < args.length; i++) {
165
- traverse(args[i]);
166
- }
167
- scopedVars.delete(iterVarNode.args);
168
- return;
169
- }
170
- }
171
- for (const argNode of args) {
172
- traverse(argNode);
173
- }
174
- };
175
- /**
176
- * Classifies a single argument node, returning the resolved CallArgument
177
- * or `undefined` if unresolvable. When `undefined` is returned, the caller
178
- * should mark the parent call as deferred.
179
- */
180
- const classifyArg = (argNode) => {
181
- const scalarArg = collectScalarArgument(argNode);
182
- if (scalarArg) {
183
- return { arg: scalarArg, traversed: false };
184
- }
185
- if (isAstNode(argNode) && argNode.op === "value") {
186
- return {
187
- arg: { type: "literal", value: argNode.args },
188
- traversed: false,
189
- };
190
- }
191
- if (isAstNode(argNode) &&
192
- argNode.op === "id" &&
193
- typeof argNode.args === "string") {
194
- if (scopedVars.has(argNode.args)) {
195
- return { arg: undefined, traversed: false };
196
- }
197
- return {
198
- arg: { type: "variable", variableName: argNode.args },
199
- traversed: false,
200
- };
201
- }
202
- if (isAstNode(argNode) && argNode.op === "rcall") {
203
- const innerCall = collectRCall(argNode);
204
- if (innerCall) {
205
- return {
206
- arg: { type: "call_result", dependsOnCallId: innerCall.id },
207
- traversed: true,
208
- };
209
- }
210
- // Inner rcall was not collected — check if it was deferred
211
- if (deferredNodes.has(argNode)) {
212
- // inner calls already collected by recursive collectRCall
213
- return { arg: undefined, traversed: true };
214
- }
215
- }
216
- return { arg: undefined, traversed: false };
217
- };
218
- /**
219
- * Processes an rcall AST node, collecting it as a contract call if the
220
- * receiver is a registered contract. Recursively processes nested rcalls
221
- * in arguments to build dependency chains.
222
- */
223
- const collectRCall = (node) => {
224
- const callArgs = getRCallArgs(node);
225
- if (!callArgs) {
226
- traverse(node.args);
227
- return undefined;
228
- }
229
- const { method, receiverName, args } = callArgs;
230
- if (receiverName === "cel" && method === "bind") {
231
- handleCelBind(args);
232
- return undefined;
233
- }
234
- if (!receiverName || !registry.get(receiverName)) {
235
- handleNonContractRCall(node, method, args);
236
- return undefined;
237
- }
238
- const collectedArgs = [];
239
- let hasUnresolvableArg = false;
240
- for (const argNode of args) {
241
- const { arg, traversed } = classifyArg(argNode);
242
- if (arg) {
243
- collectedArgs.push(arg);
244
- }
245
- else {
246
- hasUnresolvableArg = true;
247
- if (!traversed) {
248
- traverse(argNode);
249
- }
250
- }
251
- }
252
- if (hasUnresolvableArg) {
253
- deferredNodes.add(node);
254
- debug("deferred %s.%s (unresolvable args)", receiverName, method);
255
- return undefined;
256
- }
257
- const call = {
258
- id: generateCallId(receiverName, method, collectedArgs),
259
- contract: receiverName,
260
- method,
261
- args: collectedArgs,
262
- astNode: node,
263
- };
264
- calls.push(call);
265
- debug("found %s.%s (id=%s)", receiverName, method, call.id);
266
- return call;
267
- };
268
- // Start traversal from the root of the AST
269
- traverse(ast);
270
- debug("collected %d total calls", calls.length);
271
- return calls;
272
- };
@@ -1,14 +0,0 @@
1
- import type { CollectedCall, DependencyGraph } from "./types.js";
2
- /**
3
- * Builds a dependency graph from collected contract calls.
4
- *
5
- * Creates a directed graph where nodes represent contract calls and edges
6
- * represent data dependencies (when one call's argument is the result of
7
- * another call). Validates that no circular dependencies exist.
8
- *
9
- * @param calls - Array of collected contract calls from AST traversal
10
- * @returns A dependency graph with nodes and directed edges
11
- * @throws {@link CircularDependencyError} If a circular dependency is detected between calls
12
- */
13
- export declare const analyzeDependencies: (calls: CollectedCall[]) => DependencyGraph;
14
- //# sourceMappingURL=dependency-analyzer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dependency-analyzer.d.ts","sourceRoot":"","sources":["../../src/analysis/dependency-analyzer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAa,MAAM,YAAY,CAAC;AAiD5E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAO,aAAa,EAAE,KACpB,eAwCF,CAAC"}
@@ -1,76 +0,0 @@
1
- import { createLogger } from "../debug.js";
2
- import { CircularDependencyError } from "../errors/index.js";
3
- const debug = createLogger("analysis:dependency");
4
- /**
5
- * Detects circular dependencies in the graph using DFS with a recursion stack.
6
- *
7
- * @throws {@link CircularDependencyError} If a cycle is found, including the call IDs forming the cycle
8
- */
9
- const detectCycles = (nodes, edges) => {
10
- const visited = new Set();
11
- const inStack = new Set();
12
- const dfs = (nodeId, path) => {
13
- if (inStack.has(nodeId)) {
14
- const cycleStart = path.indexOf(nodeId);
15
- const cycle = path.slice(cycleStart);
16
- throw new CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")} -> ${nodeId}`, { callIds: cycle });
17
- }
18
- if (visited.has(nodeId)) {
19
- return;
20
- }
21
- visited.add(nodeId);
22
- inStack.add(nodeId);
23
- path.push(nodeId);
24
- for (const depId of edges.get(nodeId) ?? []) {
25
- dfs(depId, path);
26
- }
27
- path.pop();
28
- inStack.delete(nodeId);
29
- };
30
- for (const nodeId of nodes.keys()) {
31
- if (!visited.has(nodeId)) {
32
- dfs(nodeId, []);
33
- }
34
- }
35
- };
36
- /**
37
- * Builds a dependency graph from collected contract calls.
38
- *
39
- * Creates a directed graph where nodes represent contract calls and edges
40
- * represent data dependencies (when one call's argument is the result of
41
- * another call). Validates that no circular dependencies exist.
42
- *
43
- * @param calls - Array of collected contract calls from AST traversal
44
- * @returns A dependency graph with nodes and directed edges
45
- * @throws {@link CircularDependencyError} If a circular dependency is detected between calls
46
- */
47
- export const analyzeDependencies = (calls) => {
48
- const nodes = new Map();
49
- const edges = new Map();
50
- // First pass: create a node and empty edge set for each call
51
- for (const call of calls) {
52
- nodes.set(call.id, { call, dependsOn: [], dependedOnBy: [] });
53
- edges.set(call.id, new Set());
54
- }
55
- // Second pass: wire up dependency edges from call_result arguments
56
- for (const call of calls) {
57
- for (const arg of call.args) {
58
- if (arg.type === "call_result" && arg.dependsOnCallId) {
59
- const depId = arg.dependsOnCallId;
60
- edges.get(call.id)?.add(depId);
61
- const callNode = nodes.get(call.id);
62
- if (callNode && !callNode.dependsOn.includes(depId)) {
63
- callNode.dependsOn.push(depId);
64
- }
65
- const depNode = nodes.get(depId);
66
- if (depNode && !depNode.dependedOnBy.includes(call.id)) {
67
- depNode.dependedOnBy.push(call.id);
68
- }
69
- }
70
- }
71
- }
72
- // Run cycle detection to ensure there are no circular dependencies in the graph.
73
- detectCycles(nodes, edges);
74
- debug("graph: %d nodes, %d edges", nodes.size, [...edges.values()].reduce((sum, deps) => sum + deps.size, 0));
75
- return { nodes, edges };
76
- };
@@ -1,2 +0,0 @@
1
- export type * from "./types.js";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,32 +0,0 @@
1
- import type { DependencyGraph, ExecutionPlan } from "./types.js";
2
- /**
3
- * Defines limits for the round planner to prevent excessive execution.
4
- */
5
- export interface RoundPlannerLimits {
6
- /**
7
- * Upper limit on the number of execution rounds.
8
- *
9
- * @default 10
10
- */
11
- maxRounds?: number;
12
- /**
13
- * Upper limit on the total number of calls in the graph.
14
- *
15
- * @default 100
16
- */
17
- maxCalls?: number;
18
- }
19
- /**
20
- * Plans execution rounds from a dependency graph using topological sorting.
21
- *
22
- * Groups independent calls into parallel rounds, ensuring all dependencies
23
- * of a call are executed in previous rounds. Enforces limits on the number
24
- * of rounds and total calls to prevent excessive execution.
25
- *
26
- * @param graph Dependency graph representing calls and their relationships.
27
- * @param limits Optional limits for maximum rounds and total calls.
28
- * @returns Execution plan containing planned rounds, total calls, and maximum depth.
29
- * @throws ExecutionLimitError If total calls exceed maxCalls or rounds exceed maxRounds.
30
- */
31
- export declare const planRounds: (graph: DependencyGraph, limits?: RoundPlannerLimits) => ExecutionPlan;
32
- //# sourceMappingURL=round-planner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"round-planner.d.ts","sourceRoot":"","sources":["../../src/analysis/round-planner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,eAAe,EACf,aAAa,EAEb,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GACtB,OAAO,eAAe,EACtB,SAAQ,kBAAuB,KAC7B,aA6EF,CAAC"}
@@ -1,69 +0,0 @@
1
- import { createLogger } from "../debug.js";
2
- import { ExecutionLimitError } from "../errors/index.js";
3
- const debug = createLogger("analysis:plan");
4
- /**
5
- * Plans execution rounds from a dependency graph using topological sorting.
6
- *
7
- * Groups independent calls into parallel rounds, ensuring all dependencies
8
- * of a call are executed in previous rounds. Enforces limits on the number
9
- * of rounds and total calls to prevent excessive execution.
10
- *
11
- * @param graph Dependency graph representing calls and their relationships.
12
- * @param limits Optional limits for maximum rounds and total calls.
13
- * @returns Execution plan containing planned rounds, total calls, and maximum depth.
14
- * @throws ExecutionLimitError If total calls exceed maxCalls or rounds exceed maxRounds.
15
- */
16
- export const planRounds = (graph, limits = {}) => {
17
- const maxRounds = limits.maxRounds ?? 10;
18
- const maxCalls = limits.maxCalls ?? 100;
19
- const totalCalls = graph.nodes.size;
20
- if (totalCalls > maxCalls) {
21
- throw new ExecutionLimitError(`Execution limit exceeded: ${String(totalCalls)} calls exceeds maxCalls (${String(maxCalls)})`, { limitType: "maxCalls", limit: maxCalls, actual: totalCalls });
22
- }
23
- if (totalCalls === 0) {
24
- return { rounds: [], totalCalls: 0, maxDepth: 0 };
25
- }
26
- const inDegree = new Map();
27
- for (const nodeId of graph.nodes.keys()) {
28
- inDegree.set(nodeId, graph.edges.get(nodeId)?.size ?? 0);
29
- }
30
- const rounds = [];
31
- const processed = new Set();
32
- while (processed.size < totalCalls) {
33
- const readyNodes = [];
34
- for (const [nodeId, degree] of inDegree) {
35
- if (degree === 0 && !processed.has(nodeId)) {
36
- readyNodes.push(nodeId);
37
- }
38
- }
39
- if (readyNodes.length === 0) {
40
- break;
41
- }
42
- if (rounds.length >= maxRounds) {
43
- throw new ExecutionLimitError(`Execution limit exceeded: requires more than maxRounds (${String(maxRounds)}) rounds`, {
44
- limitType: "maxRounds",
45
- limit: maxRounds,
46
- actual: rounds.length + 1,
47
- });
48
- }
49
- const round = {
50
- roundNumber: rounds.length,
51
- calls: readyNodes
52
- .map((id) => graph.nodes.get(id)?.call)
53
- .filter((c) => c !== undefined),
54
- };
55
- rounds.push(round);
56
- for (const nodeId of readyNodes) {
57
- processed.add(nodeId);
58
- const node = graph.nodes.get(nodeId);
59
- if (!node) {
60
- continue;
61
- }
62
- for (const dependentId of node.dependedOnBy) {
63
- inDegree.set(dependentId, (inDegree.get(dependentId) ?? 0) - 1);
64
- }
65
- }
66
- }
67
- debug("planned %d rounds for %d calls (max depth=%d)", rounds.length, totalCalls, rounds.length);
68
- return { rounds, totalCalls, maxDepth: rounds.length };
69
- };