envio 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +2 -2
  2. package/index.d.ts +399 -156
  3. package/package.json +6 -6
  4. package/src/AddressRows.res +121 -0
  5. package/src/AddressRows.res.mjs +143 -0
  6. package/src/Batch.res +2 -0
  7. package/src/Batch.res.mjs +2 -1
  8. package/src/ChainState.res +82 -68
  9. package/src/ChainState.res.mjs +81 -65
  10. package/src/ChainState.resi +14 -9
  11. package/src/Config.res +41 -91
  12. package/src/Config.res.mjs +34 -68
  13. package/src/ContractMapping.res +66 -0
  14. package/src/ContractMapping.res.mjs +73 -0
  15. package/src/Core.res +20 -0
  16. package/src/Core.res.mjs +4 -0
  17. package/src/Envio.res +54 -76
  18. package/src/EventConfigBuilder.res +157 -30
  19. package/src/EventConfigBuilder.res.mjs +118 -24
  20. package/src/FetchState.res +59 -67
  21. package/src/FetchState.res.mjs +34 -44
  22. package/src/HandlerRegister.res +20 -11
  23. package/src/HandlerRegister.res.mjs +22 -5
  24. package/src/Hasura.res +30 -22
  25. package/src/Hasura.res.mjs +11 -5
  26. package/src/InMemoryStore.res +18 -36
  27. package/src/InMemoryStore.res.mjs +12 -26
  28. package/src/IndexerState.res +21 -1
  29. package/src/IndexerState.res.mjs +9 -3
  30. package/src/IndexerState.resi +1 -0
  31. package/src/Internal.res +33 -13
  32. package/src/Internal.res.mjs +12 -16
  33. package/src/Main.res +27 -20
  34. package/src/Main.res.mjs +19 -14
  35. package/src/MemoryStorage.res +78 -73
  36. package/src/MemoryStorage.res.mjs +66 -81
  37. package/src/Metrics.res +15 -1
  38. package/src/Metrics.res.mjs +5 -1
  39. package/src/Persistence.res +34 -18
  40. package/src/Persistence.res.mjs +8 -5
  41. package/src/PgStorage.res +189 -66
  42. package/src/PgStorage.res.mjs +93 -59
  43. package/src/Rollback.res +14 -10
  44. package/src/Rollback.res.mjs +4 -2
  45. package/src/SimulateItems.res +254 -9
  46. package/src/SimulateItems.res.mjs +214 -47
  47. package/src/TestIndexer.res +119 -121
  48. package/src/TestIndexer.res.mjs +95 -100
  49. package/src/Utils.res +7 -0
  50. package/src/Utils.res.mjs +9 -2
  51. package/src/Writing.res +2 -0
  52. package/src/Writing.res.mjs +2 -1
  53. package/src/bindings/ClickHouse.res +39 -6
  54. package/src/bindings/ClickHouse.res.mjs +29 -4
  55. package/src/bindings/NodeJs.res +9 -0
  56. package/src/bindings/NodeJs.res.mjs +8 -1
  57. package/src/bindings/Postgres.res +9 -0
  58. package/src/bindings/Postgres.res.mjs +3 -0
  59. package/src/db/EntityHistory.res +12 -18
  60. package/src/db/EntityHistory.res.mjs +3 -14
  61. package/src/db/InternalTable.res +182 -65
  62. package/src/db/InternalTable.res.mjs +183 -73
  63. package/src/db/Table.res +24 -0
  64. package/src/db/Table.res.mjs +20 -1
  65. package/src/sources/AddressSet.res +0 -22
  66. package/src/sources/AddressStore.res +48 -88
  67. package/src/sources/AddressStore.res.mjs +11 -22
  68. package/src/sources/EvmHyperSyncSource.res +3 -2
  69. package/src/sources/SimulateSource.res +8 -4
  70. package/src/sources/SimulateSource.res.mjs +7 -3
  71. package/src/sources/Svm.res +34 -7
  72. package/src/sources/Svm.res.mjs +32 -4
  73. package/src/sources/SvmHyperSyncClient.res +24 -30
  74. package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
  75. package/src/sources/SvmHyperSyncSource.res +88 -36
  76. package/src/sources/SvmHyperSyncSource.res.mjs +71 -39
  77. package/src/sources/TransactionStore.res +38 -0
  78. package/src/sources/TransactionStore.res.mjs +5 -0
  79. package/svm.schema.json +37 -82
