envio 3.15.0 → 3.16.0-subgraph

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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -0,0 +1,184 @@
1
+ /**
2
+ * `event.receipt.logs`: every log of the event's transaction.
3
+ *
4
+ * envio hands a handler the event and, for a `receipt: true` handler, the
5
+ * receipt's scalar fields as transaction fields — but not the other logs of the
6
+ * transaction. They are read from HyperSync: the logs of the event's block, of
7
+ * which the transaction's are kept. Every handler invocation in a batch asks
8
+ * within the same microtask, so one query answers every transaction of a block,
9
+ * and blocks run side by side. A chain HyperSync doesn't serve, or a failing
10
+ * HyperSync, falls back to `eth_getTransactionReceipt` when an RPC is set.
11
+ */
12
+
13
+ import { rpcClient } from "./rpc.ts";
14
+ import { HttpStatusError, untilAnswered, withDeadline } from "./transient.ts";
15
+
16
+ const API_TOKEN_ENV_VAR = "ENVIO_API_TOKEN";
17
+ const MAX_QUERIES_IN_FLIGHT = 8;
18
+ /** Blocks whose logs are kept for the transactions that ask later. */
19
+ const KEEP_BLOCKS = 128;
20
+
21
+ const hypersyncUrl = (chainId: number) => `https://${chainId}.hypersync.xyz/query`;
22
+
23
+ /** A log as the runtime serves it: hex strings, plain numbers. */
24
+ export type ReceiptLog = {
25
+ address: string;
26
+ topics: string[];
27
+ data: string;
28
+ logIndex: number;
29
+ transactionIndex: number;
30
+ transactionHash: string;
31
+ };
32
+
33
+ export type ReceiptRequest = { chainId: number; blockNumber: number; transactionIndex: number; transactionHash: string };
34
+
35
+ let rpcUrls: string[] = [];
36
+ /** Chains HyperSync doesn't serve, which RPC answers alone. */
37
+ let rpcOnly = new Set<number>();
38
+ /** Logs of recently read blocks, by chain then number; insertion order is age. */
39
+ let recent = new Map<string, Promise<ReceiptLog[]>>();
40
+ let inFlight = 0;
41
+ const queued: (() => void)[] = [];
42
+
43
+ export function configureReceiptLogs(urls: string[], rpcOnlyChains: number[] = []) {
44
+ rpcUrls = urls;
45
+ rpcOnly = new Set(rpcOnlyChains);
46
+ recent = new Map();
47
+ }
48
+
49
+ async function slot<T>(run: () => Promise<T>): Promise<T> {
50
+ if (inFlight >= MAX_QUERIES_IN_FLIGHT) await new Promise<void>((resolve) => queued.push(resolve));
51
+ else inFlight++;
52
+ try {
53
+ return await run();
54
+ } finally {
55
+ const next = queued.shift();
56
+ if (next) next();
57
+ else inFlight--;
58
+ }
59
+ }
60
+
61
+ type HyperSyncLogs = {
62
+ data: { logs?: Record<string, string | number | null>[] }[];
63
+ next_block: number;
64
+ };
65
+
66
+ const num = (value: unknown) => Number(value);
67
+
68
+ async function blockLogsFromHyperSync(chainId: number, blockNumber: number): Promise<ReceiptLog[]> {
69
+ const token = process.env[API_TOKEN_ENV_VAR];
70
+ const url = hypersyncUrl(chainId);
71
+ const logs: ReceiptLog[] = [];
72
+ let cursor = blockNumber;
73
+ // A block is answered whole, but resume if the response says otherwise.
74
+ while (cursor <= blockNumber) {
75
+ const from = cursor;
76
+ const ask = rpcUrls.length > 0 ? withDeadline : untilAnswered;
77
+ const body = await ask(
78
+ `HyperSync's logs of block ${blockNumber}`,
79
+ async (signal): Promise<HyperSyncLogs> => {
80
+ const response = await fetch(url, {
81
+ method: "POST",
82
+ signal,
83
+ headers: {
84
+ "content-type": "application/json",
85
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
86
+ },
87
+ body: JSON.stringify({
88
+ from_block: from,
89
+ to_block: blockNumber + 1,
90
+ logs: [{}],
91
+ field_selection: {
92
+ log: [
93
+ "block_number",
94
+ "log_index",
95
+ "transaction_index",
96
+ "transaction_hash",
97
+ "address",
98
+ "data",
99
+ "topic0",
100
+ "topic1",
101
+ "topic2",
102
+ "topic3",
103
+ ],
104
+ },
105
+ }),
106
+ });
107
+ if (!response.ok) {
108
+ throw new HttpStatusError(url, response.status, await response.text());
109
+ }
110
+ return (await response.json()) as HyperSyncLogs;
111
+ },
112
+ );
113
+ for (const page of body.data ?? []) {
114
+ for (const log of page.logs ?? []) {
115
+ if (num(log.block_number) !== blockNumber) continue;
116
+ logs.push({
117
+ address: String(log.address),
118
+ topics: [log.topic0, log.topic1, log.topic2, log.topic3].filter(
119
+ (topic): topic is string => typeof topic === "string",
120
+ ),
121
+ data: String(log.data ?? "0x"),
122
+ logIndex: num(log.log_index),
123
+ transactionIndex: num(log.transaction_index),
124
+ transactionHash: String(log.transaction_hash),
125
+ });
126
+ }
127
+ }
128
+ if (!body.next_block || body.next_block <= cursor) break;
129
+ cursor = body.next_block;
130
+ }
131
+ return logs.sort((a, b) => a.logIndex - b.logIndex);
132
+ }
133
+
134
+ function blockLogs(chainId: number, blockNumber: number): Promise<ReceiptLog[]> {
135
+ const key = `${chainId}:${blockNumber}`;
136
+ let logs = recent.get(key);
137
+ if (!logs) {
138
+ logs = slot(() => blockLogsFromHyperSync(chainId, blockNumber));
139
+ recent.set(key, logs);
140
+ // A failed read is not kept: the retry should ask again.
141
+ logs.catch(() => recent.delete(key));
142
+ if (recent.size > KEEP_BLOCKS) recent.delete(recent.keys().next().value as string);
143
+ }
144
+ return logs;
145
+ }
146
+
147
+ async function fromRpc(request: ReceiptRequest): Promise<ReceiptLog[]> {
148
+ const client = rpcClient(rpcUrls);
149
+ const receipt = await untilAnswered(
150
+ `the receipt of ${request.transactionHash} from RPC`,
151
+ () => client.getTransactionReceipt({ hash: request.transactionHash }),
152
+ { deadline: false },
153
+ );
154
+ return (receipt.logs as Record<string, any>[]).map((log) => ({
155
+ address: log.address,
156
+ topics: [...log.topics],
157
+ data: log.data,
158
+ logIndex: Number(log.logIndex),
159
+ transactionIndex: Number(log.transactionIndex),
160
+ transactionHash: log.transactionHash,
161
+ }));
162
+ }
163
+
164
+ /** The logs of one transaction, in log order. */
165
+ export async function requestReceiptLogs(request: ReceiptRequest): Promise<ReceiptLog[]> {
166
+ if (rpcOnly.has(request.chainId) && rpcUrls.length > 0) return fromRpc(request);
167
+ try {
168
+ const logs = await blockLogs(request.chainId, request.blockNumber);
169
+ const mine = logs.filter((log) => log.transactionIndex === request.transactionIndex);
170
+ // An event of this transaction has to be among them; if not, HyperSync
171
+ // is behind or answered another fork, and RPC is asked instead.
172
+ if (mine.length > 0 || rpcUrls.length === 0) return mine;
173
+ } catch (error) {
174
+ if (rpcUrls.length === 0) {
175
+ const message = error instanceof Error ? error.message : String(error);
176
+ throw new Error(
177
+ `Envio Subgraph couldn't read the logs of transaction ${request.transactionHash} from HyperSync: ${message}\n` +
178
+ `Set ${API_TOKEN_ENV_VAR} in .env or the environment — create one at\n` +
179
+ `https://envio.dev/app/api-tokens — or set ENVIO_SUBGRAPH_RPC to fall back to RPC.`,
180
+ );
181
+ }
182
+ }
183
+ return fromRpc(request);
184
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Bounds how many register passes run at once.
3
+ *
4
+ * envio's fetch loop starts a `contractRegister` for every item of a response
5
+ * and awaits them all. A response can hold tens of thousands of items (a chain
6
+ * kept open by a template rule is fetched far ahead of the handlers), and each
7
+ * pass is a live mapping run with its own scope, replay rounds and pending
8
+ * contract calls: Venus on BSC held 10,000 at once and the heap passed 4 GB
9
+ * while the RPC answered a few calls a second. A pass now needs a slot first;
10
+ * the rest wait as plain queue entries, which is also the back-pressure the
11
+ * fetch loop was missing, since it awaits its passes before the next response.
12
+ *
13
+ * Slots go to the lowest position first, the order graph-node would process
14
+ * them in. A pass that waits on an earlier pass (see `runRegisterRounds`) gives
15
+ * its slot back for the wait — otherwise every slot could be held by passes
16
+ * waiting on one that never got a slot. Waiting on an RPC answer keeps it: the
17
+ * answer depends on nothing else here.
18
+ */
19
+
20
+ const DEFAULT_REGISTER_CONCURRENCY = 256;
21
+
22
+ export function registerConcurrencyFromEnv(): number {
23
+ const value = Number(process.env.ENVIO_SUBGRAPH_REGISTER_CONCURRENCY);
24
+ return Number.isInteger(value) && value > 0 ? value : DEFAULT_REGISTER_CONCURRENCY;
25
+ }
26
+
27
+ export type RegisterSlot = {
28
+ /** Gives the slot back while `wait` is pending and takes one again after. */
29
+ pause<T>(wait: Promise<T>): Promise<T>;
30
+ release(): void;
31
+ };
32
+
33
+ export type RegisterSlots = {
34
+ acquire(position: number): Promise<RegisterSlot>;
35
+ /** Passes holding a slot, and passes queued for one. */
36
+ stats(): { active: number; queued: number };
37
+ };
38
+
39
+ export function createRegisterSlots(limit = registerConcurrencyFromEnv()): RegisterSlots {
40
+ let active = 0;
41
+ const queue: { position: number; grant: () => void }[] = [];
42
+
43
+ const take = (position: number): Promise<void> => {
44
+ if (active < limit && queue.length === 0) {
45
+ active++;
46
+ return Promise.resolve();
47
+ }
48
+ return new Promise<void>((grant) => {
49
+ let at = queue.length;
50
+ while (at > 0 && queue[at - 1].position > position) at--;
51
+ queue.splice(at, 0, { position, grant });
52
+ });
53
+ };
54
+
55
+ // A finishing pass hands its slot straight to the lowest queued position.
56
+ const give = () => {
57
+ const next = queue.shift();
58
+ if (next) next.grant();
59
+ else active--;
60
+ };
61
+
62
+ const slotFor = (position: number): RegisterSlot => {
63
+ let held = true;
64
+ return {
65
+ async pause(wait) {
66
+ if (!held) return wait;
67
+ held = false;
68
+ give();
69
+ try {
70
+ return await wait;
71
+ } finally {
72
+ await take(position);
73
+ held = true;
74
+ }
75
+ },
76
+ release() {
77
+ if (!held) return;
78
+ held = false;
79
+ give();
80
+ },
81
+ };
82
+ };
83
+
84
+ return {
85
+ async acquire(position) {
86
+ await take(position);
87
+ return slotFor(position);
88
+ },
89
+ stats: () => ({ active, queued: queue.length }),
90
+ };
91
+ }
92
+
93
+ export const registerSlots = createRegisterSlots();
@@ -0,0 +1,109 @@
1
+ import {
2
+ aggregationEntity,
3
+ bucketsBetween,
4
+ compileAggregation,
5
+ INTERVAL_MICROS,
6
+ ROLLUP_STATE_ENTITY,
7
+ rollupBucket,
8
+ storedValue,
9
+ type CompiledAggregation,
10
+ type PrevState,
11
+ type SourceFields,
12
+ } from "./aggregations.ts";
13
+ import type { Scope } from "./scope.ts";
14
+
15
+ const CLOCK_ID = "clock";
16
+ const compiledBySchema = new WeakMap<object, CompiledAggregation[]>();
17
+
18
+ function compiledFor(scope: Scope): CompiledAggregation[] {
19
+ let compiled = compiledBySchema.get(scope.schema);
20
+ if (!compiled) {
21
+ compiled = scope.schema.aggregations.map((def) => {
22
+ const declared = scope.schema.entityFieldTypes[def.source] ?? {};
23
+ const fields: SourceFields = {
24
+ kinds: Object.fromEntries(Object.entries(declared).map(([field, type]) => [field, type.kind])),
25
+ refs: new Set(scope.schema.entityRefFields[def.source] ?? []),
26
+ };
27
+ return compileAggregation(def, fields);
28
+ });
29
+ compiledBySchema.set(scope.schema, compiled);
30
+ }
31
+ return compiled;
32
+ }
33
+
34
+ function table(scope: Scope, entity: string) {
35
+ const accessor = scope.schema.entityAccessors[entity] ?? entity;
36
+ const found = scope.context[accessor];
37
+ if (!found) throw new Error(`Envio Subgraph has no table for the aggregation entity ${entity}.`);
38
+ return found;
39
+ }
40
+
41
+ /**
42
+ * graph-node rolls up when a block's time passes the end of the bucket the
43
+ * previous block's time was in (`Layout::rollup`): only that first bucket can
44
+ * hold rows, since nothing was written in between. The last block time lives in
45
+ * an entity, so a restart or a reorg rollback finds it where it left it.
46
+ */
47
+ export function advanceRollup(scope: Scope, now: bigint): void {
48
+ if (scope.mode !== "handler" || scope.schema.aggregations.length === 0) return;
49
+ const state = scope.context[ROLLUP_STATE_ENTITY];
50
+ const stored = state.getSync(CLOCK_ID);
51
+ // Preload drops writes, so every handler there reads the clock the last batch
52
+ // left and would roll that bucket up again; the execute pass rolls up once.
53
+ if (scope.context.isPreload) return;
54
+ const last: bigint | null = stored ? BigInt(JSON.parse(stored.payload).t) : null;
55
+ if (last !== null && now <= last) return;
56
+ if (last !== null) {
57
+ for (const compiled of compiledFor(scope)) {
58
+ for (const interval of compiled.def.intervals) {
59
+ const [bucket] = bucketsBetween(last, now, interval);
60
+ if (bucket) rollup(scope, compiled, interval, bucket.start, bucket.end);
61
+ }
62
+ }
63
+ }
64
+ state.set({ id: CLOCK_ID, aggregation: CLOCK_ID, payload: JSON.stringify({ t: now.toString() }) });
65
+ }
66
+
67
+ function rollup(scope: Scope, compiled: CompiledAggregation, interval: string, start: bigint, end: bigint) {
68
+ const { def } = compiled;
69
+ const source = table(scope, def.source);
70
+ const rows = source.getWhereSync({
71
+ timestamp: { _gte: new Date(Number(start / 1000n)), _lt: new Date(Number(end / 1000n)) },
72
+ });
73
+ if (rows.length === 0) return;
74
+ const state = scope.context[ROLLUP_STATE_ENTITY];
75
+ const group = `${def.name}|${interval}`;
76
+ const stateId = (dimKey: string) => `prev|${group}|${dimKey}`;
77
+ const hasCumulative = def.aggregates.some((aggregate) => aggregate.cumulative);
78
+ // Every group's carried values in one read: read one group at a time, the
79
+ // sync bridge would replay the handler once per group in the bucket.
80
+ const carried = new Map<string, PrevState>();
81
+ if (hasCumulative) {
82
+ for (const row of state.getWhereSync({ aggregation: { _eq: group } })) {
83
+ carried.set(row.id, JSON.parse(row.payload) as PrevState);
84
+ }
85
+ }
86
+ const target = table(scope, aggregationEntity(def.name, interval));
87
+ const timestamp = new Date(Number(start / 1000n));
88
+ for (const bucket of rollupBucket(compiled, rows, (dimKey) => carried.get(stateId(dimKey)) ?? null)) {
89
+ const row: Record<string, unknown> = { id: bucket.id.toString(), timestamp, ...bucket.dimensions };
90
+ for (const aggregate of def.aggregates) {
91
+ row[aggregate.column ?? aggregate.field] = storedValue(bucket.values[aggregate.field], aggregate.kind);
92
+ }
93
+ target.set(row);
94
+ if (hasCumulative) {
95
+ state.set({ id: stateId(bucket.dimKey), aggregation: group, payload: JSON.stringify(bucket.next) });
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * graph-node's `SeqGenerator::id` for an Int8: the block number in the upper 32
102
+ * bits and the block's id sequence below, shared by its handlers in the order
103
+ * they run.
104
+ */
105
+ export function timeseriesId(blockNumber: number, sequence: number): string {
106
+ return ((BigInt(blockNumber) << 32n) + BigInt(sequence)).toString();
107
+ }
108
+
109
+ export { INTERVAL_MICROS };