envio 3.5.1 → 3.6.1-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 (84) hide show
  1. package/evm.schema.json +7 -0
  2. package/fuel.schema.json +7 -0
  3. package/index.d.ts +33 -10
  4. package/package.json +6 -6
  5. package/src/ChainState.res +22 -4
  6. package/src/ChainState.res.mjs +20 -3
  7. package/src/ChainState.resi +3 -0
  8. package/src/Config.res +72 -2
  9. package/src/Config.res.mjs +57 -15
  10. package/src/Core.res +15 -0
  11. package/src/Core.res.mjs +11 -0
  12. package/src/EffectState.res +2 -2
  13. package/src/EffectState.res.mjs +2 -2
  14. package/src/EntityTables.res +32 -0
  15. package/src/EntityTables.res.mjs +44 -0
  16. package/src/Envio.res +7 -7
  17. package/src/Envio.res.mjs +5 -6
  18. package/src/EventProcessing.res +6 -6
  19. package/src/EventProcessing.res.mjs +8 -6
  20. package/src/HandlerLoader.res +20 -8
  21. package/src/HandlerLoader.res.mjs +11 -2
  22. package/src/Hasura.res +37 -3
  23. package/src/Hasura.res.mjs +23 -5
  24. package/src/InMemoryStore.res +48 -8
  25. package/src/InMemoryStore.res.mjs +37 -7
  26. package/src/IndexerState.res +30 -24
  27. package/src/IndexerState.res.mjs +20 -35
  28. package/src/IndexerState.resi +8 -6
  29. package/src/Internal.res +29 -11
  30. package/src/Internal.res.mjs +27 -10
  31. package/src/LoadLayer.res +39 -8
  32. package/src/LoadLayer.res.mjs +36 -9
  33. package/src/LoadLayer.resi +2 -0
  34. package/src/Metrics.res +5 -5
  35. package/src/Metrics.res.mjs +5 -1
  36. package/src/Persistence.res +12 -1
  37. package/src/PgStorage.res +183 -37
  38. package/src/PgStorage.res.mjs +149 -34
  39. package/src/PruneStaleHistory.res +1 -0
  40. package/src/PruneStaleHistory.res.mjs +2 -1
  41. package/src/Sink.res +3 -3
  42. package/src/Sink.res.mjs +2 -2
  43. package/src/TestIndexer.res +128 -17
  44. package/src/TestIndexer.res.mjs +75 -9
  45. package/src/UserContext.res +375 -53
  46. package/src/UserContext.res.mjs +281 -39
  47. package/src/Writing.res +8 -16
  48. package/src/Writing.res.mjs +10 -10
  49. package/src/bindings/ClickHouse.res +40 -4
  50. package/src/bindings/ClickHouse.res.mjs +37 -5
  51. package/src/db/EntityHistory.res +64 -21
  52. package/src/db/EntityHistory.res.mjs +43 -22
  53. package/src/db/InternalTable.res.mjs +31 -31
  54. package/src/db/Table.res +24 -1
  55. package/src/db/Table.res.mjs +26 -4
  56. package/src/sources/SourceManager.res +6 -12
  57. package/src/sources/SourceManager.res.mjs +8 -5
  58. package/src/subgraph/blocks.ts +176 -0
  59. package/src/subgraph/calls.ts +213 -0
  60. package/src/subgraph/conformance.ts +99 -0
  61. package/src/subgraph/division.ts +100 -0
  62. package/src/subgraph/errors.ts +90 -0
  63. package/src/subgraph/graph-ts-types/VERSION +2 -0
  64. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  65. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  66. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  67. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  68. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  69. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  70. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  71. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  72. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  73. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  74. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  75. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  76. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  77. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  78. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  79. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  80. package/src/subgraph/graph-ts.ts +1695 -0
  81. package/src/subgraph/hosts.ts +148 -0
  82. package/src/subgraph/runtime.ts +827 -0
  83. package/src/subgraph/scope.ts +63 -0
  84. package/svm.schema.json +7 -0
package/src/Hasura.res CHANGED
@@ -281,6 +281,21 @@ let createSelectPermission = async (
281
281
  )
282
282
  }
283
283
 