@@ -11,7 +11,10 @@ let sslOptionsSchema = S$RescriptSchema.$$enum([
11
11
  "verify-full"
12
12
  ]);
13
13
 
14
+ let byteaArrayOid = 1001;
15
+
14
16
  export {
15
17
  sslOptionsSchema,
18
+ byteaArrayOid,
16
19
  }
17
20
  /* sslOptionsSchema Not a pure module */
@@ -50,18 +50,14 @@ type pgEntityHistory<'entity> = {
50
50
  setChangeSchemaRows: S.t<array<Change.t<'entity>>>,
51
51
  }
52
52
 
53
- let maxPgTableNameLength = 63
54
53
  let historyTablePrefix = "envio_history_"
55
- let historyTableName = (~entityName, ~entityIndex) => {
56
- let fullName = historyTablePrefix ++ entityName
57
- if fullName->String.length > maxPgTableNameLength {
58
- let entityIndexStr = entityIndex->Int.toString
59
- fullName->Js.String.slice(~from=0, ~to_=maxPgTableNameLength - entityIndexStr->String.length) ++
60
- entityIndexStr
61
- } else {
62
- fullName
63
- }
64
- }
54
+ // `$` can't occur in a GraphQL entity name, so it marks where a truncated name
55
+ // stops and the index that keeps it unique begins. Without that boundary two
56
+ // long names whose indexes differ in digit count can truncate onto the same
57
+ // identifier, and `CREATE TABLE IF NOT EXISTS` would hand both entities one
58
+ // history table.
59
+ let historyTableName = (~entityName, ~entityIndex) =>
60
+ fitPgTableName(historyTablePrefix ++ entityName, ~uniqueSuffix=`$${entityIndex->Int.toString}`)
65
61
 
