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.
@@ -40,7 +40,7 @@ function make() {
40
40
  changesCount: 0,
41
41
  prevEntityChanges: [],
42
42
  filtersByEntityId: {},
43
- filterIndices: {}
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.filterIndices = {};
87
+ self.filterIndexes = {};
88
88
  }
89
89
 
90
- function updateIndices(self, entity) {
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.filterIndices, param => {
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 deleteEntityFromIndices(self, entityId) {
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.filterIndices[EntityFilter.toString(filter)];
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
- updateIndices(inMemTable, change.entity);
142
+ updateIndexes(inMemTable, change.entity);
143
143
  } else {
144
- deleteEntityFromIndices(inMemTable, EntityId.toKey(change.entityId));
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.filterIndices[filterKey] !== undefined;
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.filterIndices[filterKey];
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.filterIndices[filterKey];
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.filterIndices[filterKey] = [
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
- updateIndices: updateIndices,
229
- deleteEntityFromIndices: deleteEntityFromIndices,
228
+ updateIndexes: updateIndexes,
229
+ deleteEntityFromIndexes: deleteEntityFromIndexes,
230
230
  set: set,
231
231
  initValue: initValue,
232
232
  mapChangeToEntity: mapChangeToEntity,
@@ -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
@@ -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,
@@ -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 indices, not only the
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
 
@@ -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 {
@@ -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>,