284
+ // The column_mapping references columns by their db names. A reference between
285
+ // two per-chain entities is only meaningful within one chain, so the chain-id
286
+ // column joins alongside the id — without it the relationship would resolve to
287
+ // another chain's row with the same id.
288
+ let makeColumnMapping = (~relationalKey, ~isDerivedFrom, ~chainIdColumn) => {
289
+ let pairs = [
290
+ isDerivedFrom ? `"id": "${relationalKey}"` : `"${relationalKey}": "id"`,
291
+ ]
292
+ switch chainIdColumn {
293
+ | Some(column) => pairs->Array.push(`"${column}": "${column}"`)->ignore
294
+ | None => ()
295
+ }
296
+ `{${pairs->Array.joinUnsafe(", ")}}`
297
+ }
298
+
284
299
  let createEntityRelationship = async (
285
300
  ~endpoint,
286
301
  ~auth,
@@ -291,10 +306,9 @@ let createEntityRelationship = async (
291
306
  ~objectName: string,
292
307
  ~mappedEntity: string,
293
308
  ~isDerivedFrom: bool,
309
+ ~chainIdColumn: option<string>,
294
310
  ~comment: option<string>=?,
295
311
  ) => {
296
- // The column_mapping references columns by their db names
297
- let derivedFromTo = isDerivedFrom ? `"id": "${relationalKey}"` : `"${relationalKey}" : "id"`
298
312
 
299
313
  let tableJson = {
300
314
  "schema": pgSchema,
@@ -306,7 +320,9 @@ let createEntityRelationship = async (
306
320
  "schema": pgSchema,
307
321
  "name": mappedEntity,
308
322
  },
309
- "column_mapping": JSON.parseOrThrow(`{${derivedFromTo}}`),
323
+ "column_mapping": JSON.parseOrThrow(
324
+ makeColumnMapping(~relationalKey, ~isDerivedFrom, ~chainIdColumn),
325
+ ),
310
326
  },
311
327
  }->(Utils.magic: {..} => JSON.t)
312
328
 
@@ -388,9 +404,25 @@ let trackDatabase = async (
388
404
  )
389
405
  }
390
406
 
407
+ // Both sides of a relationship must be per-chain for the chain to be part of
408
+ // the join; a per-chain entity referencing a cross-chain one resolves by id
409
+ // alone. The reverse (cross-chain referencing per-chain) is rejected at
410
+ // codegen, so it can't reach here.
411
+ let chainIdColumnOf = (entityName: string) =>
412
+ userEntities
413
+ ->Array.find((e: Internal.entityConfig) => e.name === entityName)
414
+ ->Option.flatMap(e => e.table->Table.getChainIdField)
415
+ ->Option.map(Table.getPgDbFieldName)
416
+
391
417
  for i in 0 to userEntities->Array.length - 1 {
392
418
  let entityConfig = userEntities->Array.getUnsafe(i)
393
419
  let {tableName} = entityConfig.table
420
+ let ownChainIdColumn = entityConfig.table->Table.getChainIdField->Option.map(Table.getPgDbFieldName)
421
+ let sharedChainIdColumn = mappedEntity =>
422
+ switch (ownChainIdColumn, chainIdColumnOf(mappedEntity)) {
423
+ | (Some(column), Some(_)) => Some(column)
424
+ | _ => None
425
+ }
394
426
 
395
427
  //Set array relationships
396
428
  let derivedFromFields = entityConfig.table->Table.getDerivedFromFields
@@ -410,6 +442,7 @@ let trackDatabase = async (
410
442
  ~objectName=derivedFromField.fieldName,
411
443
  ~relationalKey=relationalFieldName,
412
444
  ~mappedEntity=derivedFromField.derivedFromEntity,
445
+ ~chainIdColumn=sharedChainIdColumn(derivedFromField.derivedFromEntity),
413
446
  ~comment=?derivedFromField.description,
414
447
  )
415
448
  }
@@ -428,6 +461,7 @@ let trackDatabase = async (
428
461
  ~objectName=field.fieldName,
429
462
  ~relationalKey=field->Table.getPgDbFieldName,
430
463
  ~mappedEntity=linkedEntityName,
464
+ ~chainIdColumn=sharedChainIdColumn(linkedEntityName),
431
465
  ~comment=?field.description,
432
466
  )
433
467
  }
@@ -7,6 +7,7 @@ import * as Utils from "./Utils.res.mjs";
7
7
  import * as Schema from "./db/Schema.res.mjs";
8
8
  import * as Logging from "./Logging.res.mjs";
9
9
  import * as InternalTable from "./db/InternalTable.res.mjs";
10
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
11
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
11
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
12
13
 
@@ -247,8 +248,15 @@ async function createSelectPermission(endpoint, auth, tableName, pgSchema, respo
247
248
  });
248
249
  }
249
250
 
