envio 3.1.2 → 3.2.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 (134) hide show
  1. package/evm.schema.json +83 -11
  2. package/fuel.schema.json +83 -11
  3. package/index.d.ts +184 -3
  4. package/package.json +6 -6
  5. package/src/Batch.res +2 -2
  6. package/src/ChainFetcher.res +27 -3
  7. package/src/ChainFetcher.res.mjs +17 -3
  8. package/src/ChainManager.res +163 -0
  9. package/src/ChainManager.res.mjs +136 -0
  10. package/src/Config.res +213 -30
  11. package/src/Config.res.mjs +102 -41
  12. package/src/Core.res +16 -10
  13. package/src/Ecosystem.res +0 -3
  14. package/src/Env.res +2 -2
  15. package/src/Env.res.mjs +2 -2
  16. package/src/Envio.res +101 -2
  17. package/src/Envio.res.mjs +2 -3
  18. package/src/EventConfigBuilder.res +52 -0
  19. package/src/EventConfigBuilder.res.mjs +32 -0
  20. package/src/EventUtils.res +2 -2
  21. package/src/FetchState.res +23 -14
  22. package/src/FetchState.res.mjs +21 -15
  23. package/src/GlobalState.res +219 -363
  24. package/src/GlobalState.res.mjs +314 -491
  25. package/src/GlobalStateManager.res +49 -59
  26. package/src/GlobalStateManager.res.mjs +5 -4
  27. package/src/GlobalStateManager.resi +1 -1
  28. package/src/HandlerLoader.res +12 -1
  29. package/src/HandlerLoader.res.mjs +6 -1
  30. package/src/HandlerRegister.res +9 -9
  31. package/src/HandlerRegister.res.mjs +9 -9
  32. package/src/Hasura.res +102 -32
  33. package/src/Hasura.res.mjs +88 -34
  34. package/src/InMemoryStore.res +10 -1
  35. package/src/InMemoryStore.res.mjs +4 -1
  36. package/src/InMemoryTable.res +83 -136
  37. package/src/InMemoryTable.res.mjs +57 -86
  38. package/src/Internal.res +54 -5
  39. package/src/Internal.res.mjs +2 -8
  40. package/src/LazyLoader.res +2 -2
  41. package/src/LazyLoader.res.mjs +3 -3
  42. package/src/LoadLayer.res +47 -60
  43. package/src/LoadLayer.res.mjs +28 -50
  44. package/src/LoadLayer.resi +2 -5
  45. package/src/LogSelection.res +4 -4
  46. package/src/LogSelection.res.mjs +5 -7
  47. package/src/Logging.res +1 -1
  48. package/src/Main.res +61 -2
  49. package/src/Main.res.mjs +37 -1
  50. package/src/Persistence.res +3 -16
  51. package/src/PgStorage.res +125 -114
  52. package/src/PgStorage.res.mjs +112 -95
  53. package/src/Ports.res +5 -0
  54. package/src/Ports.res.mjs +9 -0
  55. package/src/Prometheus.res +3 -3
  56. package/src/Prometheus.res.mjs +4 -4
  57. package/src/ReorgDetection.res +4 -4
  58. package/src/ReorgDetection.res.mjs +4 -5
  59. package/src/SafeCheckpointTracking.res +16 -16
  60. package/src/SafeCheckpointTracking.res.mjs +2 -2
  61. package/src/SimulateItems.res +10 -14
  62. package/src/SimulateItems.res.mjs +5 -2
  63. package/src/Sink.res +1 -1
  64. package/src/Sink.res.mjs +1 -2
  65. package/src/SvmTypes.res +9 -0
  66. package/src/SvmTypes.res.mjs +14 -0
  67. package/src/TestIndexer.res +17 -57
  68. package/src/TestIndexer.res.mjs +14 -48
  69. package/src/TestIndexerProxyStorage.res +23 -23
  70. package/src/TestIndexerProxyStorage.res.mjs +12 -15
  71. package/src/Throttler.res +2 -2
  72. package/src/Time.res +2 -2
  73. package/src/Time.res.mjs +2 -2
  74. package/src/UserContext.res +19 -118
  75. package/src/UserContext.res.mjs +10 -66
  76. package/src/Utils.res +15 -15
  77. package/src/Utils.res.mjs +7 -8
  78. package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
  79. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
  80. package/src/bindings/BigDecimal.res +1 -1
  81. package/src/bindings/BigDecimal.res.mjs +2 -2
  82. package/src/bindings/ClickHouse.res +8 -6
  83. package/src/bindings/ClickHouse.res.mjs +5 -5
  84. package/src/bindings/Hrtime.res +1 -1
  85. package/src/bindings/Pino.res +2 -2
  86. package/src/bindings/Pino.res.mjs +3 -4
  87. package/src/db/EntityFilter.res +410 -0
  88. package/src/db/EntityFilter.res.mjs +424 -0
  89. package/src/db/EntityHistory.res +1 -1
  90. package/src/db/EntityHistory.res.mjs +1 -1
  91. package/src/db/InternalTable.res +10 -10
  92. package/src/db/InternalTable.res.mjs +41 -45
  93. package/src/db/Schema.res +2 -2
  94. package/src/db/Schema.res.mjs +3 -3
  95. package/src/db/Table.res +106 -22
  96. package/src/db/Table.res.mjs +84 -35
  97. package/src/sources/EventRouter.res +67 -2
  98. package/src/sources/EventRouter.res.mjs +45 -3
  99. package/src/sources/Evm.res +0 -7
  100. package/src/sources/Evm.res.mjs +0 -15
  101. package/src/sources/EvmChain.res +1 -1
  102. package/src/sources/EvmChain.res.mjs +1 -2
  103. package/src/sources/EvmRpcClient.res +42 -0
  104. package/src/sources/EvmRpcClient.res.mjs +64 -0
  105. package/src/sources/Fuel.res +0 -7
  106. package/src/sources/Fuel.res.mjs +0 -15
  107. package/src/sources/HyperFuelSource.res +5 -4
  108. package/src/sources/HyperFuelSource.res.mjs +2 -2
  109. package/src/sources/HyperSyncClient.res +9 -5
  110. package/src/sources/HyperSyncClient.res.mjs +2 -2
  111. package/src/sources/HyperSyncHeightStream.res +2 -2
  112. package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
  113. package/src/sources/HyperSyncSource.res +10 -9
  114. package/src/sources/HyperSyncSource.res.mjs +4 -4
  115. package/src/sources/Rpc.res +1 -5
  116. package/src/sources/Rpc.res.mjs +1 -9
  117. package/src/sources/RpcSource.res +57 -21
  118. package/src/sources/RpcSource.res.mjs +47 -20
  119. package/src/sources/RpcWebSocketHeightStream.res +1 -1
  120. package/src/sources/SourceManager.res +3 -2
  121. package/src/sources/SourceManager.res.mjs +1 -1
  122. package/src/sources/Svm.res +3 -10
  123. package/src/sources/Svm.res.mjs +4 -18
  124. package/src/sources/SvmHyperSyncClient.res +265 -0
  125. package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
  126. package/src/sources/SvmHyperSyncSource.res +638 -0
  127. package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
  128. package/src/tui/Tui.res +9 -2
  129. package/src/tui/Tui.res.mjs +18 -3
  130. package/src/tui/components/BufferedProgressBar.res +2 -2
  131. package/src/tui/components/TuiData.res +3 -0
  132. package/svm.schema.json +523 -14
  133. package/src/TableIndices.res +0 -115
  134. package/src/TableIndices.res.mjs +0 -144
