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.
- package/evm.schema.json +83 -11
- package/fuel.schema.json +83 -11
- package/index.d.ts +184 -3
- package/package.json +6 -6
- package/src/Batch.res +2 -2
- package/src/ChainFetcher.res +27 -3
- package/src/ChainFetcher.res.mjs +17 -3
- package/src/ChainManager.res +163 -0
- package/src/ChainManager.res.mjs +136 -0
- package/src/Config.res +213 -30
- package/src/Config.res.mjs +102 -41
- package/src/Core.res +16 -10
- package/src/Ecosystem.res +0 -3
- package/src/Env.res +2 -2
- package/src/Env.res.mjs +2 -2
- package/src/Envio.res +101 -2
- package/src/Envio.res.mjs +2 -3
- package/src/EventConfigBuilder.res +52 -0
- package/src/EventConfigBuilder.res.mjs +32 -0
- package/src/EventUtils.res +2 -2
- package/src/FetchState.res +23 -14
- package/src/FetchState.res.mjs +21 -15
- package/src/GlobalState.res +219 -363
- package/src/GlobalState.res.mjs +314 -491
- package/src/GlobalStateManager.res +49 -59
- package/src/GlobalStateManager.res.mjs +5 -4
- package/src/GlobalStateManager.resi +1 -1
- package/src/HandlerLoader.res +12 -1
- package/src/HandlerLoader.res.mjs +6 -1
- package/src/HandlerRegister.res +9 -9
- package/src/HandlerRegister.res.mjs +9 -9
- package/src/Hasura.res +102 -32
- package/src/Hasura.res.mjs +88 -34
- package/src/InMemoryStore.res +10 -1
- package/src/InMemoryStore.res.mjs +4 -1
- package/src/InMemoryTable.res +83 -136
- package/src/InMemoryTable.res.mjs +57 -86
- package/src/Internal.res +54 -5
- package/src/Internal.res.mjs +2 -8
- package/src/LazyLoader.res +2 -2
- package/src/LazyLoader.res.mjs +3 -3
- package/src/LoadLayer.res +47 -60
- package/src/LoadLayer.res.mjs +28 -50
- package/src/LoadLayer.resi +2 -5
- package/src/LogSelection.res +4 -4
- package/src/LogSelection.res.mjs +5 -7
- package/src/Logging.res +1 -1
- package/src/Main.res +61 -2
- package/src/Main.res.mjs +37 -1
- package/src/Persistence.res +3 -16
- package/src/PgStorage.res +125 -114
- package/src/PgStorage.res.mjs +112 -95
- package/src/Ports.res +5 -0
- package/src/Ports.res.mjs +9 -0
- package/src/Prometheus.res +3 -3
- package/src/Prometheus.res.mjs +4 -4
- package/src/ReorgDetection.res +4 -4
- package/src/ReorgDetection.res.mjs +4 -5
- package/src/SafeCheckpointTracking.res +16 -16
- package/src/SafeCheckpointTracking.res.mjs +2 -2
- package/src/SimulateItems.res +10 -14
- package/src/SimulateItems.res.mjs +5 -2
- package/src/Sink.res +1 -1
- package/src/Sink.res.mjs +1 -2
- package/src/SvmTypes.res +9 -0
- package/src/SvmTypes.res.mjs +14 -0
- package/src/TestIndexer.res +17 -57
- package/src/TestIndexer.res.mjs +14 -48
- package/src/TestIndexerProxyStorage.res +23 -23
- package/src/TestIndexerProxyStorage.res.mjs +12 -15
- package/src/Throttler.res +2 -2
- package/src/Time.res +2 -2
- package/src/Time.res.mjs +2 -2
- package/src/UserContext.res +19 -118
- package/src/UserContext.res.mjs +10 -66
- package/src/Utils.res +15 -15
- package/src/Utils.res.mjs +7 -8
- package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
- package/src/bindings/BigDecimal.res +1 -1
- package/src/bindings/BigDecimal.res.mjs +2 -2
- package/src/bindings/ClickHouse.res +8 -6
- package/src/bindings/ClickHouse.res.mjs +5 -5
- package/src/bindings/Hrtime.res +1 -1
- package/src/bindings/Pino.res +2 -2
- package/src/bindings/Pino.res.mjs +3 -4
- package/src/db/EntityFilter.res +410 -0
- package/src/db/EntityFilter.res.mjs +424 -0
- package/src/db/EntityHistory.res +1 -1
- package/src/db/EntityHistory.res.mjs +1 -1
- package/src/db/InternalTable.res +10 -10
- package/src/db/InternalTable.res.mjs +41 -45
- package/src/db/Schema.res +2 -2
- package/src/db/Schema.res.mjs +3 -3
- package/src/db/Table.res +106 -22
- package/src/db/Table.res.mjs +84 -35
- package/src/sources/EventRouter.res +67 -2
- package/src/sources/EventRouter.res.mjs +45 -3
- package/src/sources/Evm.res +0 -7
- package/src/sources/Evm.res.mjs +0 -15
- package/src/sources/EvmChain.res +1 -1
- package/src/sources/EvmChain.res.mjs +1 -2
- package/src/sources/EvmRpcClient.res +42 -0
- package/src/sources/EvmRpcClient.res.mjs +64 -0
- package/src/sources/Fuel.res +0 -7
- package/src/sources/Fuel.res.mjs +0 -15
- package/src/sources/HyperFuelSource.res +5 -4
- package/src/sources/HyperFuelSource.res.mjs +2 -2
- package/src/sources/HyperSyncClient.res +9 -5
- package/src/sources/HyperSyncClient.res.mjs +2 -2
- package/src/sources/HyperSyncHeightStream.res +2 -2
- package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
- package/src/sources/HyperSyncSource.res +10 -9
- package/src/sources/HyperSyncSource.res.mjs +4 -4
- package/src/sources/Rpc.res +1 -5
- package/src/sources/Rpc.res.mjs +1 -9
- package/src/sources/RpcSource.res +57 -21
- package/src/sources/RpcSource.res.mjs +47 -20
- package/src/sources/RpcWebSocketHeightStream.res +1 -1
- package/src/sources/SourceManager.res +3 -2
- package/src/sources/SourceManager.res.mjs +1 -1
- package/src/sources/Svm.res +3 -10
- package/src/sources/Svm.res.mjs +4 -18
- package/src/sources/SvmHyperSyncClient.res +265 -0
- package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
- package/src/sources/SvmHyperSyncSource.res +638 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
- package/src/tui/Tui.res +9 -2
- package/src/tui/Tui.res.mjs +18 -3
- package/src/tui/components/BufferedProgressBar.res +2 -2
- package/src/tui/components/TuiData.res +3 -0
- package/svm.schema.json +523 -14
- package/src/TableIndices.res +0 -115
- package/src/TableIndices.res.mjs +0 -144
package/src/Hasura.res.mjs
CHANGED
|
@@ -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.
|
|
186
|
+
let columnConfigEntries = Object.entries(param.columnConfigs);
|
|
131
187
|
if (columnConfigEntries.length !== 0) {
|
|
132
|
-
let
|
|
188
|
+
let columnConfigJson = {};
|
|
133
189
|
columnConfigEntries.forEach(param => {
|
|
134
|
-
|
|
135
|
-
|
|
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"] =
|
|
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 + `
|
|
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
|
-
|
|
285
|
+
columnConfigs: {}
|
|
222
286
|
},
|
|
223
287
|
{
|
|
224
288
|
tableName: InternalTable.Views.metaViewName,
|
|
225
289
|
description: undefined,
|
|
226
|
-
|
|
290
|
+
columnConfigs: {}
|
|
227
291
|
},
|
|
228
292
|
{
|
|
229
293
|
tableName: InternalTable.Views.chainMetadataViewName,
|
|
230
294
|
description: undefined,
|
|
231
|
-
|
|
295
|
+
columnConfigs: {}
|
|
232
296
|
}
|
|
233
297
|
];
|
|
234
|
-
let userTableConfigs = userEntities.map(entity => {
|
|
235
|
-
|
|
236
|
-
entity.table.
|
|
237
|
-
|
|
238
|
-
|
|
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.
|
|
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
|
|
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,
|
package/src/InMemoryStore.res
CHANGED
|
@@ -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 =
|
|
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.
|
|
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, {
|
package/src/InMemoryTable.res
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
module Entity = {
|
|
2
2
|
type relatedEntityId = string
|
|
3
|
-
type
|
|
4
|
-
// Keyed by
|
|
5
|
-
type
|
|
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
|
|
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
|
|
19
|
-
mutable
|
|
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
|
|
34
|
-
switch self.
|
|
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.
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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 -.
|
|
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.
|
|
103
|
-
self.
|
|
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
|
-
|
|
109
|
-
|
|
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(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
->
|
|
131
|
-
|
|
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.
|
|
129
|
+
switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
146
130
|
| None => ()
|
|
147
|
-
| Some(
|
|
148
|
-
|
|
149
|
-
switch self.
|
|
150
|
-
|
|
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((
|
|
136
|
+
| Some((_filter, relatedEntityIds)) =>
|
|
155
137
|
let _wasRemoved = relatedEntityIds->Utils.Set.delete(entityId)
|
|
156
|
-
| None => () //Unexpected index should exist if it is
|
|
138
|
+
| None => () //Unexpected filter index should exist if it is in entityFilters
|
|
157
139
|
}
|
|
158
|
-
let _wasRemoved =
|
|
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
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
203
|
+
let getUnsafeOnIndex = (inMemTable: t) => {
|
|
229
204
|
let getEntity = inMemTable->getUnsafe
|
|
230
|
-
|
|
231
|
-
switch inMemTable.
|
|
205
|
+
(filterKey: string) => {
|
|
206
|
+
switch inMemTable.filterIndices->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
232
207
|
| None =>
|
|
233
|
-
JsError.throwWithMessage(`Unexpected error. Must have an index
|
|
234
|
-
| Some(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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, ~
|
|
260
|
-
let
|
|
261
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
}
|