250
- async function createEntityRelationship(endpoint, auth, pgSchema, tableName, relationshipType, relationalKey, objectName, mappedEntity, isDerivedFrom, comment) {
251
- let derivedFromTo = isDerivedFrom ? `"id": "` + relationalKey + `"` : `"` + relationalKey + `" : "id"`;
251
+ function makeColumnMapping(relationalKey, isDerivedFrom, chainIdColumn) {
252
+ let pairs = [isDerivedFrom ? `"id": "` + relationalKey + `"` : `"` + relationalKey + `": "id"`];
253
+ if (chainIdColumn !== undefined) {
254
+ pairs.push(`"` + chainIdColumn + `": "` + chainIdColumn + `"`);
255
+ }
256
+ return `{` + pairs.join(", ") + `}`;
257
+ }
258
+
259
+ async function createEntityRelationship(endpoint, auth, pgSchema, tableName, relationshipType, relationalKey, objectName, mappedEntity, isDerivedFrom, chainIdColumn, comment) {
252
260
  let tableJson = {
253
261
  schema: pgSchema,
254
262
  name: tableName
@@ -259,7 +267,7 @@ async function createEntityRelationship(endpoint, auth, pgSchema, tableName, rel
259
267
  schema: pgSchema,
260
268
  name: mappedEntity
261
269
  },
262
- column_mapping: JSON.parse(`{` + derivedFromTo + `}`)
270
+ column_mapping: JSON.parse(makeColumnMapping(relationalKey, isDerivedFrom, chainIdColumn))
263
271
  }
264
272
  };
265
273
  let args = {
@@ -313,21 +321,30 @@ async function trackDatabase(endpoint, auth, pgSchema, userEntities, aggregateEn
313
321
  let tableName = tableNames[i];
314
322
  await createSelectPermission(endpoint, auth, tableName, pgSchema, responseLimit, aggregateEntities);
315
323
  }
324
+ let chainIdColumnOf = entityName => Stdlib_Option.map(Stdlib_Option.flatMap(userEntities.find(e => e.name === entityName), e => Table.getChainIdField(e.table)), Table.getPgDbFieldName);
316
325
  for (let i$1 = 0, i_finish$1 = userEntities.length; i$1 < i_finish$1; ++i$1) {
317
326
  let entityConfig = userEntities[i$1];
318
327
  let match = entityConfig.table;
319
328
  let tableName$1 = match.tableName;
329
+ let ownChainIdColumn = Stdlib_Option.map(Table.getChainIdField(entityConfig.table), Table.getPgDbFieldName);
330
+ let sharedChainIdColumn = mappedEntity => {
331
+ let match = chainIdColumnOf(mappedEntity);
332
+ if (ownChainIdColumn !== undefined && match !== undefined) {
333
+ return ownChainIdColumn;
334
+ }
335
+ };
320
336
  let derivedFromFields = Table.getDerivedFromFields(entityConfig.table);
321
337
  for (let j = 0, j_finish = derivedFromFields.length; j < j_finish; ++j) {
322
338
  let derivedFromField = derivedFromFields[j];
323
339
  let relationalFieldName = Utils.unwrapResultExn(Schema.getDerivedFromPgFieldName(schema, derivedFromField));
324
- await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "array", relationalFieldName, derivedFromField.fieldName, derivedFromField.derivedFromEntity, true, derivedFromField.description);
340
+ await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "array", relationalFieldName, derivedFromField.fieldName, derivedFromField.derivedFromEntity, true, sharedChainIdColumn(derivedFromField.derivedFromEntity), derivedFromField.description);
325
341
  }
326
342
  let linkedEntityFields = Table.getLinkedEntityFields(entityConfig.table);
327
343
  for (let j$1 = 0, j_finish$1 = linkedEntityFields.length; j$1 < j_finish$1; ++j$1) {
328
344
  let match$1 = linkedEntityFields[j$1];
345
+ let linkedEntityName = match$1[1];
329
346
  let field = match$1[0];
330
- await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "object", Table.getPgDbFieldName(field), field.fieldName, match$1[1], false, field.description);
347
+ await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "object", Table.getPgDbFieldName(field), field.fieldName, linkedEntityName, false, sharedChainIdColumn(linkedEntityName), field.description);
331
348
  }
332
349
  }
333
350
  return Logging.info("Hasura configuration completed");
@@ -346,6 +363,7 @@ export {
346
363
  makeColumnConfigs,
347
364
  trackTables,
348
365
  createSelectPermission,
366
+ makeColumnMapping,
349
367
  createEntityRelationship,
350
368
  trackDatabase,
351
369
  }
@@ -2,11 +2,45 @@
2
2
  // mutations route through IndexerState's domain operations; the write loop and
3
3
  // capacity/flush coordination live in Writing.
4
4
 
5
+ // The scope must match the entity's own: a cross-chain entity has one table on
6
+ // the indexer, a per-chain entity one table per ChainState. Taking the scope
7
+ // rather than an optional chain id keeps a dummy chain id unrepresentable.
5
8
  let getInMemTable = (
6
9
  state: IndexerState.t,
7
10
  ~entityConfig: Internal.entityConfig,
11
+ ~scope: Internal.chainScope,
8
12
  ): InMemoryTable.Entity.t =>
