envio 3.5.1 → 3.6.0

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 (48) 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 +12 -0
  6. package/src/ChainState.res.mjs +15 -2
  7. package/src/ChainState.resi +3 -0
  8. package/src/Config.res +60 -2
  9. package/src/Config.res.mjs +49 -13
  10. package/src/EffectState.res +2 -2
  11. package/src/EffectState.res.mjs +2 -2
  12. package/src/EntityTables.res +32 -0
  13. package/src/EntityTables.res.mjs +44 -0
  14. package/src/Envio.res +7 -7
  15. package/src/Envio.res.mjs +5 -6
  16. package/src/Hasura.res +37 -3
  17. package/src/Hasura.res.mjs +23 -5
  18. package/src/InMemoryStore.res +48 -8
  19. package/src/InMemoryStore.res.mjs +37 -7
  20. package/src/IndexerState.res +30 -24
  21. package/src/IndexerState.res.mjs +20 -35
  22. package/src/IndexerState.resi +8 -6
  23. package/src/Internal.res +29 -11
  24. package/src/Internal.res.mjs +27 -10
  25. package/src/LoadLayer.res +39 -8
  26. package/src/LoadLayer.res.mjs +36 -9
  27. package/src/LoadLayer.resi +2 -0
  28. package/src/Persistence.res +12 -1
  29. package/src/PgStorage.res +183 -37
  30. package/src/PgStorage.res.mjs +149 -34
  31. package/src/PruneStaleHistory.res +1 -0
  32. package/src/PruneStaleHistory.res.mjs +2 -1
  33. package/src/Sink.res +3 -3
  34. package/src/Sink.res.mjs +2 -2
  35. package/src/TestIndexer.res +128 -17
  36. package/src/TestIndexer.res.mjs +75 -9
  37. package/src/UserContext.res +19 -4
  38. package/src/UserContext.res.mjs +15 -9
  39. package/src/Writing.res +8 -16
  40. package/src/Writing.res.mjs +10 -10
  41. package/src/bindings/ClickHouse.res +40 -4
  42. package/src/bindings/ClickHouse.res.mjs +37 -5
  43. package/src/db/EntityHistory.res +64 -21
  44. package/src/db/EntityHistory.res.mjs +43 -22
  45. package/src/db/InternalTable.res.mjs +31 -31
  46. package/src/db/Table.res +24 -1
  47. package/src/db/Table.res.mjs +26 -4
  48. package/svm.schema.json +7 -0
@@ -0,0 +1,44 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as ErrorHandling from "./ErrorHandling.res.mjs";
4
+ import * as InMemoryTable from "./InMemoryTable.res.mjs";
5
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
6
+
7
+ let UndefinedEntity = /* @__PURE__ */Primitive_exceptions.create("EntityTables.UndefinedEntity");
8
+
9
+ function make(entities) {
10
+ let init = {};
11
+ entities.forEach(entityConfig => {
12
+ init[entityConfig.name] = InMemoryTable.Entity.make();
13
+ });
14
+ return init;
15
+ }
16
+
17
+ function get(self, entityName) {
18
+ let table = self[entityName];
19
+ if (table !== undefined) {
20
+ return table;
21
+ } else {
22
+ return ErrorHandling.mkLogAndRaise(undefined, "Unexpected, entity InMemoryTable is undefined", {
23
+ RE_EXN_ID: UndefinedEntity,
24
+ entityName: entityName
25
+ });
26
+ }
27
+ }
28
+
29
+ function crossChain(entities) {
30
+ return entities.filter(entityConfig => entityConfig.crossChain);
31
+ }
32
+
33
+ function perChain(entities) {
34
+ return entities.filter(entityConfig => !entityConfig.crossChain);
35
+ }
36
+
37
+ export {
38
+ UndefinedEntity,
39
+ make,
40
+ get,
41
+ crossChain,
42
+ perChain,
43
+ }
44
+ /* ErrorHandling Not a pure module */
package/src/Envio.res CHANGED
@@ -186,7 +186,8 @@ and effectOptions<'input, 'output> = {
186
186
  rateLimit: rateLimit,
187
187
  /** Whether the effect should be cached. */
188
188
  cache?: bool,
189
- /** Whether the effect's cache is shared across all chains. Defaults to `true`.
189
+ /** Whether the effect's cache is shared across all chains. Defaults to `true`,
190
+ or to `false` when config.yaml sets `disable_default_cross_chain: true`.
190
191
  Set to `false` to isolate the cache (and rate limiting) per chain and enable
191
192
  `context.chain.id` inside the handler. */
192
193
  crossChain?: bool,
@@ -196,8 +197,8 @@ and effectContext = {
196
197
  log: logger,
197
198
  effect: 'input 'output. (effect<'input, 'output>, 'input) => promise<'output>,
198
199
  mutable cache: bool,
199
- /** The chain the effect was called on. Only available on effects created with
200
- `crossChain: false`; accessing it on a cross-chain effect throws. */
200
+ /** The chain the effect was called on. Only available on chain-scoped
201
+ effects; accessing it on a cross-chain effect throws. */
201
202
  chain: effectChain,
202
203
  }
203
204
  and effectArgs<'input> = {
@@ -259,10 +260,9 @@ let createEffect = (
259
260
  | Some(true) => true
260
261
  | _ => false
261
262
  },
262
- crossChain: switch options.crossChain {
263
- | Some(false) => false
264
- | _ => true
265
- },
263
+ // Left unresolved: the config's `defaultCrossChain` fills it in when the
264
+ // effect didn't state one, and the config isn't available here.
265
+ crossChain: options.crossChain,
266
266
  rateLimit: switch options.rateLimit {
267
267
  | Disable => None
268
268
  | Enable({calls, per}) =>
package/src/Envio.res.mjs CHANGED
@@ -30,12 +30,11 @@ function createEffect(options, handler) {
30
30
  output: s.m(outputSchema)
31
31
  }));
32
32
  let match = options.cache;
33
- let match$1 = options.crossChain;
34
- let match$2 = options.rateLimit;
33
+ let match$1 = options.rateLimit;
35
34
  let tmp;
36
- tmp = match$2 === false ? undefined : ({
37
- callsPerDuration: match$2.calls,
38
- durationMs: durationToMs(match$2.per)
35
+ tmp = match$1 === false ? undefined : ({
36
+ callsPerDuration: match$1.calls,
37
+ durationMs: durationToMs(match$1.per)
39
38
  });
40
39
  return {
41
40
  name: options.name,
@@ -45,7 +44,7 @@ function createEffect(options, handler) {
45
44
  outputSchema: outputSchema
46
45
  },
47
46
  defaultShouldCache: match !== undefined ? match : false,
48
- crossChain: match$1 !== undefined ? match$1 : true,
47
+ crossChain: options.crossChain,
49
48
  output: outputSchema,
50
49
  input: S$RescriptSchema.schema(param => options.input),
51
50
  rateLimit: tmp
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,