envio 3.12.0 → 3.13.0-alpha.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 (84) hide show
  1. package/package.json +6 -6
  2. package/src/BatchProcessing.res +9 -9
  3. package/src/BatchProcessing.res.mjs +5 -5
  4. package/src/Bin.res +24 -17
  5. package/src/Bin.res.mjs +5 -0
  6. package/src/ChainFetching.res +31 -13
  7. package/src/ChainFetching.res.mjs +9 -4
  8. package/src/ChainState.res +55 -3
  9. package/src/ChainState.res.mjs +46 -3
  10. package/src/ChainState.resi +3 -1
  11. package/src/Config.res +35 -0
  12. package/src/Config.res.mjs +39 -0
  13. package/src/Core.res +4 -0
  14. package/src/Core.res.mjs +4 -0
  15. package/src/CrossChainState.res +61 -4
  16. package/src/CrossChainState.res.mjs +39 -5
  17. package/src/CrossChainState.resi +10 -1
  18. package/src/Env.res +4 -0
  19. package/src/FetchState.res +51 -46
  20. package/src/FetchState.res.mjs +54 -36
  21. package/src/InMemoryTable.res +155 -67
  22. package/src/InMemoryTable.res.mjs +151 -60
  23. package/src/IndexerLoop.res +2 -0
  24. package/src/IndexerLoop.res.mjs +1 -0
  25. package/src/IndexerState.res +41 -1
  26. package/src/IndexerState.res.mjs +43 -4
  27. package/src/IndexerState.resi +10 -0
  28. package/src/LoadLayer.res +57 -34
  29. package/src/LoadLayer.res.mjs +45 -62
  30. package/src/LoadLayer.resi +1 -1
  31. package/src/Logging.res +38 -6
  32. package/src/Logging.res.mjs +32 -5
  33. package/src/Main.res +131 -268
  34. package/src/Main.res.mjs +28 -152
  35. package/src/Metrics.res +263 -102
  36. package/src/Metrics.res.mjs +227 -48
  37. package/src/Persistence.res +27 -2
  38. package/src/Persistence.res.mjs +9 -2
  39. package/src/PgStorage.res +109 -71
  40. package/src/PgStorage.res.mjs +88 -58
  41. package/src/Server.res +181 -0
  42. package/src/Server.res.mjs +143 -0
  43. package/src/Supervisor.res +415 -0
  44. package/src/Supervisor.res.mjs +325 -0
  45. package/src/TestIndexer.res +9 -25
  46. package/src/TestIndexer.res.mjs +5 -4
  47. package/src/UserContext.res +13 -32
  48. package/src/UserContext.res.mjs +1 -7
  49. package/src/Utils.res +1 -4
  50. package/src/Utils.res.mjs +7 -16
  51. package/src/Worker.res +95 -0
  52. package/src/Worker.res.mjs +80 -0
  53. package/src/bindings/NodeJs.res +41 -0
  54. package/src/db/EntityFilter.res +487 -275
  55. package/src/db/EntityFilter.res.mjs +557 -309
  56. package/src/db/InternalTable.res +8 -1
  57. package/src/db/InternalTable.res.mjs +5 -1
  58. package/src/db/Table.res +21 -6
  59. package/src/db/Table.res.mjs +13 -4
  60. package/src/sources/BlockStore.res +7 -2
  61. package/src/sources/EvmHyperSyncSource.res +2 -0
  62. package/src/sources/EvmHyperSyncSource.res.mjs +2 -2
  63. package/src/sources/FuelHyperSyncSource.res +1 -0
  64. package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
  65. package/src/sources/HyperSync.res +4 -0
  66. package/src/sources/HyperSync.res.mjs +4 -2
  67. package/src/sources/HyperSync.resi +1 -0
  68. package/src/sources/HyperSyncClient.res +3 -0
  69. package/src/sources/HyperSyncSSE.res +1 -1
  70. package/src/sources/HyperSyncSSE.res.mjs +4 -10
  71. package/src/sources/RpcSource.res +1 -0
  72. package/src/sources/RpcSource.res.mjs +1 -1
  73. package/src/sources/SimulateSource.res +1 -0
  74. package/src/sources/SimulateSource.res.mjs +1 -1
  75. package/src/sources/Source.res +7 -0
  76. package/src/sources/SourceManager.res +4 -3
  77. package/src/sources/SourceManager.res.mjs +2 -2
  78. package/src/sources/SvmHyperSyncClient.res +5 -0
  79. package/src/sources/SvmHyperSyncSource.res +3 -0
  80. package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
  81. package/src/tui/Tui.res +24 -0
  82. package/src/tui/Tui.res.mjs +18 -0
  83. package/src/tui/components/SyncETA.res +12 -6
  84. package/src/tui/components/SyncETA.res.mjs +12 -8