@@ -6,7 +6,6 @@ import * as Table from "./db/Table.res.mjs";
6
6
  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
- import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
10
9
  import * as InternalTable from "./db/InternalTable.res.mjs";
11
10
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
12
11
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -46,6 +45,21 @@ function clearMetadataRoute() {
46
45
  };
47
46
  }
48
47
 
48
+ function reloadMetadataRoute() {
49
+ return {
50
+ method: "POST",
51
+ path: "",
52
+ input: s => {
53
+ s.field("type", S$RescriptSchema.literal("reload_metadata"));
54
+ return {
55
+ args: s.field("args", S$RescriptSchema.json(false)),
56
+ auth: auth(s)
57
+ };
58
+ },
59
+ responses: responses
60
+ };
61
+ }
62
+
49
63
  function trackTablesRoute() {
50
64
  return {
51
65
  method: "POST",
@@ -112,6 +126,48 @@ async function clearHasuraMetadata(endpoint, auth) {
112
126
  }
113
127
  }
114
128
 
129
+ async function reloadHasuraMetadata(endpoint, auth) {
130
+ try {
131
+ let result = await Rest.fetch(reloadMetadataRoute, {
132
+ auth: auth,
133
+ args: {
134
+ reload_sources: ["default"]
135
+ }
136
+ }, Rest.client(endpoint, undefined));
137
+ let tmp;
138
+ tmp = result === "QuerySucceeded" ? "Hasura metadata reloaded" : "Hasura metadata reload acknowledged";
139
+ return Logging.trace(tmp);
140
+ } catch (raw_exn) {
141
+ let exn = Primitive_exceptions.internalToException(raw_exn);
142
+ return Logging.error({
143
+ msg: `There was an issue reloading hasura metadata - table tracking may race with schema creation.`,
144
+ err: Utils.prettifyExn(exn)
145
+ });
146
+ }
147
+ }
148
+
149
+ function makeColumnConfigs(table) {
150
+ let columnConfigs = {};
151
+ table.fields.forEach(fieldOrDerived => {
152
+ if (fieldOrDerived.TAG !== "Field") {
153
+ return;
154
+ }
155
+ let field = fieldOrDerived._0;
156
+ let apiFieldName = Table.getApiFieldName(field);
157
+ let dbFieldName = Table.getPgDbFieldName(field);
158
+ let customName = apiFieldName === dbFieldName ? undefined : apiFieldName;
159
+ let match = field.description;
160
+ if (customName === undefined && match === undefined) {
161
+ return;
162
+ }
163
+ columnConfigs[dbFieldName] = {
164
+ customName: customName,
165
+ comment: match
166
+ };
167
+ });
168
+ return columnConfigs;
169
+ }
170
+
115
171
  async function trackTables(endpoint, auth, pgSchema, tableConfigs) {
116
172
  try {
117
173
  let result = await Rest.fetch(trackTablesRoute, {
@@ -127,15 +183,23 @@ async function trackTables(endpoint, auth, pgSchema, tableConfigs) {
127
183
  if (description !== undefined) {
128
184
  configuration["comment"] = description;
129
185
  }
130
- let columnConfigEntries = Object.entries(param.columnDescriptions);
186
+ let columnConfigEntries = Object.entries(param.columnConfigs);
131
187
  if (columnConfigEntries.length !== 0) {
132
- let columnConfig = {};
188
+ let columnConfigJson = {};
133
189
  columnConfigEntries.forEach(param => {
134
- columnConfig[param[0]] = {
135
- comment: param[1]
136
- };
190
+ let config = param[1];
191
+ let entry = {};
192
+ let customName = config.customName;
193
+ if (customName !== undefined) {
194
+ entry["custom_name"] = customName;
195
+ }
196
+ let comment = config.comment;
197
+ if (comment !== undefined) {
198
+ entry["comment"] = comment;
199
+ }
200
+ columnConfigJson[param[0]] = entry;
137
201
  });
138
- configuration["column_config"] = columnConfig;
202
+ configuration["column_config"] = columnConfigJson;
139
203
  }
140
204
  return {
141
205
  table: {
@@ -184,7 +248,7 @@ async function createSelectPermission(endpoint, auth, tableName, pgSchema, respo
184
248
  }
185
249
 
186
250
  async function createEntityRelationship(endpoint, auth, pgSchema, tableName, relationshipType, relationalKey, objectName, mappedEntity, isDerivedFrom, comment) {
187
- let derivedFromTo = isDerivedFrom ? `"id": "` + relationalKey + `"` : `"` + relationalKey + `_id" : "id"`;
251
+ let derivedFromTo = isDerivedFrom ? `"id": "` + relationalKey + `"` : `"` + relationalKey + `" : "id"`;
188
252
  let tableJson = {
189
253
  schema: pgSchema,
190
254
  name: tableName
@@ -218,45 +282,32 @@ async function trackDatabase(endpoint, auth, pgSchema, userEntities, aggregateEn
218
282
  {
219
283
  tableName: InternalTable.RawEvents.table.tableName,
220
284
  description: undefined,
221
- columnDescriptions: {}
285
+ columnConfigs: {}
222
286
  },
223
287
  {
224
288
  tableName: InternalTable.Views.metaViewName,
225
289
  description: undefined,
226
- columnDescriptions: {}
290
+ columnConfigs: {}
227
291
  },
228
292
  {
229
293
  tableName: InternalTable.Views.chainMetadataViewName,
230
294
  description: undefined,
231
- columnDescriptions: {}
295
+ columnConfigs: {}
232
296
  }
233
297
  ];
234
- let userTableConfigs = userEntities.map(entity => {
235
- let columnDescriptions = {};
236
- entity.table.fields.forEach(fieldOrDerived => {
237
- if (fieldOrDerived.TAG !== "Field") {
238
- return;
239
- }
240
- let field = fieldOrDerived._0;
241
- let d = field.description;
242
- if (d !== undefined) {
243
- columnDescriptions[Table.getDbFieldName(field)] = d;
244
- return;
245
- }
246
- });
247
- return {
248
- tableName: entity.table.tableName,
249
- description: entity.table.description,
250
- columnDescriptions: columnDescriptions
251
- };
252
- });
253
- let tableConfigs = Belt_Array.concatMany([
298
+ let userTableConfigs = userEntities.map(entity => ({
299
+ tableName: entity.table.tableName,
300
+ description: entity.table.description,
301
+ columnConfigs: makeColumnConfigs(entity.table)
302
+ }));
303
+ let tableConfigs = [
254
304
  exposedInternalTableConfigs,
255
305
  userTableConfigs
256
- ]);
306
+ ].flat();
257
307
  let tableNames = tableConfigs.map(c => c.tableName);
258
308
  Logging.info("Tracking tables in Hasura");
259
309
  await clearHasuraMetadata(endpoint, auth);
310
+ await reloadHasuraMetadata(endpoint, auth);
260
311
  await trackTables(endpoint, auth, pgSchema, tableConfigs);
261
312
  for (let i = 0, i_finish = tableNames.length; i < i_finish; ++i) {
262
313
  let tableName = tableNames[i];
@@ -269,14 +320,14 @@ async function trackDatabase(endpoint, auth, pgSchema, userEntities, aggregateEn
269
320
  let derivedFromFields = Table.getDerivedFromFields(entityConfig.table);
270
321
  for (let j = 0, j_finish = derivedFromFields.length; j < j_finish; ++j) {
271
322
  let derivedFromField = derivedFromFields[j];
272
- let relationalFieldName = Utils.unwrapResultExn(Schema.getDerivedFromFieldName(schema, derivedFromField));
323
+ let relationalFieldName = Utils.unwrapResultExn(Schema.getDerivedFromPgFieldName(schema, derivedFromField));
273
324
  await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "array", relationalFieldName, derivedFromField.fieldName, derivedFromField.derivedFromEntity, true, derivedFromField.description);
274
325
  }
275
326
  let linkedEntityFields = Table.getLinkedEntityFields(entityConfig.table);
276
327
  for (let j$1 = 0, j_finish$1 = linkedEntityFields.length; j$1 < j_finish$1; ++j$1) {
277
328
  let match$1 = linkedEntityFields[j$1];
278
329
  let field = match$1[0];
279
- await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "object", field.fieldName, field.fieldName, match$1[1], false, field.description);
330
+ await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "object", Table.getPgDbFieldName(field), field.fieldName, match$1[1], false, field.description);
280
331
  }
281
332
  }
282
333
  return Logging.info("Hasura configuration completed");
@@ -286,10 +337,13 @@ export {
286
337
  auth,
287
338
  responses,
288
339
  clearMetadataRoute,
340
+ reloadMetadataRoute,
289
341
  trackTablesRoute,
290
342
  rawBodyRoute,
291
343
  sendOperation,
292
344
  clearHasuraMetadata,
345
+ reloadHasuraMetadata,
346
+ makeColumnConfigs,
293
347
  trackTables,
294
348
  createSelectPermission,
295
349
  createEntityRelationship,
@@ -45,6 +45,10 @@ type t = {
45
45
  // Processed but unwritten. The cycle drains them, splitting each write at a
46
46
  // change in isInReorgThreshold so it never mixes history-saving modes.
47
47
  mutable processedBatches: array<Batch.t>,
48
+ // True while a batch is being processed; guards ProcessEventBatch re-entry.
49
+ mutable isProcessing: bool,
50
+ // Count of processed batches; version-independent progress counter.
51
+ mutable processedBatchesCount: int,
48
52
  // The single in-flight write loop, None when idle.
49
53
  mutable writeFiber: option<promise<unit>>,
50
54
  // Set once a write throws, to stop the loop. The error itself goes to onError.
@@ -91,6 +95,8 @@ let make = (
91
95
  committedCheckpointId,
92
96
  processedCheckpointId: committedCheckpointId,
93
97
  processedBatches: [],
98
+ isProcessing: false,
99
+ processedBatchesCount: 0,
94
100
  writeFiber: None,
95
101
  hasFailedWrite: false,
96
102
  onError,
@@ -542,7 +548,10 @@ let prepareRollbackDiff = async (
542
548
  )
543
549
  })
544
550
 
545
- let restoredEntities = restoredEntitiesResult->S.parseOrThrow(entityConfig.rowsSchema)
551
+ let restoredEntities =
552
+ restoredEntitiesResult
553
+ ->S.parseOrThrow(entityConfig.table->Table.pgRowsSchema)
554
+ ->(Utils.magic: array<unknown> => array<Internal.entity>)
546
555
 
547
556
  restoredEntities->Array.forEach((entity: Internal.entity) => {
548
557
  setEntities->Utils.Dict.push(entityConfig.name, entity.id)
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Env from "./Env.res.mjs";
4
+ import * as Table from "./db/Table.res.mjs";
4
5
  import * as Utils from "./Utils.res.mjs";
5
6
  import * as Config from "./Config.res.mjs";
6
7
  import * as Logging from "./Logging.res.mjs";
@@ -61,6 +62,8 @@ function make$1(entities, committedCheckpointIdOpt, persistence, config, onError
61
62
  committedCheckpointId: committedCheckpointId,
62
63
  processedCheckpointId: committedCheckpointId,
63
64
  processedBatches: [],
65
+ isProcessing: false,
66
+ processedBatchesCount: 0,
64
67
  writeFiber: undefined,
65
68
  hasFailedWrite: false,
66
69
  onError: onError,
@@ -436,7 +439,7 @@ async function prepareRollbackDiff(inMemoryStore, persistence, rollbackTargetChe
436
439
  checkpointId: rollbackDiffCheckpointId
437
440
  });
438
441
  });
439
- let restoredEntities = S$RescriptSchema.parseOrThrow(match[1], entityConfig.rowsSchema);
442
+ let restoredEntities = S$RescriptSchema.parseOrThrow(match[1], Table.pgRowsSchema(entityConfig.table));
440
443
  restoredEntities.forEach(entity => {
441
444
  Utils.Dict.push(setEntities, entityConfig.name, entity.id);
442
445
  InMemoryTable.Entity.set(entityTable, inMemoryStore.committedCheckpointId, {
@@ -1,12 +1,10 @@
1
1
  module Entity = {
2
2
  type relatedEntityId = string
3
- type indexWithRelatedIds = (TableIndices.Index.t, Utils.Set.t<relatedEntityId>)
4
- // Keyed by TableIndices.Index.toString
5
- type indicesSerializedToValue = dict<indexWithRelatedIds>
6
- // Keyed by TableIndices.Index.getFieldName
7
- type indexFieldNameToIndices = dict<indicesSerializedToValue>
3
+ type filterWithRelatedIds = (EntityFilter.t, Utils.Set.t<relatedEntityId>)
4
+ // Keyed by EntityFilter.toString
5
+ type filterIndices = dict<filterWithRelatedIds>
8
6
 
9
- type entityIndices = Utils.Set.t<TableIndices.Index.t>
7
+ type entityFilters = Utils.Set.t<EntityFilter.t>
10
8
  type t = {
11
9
  latestEntityChangeById: dict<Change.t<Internal.entity>>,
12
10
  // Recorded changes (new latest ids + prevEntityChanges pushes), tracked
@@ -15,8 +13,8 @@ module Entity = {
15
13
  // Swapped out when a write starts so processing keeps appending while the
16
14
  // previous changes persist in the background.
17
15
  mutable prevEntityChanges: array<Change.t<Internal.entity>>,
18
- mutable indicesByEntityId: dict<entityIndices>,
19
- mutable fieldNameIndices: indexFieldNameToIndices,
16
+ mutable filtersByEntityId: dict<entityFilters>,
17
+ mutable filterIndices: filterIndices,
20
18
  }
21
19
 
22
20
  // Helper to extract entity ID from any entity
@@ -30,30 +28,21 @@ module Entity = {
30
28
  )
31
29
  }
32
30
 
33
- let getOrCreateEntityIndices = (self: t, ~entityId) =>
34
- switch self.indicesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
31
+ let getOrCreateEntityFilters = (self: t, ~entityId) =>
32
+ switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
35
33
  | Some(s) => s
36
34
  | None =>
37
35
  let s = Utils.Set.make()
38
- self.indicesByEntityId->Dict.set(entityId, s)
36
+ self.filtersByEntityId->Dict.set(entityId, s)
39
37
  s
40
38
  }
41
39
 
42
- let makeIndicesSerializedToValue = (
43
- ~index,
44
- ~relatedEntityIds=Utils.Set.make(),
45
- ): indicesSerializedToValue => {
46
- let empty = Dict.make()
47
- empty->Dict.set(index->TableIndices.Index.toString, (index, relatedEntityIds))
48
- empty
49
- }
50
-
51
40
  let make = (): t => {
52
41
  latestEntityChangeById: Dict.make(),
53
42
  changesCount: 0.,
54
43
  prevEntityChanges: [],
55
- indicesByEntityId: Dict.make(),
56
- fieldNameIndices: Dict.make(),
44
+ filtersByEntityId: Dict.make(),
45
+ filterIndices: Dict.make(),
57
46
  }
58
47
 
59
48
  // Changes to persist for checkpoints in (committedCheckpointId, upToCheckpointId].
@@ -64,15 +53,22 @@ module Entity = {
64
53
  > => {
65
54
  let changes = []
66
55
  let keptPrev = []
67
- self.prevEntityChanges->Array.forEach(change =>
68
- if change->Change.getCheckpointId > upToCheckpointId {
56
+ self.prevEntityChanges->Array.forEach(change => {
57
+ let checkpointId = change->Change.getCheckpointId
58
+ if checkpointId > upToCheckpointId {
69
59
  keptPrev->Array.push(change)
70
- } else {
60
+ } else if checkpointId > committedCheckpointId {
71
61
  changes->Array.push(change)
72
62
  }
73
- )
63
+ // Drop changes at or below committedCheckpointId: they were already
64
+ // snapshotted by the write that committed them. They land here when an
65
+ // entity is overwritten while that write is still in flight — set's
66
+ // guard compares against the not-yet-advanced committed checkpoint —
67
+ // and re-emitting them would write duplicate history rows.
68
+ })
69
+ let removedCount = self.prevEntityChanges->Array.length - keptPrev->Array.length
74
70
  self.prevEntityChanges = keptPrev
75
- self.changesCount = self.changesCount -. changes->Array.length->Int.toFloat
71
+ self.changesCount = self.changesCount -. removedCount->Int.toFloat
76
72
  self.latestEntityChangeById->Utils.Dict.forEach(change => {
77
73
  let checkpointId = change->Change.getCheckpointId
78
74
  if checkpointId > committedCheckpointId && !(checkpointId > upToCheckpointId) {
@@ -99,63 +95,49 @@ module Entity = {
99
95
  })
100
96
  keysToDelete->Array.forEach(key => self.latestEntityChangeById->Utils.Dict.deleteInPlace(key))
101
97
  self.changesCount = self.changesCount -. keysToDelete->Array.length->Int.toFloat
102
- self.indicesByEntityId = Dict.make()
103
- self.fieldNameIndices = Dict.make()
98
+ self.filtersByEntityId = Dict.make()
99
+ self.filterIndices = Dict.make()
104
100
  }
105
101
 
106
102
  let updateIndices = (self: t, ~entity: Internal.entity) => {
107
103
  let entityId = entity->getEntityIdUnsafe
108
- //Remove any invalid indices on entity
109
- switch self.indicesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
104
+ let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
105
+
106
+ //Remove any invalid filters on entity
107
+ switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
110
108
  | None => ()
111
- | Some(entityIndices) =>
112
- entityIndices->Utils.Set.forEach(index => {
113
- let fieldName = index->TableIndices.Index.getFieldName
114
- let fieldValue =
115
- entity
116
- ->(Utils.magic: Internal.entity => dict<TableIndices.FieldValue.t>)
117
- ->Dict.getUnsafe(fieldName)
118
- if !(index->TableIndices.Index.evaluate(~fieldName, ~fieldValue)) {
119
- entityIndices->Utils.Set.delete(index)->ignore
109
+ | Some(entityFilters) =>
110
+ entityFilters->Utils.Set.forEach(filter => {
111
+ if !(filter->EntityFilter.matches(~entity=entityAsDict)) {
112
+ entityFilters->Utils.Set.delete(filter)->ignore
120
113
  }
121
114
  })
122
115
  }
123
116
 
124
- self.fieldNameIndices->Utils.Dict.forEachWithKey((indices, fieldName) => {
125
- // A missing key reads as `undefined`, which matches the `None` arm of
126
- // `FieldValue.t` (`option<...>`). Mirror `addEmptyIndex` so nullable
127
- // FK columns that were omitted on the set entity don't crash.
128
- let fieldValue =
129
- entity
130
- ->(Utils.magic: Internal.entity => dict<TableIndices.FieldValue.t>)
131
- ->Dict.getUnsafe(fieldName)
132
- indices->Utils.Dict.forEach(((index, relatedEntityIds)) => {
133
- if index->TableIndices.Index.evaluate(~fieldName, ~fieldValue) {
134
- //Add entity id to indices and add index to entity indicies
135
- relatedEntityIds->Utils.Set.add(entityId)->ignore
136
- self->getOrCreateEntityIndices(~entityId)->Utils.Set.add(index)->ignore
137
- } else {
138
- relatedEntityIds->Utils.Set.delete(entityId)->ignore
139
- }
140
- })
117
+ self.filterIndices->Utils.Dict.forEach(((filter, relatedEntityIds)) => {
118
+ if filter->EntityFilter.matches(~entity=entityAsDict) {
119
+ //Add entity id to the filter index and the filter to entity filters
120
+ relatedEntityIds->Utils.Set.add(entityId)->ignore
121
+ self->getOrCreateEntityFilters(~entityId)->Utils.Set.add(filter)->ignore
122
+ } else {
123
+ relatedEntityIds->Utils.Set.delete(entityId)->ignore
124
+ }
141
125
  })
142
126
  }
143
127
 
144
128
  let deleteEntityFromIndices = (self: t, ~entityId: string) =>
145
- switch self.indicesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
129
+ switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
146
130
  | None => ()
147
- | Some(entityIndices) =>
148
- entityIndices->Utils.Set.forEach(index => {
149
- switch self.fieldNameIndices
150
- ->Utils.Dict.dangerouslyGetNonOption(index->TableIndices.Index.getFieldName)
151
- ->Option.flatMap(indices =>
152
- indices->Utils.Dict.dangerouslyGetNonOption(index->TableIndices.Index.toString)
131
+ | Some(entityFilters) =>
132
+ entityFilters->Utils.Set.forEach(filter => {
133
+ switch self.filterIndices->Utils.Dict.dangerouslyGetNonOption(
134
+ filter->EntityFilter.toString,
153
135
  ) {
154
- | Some((_index, relatedEntityIds)) =>
136
+ | Some((_filter, relatedEntityIds)) =>
155
137
  let _wasRemoved = relatedEntityIds->Utils.Set.delete(entityId)
156
- | None => () //Unexpected index should exist if it is entityIndices
138
+ | None => () //Unexpected filter index should exist if it is in entityFilters
157
139
  }
158
- let _wasRemoved = entityIndices->Utils.Set.delete(index)
140
+ let _wasRemoved = entityFilters->Utils.Set.delete(filter)
159
141
  })
160
142
  }
161
143
 
@@ -214,84 +196,49 @@ module Entity = {
214
196
  ->Dict.getUnsafe(key)
215
197
  ->mapChangeToEntity
216
198
 
217
- let hasIndex = (inMemTable: t, ~fieldName, ~operator: TableIndices.Operator.t) =>
218
- fieldValueHash => {
219
- switch inMemTable.fieldNameIndices->Utils.Dict.dangerouslyGetNonOption(fieldName) {
220
- | None => false
221
- | Some(indicesSerializedToValue) => {
222
- let key = TableIndices.Index.toStringByParts(~fieldName, ~operator, ~fieldValueHash)
223
- indicesSerializedToValue->Utils.Dict.dangerouslyGetNonOption(key) !== None
224
- }
225
- }
226
- }
199
+ let hasIndex = (inMemTable: t) =>
200
+ (filterKey: string) =>
201
+ inMemTable.filterIndices->Utils.Dict.dangerouslyGetNonOption(filterKey) !== None
227
202
 
228
- let getUnsafeOnIndex = (inMemTable: t, ~fieldName, ~operator: TableIndices.Operator.t) => {
203
+ let getUnsafeOnIndex = (inMemTable: t) => {
229
204
  let getEntity = inMemTable->getUnsafe
230
- fieldValueHash => {
231
- switch inMemTable.fieldNameIndices->Utils.Dict.dangerouslyGetNonOption(fieldName) {
205
+ (filterKey: string) => {
206
+ switch inMemTable.filterIndices->Utils.Dict.dangerouslyGetNonOption(filterKey) {
232
207
  | None =>
233
- JsError.throwWithMessage(`Unexpected error. Must have an index on field ${fieldName}`)
234
- | Some(indicesSerializedToValue) => {
235
- let key = TableIndices.Index.toStringByParts(~fieldName, ~operator, ~fieldValueHash)
236
- switch indicesSerializedToValue->Utils.Dict.dangerouslyGetNonOption(key) {
237
- | None =>
238
- JsError.throwWithMessage(
239
- `Unexpected error. Must have an index for the value ${fieldValueHash} on field ${fieldName}`,
240
- )
241
- | Some((_index, relatedEntityIds)) => {
242
- let res =
243
- relatedEntityIds
244
- ->Utils.Set.toArray
245
- ->Array.filterMap(entityId => {
246
- switch inMemTable.latestEntityChangeById->Dict.has(entityId) {
247
- | true => getEntity(entityId)
248
- | false => None
249
- }
250
- })
251
- res
252
- }
208
+ JsError.throwWithMessage(`Unexpected error. Must have an index for the filter ${filterKey}`)
209
+ | Some((_filter, relatedEntityIds)) =>
210
+ relatedEntityIds
211
+ ->Utils.Set.toArray
212
+ ->Array.filterMap(entityId => {
213
+ switch inMemTable.latestEntityChangeById->Dict.has(entityId) {
214
+ | true => getEntity(entityId)
215
+ | false => None
253
216
  }
254
- }
217
+ })
255
218
  }
256
219
  }
257
220
  }
258
221
 
259
- let addEmptyIndex = (inMemTable: t, ~index) => {
260
- let fieldName = index->TableIndices.Index.getFieldName
261
- let relatedEntityIds = Utils.Set.make()
262
-
263
- inMemTable.latestEntityChangeById->Utils.Dict.forEach(change => {
264
- switch change->mapChangeToEntity {
265
- | Some(entity) =>
266
- let fieldValue =
267
- entity
268
- ->(Utils.magic: Internal.entity => dict<TableIndices.FieldValue.t>)
269
- ->Dict.getUnsafe(fieldName)
270
- if index->TableIndices.Index.evaluate(~fieldName, ~fieldValue) {
271
- let entityId = entity->getEntityIdUnsafe
272
- let _ = inMemTable->getOrCreateEntityIndices(~entityId)->Utils.Set.add(index)
273
- let _ = relatedEntityIds->Utils.Set.add(entityId)
274
- }
275
- | None => ()
276
- }
277
- })
278
- switch inMemTable.fieldNameIndices->Utils.Dict.dangerouslyGetNonOption(fieldName) {
222
+ let addEmptyIndex = (inMemTable: t, ~filter: EntityFilter.t) => {
223
+ let filterKey = filter->EntityFilter.toString
224
+ switch inMemTable.filterIndices->Utils.Dict.dangerouslyGetNonOption(filterKey) {
225
+ | Some(_) => () //Should not happen, this means the index already exists
279
226
  | None =>
280
- inMemTable.fieldNameIndices->Dict.set(
281
- fieldName,
282
- makeIndicesSerializedToValue(~index, ~relatedEntityIds),
283
- )
284
- | Some(indicesSerializedToValue) =>
285
- switch indicesSerializedToValue->Utils.Dict.dangerouslyGetNonOption(
286
- index->TableIndices.Index.toString,
287
- ) {
288
- | None =>
289
- indicesSerializedToValue->Dict.set(
290
- index->TableIndices.Index.toString,
291
- (index, relatedEntityIds),
292
- )
293
- | Some(_) => () //Should not happen, this means the index already exists
294
- }
227
+ let relatedEntityIds = Utils.Set.make()
228
+ inMemTable.latestEntityChangeById->Utils.Dict.forEach(change => {
229
+ switch change->mapChangeToEntity {
230
+ | Some(entity) =>
231
+ let entityAsDict =
232
+ entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
233
+ if filter->EntityFilter.matches(~entity=entityAsDict) {
234
+ let entityId = entity->getEntityIdUnsafe
235
+ let _ = inMemTable->getOrCreateEntityFilters(~entityId)->Utils.Set.add(filter)
236
+ let _ = relatedEntityIds->Utils.Set.add(entityId)
237
+ }
238
+ | None => ()
239
+ }
240
+ })
241
+ inMemTable.filterIndices->Dict.set(filterKey, (filter, relatedEntityIds))
295
242
  }
296
243
  }
297
244
  }