envio 3.0.2 → 3.1.0-rc.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.
Files changed (101) hide show
  1. package/README.md +0 -1
  2. package/evm.schema.json +15 -8
  3. package/fuel.schema.json +19 -12
  4. package/index.d.ts +0 -2
  5. package/package.json +6 -7
  6. package/rescript.json +1 -1
  7. package/src/Batch.res +4 -214
  8. package/src/Batch.res.mjs +6 -165
  9. package/src/ChainFetcher.res +12 -28
  10. package/src/ChainFetcher.res.mjs +8 -17
  11. package/src/ChainManager.res +10 -9
  12. package/src/ChainManager.res.mjs +6 -10
  13. package/src/Config.res +9 -25
  14. package/src/Config.res.mjs +17 -27
  15. package/src/Core.res +7 -0
  16. package/src/Ctx.res +1 -0
  17. package/src/Env.res +0 -8
  18. package/src/Env.res.mjs +0 -6
  19. package/src/EventConfigBuilder.res +13 -123
  20. package/src/EventConfigBuilder.res.mjs +6 -73
  21. package/src/EventProcessing.res +5 -29
  22. package/src/EventProcessing.res.mjs +11 -20
  23. package/src/EventUtils.res +0 -27
  24. package/src/EventUtils.res.mjs +0 -24
  25. package/src/FetchState.res +2 -15
  26. package/src/FetchState.res.mjs +3 -18
  27. package/src/GlobalState.res +26 -39
  28. package/src/GlobalState.res.mjs +12 -40
  29. package/src/HandlerLoader.res +6 -5
  30. package/src/HandlerLoader.res.mjs +27 -9
  31. package/src/HandlerRegister.res +1 -12
  32. package/src/HandlerRegister.res.mjs +1 -6
  33. package/src/HandlerRegister.resi +1 -1
  34. package/src/Hasura.res +96 -32
  35. package/src/Hasura.res.mjs +93 -38
  36. package/src/InMemoryStore.res +205 -45
  37. package/src/InMemoryStore.res.mjs +157 -40
  38. package/src/InMemoryTable.res +165 -249
  39. package/src/InMemoryTable.res.mjs +156 -227
  40. package/src/Internal.res +10 -34
  41. package/src/Internal.res.mjs +9 -3
  42. package/src/LoadLayer.res +5 -5
  43. package/src/LoadLayer.res.mjs +5 -5
  44. package/src/LogSelection.res +15 -19
  45. package/src/LogSelection.res.mjs +5 -6
  46. package/src/Main.res +4 -6
  47. package/src/Main.res.mjs +26 -15
  48. package/src/Persistence.res +7 -132
  49. package/src/Persistence.res.mjs +1 -102
  50. package/src/PgStorage.res +57 -40
  51. package/src/PgStorage.res.mjs +60 -34
  52. package/src/ReorgDetection.res +35 -58
  53. package/src/ReorgDetection.res.mjs +21 -29
  54. package/src/SimulateItems.res.mjs +21 -3
  55. package/src/Sink.res +2 -2
  56. package/src/Sink.res.mjs +1 -1
  57. package/src/TableIndices.res +9 -2
  58. package/src/TableIndices.res.mjs +7 -1
  59. package/src/TestIndexer.res +53 -60
  60. package/src/TestIndexer.res.mjs +77 -63
  61. package/src/TestIndexerProxyStorage.res +4 -14
  62. package/src/TestIndexerProxyStorage.res.mjs +1 -5
  63. package/src/UserContext.res +2 -4
  64. package/src/UserContext.res.mjs +4 -5
  65. package/src/Utils.res +0 -2
  66. package/src/Utils.res.mjs +0 -3
  67. package/src/bindings/ClickHouse.res +45 -38
  68. package/src/bindings/ClickHouse.res.mjs +16 -17
  69. package/src/bindings/Vitest.res +3 -0
  70. package/src/db/InternalTable.res +59 -18
  71. package/src/db/InternalTable.res.mjs +82 -51
  72. package/src/db/Table.res +9 -2
  73. package/src/db/Table.res.mjs +10 -7
  74. package/src/sources/EnvioApiClient.res +15 -0
  75. package/src/sources/EnvioApiClient.res.mjs +24 -0
  76. package/src/sources/EvmChain.res +32 -10
  77. package/src/sources/EvmChain.res.mjs +31 -5
  78. package/src/sources/HyperFuelSource.res +15 -58
  79. package/src/sources/HyperFuelSource.res.mjs +20 -39
  80. package/src/sources/HyperSync.res +54 -100
  81. package/src/sources/HyperSync.res.mjs +67 -96
  82. package/src/sources/HyperSync.resi +4 -22
  83. package/src/sources/HyperSyncClient.res +70 -247
  84. package/src/sources/HyperSyncClient.res.mjs +47 -46
  85. package/src/sources/HyperSyncSource.res +94 -166
  86. package/src/sources/HyperSyncSource.res.mjs +100 -127
  87. package/src/sources/RpcSource.res +43 -22
  88. package/src/sources/RpcSource.res.mjs +50 -35
  89. package/src/sources/SimulateSource.res +1 -7
  90. package/src/sources/SimulateSource.res.mjs +1 -7
  91. package/src/sources/Source.res +10 -1
  92. package/src/sources/Source.res.mjs +3 -0
  93. package/src/sources/SourceManager.res +177 -8
  94. package/src/sources/SourceManager.res.mjs +141 -3
  95. package/src/sources/SourceManager.resi +19 -0
  96. package/src/tui/Tui.res +44 -6
  97. package/src/tui/Tui.res.mjs +56 -8
  98. package/src/tui/components/TuiData.res +3 -0
  99. package/svm.schema.json +11 -4
  100. package/src/sources/HyperSyncJsonApi.res +0 -390
  101. package/src/sources/HyperSyncJsonApi.res.mjs +0 -237