@@ -1,10 +1,26 @@
1
1
  module Entity = {
2
2
  type relatedEntityId = string
3
- type filterWithRelatedIds = (EntityFilter.t, Utils.Set.t<relatedEntityId>)
4
- // Keyed by EntityFilter.toString
5
- type filterIndexes = dict<filterWithRelatedIds>
3
+ // The matcher is specialized from the filter once at index creation and
4
+ // reused for every entity write against this index.
5
+ type index = {
6
+ matcher: EntityFilter.matcher,
7
+ ids: Utils.Set.t<relatedEntityId>,
8
+ }
9
+
10
+ // Indexes on one field that a single value can resolve — _eq, and _in under
11
+ // each of its values — found by the field's value rather than by running
12
+ // their matchers, so a write costs a lookup per indexed field instead of a
13
+ // pass over every registered index. Several indexes can share a value: an
14
+ // _in and an _eq naming it, or two _in filters overlapping on it.
15
+ type eqBucket = {
16
+ keyOf: unknown => unknown,
17
+ byValue: Utils.Map.t<unknown, array<index>>,
18
+ // Values a completed query has already loaded every row for. A later
19
+ // filter naming only these is answered from the table instead of a
20
+ // second round trip for rows that are already in it.
21
+ loadedValues: Utils.Set.t<unknown>,
22
+ }
6
23
 
7
- type entityFilters = Utils.Set.t<EntityFilter.t>
8
24
  type t = {
9
25
  latestEntityChangeById: dict<Change.t<Internal.entity>>,
10
26
  // Recorded changes (new latest ids + prevEntityChanges pushes), tracked
@@ -13,8 +29,14 @@ module Entity = {
13
29
  // Swapped out when a write starts so processing keeps appending while the
14
30
  // previous changes persist in the background.
15
31
  mutable prevEntityChanges: array<Change.t<Internal.entity>>,
16
- mutable filtersByEntityId: dict<entityFilters>,
17
- mutable filterIndexes: filterIndexes,
32
+ // Every index an entity currently belongs to, so a write can drop it from
33
+ // the ones it stopped matching without consulting the others.
34
+ mutable indexesByEntityId: dict<Utils.Set.t<index>>,
35
+ // Keyed by EntityFilter.toString, for lookups coming from the load manager.
36
+ mutable indexesByKey: dict<index>,
37
+ mutable eqBucketsByField: dict<eqBucket>,
38
+ // Ranges and composites, which no single field value can resolve.
39
+ mutable scanIndexes: array<index>,
18
40
  }
19
41
 
20
42
  // Helper to extract an entity's id as a dict key. The raw id may be a
@@ -30,21 +52,22 @@ module Entity = {
30
52
  )
31
53
  }
32
54
 
33
- let getOrCreateEntityFilters = (self: t, ~entityId) =>
34
- switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
35
- | Some(s) => s
36
- | None =>
37
- let s = Utils.Set.make()
38
- self.filtersByEntityId->Dict.set(entityId, s)
39
- s
55
+ let addToIndex = (self: t, ~index: index, ~entityId) => {
56
+ index.ids->Utils.Set.add(entityId)->ignore
57
+ switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
58
+ | Some(indexes) => indexes->Utils.Set.add(index)->ignore
59
+ | None => self.indexesByEntityId->Dict.set(entityId, Utils.Set.fromArray([index]))
40
60
  }
61
+ }
41
62
 