9
- state->IndexerState.entities->IndexerState.EntityTables.get(~entityName=entityConfig.name)
13
+ switch scope {
14
+ | CrossChain => state->IndexerState.entities
15
+ | Chain(chainId) => state->IndexerState.getChainState(~chainId)->ChainState.entities
16
+ }->EntityTables.get(~entityName=entityConfig.name)
17
+
18
+ // The scope a given entity's rows live in when reached from a handler running
19
+ // on `chainId`.
20
+ let entityScope = (entityConfig: Internal.entityConfig, ~chainId): Internal.chainScope =>
21
+ entityConfig.crossChain ? CrossChain : Chain(chainId)
22
+
23
+ // The chain a row loaded from storage belongs to, taken off the row so what's
24
+ // left matches the entity schema the handlers see. A per-chain entity whose row
25
+ // carries no chain id would silently land in the wrong partition, so it throws.
26
+ let takeRowScope = (
27
+ entity: Internal.entity,
28
+ ~entityConfig: Internal.entityConfig,
29
+ ): Internal.chainScope =>
30
+ switch entityConfig.table->Table.getChainIdField {
31
+ | None => CrossChain
32
+ | Some(field) =>
33
+ let row = entity->(Utils.magic: Internal.entity => dict<ChainId.t>)
34
+ switch row->Utils.Dict.dangerouslyGetNonOption(field.fieldName) {
35
+ | Some(chainId) =>
36
+ row->Utils.Dict.deleteInPlace(field.fieldName)
37
+ Chain(chainId)
38
+ | None =>
39
+ JsError.throwWithMessage(
40
+ `Rollback row for the per-chain entity "${entityConfig.name}" with id "${entity.id}" is missing its "${field.fieldName}" value.`,
41
+ )
42
+ }
43
+ }
10
44
 
