envio 3.13.0 → 3.14.0-subgraph

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 (111) hide show
  1. package/bin.mjs +10 -1
  2. package/index.d.ts +11 -0
  3. package/package.json +8 -20
  4. package/rescript.json +1 -4
  5. package/src/Config.res +78 -100
  6. package/src/Config.res.mjs +55 -64
  7. package/src/Core.res +67 -6
  8. package/src/Core.res.mjs +58 -4
  9. package/src/EventConfigBuilder.res +4 -10
  10. package/src/EventConfigBuilder.res.mjs +1 -3
  11. package/src/EventProcessing.res +6 -6
  12. package/src/EventProcessing.res.mjs +8 -6
  13. package/src/ExitOnCaughtUp.res +5 -1
  14. package/src/ExitOnCaughtUp.res.mjs +8 -3
  15. package/src/HandlerLoader.res +98 -36
  16. package/src/HandlerLoader.res.mjs +89 -24
  17. package/src/HandlerTypeCheck.mjs +14 -0
  18. package/src/HandlerTypeCheckWorker.mjs +68 -0
  19. package/src/Internal.res +1 -1
  20. package/src/LoadLayer.res +39 -7
  21. package/src/LoadLayer.res.mjs +65 -38
  22. package/src/LoadLayer.resi +28 -0
  23. package/src/Main.res +17 -12
  24. package/src/Main.res.mjs +9 -3
  25. package/src/NodeVersion.mjs +13 -0
  26. package/src/Persistence.res +79 -38
  27. package/src/Persistence.res.mjs +41 -10
  28. package/src/PgStorage.res +86 -39
  29. package/src/PgStorage.res.mjs +83 -32
  30. package/src/ResumePlan.res +172 -0
  31. package/src/ResumePlan.res.mjs +122 -0
  32. package/src/Supervisor.res +8 -8
  33. package/src/Supervisor.res.mjs +6 -1
  34. package/src/SyncWrites.res +86 -0
  35. package/src/SyncWrites.res.mjs +85 -0
  36. package/src/TestIndexer.res +14 -4
  37. package/src/TestIndexer.res.mjs +3 -2
  38. package/src/TsModuleHooks.mjs +121 -0
  39. package/src/UserContext.res +377 -62
  40. package/src/UserContext.res.mjs +297 -39
  41. package/src/Utils.res +0 -8
  42. package/src/Worker.res +8 -0
  43. package/src/Worker.res.mjs +11 -0
  44. package/src/bindings/NodeJs.res +8 -7
  45. package/src/bindings/NodeJs.res.mjs +0 -3
  46. package/src/bindings/Pino.res +2 -2
  47. package/src/db/InternalTable.res +52 -2
  48. package/src/db/InternalTable.res.mjs +32 -3
  49. package/src/db/Table.res +10 -0
  50. package/src/db/Table.res.mjs +22 -11
  51. package/src/sources/FuelHyperSync.res +2 -0
  52. package/src/sources/FuelHyperSync.res.mjs +1 -0
  53. package/src/sources/FuelHyperSync.resi +1 -0
  54. package/src/sources/FuelHyperSyncClient.res +25 -8
  55. package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
  56. package/src/sources/FuelHyperSyncSource.res +24 -22
  57. package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
  58. package/src/subgraph/abi-types.ts +63 -0
  59. package/src/subgraph/assemblyscript.ts +85 -0
  60. package/src/subgraph/blocks.ts +183 -0
  61. package/src/subgraph/calls.ts +149 -0
  62. package/src/subgraph/conformance.ts +83 -0
  63. package/src/subgraph/errors.ts +96 -0
  64. package/src/subgraph/graph-cli-worker.mjs +26 -0
  65. package/src/subgraph/graph-cli.ts +190 -0
  66. package/src/subgraph/graph-ts-enums.ts +28 -0
  67. package/src/subgraph/graph-ts-types/VERSION +2 -0
  68. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  69. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  70. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  71. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  72. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  73. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  74. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  75. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  76. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  77. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  78. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  79. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  80. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  81. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  82. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  83. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  84. package/src/subgraph/graph-ts.ts +2285 -0
  85. package/src/subgraph/hosts.ts +158 -0
  86. package/src/subgraph/rpc.ts +65 -0
  87. package/src/subgraph/runtime.ts +595 -0
  88. package/src/subgraph/scope.ts +68 -0
  89. package/src/tui/InitApi.res +85 -0
  90. package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
  91. package/src/tui/Tui.res +142 -249
  92. package/src/tui/Tui.res.mjs +123 -332
  93. package/src/Js.shim.ts +0 -11
  94. package/src/bindings/DateFns.res +0 -74
  95. package/src/bindings/DateFns.res.mjs +0 -22
  96. package/src/sources/FuelSDK.res +0 -38
  97. package/src/sources/FuelSDK.res.mjs +0 -27
  98. package/src/sources/vendored-fuel-abi-coder.js +0 -1792
  99. package/src/tui/bindings/Ink.res +0 -369
  100. package/src/tui/bindings/Ink.res.mjs +0 -66
  101. package/src/tui/bindings/Style.res +0 -123
  102. package/src/tui/bindings/Style.res.mjs +0 -2
  103. package/src/tui/components/BufferedProgressBar.res +0 -40
  104. package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
  105. package/src/tui/components/CustomHooks.res +0 -122
  106. package/src/tui/components/Messages.res +0 -40
  107. package/src/tui/components/Messages.res.mjs +0 -71
  108. package/src/tui/components/SyncETA.res +0 -179
  109. package/src/tui/components/SyncETA.res.mjs +0 -249
  110. package/src/tui/components/TuiData.res +0 -98
  111. package/src/tui/components/TuiData.res.mjs +0 -73