42
63
  let make = (): t => {
43
64
  latestEntityChangeById: Dict.make(),
44
65
  changesCount: 0.,
45
66
  prevEntityChanges: [],
46
- filtersByEntityId: Dict.make(),
47
- filterIndexes: Dict.make(),
67
+ indexesByEntityId: Dict.make(),
68
+ indexesByKey: Dict.make(),
69
+ eqBucketsByField: Dict.make(),
70
+ scanIndexes: [],
48
71
  }
49
72
 
50
73
  // Changes to persist for checkpoints in (committedCheckpointId, upToCheckpointId].
@@ -97,50 +120,52 @@ module Entity = {
97
120
  })
98
121
  keysToDelete->Array.forEach(key => self.latestEntityChangeById->Utils.Dict.deleteInPlace(key))
99
122
  self.changesCount = self.changesCount -. keysToDelete->Array.length->Int.toFloat
100
- self.filtersByEntityId = Dict.make()
101
- self.filterIndexes = Dict.make()
123
+ self.indexesByEntityId = Dict.make()
124
+ self.indexesByKey = Dict.make()
125
+ self.eqBucketsByField = Dict.make()
126
+ self.scanIndexes = []
102
127
  }
103
128
 
104
129
  let updateIndexes = (self: t, ~entity: Internal.entity) => {
105
130
  let entityId = entity->getEntityIdUnsafe
106
- let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
107
131
 
108
- //Remove any invalid filters on entity
109
- switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
132
+ switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
110
133
  | None => ()
111
- | Some(entityFilters) =>
112
- entityFilters->Utils.Set.forEach(filter => {
113
- if !(filter->EntityFilter.matches(~entity=entityAsDict)) {
114
- entityFilters->Utils.Set.delete(filter)->ignore
134
+ | Some(indexes) =>
135
+ indexes->Utils.Set.forEach(index =>
136
+ if !index.matcher(entity) {
137
+ index.ids->Utils.Set.delete(entityId)->ignore
138
+ indexes->Utils.Set.delete(index)->ignore
115
139
  }
116
- })
140
+ )
117
141
  }
118
142
 
119
- self.filterIndexes->Utils.Dict.forEach(((filter, relatedEntityIds)) => {
120
- if filter->EntityFilter.matches(~entity=entityAsDict) {
121
- //Add entity id to the filter index and the filter to entity filters
122
- relatedEntityIds->Utils.Set.add(entityId)->ignore
123
- self->getOrCreateEntityFilters(~entityId)->Utils.Set.add(filter)->ignore
124
- } else {
125
- relatedEntityIds->Utils.Set.delete(entityId)->ignore
143
+ self.eqBucketsByField->Utils.Dict.forEachWithKey((bucket, fieldName) => {
144
+ let fieldValue = entity->EntityFilter.getField(fieldName)
145
+
146
+ // A nullish column matches no equality index, and the key projections
147
+ // would throw on it.
148
+ if !(fieldValue->EntityFilter.nullish) {
149
+ switch bucket.byValue->Utils.Map.get(bucket.keyOf(fieldValue)) {
150
+ | Some(indexes) => indexes->Array.forEach(index => self->addToIndex(~index, ~entityId))
151
+ | None => ()
152
+ }
126
153
  }
127
154
  })
155
+
156
+ self.scanIndexes->Array.forEach(index =>
157
+ if index.matcher(entity) {
158
+ self->addToIndex(~index, ~entityId)
159
+ }
160
+ )
128
161
  }
129
162
 
130
163
  let deleteEntityFromIndexes = (self: t, ~entityId: string) =>
131
- switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
164
+ switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
132
165
  | None => ()
133
- | Some(entityFilters) =>
134
- entityFilters->Utils.Set.forEach(filter => {
135
- switch self.filterIndexes->Utils.Dict.dangerouslyGetNonOption(
136
- filter->EntityFilter.toString,
137
- ) {
138
- | Some((_filter, relatedEntityIds)) =>
139
- let _wasRemoved = relatedEntityIds->Utils.Set.delete(entityId)
140
- | None => () //Unexpected filter index should exist if it is in entityFilters
141
- }
142
- let _wasRemoved = entityFilters->Utils.Set.delete(filter)
143
- })
166
+ | Some(indexes) =>
167
+ indexes->Utils.Set.forEach(index => index.ids->Utils.Set.delete(entityId)->ignore)
168
+ self.indexesByEntityId->Utils.Dict.deleteInPlace(entityId)
144
169
  }
145
170
 
146
171
  let set = (inMemTable: t, ~committedCheckpointId, change: Change.t<Internal.entity>) => {
@@ -200,47 +225,110 @@ module Entity = {
200
225
 
201
226
  let hasIndex = (inMemTable: t) =>
202
227
  (filterKey: string) =>
203
- inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) !== None
228
+ inMemTable.indexesByKey->Utils.Dict.dangerouslyGetNonOption(filterKey) !== None
204
229
 
205
- let getUnsafeOnIndex = (inMemTable: t) => {
206
- let getEntity = inMemTable->getUnsafe
230
+ let getUnsafeOnIndex = (inMemTable: t) =>
207
231
  (filterKey: string) => {
208
- switch inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) {
232
+ switch inMemTable.indexesByKey->Utils.Dict.dangerouslyGetNonOption(filterKey) {
209
233
  | None =>
210
234
  JsError.throwWithMessage(`Unexpected error. Must have an index for the filter ${filterKey}`)
211
- | Some((_filter, relatedEntityIds)) =>
212
- relatedEntityIds
235
+ | Some({ids}) =>
236
+ ids
213
237
  ->Utils.Set.toArray
214
- ->Array.filterMap(entityId => {
215
- switch inMemTable.latestEntityChangeById->Dict.has(entityId) {
216
- | true => getEntity(entityId)
217
- | false => None
238
+ ->Array.filterMap(entityId =>
239
+ switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityId) {
240
+ | Some(change) => change->mapChangeToEntity
241
+ | None => None
218
242
  }
219
- })
243
+ )
220
244
  }
221
245
  }
222
- }
223
246
 
224
- let addEmptyIndex = (inMemTable: t, ~filter: EntityFilter.t) => {
225
- let filterKey = filter->EntityFilter.toString
226
- switch inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) {
247
+ let getOrCreateEqBucket = (inMemTable: t, ~table: Table.table, ~fieldName) =>
248
+ switch inMemTable.eqBucketsByField->Utils.Dict.dangerouslyGetNonOption(fieldName) {
249
+ | Some(bucket) => bucket
250
+ | None =>
251
+ let bucket = {
252
+ keyOf: EntityFilter.makeValueKey(~table, ~fieldName),
253
+ byValue: Utils.Map.make(),
254
+ loadedValues: Utils.Set.make(),
255
+ }
256
+ inMemTable.eqBucketsByField->Dict.set(fieldName, bucket)
257
+ bucket
258
+ }
259
+
260
+ // The values a filter constrains a single field to exactly, which is what a
261
+ // bucket and the loaded set are keyed by. Everything else — ranges, several
262
+ // fields, several operators — has no such list.
263
+ let singleFieldValues = (filter: EntityFilter.t) =>
264
+ switch filter->EntityFilter.asSingleOperator {
265
+ | Some((fieldName, "_eq", fieldValue)) => Some((fieldName, [fieldValue]))
266
+ | Some((fieldName, "_in", fieldValue)) => Some((fieldName, fieldValue->EntityFilter.asArray))
267
+ | Some(_) | None => None
268
+ }
269
+
270
+ let addEmptyIndex = (inMemTable: t, ~filter: EntityFilter.t, ~table: Table.table) => {
271
+ let filterKey = filter->EntityFilter.toString(~table)
272
+ switch inMemTable.indexesByKey->Utils.Dict.dangerouslyGetNonOption(filterKey) {
227
273
  | Some(_) => () //Should not happen, this means the index already exists
228
274
  | None =>
229
- let relatedEntityIds = Utils.Set.make()
275
+ let index = {matcher: filter->EntityFilter.makeMatcher(~table), ids: Utils.Set.make()}
276
+ inMemTable.indexesByKey->Dict.set(filterKey, index)
277
+
278
+ switch filter->singleFieldValues {
279
+ | Some((fieldName, fieldValues)) =>
280
+ let bucket = inMemTable->getOrCreateEqBucket(~table, ~fieldName)
281
+ fieldValues->Array.forEach(fieldValue => {
282
+ let valueKey = bucket.keyOf(fieldValue)
283
+ switch bucket.byValue->Utils.Map.get(valueKey) {
284
+ | Some(indexes) => indexes->Array.push(index)->ignore
285
+ | None => bucket.byValue->Utils.Map.set(valueKey, [index])->ignore
286
+ }
287
+ })
288
+ | None => inMemTable.scanIndexes->Array.push(index)->ignore
289
+ }
290
+
230
291
  inMemTable.latestEntityChangeById->Utils.Dict.forEach(change => {
231
292
  switch change->mapChangeToEntity {
232
293
  | Some(entity) =>
233
- let entityAsDict =
234
- entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
235
- if filter->EntityFilter.matches(~entity=entityAsDict) {
236
- let entityId = entity->getEntityIdUnsafe
237
- let _ = inMemTable->getOrCreateEntityFilters(~entityId)->Utils.Set.add(filter)
238
- let _ = relatedEntityIds->Utils.Set.add(entityId)
294
+ if index.matcher(entity) {
295
+ inMemTable->addToIndex(~index, ~entityId=entity->getEntityIdUnsafe)
239
296
  }
240
297
  | None => ()
241
298
  }
242
299
  })
243
- inMemTable.filterIndexes->Dict.set(filterKey, (filter, relatedEntityIds))
244
300
  }
245
301
  }
302
+
303
+ // Called once a query's rows are in the table, so every row the filter's
304
+ // values could match is now in memory.
305
+ let recordLoadedValues = (inMemTable: t, ~filter: EntityFilter.t, ~table: Table.table) =>
306
+ switch filter->singleFieldValues {
307
+ | Some((fieldName, fieldValues)) =>
308
+ let bucket = inMemTable->getOrCreateEqBucket(~table, ~fieldName)
309
+ fieldValues->Array.forEach(fieldValue =>
310
+ bucket.loadedValues->Utils.Set.add(bucket.keyOf(fieldValue))->ignore
311
+ )
312
+ | None => ()
313
+ }
314
+
315
+ let isFullyLoaded = (inMemTable: t, ~filter: EntityFilter.t) =>
316
+ switch filter->singleFieldValues {
317
+ | Some((fieldName, fieldValues)) =>
318
+ switch inMemTable.eqBucketsByField->Utils.Dict.dangerouslyGetNonOption(fieldName) {
319
+ | Some(bucket) =>
320
+ fieldValues->Array.every(fieldValue =>
321
+ bucket.loadedValues->Utils.Set.has(bucket.keyOf(fieldValue))
322
+ )
323
+ | None => false
324
+ }
325
+ | None => false
326
+ }
327
+
328
+ // Builds the index for a filter whose every value an earlier query already
329
+ // loaded, so the call is served from the table instead of going to storage.
330
+ let tryIndexFromLoadedValues = (inMemTable: t, ~filter: EntityFilter.t, ~table: Table.table) =>
331
+ if inMemTable->isFullyLoaded(~filter) {
332
+ inMemTable->addEmptyIndex(~filter, ~table)
333
+ }
246
334
  }
@@ -24,14 +24,14 @@ function getEntityIdUnsafe(entity) {
24
24
  }
25
25
  }
26
26
 
27
- function getOrCreateEntityFilters(self, entityId) {
28
- let s = self.filtersByEntityId[entityId];
29
- if (s !== undefined) {
30
- return Primitive_option.valFromOption(s);
27
+ function addToIndex(self, index, entityId) {
28
+ index.ids.add(entityId);
29
+ let indexes = self.indexesByEntityId[entityId];
30
+ if (indexes !== undefined) {
31
+ Primitive_option.valFromOption(indexes).add(index);
32
+ } else {
33
+ self.indexesByEntityId[entityId] = new Set([index]);
31
34
  }
32
- let s$1 = new Set();
33
- self.filtersByEntityId[entityId] = s$1;
34
- return s$1;
35
35
  }
36
36
 
37
37
  function make() {
@@ -39,8 +39,10 @@ function make() {
39
39
  latestEntityChangeById: {},
40
40
  changesCount: 0,
41
41
  prevEntityChanges: [],
42
- filtersByEntityId: {},
43
- filterIndexes: {}
42
+ indexesByEntityId: {},
43
+ indexesByKey: {},
44
+ eqBucketsByField: {},
45
+ scanIndexes: []
44
46
  };
45
47
  }
46
48
 
@@ -83,47 +85,51 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
83
85
  });
84
86
  keysToDelete.forEach(key => Utils.Dict.deleteInPlace(self.latestEntityChangeById, key));
85
87
  self.changesCount = self.changesCount - keysToDelete.length;
86
- self.filtersByEntityId = {};
87
- self.filterIndexes = {};
88
+ self.indexesByEntityId = {};
89
+ self.indexesByKey = {};
90
+ self.eqBucketsByField = {};
91
+ self.scanIndexes = [];
88
92
  }
89
93
 
90
94
  function updateIndexes(self, entity) {
91
95
  let entityId = getEntityIdUnsafe(entity);
92
- let entityFilters = self.filtersByEntityId[entityId];
93
- if (entityFilters !== undefined) {
94
- let entityFilters$1 = Primitive_option.valFromOption(entityFilters);
95
- entityFilters$1.forEach(filter => {
96
- if (!EntityFilter.matches(filter, entity)) {
97
- entityFilters$1.delete(filter);
96
+ let indexes = self.indexesByEntityId[entityId];
97
+ if (indexes !== undefined) {
98
+ let indexes$1 = Primitive_option.valFromOption(indexes);
99
+ indexes$1.forEach(index => {
100
+ if (!index.matcher(entity)) {
101
+ index.ids.delete(entityId);
102
+ indexes$1.delete(index);
98
103
  return;
99
104
  }
100
105
  });
101
106
  }
102
- Utils.Dict.forEach(self.filterIndexes, param => {
103
- let relatedEntityIds = param[1];
104
- let filter = param[0];
105
- if (EntityFilter.matches(filter, entity)) {
106
- relatedEntityIds.add(entityId);
107
- getOrCreateEntityFilters(self, entityId).add(filter);
108
- } else {
109
- relatedEntityIds.delete(entityId);
107
+ Utils.Dict.forEachWithKey(self.eqBucketsByField, (bucket, fieldName) => {
108
+ let fieldValue = entity[fieldName];
109
+ if (EntityFilter.nullish(fieldValue)) {
110
+ return;
111
+ }
112
+ let indexes = bucket.byValue.get(bucket.keyOf(fieldValue));
113
+ if (indexes !== undefined) {
114
+ indexes.forEach(index => addToIndex(self, index, entityId));
115
+ return;
116
+ }
117
+ });
118
+ self.scanIndexes.forEach(index => {
119
+ if (index.matcher(entity)) {
120
+ return addToIndex(self, index, entityId);
110
121
  }
111
122
  });
112
123
  }
113
124
 
114
125
  function deleteEntityFromIndexes(self, entityId) {
115
- let entityFilters = self.filtersByEntityId[entityId];
116
- if (entityFilters === undefined) {
117
- return;
126
+ let indexes = self.indexesByEntityId[entityId];
127
+ if (indexes !== undefined) {
128
+ Primitive_option.valFromOption(indexes).forEach(index => {
129
+ index.ids.delete(entityId);
130
+ });
131
+ return Utils.Dict.deleteInPlace(self.indexesByEntityId, entityId);
118
132
  }
119
- let entityFilters$1 = Primitive_option.valFromOption(entityFilters);
120
- entityFilters$1.forEach(filter => {
121
- let match = self.filterIndexes[EntityFilter.toString(filter)];
122
- if (match !== undefined) {
123
- match[1].delete(entityId);
124
- }
125
- entityFilters$1.delete(filter);
126
- });
127
133
  }
128
134
 
129
135
  function set(inMemTable, committedCheckpointId, change) {
@@ -174,17 +180,17 @@ function getUnsafe(inMemTable) {
174
180
  }
175
181
 
176
182
  function hasIndex(inMemTable) {
177
- return filterKey => inMemTable.filterIndexes[filterKey] !== undefined;
183
+ return filterKey => inMemTable.indexesByKey[filterKey] !== undefined;
178
184
  }
179
185
 
180
186
  function getUnsafeOnIndex(inMemTable) {
181
- let getEntity = getUnsafe(inMemTable);
182
187
  return filterKey => {
183
- let match = inMemTable.filterIndexes[filterKey];
188
+ let match = inMemTable.indexesByKey[filterKey];
184
189
  if (match !== undefined) {
185
- return Stdlib_Array.filterMap(Array.from(match[1]), entityId => {
186
- if (entityId in inMemTable.latestEntityChangeById) {
187
- return getEntity(entityId);
190
+ return Stdlib_Array.filterMap(Array.from(match.ids), entityId => {
191
+ let change = inMemTable.latestEntityChangeById[entityId];
192
+ if (change !== undefined) {
193
+ return mapChangeToEntity(change);
188
194
  }
189
195
  });
190
196
  } else {
@@ -193,35 +199,115 @@ function getUnsafeOnIndex(inMemTable) {
193
199
  };
194
200
  }
195
201
 
196
- function addEmptyIndex(inMemTable, filter) {
197
- let filterKey = EntityFilter.toString(filter);
198
- let match = inMemTable.filterIndexes[filterKey];
202
+ function getOrCreateEqBucket(inMemTable, table, fieldName) {
203
+ let bucket = inMemTable.eqBucketsByField[fieldName];
204
+ if (bucket !== undefined) {
205
+ return bucket;
206
+ }
207
+ let bucket_keyOf = EntityFilter.makeValueKey(table, fieldName);
208
+ let bucket_byValue = new Map();
209
+ let bucket_loadedValues = new Set();
210
+ let bucket$1 = {
211
+ keyOf: bucket_keyOf,
212
+ byValue: bucket_byValue,
213
+ loadedValues: bucket_loadedValues
214
+ };
215
+ inMemTable.eqBucketsByField[fieldName] = bucket$1;
216
+ return bucket$1;
217
+ }
218
+
219
+ function singleFieldValues(filter) {
220
+ let match = EntityFilter.asSingleOperator(filter);
221
+ if (match === undefined) {
222
+ return;
223
+ }
224
+ let fieldName = match[0];
225
+ switch (match[1]) {
226
+ case "_eq" :
227
+ return [
228
+ fieldName,
229
+ [match[2]]
230
+ ];
231
+ case "_in" :
232
+ return [
233
+ fieldName,
234
+ EntityFilter.asArray(match[2])
235
+ ];
236
+ default:
237
+ return;
238
+ }
239
+ }
240
+
241
+ function addEmptyIndex(inMemTable, filter, table) {
242
+ let filterKey = EntityFilter.toString(filter, table);
243
+ let match = inMemTable.indexesByKey[filterKey];
199
244
  if (match !== undefined) {
200
245
  return;
201
246
  }
202
- let relatedEntityIds = new Set();
247
+ let index_matcher = EntityFilter.makeMatcher(filter, table);
248
+ let index_ids = new Set();
249
+ let index = {
250
+ matcher: index_matcher,
251
+ ids: index_ids
252
+ };
253
+ inMemTable.indexesByKey[filterKey] = index;
254
+ let match$1 = singleFieldValues(filter);
255
+ if (match$1 !== undefined) {
256
+ let bucket = getOrCreateEqBucket(inMemTable, table, match$1[0]);
257
+ match$1[1].forEach(fieldValue => {
258
+ let valueKey = bucket.keyOf(fieldValue);
259
+ let indexes = bucket.byValue.get(valueKey);
260
+ if (indexes !== undefined) {
261
+ indexes.push(index);
262
+ } else {
263
+ bucket.byValue.set(valueKey, [index]);
264
+ }
265
+ });
266
+ } else {
267
+ inMemTable.scanIndexes.push(index);
268
+ }
203
269
  Utils.Dict.forEach(inMemTable.latestEntityChangeById, change => {
204
270
  let entity = mapChangeToEntity(change);
205
- if (entity === undefined) {
206
- return;
271
+ if (entity !== undefined && index_matcher(entity)) {
272
+ return addToIndex(inMemTable, index, getEntityIdUnsafe(entity));
207
273
  }
208
- if (!EntityFilter.matches(filter, entity)) {
209
- return;
210
- }
211
- let entityId = getEntityIdUnsafe(entity);
212
- getOrCreateEntityFilters(inMemTable, entityId).add(filter);
213
- relatedEntityIds.add(entityId);
214
274
  });
215
- inMemTable.filterIndexes[filterKey] = [
216
- filter,
217
- relatedEntityIds
218
- ];
275
+ }
276
+
277
+ function recordLoadedValues(inMemTable, filter, table) {
278
+ let match = singleFieldValues(filter);
279
+ if (match === undefined) {
280
+ return;
281
+ }
282
+ let bucket = getOrCreateEqBucket(inMemTable, table, match[0]);
283
+ match[1].forEach(fieldValue => {
284
+ bucket.loadedValues.add(bucket.keyOf(fieldValue));
285
+ });
286
+ }
287
+
288
+ function isFullyLoaded(inMemTable, filter) {
289
+ let match = singleFieldValues(filter);
290
+ if (match === undefined) {
291
+ return false;
292
+ }
293
+ let bucket = inMemTable.eqBucketsByField[match[0]];
294
+ if (bucket !== undefined) {
295
+ return match[1].every(fieldValue => bucket.loadedValues.has(bucket.keyOf(fieldValue)));
296
+ } else {
297
+ return false;
298
+ }
299
+ }
300
+
301
+ function tryIndexFromLoadedValues(inMemTable, filter, table) {
302
+ if (isFullyLoaded(inMemTable, filter)) {
303
+ return addEmptyIndex(inMemTable, filter, table);
304
+ }
219
305
  }
220
306
 
221
307
  let Entity = {
222
308
  UnexpectedIdNotDefinedOnEntity: UnexpectedIdNotDefinedOnEntity,
223
309
  getEntityIdUnsafe: getEntityIdUnsafe,
224
- getOrCreateEntityFilters: getOrCreateEntityFilters,
310
+ addToIndex: addToIndex,
225
311
  make: make,
226
312
  snapshotChanges: snapshotChanges,
227
313
  dropCommittedChanges: dropCommittedChanges,
@@ -233,7 +319,12 @@ let Entity = {
233
319
  getUnsafe: getUnsafe,
234
320
  hasIndex: hasIndex,
235
321
  getUnsafeOnIndex: getUnsafeOnIndex,
236
- addEmptyIndex: addEmptyIndex
322
+ getOrCreateEqBucket: getOrCreateEqBucket,
323
+ singleFieldValues: singleFieldValues,
324
+ addEmptyIndex: addEmptyIndex,
325
+ recordLoadedValues: recordLoadedValues,
326
+ isFullyLoaded: isFullyLoaded,
327
+ tryIndexFromLoadedValues: tryIndexFromLoadedValues
237
328
  };
238
329
 
239
330
  export {
@@ -45,6 +45,8 @@ let start = (state: IndexerState.t) => {
45
45
  launch(state, () => FinalizeBackfill.repairSchemaIndexes(state))
46
46
  }
47
47
 
48
+ state->IndexerState.bindScheduleProcessing(scheduleProcessing)
49
+
48
50
  scheduleFetch()
49
51
  scheduleProcessing()
50
52
  }
@@ -42,6 +42,7 @@ function start(state) {
42
42
  work();
43
43
  }
44
44
  }
45
+ IndexerState.bindScheduleProcessing(state, scheduleProcessing);
45
46
  scheduleFetch();
46
47
  scheduleProcessing();
47
48
  }