envio 3.5.0-alpha.2 → 3.5.0-alpha.3
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/package.json +6 -6
- package/src/BatchProcessing.res +14 -0
- package/src/BatchProcessing.res.mjs +8 -0
- package/src/ChainState.res +10 -4
- package/src/ChainState.res.mjs +7 -6
- package/src/ChainState.resi +1 -1
- package/src/Config.res +2 -2
- package/src/Config.res.mjs +2 -2
- package/src/CrossChainState.res +33 -10
- package/src/CrossChainState.res.mjs +21 -6
- package/src/CrossChainState.resi +3 -0
- package/src/FetchState.res +230 -66
- package/src/FetchState.res.mjs +213 -67
- package/src/FinalizeBackfill.res +34 -0
- package/src/FinalizeBackfill.res.mjs +26 -0
- package/src/InMemoryTable.res +15 -15
- package/src/InMemoryTable.res.mjs +14 -14
- package/src/IndexerState.res +12 -0
- package/src/IndexerState.res.mjs +19 -0
- package/src/IndexerState.resi +3 -0
- package/src/LoadLayer.res +9 -1
- package/src/LoadLayer.res.mjs +1 -0
- package/src/Persistence.res +11 -0
- package/src/PgStorage.res +356 -35
- package/src/PgStorage.res.mjs +237 -24
- package/src/TestIndexer.res +3 -0
- package/src/TestIndexer.res.mjs +2 -0
- package/src/db/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/IndexRegistry.res +219 -0
- package/src/db/IndexRegistry.res.mjs +195 -0
- package/src/db/InternalTable.res +7 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/db/Table.res +13 -13
- package/src/db/Table.res.mjs +12 -12
|
@@ -40,7 +40,7 @@ function make() {
|
|
|
40
40
|
changesCount: 0,
|
|
41
41
|
prevEntityChanges: [],
|
|
42
42
|
filtersByEntityId: {},
|
|
43
|
-
|
|
43
|
+
filterIndexes: {}
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -84,10 +84,10 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
|
|
|
84
84
|
keysToDelete.forEach(key => Utils.Dict.deleteInPlace(self.latestEntityChangeById, key));
|
|
85
85
|
self.changesCount = self.changesCount - keysToDelete.length;
|
|
86
86
|
self.filtersByEntityId = {};
|
|
87
|
-
self.
|
|
87
|
+
self.filterIndexes = {};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
function
|
|
90
|
+
function updateIndexes(self, entity) {
|
|
91
91
|
let entityId = getEntityIdUnsafe(entity);
|
|
92
92
|
let entityFilters = self.filtersByEntityId[entityId];
|
|
93
93
|
if (entityFilters !== undefined) {
|
|
@@ -99,7 +99,7 @@ function updateIndices(self, entity) {
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
-
Utils.Dict.forEach(self.
|
|
102
|
+
Utils.Dict.forEach(self.filterIndexes, param => {
|
|
103
103
|
let relatedEntityIds = param[1];
|
|
104
104
|
let filter = param[0];
|
|
105
105
|
if (EntityFilter.matches(filter, entity)) {
|
|
@@ -111,14 +111,14 @@ function updateIndices(self, entity) {
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function
|
|
114
|
+
function deleteEntityFromIndexes(self, entityId) {
|
|
115
115
|
let entityFilters = self.filtersByEntityId[entityId];
|
|
116
116
|
if (entityFilters === undefined) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
let entityFilters$1 = Primitive_option.valFromOption(entityFilters);
|
|
120
120
|
entityFilters$1.forEach(filter => {
|
|
121
|
-
let match = self.
|
|
121
|
+
let match = self.filterIndexes[EntityFilter.toString(filter)];
|
|
122
122
|
if (match !== undefined) {
|
|
123
123
|
match[1].delete(entityId);
|
|
124
124
|
}
|
|
@@ -139,9 +139,9 @@ function set(inMemTable, committedCheckpointId, change) {
|
|
|
139
139
|
inMemTable.changesCount = inMemTable.changesCount + 1;
|
|
140
140
|
}
|
|
141
141
|
if (change.type === "SET") {
|
|
142
|
-
|
|
142
|
+
updateIndexes(inMemTable, change.entity);
|
|
143
143
|
} else {
|
|
144
|
-
|
|
144
|
+
deleteEntityFromIndexes(inMemTable, EntityId.toKey(change.entityId));
|
|
145
145
|
}
|
|
146
146
|
inMemTable.latestEntityChangeById[entityKey] = change;
|
|
147
147
|
}
|
|
@@ -174,13 +174,13 @@ function getUnsafe(inMemTable) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
function hasIndex(inMemTable) {
|
|
177
|
-
return filterKey => inMemTable.
|
|
177
|
+
return filterKey => inMemTable.filterIndexes[filterKey] !== undefined;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function getUnsafeOnIndex(inMemTable) {
|
|
181
181
|
let getEntity = getUnsafe(inMemTable);
|
|
182
182
|
return filterKey => {
|
|
183
|
-
let match = inMemTable.
|
|
183
|
+
let match = inMemTable.filterIndexes[filterKey];
|
|
184
184
|
if (match !== undefined) {
|
|
185
185
|
return Stdlib_Array.filterMap(Array.from(match[1]), entityId => {
|
|
186
186
|
if (entityId in inMemTable.latestEntityChangeById) {
|
|
@@ -195,7 +195,7 @@ function getUnsafeOnIndex(inMemTable) {
|
|
|
195
195
|
|
|
196
196
|
function addEmptyIndex(inMemTable, filter) {
|
|
197
197
|
let filterKey = EntityFilter.toString(filter);
|
|
198
|
-
let match = inMemTable.
|
|
198
|
+
let match = inMemTable.filterIndexes[filterKey];
|
|
199
199
|
if (match !== undefined) {
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
@@ -212,7 +212,7 @@ function addEmptyIndex(inMemTable, filter) {
|
|
|
212
212
|
getOrCreateEntityFilters(inMemTable, entityId).add(filter);
|
|
213
213
|
relatedEntityIds.add(entityId);
|
|
214
214
|
});
|
|
215
|
-
inMemTable.
|
|
215
|
+
inMemTable.filterIndexes[filterKey] = [
|
|
216
216
|
filter,
|
|
217
217
|
relatedEntityIds
|
|
218
218
|
];
|
|
@@ -225,8 +225,8 @@ let Entity = {
|
|
|
225
225
|
make: make,
|
|
226
226
|
snapshotChanges: snapshotChanges,
|
|
227
227
|
dropCommittedChanges: dropCommittedChanges,
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
updateIndexes: updateIndexes,
|
|
229
|
+
deleteEntityFromIndexes: deleteEntityFromIndexes,
|
|
230
230
|
set: set,
|
|
231
231
|
initValue: initValue,
|
|
232
232
|
mapChangeToEntity: mapChangeToEntity,
|
package/src/IndexerState.res
CHANGED
|
@@ -468,6 +468,18 @@ let crossChainState = (state: t) => state.crossChainState
|
|
|
468
468
|
let chainStates = (state: t) => state.crossChainState->CrossChainState.chainStates
|
|
469
469
|
let isInReorgThreshold = (state: t) => state.crossChainState->CrossChainState.isInReorgThreshold
|
|
470
470
|
let isRealtime = (state: t) => state.crossChainState->CrossChainState.isRealtime
|
|
471
|
+
|
|
472
|
+
// The indexer runs Backfilling → FinalizingIndexes → Ready. This is true only
|
|
473
|
+
// in the middle phase: every chain has caught up, but the deferred schema
|
|
474
|
+
// indexes and `ready_at` haven't been committed yet.
|
|
475
|
+
let isFinalizingIndexes = (state: t) =>
|
|
476
|
+
state.crossChainState->CrossChainState.isCaughtUp &&
|
|
477
|
+
!(state.crossChainState->CrossChainState.isRealtime)
|
|
478
|
+
|
|
479
|
+
let markCaughtUp = (state: t) => state.crossChainState->CrossChainState.markCaughtUp
|
|
480
|
+
|
|
481
|
+
let markReady = (state: t, ~readyAt) => state.crossChainState->CrossChainState.markReady(~readyAt)
|
|
482
|
+
|
|
471
483
|
let rollbackState = (state: t) => state.rollbackState
|
|
472
484
|
let indexerStartTime = (state: t) => state.indexerStartTime
|
|
473
485
|
let loadManager = (state: t) => state.loadManager
|
package/src/IndexerState.res.mjs
CHANGED
|
@@ -336,6 +336,22 @@ function isRealtime(state) {
|
|
|
336
336
|
return CrossChainState.isRealtime(state.crossChainState);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
function isFinalizingIndexes(state) {
|
|
340
|
+
if (CrossChainState.isCaughtUp(state.crossChainState)) {
|
|
341
|
+
return !CrossChainState.isRealtime(state.crossChainState);
|
|
342
|
+
} else {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function markCaughtUp(state) {
|
|
348
|
+
CrossChainState.markCaughtUp(state.crossChainState);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function markReady(state, readyAt) {
|
|
352
|
+
CrossChainState.markReady(state.crossChainState, readyAt);
|
|
353
|
+
}
|
|
354
|
+
|
|
339
355
|
function rollbackState(state) {
|
|
340
356
|
return state.rollbackState;
|
|
341
357
|
}
|
|
@@ -762,6 +778,9 @@ export {
|
|
|
762
778
|
chainStates,
|
|
763
779
|
isInReorgThreshold,
|
|
764
780
|
isRealtime,
|
|
781
|
+
isFinalizingIndexes,
|
|
782
|
+
markCaughtUp,
|
|
783
|
+
markReady,
|
|
765
784
|
rollbackState,
|
|
766
785
|
indexerStartTime,
|
|
767
786
|
loadManager,
|
package/src/IndexerState.resi
CHANGED
|
@@ -94,6 +94,9 @@ let crossChainState: t => CrossChainState.t
|
|
|
94
94
|
let chainStates: t => dict<ChainState.t>
|
|
95
95
|
let isInReorgThreshold: t => bool
|
|
96
96
|
let isRealtime: t => bool
|
|
97
|
+
let isFinalizingIndexes: t => bool
|
|
98
|
+
let markCaughtUp: t => unit
|
|
99
|
+
let markReady: (t, ~readyAt: Date.t) => unit
|
|
97
100
|
let rollbackState: t => rollbackState
|
|
98
101
|
let indexerStartTime: t => Date.t
|
|
99
102
|
let loadManager: t => LoadManager.t
|
package/src/LoadLayer.res
CHANGED
|
@@ -338,6 +338,7 @@ let loadByFilter = (
|
|
|
338
338
|
|
|
339
339
|
let load = async (filters: array<EntityFilter.t>, ~onError as _) => {
|
|
340
340
|
let storage = persistence->Persistence.getInitializedStorageOrThrow
|
|
341
|
+
|
|
341
342
|
let timerRef =
|
|
342
343
|
indexerState->IndexerState.startStorageLoad(~storage=storage.name, ~operation=key)
|
|
343
344
|
|
|
@@ -345,8 +346,15 @@ let loadByFilter = (
|
|
|
345
346
|
|
|
346
347
|
filters->Array.forEach(filter => inMemTable->InMemoryTable.Entity.addEmptyIndex(~filter))
|
|
347
348
|
|
|
349
|
+
// Any non-derived field can be filtered on, so the columns this query reads
|
|
350
|
+
// are indexed on demand before it runs rather than promised by the schema.
|
|
351
|
+
// Inside the load timing: waiting on the build is time the handler spends
|
|
352
|
+
// waiting for this operation, and it's the only thing that explains an
|
|
353
|
+
// occasional very slow getWhere.
|
|
354
|
+
await storage.ensureQueryIndexes(~table=entityConfig.table, ~filters)
|
|
355
|
+
|
|
348
356
|
// Loading a superset of rows via a merged query is safe: every loaded
|
|
349
|
-
// entity is matched against all registered
|
|
357
|
+
// entity is matched against all registered indexes, not only the
|
|
350
358
|
// query's own filter.
|
|
351
359
|
let queries = filters->EntityFilter.merge
|
|
352
360
|
|
package/src/LoadLayer.res.mjs
CHANGED
|
@@ -190,6 +190,7 @@ function loadByFilter(loadManager, persistence, entityConfig, indexerState, shou
|
|
|
190
190
|
contents: 0
|
|
191
191
|
};
|
|
192
192
|
filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter));
|
|
193
|
+
await storage.ensureQueryIndexes(entityConfig.table, filters);
|
|
193
194
|
let queries = EntityFilter.merge(filters);
|
|
194
195
|
await Promise.all(queries.map(async filter => {
|
|
195
196
|
try {
|
package/src/Persistence.res
CHANGED
|
@@ -89,6 +89,17 @@ type storage = {
|
|
|
89
89
|
// Field values are serialized and rows parsed with the table's field schemas.
|
|
90
90
|
@raises("StorageError")
|
|
91
91
|
loadOrThrow: (~filter: EntityFilter.t, ~table: Table.table) => promise<array<unknown>>,
|
|
92
|
+
// Creates whatever indexes the filters need and aren't there yet, resolving
|
|
93
|
+
// once they're queryable. Best-effort: it resolves even when a build fails,
|
|
94
|
+
// leaving the query to run unindexed rather than failing the handler.
|
|
95
|
+
ensureQueryIndexes: (~table: Table.table, ~filters: array<EntityFilter.t>) => promise<unit>,
|
|
96
|
+
// Creates every schema-defined index still missing and stamps `ready_at` on
|
|
97
|
+
// the given chains, atomically. Called once, when backfill completes.
|
|
98
|
+
finalizeBackfill: (
|
|
99
|
+
~entities: array<Internal.entityConfig>,
|
|
100
|
+
~chainIds: array<int>,
|
|
101
|
+
~readyAt: Date.t,
|
|
102
|
+
) => promise<unit>,
|
|
92
103
|
// This is to download cache from the database to .envio/cache
|
|
93
104
|
dumpEffectCache: unit => promise<unit>,
|
|
94
105
|
reset: unit => promise<unit>,
|