envio 3.5.0-alpha.1 → 3.5.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +10 -6
- package/package.json +6 -6
- package/src/BatchProcessing.res +27 -0
- package/src/BatchProcessing.res.mjs +12 -0
- package/src/ChainFetching.res +5 -5
- package/src/ChainFetching.res.mjs +3 -4
- package/src/ChainState.res +48 -25
- package/src/ChainState.res.mjs +23 -29
- package/src/ChainState.resi +2 -6
- package/src/Change.res +3 -3
- package/src/Config.res +2 -6
- package/src/Config.res.mjs +2 -12
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +33 -10
- package/src/CrossChainState.res.mjs +21 -6
- package/src/CrossChainState.resi +3 -0
- package/src/EntityId.res +15 -0
- package/src/EntityId.res.mjs +9 -0
- package/src/EventConfigBuilder.res +1 -58
- package/src/EventConfigBuilder.res.mjs +1 -33
- package/src/FetchState.res +510 -460
- package/src/FetchState.res.mjs +413 -428
- package/src/FinalizeBackfill.res +34 -0
- package/src/FinalizeBackfill.res.mjs +26 -0
- package/src/HandlerRegister.res +45 -0
- package/src/HandlerRegister.res.mjs +43 -0
- package/src/InMemoryStore.res +11 -4
- package/src/InMemoryTable.res +26 -24
- package/src/InMemoryTable.res.mjs +19 -18
- package/src/IndexerState.res +71 -2
- package/src/IndexerState.res.mjs +67 -3
- package/src/IndexerState.resi +5 -0
- package/src/Internal.res +17 -8
- package/src/LoadLayer.res +9 -1
- package/src/LoadLayer.res.mjs +1 -0
- package/src/LogSelection.res +2 -1
- package/src/Metrics.res +37 -6
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +12 -1
- package/src/PgStorage.res +411 -57
- package/src/PgStorage.res.mjs +262 -41
- package/src/SimulateItems.res +2 -2
- package/src/TestIndexer.res +9 -5
- package/src/TestIndexer.res.mjs +5 -2
- package/src/UserContext.res +3 -3
- package/src/Writing.res +12 -2
- package/src/Writing.res.mjs +13 -2
- package/src/bindings/ClickHouse.res +5 -3
- package/src/bindings/ClickHouse.res.mjs +2 -5
- package/src/bindings/Vitest.res +22 -1
- package/src/bindings/Vitest.res.mjs +15 -0
- package/src/db/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/EntityHistory.res +16 -7
- package/src/db/EntityHistory.res.mjs +7 -6
- package/src/db/IndexRegistry.res +219 -0
- package/src/db/IndexRegistry.res.mjs +195 -0
- package/src/db/InternalTable.res +7 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/db/Table.res +53 -22
- package/src/db/Table.res.mjs +56 -18
- package/src/sources/AddressSet.res +48 -0
- package/src/sources/AddressSet.res.mjs +11 -0
- package/src/sources/AddressStore.res +152 -0
- package/src/sources/AddressStore.res.mjs +97 -0
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +6 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcClient.res +6 -3
- package/src/sources/EvmRpcClient.res.mjs +3 -3
- package/src/sources/FuelHyperSync.res +4 -3
- package/src/sources/FuelHyperSync.res.mjs +3 -3
- package/src/sources/FuelHyperSync.resi +2 -1
- package/src/sources/FuelHyperSyncClient.res +12 -6
- package/src/sources/FuelHyperSyncClient.res.mjs +2 -2
- package/src/sources/FuelHyperSyncSource.res +7 -4
- package/src/sources/FuelHyperSyncSource.res.mjs +3 -3
- package/src/sources/HyperSync.res +6 -4
- package/src/sources/HyperSync.res.mjs +2 -3
- package/src/sources/HyperSync.resi +1 -1
- package/src/sources/HyperSyncClient.res +18 -9
- package/src/sources/HyperSyncClient.res.mjs +4 -4
- package/src/sources/RpcSource.res +15 -10
- package/src/sources/RpcSource.res.mjs +3 -4
- package/src/sources/SimulateSource.res +26 -11
- package/src/sources/SimulateSource.res.mjs +13 -5
- package/src/sources/Source.res +4 -2
- package/src/sources/SourceManager.res +2 -3
- package/src/sources/SourceManager.res.mjs +2 -3
- package/src/sources/Svm.res +1 -2
- package/src/sources/Svm.res.mjs +1 -1
- package/src/sources/SvmHyperSyncClient.res +12 -3
- package/src/sources/SvmHyperSyncClient.res.mjs +2 -2
- package/src/sources/SvmHyperSyncSource.res +10 -5
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/src/IndexingAddresses.res +0 -151
- package/src/IndexingAddresses.res.mjs +0 -130
- package/src/IndexingAddresses.resi +0 -39
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The FinalizingIndexes phase. Runs once, from the processing loop, when every
|
|
2
|
+
// chain has caught up: processing is already paused (the loop awaits this),
|
|
3
|
+
// pending writes are flushed, then storage creates every missing schema-defined
|
|
4
|
+
// index and stamps `ready_at` in one transaction. A failure rolls back both, so
|
|
5
|
+
// the next batch simply retries — the indexer never reports ready with an index
|
|
6
|
+
// the schema promised still missing.
|
|
7
|
+
|
|
8
|
+
let run = async (state: IndexerState.t) => {
|
|
9
|
+
Logging.info(
|
|
10
|
+
"All chains are caught up. Finalizing the indexer before switching to realtime: flushing pending writes, then creating the indexes the schema promises.",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
await Writing.flush(state)
|
|
14
|
+
|
|
15
|
+
// A failed write already surfaced through onError; committing ready_at on top
|
|
16
|
+
// of an incomplete write would claim progress that isn't durable.
|
|
17
|
+
if !(state->IndexerState.hasFailedWrite) {
|
|
18
|
+
let persistence = state->IndexerState.persistence
|
|
19
|
+
let storage = persistence->Persistence.getInitializedStorageOrThrow
|
|
20
|
+
let readyAt = Date.make()
|
|
21
|
+
|
|
22
|
+
await storage.finalizeBackfill(
|
|
23
|
+
~entities=persistence.allEntities,
|
|
24
|
+
~chainIds=state
|
|
25
|
+
->IndexerState.chainStates
|
|
26
|
+
->Dict.valuesToArray
|
|
27
|
+
->Array.map(cs => (cs->ChainState.chainConfig).id),
|
|
28
|
+
~readyAt,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
state->IndexerState.markReady(~readyAt)
|
|
32
|
+
Logging.info("The indexer is ready. Switching to realtime indexing.")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Logging from "./Logging.res.mjs";
|
|
4
|
+
import * as Writing from "./Writing.res.mjs";
|
|
5
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
6
|
+
import * as Persistence from "./Persistence.res.mjs";
|
|
7
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
8
|
+
|
|
9
|
+
async function run(state) {
|
|
10
|
+
Logging.info("All chains are caught up. Finalizing the indexer before switching to realtime: flushing pending writes, then creating the indexes the schema promises.");
|
|
11
|
+
await Writing.flush(state);
|
|
12
|
+
if (IndexerState.hasFailedWrite(state)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let persistence = IndexerState.persistence(state);
|
|
16
|
+
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
17
|
+
let readyAt = new Date();
|
|
18
|
+
await storage.finalizeBackfill(persistence.allEntities, Object.values(IndexerState.chainStates(state)).map(cs => ChainState.chainConfig(cs).id), readyAt);
|
|
19
|
+
IndexerState.markReady(state, readyAt);
|
|
20
|
+
return Logging.info("The indexer is ready. Switching to realtime indexing.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
run,
|
|
25
|
+
}
|
|
26
|
+
/* Logging Not a pure module */
|
package/src/HandlerRegister.res
CHANGED
|
@@ -215,6 +215,25 @@ let mergeRegistrations = (resolved: array<Internal.onEventRegistration>, ~config
|
|
|
215
215
|
let isDroppedByWhere = (~config: Config.t, reg: Internal.onEventRegistration) =>
|
|
216
216
|
config.ecosystem.name === Evm && (reg->getResolvedWhere).topicSelections->Utils.Array.isEmpty
|
|
217
217
|
|
|
218
|
+
// Names configured across every chain, used to explain a registration that
|
|
219
|
+
// matched nothing. Only built on the failure path.
|
|
220
|
+
let describeConfigured = (registration: activeRegistration, ~contractName) => {
|
|
221
|
+
let contractNames = Utils.Set.make()
|
|
222
|
+
let eventNames = Utils.Set.make()
|
|
223
|
+
registration.config.chainMap
|
|
224
|
+
->ChainMap.values
|
|
225
|
+
->Array.forEach(chainConfig =>
|
|
226
|
+
chainConfig.contracts->Array.forEach(contract => {
|
|
227
|
+
contractNames->Utils.Set.add(contract.name)->ignore
|
|
228
|
+
if contract.name === contractName {
|
|
229
|
+
contract.events->Array.forEach(e => eventNames->Utils.Set.add(e.name)->ignore)
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
)
|
|
233
|
+
let sorted = set => set->Utils.Set.toArray->Array.toSorted(String.compare)
|
|
234
|
+
(sorted(contractNames), sorted(eventNames))
|
|
235
|
+
}
|
|
236
|
+
|
|
218
237
|
// Resolve one `onEvent`/`contractRegister` call into a registration for every
|
|
219
238
|
// chain in the config (the full chain set) and store it in registration order.
|
|
220
239
|
// Building runs the user's `where` callback here — once per chain — so a broken
|
|
@@ -230,6 +249,7 @@ let addOnEventRegistration = (
|
|
|
230
249
|
) => {
|
|
231
250
|
let isWildcard = eventOptions->Option.flatMap(v => v.wildcard)->Option.getOr(false)
|
|
232
251
|
let where = eventOptions->Option.flatMap(v => v.where)
|
|
252
|
+
let matched = ref(false)
|
|
233
253
|
registration.config.chainMap
|
|
234
254
|
->ChainMap.values
|
|
235
255
|
->Array.forEach(chainConfig =>
|
|
@@ -239,6 +259,7 @@ let addOnEventRegistration = (
|
|
|
239
259
|
switch contract.events->Array.find(e => e.name === eventName) {
|
|
240
260
|
| None => ()
|
|
241
261
|
| Some(eventConfig) =>
|
|
262
|
+
matched := true
|
|
242
263
|
let reg = buildOnEventRegistrationWith(
|
|
243
264
|
~config=registration.config,
|
|
244
265
|
~chainId=chainConfig.id,
|
|
@@ -255,6 +276,30 @@ let addOnEventRegistration = (
|
|
|
255
276
|
}
|
|
256
277
|
}
|
|
257
278
|
)
|
|
279
|
+
|
|
280
|
+
// Nothing matched on any chain, so the callback could never be dispatched.
|
|
281
|
+
// Reported at the registration call site, where the stack still points at the
|
|
282
|
+
// offending `onEvent`/`contractRegister`.
|
|
283
|
+
if !matched.contents {
|
|
284
|
+
let (contractNames, eventNames) = registration->describeConfigured(~contractName)
|
|
285
|
+
let listOr = (names, empty) =>
|
|
286
|
+
names->Utils.Array.isEmpty ? empty : names->Array.joinUnsafe(", ")
|
|
287
|
+
if eventNames->Utils.Array.isEmpty {
|
|
288
|
+
JsError.throwWithMessage(
|
|
289
|
+
`Contract "${contractName}" is not configured on any chain, so its handler for "${eventName}" would never run. Add it to your config, or remove the registration. Configured contracts: ${listOr(
|
|
290
|
+
contractNames,
|
|
291
|
+
"none",
|
|
292
|
+
)}.`,
|
|
293
|
+
)
|
|
294
|
+
} else {
|
|
295
|
+
JsError.throwWithMessage(
|
|
296
|
+
`Event "${eventName}" is not configured on contract "${contractName}", so its handler would never run. Add it to your config, or remove the registration. Configured events on "${contractName}": ${listOr(
|
|
297
|
+
eventNames,
|
|
298
|
+
"none",
|
|
299
|
+
)}.`,
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
258
303
|
}
|
|
259
304
|
|
|
260
305
|
let setHandler = (~contractName, ~eventName, handler, ~eventOptions) => {
|
|
@@ -8,6 +8,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
8
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
9
9
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
10
10
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
11
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
11
12
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
12
13
|
import * as EventConfigBuilder from "./EventConfigBuilder.res.mjs";
|
|
13
14
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
@@ -145,9 +146,33 @@ function isDroppedByWhere(config, reg) {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
function describeConfigured(registration, contractName) {
|
|
150
|
+
let contractNames = new Set();
|
|
151
|
+
let eventNames = new Set();
|
|
152
|
+
ChainMap.values(registration.config.chainMap).forEach(chainConfig => {
|
|
153
|
+
chainConfig.contracts.forEach(contract => {
|
|
154
|
+
contractNames.add(contract.name);
|
|
155
|
+
if (contract.name === contractName) {
|
|
156
|
+
contract.events.forEach(e => {
|
|
157
|
+
eventNames.add(e.name);
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
let sorted = set => Array.from(set).toSorted(Primitive_string.compare);
|
|
164
|
+
return [
|
|
165
|
+
sorted(contractNames),
|
|
166
|
+
sorted(eventNames)
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
148
170
|
function addOnEventRegistration(registration, contractName, eventName, handler, contractRegister, eventOptions) {
|
|
149
171
|
let isWildcard = Stdlib_Option.getOr(Stdlib_Option.flatMap(eventOptions, v => v.wildcard), false);
|
|
150
172
|
let where = Stdlib_Option.flatMap(eventOptions, v => v.where);
|
|
173
|
+
let matched = {
|
|
174
|
+
contents: false
|
|
175
|
+
};
|
|
151
176
|
ChainMap.values(registration.config.chainMap).forEach(chainConfig => {
|
|
152
177
|
let contract = chainConfig.contracts.find(c => c.name === contractName);
|
|
153
178
|
if (contract === undefined) {
|
|
@@ -157,9 +182,27 @@ function addOnEventRegistration(registration, contractName, eventName, handler,
|
|
|
157
182
|
if (eventConfig === undefined) {
|
|
158
183
|
return;
|
|
159
184
|
}
|
|
185
|
+
matched.contents = true;
|
|
160
186
|
let reg = buildOnEventRegistrationWith(registration.config, chainConfig.id, eventConfig, isWildcard, handler, contractRegister, where, contract.startBlock);
|
|
161
187
|
getChainRegistrations(registration, chainConfig.id).onEventRegistrations.push(reg);
|
|
162
188
|
});
|
|
189
|
+
if (matched.contents) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let match = describeConfigured(registration, contractName);
|
|
193
|
+
let eventNames = match[1];
|
|
194
|
+
let listOr = (names, empty) => {
|
|
195
|
+
if (Utils.$$Array.isEmpty(names)) {
|
|
196
|
+
return empty;
|
|
197
|
+
} else {
|
|
198
|
+
return names.join(", ");
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
if (Utils.$$Array.isEmpty(eventNames)) {
|
|
202
|
+
return Stdlib_JsError.throwWithMessage(`Contract "` + contractName + `" is not configured on any chain, so its handler for "` + eventName + `" would never run. Add it to your config, or remove the registration. Configured contracts: ` + listOr(match[0], "none") + `.`);
|
|
203
|
+
} else {
|
|
204
|
+
return Stdlib_JsError.throwWithMessage(`Event "` + eventName + `" is not configured on contract "` + contractName + `", so its handler would never run. Add it to your config, or remove the registration. Configured events on "` + contractName + `": ` + listOr(eventNames, "none") + `.`);
|
|
205
|
+
}
|
|
163
206
|
}
|
|
164
207
|
|
|
165
208
|
function setHandler(contractName, eventName, handler, eventOptions) {
|
package/src/InMemoryStore.res
CHANGED
|
@@ -45,7 +45,10 @@ let setEffectOutput = (
|
|
|
45
45
|
| Some(_) => ()
|
|
46
46
|
| None => inMemTable.changesCount = inMemTable.changesCount +. 1.
|
|
47
47
|
}
|
|
48
|
-
inMemTable.dict->Dict.set(
|
|
48
|
+
inMemTable.dict->Dict.set(
|
|
49
|
+
cacheKey,
|
|
50
|
+
Set({entityId: cacheKey->EntityId.unsafeOfString, entity: output, checkpointId}),
|
|
51
|
+
)
|
|
49
52
|
if shouldCache {
|
|
50
53
|
inMemTable.idsToStore->Array.push(cacheKey)->ignore
|
|
51
54
|
}
|
|
@@ -58,7 +61,11 @@ let initEffectOutputFromDb = (inMemTable: EffectState.effectCacheInMemTable, ~ca
|
|
|
58
61
|
inMemTable.changesCount = inMemTable.changesCount +. 1.
|
|
59
62
|
inMemTable.dict->Dict.set(
|
|
60
63
|
cacheKey,
|
|
61
|
-
Set({
|
|
64
|
+
Set({
|
|
65
|
+
entityId: cacheKey->EntityId.unsafeOfString,
|
|
66
|
+
entity: output,
|
|
67
|
+
checkpointId: Internal.loadedFromDbCheckpointId,
|
|
68
|
+
}),
|
|
62
69
|
)
|
|
63
70
|
}
|
|
64
71
|
|
|
@@ -131,7 +138,7 @@ let prepareRollbackDiff = async (
|
|
|
131
138
|
entityTable->InMemoryTable.Entity.set(
|
|
132
139
|
~committedCheckpointId,
|
|
133
140
|
Set({
|
|
134
|
-
entityId: entity.id,
|
|
141
|
+
entityId: entity.id->EntityId.unsafeOfString,
|
|
135
142
|
checkpointId: rollbackDiffCheckpointId,
|
|
136
143
|
entity,
|
|
137
144
|
}),
|
|
@@ -177,7 +184,7 @@ let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
|
|
|
177
184
|
inMemTable->InMemoryTable.Entity.set(
|
|
178
185
|
~committedCheckpointId,
|
|
179
186
|
Set({
|
|
180
|
-
entityId: entity.id,
|
|
187
|
+
entityId: entity.id->EntityId.unsafeOfString,
|
|
181
188
|
checkpointId,
|
|
182
189
|
entity: entity->InternalTable.EnvioAddresses.castToInternal,
|
|
183
190
|
}),
|
package/src/InMemoryTable.res
CHANGED
|
@@ -2,7 +2,7 @@ module Entity = {
|
|
|
2
2
|
type relatedEntityId = string
|
|
3
3
|
type filterWithRelatedIds = (EntityFilter.t, Utils.Set.t<relatedEntityId>)
|
|
4
4
|
// Keyed by EntityFilter.toString
|
|
5
|
-
type
|
|
5
|
+
type filterIndexes = dict<filterWithRelatedIds>
|
|
6
6
|
|
|
7
7
|
type entityFilters = Utils.Set.t<EntityFilter.t>
|
|
8
8
|
type t = {
|
|
@@ -14,14 +14,16 @@ module Entity = {
|
|
|
14
14
|
// previous changes persist in the background.
|
|
15
15
|
mutable prevEntityChanges: array<Change.t<Internal.entity>>,
|
|
16
16
|
mutable filtersByEntityId: dict<entityFilters>,
|
|
17
|
-
mutable
|
|
17
|
+
mutable filterIndexes: filterIndexes,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// Helper to extract entity
|
|
20
|
+
// Helper to extract an entity's id as a dict key. The raw id may be a
|
|
21
|
+
// string/int/bigint, so it's stringified to a stable key for in-memory
|
|
22
|
+
// indexing.
|
|
21
23
|
exception UnexpectedIdNotDefinedOnEntity
|
|
22
24
|
let getEntityIdUnsafe = (entity: Internal.entity): string =>
|
|
23
|
-
switch (entity->(Utils.magic: Internal.entity => {"id": option<
|
|
24
|
-
| Some(id) => id
|
|
25
|
+
switch (entity->(Utils.magic: Internal.entity => {"id": option<EntityId.t>}))["id"] {
|
|
26
|
+
| Some(id) => id->EntityId.toKey
|
|
25
27
|
| None =>
|
|
26
28
|
UnexpectedIdNotDefinedOnEntity->ErrorHandling.mkLogAndRaise(
|
|
27
29
|
~msg="Property 'id' does not exist on expected entity object",
|
|
@@ -42,7 +44,7 @@ module Entity = {
|
|
|
42
44
|
changesCount: 0.,
|
|
43
45
|
prevEntityChanges: [],
|
|
44
46
|
filtersByEntityId: Dict.make(),
|
|
45
|
-
|
|
47
|
+
filterIndexes: Dict.make(),
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
// Changes to persist for checkpoints in (committedCheckpointId, upToCheckpointId].
|
|
@@ -79,7 +81,7 @@ module Entity = {
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
// Frees committed changes: drops latest entries at or below committedCheckpointId
|
|
82
|
-
// (re-readable from the db) and clears the per-batch
|
|
84
|
+
// (re-readable from the db) and clears the per-batch indexes (rebuilt on the next
|
|
83
85
|
// getWhere). Uncommitted changes are kept. With keepLoadedFromDb, entries seeded
|
|
84
86
|
// from a db read are spared so the cheaper-to-re-derive writes are dropped first.
|
|
85
87
|
let dropCommittedChanges = (self: t, ~committedCheckpointId, ~keepLoadedFromDb) => {
|
|
@@ -96,10 +98,10 @@ module Entity = {
|
|
|
96
98
|
keysToDelete->Array.forEach(key => self.latestEntityChangeById->Utils.Dict.deleteInPlace(key))
|
|
97
99
|
self.changesCount = self.changesCount -. keysToDelete->Array.length->Int.toFloat
|
|
98
100
|
self.filtersByEntityId = Dict.make()
|
|
99
|
-
self.
|
|
101
|
+
self.filterIndexes = Dict.make()
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
let
|
|
104
|
+
let updateIndexes = (self: t, ~entity: Internal.entity) => {
|
|
103
105
|
let entityId = entity->getEntityIdUnsafe
|
|
104
106
|
let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
|
|
105
107
|
|
|
@@ -114,7 +116,7 @@ module Entity = {
|
|
|
114
116
|
})
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
self.
|
|
119
|
+
self.filterIndexes->Utils.Dict.forEach(((filter, relatedEntityIds)) => {
|
|
118
120
|
if filter->EntityFilter.matches(~entity=entityAsDict) {
|
|
119
121
|
//Add entity id to the filter index and the filter to entity filters
|
|
120
122
|
relatedEntityIds->Utils.Set.add(entityId)->ignore
|
|
@@ -125,12 +127,12 @@ module Entity = {
|
|
|
125
127
|
})
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
let
|
|
130
|
+
let deleteEntityFromIndexes = (self: t, ~entityId: string) =>
|
|
129
131
|
switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
130
132
|
| None => ()
|
|
131
133
|
| Some(entityFilters) =>
|
|
132
134
|
entityFilters->Utils.Set.forEach(filter => {
|
|
133
|
-
switch self.
|
|
135
|
+
switch self.filterIndexes->Utils.Dict.dangerouslyGetNonOption(
|
|
134
136
|
filter->EntityFilter.toString,
|
|
135
137
|
) {
|
|
136
138
|
| Some((_filter, relatedEntityIds)) =>
|
|
@@ -142,8 +144,8 @@ module Entity = {
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
let set = (inMemTable: t, ~committedCheckpointId, change: Change.t<Internal.entity>) => {
|
|
145
|
-
let
|
|
146
|
-
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(
|
|
147
|
+
let entityKey = change->Change.getEntityId->EntityId.toKey
|
|
148
|
+
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityKey) {
|
|
147
149
|
| Some(prev) =>
|
|
148
150
|
let prevCheckpointId = prev->Change.getCheckpointId
|
|
149
151
|
if (
|
|
@@ -157,10 +159,10 @@ module Entity = {
|
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
switch change {
|
|
160
|
-
| Set({entity}) => inMemTable->
|
|
161
|
-
| Delete({entityId}) => inMemTable->
|
|
162
|
+
| Set({entity}) => inMemTable->updateIndexes(~entity)
|
|
163
|
+
| Delete({entityId}) => inMemTable->deleteEntityFromIndexes(~entityId=entityId->EntityId.toKey)
|
|
162
164
|
}
|
|
163
|
-
inMemTable.latestEntityChangeById->Dict.set(
|
|
165
|
+
inMemTable.latestEntityChangeById->Dict.set(entityKey, change)
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
// Only writes when the id isn't already present, so set always takes its
|
|
@@ -172,10 +174,10 @@ module Entity = {
|
|
|
172
174
|
~entity: option<Internal.entity>,
|
|
173
175
|
) =>
|
|
174
176
|
if inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(key)->Option.isNone {
|
|
177
|
+
let entityId = key->EntityId.unsafeOfString
|
|
175
178
|
let change: Change.t<Internal.entity> = switch entity {
|
|
176
|
-
| Some(entity) =>
|
|
177
|
-
|
|
178
|
-
| None => Delete({entityId: key, checkpointId: Internal.loadedFromDbCheckpointId})
|
|
179
|
+
| Some(entity) => Set({entityId, entity, checkpointId: Internal.loadedFromDbCheckpointId})
|
|
180
|
+
| None => Delete({entityId, checkpointId: Internal.loadedFromDbCheckpointId})
|
|
179
181
|
}
|
|
180
182
|
inMemTable->set(~committedCheckpointId, change)
|
|
181
183
|
}
|
|
@@ -198,12 +200,12 @@ module Entity = {
|
|
|
198
200
|
|
|
199
201
|
let hasIndex = (inMemTable: t) =>
|
|
200
202
|
(filterKey: string) =>
|
|
201
|
-
inMemTable.
|
|
203
|
+
inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) !== None
|
|
202
204
|
|
|
203
205
|
let getUnsafeOnIndex = (inMemTable: t) => {
|
|
204
206
|
let getEntity = inMemTable->getUnsafe
|
|
205
207
|
(filterKey: string) => {
|
|
206
|
-
switch inMemTable.
|
|
208
|
+
switch inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
207
209
|
| None =>
|
|
208
210
|
JsError.throwWithMessage(`Unexpected error. Must have an index for the filter ${filterKey}`)
|
|
209
211
|
| Some((_filter, relatedEntityIds)) =>
|
|
@@ -221,7 +223,7 @@ module Entity = {
|
|
|
221
223
|
|
|
222
224
|
let addEmptyIndex = (inMemTable: t, ~filter: EntityFilter.t) => {
|
|
223
225
|
let filterKey = filter->EntityFilter.toString
|
|
224
|
-
switch inMemTable.
|
|
226
|
+
switch inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
225
227
|
| Some(_) => () //Should not happen, this means the index already exists
|
|
226
228
|
| None =>
|
|
227
229
|
let relatedEntityIds = Utils.Set.make()
|
|
@@ -238,7 +240,7 @@ module Entity = {
|
|
|
238
240
|
| None => ()
|
|
239
241
|
}
|
|
240
242
|
})
|
|
241
|
-
inMemTable.
|
|
243
|
+
inMemTable.filterIndexes->Dict.set(filterKey, (filter, relatedEntityIds))
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as EntityId from "./EntityId.res.mjs";
|
|
4
5
|
import * as Internal from "./Internal.res.mjs";
|
|
5
6
|
import * as EntityFilter from "./db/EntityFilter.res.mjs";
|
|
6
7
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
@@ -15,7 +16,7 @@ let UnexpectedIdNotDefinedOnEntity = /* @__PURE__ */Primitive_exceptions.create(
|
|
|
15
16
|
function getEntityIdUnsafe(entity) {
|
|
16
17
|
let id = entity.id;
|
|
17
18
|
if (id !== undefined) {
|
|
18
|
-
return id;
|
|
19
|
+
return EntityId.toKey(Primitive_option.valFromOption(id));
|
|
19
20
|
} else {
|
|
20
21
|
return ErrorHandling.mkLogAndRaise(undefined, "Property 'id' does not exist on expected entity object", {
|
|
21
22
|
RE_EXN_ID: UnexpectedIdNotDefinedOnEntity
|
|
@@ -39,7 +40,7 @@ function make() {
|
|
|
39
40
|
changesCount: 0,
|
|
40
41
|
prevEntityChanges: [],
|
|
41
42
|
filtersByEntityId: {},
|
|
42
|
-
|
|
43
|
+
filterIndexes: {}
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -83,10 +84,10 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
|
|
|
83
84
|
keysToDelete.forEach(key => Utils.Dict.deleteInPlace(self.latestEntityChangeById, key));
|
|
84
85
|
self.changesCount = self.changesCount - keysToDelete.length;
|
|
85
86
|
self.filtersByEntityId = {};
|
|
86
|
-
self.
|
|
87
|
+
self.filterIndexes = {};
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
function
|
|
90
|
+
function updateIndexes(self, entity) {
|
|
90
91
|
let entityId = getEntityIdUnsafe(entity);
|
|
91
92
|
let entityFilters = self.filtersByEntityId[entityId];
|
|
92
93
|
if (entityFilters !== undefined) {
|
|
@@ -98,7 +99,7 @@ function updateIndices(self, entity) {
|
|
|
98
99
|
}
|
|
99
100
|
});
|
|
100
101
|
}
|
|
101
|
-
Utils.Dict.forEach(self.
|
|
102
|
+
Utils.Dict.forEach(self.filterIndexes, param => {
|
|
102
103
|
let relatedEntityIds = param[1];
|
|
103
104
|
let filter = param[0];
|
|
104
105
|
if (EntityFilter.matches(filter, entity)) {
|
|
@@ -110,14 +111,14 @@ function updateIndices(self, entity) {
|
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
function
|
|
114
|
+
function deleteEntityFromIndexes(self, entityId) {
|
|
114
115
|
let entityFilters = self.filtersByEntityId[entityId];
|
|
115
116
|
if (entityFilters === undefined) {
|
|
116
117
|
return;
|
|
117
118
|
}
|
|
118
119
|
let entityFilters$1 = Primitive_option.valFromOption(entityFilters);
|
|
119
120
|
entityFilters$1.forEach(filter => {
|
|
120
|
-
let match = self.
|
|
121
|
+
let match = self.filterIndexes[EntityFilter.toString(filter)];
|
|
121
122
|
if (match !== undefined) {
|
|
122
123
|
match[1].delete(entityId);
|
|
123
124
|
}
|
|
@@ -126,8 +127,8 @@ function deleteEntityFromIndices(self, entityId) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function set(inMemTable, committedCheckpointId, change) {
|
|
129
|
-
let
|
|
130
|
-
let prev = inMemTable.latestEntityChangeById[
|
|
130
|
+
let entityKey = EntityId.toKey(change.entityId);
|
|
131
|
+
let prev = inMemTable.latestEntityChangeById[entityKey];
|
|
131
132
|
if (prev !== undefined) {
|
|
132
133
|
let prevCheckpointId = prev.checkpointId;
|
|
133
134
|
if (prevCheckpointId > committedCheckpointId && prevCheckpointId < change.checkpointId) {
|
|
@@ -138,11 +139,11 @@ function set(inMemTable, committedCheckpointId, change) {
|
|
|
138
139
|
inMemTable.changesCount = inMemTable.changesCount + 1;
|
|
139
140
|
}
|
|
140
141
|
if (change.type === "SET") {
|
|
141
|
-
|
|
142
|
+
updateIndexes(inMemTable, change.entity);
|
|
142
143
|
} else {
|
|
143
|
-
|
|
144
|
+
deleteEntityFromIndexes(inMemTable, EntityId.toKey(change.entityId));
|
|
144
145
|
}
|
|
145
|
-
inMemTable.latestEntityChangeById[
|
|
146
|
+
inMemTable.latestEntityChangeById[entityKey] = change;
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
function initValue(inMemTable, committedCheckpointId, key, entity) {
|
|
@@ -173,13 +174,13 @@ function getUnsafe(inMemTable) {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
function hasIndex(inMemTable) {
|
|
176
|
-
return filterKey => inMemTable.
|
|
177
|
+
return filterKey => inMemTable.filterIndexes[filterKey] !== undefined;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
function getUnsafeOnIndex(inMemTable) {
|
|
180
181
|
let getEntity = getUnsafe(inMemTable);
|
|
181
182
|
return filterKey => {
|
|
182
|
-
let match = inMemTable.
|
|
183
|
+
let match = inMemTable.filterIndexes[filterKey];
|
|
183
184
|
if (match !== undefined) {
|
|
184
185
|
return Stdlib_Array.filterMap(Array.from(match[1]), entityId => {
|
|
185
186
|
if (entityId in inMemTable.latestEntityChangeById) {
|
|
@@ -194,7 +195,7 @@ function getUnsafeOnIndex(inMemTable) {
|
|
|
194
195
|
|
|
195
196
|
function addEmptyIndex(inMemTable, filter) {
|
|
196
197
|
let filterKey = EntityFilter.toString(filter);
|
|
197
|
-
let match = inMemTable.
|
|
198
|
+
let match = inMemTable.filterIndexes[filterKey];
|
|
198
199
|
if (match !== undefined) {
|
|
199
200
|
return;
|
|
200
201
|
}
|
|
@@ -211,7 +212,7 @@ function addEmptyIndex(inMemTable, filter) {
|
|
|
211
212
|
getOrCreateEntityFilters(inMemTable, entityId).add(filter);
|
|
212
213
|
relatedEntityIds.add(entityId);
|
|
213
214
|
});
|
|
214
|
-
inMemTable.
|
|
215
|
+
inMemTable.filterIndexes[filterKey] = [
|
|
215
216
|
filter,
|
|
216
217
|
relatedEntityIds
|
|
217
218
|
];
|
|
@@ -224,8 +225,8 @@ let Entity = {
|
|
|
224
225
|
make: make,
|
|
225
226
|
snapshotChanges: snapshotChanges,
|
|
226
227
|
dropCommittedChanges: dropCommittedChanges,
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
updateIndexes: updateIndexes,
|
|
229
|
+
deleteEntityFromIndexes: deleteEntityFromIndexes,
|
|
229
230
|
set: set,
|
|
230
231
|
initValue: initValue,
|
|
231
232
|
mapChangeToEntity: mapChangeToEntity,
|