@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
@@ -1,113 +0,0 @@
1
- /**
2
- * Represents a contract call found in AST traversal.
3
- * Contains all information needed to execute the call and track dependencies.
4
- */
5
- export interface CollectedCall {
6
- /**
7
- * Unique identifier: "contract:method:serializedArgs"
8
- */
9
- id: string;
10
- /**
11
- * Contract name: e.g., "erc20_usdc"
12
- */
13
- contract: string;
14
- /**
15
- * Method name: e.g., "balanceOf"
16
- */
17
- method: string;
18
- /**
19
- * Arguments with type and dependency information
20
- */
21
- args: CallArgument[];
22
- /**
23
- * Reference to original AST node for error reporting
24
- */
25
- astNode: unknown;
26
- }
27
- /**
28
- * Argument to a contract call with type information.
29
- * Tracks whether the argument is a literal, variable, or result of another call.
30
- */
31
- export interface CallArgument {
32
- /**
33
- * Type of argument: literal value, variable reference, or call result
34
- */
35
- type: "literal" | "variable" | "call_result";
36
- /**
37
- * Literal value (when type is "literal")
38
- */
39
- value?: unknown;
40
- /**
41
- * Variable name (when type is "variable")
42
- */
43
- variableName?: string;
44
- /**
45
- * Call ID this depends on (when type is "call_result")
46
- */
47
- dependsOnCallId?: string;
48
- }
49
- /**
50
- * Dependency graph for all collected calls.
51
- * Maps call IDs to their nodes and dependency relationships.
52
- */
53
- export interface DependencyGraph {
54
- /**
55
- * Call ID to graph node mapping
56
- */
57
- nodes: Map<string, GraphNode>;
58
- /**
59
- * Call ID to set of call IDs it depends on
60
- */
61
- edges: Map<string, Set<string>>;
62
- }
63
- /**
64
- * Single node in the dependency graph.
65
- * Tracks both incoming and outgoing dependencies.
66
- */
67
- export interface GraphNode {
68
- /**
69
- * The original collected call
70
- */
71
- call: CollectedCall;
72
- /**
73
- * Call IDs this call depends on (must complete first)
74
- */
75
- dependsOn: string[];
76
- /**
77
- * Call IDs that depend on this call (will execute after)
78
- */
79
- dependedOnBy: string[];
80
- }
81
- /**
82
- * Execution plan with rounds of parallel calls.
83
- * Result of topological sorting the dependency graph.
84
- */
85
- export interface ExecutionPlan {
86
- /**
87
- * Rounds of calls to execute in sequence
88
- */
89
- rounds: ExecutionRound[];
90
- /**
91
- * Total number of calls across all rounds
92
- */
93
- totalCalls: number;
94
- /**
95
- * Number of rounds (maximum dependency depth)
96
- */
97
- maxDepth: number;
98
- }
99
- /**
100
- * Single round of calls that can execute in parallel.
101
- * All calls in a round have no dependencies on each other.
102
- */
103
- export interface ExecutionRound {
104
- /**
105
- * Round number (0-indexed)
106
- */
107
- roundNumber: number;
108
- /**
109
- * Calls that can execute in parallel this round
110
- */
111
- calls: CollectedCall[];
112
- }
113
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/analysis/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,YAAY,EAAE,CAAC;IAErB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IAE7C;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAE9B;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB;;OAEG;IACH,IAAI,EAAE,aAAa,CAAC;IAEpB;;OAEG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B;;OAEG;IACH,MAAM,EAAE,cAAc,EAAE,CAAC;IAEzB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,KAAK,EAAE,aAAa,EAAE,CAAC;CACvB"}
@@ -1 +0,0 @@
1
- export {};
package/dist/debug.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import createDebug from "debug";
2
- /**
3
- * Creates a namespaced debug logger for a SEL module.
4
- *
5
- * Enable via the `DEBUG` environment variable:
6
- * - `DEBUG=sel:*` — all SEL logging
7
- * - `DEBUG=sel:evaluate` — only evaluate flow
8
- * - `DEBUG=sel:execute:*` — all execution sub-loggers
9
- *
10
- * @param namespace - Module namespace (e.g. "evaluate", "execute:round")
11
- */
12
- export declare const createLogger: (namespace: string) => createDebug.Debugger;
13
- //# sourceMappingURL=debug.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,OAAO,CAAC;AAEhC;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,KAAG,WAAW,CAAC,QAC7B,CAAC"}
package/dist/debug.js DELETED
@@ -1,12 +0,0 @@
1
- import createDebug from "debug";
2
- /**
3
- * Creates a namespaced debug logger for a SEL module.
4
- *
5
- * Enable via the `DEBUG` environment variable:
6
- * - `DEBUG=sel:*` — all SEL logging
7
- * - `DEBUG=sel:evaluate` — only evaluate flow
8
- * - `DEBUG=sel:execute:*` — all execution sub-loggers
9
- *
10
- * @param namespace - Module namespace (e.g. "evaluate", "execute:round")
11
- */
12
- export const createLogger = (namespace) => createDebug(`sel:${namespace}`);
@@ -1,3 +0,0 @@
1
- import type { CelCodecRegistry } from "@seljs/checker";
2
- export declare const normalizeContextForEvaluation: (context: Record<string, unknown>, variableTypes: Map<string, string>, codecRegistry: CelCodecRegistry) => Record<string, unknown>;
3
- //# sourceMappingURL=context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/environment/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,eAAO,MAAM,6BAA6B,GACzC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,eAAe,gBAAgB,KAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CASxB,CAAC"}
@@ -1,8 +0,0 @@
1
- export const normalizeContextForEvaluation = (context, variableTypes, codecRegistry) => {
2
- const normalized = {};
3
- for (const [key, value] of Object.entries(context)) {
4
- const type = variableTypes.get(key);
5
- normalized[key] = codecRegistry.resolve(type ?? "dyn").parse(value);
6
- }
7
- return normalized;
8
- };
@@ -1,25 +0,0 @@
1
- import type { CelCodecRegistry } from "@seljs/checker";
2
- import type { ContractSchema, MethodSchema } from "@seljs/schema";
3
- import type { Abi, Address, PublicClient } from "viem";
4
- /**
5
- * Tracks total RPC calls (pre-executed + live) across a single evaluation.
6
- * Create one per `evaluate()` call and pass it through the handler closure.
7
- */
8
- export declare class CallCounter {
9
- private readonly maxCalls;
10
- private count;
11
- constructor(maxCalls: number, initialCount?: number);
12
- increment(contractName: string, methodName: string): void;
13
- }
14
- export declare const executeContractCall: (contract: ContractSchema, method: MethodSchema, args: unknown[], options: {
15
- executionCache?: Map<string, unknown>;
16
- client?: PublicClient;
17
- codecRegistry?: CelCodecRegistry;
18
- callCounter?: CallCounter;
19
- }) => Promise<unknown>;
20
- export declare const resolveExecutionBlockNumber: (client: PublicClient) => Promise<bigint | undefined>;
21
- export declare const buildContractInfoMap: (contracts: ContractSchema[]) => Map<string, {
22
- abi: Abi;
23
- address: Address;
24
- }>;
25
- //# sourceMappingURL=contract-caller.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-caller.d.ts","sourceRoot":"","sources":["../../src/environment/contract-caller.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIvD;;;GAGG;AACH,qBAAa,WAAW;IAGtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAF1B,OAAO,CAAC,KAAK,CAAK;gBAEA,QAAQ,EAAE,MAAM,EACjC,YAAY,GAAE,MAAU;IAKlB,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;CAqBhE;AAED,eAAO,MAAM,mBAAmB,GAC/B,UAAU,cAAc,EACxB,QAAQ,YAAY,EACpB,MAAM,OAAO,EAAE,EACf,SAAS;IACR,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,aAAa,CAAC,EAAE,gBAAgB,CAAC;IACjC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B,KAEC,OAAO,CAAC,OAAO,CAwDjB,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACvC,QAAQ,YAAY,KAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAChC,WAAW,cAAc,EAAE,KACzB,GAAG,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAQ5C,CAAC"}
@@ -1,85 +0,0 @@
1
- import { readContract } from "viem/actions";
2
- import { createReplayCallId } from "./replay-cache.js";
3
- import { createLogger } from "../debug.js";
4
- import { ExecutionLimitError, SELContractError } from "../errors/index.js";
5
- const debug = createLogger("contract-caller");
6
- /**
7
- * Tracks total RPC calls (pre-executed + live) across a single evaluation.
8
- * Create one per `evaluate()` call and pass it through the handler closure.
9
- */
10
- export class CallCounter {
11
- maxCalls;
12
- count = 0;
13
- constructor(maxCalls, initialCount = 0) {
14
- this.maxCalls = maxCalls;
15
- this.count = initialCount;
16
- }
17
- increment(contractName, methodName) {
18
- this.count++;
19
- if (this.count > this.maxCalls) {
20
- throw new ExecutionLimitError(`Execution limit exceeded: ${String(this.count)} calls exceeds maxCalls (${String(this.maxCalls)})`, {
21
- limitType: "maxCalls",
22
- limit: this.maxCalls,
23
- actual: this.count,
24
- });
25
- }
26
- debug("call count: %d/%d (%s.%s)", this.count, this.maxCalls, contractName, methodName);
27
- }
28
- }
29
- export const executeContractCall = async (contract, method, args, options) => {
30
- const normalizedArgs = options.codecRegistry
31
- ? args.map((arg, i) => options.codecRegistry.encode(method.params[i]?.type ?? "dyn", arg))
32
- : args;
33
- if (options.executionCache) {
34
- const callId = createReplayCallId(contract.name, method.name, normalizedArgs);
35
- if (options.executionCache.has(callId)) {
36
- debug("cache hit: %s.%s", contract.name, method.name);
37
- return options.executionCache.get(callId);
38
- }
39
- debug("cache miss: %s.%s — executing live", contract.name, method.name);
40
- }
41
- if (!options.client) {
42
- throw new SELContractError("No client provided for contract call. Provide a client in SELRuntime config or evaluate() context.", {
43
- contractName: contract.name,
44
- methodName: method.name,
45
- });
46
- }
47
- // Count live RPC calls against the limit
48
- options.callCounter?.increment(contract.name, method.name);
49
- try {
50
- return await readContract(options.client, {
51
- address: contract.address,
52
- abi: [method.abi],
53
- functionName: method.name,
54
- args: normalizedArgs,
55
- });
56
- }
57
- catch (error) {
58
- if (error instanceof SELContractError) {
59
- throw error;
60
- }
61
- throw new SELContractError(`Contract call failed: ${contract.name}.${method.name}`, {
62
- cause: error,
63
- contractName: contract.name,
64
- methodName: method.name,
65
- });
66
- }
67
- };
68
- export const resolveExecutionBlockNumber = async (client) => {
69
- const clientWithGetBlockNumber = client;
70
- if (typeof clientWithGetBlockNumber.getBlockNumber === "function") {
71
- return await clientWithGetBlockNumber.getBlockNumber();
72
- }
73
- if (typeof clientWithGetBlockNumber.request === "function") {
74
- return undefined;
75
- }
76
- return 0n;
77
- };
78
- export const buildContractInfoMap = (contracts) => {
79
- const map = new Map();
80
- for (const contract of contracts) {
81
- const abi = contract.methods.map((m) => m.abi);
82
- map.set(contract.name, { abi, address: contract.address });
83
- }
84
- return map;
85
- };
@@ -1,81 +0,0 @@
1
- import type { SELRuntimeConfig } from "./types.js";
2
- import type { EvaluateOptions, EvaluateResult } from "../execution/types.js";
3
- import type { TypeCheckResult } from "@marcbachmann/cel-js";
4
- export declare class SELRuntime {
5
- private readonly env;
6
- private readonly client?;
7
- private readonly schema;
8
- private readonly variableTypes;
9
- private readonly maxRounds;
10
- private readonly maxCalls;
11
- private readonly multicallOptions?;
12
- private readonly contractBindings;
13
- private readonly codecRegistry;
14
- /**
15
- * Per-evaluation mutable state.
16
- *
17
- * These fields are set before CEL evaluation and cleared in `finally`.
18
- * They exist because the contract call handler closure (created in the
19
- * constructor) needs access to per-evaluation context, but CEL's
20
- * `Environment.parse()` returns a function that doesn't accept extra
21
- * parameters beyond the variable bindings.
22
- *
23
- * Thread safety: The `mutex` field serializes concurrent `evaluate()`
24
- * calls, ensuring these fields are never accessed concurrently.
25
- *
26
- * TODO: Consider refactoring to pass an EvaluationContext object through
27
- * the handler closure instead of mutating instance state. This would
28
- * eliminate the need for the mutex and make the code more testable.
29
- * See .omc/drafts/context-object-spike.md for preliminary analysis.
30
- */
31
- private currentCache?;
32
- private currentClient?;
33
- private currentCallCounter?;
34
- /** Mutex to serialize concurrent evaluate() calls (protects current* fields) */
35
- private mutex;
36
- /**
37
- * Creates a new immutable SEL runtime with Solidity types pre-registered.
38
- *
39
- * Initializes the underlying CEL runtime, registers all Solidity primitive types,
40
- * and processes any contracts and variables from the schema. After construction,
41
- * the environment is fully configured and immutable.
42
- *
43
- * @param config - Configuration containing the SEL schema, optional viem client,
44
- * multicall settings, and CEL/execution limits (maxRounds defaults to 10, maxCalls to 100)
45
- */
46
- constructor(config: SELRuntimeConfig);
47
- /**
48
- * Type-checks an expression against registered variables and contract methods.
49
- *
50
- * @param expression - A CEL expression string to type-check
51
- * @returns The type-check result containing validity, inferred type, and any errors
52
- * @throws {@link SELTypeError} If the expression contains unrecoverable type errors
53
- */
54
- check(expression: string): TypeCheckResult;
55
- /**
56
- * Evaluates a SEL expression, executing any embedded contract calls on-chain.
57
- *
58
- * When contract calls are present, the full pipeline runs:
59
- * 1. Parse the expression and collect contract calls from the AST
60
- * 2. Type-check against registered variables and contract methods
61
- * 3. Build a dependency graph and plan execution rounds
62
- * 4. Execute contract calls via multicall3 batching at a pinned block number
63
- * 5. Evaluate the CEL expression with resolved contract results and context values
64
- * 6. Unwrap Solidity wrapper types back to native JS values (BigInt, string, etc.)
65
- *
66
- * @param expression A CEL expression string
67
- * @param context Variable bindings for evaluation
68
- * @param options Optional client override
69
- * @returns An {@link EvaluateResult} containing the value and optional execution metadata
70
- * @throws {@link SELParseError} If the expression has invalid syntax
71
- * @throws {@link SELTypeError} If type-checking fails
72
- * @throws {@link SELContractError} If a contract call fails or no client is available
73
- * @throws {@link SELEvaluationError} If CEL evaluation fails
74
- */
75
- evaluate<T = unknown>(expression: string, context?: Record<string, unknown>, options?: EvaluateOptions): Promise<EvaluateResult<T>>;
76
- private planExecution;
77
- private executeContractCalls;
78
- private doEvaluate;
79
- private findContract;
80
- }
81
- //# sourceMappingURL=environment.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/environment/environment.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAoB,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,KAAK,EACX,eAAe,EACf,cAAc,EAEd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAezE,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAmB;IACrD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IAEjD;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,YAAY,CAAC,CAAuB;IAC5C,OAAO,CAAC,aAAa,CAAC,CAAe;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAAc;IAEzC,gFAAgF;IAChF,OAAO,CAAC,KAAK,CAAqB;IAElC;;;;;;;;;OASG;gBACgB,MAAM,EAAE,gBAAgB;IAgE3C;;;;;;OAMG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe;IAQjD;;;;;;;;;;;;;;;;;;;OAmBG;IACU,QAAQ,CAAC,CAAC,GAAG,OAAO,EAChC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAiB7B,OAAO,CAAC,aAAa;YA+CP,oBAAoB;YA0FpB,UAAU;IAyExB,OAAO,CAAC,YAAY;CAGpB"}
@@ -1,279 +0,0 @@
1
- import { createRuntimeEnvironment } from "@seljs/checker";
2
- import { normalizeContextForEvaluation } from "./context.js";
3
- import { CallCounter, buildContractInfoMap, executeContractCall, resolveExecutionBlockNumber, } from "./contract-caller.js";
4
- import { wrapError } from "./error-wrapper.js";
5
- import { buildExecutionReplayCache } from "./replay-cache.js";
6
- import { collectCalls } from "../analysis/call-collector.js";
7
- import { analyzeDependencies } from "../analysis/dependency-analyzer.js";
8
- import { planRounds } from "../analysis/round-planner.js";
9
- import { createLogger } from "../debug.js";
10
- import { MulticallBatchError, SELContractError, SELTypeError, } from "../errors/index.js";
11
- import { MultiRoundExecutor } from "../execution/multi-round-executor.js";
12
- /**
13
- * Core SEL runtime that bridges CEL expression evaluation with EVM contract reads.
14
- *
15
- * Manages a CEL runtime extended with Solidity primitive types (uint256, address, bool, etc.),
16
- * registered smart contracts whose view functions become callable in expressions, and typed
17
- * variables that are supplied at evaluation time. Contract calls are automatically batched
18
- * via multicall3 and executed in dependency-ordered rounds.
19
- */
20
- const debug = createLogger("environment");
21
- export class SELRuntime {
22
- env;
23
- client;
24
- schema;
25
- variableTypes = new Map();
26
- maxRounds;
27
- maxCalls;
28
- multicallOptions;
29
- contractBindings;
30
- codecRegistry;
31
- /**
32
- * Per-evaluation mutable state.
33
- *
34
- * These fields are set before CEL evaluation and cleared in `finally`.
35
- * They exist because the contract call handler closure (created in the
36
- * constructor) needs access to per-evaluation context, but CEL's
37
- * `Environment.parse()` returns a function that doesn't accept extra
38
- * parameters beyond the variable bindings.
39
- *
40
- * Thread safety: The `mutex` field serializes concurrent `evaluate()`
41
- * calls, ensuring these fields are never accessed concurrently.
42
- *
43
- * TODO: Consider refactoring to pass an EvaluationContext object through
44
- * the handler closure instead of mutating instance state. This would
45
- * eliminate the need for the mutex and make the code more testable.
46
- * See .omc/drafts/context-object-spike.md for preliminary analysis.
47
- */
48
- currentCache;
49
- currentClient;
50
- currentCallCounter;
51
- /** Mutex to serialize concurrent evaluate() calls (protects current* fields) */
52
- mutex = Promise.resolve();
53
- /**
54
- * Creates a new immutable SEL runtime with Solidity types pre-registered.
55
- *
56
- * Initializes the underlying CEL runtime, registers all Solidity primitive types,
57
- * and processes any contracts and variables from the schema. After construction,
58
- * the environment is fully configured and immutable.
59
- *
60
- * @param config - Configuration containing the SEL schema, optional viem client,
61
- * multicall settings, and CEL/execution limits (maxRounds defaults to 10, maxCalls to 100)
62
- */
63
- constructor(config) {
64
- const limits = config.limits;
65
- const celLimits = limits
66
- ? {
67
- maxAstNodes: limits.maxAstNodes,
68
- maxDepth: limits.maxDepth,
69
- maxListElements: limits.maxListElements,
70
- maxMapEntries: limits.maxMapEntries,
71
- maxCallArguments: limits.maxCallArguments,
72
- }
73
- : undefined;
74
- this.maxRounds = limits?.maxRounds ?? 10;
75
- this.maxCalls = limits?.maxCalls ?? 100;
76
- this.multicallOptions = config.multicall;
77
- this.client = config.client;
78
- this.schema = config.schema;
79
- // Build handler closure that captures `this` for contract execution
80
- const handler = (contractName, methodName, args) => {
81
- const contract = this.findContract(contractName);
82
- if (!contract) {
83
- throw new SELContractError(`Unknown contract "${contractName}"`, {
84
- contractName,
85
- });
86
- }
87
- const method = contract.methods.find((m) => m.name === methodName);
88
- if (!method) {
89
- throw new SELContractError(`Unknown method "${contractName}.${methodName}"`, { contractName, methodName });
90
- }
91
- return executeContractCall(contract, method, args, {
92
- executionCache: this.currentCache,
93
- client: this.currentClient ?? this.client,
94
- codecRegistry: this.codecRegistry,
95
- callCounter: this.currentCallCounter,
96
- });
97
- };
98
- // Create environment via unified hydration
99
- const { env, contractBindings, codecRegistry } = createRuntimeEnvironment(this.schema, handler, { limits: celLimits, unlistedVariablesAreDyn: true });
100
- this.env = env;
101
- this.contractBindings = contractBindings;
102
- this.codecRegistry = codecRegistry;
103
- // Populate variableTypes from schema for normalizeContextForEvaluation
104
- for (const v of this.schema.variables) {
105
- this.variableTypes.set(v.name, v.type);
106
- }
107
- }
108
- /**
109
- * Type-checks an expression against registered variables and contract methods.
110
- *
111
- * @param expression - A CEL expression string to type-check
112
- * @returns The type-check result containing validity, inferred type, and any errors
113
- * @throws {@link SELTypeError} If the expression contains unrecoverable type errors
114
- */
115
- check(expression) {
116
- try {
117
- return this.env.check(expression);
118
- }
119
- catch (error) {
120
- throw wrapError(error);
121
- }
122
- }
123
- /**
124
- * Evaluates a SEL expression, executing any embedded contract calls on-chain.
125
- *
126
- * When contract calls are present, the full pipeline runs:
127
- * 1. Parse the expression and collect contract calls from the AST
128
- * 2. Type-check against registered variables and contract methods
129
- * 3. Build a dependency graph and plan execution rounds
130
- * 4. Execute contract calls via multicall3 batching at a pinned block number
131
- * 5. Evaluate the CEL expression with resolved contract results and context values
132
- * 6. Unwrap Solidity wrapper types back to native JS values (BigInt, string, etc.)
133
- *
134
- * @param expression A CEL expression string
135
- * @param context Variable bindings for evaluation
136
- * @param options Optional client override
137
- * @returns An {@link EvaluateResult} containing the value and optional execution metadata
138
- * @throws {@link SELParseError} If the expression has invalid syntax
139
- * @throws {@link SELTypeError} If type-checking fails
140
- * @throws {@link SELContractError} If a contract call fails or no client is available
141
- * @throws {@link SELEvaluationError} If CEL evaluation fails
142
- */
143
- async evaluate(expression, context, options) {
144
- let release;
145
- const prev = this.mutex;
146
- this.mutex = new Promise((resolve) => {
147
- release = resolve;
148
- });
149
- await prev;
150
- try {
151
- return await this.doEvaluate(expression, context, options);
152
- }
153
- finally {
154
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155
- release();
156
- }
157
- }
158
- planExecution(expression, evaluationContext) {
159
- const parseResult = this.env.parse(expression);
160
- const collectedCalls = collectCalls(parseResult.ast, {
161
- get: (name) => this.findContract(name),
162
- });
163
- debug("evaluate: collected %d calls", collectedCalls.length);
164
- const normalizedContext = Object.keys(evaluationContext).length
165
- ? normalizeContextForEvaluation(evaluationContext, this.variableTypes, this.codecRegistry)
166
- : undefined;
167
- const executionVariables = Object.keys(evaluationContext).length
168
- ? evaluationContext
169
- : {};
170
- const typeCheckResult = parseResult.check();
171
- if (!typeCheckResult.valid) {
172
- throw new SELTypeError(typeCheckResult.error?.message ?? "Type check failed", { cause: typeCheckResult.error });
173
- }
174
- return {
175
- parseResult,
176
- collectedCalls,
177
- normalizedContext,
178
- executionVariables,
179
- typeCheckResult,
180
- };
181
- }
182
- async executeContractCalls(collectedCalls, executionVariables, resolvedClient) {
183
- debug("evaluate: calls to execute — %o", collectedCalls.map((c) => `${c.contract}.${c.method}`));
184
- const graph = analyzeDependencies(collectedCalls);
185
- const plan = planRounds(graph, {
186
- maxRounds: this.maxRounds,
187
- maxCalls: this.maxCalls,
188
- });
189
- const executor = new MultiRoundExecutor(resolvedClient, buildContractInfoMap(this.schema.contracts), this.multicallOptions);
190
- let executionResult;
191
- try {
192
- executionResult = await executor.execute(plan, executionVariables, await resolveExecutionBlockNumber(resolvedClient));
193
- }
194
- catch (error) {
195
- let failedCall = collectedCalls[0];
196
- if (error instanceof MulticallBatchError) {
197
- if (error.contractName && error.methodName) {
198
- const contractName = error.contractName;
199
- const methodName = error.methodName;
200
- const base = failedCall ?? {
201
- id: "",
202
- contract: contractName,
203
- method: methodName,
204
- args: [],
205
- astNode: undefined,
206
- };
207
- failedCall = {
208
- ...base,
209
- contract: contractName,
210
- method: methodName,
211
- };
212
- }
213
- else if (typeof error.failedCallIndex === "number") {
214
- failedCall = collectedCalls[error.failedCallIndex] ?? failedCall;
215
- }
216
- }
217
- if (!failedCall) {
218
- throw error;
219
- }
220
- throw new SELContractError(`Contract call failed: ${failedCall.contract}.${failedCall.method}`, {
221
- cause: error,
222
- contractName: failedCall.contract,
223
- methodName: failedCall.method,
224
- });
225
- }
226
- const executionMeta = executionResult.meta;
227
- const executionCache = buildExecutionReplayCache(collectedCalls, executionResult.results, executionVariables, this.codecRegistry, (contract, method) => {
228
- const c = this.findContract(contract);
229
- const m = c?.methods.find((m) => m.name === method);
230
- return m?.params.map((p) => p.type) ?? [];
231
- });
232
- return { executionMeta, executionCache };
233
- }
234
- async doEvaluate(expression, context, options) {
235
- debug("evaluate: %s", expression);
236
- const resolvedClient = options?.client ?? this.client;
237
- try {
238
- const { parseResult, collectedCalls, normalizedContext, executionVariables, typeCheckResult, } = this.planExecution(expression, context ?? {});
239
- let executionMeta;
240
- let executionCache;
241
- if (collectedCalls.length > 0) {
242
- if (!resolvedClient) {
243
- throw new SELContractError("No client provided for contract call. Provide a client in SELRuntime config or evaluate() options.", {
244
- contractName: collectedCalls[0]?.contract,
245
- methodName: collectedCalls[0]?.method,
246
- });
247
- }
248
- ({ executionMeta, executionCache } = await this.executeContractCalls(collectedCalls, executionVariables, resolvedClient));
249
- }
250
- // Set per-evaluation state for the handler closure
251
- this.currentCache = executionCache;
252
- this.currentClient = resolvedClient;
253
- this.currentCallCounter = new CallCounter(this.maxCalls, executionMeta?.totalCalls ?? 0);
254
- let result;
255
- try {
256
- result = await parseResult({
257
- ...normalizedContext,
258
- ...this.contractBindings,
259
- });
260
- }
261
- finally {
262
- this.currentCache = undefined;
263
- this.currentClient = undefined;
264
- this.currentCallCounter = undefined;
265
- }
266
- const value = (typeCheckResult.type
267
- ? this.codecRegistry.encode(typeCheckResult.type, result)
268
- : result);
269
- debug("evaluate: result type=%s", typeof value);
270
- return executionMeta ? { value, meta: executionMeta } : { value };
271
- }
272
- catch (error) {
273
- throw wrapError(error);
274
- }
275
- }
276
- findContract(name) {
277
- return this.schema.contracts.find((c) => c.name === name);
278
- }
279
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * Wraps an unknown error into a known SEL error type.
3
- * - If the error is already a SEL error, it is returned as is.
4
- * - If the error is a CEL error, it is wrapped into the corresponding SEL error type.
5
- * - If the error is an unknown type, it is wrapped into a generic Error.
6
- *
7
- * @param error The error to wrap.
8
- * @returns The wrapped error.
9
- */
10
- export declare const wrapError: (error: unknown) => Error;
11
- //# sourceMappingURL=error-wrapper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error-wrapper.d.ts","sourceRoot":"","sources":["../../src/environment/error-wrapper.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,KA2B1C,CAAC"}