envio 3.12.0 → 3.12.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 (48) hide show
  1. package/package.json +6 -6
  2. package/src/ChainFetching.res +7 -3
  3. package/src/ChainFetching.res.mjs +1 -1
  4. package/src/ChainState.res +12 -3
  5. package/src/ChainState.res.mjs +3 -3
  6. package/src/ChainState.resi +1 -1
  7. package/src/CrossChainState.res +1 -1
  8. package/src/CrossChainState.res.mjs +1 -1
  9. package/src/FetchState.res +51 -46
  10. package/src/FetchState.res.mjs +54 -36
  11. package/src/InMemoryTable.res +155 -67
  12. package/src/InMemoryTable.res.mjs +151 -60
  13. package/src/LoadLayer.res +57 -34
  14. package/src/LoadLayer.res.mjs +45 -62
  15. package/src/LoadLayer.resi +1 -1
  16. package/src/PgStorage.res +92 -62
  17. package/src/PgStorage.res.mjs +77 -50
  18. package/src/TestIndexer.res +9 -25
  19. package/src/TestIndexer.res.mjs +4 -3
  20. package/src/UserContext.res +13 -32
  21. package/src/UserContext.res.mjs +1 -7
  22. package/src/Utils.res +1 -4
  23. package/src/Utils.res.mjs +7 -16
  24. package/src/db/EntityFilter.res +487 -275
  25. package/src/db/EntityFilter.res.mjs +557 -309
  26. package/src/db/Table.res +21 -6
  27. package/src/db/Table.res.mjs +13 -4
  28. package/src/sources/BlockStore.res +7 -2
  29. package/src/sources/EvmHyperSyncSource.res +2 -0
  30. package/src/sources/EvmHyperSyncSource.res.mjs +2 -2
  31. package/src/sources/FuelHyperSyncSource.res +1 -0
  32. package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
  33. package/src/sources/HyperSync.res +4 -0
  34. package/src/sources/HyperSync.res.mjs +4 -2
  35. package/src/sources/HyperSync.resi +1 -0
  36. package/src/sources/HyperSyncClient.res +3 -0
  37. package/src/sources/HyperSyncSSE.res +1 -1
  38. package/src/sources/HyperSyncSSE.res.mjs +4 -10
  39. package/src/sources/RpcSource.res +1 -0
  40. package/src/sources/RpcSource.res.mjs +1 -1
  41. package/src/sources/SimulateSource.res +1 -0
  42. package/src/sources/SimulateSource.res.mjs +1 -1
  43. package/src/sources/Source.res +7 -0
  44. package/src/sources/SourceManager.res +4 -3
  45. package/src/sources/SourceManager.res.mjs +2 -2
  46. package/src/sources/SvmHyperSyncClient.res +5 -0
  47. package/src/sources/SvmHyperSyncSource.res +3 -0
  48. package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
@@ -19,6 +19,7 @@ import * as ChainState from "./ChainState.res.mjs";
19
19
  import * as AddressRows from "./AddressRows.res.mjs";
20
20
  import * as Performance from "./bindings/Performance.res.mjs";
21
21
  import * as Persistence from "./Persistence.res.mjs";
22
+ import * as EntityFilter from "./db/EntityFilter.res.mjs";
22
23
  import * as IndexCatalog from "./db/IndexCatalog.res.mjs";
23
24
  import * as IndexManager from "./db/IndexManager.res.mjs";
24
25
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -301,7 +302,7 @@ function makeLoadQuery(pgSchema, tableName, condition) {
301
302
  return `SELECT * FROM "` + pgSchema + `"."` + tableName + `" WHERE ` + condition + `;`;
302
303
  }
303
304
 