@@ -39,9 +39,6 @@ type initialState = {
39
39
  // On a resume this is what the database holds, not what the config would
40
40
  // derive — the ids must never reshuffle under stored rows.
41
41
  contractMapping: ContractMapping.t,
42
- // Public config snapshot, restored with the address rows. None when
43
- // envio_info or envio_contracts is missing.
44
- envioInfo: option<JSON.t>,
45
42
  cache: dict<effectCacheRecord>,
46
43
  chains: array<initialChainState>,
47
44
  // Where each chain's checkpoint sequence stands in the database.
@@ -123,20 +120,25 @@ type storage = {
123
120
  ~contractMapping: ContractMapping.t,
124
121
  ~envioInfo: JSON.t,
125
122
  ) => promise<initialState>,
126
- // `throwIfIncompatible` gets what the storage holds before any sink is
127
- // resumed, so a config the stored one rules out is reported as such rather
128
- // than as the sink tripping over tables it never created.
129
- //
123
+ // Read before anything is resumed, so a config the stored one rules out is
124
+ // reported as such rather than as a sink tripping over tables it never
125
+ // created.
126
+ readStoredConfig: unit => promise<ResumePlan.stored>,
127
+ // Brings an initialized storage a chain it doesn't have yet: its row, its
128
+ // partitions and its config addresses, as `initialize` would have created
129
+ // them. The chains already there are left untouched.
130
+ addChain: (
131
+ ~chainConfig: Config.chain,
132
+ ~entities: array<Internal.entityConfig>,
133
+ ~contractMapping: ContractMapping.t,
134
+ ) => promise<unit>,
130
135
  // `chainIds` is what this run drives. An isolated run resumes a subset of
131
136
  // the stored chains, and the sink's resume trims past each resumed chain's
132
137
  // checkpoint, so chains a sibling process is still writing must stay out.
133
138
  resumeInitialState: (
134
139
  ~entities: array<Internal.entityConfig>,
135
140
  ~chainIds: array<ChainId.t>,
136
- ~throwIfIncompatible: (
137
- ~storedEnvioInfo: option<JSON.t>,
138
- ~storedContractMapping: ContractMapping.t,
139
- ) => unit,
141
+ ~contractMapping: ContractMapping.t,
140
142
  ) => promise<initialState>,
141
143
  // Returns rows matching the filter.
142
144
  // Field values are serialized and rows parsed with the table's field schemas.
@@ -243,6 +245,10 @@ type t = {
243
245
 
244
246
  exception StorageError({message: string, reason: exn})
245
247
 
248
+ // A start the operator has to act on, not a failure: what it says is all they
249
+ // need, so it's printed once, without a stack trace.
250
+ exception Refused(string)
251
+
246
252
  let make = (
247
253
  ~userEntities,
248
254
  // TODO: Should only pass userEnums and create internal config in runtime
@@ -271,10 +277,11 @@ let init = {
271
277
  ~runCommand,
272
278
  ~reset=false,
273
279
  ~lowercaseAddresses=false,
274
- // An isolated run needs the schema to exist already: initializing under it
275
- // would create rows for this process's chains only, leaving the ones it
276
- // skipped with no state for their own processes to resume.
277
- ~requireInitialized=false,
280
+ // An `envio start --chain` run. It needs the schema to exist already:
281
+ // initializing under it would create rows for this process's chains only,
282
+ // leaving the ones it skipped with no state for their own processes to
283
+ // resume. It is also the one run that may add its chain to the schema.
284
+ ~isolated=false,
278
285
  // Whether this process is the one that tells the operator the run resumed.
279
286
  // A supervisor says it once for the whole run, so the workers it forked
280
287
  // keep it to their own log files.
@@ -297,9 +304,11 @@ let init = {
297
304
  })
298
305
  persistence.storageStatus = Initializing(promise)
299
306
  if reset || !(await persistence.storage.isInitialized()) {
300
- if requireInitialized {
301
- JsError.throwWithMessage(
302
- "`envio start --chain` needs a database that already holds every chain. Run `envio local db-migrate up` once with the full config, then start a process per chain.",
307
+ if isolated {
308
+ throw(
309
+ Refused(
310
+ "`envio start --chain` needs a database that is already set up. Run `envio local db-migrate up` once with the full config, then start a process per chain.",
311
+ ),
303
312
  )
304
313
  }
305
314
  Logging.info(`Initializing the indexer storage...`)
@@ -330,18 +339,51 @@ let init = {
330
339
  ) {
331
340
  let logResume = announceResume ? Logging.info : Logging.trace
332
341
  logResume(`Found existing indexer storage. Resuming indexing state...`)
342
+ let stored = await persistence.storage.readStoredConfig()
343
+ switch ResumePlan.make(
344
+ ~stored,
345
+ ~envioInfo,
346
+ ~chainConfigs,
347
+ ~contractMapping,
348
+ ~lowercaseAddresses,
349
+ ~isolated,
350
+ ) {
351
+ | Resume => ()
352
+ | Incompatible(changedPaths) =>
353
+ throw(
354
+ Refused(
355
+ ResumePlan.incompatibleMessage(
356
+ changedPaths,
357
+ ~envioInfo,
358
+ ~resetCommand,
359
+ ~runCommand,
360
+ ),
361
+ ),
362
+ )
363
+ | AddChain(chainConfig) =>
364
+ Logging.info({
365
+ "msg": `Adding the chain to the existing indexer storage...`,
366
+ "chainId": chainConfig.id,
367
+ })
368
+ // The same resolution `initialize` runs, and for the same reason:
369
+ // this is the one time the chain's row is written.
370
+ let chainConfig =
371
+ (
372
+ await [chainConfig]->StartBlockResolver.resolveAllOrThrow(
373
+ ~lowercaseAddresses,
374
+ ~retry=startBlockRetry,
375
+ )
376
+ )->Array.getUnsafe(0)
377
+ await persistence.storage.addChain(
378
+ ~chainConfig,
379
+ ~entities=persistence.allEntities,
380
+ ~contractMapping=stored.contractMapping,
381
+ )
382
+ }
333
383
  let initialState = await persistence.storage.resumeInitialState(
334
384
  ~entities=persistence.allEntities,
335
385
  ~chainIds=chainConfigs->Array.map(chain => chain.id),
336
- ~throwIfIncompatible=(~storedEnvioInfo, ~storedContractMapping) =>
337
- Config.throwIfResumeIncompatible(
338
- ~storedEnvioInfo,
339
- ~storedContractMapping,
340
- ~envioInfo,
341
- ~contractMapping,
342
- ~resetCommand,
343
- ~runCommand,
344
- ),
386
+ ~contractMapping=stored.contractMapping,
345
387
  )
346
388
  persistence.storageStatus = Ready(initialState)
347
389
  let progress = Dict.make()
@@ -356,6 +398,7 @@ let init = {
356
398
  resolveRef.contents()
357
399
  }
358
400
  } catch {
401
+ | Refused(message) => JsError.throwWithMessage(message)
359
402
  | exn => exn->ErrorHandling.mkLogAndRaise(~msg=`Failed to initialize the indexer storage.`)
360
403
  }
361
404
  }
@@ -365,23 +408,21 @@ let init = {
365
408
  // a migration command: what a config change prints names the command the
366
409
  // operator ran, and an unreachable chain is waited on rather than reported,
367
410
  // since somebody is watching the run come up.
368
- let initForRun = (
369
- persistence,
370
- ~config: Config.t,
371
- ~reset,
372
- ~isDevelopmentMode,
373
- ~requireInitialized,
374
- ) =>
411
+ let initForRun = (persistence, ~config: Config.t, ~reset, ~isDevelopmentMode) =>
375
412
  persistence->init(
376
413
  ~announceResume=!Worker.isEnabled,
377
414
  ~reset,
378
415
  ~chainConfigs=config.chainMap->ChainMap.values,
379
416
  ~contractMapping=config.contractMapping,
380
- ~envioInfo=Config.envioInfo(),
381
- ~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
382
- ~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
417
+ ~envioInfo=config.envioInfo,
418
+ ~resetCommand=(isDevelopmentMode ? "envio dev -r" : "envio start -r")->Config.withProjectFlags(
419
+ ~config,
420
+ ),
421
+ ~runCommand=Some(
422
+ (isDevelopmentMode ? "envio dev" : "envio start")->Config.withProjectFlags(~config),
423
+ ),
383
424
  ~lowercaseAddresses=config.lowercaseAddresses,
384
- ~requireInitialized,
425
+ ~isolated=config.isolated,
385
426
  )
386
427
 
387
428
  let getInitializedStorageOrThrow = persistence => {
@@ -6,6 +6,7 @@ import * as Worker from "./Worker.res.mjs";
6
6
  import * as Logging from "./Logging.res.mjs";
7
7
  import * as ChainMap from "./ChainMap.res.mjs";
8
8
  import * as Frontier from "./db/Frontier.res.mjs";
9
+ import * as ResumePlan from "./ResumePlan.res.mjs";
9
10
  import * as EntityHistory from "./db/EntityHistory.res.mjs";
10
11
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
11
12
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
@@ -22,6 +23,8 @@ function writtenFrontier(batch, rollback) {
22
23
 
23
24
  let StorageError = /* @__PURE__ */Primitive_exceptions.create("Persistence.StorageError");
24
25
 
26
+ let Refused = /* @__PURE__ */Primitive_exceptions.create("Persistence.Refused");
27
+
25
28
  function make(userEntities, allEnums, storage) {
26
29
  let allEnums$1 = allEnums.concat([EntityHistory.RowAction.config]);
27
30
  return {
@@ -33,10 +36,10 @@ function make(userEntities, allEnums, storage) {
33
36
  };
34
37
  }
35
38
 
36
- async function init(persistence, chainConfigs, contractMapping, envioInfo, resetCommand, runCommand, resetOpt, lowercaseAddressesOpt, requireInitializedOpt, announceResumeOpt, startBlockRetryOpt) {
39
+ async function init(persistence, chainConfigs, contractMapping, envioInfo, resetCommand, runCommand, resetOpt, lowercaseAddressesOpt, isolatedOpt, announceResumeOpt, startBlockRetryOpt) {
37
40
  let reset = resetOpt !== undefined ? resetOpt : false;
38
41
  let lowercaseAddresses = lowercaseAddressesOpt !== undefined ? lowercaseAddressesOpt : false;
39
- let requireInitialized = requireInitializedOpt !== undefined ? requireInitializedOpt : false;
42
+ let isolated = isolatedOpt !== undefined ? isolatedOpt : false;
40
43
  let announceResume = announceResumeOpt !== undefined ? announceResumeOpt : true;
41
44
  let startBlockRetry = startBlockRetryOpt !== undefined ? startBlockRetryOpt : "UntilItAnswers";
42
45
  try {
@@ -64,8 +67,12 @@ async function init(persistence, chainConfigs, contractMapping, envioInfo, reset
64
67
  _0: promise$1
65
68
  };
66
69
  if (reset || !await persistence.storage.isInitialized()) {
67
- if (requireInitialized) {
68
- Stdlib_JsError.throwWithMessage("`envio start --chain` needs a database that already holds every chain. Run `envio local db-migrate up` once with the full config, then start a process per chain.");
70
+ if (isolated) {
71
+ throw {
72
+ RE_EXN_ID: Refused,
73
+ _1: "`envio start --chain` needs a database that is already set up. Run `envio local db-migrate up` once with the full config, then start a process per chain.",
74
+ Error: new Error()
75
+ };
69
76
  }
70
77
  Logging.info(`Initializing the indexer storage...`);
71
78
  let chainConfigs$1 = await StartBlockResolver.resolveAllOrThrow(chainConfigs, lowercaseAddresses, startBlockRetry, undefined, undefined);
@@ -82,7 +89,26 @@ async function init(persistence, chainConfigs, contractMapping, envioInfo, reset
82
89
  if (tmp) {
83
90
  let logResume = announceResume ? Logging.info : Logging.trace;
84
91
  logResume(`Found existing indexer storage. Resuming indexing state...`);
85
- let initialState$1 = await persistence.storage.resumeInitialState(persistence.allEntities, chainConfigs.map(chain => chain.id), (storedEnvioInfo, storedContractMapping) => Config.throwIfResumeIncompatible(storedEnvioInfo, storedContractMapping, envioInfo, contractMapping, resetCommand, runCommand));
92
+ let stored = await persistence.storage.readStoredConfig();
93
+ let changedPaths = ResumePlan.make(stored, envioInfo, chainConfigs, contractMapping, lowercaseAddresses, isolated);
94
+ if (typeof changedPaths === "object") {
95
+ if (changedPaths.TAG === "AddChain") {
96
+ let chainConfig = changedPaths._0;
97
+ Logging.info({
98
+ msg: `Adding the chain to the existing indexer storage...`,
99
+ chainId: chainConfig.id
100
+ });
101
+ let chainConfig$1 = (await StartBlockResolver.resolveAllOrThrow([chainConfig], lowercaseAddresses, startBlockRetry, undefined, undefined))[0];
102
+ await persistence.storage.addChain(chainConfig$1, persistence.allEntities, stored.contractMapping);
103
+ } else {
104
+ throw {
105
+ RE_EXN_ID: Refused,
106
+ _1: ResumePlan.incompatibleMessage(changedPaths._0, envioInfo, resetCommand, runCommand),
107
+ Error: new Error()
108
+ };
109
+ }
110
+ }
111
+ let initialState$1 = await persistence.storage.resumeInitialState(persistence.allEntities, chainConfigs.map(chain => chain.id), stored.contractMapping);
86
112
  persistence.storageStatus = {
87
113
  TAG: "Ready",
88
114
  _0: initialState$1
@@ -98,14 +124,18 @@ async function init(persistence, chainConfigs, contractMapping, envioInfo, reset
98
124
  }
99
125
  }
100
126
  return resolveRef.contents();
101
- } catch (raw_exn) {
102
- let exn = Primitive_exceptions.internalToException(raw_exn);
103
- return ErrorHandling.mkLogAndRaise(undefined, `Failed to initialize the indexer storage.`, exn);
127
+ } catch (raw_message) {
128
+ let message = Primitive_exceptions.internalToException(raw_message);
129
+ if (message.RE_EXN_ID === Refused) {
130
+ return Stdlib_JsError.throwWithMessage(message._1);
131
+ } else {
132
+ return ErrorHandling.mkLogAndRaise(undefined, `Failed to initialize the indexer storage.`, message);
133
+ }
104
134
  }
105
135
  }
106
136
 
107
- function initForRun(persistence, config, reset, isDevelopmentMode, requireInitialized) {
108
- return init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.envioInfo(), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset, config.lowercaseAddresses, requireInitialized, !Worker.isEnabled, undefined);
137
+ function initForRun(persistence, config, reset, isDevelopmentMode) {
138
+ return init(persistence, ChainMap.values(config.chainMap), config.contractMapping, config.envioInfo, Config.withProjectFlags(isDevelopmentMode ? "envio dev -r" : "envio start -r", config), Config.withProjectFlags(isDevelopmentMode ? "envio dev" : "envio start", config), reset, config.lowercaseAddresses, config.isolated, !Worker.isEnabled, undefined);
109
139
  }
110
140
 
111
141
  function getInitializedStorageOrThrow(persistence) {
@@ -129,6 +159,7 @@ function getInitializedState(persistence) {
129
159
  export {
130
160
  writtenFrontier,
131
161
  StorageError,
162
+ Refused,
132
163
  make,
133
164
  init,
134
165
  initForRun,
package/src/PgStorage.res CHANGED
@@ -270,11 +270,16 @@ let getEntityHistory = (~entityConfig: Internal.entityConfig): EntityHistory.pgE
270
270
  }
271
271
  }
272
272
 
273
+ let makeCreatePartitionQuery = (entityConfig: Internal.entityConfig, ~pgSchema, ~chainId) =>
274
+ `CREATE TABLE IF NOT EXISTS "${pgSchema}"."${partitionTableName(
275
+ ~entityConfig,
276
+ ~chainId,
277
+ )}" PARTITION OF "${pgSchema}"."${entityConfig.table.tableName}" FOR VALUES IN (${chainId->ChainId.toString});`
278
+
273
279
  // Every table an entity needs: its own, one partition per chain when it's
274
- // per-chain, and its history table. The chain set is fixed for the life of a
275
- // schema — changing it fails the resume compat check against `envio_info` and
276
- // forces a resync — so every partition the entity will ever need is created
277
- // here, at init.
280
+ // per-chain, and its history table. A chain the schema gains later gets its
281
+ // partitions from `addChain`, which the resume compat check allows only for an
282
+ // `envio start --chain` process naming it.
278
283
  //
279
284
  // History stays unpartitioned: it is only ever read by checkpoint, never by
280
285
  // chain, so partitioning it would route every write and prune nothing.
@@ -294,12 +299,7 @@ let makeCreateEntityTableQueries = (
294
299
  [
295
300
  entityConfig.table->createTable(~partitionByColumn=chainIdField->Table.getPgDbFieldName),
296
301
  ]->Array.concat(
297
- chainIds->Array.map(chainId =>
298
- `CREATE TABLE IF NOT EXISTS "${pgSchema}"."${partitionTableName(
299
- ~entityConfig,
300
- ~chainId,
301
- )}" PARTITION OF "${pgSchema}"."${entityConfig.table.tableName}" FOR VALUES IN (${chainId->ChainId.toString});`
302
- ),
302
+ chainIds->Array.map(chainId => entityConfig->makeCreatePartitionQuery(~pgSchema, ~chainId)),
303
303
  )
304
304
  }->Array.concat([getEntityHistory(~entityConfig).table->createTable])
305
305
  }
@@ -602,7 +602,7 @@ let makeInsertValuesSetQuery = (
602
602
  ~itemsCount,
603
603
  ~chainIdMode: ChainId.mode=Int32,
604
604
  ) => {
605
- let {quotedFieldNames, quotedNonPrimaryFieldNames} =
605
+ let {quotedFieldNames, quotedNonPrimaryFieldNames, booleanArrayColumnIndexes} =
606
606
  table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
607
607
 
608
608
  let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table)
@@ -620,6 +620,9 @@ let makeInsertValuesSetQuery = (
620
620
  placeholders := placeholders.contents ++ ","
621
621
  }
622
622
  placeholders := placeholders.contents ++ `$${(fieldIdx * itemsCount + idx)->Int.toString}`
623
+ if booleanArrayColumnIndexes->Array.includes(fieldIdx) {
624
+ placeholders := placeholders.contents ++ "::integer[]::boolean[]"
625
+ }
623
626
  }
624
627
  placeholders := placeholders.contents ++ ")"
625
628
  }
@@ -652,7 +655,7 @@ let makeTableBatchSetQuery = (
652
655
  ~itemSchema: S.t<'item>,
653
656
  ~chainIdMode: ChainId.mode=Int32,
654
657
  ) => {
655
- let {dbSchema, hasArrayField, byteaColumnIndexes} =
658
+ let {dbSchema, hasArrayField, byteaColumnIndexes, booleanArrayColumnIndexes} =
656
659
  table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
657
660
 
658
661
  // Should move this to a better place
@@ -713,9 +716,25 @@ let makeTableBatchSetQuery = (
713
716
  ),
714
717
  "convertOrThrow": S.compile(
715
718
  S.unnest(itemSchema)->S.preprocess(_ => {
716
- serializer: Utils.Array.flatten->(
717
- Utils.magic: (array<array<'a>> => array<'a>) => unknown => unknown
718
- ),
719
+ serializer: columns => {
720
+ let columns = columns->(Utils.magic: unknown => array<array<Nullable.t<array<bool>>>>)
721
+ booleanArrayColumnIndexes->Array.forEach(index =>
722
+ columns->Array.setUnsafe(
723
+ index,
724
+ columns
725
+ ->Array.getUnsafe(index)
726
+ ->Array.map(
727
+ value =>
728
+ value
729
+ ->Nullable.map(flags => flags->Array.map(flag => flag ? 1 : 0))
730
+ ->(Utils.magic: Nullable.t<array<int>> => Nullable.t<array<bool>>),
731
+ ),
732
+ )
733
+ )
734
+ columns
735
+ ->Utils.Array.flatten
736
+ ->(Utils.magic: array<Nullable.t<array<bool>>> => unknown)
737
+ },
719
738
  }),
720
739
  ~input=Value,
721
740
  ~output=Unknown,
@@ -2009,7 +2028,6 @@ let make = (
2009
2028
  cache,
2010
2029
  reorgCheckpoints: [],
2011
2030
  contractMapping,
2012
- envioInfo: Some(envioInfo),
2013
2031
  chains: chainConfigs->Array.mapWithIndex((
2014
2032
  chainConfig,
2015
2033
  idx,
@@ -2437,17 +2455,59 @@ let make = (
2437
2455
  }
2438
2456
  }
2439
2457
 
2458
+ let readStoredConfig = async (): ResumePlan.stored => {
2459
+ let (envioInfo, contractNames) = await Promise.all2((
2460
+ InternalTable.EnvioInfo.read(sql, ~pgSchema),
2461
+ InternalTable.EnvioContracts.read(sql, ~pgSchema),
2462
+ ))
2463
+ // Both tables join the schema in one transaction. A missing mapping means
2464
+ // an older envio wrote this schema, so treat the record as unreadable
2465
+ // rather than decoding address rows against ids nothing assigned.
2466
+ switch (envioInfo, contractNames) {
2467
+ | (Some(envioInfo), Some(contractNames)) => {
2468
+ envioInfo: Some(envioInfo),
2469
+ chains: await InternalTable.Chains.readStoredChains(sql, ~pgSchema),
2470
+ contractMapping: ContractMapping.fromStoredNames(contractNames),
2471
+ }
2472
+ | _ => {envioInfo: None, chains: [], contractMapping: ContractMapping.empty}
2473
+ }
2474
+ }
2475
+
2476
+ let addChain = async (~chainConfig: Config.chain, ~entities, ~contractMapping) => {
2477
+ let partitionQueries =
2478
+ entities
2479
+ ->Array.filter((entityConfig: Internal.entityConfig) =>
2480
+ entityConfig.storage.postgres && entityConfig.table->Table.getChainIdField->Option.isSome
2481
+ )
2482
+ ->Array.map(entityConfig =>
2483
+ entityConfig->makeCreatePartitionQuery(~pgSchema, ~chainId=chainConfig.id)
2484
+ )
2485
+ let addressRows = chainConfig->ChainState.configStorageRows(~ecosystem, ~contractMapping)
2486
+ // No conflict clauses: two processes adding the same chain are two
2487
+ // processes driving it, and the second one fails on a primary key instead
2488
+ // of indexing alongside the first.
2489
+ let _ = await sql->Postgres.beginSql(async sql => {
2490
+ for idx in 0 to partitionQueries->Array.length - 1 {
2491
+ await sql
2492
+ ->Postgres.unsafe(partitionQueries->Array.getUnsafe(idx))
2493
+ ->Utils.Promise.ignoreValue
2494
+ }
2495
+ if addressRows->Utils.Array.notEmpty {
2496
+ await InternalTable.EnvioAddresses.insert(sql, ~pgSchema, ~rows=addressRows, ~chainIdMode)
2497
+ }
2498
+ switch InternalTable.Chains.makeInitialValuesQuery(~pgSchema, ~chainConfigs=[chainConfig]) {
2499
+ | Some(query) => await sql->Postgres.unsafe(query)->Utils.Promise.ignoreValue
2500
+ | None => ()
2501
+ }
2502
+ })
2503
+ }
2504
+
2440
2505
  let resumeInitialState = async (
2441
2506
  ~entities,
2442
2507
  ~chainIds,
2443
- ~throwIfIncompatible,
2508
+ ~contractMapping,
2444
2509
  ): Persistence.initialState => {
2445
- let (
2446
- cache,
2447
- (chains, checkpointFrontier),
2448
- reorgCheckpoints,
2449
- (storedEnvioInfo, storedContractMapping),
2450
- ) = await Promise.all4((
2510
+ let (cache, (chains, checkpointFrontier), reorgCheckpoints) = await Promise.all3((
2451
2511
  restoreEffectCache(~withUpload=false),
2452
2512
  InternalTable.Chains.getInitialState(
2453
2513
  sql,
@@ -2491,22 +2551,8 @@ let make = (
2491
2551
  }>,
2492
2552
  >
2493
2553
  ),
2494
- Promise.all2((
2495
- InternalTable.EnvioInfo.read(sql, ~pgSchema),
2496
- InternalTable.EnvioContracts.read(sql, ~pgSchema),
2497
- ))->Promise.thenResolve(((info, names)) =>
2498
- // Both tables join the schema in one transaction. A missing mapping
2499
- // means an older envio wrote this schema, so treat the snapshot as
2500
- // unreadable rather than decoding address rows against ids nothing assigned.
2501
- switch (info, names) {
2502
- | (Some(info), Some(names)) => (Some(info), ContractMapping.fromStoredNames(names))
2503
- | _ => (None, ContractMapping.empty)
2504
- }
2505
- ),
2506
2554
  ))
2507
2555
 
2508
- throwIfIncompatible(~storedEnvioInfo, ~storedContractMapping)
2509
-
2510
2556
  await reloadIndexCatalog()
2511
2557
 
2512
2558
  // Convert string checkpoint IDs from DB to bigint
@@ -2529,8 +2575,7 @@ let make = (
2529
2575
  cache,
2530
2576
  chains,
2531
2577
  checkpointFrontier,
2532
- contractMapping: storedContractMapping,
2533
- envioInfo: storedEnvioInfo,
2578
+ contractMapping,
2534
2579
  }
2535
2580
  }
2536
2581
 
@@ -2687,6 +2732,8 @@ let make = (
2687
2732
  name: storageName,
2688
2733
  isInitialized,
2689
2734
  initialize,
2735
+ readStoredConfig,
2736
+ addChain,
2690
2737
  resumeInitialState,
2691
2738
  loadOrThrow,
2692
2739
  ensureQueryIndexes,