66
62
  type safeReorgBlocks = {
67
63
  chainIds: array<ChainId.t>,
@@ -136,7 +132,7 @@ let pruneStaleEntityHistory = (
136
132
  // If an entity doesn't have a history before the update
137
133
  // we create it automatically with envio_checkpoint_id 0
138
134
  // The ids belong to a single chain (the flush group's scope), so the chain is
139
- // bound once as $2 rather than unnested alongside them.
135
+ // named once in the query rather than unnested alongside them.
140
136
  let makeBackfillHistoryQuery = (
141
137
  ~pgSchema,
142
138
  ~entityName,
@@ -146,8 +142,11 @@ let makeBackfillHistoryQuery = (
146
142
  ~chainId: option<ChainId.t>,
147
143
  ) => {
148
144
  let historyTableRef = `"${pgSchema}"."${historyTableName(~entityName, ~entityIndex)}"`
145
+ // Written into the SQL rather than bound: this scans the entity table, which
146
+ // is partitioned by the chain-id column, and Postgres can only prune a plan
147
+ // it caches when that column is a constant.
149
148
  let chainFilter = switch (chainIdColumn, chainId) {
150
- | (Some(column), Some(_)) => ` AND e."${column}" = $2`
149
+ | (Some(column), Some(chainId)) => ` AND e."${column}" = ${chainId->ChainId.toString}`
151
150
  | _ => ""
152
151
  }
153
152
  `WITH target_ids AS (
@@ -176,11 +175,6 @@ let backfillHistory = (
176
175
  let idPgType = table->Table.getIdPgFieldType(~pgSchema)
177
176
  let chainIdColumn = table->Table.getChainIdField->Option.map(Table.getPgDbFieldName)
178
177
  let params = [table->Table.encodeIdsToJson(ids)->(Utils.magic: JSON.t => unknown)]
179
- switch (chainIdColumn, chainId) {
180
- | (Some(_), Some(chainId)) =>
181
- params->Array.push(chainId->(Utils.magic: ChainId.t => unknown))->ignore
182
- | _ => ()
183
- }
184
178
  sql
185
179
  ->Postgres.preparedUnsafe(
186
180
  makeBackfillHistoryQuery(
@@ -1,7 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Table from "./Table.res.mjs";
4
- import * as Js_string from "@rescript/runtime/lib/es6/Js_string.js";
4
+ import * as ChainId from "../ChainId.res.mjs";
5
5
  import * as Stdlib_BigInt from "@rescript/runtime/lib/es6/Stdlib_BigInt.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
7
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
@@ -65,12 +65,7 @@ function makeSetUpdateSchema(idSchema, entitySchema) {
65
65
  let historyTablePrefix = "envio_history_";
66
66
 
67
67
  function historyTableName(entityName, entityIndex) {
68
- let fullName = historyTablePrefix + entityName;
69
- if (fullName.length <= 63) {
70
- return fullName;
71
- }
72
- let entityIndexStr = entityIndex.toString();
73
- return Js_string.slice(0, 63 - entityIndexStr.length | 0, fullName) + entityIndexStr;
68
+ return Table.fitPgTableName(historyTablePrefix + entityName, `$` + entityIndex.toString());
74
69
  }
75
70
 
76
71
  function makeKeyColumns(chainIdColumn) {
@@ -112,7 +107,7 @@ function pruneStaleEntityHistory(sql, entityName, entityIndex, pgSchema, chainId
112
107
 
113
108
  function makeBackfillHistoryQuery(pgSchema, entityName, entityIndex, idPgType, chainIdColumn, chainId) {
114
109
  let historyTableRef = `"` + pgSchema + `"."` + historyTableName(entityName, entityIndex) + `"`;
115
- let chainFilter = chainIdColumn !== undefined && chainId !== undefined ? ` AND e."` + chainIdColumn + `" = $2` : "";
110
+ let chainFilter = chainIdColumn !== undefined && chainId !== undefined ? ` AND e."` + chainIdColumn + `" = ` + ChainId.toString(Primitive_option.valFromOption(chainId)) : "";
116
111
  return `WITH target_ids AS (
117
112
  SELECT UNNEST($1::` + idPgType + `[]) AS id
118
113
  ),
@@ -132,9 +127,6 @@ function backfillHistory(sql, pgSchema, table, entityIndex, chainId, ids) {
132
127
  let idPgType = Table.getIdPgFieldType(table, pgSchema);
133
128
  let chainIdColumn = Stdlib_Option.map(Table.getChainIdField(table), Table.getPgDbFieldName);
134
129
  let params = [Table.encodeIdsToJson(table, ids)];
135
- if (chainIdColumn !== undefined && chainId !== undefined) {
136
- params.push(Primitive_option.valFromOption(chainId));
137
- }
138
130
  return sql.unsafe(makeBackfillHistoryQuery(pgSchema, table.tableName, entityIndex, idPgType, chainIdColumn, chainId), params, {prepare: true});
139
131
  }
140
132
 
@@ -144,8 +136,6 @@ function rollback(sql, pgSchema, entityName, entityIndex, rollbackTargetCheckpoi
144
136
 
145
137
  let checkpointIdFieldType = "UInt64";
146
138
 
147
- let maxPgTableNameLength = 63;
148
-
149
139
  export {
150
140
  RowAction,
151
141
  changeFieldName,
@@ -154,7 +144,6 @@ export {
154
144
  changeFieldType,
155
145
  unsafeCheckpointIdSchema,
156
146
  makeSetUpdateSchema,
157
- maxPgTableNameLength,
158
147
  historyTablePrefix,
159
148
  historyTableName,
160
149
  makeKeyColumns,
@@ -5,7 +5,167 @@ let isPrimaryKey = true
5
5
  let isNullable = true
6
6
  let isIndex = true
7
7
 
8
- module EnvioAddresses = Config.EnvioAddresses
8
+ // Postgres SQLSTATE for "undefined_table" — what a read gets when the schema was
9
+ // initialized by an older envio that didn't have the table.
10
+ let undefinedTableSqlState = "42P01"
11
+
12
+ @get external getSqlStateCode: JsExn.t => option<string> = "code"
13
+
14
+ let isUndefinedTable = exn =>
15
+ switch exn->JsExn.anyToExnInternal {
16
+ | JsExn(e) => e->getSqlStateCode === Some(undefinedTableSqlState)
17
+ | _ => false
18
+ }
19
+
20
+ // The array type an unnest binds a chain-id column to. Resolved from the
21
+ // config's mode, so every internal query casts the parameter the same way the
22
+ // column was created.
23
+ let chainIdArrayType = (~pgSchema, ~chainIdMode: ChainId.mode) =>
24
+ Table.getPgFieldType(
25
+ ~fieldType=ChainId,
26
+ ~pgSchema,
27
+ ~isArray=true,
28
+ ~isNumericArrayAsText=false,
29
+ ~isNullable=false,
30
+ ~chainIdMode,
31
+ )
32
+
33
+ // The canonical contract ids. Written once at initialize from the config's
34
+ // contract names in byte order, so an id names the same contract on every
35
+ // chain and across restarts; read back on resume, where the stored mapping is
36
+ // what every address row means.
37
+ module EnvioContracts = {
38
+ let table = mkTable(
39
+ "envio_contracts",
40
+ ~fields=[
41
+ mkField("id", SmallInt, ~fieldSchema=S.int, ~isPrimaryKey),
42
+ mkField("name", String, ~fieldSchema=S.string),
43
+ ],
44
+ )
45
+
46
+ let makeInsertQuery = (~pgSchema) =>
47
+ `INSERT INTO "${pgSchema}"."${table.tableName}" ("id", "name")
48
+ SELECT * FROM unnest($1::${(SmallInt: Postgres.columnType :> string)}[],$2::${(Text: Postgres.columnType :> string)}[]);`
49
+
50
+ // `contractNames` is the canonical list: a name's position is its id.
51
+ let insert = (sql, ~pgSchema, ~contractNames: array<string>) =>
52
+ sql
53
+ ->Postgres.preparedUnsafe(
54
+ makeInsertQuery(~pgSchema),
55
+ (contractNames->Array.mapWithIndex((_, idx) => idx), contractNames)->(
56
+ Utils.magic: ((array<int>, array<string>)) => unknown
57
+ ),
58
+ )
59
+ ->Utils.Promise.ignoreValue
60
+
61
+ // Ordered by id, so the result is the canonical list itself. None when the
62
+ // schema has no such table: it was written by an envio that predates the
63
+ // contract mapping, and every address row in it is shaped differently — so a
64
+ // resume has to stop at the compat check rather than at a missing column.
65
+ let read = async (sql, ~pgSchema): option<array<string>> =>
66
+ try {
67
+ let rows: array<{
68
+ "name": string,
69
+ }> = await sql->Postgres.unsafe(
70
+ `SELECT "name" FROM "${pgSchema}"."${table.tableName}" ORDER BY "id";`,
71
+ )
72
+ Some(rows->Array.map(row => row["name"]))
73
+ } catch {
74
+ | exn => isUndefinedTable(exn) ? None : throw(exn)
75
+ }
76
+ }
77
+
78
+ module EnvioAddresses = {
79
+ let name = "envio_addresses"
80
+
81
+ let table = mkTable(
82
+ name,
83
+ ~fields=[
84
+ mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey),
85
+ // The field schemas are unused: this table is read and written by the
86
+ // hand-written queries below, never through the generic row encoding.
87
+ mkField("address", Bytea, ~fieldSchema=S.string, ~isPrimaryKey),
88
+ mkField("contract_id", SmallInt, ~fieldSchema=S.int, ~isPrimaryKey),
89
+ mkField("registration_block", Int32, ~fieldSchema=S.int),
90
+ ],
91
+ )
92
+
93
+ let makeInsertQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
94
+ let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
95
+ `INSERT INTO "${pgSchema}"."${table.tableName}" ("chain_id", "address", "contract_id", "registration_block")
96
+ SELECT * FROM unnest($1::${chainIdArrayType},$2::${(Bytea: Postgres.columnType :> string)}[],$3::${(SmallInt: Postgres.columnType :> string)}[],$4::${(Integer: Postgres.columnType :> string)}[])
97
+ ON CONFLICT ("chain_id", "address", "contract_id") DO NOTHING;`
98
+ }
99
+
100
+ let insert = (
101
+ sql,
102
+ ~pgSchema,
103
+ ~rows: array<AddressRows.row>,
104
+ ~chainIdMode: ChainId.mode=Int32,
105
+ ) => {
106
+ let chainIds = []
107
+ let addresses = []
108
+ let contractIds = []
109
+ let registrationBlocks = []
110
+ rows->Array.forEach(row => {
111
+ chainIds->Array.push(row.chainId)->ignore
112
+ addresses->Array.push(row.address)->ignore
113
+ contractIds->Array.push(row.contractId)->ignore
114
+ registrationBlocks->Array.push(row.registrationBlock)->ignore
115
+ })
116
+ sql
117
+ ->Postgres.preparedUnsafe(
118
+ makeInsertQuery(~pgSchema, ~chainIdMode),
119
+ (
120
+ chainIds,
121
+ sql->Postgres.typed(addresses, Postgres.byteaArrayOid),
122
+ contractIds,
123
+ registrationBlocks,
124
+ )->(Utils.magic: ((array<ChainId.t>, unknown, array<int>, array<int>)) => unknown),
125
+ )
126
+ ->Utils.Promise.ignoreValue
127
+ }
128
+
129
+ let makeDeleteQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
130
+ let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
131
+ `DELETE FROM "${pgSchema}"."${table.tableName}"
132
+ USING unnest($1::${chainIdArrayType},$2::${(Bytea: Postgres.columnType :> string)}[],$3::${(SmallInt: Postgres.columnType :> string)}[]) AS dead(chain_id, address, contract_id)
133
+ WHERE "${table.tableName}"."chain_id" = dead.chain_id
134
+ AND "${table.tableName}"."address" = dead.address
135
+ AND "${table.tableName}"."contract_id" = dead.contract_id;`
136
+ }
137
+
138
+ let delete = (
139
+ sql,
140
+ ~pgSchema,
141
+ ~keys: array<AddressRows.key>,
142
+ ~chainIdMode: ChainId.mode=Int32,
143
+ ) => {
144
+ let chainIds = []
145
+ let addresses = []
146
+ let contractIds = []
147
+ keys->Array.forEach(key => {
148
+ chainIds->Array.push(key.chainId)->ignore
149
+ addresses->Array.push(key.address)->ignore
150
+ contractIds->Array.push(key.contractId)->ignore
151
+ })
152
+ sql
153
+ ->Postgres.preparedUnsafe(
154
+ makeDeleteQuery(~pgSchema, ~chainIdMode),
155
+ (chainIds, sql->Postgres.typed(addresses, Postgres.byteaArrayOid), contractIds)->(
156
+ Utils.magic: ((array<ChainId.t>, unknown, array<int>)) => unknown
157
+ ),
158
+ )
159
+ ->Utils.Promise.ignoreValue
160
+ }
161
+
162
+ let makeGetRowsQuery = (~pgSchema) =>
163
+ `SELECT "chain_id" as "chainId",
164
+ "address" as "address",
165
+ "contract_id" as "contractId",
166
+ "registration_block" as "registrationBlock"
167
+ FROM "${pgSchema}"."${table.tableName}";`
168
+ }
9
169
 
10
170
  module Chains = {
11
171
  type progressFields = [
@@ -17,6 +177,7 @@ module Chains = {
17
177
  type field = [
18
178
  | progressFields
19
179
  | #id
180
+ | #ecosystem
20
181
  | #start_block
21
182
  | #end_block
22
183
  | #max_reorg_depth
@@ -29,6 +190,7 @@ module Chains = {
29
190
 
30
191
  let fields: array<field> = [
31
192
  #id,
193
+ #ecosystem,
32
194
  #start_block,
33
195
  #end_block,
34
196
  #max_reorg_depth,
@@ -57,6 +219,7 @@ module Chains = {
57
219
 
58
220
  type t = {
59
221
  @as("id") id: ChainId.t,
222
+ @as("ecosystem") ecosystem: string,
60
223
  @as("start_block") startBlock: int,
61
224
  @as("end_block") endBlock: Null.t<int>,
62
225
  @as("max_reorg_depth") maxReorgDepth: int,
@@ -70,6 +233,10 @@ module Chains = {
70
233
  "envio_chains",
71
234
  ~fields=[
72
235
  mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey),
236
+ // Which ecosystem the chain belongs to (evm / fuel / svm). Chain ids are
237
+ // only unique within an ecosystem (HOS-1880: Svm ids are Envio-assigned),
238
+ // so consumers should treat (ecosystem, id) as the chain's identity.
239
+ mkField((#ecosystem: field :> string), String, ~fieldSchema=S.string),
73
240
  // Values populated from config
74
241
  mkField((#start_block: field :> string), Int32, ~fieldSchema=S.int),
75
242
  mkField((#end_block: field :> string), Int32, ~fieldSchema=S.null(S.int), ~isNullable),
@@ -104,6 +271,7 @@ module Chains = {
104
271
  let initialFromConfig = (chainConfig: Config.chain) => {
105
272
  {
106
273
  id: chainConfig.id,
274
+ ecosystem: (chainConfig.ecosystem: Ecosystem.name :> string),
107
275
  startBlock: chainConfig.startBlock,
108
276
  endBlock: chainConfig.endBlock->Null.fromOption,
109
277
  maxReorgDepth: chainConfig.maxReorgDepth,
@@ -131,6 +299,7 @@ module Chains = {
131
299
  let value = initialValues->(Utils.magic: t => dict<unknown>)->Dict.get((field :> string))
132
300
  switch typeof(value) {
133
301
  | #object => "NULL"
302
+ | #string => `'${value->(Utils.magic: option<unknown> => string)}'`
134
303
  | #number => value->(Utils.magic: option<unknown> => int)->Int.toString
135
304
  | #bigint => value->(Utils.magic: option<unknown> => bigint)->BigInt.toString
136
305
  | #boolean => value->(Utils.magic: option<unknown> => bool) ? "true" : "false"
@@ -189,7 +358,7 @@ WHERE "${(#id: field :> string)}" = $2
189
358
  timestampCaughtUpToHeadOrEndblock: Null.t<Date.t>,
190
359
  numEventsProcessed: float,
191
360
  progressBlockNumber: int,
192
- indexingAddresses: array<Internal.indexingAddress>,
361
+ addressRows: AddressRows.seedRows,
193
362
  sourceBlockNumber: int,
194
363
  }
195
364
 
@@ -206,67 +375,30 @@ WHERE "${(#id: field :> string)}" = $2
206
375
  FROM "${pgSchema}"."${table.tableName}";`
207
376
  }
208
377
 
209
- type rawIndexingAddress = {
210
- chainId: ChainId.t,
211
- address: Address.t,
212
- contractName: string,
213
- registrationBlock: int,
214
- }
215
-
216
378
  // Addresses are read as plain rows rather than aggregated per chain with
217
379
  // json_agg: a single chain's aggregate can exceed V8's max string length
218
380
  // (postgres.js decodes the column with Buffer.toString and throws
219
381
  // ERR_STRING_TOO_LONG). Grouping happens in JS instead — see getInitialState.
220
- let makeGetIndexingAddressesQuery = (~pgSchema) => {
221
- // envio_addresses.id is a composite "{chainId}-{address}" string produced by
222
- // Config.EnvioAddresses.makeId; extract the address by taking everything
223
- // after the first '-'. Keep in sync with makeId / getAddress.
224
- `SELECT "chain_id" as "chainId",
225
- SUBSTRING("id" FROM POSITION('-' IN "id") + 1) as "address",
226
- "contract_name" as "contractName",
227
- "registration_block" as "registrationBlock"
228
- FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";`
229
- }
230
-
231
382
  let getInitialState = async (sql, ~pgSchema) => {
232
- let (rawInitialStates, rawIndexingAddresses) = await Promise.all2((
383
+ let (rawInitialStates, rawAddressRows) = await Promise.all2((
233
384
  sql
234
385
  ->Postgres.unsafe(makeGetInitialStateQuery(~pgSchema))
235
386
  ->(Utils.magic: promise<array<unknown>> => promise<array<rawInitialState>>),
236
387
  sql
237
- ->Postgres.unsafe(makeGetIndexingAddressesQuery(~pgSchema))
238
- ->(Utils.magic: promise<array<unknown>> => promise<array<rawIndexingAddress>>),
388
+ ->Postgres.unsafe(EnvioAddresses.makeGetRowsQuery(~pgSchema))
389
+ ->(Utils.magic: promise<array<unknown>> => promise<array<AddressRows.row>>),
239
390
  ))
240
391
 
241
- let indexingAddressesByChainId = Dict.make()
242
- rawIndexingAddresses->Array.forEach(row => {
243
- // BIGINT chain ids come back as strings; normalizing here keeps the
244
- // grouping key identical to the one derived from the chains rows below.
245
- let key = row.chainId->ChainId.normalizeOrThrow->ChainId.toString
246
- let addresses = switch indexingAddressesByChainId->Dict.get(key) {
247
- | Some(addresses) => addresses
248
- | None =>
249
- let addresses: array<Internal.indexingAddress> = []
250
- indexingAddressesByChainId->Dict.set(key, addresses)
251
- addresses
252
- }
253
- addresses
254
- ->Array.push({
255
- address: row.address,
256
- contractName: row.contractName,
257
- registrationBlock: row.registrationBlock,
258
- })
259
- ->ignore
260
- })
392
+ let addressRowsByChainId = rawAddressRows->AddressRows.group
261
393
 
262
394
  rawInitialStates->Array.map(rawInitialState => {
263
395
  let id = rawInitialState.id->ChainId.normalizeOrThrow
264
396
  {
265
397
  ...rawInitialState,
266
398
  id,
267
- indexingAddresses: indexingAddressesByChainId
268
- ->Dict.get(id->ChainId.toString)
269
- ->Option.getOr([]),
399
+ addressRows: addressRowsByChainId
400
+ ->Utils.Dict.dangerouslyGetNonOption(id->ChainId.toString)
401
+ ->Option.getOr(AddressRows.emptySeedRows()),
270
402
  }
271
403
  })
272
404
  }
@@ -362,23 +494,13 @@ module EnvioInfo = {
362
494
  ],
363
495
  )
364
496
 
365
- // Postgres SQLSTATE for "undefined_table" — what we get when the schema
366
- // was initialized by an older envio that didn't have `envio_info`.
367
- let undefinedTableSqlState = "42P01"
368
-
369
- @get external getCode: JsExn.t => option<string> = "code"
370
-
371
497
  let read = async (sql, ~pgSchema): option<JSON.t> => {
372
498
  let rows: array<{
373
499
  "config": string,
374
500
  }> = try await sql->Postgres.unsafe(
375
501
  `SELECT "config" FROM "${pgSchema}"."${table.tableName}" LIMIT 1;`,
376
502
  ) catch {
377
- | exn =>
378
- switch exn->JsExn.anyToExnInternal {
379
- | JsExn(e) if e->getCode === Some(undefinedTableSqlState) => []
380
- | _ => throw(exn)
381
- }
503
+ | exn => isUndefinedTable(exn) ? [] : throw(exn)
382
504
  }
383
505
  rows->Array.get(0)->Option.map(row => row["config"]->JSON.parseOrThrow)
384
506
  }
@@ -476,14 +598,7 @@ WHERE cp."${(#block_hash: field :> string)}" IS NOT NULL
476
598
  }
477
599
 
478
600
  let makeInsertCheckpointQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
479
- let chainIdArrayType = Table.getPgFieldType(
480
- ~fieldType=ChainId,
481
- ~pgSchema,
482
- ~isArray=true,
483
- ~isNumericArrayAsText=false,
484
- ~isNullable=false,
485
- ~chainIdMode,
486
- )
601
+ let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
487
602
  `INSERT INTO "${pgSchema}"."${table.tableName}" ("${(#id: field :> string)}", "${(#chain_id: field :> string)}", "${(#block_number: field :> string)}", "${(#block_hash: field :> string)}", "${(#events_processed: field :> string)}")
488
603
  SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${chainIdArrayType},$3::${(Integer: Postgres.columnType :> string)}[],$4::${(Text: Postgres.columnType :> string)}[],$5::${(Integer: Postgres.columnType :> string)}[]);`
489
604
  }
@@ -648,6 +763,7 @@ module Views = {
648
763
  `CREATE VIEW "${pgSchema}"."${metaViewName}" AS
649
764
  SELECT
650
765
  "${(#id: Chains.field :> string)}" AS "chainId",
766
+ "${(#ecosystem: Chains.field :> string)}" AS "ecosystem",
651
767
  "${(#start_block: Chains.field :> string)}" AS "startBlock",
652
768
  "${(#end_block: Chains.field :> string)}" AS "endBlock",
653
769
  "${(#progress_block: Chains.field :> string)}" AS "progressBlock",
@@ -666,6 +782,7 @@ ORDER BY "${(#id: Chains.field :> string)}";`
666
782
  SELECT
667
783
  "${(#source_block: Chains.field :> string)}" AS "block_height",
668
784
  "${(#id: Chains.field :> string)}" AS "chain_id",
785
+ "${(#ecosystem: Chains.field :> string)}" AS "ecosystem",
669
786
  "${(#end_block: Chains.field :> string)}" AS "end_block",
670
787
  "${(#first_event_block: Chains.field :> string)}" AS "first_event_block_number",
671
788
  "${(#_is_hyper_sync: Chains.field :> string)}" AS "is_hyper_sync",