304
- function makeFilterCondition(filter, table, params) {
305
+ function makeFilterCondition(filter, table, pgSchema, params) {
305
306
  let getQueryFieldOrThrow = fieldName => {
306
307
  let queryField = Table.queryFields(table)[fieldName];
307
308
  if (queryField !== undefined) {
@@ -333,39 +334,76 @@ function makeFilterCondition(filter, table, params) {
333
334
  params.push(param);
334
335
  return `$` + params.length.toString();
335
336
  };
336
- let scalarCondition = (fieldName, fieldValue, op) => {
337
- let queryField = getQueryFieldOrThrow(fieldName);
338
- return `"` + queryField.pgDbFieldName + `" ` + op + ` ` + serializeParamOrThrow(queryField, fieldName, fieldValue, false);
337
+ let condition = {
338
+ contents: ""
339
339
  };
340
- switch (filter.operator) {
341
- case "=" :
342
- let fieldValue = filter.fieldValue;
343
- let fieldName = filter.fieldName;
344
- if (getQueryFieldOrThrow(fieldName).isChainId) {
345
- return `"` + getQueryFieldOrThrow(fieldName).pgDbFieldName + `" = ` + ChainId.toString(ChainId.normalizeOrThrow(fieldValue));
346
- } else {
347
- return scalarCondition(fieldName, fieldValue, "=");
340
+ Utils.Dict.forEachWithKey(EntityFilter.entries(filter), (operators, fieldName) => {
341
+ let queryField = getQueryFieldOrThrow(fieldName);
342
+ Utils.Dict.forEachWithKey(operators, (fieldValue, operator) => {
343
+ let column = `"` + queryField.pgDbFieldName + `"`;
344
+ let part;
345
+ let exit = 0;
346
+ switch (operator) {
347
+ case "_eq" :
348
+ if (queryField.isChainId) {
349
+ part = column + ` = ` + ChainId.toString(ChainId.normalizeOrThrow(fieldValue));
350
+ } else {
351
+ exit = 1;
352
+ }
353
+ break;
354
+ case "_in" :
355
+ if (queryField.isArray || queryField.fieldType === "Boolean") {
356
+ let candidates = EntityFilter.asArray(fieldValue);
357
+ part = candidates.length !== 0 ? `(` + candidates.map(candidate => column + ` = ` + serializeParamOrThrow(queryField, fieldName, candidate, false)).join(" OR ") + `)` : "FALSE";
358
+ } else {
359
+ let param = serializeParamOrThrow(queryField, fieldName, fieldValue, true);
360
+ let match = queryField.fieldType;
361
+ let exit$1 = 0;
362
+ if (typeof match !== "object" || match.type !== "Enum") {
363
+ exit$1 = 2;
364
+ } else {
365
+ part = column + ` = ANY(` + param + `::TEXT[]::"` + pgSchema + `".` + match.config.name + `[])`;
366
+ }
367
+ if (exit$1 === 2) {
368
+ part = column + ` = ANY(` + param + `)`;
369
+ }
370
+ }
371
+ break;
372
+ default:
373
+ exit = 1;
348
374
  }
349
- case ">" :
350
- return scalarCondition(filter.fieldName, filter.fieldValue, ">");
351
- case "<" :
352
- return scalarCondition(filter.fieldName, filter.fieldValue, "<");
353
- case "in" :
354
- let fieldName$1 = filter.fieldName;
355
- let queryField = getQueryFieldOrThrow(fieldName$1);
356
- return `"` + queryField.pgDbFieldName + `" = ANY(` + serializeParamOrThrow(queryField, fieldName$1, filter.fieldValue, true) + `)`;
357
- case "and" :
358
- let filters = filter.filters;
359
- if (filters.length !== 0) {
360
- return `(` + filters.map(filter => makeFilterCondition(filter, table, params)).join(" AND ") + `)`;
375
+ if (exit === 1) {
376
+ let sqlOperator;
377
+ switch (operator) {
378
+ case "_eq" :
379
+ sqlOperator = "=";
380
+ break;
381
+ case "_gt" :
382
+ sqlOperator = ">";
383
+ break;
384
+ case "_gte" :
385
+ sqlOperator = ">=";
386
+ break;
387
+ case "_lt" :
388
+ sqlOperator = "<";
389
+ break;
390
+ case "_lte" :
391
+ sqlOperator = "<=";
392
+ break;
393
+ default:
394
+ throw {
395
+ RE_EXN_ID: Persistence.StorageError,
396
+ message: `Failed loading "` + table.tableName + `" from storage. Unknown filter operator "` + operator + `".`,
397
+ reason: new Error(`Unknown filter operator "` + operator + `"`),
398
+ Error: new Error()
399
+ };
400
+ }
401
+ part = column + ` ` + sqlOperator + ` ` + serializeParamOrThrow(queryField, fieldName, fieldValue, false);
361
402
  }
362
- throw {
363
- RE_EXN_ID: Persistence.StorageError,
364
- message: `Failed loading "` + table.tableName + `" from storage. The "and" filter must contain at least one nested filter.`,
365
- reason: new Error(`Empty "and" filter`),
366
- Error: new Error()
367
- };
368
- }
403
+ condition.contents = condition.contents === "" ? part : condition.contents + " AND " + part;
404
+ });
405
+ });
406
+ return condition.contents;
369
407
  }
370
408
 
371
409
  function makeChainIdCondition(table, chainId) {
@@ -1032,16 +1070,10 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1032
1070
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
1033
1071
  let isolated = isolatedOpt !== undefined ? isolatedOpt : false;
1034
1072
  let containerName = "envio-postgres";
1035
- let psqlExecOptions_env = Object.fromEntries([
1036
- [
1037
- "PGPASSWORD",
1038
- pgPassword
1039
- ],
1040
- [
1041
- "PATH",
1042
- process.env.PATH
1043
- ]
1044
- ]);
1073
+ let psqlExecOptions_env = {
1074
+ PGPASSWORD: pgPassword,
1075
+ PATH: process.env.PATH
1076
+ };
1045
1077
  let psqlExecOptions = {
1046
1078
  env: psqlExecOptions_env
1047
1079
  };
@@ -1222,7 +1254,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1222
1254
  };
1223
1255
  let loadOrThrow = async (filter, table) => {
1224
1256
  let params = [];
1225
- let condition = makeFilterCondition(filter, table, params);
1257
+ let condition = makeFilterCondition(filter, table, pgSchema, params);
1226
1258
  let rows;
1227
1259
  try {
1228
1260
  rows = await sql.unsafe(makeLoadQuery(pgSchema, table.tableName, condition), params, {prepare: true});
@@ -1251,12 +1283,8 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1251
1283
  let queryFields = Table.queryFields(table);
1252
1284
  let columns = [];
1253
1285
  let seen = new Set();
1254
- let collect = filter => {
1255
- if (filter.operator === "and") {
1256
- filter.filters.forEach(collect);
1257
- return;
1258
- }
1259
- let match = queryFields[filter.fieldName];
1286
+ filters.forEach(filter => Utils.Dict.forEachWithKey(EntityFilter.entries(filter), (param, fieldName) => {
1287
+ let match = queryFields[fieldName];
1260
1288
  if (match === undefined) {
1261
1289
  return;
1262
1290
  }
@@ -1266,8 +1294,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1266
1294
  columns.push(pgDbFieldName);
1267
1295
  return;
1268
1296
  }
1269
- };
1270
- filters.forEach(collect);
1297
+ }));
1271
1298
  return columns;
1272
1299
  };
1273
1300
  let runAndVerify = async (sql, prepared) => {
@@ -75,15 +75,10 @@ let handleLoad = (state: testIndexerState, ~tableName: string, ~filter: EntityFi
75
75
  | None => []
76
76
  | Some(entityConfig) =>
77
77
  let entityDict = state.entities->Dict.get(tableName)->Option.getOr(Dict.make())
78
- let matched =
79
- entityDict
80
- ->Dict.valuesToArray
81
- ->Array.filter(entity => {
82
- // The store holds decoded entities and the filter carries decoded values,
83
- // so compare directly (same approach as InMemoryTable) — no JSON round-trip.
84
- let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
85
- filter->EntityFilter.matches(~entity=entityAsDict)
86
- })
78
+ // The store holds decoded entities and the filter carries decoded values,
79
+ // so compare directly (same approach as InMemoryTable) — no JSON round-trip.
80
+ let matcher = filter->EntityFilter.makeMatcher(~table=entityConfig.table)
81
+ let matched = entityDict->Dict.valuesToArray->Array.filter(matcher)
87
82
  // The chain is already fixed by the scope the load ran for, so the loaded
88
83
  // entity is handed back in the shape the handlers see.
89
84
  switch entityConfig.table->Table.getChainIdField {
@@ -544,23 +539,12 @@ let makeEntityGetWhere = (~state: testIndexerState, ~entityConfig: Internal.enti
544
539
  `Cannot call ${entityConfig.name}.getWhere() while indexer.process() is running. ` ++ "Wait for process() to complete before accessing entities directly.",
545
540
  )
546
541
  }
547
- let filters =
548
- filter->EntityFilter.parseGetWhereOrThrow(
549
- ~entityName=entityConfig.name,
550
- ~table=entityConfig.table,
551
- )
542
+ let matcher =
543
+ filter
544
+ ->EntityFilter.parseOrThrow(~entityName=entityConfig.name, ~table=entityConfig.table)
545
+ ->EntityFilter.makeMatcher(~table=entityConfig.table)
552
546
  let entityDict = state.entities->Dict.get(entityConfig.name)->Option.getOr(Dict.make())
553
- // parseGetWhereOrThrow expands an operator group into alternatives whose
554
- // matches are disjoint, so the union needs no dedup.
555
- Promise.resolve(
556
- entityDict
557
- ->Dict.valuesToArray
558
- ->Array.filter(entity => {
559
- let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
560
- filters->Array.some(filter => filter->EntityFilter.matches(~entity=entityAsDict))
561
- })
562
- ->Array.map(copyEntity),
563
- )
547
+ Promise.resolve(entityDict->Dict.valuesToArray->Array.filter(matcher)->Array.map(copyEntity))
564
548
  }
565
549
  }
566
550
 
@@ -56,7 +56,8 @@ function handleLoad(state, tableName, filter) {
56
56
  return [];
57
57
  }
58
58
  let entityDict = Stdlib_Option.getOr(state.entities[tableName], {});
59
- let matched = Object.values(entityDict).filter(entity => EntityFilter.matches(filter, entity));
59
+ let matcher = EntityFilter.makeMatcher(filter, entityConfig.table);
60
+ let matched = Object.values(entityDict).filter(matcher);
60
61
  let field = Table.getChainIdField(entityConfig.table);
61
62
  if (field !== undefined) {
62
63
  return matched.map(entity => {
@@ -376,9 +377,9 @@ function makeEntityGetWhere(state, entityConfig) {
376
377
  if (state.processInProgress) {
377
378
  Stdlib_JsError.throwWithMessage(`Cannot call ` + entityConfig.name + `.getWhere() while indexer.process() is running. ` + "Wait for process() to complete before accessing entities directly.");
378
379
  }
379
- let filters = EntityFilter.parseGetWhereOrThrow(filter, entityConfig.name, entityConfig.table);
380
+ let matcher = EntityFilter.makeMatcher(EntityFilter.parseOrThrow(filter, entityConfig.name, entityConfig.table), entityConfig.table);
380
381
  let entityDict = Stdlib_Option.getOr(state.entities[entityConfig.name], {});
381
- return Promise.resolve(Object.values(entityDict).filter(entity => filters.some(filter => EntityFilter.matches(filter, entity))).map(copyEntity));
382
+ return Promise.resolve(Object.values(entityDict).filter(matcher).map(copyEntity));
382
383
  };
383
384
  }
384
385
 
@@ -122,39 +122,20 @@ type entityContextParams = {
122
122
  // The handler context is always chain-scoped, so a per-chain entity resolves to
123
123
  // the chain the handler runs on and a cross-chain one to the shared partition.
124
124
  let entityScope = (params: entityContextParams) =>
125
- params.entityConfig->InMemoryStore.entityScope(
126
- ~chainId=params.item->Internal.getItemChainId,
127
- )
128
-
129
- let getWhereHandler = (params: entityContextParams, filter: dict<dict<unknown>>) => {
130
- let entityConfig = params.entityConfig
131
-
132
- @inline
133
- let loadWithFilter = filter =>
134
- LoadLayer.loadByFilter(
135
- ~loadManager=params.loadManager,
136
- ~persistence=params.persistence,
137
- ~entityConfig,
138
- ~scope=params->entityScope,
139
- ~indexerState=params.indexerState,
140
- ~shouldGroup=params.isPreload,
141
- ~item=params.item,
142
- ~ecosystem=params.config.ecosystem,
143
- ~filter,
144
- )
125
+ params.entityConfig->InMemoryStore.entityScope(~chainId=params.item->Internal.getItemChainId)
145
126
 
146
- switch filter->EntityFilter.parseGetWhereOrThrow(
147
- ~entityName=entityConfig.name,
148
- ~table=entityConfig.table,
149
- ) {
150
- | [single] => loadWithFilter(single)
151
- | filters =>
152
- filters
153
- ->Array.map(filter => loadWithFilter(filter))
154
- ->Promise.all
155
- ->Promise.thenResolve(results => results->Array.flat)
156
- }
157
- }
127
+ let getWhereHandler = (params: entityContextParams, filter: dict<dict<unknown>>) =>
128
+ LoadLayer.loadByFilter(
129
+ ~loadManager=params.loadManager,
130
+ ~persistence=params.persistence,
131
+ ~entityConfig=params.entityConfig,
132
+ ~scope=params->entityScope,
133
+ ~indexerState=params.indexerState,
134
+ ~shouldGroup=params.isPreload,
135
+ ~item=params.item,
136
+ ~ecosystem=params.config.ecosystem,
137
+ ~filter,
138
+ )
158
139
 
159
140
  let noopSet = (_entity: Internal.entity) => ()
160
141
  let noopDeleteUnsafe = (_entityId: EntityId.t) => ()
@@ -77,13 +77,7 @@ function entityScope(params) {
77
77
  }
78
78
 
79
79
  function getWhereHandler(params, filter) {
80
- let entityConfig = params.entityConfig;
81
- let filters = EntityFilter.parseGetWhereOrThrow(filter, entityConfig.name, entityConfig.table);
82
- if (filters.length !== 1) {
83
- return Promise.all(filters.map(filter => LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, filter))).then(results => results.flat());
84
- } else {
85
- return LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, filters[0]);
86
- }
80
+ return LoadLayer.loadByFilter(params.loadManager, params.persistence, params.entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, filter);
87
81
  }
88
82
 
89
83
  function noopSet(_entity) {
package/src/Utils.res CHANGED
@@ -273,7 +273,7 @@ module Dict = {
273
273
  }
274
274
 
275
275
  module Math = {
276
- let minOptInt = (a, b) =>
276
+ let minOptInt = (a: option<int>, b: option<int>) =>
277
277
  switch (a, b) {
278
278
  | (Some(a), Some(b)) => Some(a < b ? a : b)
279
279
  | (Some(a), None) => Some(a)
@@ -374,9 +374,6 @@ module Array = {
374
374
  /**
375
375
  Helper to check if a value exists in an array
376
376
  */
377
- let includes = (arr: array<'a>, val: 'a) =>
378
- arr->Array.find(item => item == val)->Stdlib.Option.isSome
379
-
380
377
  let isEmpty = (arr: array<_>) =>
381
378
  switch arr {
382
379
  | [] => true
package/src/Utils.res.mjs CHANGED
@@ -6,11 +6,9 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
6
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
7
7
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
8
8
  import * as Stdlib_BigInt from "@rescript/runtime/lib/es6/Stdlib_BigInt.js";
9
- import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
9
  import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
11
10
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
11
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
13
- import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
14
12
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
15
13
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
16
14
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -221,19 +219,17 @@ let Dict = {
221
219
  };
222
220
 
223
221
  function minOptInt(a, b) {
224
- if (a === undefined) {
222
+ if (a !== undefined) {
225
223
  if (b !== undefined) {
226
- return Primitive_option.some(Primitive_option.valFromOption(b));
224
+ return a < b ? a : b;
227
225
  } else {
228
- return;
226
+ return a;
229
227
  }
228
+ } else if (b !== undefined) {
229
+ return b;
230
+ } else {
231
+ return;
230
232
  }
231
- let a$1 = Primitive_option.valFromOption(a);
232
- if (b === undefined) {
233
- return Primitive_option.some(a$1);
234
- }
235
- let b$1 = Primitive_option.valFromOption(b);
236
- return Primitive_option.some(Primitive_object.lessthan(a$1, b$1) ? a$1 : b$1);
237
233
  }
238
234
 
239
235
  let $$Math = {
@@ -324,10 +320,6 @@ function transposeResults(results) {
324
320
  };
325
321
  }
326
322
 
327
- function includes(arr, val) {
328
- return Stdlib_Option.isSome(arr.find(item => Primitive_object.equal(item, val)));
329
- }
330
-
331
323
  function isEmpty$1(arr) {
332
324
  return arr.length === 0;
333
325
  }
@@ -435,7 +427,6 @@ let $$Array$1 = {
435
427
  clearInPlace: clearInPlace$1,
436
428
  setIndexImmutable: setIndexImmutable,
437
429
  transposeResults: transposeResults,
438
- includes: includes,
439
430
  isEmpty: isEmpty$1,
440
431
  notEmpty: notEmpty,
441
432
  awaitEach: awaitEach,