@@ -1,55 +1,27 @@
1
- type t<'key, 'val> = {
2
- dict: dict<'val>,
3
- hash: 'key => string,
4
- }
5
-
6
- let make = (~hash): t<'key, 'val> => {
7
- dict: Dict.make(),
8
- hash,
9
- }
10
-
11
- let set = (self: t<'key, 'val>, key, value) => self.dict->Dict.set(key->self.hash, value)
12
-
13
- let setByHash = (self: t<'key, 'val>, hash, value) => self.dict->Dict.set(hash, value)
14
-
15
- let hasByHash = (self: t<'key, 'val>, hash) => {
16
- self.dict->Utils.Dict.has(hash)
17
- }
18
-
19
- let getUnsafeByHash = (self: t<'key, 'val>, hash) => {
20
- self.dict->Dict.getUnsafe(hash)
21
- }
22
-
23
- let get = (self: t<'key, 'val>, key: 'key) =>
24
- self.dict->Utils.Dict.dangerouslyGetNonOption(key->self.hash)
25
-
26
- let values = (self: t<'key, 'val>) => self.dict->Dict.valuesToArray
27
-
28
- let clone = (self: t<'key, 'val>) => {
29
- ...self,
30
- dict: self.dict->Lodash.cloneDeep,
31
- }
32
-
33
1
  module Entity = {
34
2
  type relatedEntityId = string
35
3
  type indexWithRelatedIds = (TableIndices.Index.t, Utils.Set.t<relatedEntityId>)
36
- type indicesSerializedToValue = t<TableIndices.Index.t, indexWithRelatedIds>
37
- type indexFieldNameToIndices = t<TableIndices.Index.t, indicesSerializedToValue>
38
-
39
- type entityWithIndices<'entity> = {
40
- latest: option<'entity>,
41
- status: Internal.inMemoryStoreEntityStatus<'entity>,
42
- entityIndices: Utils.Set.t<TableIndices.Index.t>,
43
- }
44
- type t<'entity> = {
45
- table: t<string, entityWithIndices<'entity>>,
4
+ // Keyed by TableIndices.Index.toString
5
+ type indicesSerializedToValue = dict<indexWithRelatedIds>
6
+ // Keyed by TableIndices.Index.getFieldName
7
+ type indexFieldNameToIndices = dict<indicesSerializedToValue>
8
+
9
+ type entityIndices = Utils.Set.t<TableIndices.Index.t>
10
+ type t = {
11
+ latestEntityChangeById: dict<Change.t<Internal.entity>>,
12
+ // Counts every recorded change (new latest ids and pushes to
13
+ // prevEntityChanges), kept in sync manually so InMemoryStore can gauge the
14
+ // store size without scanning every dict.
15
+ mutable changesCount: float,
16
+ prevEntityChanges: array<Change.t<Internal.entity>>,
17
+ indicesByEntityId: dict<entityIndices>,
46
18
  fieldNameIndices: indexFieldNameToIndices,
47
19
  }
48
20
 
49
21
  // Helper to extract entity ID from any entity
50
22
  exception UnexpectedIdNotDefinedOnEntity
51
- let getEntityIdUnsafe = (entity: 'entity): string =>
52
- switch (entity->(Utils.magic: 'entity => {"id": option<string>}))["id"] {
23
+ let getEntityIdUnsafe = (entity: Internal.entity): string =>
24
+ switch (entity->(Utils.magic: Internal.entity => {"id": option<string>}))["id"] {
53
25
  | Some(id) => id
54
26
  | None =>
55
27
  UnexpectedIdNotDefinedOnEntity->ErrorHandling.mkLogAndRaise(
@@ -57,207 +29,192 @@ module Entity = {
57
29
  )
58
30
  }
59
31
 
32
+ let getOrCreateEntityIndices = (self: t, ~entityId) =>
33
+ switch self.indicesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
34
+ | Some(s) => s
35
+ | None =>
36
+ let s = Utils.Set.make()
37
+ self.indicesByEntityId->Dict.set(entityId, s)
38
+ s
39
+ }
40
+
60
41
  let makeIndicesSerializedToValue = (
61
42
  ~index,
62
43
  ~relatedEntityIds=Utils.Set.make(),
63
44
  ): indicesSerializedToValue => {
64
- let empty = make(~hash=TableIndices.Index.toString)
65
- empty->set(index, (index, relatedEntityIds))
45
+ let empty = Dict.make()
46
+ empty->Dict.set(index->TableIndices.Index.toString, (index, relatedEntityIds))
66
47
  empty
67
48
  }
68
49
 
69
- let make = (): t<'entity> => {
70
- table: make(~hash=str => str),
71
- fieldNameIndices: make(~hash=TableIndices.Index.getFieldName),
50
+ let make = (): t => {
51
+ latestEntityChangeById: Dict.make(),
52
+ changesCount: 0.,
53
+ prevEntityChanges: [],
54
+ indicesByEntityId: Dict.make(),
55
+ fieldNameIndices: Dict.make(),
72
56
  }
73
57
 
74
- let updateIndices = (
75
- self: t<'entity>,
76
- ~entity: 'entity,
77
- ~entityIndices: Utils.Set.t<TableIndices.Index.t>,
78
- ) => {
79
- //Remove any invalid indices on entity
80
- entityIndices->Utils.Set.forEach(index => {
81
- let fieldName = index->TableIndices.Index.getFieldName
82
- let fieldValue =
83
- entity
84
- ->(Utils.magic: 'entity => dict<TableIndices.FieldValue.t>)
85
- ->Dict.getUnsafe(fieldName)
86
- if !(index->TableIndices.Index.evaluate(~fieldName, ~fieldValue)) {
87
- entityIndices->Utils.Set.delete(index)->ignore
58
+ // Drops the per-batch index state and rollback history, but keeps the
59
+ // already committed entities so the next batch can read them without
60
+ // hitting the database.
61
+ let resetButKeepLatestChanges = (self: t): t => {
62
+ ...make(),
63
+ latestEntityChangeById: self.latestEntityChangeById,
64
+ // writeBatch already mutated this to subtract the dropped prevEntityChanges.
65
+ changesCount: self.changesCount,
66
+ }
67
+
68
+ // Like resetButKeepLatestChanges, but only keeps entities loaded from the db
69
+ // (changes carrying loadedFromDbCheckpointId), dropping everything written in
70
+ // a batch. The kept count is exposed through the table's changesCount.
71
+ let resetButKeepLoadedFromDbChanges = (self: t): t => {
72
+ let latestEntityChangeById = Dict.make()
73
+ let keptCount = ref(0.)
74
+ self.latestEntityChangeById->Utils.Dict.forEachWithKey((change, key) =>
75
+ if change->Change.getCheckpointId === Internal.loadedFromDbCheckpointId {
76
+ latestEntityChangeById->Dict.set(key, change)
77
+ keptCount := keptCount.contents +. 1.
88
78
  }
89
- })
79
+ )
80
+ {
81
+ ...make(),
82
+ latestEntityChangeById,
83
+ changesCount: keptCount.contents,
84
+ }
85
+ }
86
+
87
+ let updateIndices = (self: t, ~entity: Internal.entity) => {
88
+ let entityId = entity->getEntityIdUnsafe
89
+ //Remove any invalid indices on entity
90
+ switch self.indicesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
91
+ | None => ()
92
+ | Some(entityIndices) =>
93
+ entityIndices->Utils.Set.forEach(index => {
94
+ let fieldName = index->TableIndices.Index.getFieldName
95
+ let fieldValue =
96
+ entity
97
+ ->(Utils.magic: Internal.entity => dict<TableIndices.FieldValue.t>)
98
+ ->Dict.getUnsafe(fieldName)
99
+ if !(index->TableIndices.Index.evaluate(~fieldName, ~fieldValue)) {
100
+ entityIndices->Utils.Set.delete(index)->ignore
101
+ }
102
+ })
103
+ }
90
104
 
91
- self.fieldNameIndices.dict
92
- ->Dict.keysToArray
93
- ->Array.forEach(fieldName => {
94
- let indices = self.fieldNameIndices.dict->Dict.getUnsafe(fieldName)
105
+ self.fieldNameIndices->Utils.Dict.forEachWithKey((indices, fieldName) => {
95
106
  // A missing key reads as `undefined`, which matches the `None` arm of
96
107
  // `FieldValue.t` (`option<...>`). Mirror `addEmptyIndex` so nullable
97
108
  // FK columns that were omitted on the set entity don't crash.
98
109
  let fieldValue =
99
110
  entity
100
- ->(Utils.magic: 'entity => dict<TableIndices.FieldValue.t>)
111
+ ->(Utils.magic: Internal.entity => dict<TableIndices.FieldValue.t>)
101
112
  ->Dict.getUnsafe(fieldName)
102
- indices
103
- ->values
104
- ->Array.forEach(((index, relatedEntityIds)) => {
113
+ indices->Utils.Dict.forEach(((index, relatedEntityIds)) => {
105
114
  if index->TableIndices.Index.evaluate(~fieldName, ~fieldValue) {
106
115
  //Add entity id to indices and add index to entity indicies
107
- relatedEntityIds->Utils.Set.add(getEntityIdUnsafe(entity))->ignore
108
- entityIndices->Utils.Set.add(index)->ignore
116
+ relatedEntityIds->Utils.Set.add(entityId)->ignore
117
+ self->getOrCreateEntityIndices(~entityId)->Utils.Set.add(index)->ignore
109
118
  } else {
110
- relatedEntityIds->Utils.Set.delete(getEntityIdUnsafe(entity))->ignore
119
+ relatedEntityIds->Utils.Set.delete(entityId)->ignore
111
120
  }
112
121
  })
113
122
  })
114
123
  }
115
124
 
116
- let deleteEntityFromIndices = (self: t<'entity>, ~entityId: string, ~entityIndices) =>
117
- entityIndices->Utils.Set.forEach(index => {
118
- switch self.fieldNameIndices
119
- ->get(index)
120
- ->Option.flatMap(get(_, index)) {
121
- | Some((_index, relatedEntityIds)) =>
122
- let _wasRemoved = relatedEntityIds->Utils.Set.delete(entityId)
123
- | None => () //Unexpected index should exist if it is entityIndices
124
- }
125
- let _wasRemoved = entityIndices->Utils.Set.delete(index)
126
- })
127
-
128
- let initValue = (
129
- inMemTable: t<'entity>,
130
- ~key: string,
131
- ~entity: option<'entity>,
132
- // NOTE: This value is only set to true in the internals of the test framework to create the mockDb.
133
- ~allowOverWriteEntity=false,
134
- ) => {
135
- let shouldWriteEntity =
136
- allowOverWriteEntity ||
137
- inMemTable.table.dict->Dict.get(key->inMemTable.table.hash)->Option.isNone
125
+ let deleteEntityFromIndices = (self: t, ~entityId: string) =>
126
+ switch self.indicesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
127
+ | None => ()
128
+ | Some(entityIndices) =>
129
+ entityIndices->Utils.Set.forEach(index => {
130
+ switch self.fieldNameIndices
131
+ ->Utils.Dict.dangerouslyGetNonOption(index->TableIndices.Index.getFieldName)
132
+ ->Option.flatMap(indices =>
133
+ indices->Utils.Dict.dangerouslyGetNonOption(index->TableIndices.Index.toString)
134
+ ) {
135
+ | Some((_index, relatedEntityIds)) =>
136
+ let _wasRemoved = relatedEntityIds->Utils.Set.delete(entityId)
137
+ | None => () //Unexpected index should exist if it is entityIndices
138
+ }
139
+ let _wasRemoved = entityIndices->Utils.Set.delete(index)
140
+ })
141
+ }
138
142
 
139
- //Only initialize a row in the case where it is none
140
- //or if allowOverWriteEntity is true (used for mockDb in test helpers)
141
- if shouldWriteEntity {
142
- let entityIndices = Utils.Set.make()
143
- switch entity {
144
- | Some(entity) =>
145
- //update table indices in the case where there
146
- //is an already set entity
147
- inMemTable->updateIndices(~entity, ~entityIndices)
148
- | None => ()
143
+ let set = (inMemTable: t, ~committedCheckpointId, change: Change.t<Internal.entity>) => {
144
+ let entityId = change->Change.getEntityId
145
+ switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityId) {
146
+ | Some(prev) =>
147
+ let prevCheckpointId = prev->Change.getCheckpointId
148
+ if (
149
+ prevCheckpointId > committedCheckpointId &&
150
+ prevCheckpointId < change->Change.getCheckpointId
151
+ ) {
152
+ inMemTable.prevEntityChanges->Array.push(prev)
153
+ inMemTable.changesCount = inMemTable.changesCount +. 1.
149
154
  }
150
- inMemTable.table.dict->Dict.set(
151
- key->inMemTable.table.hash,
152
- {
153
- latest: entity,
154
- status: Loaded,
155
- entityIndices,
156
- },
157
- )
155
+ | None => inMemTable.changesCount = inMemTable.changesCount +. 1.
158
156
  }
159
- }
160
157
 
161
- let setRow = set
162
- let set = (
163
- inMemTable: t<'entity>,
164
- change: Change.t<'entity>,
165
- ~shouldSaveHistory,
166
- ~containsRollbackDiffChange=false,
167
- ) => {
168
- //New entity row with only the latest update
169
- @inline
170
- let newStatus = () => Internal.Updated({
171
- latestChange: change,
172
- history: shouldSaveHistory
173
- ? [change]
174
- : Utils.Array.immutableEmpty->(Utils.magic: array<unknown> => array<Change.t<'entity>>),
175
- containsRollbackDiffChange,
176
- })
177
- let latest = switch change {
178
- | Set({entity}) => Some(entity)
179
- | Delete(_) => None
158
+ switch change {
159
+ | Set({entity}) => inMemTable->updateIndices(~entity)
160
+ | Delete({entityId}) => inMemTable->deleteEntityFromIndices(~entityId)
180
161
  }
162
+ inMemTable.latestEntityChangeById->Dict.set(entityId, change)
163
+ }
181
164
 
182
- let updatedEntityRecord = switch inMemTable.table->get(change->Change.getEntityId) {
183
- | None => {latest, status: newStatus(), entityIndices: Utils.Set.make()}
184
- | Some({status: Loaded, entityIndices}) => {
185
- latest,
186
- status: newStatus(),
187
- entityIndices,
165
+ // Only writes when the id isn't already present, so set always takes its
166
+ // None branch here (committedCheckpointId is never read).
167
+ let initValue = (
168
+ inMemTable: t,
169
+ ~committedCheckpointId,
170
+ ~key: string,
171
+ ~entity: option<Internal.entity>,
172
+ ) =>
173
+ if inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(key)->Option.isNone {
174
+ let change: Change.t<Internal.entity> = switch entity {
175
+ | Some(entity) =>
176
+ Set({entityId: key, entity, checkpointId: Internal.loadedFromDbCheckpointId})
177
+ | None => Delete({entityId: key, checkpointId: Internal.loadedFromDbCheckpointId})
188
178
  }
189
- | Some({status: Updated(previous_values), entityIndices}) =>
190
- let newStatus = Internal.Updated({
191
- latestChange: change,
192
- history: switch shouldSaveHistory {
193
- // This prevents two db actions in the same event on the same entity from being recorded to the history table.
194
- | true
195
- if previous_values.latestChange->Change.getCheckpointId ===
196
- change->Change.getCheckpointId =>
197
- previous_values.history->Utils.Array.setIndexImmutable(
198
- previous_values.history->Array.length - 1,
199
- change,
200
- )
201
- | true => [...previous_values.history, change]
202
- | false => previous_values.history
203
- },
204
- containsRollbackDiffChange: previous_values.containsRollbackDiffChange,
205
- })
206
- {latest, status: newStatus, entityIndices}
179
+ inMemTable->set(~committedCheckpointId, change)
207
180
  }
208
181
 
182
+ let mapChangeToEntity = (change: Change.t<Internal.entity>) =>
209
183
  switch change {
210
- | Set({entity}) =>
211
- inMemTable->updateIndices(~entity, ~entityIndices=updatedEntityRecord.entityIndices)
212
- | Delete({entityId}) =>
213
- inMemTable->deleteEntityFromIndices(
214
- ~entityId,
215
- ~entityIndices=updatedEntityRecord.entityIndices,
216
- )
184
+ | Set({entity}) => Some(entity)
185
+ | Delete(_) => None
217
186
  }
218
- inMemTable.table->setRow(change->Change.getEntityId, updatedEntityRecord)
219
- }
220
-
221
- let rowToEntity = row => row.latest
222
-
223
- let getRow = get
224
187
 
225
188
  /** It returns option<option<'entity>> where the first option means
226
189
  that the entity is not set to the in memory store,
227
190
  and the second option means that the entity doesn't esist/deleted.
228
191
  It's needed to prevent an additional round trips to the database for deleted entities. */
229
- let getUnsafe = (inMemTable: t<'entity>) =>
192
+ let getUnsafe = (inMemTable: t) =>
230
193
  (key: string) =>
231
- inMemTable.table.dict
194
+ inMemTable.latestEntityChangeById
232
195
  ->Dict.getUnsafe(key)
233
- ->rowToEntity
196
+ ->mapChangeToEntity
234
197
 
235
- let hasIndex = (inMemTable: t<'entity>, ~fieldName, ~operator: TableIndices.Operator.t) =>
198
+ let hasIndex = (inMemTable: t, ~fieldName, ~operator: TableIndices.Operator.t) =>
236
199
  fieldValueHash => {
237
- switch inMemTable.fieldNameIndices.dict->Utils.Dict.dangerouslyGetNonOption(fieldName) {
200
+ switch inMemTable.fieldNameIndices->Utils.Dict.dangerouslyGetNonOption(fieldName) {
238
201
  | None => false
239
202
  | Some(indicesSerializedToValue) => {
240
- // Should match TableIndices.toString logic
241
- let key = `${fieldName}:${(operator :> string)}:${fieldValueHash}`
242
- indicesSerializedToValue.dict->Utils.Dict.dangerouslyGetNonOption(key) !== None
203
+ let key = TableIndices.Index.toStringByParts(~fieldName, ~operator, ~fieldValueHash)
204
+ indicesSerializedToValue->Utils.Dict.dangerouslyGetNonOption(key) !== None
243
205
  }
244
206
  }
245
207
  }
246
208
 
247
- let getUnsafeOnIndex = (
248
- inMemTable: t<'entity>,
249
- ~fieldName,
250
- ~operator: TableIndices.Operator.t,
251
- ) => {
209
+ let getUnsafeOnIndex = (inMemTable: t, ~fieldName, ~operator: TableIndices.Operator.t) => {
252
210
  let getEntity = inMemTable->getUnsafe
253
211
  fieldValueHash => {
254
- switch inMemTable.fieldNameIndices.dict->Utils.Dict.dangerouslyGetNonOption(fieldName) {
212
+ switch inMemTable.fieldNameIndices->Utils.Dict.dangerouslyGetNonOption(fieldName) {
255
213
  | None =>
256
214
  JsError.throwWithMessage(`Unexpected error. Must have an index on field ${fieldName}`)
257
215
  | Some(indicesSerializedToValue) => {
258
- // Should match TableIndices.toString logic
259
- let key = `${fieldName}:${(operator :> string)}:${fieldValueHash}`
260
- switch indicesSerializedToValue.dict->Utils.Dict.dangerouslyGetNonOption(key) {
216
+ let key = TableIndices.Index.toStringByParts(~fieldName, ~operator, ~fieldValueHash)
217
+ switch indicesSerializedToValue->Utils.Dict.dangerouslyGetNonOption(key) {
261
218
  | None =>
262
219
  JsError.throwWithMessage(
263
220
  `Unexpected error. Must have an index for the value ${fieldValueHash} on field ${fieldName}`,
@@ -267,7 +224,7 @@ module Entity = {
267
224
  relatedEntityIds
268
225
  ->Utils.Set.toArray
269
226
  ->Array.filterMap(entityId => {
270
- switch hasByHash(inMemTable.table, entityId) {
227
+ switch inMemTable.latestEntityChangeById->Dict.has(entityId) {
271
228
  | true => getEntity(entityId)
272
229
  | false => None
273
230
  }
@@ -280,83 +237,42 @@ module Entity = {
280
237
  }
281
238
  }
282
239
 
283
- let addEmptyIndex = (inMemTable: t<'entity>, ~index) => {
240
+ let addEmptyIndex = (inMemTable: t, ~index) => {
284
241
  let fieldName = index->TableIndices.Index.getFieldName
285
242
  let relatedEntityIds = Utils.Set.make()
286
243
 
287
- inMemTable.table
288
- ->values
289
- ->Array.forEach(row => {
290
- switch row->rowToEntity {
244
+ inMemTable.latestEntityChangeById->Utils.Dict.forEach(change => {
245
+ switch change->mapChangeToEntity {
291
246
  | Some(entity) =>
292
247
  let fieldValue =
293
248
  entity
294
- ->(Utils.magic: 'entity => dict<TableIndices.FieldValue.t>)
249
+ ->(Utils.magic: Internal.entity => dict<TableIndices.FieldValue.t>)
295
250
  ->Dict.getUnsafe(fieldName)
296
251
  if index->TableIndices.Index.evaluate(~fieldName, ~fieldValue) {
297
- let _ = row.entityIndices->Utils.Set.add(index)
298
- let _ = relatedEntityIds->Utils.Set.add(entity->getEntityIdUnsafe)
252
+ let entityId = entity->getEntityIdUnsafe
253
+ let _ = inMemTable->getOrCreateEntityIndices(~entityId)->Utils.Set.add(index)
254
+ let _ = relatedEntityIds->Utils.Set.add(entityId)
299
255
  }
300
256
  | None => ()
301
257
  }
302
258
  })
303
- switch inMemTable.fieldNameIndices->getRow(index) {
259
+ switch inMemTable.fieldNameIndices->Utils.Dict.dangerouslyGetNonOption(fieldName) {
304
260
  | None =>
305
- inMemTable.fieldNameIndices->setRow(
306
- index,
261
+ inMemTable.fieldNameIndices->Dict.set(
262
+ fieldName,
307
263
  makeIndicesSerializedToValue(~index, ~relatedEntityIds),
308
264
  )
309
265
  | Some(indicesSerializedToValue) =>
310
- switch indicesSerializedToValue->getRow(index) {
311
- | None => indicesSerializedToValue->setRow(index, (index, relatedEntityIds))
312
- | Some(_) => () //Should not happen, this means the index already exists
313
- }
314
- }
315
- }
316
-
317
- let addIdToIndex = (inMemTable: t<'entity>, ~index, ~entityId) =>
318
- switch inMemTable.fieldNameIndices->getRow(index) {
319
- | None =>
320
- inMemTable.fieldNameIndices->setRow(
321
- index,
322
- makeIndicesSerializedToValue(
323
- ~index,
324
- ~relatedEntityIds=Utils.Set.make()->Utils.Set.add(entityId),
325
- ),
326
- )
327
- | Some(indicesSerializedToValue) =>
328
- switch indicesSerializedToValue->getRow(index) {
266
+ switch indicesSerializedToValue->Utils.Dict.dangerouslyGetNonOption(
267
+ index->TableIndices.Index.toString,
268
+ ) {
329
269
  | None =>
330
- indicesSerializedToValue->setRow(index, (index, Utils.Set.make()->Utils.Set.add(entityId)))
331
- | Some((_index, relatedEntityIds)) => relatedEntityIds->Utils.Set.add(entityId)->ignore
270
+ indicesSerializedToValue->Dict.set(
271
+ index->TableIndices.Index.toString,
272
+ (index, relatedEntityIds),
273
+ )
274
+ | Some(_) => () //Should not happen, this means the index already exists
332
275
  }
333
276
  }
334
-
335
- let updates = (inMemTable: t<'entity>) => {
336
- inMemTable.table
337
- ->values
338
- ->Array.filterMap(v =>
339
- switch v.status {
340
- | Updated(update) => Some(update)
341
- | Loaded => None
342
- }
343
- )
344
- }
345
-
346
- let values = (inMemTable: t<'entity>) => {
347
- inMemTable.table
348
- ->values
349
- ->Array.filterMap(rowToEntity)
350
- }
351
-
352
- let clone = ({table, fieldNameIndices}: t<'entity>) => {
353
- table: table->clone,
354
- fieldNameIndices: {
355
- ...fieldNameIndices,
356
- dict: fieldNameIndices.dict
357
- ->Dict.toArray
358
- ->Array.map(((k, v)) => (k, v->clone))
359
- ->Dict.fromArray,
360
- },
361
277
  }
362
278
  }