envio 3.5.1 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/evm.schema.json +7 -0
- package/fuel.schema.json +7 -0
- package/index.d.ts +33 -10
- package/package.json +6 -6
- package/src/ChainState.res +22 -4
- package/src/ChainState.res.mjs +20 -3
- package/src/ChainState.resi +3 -0
- package/src/Config.res +60 -2
- package/src/Config.res.mjs +49 -13
- package/src/EffectState.res +2 -2
- package/src/EffectState.res.mjs +2 -2
- package/src/EntityTables.res +32 -0
- package/src/EntityTables.res.mjs +44 -0
- package/src/Envio.res +7 -7
- package/src/Envio.res.mjs +5 -6
- package/src/Hasura.res +37 -3
- package/src/Hasura.res.mjs +23 -5
- package/src/InMemoryStore.res +48 -8
- package/src/InMemoryStore.res.mjs +37 -7
- package/src/IndexerState.res +30 -24
- package/src/IndexerState.res.mjs +20 -35
- package/src/IndexerState.resi +8 -6
- package/src/Internal.res +29 -11
- package/src/Internal.res.mjs +27 -10
- package/src/LoadLayer.res +39 -8
- package/src/LoadLayer.res.mjs +36 -9
- package/src/LoadLayer.resi +2 -0
- package/src/Metrics.res +5 -5
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +12 -1
- package/src/PgStorage.res +183 -37
- package/src/PgStorage.res.mjs +149 -34
- package/src/PruneStaleHistory.res +1 -0
- package/src/PruneStaleHistory.res.mjs +2 -1
- package/src/Sink.res +3 -3
- package/src/Sink.res.mjs +2 -2
- package/src/TestIndexer.res +128 -17
- package/src/TestIndexer.res.mjs +75 -9
- package/src/UserContext.res +19 -4
- package/src/UserContext.res.mjs +15 -9
- package/src/Writing.res +8 -16
- package/src/Writing.res.mjs +10 -10
- package/src/bindings/ClickHouse.res +40 -4
- package/src/bindings/ClickHouse.res.mjs +37 -5
- package/src/db/EntityHistory.res +64 -21
- package/src/db/EntityHistory.res.mjs +43 -22
- package/src/db/InternalTable.res.mjs +31 -31
- package/src/db/Table.res +24 -1
- package/src/db/Table.res.mjs +26 -4
- package/src/sources/SourceManager.res +6 -12
- package/src/sources/SourceManager.res.mjs +8 -5
- package/svm.schema.json +7 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// One in-memory table per entity, for a single chain scope. The indexer holds
|
|
2
|
+
// the cross-chain partition; each ChainState holds the per-chain one.
|
|
3
|
+
|
|
4
|
+
type t = dict<InMemoryTable.Entity.t>
|
|
5
|
+
|
|
6
|
+
exception UndefinedEntity({entityName: string})
|
|
7
|
+
|
|
8
|
+
let make = (entities: array<Internal.entityConfig>): t => {
|
|
9
|
+
let init = Dict.make()
|
|
10
|
+
entities->Array.forEach(entityConfig => {
|
|
11
|
+
init->Dict.set((entityConfig.name :> string), InMemoryTable.Entity.make())
|
|
12
|
+
})
|
|
13
|
+
init
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let get = (self: t, ~entityName: string) => {
|
|
17
|
+
switch self->Utils.Dict.dangerouslyGetNonOption(entityName) {
|
|
18
|
+
| Some(table) => table
|
|
19
|
+
| None =>
|
|
20
|
+
UndefinedEntity({entityName: entityName})->ErrorHandling.mkLogAndRaise(
|
|
21
|
+
~msg="Unexpected, entity InMemoryTable is undefined",
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Entities whose rows are shared by every chain, so their tables live on the
|
|
27
|
+
// indexer rather than on a ChainState.
|
|
28
|
+
let crossChain = (entities: array<Internal.entityConfig>) =>
|
|
29
|
+
entities->Array.filter((entityConfig: Internal.entityConfig) => entityConfig.crossChain)
|
|
30
|
+
|
|
31
|
+
let perChain = (entities: array<Internal.entityConfig>) =>
|
|
32
|
+
entities->Array.filter((entityConfig: Internal.entityConfig) => !entityConfig.crossChain)
|
|
@@ -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
|
|
200
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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.
|
|
34
|
-
let match$2 = options.rateLimit;
|
|
33
|
+
let match$1 = options.rateLimit;
|
|
35
34
|
let tmp;
|
|
36
|
-
tmp = match$
|
|
37
|
-
callsPerDuration: match$
|
|
38
|
-
durationMs: durationToMs(match$
|
|
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:
|
|
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(
|
|
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
|
}
|
package/src/Hasura.res.mjs
CHANGED
|
@@ -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
|
-
|
|
251
|
-
let
|
|
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(
|
|
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,
|
|
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
|
}
|
package/src/InMemoryStore.res
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
156
|
+
removals->Array.forEach(({entityId, scope}: Persistence.rollbackRemoval) => {
|
|
125
157
|
deletedEntities->Utils.Dict.push(entityConfig.name, entityId)
|
|
126
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
119
|
+
match[0].forEach(param => {
|
|
120
|
+
let entityId = param.entityId;
|
|
94
121
|
Utils.Dict.push(deletedEntities, entityConfig.name, entityId);
|
|
95
|
-
InMemoryTable.Entity.set(
|
|
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(
|
|
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,
|
package/src/IndexerState.res
CHANGED
|
@@ -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
|
-
|
|
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,
|