11
45
  let getEffectInMemTable = (
12
46
  state: IndexerState.t,
@@ -114,16 +148,16 @@ let prepareRollbackDiff = async (
114
148
  let _ = await persistence.allEntities
115
149
  ->Array.filter(entityConfig => entityConfig.storage.postgres)
116
150
  ->Array.map(async entityConfig => {
117
- let entityTable = state->getInMemTable(~entityConfig)
118
-
119
- let (removedIds, restoredEntitiesResult) = await persistence.storage.getRollbackData(
151
+ let (removals, restoredEntitiesResult) = await persistence.storage.getRollbackData(
120
152
  ~entityConfig,
121
153
  ~rollbackTargetCheckpointId,
122
154
  )
123
155
 
124
- removedIds->Array.forEach(entityId => {
156
+ removals->Array.forEach(({entityId, scope}: Persistence.rollbackRemoval) => {
125
157
  deletedEntities->Utils.Dict.push(entityConfig.name, entityId)
126
- entityTable->InMemoryTable.Entity.set(
158
+ state
159
+ ->getInMemTable(~entityConfig, ~scope)
160
+ ->InMemoryTable.Entity.set(
127
161
  ~committedCheckpointId,
128
162
  Delete({
129
163
  entityId,
@@ -138,8 +172,11 @@ let prepareRollbackDiff = async (
138
172
  ->(Utils.magic: array<unknown> => array<Internal.entity>)
139
173
 
140
174
  restoredEntities->Array.forEach((entity: Internal.entity) => {
175
+ let scope = entity->takeRowScope(~entityConfig)
141
176
  setEntities->Utils.Dict.push(entityConfig.name, entity.id)
142
- entityTable->InMemoryTable.Entity.set(
177
+ state
178
+ ->getInMemTable(~entityConfig, ~scope)
179
+ ->InMemoryTable.Entity.set(
143
180
  ~committedCheckpointId,
144
181
  Set({
145
182
  entityId: entity.id->EntityId.unsafeOfString,
@@ -162,7 +199,10 @@ let prepareRollbackDiff = async (
162
199
  // registered them: the store is where they already live, and it knows which
163
200
  // ones the database hasn't seen yet.
164
201
  let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
165
- let inMemTable = state->getInMemTable(~entityConfig=InternalTable.EnvioAddresses.entityConfig)
202
+ let inMemTable = state->getInMemTable(
203
+ ~entityConfig=InternalTable.EnvioAddresses.entityConfig,
204
+ ~scope=CrossChain,
205
+ )
166
206
  let committedCheckpointId = state->IndexerState.committedCheckpointId
167
207
 
168
208
  batch.progressedChainsById->Utils.Dict.forEach(progressedChain => {
@@ -6,13 +6,40 @@ import * as Config from "./Config.res.mjs";
6
6
  import * as Internal from "./Internal.res.mjs";
7
7
  import * as ChainState from "./ChainState.res.mjs";
8
8
  import * as EffectState from "./EffectState.res.mjs";
9
+ import * as EntityTables from "./EntityTables.res.mjs";
9
10
  import * as IndexerState from "./IndexerState.res.mjs";
10
11
  import * as InMemoryTable from "./InMemoryTable.res.mjs";
11
12
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
13
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
14
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
12
15
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
13
16
 
14
- function getInMemTable(state, entityConfig) {
15
- return IndexerState.EntityTables.get(IndexerState.entities(state), entityConfig.name);
17
+ function getInMemTable(state, entityConfig, scope) {
18
+ let tmp;
19
+ tmp = scope === "crossChain" ? IndexerState.entities(state) : ChainState.entities(IndexerState.getChainState(state, scope));
20
+ return EntityTables.get(tmp, entityConfig.name);
21
+ }
22
+
23
+ function entityScope(entityConfig, chainId) {
24
+ if (entityConfig.crossChain) {
25
+ return "crossChain";
26
+ } else {
27
+ return chainId;
28
+ }
29
+ }
30
+
31
+ function takeRowScope(entity, entityConfig) {
32
+ let field = Table.getChainIdField(entityConfig.table);
33
+ if (field === undefined) {
34
+ return "crossChain";
35
+ }
36
+ let chainId = entity[field.fieldName];
37
+ if (chainId !== undefined) {
38
+ Utils.Dict.deleteInPlace(entity, field.fieldName);
39
+ return Primitive_option.valFromOption(chainId);
40
+ } else {
41
+ return Stdlib_JsError.throwWithMessage(`Rollback row for the per-chain entity "` + entityConfig.name + `" with id "` + entity.id + `" is missing its "` + field.fieldName + `" value.`);
42
+ }
16
43
  }
17
44
 
18
45
  function getEffectInMemTable(state, effect, scope) {
@@ -88,11 +115,11 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
88
115
  let deletedEntities = {};
89
116
  let setEntities = {};
90
117
  await Promise.all(persistence.allEntities.filter(entityConfig => entityConfig.storage.postgres).map(async entityConfig => {
91
- let entityTable = getInMemTable(state, entityConfig);
92
118
  let match = await persistence.storage.getRollbackData(entityConfig, rollbackTargetCheckpointId);
93
- match[0].forEach(entityId => {
119
+ match[0].forEach(param => {
120
+ let entityId = param.entityId;
94
121
  Utils.Dict.push(deletedEntities, entityConfig.name, entityId);
95
- InMemoryTable.Entity.set(entityTable, committedCheckpointId, {
122
+ InMemoryTable.Entity.set(getInMemTable(state, entityConfig, param.scope), committedCheckpointId, {
96
123
  type: "DELETE",
97
124
  entityId: entityId,
98
125
  checkpointId: rollbackDiffCheckpointId
@@ -100,8 +127,9 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
100
127
  });
101
128
  let restoredEntities = S$RescriptSchema.parseOrThrow(match[1], Table.pgRowsSchema(entityConfig.table));
102
129
  restoredEntities.forEach(entity => {
130
+ let scope = takeRowScope(entity, entityConfig);
103
131
  Utils.Dict.push(setEntities, entityConfig.name, entity.id);
104
- InMemoryTable.Entity.set(entityTable, committedCheckpointId, {
132
+ InMemoryTable.Entity.set(getInMemTable(state, entityConfig, scope), committedCheckpointId, {
105
133
  type: "SET",
106
134
  entityId: entity.id,
107
135
  entity: entity,
@@ -116,7 +144,7 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
116
144
  }
117
145
 
118
146
  function setBatchDcs(state, batch) {
119
- let inMemTable = getInMemTable(state, Config.EnvioAddresses.entityConfig);
147
+ let inMemTable = getInMemTable(state, Config.EnvioAddresses.entityConfig, "crossChain");
120
148
  let committedCheckpointId = IndexerState.committedCheckpointId(state);
121
149
  Utils.Dict.forEach(batch.progressedChainsById, progressedChain => {
122
150
  let chainId = progressedChain.fetchState.chainId;
@@ -155,6 +183,8 @@ function setBatchDcs(state, batch) {
155
183
 
156
184
  export {
157
185
  getInMemTable,
186
+ entityScope,
187
+ takeRowScope,
158
188
  getEffectInMemTable,
159
189
  hasEffectOutput,
160
190
  getEffectOutputUnsafe,
@@ -5,28 +5,6 @@ type rollbackState =
5
5
  | FoundReorgDepth({chainId: ChainId.t, rollbackTargetBlockNumber: int})
6
6
  | RollbackReady({eventsProcessedDiffByChain: dict<float>})
7
7
 
8
- module EntityTables = {
9
- type t = dict<InMemoryTable.Entity.t>
10
- exception UndefinedEntity({entityName: string})
11
- let make = (entities: array<Internal.entityConfig>): t => {
12
- let init = Dict.make()
13
- entities->Array.forEach(entityConfig => {
14
- init->Dict.set((entityConfig.name :> string), InMemoryTable.Entity.make())
15
- })
16
- init
17
- }
18
-
19
- let get = (self: t, ~entityName: string) => {
20
- switch self->Utils.Dict.dangerouslyGetNonOption(entityName) {
21
- | Some(table) => table
22
- | None =>
23
- UndefinedEntity({entityName: entityName})->ErrorHandling.mkLogAndRaise(
24
- ~msg="Unexpected, entity InMemoryTable is undefined",
25
- )
26
- }
27
- }
28
- }
29
-
30
8
  // Per-(contract, event) handler counters rendered into the
31
9
  // envio_processing_handler_* and envio_preload_handler_* metrics.
32
10
  type handlerStat = {
@@ -75,6 +53,8 @@ type t = {
75
53
  persistence: Persistence.t,
76
54
  // --- In-memory store: entity/effect tables and the pending-write queue. ---
77
55
  allEntities: array<Internal.entityConfig>,
56
+ // Cross-chain entities only; each ChainState holds its own partition for the
57
+ // per-chain ones.
78
58
  mutable entities: EntityTables.t,
79
59
  effectState: EffectState.t,
80
60
  mutable rollback: option<Persistence.rollback>,
@@ -186,7 +166,7 @@ let make = (
186
166
  config,
187
167
  persistence,
188
168
  allEntities: persistence.allEntities,
189
- entities: EntityTables.make(persistence.allEntities),
169
+ entities: EntityTables.make(persistence.allEntities->EntityTables.crossChain),
190
170
  effectState: EffectState.make(),
191
171
  rollback: None,
192
172
  committedCheckpointId,
@@ -467,6 +447,30 @@ let config = (state: t) => state.config
467
447
  let persistence = (state: t) => state.persistence
468
448
  let allEntities = (state: t) => state.allEntities
469
449
  let entities = (state: t) => state.entities
450
+
451
+ // Every in-memory entity table across all scopes, cross-chain first. The size,
452
+ // drop and flush passes walk the whole store this way instead of assuming a
453
+ // single partition.
454
+ let eachEntityTable = (state: t, fn: (~entityConfig: Internal.entityConfig, ~scope: Internal.chainScope, ~table: InMemoryTable.Entity.t) => unit) => {
455
+ let chainStates = state.crossChainState->CrossChainState.chainStates
456
+ state.allEntities->Array.forEach(entityConfig =>
457
+ if entityConfig.crossChain {
458
+ fn(
459
+ ~entityConfig,
460
+ ~scope=Internal.CrossChain,
461
+ ~table=state.entities->EntityTables.get(~entityName=entityConfig.name),
462
+ )
463
+ } else {
464
+ chainStates->Utils.Dict.forEach(chainState => {
465
+ fn(
466
+ ~entityConfig,
467
+ ~scope=Internal.Chain((chainState->ChainState.chainConfig).id),
468
+ ~table=chainState->ChainState.entities->EntityTables.get(~entityName=entityConfig.name),
469
+ )
470
+ })
471
+ }
472
+ )
473
+ }
470
474
  let effectState = (state: t) => state.effectState
471
475
  let committedCheckpointId = (state: t) => state.committedCheckpointId
472
476
  let processedCheckpointId = (state: t) => state.processedCheckpointId
@@ -802,7 +806,9 @@ let beginRollbackDiff = (
802
806
  ~diffCheckpointId,
803
807
  ~progressBlockNumberByChainId,
804
808
  ) => {
805
- state.entities = EntityTables.make(state.allEntities)
809
+ let perChainEntities = state.allEntities->EntityTables.perChain
810
+ state.entities = EntityTables.make(state.allEntities->EntityTables.crossChain)
811
+ state->chainStates->Utils.Dict.forEach(cs => cs->ChainState.resetEntities(~perChainEntities))
806
812
  state.effectState->EffectState.resetForRollback
807
813
  state.rollback = Some({
808
814
  targetCheckpointId,
@@ -12,40 +12,17 @@ import * as ChainState from "./ChainState.res.mjs";
12
12
  import * as EffectState from "./EffectState.res.mjs";
13
13
  import * as LoadManager from "./LoadManager.res.mjs";
14
14
  import * as Performance from "./bindings/Performance.res.mjs";
15
+ import * as EntityTables from "./EntityTables.res.mjs";
15
16
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
16
- import * as InMemoryTable from "./InMemoryTable.res.mjs";
17
17
  import * as SourceManager from "./sources/SourceManager.res.mjs";
18
18
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
19
19
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
20
20
  import * as CrossChainState from "./CrossChainState.res.mjs";
21
21
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
22
22
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
23
- import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
24
23
  import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
25
24
 
26
- let UndefinedEntity = /* @__PURE__ */Primitive_exceptions.create("IndexerState.EntityTables.UndefinedEntity");
27
-
28
- function make(entities) {
29
- let init = {};
30
- entities.forEach(entityConfig => {
31
- init[entityConfig.name] = InMemoryTable.Entity.make();
32
- });
33
- return init;
34
- }
35
-
36
- function get(self, entityName) {
37
- let table = self[entityName];
38
- if (table !== undefined) {
39
- return table;
40
- } else {
41
- return ErrorHandling.mkLogAndRaise(undefined, "Unexpected, entity InMemoryTable is undefined", {
42
- RE_EXN_ID: UndefinedEntity,
43
- entityName: entityName
44
- });
45
- }
46
- }
47
-
48
- function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
25
+ function make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
49
26
  let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
50
27
  let committedCheckpointId = committedCheckpointIdOpt !== undefined ? committedCheckpointIdOpt : Internal.initialCheckpointId;
51
28
  let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
@@ -60,7 +37,7 @@ function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime
60
37
  config: config,
61
38
  persistence: persistence,
62
39
  allEntities: persistence.allEntities,
63
- entities: make(persistence.allEntities),
40
+ entities: EntityTables.make(EntityTables.crossChain(persistence.allEntities)),
64
41
  effectState: EffectState.make(),
65
42
  rollback: undefined,
66
43
  committedCheckpointId: committedCheckpointId,
@@ -125,7 +102,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
125
102
  let chainConfig = ChainMap.get(config.chainMap, chainId);
126
103
  chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval);
127
104
  });
128
- let state = make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
105
+ let state = make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
129
106
  CrossChainState.markCaughtUpOnResume(state.crossChainState);
130
107
  Utils.Dict.forEach(initialState.cache, param => {
131
108
  let count = param.count;
@@ -288,6 +265,17 @@ function entities(state) {
288
265
  return state.entities;
289
266
  }
290
267
 
268
+ function eachEntityTable(state, fn) {
269
+ let chainStates = CrossChainState.chainStates(state.crossChainState);
270
+ state.allEntities.forEach(entityConfig => {
271
+ if (entityConfig.crossChain) {
272
+ return fn(entityConfig, "crossChain", EntityTables.get(state.entities, entityConfig.name));
273
+ } else {
274
+ return Utils.Dict.forEach(chainStates, chainState => fn(entityConfig, ChainState.chainConfig(chainState).id, EntityTables.get(ChainState.entities(chainState), entityConfig.name)));
275
+ }
276
+ });
277
+ }
278
+
291
279
  function effectState(state) {
292
280
  return state.effectState;
293
281
  }
@@ -678,7 +666,9 @@ function markCommitted(state, upToCheckpointId) {
678
666
  }
679
667
 
680
668
  function beginRollbackDiff(state, targetCheckpointId, diffCheckpointId, progressBlockNumberByChainId) {
681
- state.entities = make(state.allEntities);
669
+ let perChainEntities = EntityTables.perChain(state.allEntities);
670
+ state.entities = EntityTables.make(EntityTables.crossChain(state.allEntities));
671
+ Utils.Dict.forEach(CrossChainState.chainStates(state.crossChainState), cs => ChainState.resetEntities(cs, perChainEntities));
682
672
  EffectState.resetForRollback(state.effectState);
683
673
  state.rollback = {
684
674
  targetCheckpointId: targetCheckpointId,
@@ -745,16 +735,10 @@ function takeChainMetaSnapshot(state) {
745
735
  }
746
736
  }
747
737
 
748
- let EntityTables = {
749
- make: make,
750
- get: get
751
- };
752
-
753
738
  let unexpectedErrorMsg = "Indexer has failed with an unexpected error";
754
739
 
755
740
  export {
756
- EntityTables,
757
- make$1 as make,
741
+ make,
758
742
  makeFromDbState,
759
743
  unexpectedErrorMsg,
760
744
  isStale,
@@ -781,6 +765,7 @@ export {
781
765
  persistence,
782
766
  allEntities,
783
767
  entities,
768
+ eachEntityTable,
784
769
  effectState,
785
770
  committedCheckpointId,
786
771
  processedCheckpointId,
@@ -9,12 +9,6 @@ type rollbackState =
9
9
  | FoundReorgDepth({chainId: ChainId.t, rollbackTargetBlockNumber: int})
10
10
  | RollbackReady({eventsProcessedDiffByChain: dict<float>})
11
11
 
12
- module EntityTables: {
13
- type t = dict<InMemoryTable.Entity.t>
14
- let make: array<Internal.entityConfig> => t
15
- let get: (t, ~entityName: string) => InMemoryTable.Entity.t
16
- }
17
-
18
12
  type t
19
13
 
20
14
  let make: (
@@ -80,6 +74,14 @@ let config: t => Config.t
80
74
  let persistence: t => Persistence.t
81
75
  let allEntities: t => array<Internal.entityConfig>
82
76
  let entities: t => EntityTables.t
77
+ let eachEntityTable: (
78
+ t,
79
+ (
80
+ ~entityConfig: Internal.entityConfig,
81
+ ~scope: Internal.chainScope,
82
+ ~table: InMemoryTable.Entity.t,
83
+ ) => unit,
84
+ ) => unit
83
85
  let effectState: t => EffectState.t
84
86
  let committedCheckpointId: t => Internal.checkpointId
85
87
  let processedCheckpointId: t => Internal.checkpointId
package/src/Internal.res CHANGED
@@ -744,6 +744,10 @@ type genericEntityConfig<'entity> = {
744
744
  schema: S.t<'entity>,
745
745
  table: Table.table,
746
746
  storage: entityStorage,
747
+ // Resolved by the CLI against the config's `defaultCrossChain` and the
748
+ // entity's `@crossChain`. When false the table carries a chain-id column in
749
+ // its primary key and every row belongs to exactly one chain.
750
+ crossChain: bool,
747
751
  }
748
752
  type entityConfig = genericEntityConfig<entity>
749
753
  external fromGenericEntityConfig: genericEntityConfig<'entity> => entityConfig = "%identity"
@@ -773,10 +777,11 @@ type effect = {
773
777
  handler: effectArgs => promise<effectOutput>,
774
778
  storageMeta: effectCacheStorageMeta,
775
779
  defaultShouldCache: bool,
776
- // When true (the default) a single cache is shared across every chain and the
777
- // handler must not read context.chain. When false the cache is isolated per
778
- // chain and context.chain.id is available.
779
- crossChain: bool,
780
+ // When true a single cache is shared across every chain and the handler must
781
+ // not read context.chain. When false the cache is isolated per chain and
782
+ // context.chain.id is available. None means the effect didn't say, and the
783
+ // config's `defaultCrossChain` decides.
784
+ crossChain: option<bool>,
780
785
  output: S.t<effectOutput>,
781
786
  input: S.t<effectInput>,
782
787
  rateLimit: option<rateLimitOptions>,
@@ -791,6 +796,26 @@ type chainScope =
791
796
  | @as("crossChain") CrossChain
792
797
  | Chain(ChainId.t)
793
798
 
799
+ let chainScopeToString = scope =>
800
+ switch scope {
801
+ | CrossChain => "crossChain"
802
+ | Chain(chainId) => chainId->ChainId.toString
803
+ }
804
+
805
+ let chainScopeChainId = scope =>
806
+ switch scope {
807
+ | CrossChain => None
808
+ | Chain(chainId) => Some(chainId)
809
+ }
810
+
811
+ // A copy of the entity carrying the chain-id column a per-chain entity's row
812
+ // needs. Never mutates the handler's object, which stays in the in-memory table.
813
+ let stampChainId = (entity: entity, ~fieldName, ~chainId: ChainId.t): entity =>
814
+ Utils.Dict.merge(
815
+ entity->(Utils.magic: entity => dict<unknown>),
816
+ Dict.fromArray([(fieldName, chainId->(Utils.magic: ChainId.t => unknown))]),
817
+ )->(Utils.magic: dict<unknown> => entity)
818
+
794
819
  let cacheTablePrefix = "envio_effect_"
795
820
 
796
821
  // The single reversible mapping between an effect's (name, scope) and its
@@ -806,13 +831,6 @@ module EffectCache = {
806
831
  | Chain(chainId) => `envio_${chainId->ChainId.toString}_effect_${effectName}`
807
832
  }
808
833
 
809
- // "crossChain" or the decimal chain id. Used as the `scope` Prometheus label.
810
- let scopeToString = scope =>
811
- switch scope {
812
- | CrossChain => "crossChain"
813
- | Chain(chainId) => chainId->ChainId.toString
814
- }
815
-
816
834
  // Only accepts a canonical decimal chain id ("7", not "007" or "1foo") —
817
835
  // the schema's parser follows parseFloat semantics and accepts both.
818
836
  let parseChainId = str =>