envio 3.15.0 → 3.16.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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -27,7 +27,7 @@ async function runOnce(state) {
27
27
  let persistence = IndexerState.persistence(state);
28
28
  let storage = Persistence.getInitializedStorageOrThrow(persistence);
29
29
  let readyAt = new Date();
30
- await storage.finalizeBackfill(persistence.allEntities, chainIds, readyAt);
30
+ await storage.finalizeBackfill(chainIds, readyAt);
31
31
  return IndexerState.markReady(state, readyAt);
32
32
  }
33
33
 
@@ -42,9 +42,8 @@ function run(state) {
42
42
  }
43
43
 
44
44
  function repairSchemaIndexes(state) {
45
- let persistence = IndexerState.persistence(state);
46
- let storage = Persistence.getInitializedStorageOrThrow(persistence);
47
- return storage.ensureSchemaIndexes(persistence.allEntities, CrossChainState.chainIds(IndexerState.crossChainState(state)));
45
+ let storage = Persistence.getInitializedStorageOrThrow(IndexerState.persistence(state));
46
+ return storage.ensureSchemaIndexes(CrossChainState.chainIds(IndexerState.crossChainState(state)));
48
47
  }
49
48
 
50
49
  export {
@@ -54,8 +54,8 @@ let registerContractHandlers = async (~logger, ~contractName, ~handler: option<s
54
54
  }
55
55
  }
56
56
 
57
- let getAutoLoadFiles = async (~config: Config.t) => {
58
- let srcPattern = `./${config.handlers}/**/*.{js,mjs,ts}`
57
+ let globAutoLoadFiles = async (~handlers) => {
58
+ let srcPattern = `./${handlers}/**/*.{js,mjs,ts}`
59
59
  try {
60
60
  let iterator = globIterator(srcPattern)
61
61
  let files = await iterator->Utils.Array.fromAsyncIterator
@@ -77,6 +77,14 @@ let getAutoLoadFiles = async (~config: Config.t) => {
77
77
  }
78
78
  }
79
79
 
80
+ let getAutoLoadFiles = async (~config: Config.t) =>
81
+ switch config.subgraph {
82
+ // A subgraph project's `src/` holds AssemblyScript mappings, not envio
83
+ // handlers: auto-loading or type-checking them as TypeScript would fail.
84
+ | Some(_) => []
85
+ | None => await globAutoLoadFiles(~handlers=config.handlers)
86
+ }
87
+
80
88
  let typeCheck = async (~config: Config.t, ~logger, ~autoLoadFiles) => {
81
89
  // A contract's `handler:` may also be an auto-loaded file.
82
90
  let files =
@@ -103,6 +111,16 @@ let typeCheck = async (~config: Config.t, ~logger, ~autoLoadFiles) => {
103
111
  // `HandlerRegister.finishRegistration`. This loads the user handler files
104
112
  // (populating the global `HandlerRegister` registry as a side effect) and
105
113
  // returns the resulting per-chain registrations.
114
+ // The subgraph runtime lives in this package but is never exported: it's
115
+ // reached only from here, when the resolved config carries a translated
116
+ // manifest.
117
+ let registerSubgraph: (
118
+ JSON.t,
119
+ ~isDev: bool,
120
+ ~logger: Logger.t,
121
+ ) => promise<unit> = %raw(`(subgraph, isDev, logger) =>
122
+ import("./subgraph/runtime.ts").then((m) => m.registerSubgraph({ ...subgraph, isDev }, logger))`)
123
+
106
124
  let registerAllHandlers = async (
107
125
  ~config: Config.t,
108
126
  ~logger,
@@ -111,21 +129,27 @@ let registerAllHandlers = async (
111
129
  HandlerRegister.startRegistration(~config, ~logger)
112
130
  registerTsHooks()
113
131
 
114
- let _ = await autoLoadFiles
115
- ->Array.map(file => {
116
- importHandler(file)->Promise.catch(exn => {
117
- let cause = exn->Utils.prettifyExn->Obj.magic
118
- logger->Logger.errorWithExn(`Failed to auto-load handler file: ${file}`, exn)
119
- JsError.throwWithMessage(`Failed to auto-load handler file: ${file}. Cause: ${cause}`)
132
+ switch config.subgraph {
133
+ // Registered after the TypeScript hooks, so the subgraph runtime's own hooks
134
+ // see a mapping first.
135
+ | Some(subgraph) => await registerSubgraph(subgraph, ~isDev=config.isDev, ~logger)
136
+ | None =>
137
+ let _ = await autoLoadFiles
138
+ ->Array.map(file => {
139
+ importHandler(file)->Promise.catch(exn => {
140
+ let cause = exn->Utils.prettifyExn->Obj.magic
141
+ logger->Logger.errorWithExn(`Failed to auto-load handler file: ${file}`, exn)
142
+ JsError.throwWithMessage(`Failed to auto-load handler file: ${file}. Cause: ${cause}`)
143
+ })
120
144
  })
121
- })
122
- ->Promise.all
145
+ ->Promise.all
123
146
 
124
- let _ = await config.contractHandlers
125
- ->Array.map(({name, handler}) => {
126
- registerContractHandlers(~logger, ~contractName=name, ~handler)
127
- })
128
- ->Promise.all
147
+ let _ = await config.contractHandlers
148
+ ->Array.map(({name, handler}) => {
149
+ registerContractHandlers(~logger, ~contractName=name, ~handler)
150
+ })
151
+ ->Promise.all
152
+ }
129
153
 
130
154
  HandlerRegister.finishRegistration(~config)
131
155
  }
@@ -58,8 +58,8 @@ async function registerContractHandlers(logger, contractName, handler) {
58
58
  }
59
59
  }
60
60
 
61
- async function getAutoLoadFiles(config) {
62
- let srcPattern = `./` + config.handlers + `/**/*.{js,mjs,ts}`;
61
+ async function globAutoLoadFiles(handlers) {
62
+ let srcPattern = `./` + handlers + `/**/*.{js,mjs,ts}`;
63
63
  try {
64
64
  let iterator = Promises.glob(srcPattern);
65
65
  let files = await Utils.$$Array.fromAsyncIterator(iterator);
@@ -70,6 +70,15 @@ async function getAutoLoadFiles(config) {
70
70
  }
71
71
  }
72
72
 
73
+ async function getAutoLoadFiles(config) {
74
+ let match = config.subgraph;
75
+ if (match !== undefined) {
76
+ return [];
77
+ } else {
78
+ return await globAutoLoadFiles(config.handlers);
79
+ }
80
+ }
81
+
73
82
  async function typeCheck(config, logger, autoLoadFiles) {
74
83
  let files = Array.from(new Set(autoLoadFiles.concat(Stdlib_Array.filterMap(config.contractHandlers, param => param.handler)).filter(isTypeScript).map(toAbsolutePath)));
75
84
  if (files.length === 0) {
@@ -85,15 +94,23 @@ async function typeCheck(config, logger, autoLoadFiles) {
85
94
  }
86
95
  }
87
96
 
97
+ let registerSubgraph = ((subgraph, isDev, logger) =>
98
+ import("./subgraph/runtime.ts").then((m) => m.registerSubgraph({ ...subgraph, isDev }, logger)));
99
+
88
100
  async function registerAllHandlers(config, logger, autoLoadFiles) {
89
101
  HandlerRegister.startRegistration(config, logger);
90
102
  registerTsHooks();
91
- await Promise.all(autoLoadFiles.map(file => Stdlib_Promise.$$catch(importHandler(file), exn => {
92
- let cause = Utils.prettifyExn(exn);
93
- Logger.errorWithExn(logger, `Failed to auto-load handler file: ` + file, exn);
94
- return Stdlib_JsError.throwWithMessage(`Failed to auto-load handler file: ` + file + `. Cause: ` + cause);
95
- })));
96
- await Promise.all(config.contractHandlers.map(param => registerContractHandlers(logger, param.name, param.handler)));
103
+ let subgraph = config.subgraph;
104
+ if (subgraph !== undefined) {
105
+ await registerSubgraph(subgraph, config.isDev, logger);
106
+ } else {
107
+ await Promise.all(autoLoadFiles.map(file => Stdlib_Promise.$$catch(importHandler(file), exn => {
108
+ let cause = Utils.prettifyExn(exn);
109
+ Logger.errorWithExn(logger, `Failed to auto-load handler file: ` + file, exn);
110
+ return Stdlib_JsError.throwWithMessage(`Failed to auto-load handler file: ` + file + `. Cause: ` + cause);
111
+ })));
112
+ await Promise.all(config.contractHandlers.map(param => registerContractHandlers(logger, param.name, param.handler)));
113
+ }
97
114
  return HandlerRegister.finishRegistration(config);
98
115
  }
99
116
 
@@ -104,8 +121,10 @@ export {
104
121
  toAbsolutePath,
105
122
  importHandler,
106
123
  registerContractHandlers,
124
+ globAutoLoadFiles,
107
125
  getAutoLoadFiles,
108
126
  typeCheck,
127
+ registerSubgraph,
109
128
  registerAllHandlers,
110
129
  }
111
130
  /* url Not a pure module */
@@ -699,6 +699,8 @@ let registerOnBlock = (
699
699
  ~where: unknown,
700
700
  ~fields: unknown,
701
701
  ~handler: Internal.onBlockArgs => promise<unit>,
702
+ ~runsFirst=false,
703
+ ~contractRegister=?,
702
704
  ~getChainsObject: Config.t => dict<unknown>,
703
705
  ) => {
704
706
  withRegistration(registration => {
@@ -792,6 +794,8 @@ let registerOnBlock = (
792
794
  interval: range._every,
793
795
  chainId,
794
796
  handler,
797
+ runsFirst,
798
+ ?contractRegister,
795
799
  blockFieldMask,
796
800
  }: Internal.onBlockRegistration
797
801
  ),
@@ -440,7 +440,8 @@ function extractRange(filter, name, ecosystem) {
440
440
  }
441
441
  }
442
442
 
443
- function registerOnBlock(name, where, fields, handler, getChainsObject) {
443
+ function registerOnBlock(name, where, fields, handler, runsFirstOpt, contractRegister, getChainsObject) {
444
+ let runsFirst = runsFirstOpt !== undefined ? runsFirstOpt : false;
444
445
  withRegistration(registration => {
445
446
  let config = registration.config;
446
447
  let ecosystem = config.ecosystem;
@@ -497,7 +498,9 @@ function registerOnBlock(name, where, fields, handler, getChainsObject) {
497
498
  endBlock: range._lte,
498
499
  interval: range._every,
499
500
  handler: handler,
500
- blockFieldMask: blockFieldMask
501
+ blockFieldMask: blockFieldMask,
502
+ runsFirst: runsFirst,
503
+ contractRegister: contractRegister
501
504
  });
502
505
  });
503
506
  if (!matchedAny.contents) {
@@ -46,5 +46,7 @@ let registerOnBlock: (
46
46
  ~where: unknown,
47
47
  ~fields: unknown,
48
48
  ~handler: Internal.onBlockArgs => promise<unit>,
49
+ ~runsFirst: bool=?,
50
+ ~contractRegister: Internal.onBlockContractRegisterArgs => promise<unit>=?,
49
51
  ~getChainsObject: Config.t => dict<unknown>,
50
52
  ) => unit
@@ -22,6 +22,8 @@ module Entity = {
22
22
  }
23
23
 
24
24
  type t = {
25
+ // Keys every id dict and Set below; see Table.table.
26
+ entityIdToKey: EntityId.t => string,
25
27
  latestEntityChangeById: dict<Change.t<Internal.entity>>,
26
28
  // Recorded changes (new latest ids + prevEntityChanges pushes), tracked
27
29
  // manually so InMemoryStore can gauge size without scanning every dict.
@@ -45,12 +47,9 @@ module Entity = {
45
47
  mutable hasIndexes: bool,
46
48
  }
47
49
 
48
- // Helper to extract an entity's id as a dict key. The raw id may be a
49
- // string/int/bigint, so it's stringified to a stable key for in-memory
50
- // indexing.
51
- let getEntityIdUnsafe = (entity: Internal.entity): string =>
50
+ let getEntityKeyUnsafe = (self: t, entity: Internal.entity): string =>
52
51
  switch (entity->(Utils.magic: Internal.entity => {"id": option<EntityId.t>}))["id"] {
53
- | Some(id) => id->EntityId.toKey
52
+ | Some(id) => self.entityIdToKey(id)
54
53
  | None => JsError.throwWithMessage("Property 'id' does not exist on expected entity object")
55
54
  }
56
55
 
@@ -62,7 +61,8 @@ module Entity = {
62
61
  }
63
62
  }
64
63
 
65
- let make = (): t => {
64
+ let make = (~entityIdToKey): t => {
65
+ entityIdToKey,
66
66
  latestEntityChangeById: Dict.make(),
67
67
  changesCount: 0.,
68
68
  dirtyIds: Utils.Set.make(),
@@ -141,10 +141,8 @@ module Entity = {
141
141
  self.hasIndexes = false
142
142
  }
143
143
 
144
- let updateIndexes = (self: t, ~entity: Internal.entity) =>
144
+ let updateIndexes = (self: t, ~entityId: string, ~entity: Internal.entity) =>
145
145
  if self.hasIndexes {
146
- let entityId = entity->getEntityIdUnsafe
147
-
148
146
  switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
149
147
  | None => ()
150
148
  | Some(indexes) =>
@@ -185,7 +183,7 @@ module Entity = {
185
183
  }
186
184
 
187
185
  let set = (inMemTable: t, ~committedCheckpointId, change: Change.t<Internal.entity>) => {
188
- let entityKey = change->Change.getEntityId->EntityId.toKey
186
+ let entityKey = inMemTable.entityIdToKey(change->Change.getEntityId)
189
187
  switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityKey) {
190
188
  | Some(prev) =>
191
189
  let prevCheckpointId = prev->Change.getCheckpointId
@@ -200,8 +198,8 @@ module Entity = {
200
198
  }
201
199
 
202
200
  switch change {
203
- | Set({entity}) => inMemTable->updateIndexes(~entity)
204
- | Delete({entityId}) => inMemTable->deleteEntityFromIndexes(~entityId=entityId->EntityId.toKey)
201
+ | Set({entity}) => inMemTable->updateIndexes(~entityId=entityKey, ~entity)
202
+ | Delete(_) => inMemTable->deleteEntityFromIndexes(~entityId=entityKey)
205
203
  }
206
204
  inMemTable.latestEntityChangeById->Dict.set(entityKey, change)
207
205
  if change->Change.getCheckpointId > committedCheckpointId {
@@ -214,11 +212,14 @@ module Entity = {
214
212
  let initValue = (
215
213
  inMemTable: t,
216
214
  ~committedCheckpointId,
217
- ~key: string,
215
+ ~entityId: EntityId.t,
218
216
  ~entity: option<Internal.entity>,
219
217
  ) =>
220
- if inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(key)->Option.isNone {
221
- let entityId = key->EntityId.unsafeOfString
218
+ if (
219
+ inMemTable.latestEntityChangeById
220
+ ->Utils.Dict.dangerouslyGetNonOption(inMemTable.entityIdToKey(entityId))
221
+ ->Option.isNone
222
+ ) {
222
223
  let change: Change.t<Internal.entity> = switch entity {
223
224
  | Some(entity) => Set({entityId, entity, checkpointId: Internal.loadedFromDbCheckpointId})
224
225
  | None => Delete({entityId, checkpointId: Internal.loadedFromDbCheckpointId})
@@ -312,7 +313,7 @@ module Entity = {
312
313
  switch change->mapChangeToEntity {
313
314
  | Some(entity) =>
314
315
  if index.matcher(entity) {
315
- inMemTable->addToIndex(~index, ~entityId=entity->getEntityIdUnsafe)
316
+ inMemTable->addToIndex(~index, ~entityId=inMemTable->getEntityKeyUnsafe(entity))
316
317
  }
317
318
  | None => ()
318
319
  }
@@ -1,7 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as EntityId from "./EntityId.res.mjs";
5
4
  import * as Internal from "./Internal.res.mjs";
6
5
  import * as EntityFilter from "./db/EntityFilter.res.mjs";
7
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -9,10 +8,10 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
9
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11
10
 
12
- function getEntityIdUnsafe(entity) {
11
+ function getEntityKeyUnsafe(self, entity) {
13
12
  let id = entity.id;
14
13
  if (id !== undefined) {
15
- return EntityId.toKey(Primitive_option.valFromOption(id));
14
+ return self.entityIdToKey(Primitive_option.valFromOption(id));
16
15
  } else {
17
16
  return Stdlib_JsError.throwWithMessage("Property 'id' does not exist on expected entity object");
18
17
  }
@@ -28,8 +27,9 @@ function addToIndex(self, index, entityId) {
28
27
  }
29
28
  }
30
29
 
31
- function make() {
30
+ function make(entityIdToKey) {
32
31
  return {
32
+ entityIdToKey: entityIdToKey,
33
33
  latestEntityChangeById: {},
34
34
  changesCount: 0,
35
35
  dirtyIds: new Set(),
@@ -99,11 +99,10 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
99
99
  self.hasIndexes = false;
100
100
  }
101
101
 
102
- function updateIndexes(self, entity) {
102
+ function updateIndexes(self, entityId, entity) {
103
103
  if (!self.hasIndexes) {
104
104
  return;
105
105
  }
106
- let entityId = getEntityIdUnsafe(entity);
107
106
  let indexes = self.indexesByEntityId[entityId];
108
107
  if (indexes !== undefined) {
109
108
  let indexes$1 = Primitive_option.valFromOption(indexes);
@@ -144,7 +143,7 @@ function deleteEntityFromIndexes(self, entityId) {
144
143
  }
145
144
 
146
145
  function set(inMemTable, committedCheckpointId, change) {
147
- let entityKey = EntityId.toKey(change.entityId);
146
+ let entityKey = inMemTable.entityIdToKey(change.entityId);
148
147
  let prev = inMemTable.latestEntityChangeById[entityKey];
149
148
  if (prev !== undefined) {
150
149
  let prevCheckpointId = prev.checkpointId;
@@ -156,9 +155,9 @@ function set(inMemTable, committedCheckpointId, change) {
156
155
  inMemTable.changesCount = inMemTable.changesCount + 1;
157
156
  }
158
157
  if (change.type === "SET") {
159
- updateIndexes(inMemTable, change.entity);
158
+ updateIndexes(inMemTable, entityKey, change.entity);
160
159
  } else {
161
- deleteEntityFromIndexes(inMemTable, EntityId.toKey(change.entityId));
160
+ deleteEntityFromIndexes(inMemTable, entityKey);
162
161
  }
163
162
  inMemTable.latestEntityChangeById[entityKey] = change;
164
163
  if (change.checkpointId > committedCheckpointId) {
@@ -167,18 +166,18 @@ function set(inMemTable, committedCheckpointId, change) {
167
166
  }
168
167
  }
169
168
 
170
- function initValue(inMemTable, committedCheckpointId, key, entity) {
171
- if (!Stdlib_Option.isNone(inMemTable.latestEntityChangeById[key])) {
169
+ function initValue(inMemTable, committedCheckpointId, entityId, entity) {
170
+ if (!Stdlib_Option.isNone(inMemTable.latestEntityChangeById[inMemTable.entityIdToKey(entityId)])) {
172
171
  return;
173
172
  }
174
173
  let change = entity !== undefined ? ({
175
174
  type: "SET",
176
- entityId: key,
175
+ entityId: entityId,
177
176
  entity: entity,
178
177
  checkpointId: Internal.loadedFromDbCheckpointId
179
178
  }) : ({
180
179
  type: "DELETE",
181
- entityId: key,
180
+ entityId: entityId,
182
181
  checkpointId: Internal.loadedFromDbCheckpointId
183
182
  });
184
183
  set(inMemTable, committedCheckpointId, change);
@@ -285,7 +284,7 @@ function addEmptyIndex(inMemTable, filter, table) {
285
284
  Utils.Dict.forEach(inMemTable.latestEntityChangeById, change => {
286
285
  let entity = mapChangeToEntity(change);
287
286
  if (entity !== undefined && index_matcher(entity)) {
288
- return addToIndex(inMemTable, index, getEntityIdUnsafe(entity));
287
+ return addToIndex(inMemTable, index, getEntityKeyUnsafe(inMemTable, entity));
289
288
  }
290
289
  });
291
290
  }
@@ -321,7 +320,7 @@ function tryIndexFromLoadedValues(inMemTable, filter, table) {
321
320
  }
322
321
 
323
322
  let Entity = {
324
- getEntityIdUnsafe: getEntityIdUnsafe,
323
+ getEntityKeyUnsafe: getEntityKeyUnsafe,
325
324
  addToIndex: addToIndex,
326
325
  make: make,
327
326
  snapshotChanges: snapshotChanges,
@@ -108,6 +108,12 @@ type t = {
108
108
  // in-process test runner injects a callback that resolves its run promise
109
109
  // instead, so a caught-up run doesn't kill the test process.
110
110
  onExit: option<unit => unit>,
111
+ // Told, in the processing pass that gets it there, that a run holding its
112
+ // realtime transitions has gone as far as it can on its own. The test
113
+ // runner's stand-in supervisor releases from it, so the release lands at a
114
+ // point in the indexer's own progress rather than on a timer.
115
+ onArrivedAtHead: option<unit => unit>,
116
+ mutable reportedArrivalAtHead: bool,
111
117
  // Set once on any fatal error. Every loop checks it to stop iterating and
112
118
  // every launch skips when it's set, so a single failure quiesces the indexer.
113
119
  mutable isStopped: bool,
@@ -157,10 +163,13 @@ let make = (
157
163
  ~exitAfterFirstEventBlock=false,
158
164
  ~onError: ErrorHandling.t => unit,
159
165
  ~onExit=?,
166
+ ~onArrivedAtHead=?,
167
+ ~clock=Clock.real,
160
168
  ) => {
161
169
  let chainMetaThrottler = {
162
170
  let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis
163
171
  Throttler.make(
172
+ ~clock,
164
173
  ~intervalMillis,
165
174
  ~logger=logger->Logger.withFields({
166
175
  "context": "Throttler for chain metadata writes",
@@ -175,7 +184,7 @@ let make = (
175
184
  persistence,
176
185
  allEntities: persistence.allEntities,
177
186
  entities: EntityTables.make(persistence.allEntities->EntityTables.crossChain),
178
- effectState: EffectState.make(),
187
+ effectState: EffectState.make(~clock, ()),
179
188
  rollback: None,
180
189
  committedFrontier,
181
190
  processedFrontier: committedFrontier->Frontier.copy,
@@ -205,6 +214,8 @@ let make = (
205
214
  exitAfterFirstEventBlock,
206
215
  onError,
207
216
  onExit,
217
+ onArrivedAtHead,
218
+ reportedArrivalAtHead: false,
208
219
  isStopped: false,
209
220
  epoch: 0,
210
221
  simulateDeadInputTracker: SimulateDeadInputTracker.makeFromConfig(config),
@@ -225,12 +236,13 @@ let make = (
225
236
  }
226
237
 
227
238
  // Check if progress is past the reorg threshold (safe block).
228
- // A chain is in reorg threshold when progressBlockNumber > sourceBlockNumber - maxReorgDepth.
229
- // This matches the logic in InternalTable.Checkpoints.makeGetReorgCheckpointsQuery.
239
+ // A chain is in reorg threshold once its progress passes the safe block,
240
+ // sourceBlockNumber - maxReorgDepth - 1. This matches the reorg checkpoints
241
+ // query in packages/cli/src/postgres/internal.rs.
230
242
  let isProgressInReorgThreshold = (~progressBlockNumber, ~sourceBlockNumber, ~maxReorgDepth) => {
231
243
  maxReorgDepth > 0 &&
232
244
  sourceBlockNumber > 0 &&
233
- progressBlockNumber > sourceBlockNumber - maxReorgDepth
245
+ progressBlockNumber > sourceBlockNumber - maxReorgDepth - 1
234
246
  }
235
247
 
236
248
  let makeFromDbState = (
@@ -249,6 +261,7 @@ let makeFromDbState = (
249
261
  ~holdRealtime=false,
250
262
  ~onError,
251
263
  ~onExit=?,
264
+ ~onArrivedAtHead=?,
252
265
  ) => {
253
266
  let isInReorgThreshold = if initialState.cleanRun {
254
267
  false
@@ -303,6 +316,8 @@ let makeFromDbState = (
303
316
  ~exitAfterFirstEventBlock,
304
317
  ~onError,
305
318
  ~onExit?,
319
+ ~onArrivedAtHead?,
320
+ ~clock=?sourceTiming->Option.flatMap(timing => timing.clock),
306
321
  )
307
322
  state.crossChainState->CrossChainState.markCaughtUpOnResume
308
323
  initialState.cache->Utils.Dict.forEach(({effectName, count, scope}) => {
@@ -559,6 +574,15 @@ let isHoldingRealtime = (state: t) => state.crossChainState->CrossChainState.isH
559
574
 
560
575
  let hasArrivedAtHead = (state: t) => state.crossChainState->CrossChainState.hasArrivedAtHead
561
576
 
577
+ let reportArrivalAtHead = (state: t) =>
578
+ switch state.onArrivedAtHead {
579
+ | Some(onArrivedAtHead)
580
+ if !state.reportedArrivalAtHead && state->isHoldingRealtime && state->hasArrivedAtHead =>
581
+ state.reportedArrivalAtHead = true
582
+ onArrivedAtHead()
583
+ | _ => ()
584
+ }
585
+
562
586
  let releaseRealtime = (state: t) => {
563
587
  state.crossChainState->CrossChainState.releaseRealtime
564
588
  // Every chain is parked at the head with no batch coming, so nothing would
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Env from "./Env.res.mjs";
4
4
  import * as Batch from "./Batch.res.mjs";
5
+ import * as Clock from "./Clock.res.mjs";
5
6
  import * as Utils from "./Utils.res.mjs";
6
7
  import * as Config from "./Config.res.mjs";
7
8
  import * as Logger from "./Logger.res.mjs";
@@ -26,25 +27,26 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
26
27
  import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
27
28
  import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
28
29
 
29
- function make(config, logger, persistence, chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
30
+ function make(config, logger, persistence, chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit, onArrivedAtHead, clockOpt) {
30
31
  let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
31
32
  let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
32
33
  let committedFrontier = committedFrontierOpt !== undefined ? committedFrontierOpt : Frontier.empty();
33
34
  let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
34
35
  let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
35
36
  let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
37
+ let clock = clockOpt !== undefined ? clockOpt : Clock.real;
36
38
  let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis;
37
39
  let chainMetaThrottler = Throttler.make(intervalMillis, Logger.withFields(logger, {
38
40
  context: "Throttler for chain metadata writes",
39
41
  intervalMillis: intervalMillis
40
- }));
42
+ }), clock);
41
43
  return {
42
44
  config: config,
43
45
  logger: logger,
44
46
  persistence: persistence,
45
47
  allEntities: persistence.allEntities,
46
48
  entities: EntityTables.make(EntityTables.crossChain(persistence.allEntities)),
47
- effectState: EffectState.make(),
49
+ effectState: EffectState.make(clock, undefined),
48
50
  rollback: undefined,
49
51
  committedFrontier: committedFrontier,
50
52
  processedFrontier: Frontier.copy(committedFrontier),
@@ -68,6 +70,8 @@ function make(config, logger, persistence, chainStates, isRealtime, targetBuffer
68
70
  exitAfterFirstEventBlock: exitAfterFirstEventBlock,
69
71
  onError: onError,
70
72
  onExit: onExit,
73
+ onArrivedAtHead: onArrivedAtHead,
74
+ reportedArrivalAtHead: false,
71
75
  isStopped: false,
72
76
  epoch: 0,
73
77
  scheduleProcessing: () => {},
@@ -88,7 +92,7 @@ function make(config, logger, persistence, chainStates, isRealtime, targetBuffer
88
92
  };
89
93
  }
90
94
 
91
- function makeFromDbState(config, logger, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, sourceTiming, targetBufferSizeOpt, holdRealtimeOpt, onError, onExit) {
95
+ function makeFromDbState(config, logger, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, sourceTiming, targetBufferSizeOpt, holdRealtimeOpt, onError, onExit, onArrivedAtHead) {
92
96
  let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
93
97
  let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
94
98
  let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
@@ -99,7 +103,7 @@ function makeFromDbState(config, logger, persistence, initialState, registration
99
103
  let sourceBlockNumber = resumedChainState.sourceBlockNumber;
100
104
  let maxReorgDepth = resumedChainState.maxReorgDepth;
101
105
  if (maxReorgDepth > 0 && sourceBlockNumber > 0) {
102
- return progressBlockNumber > (sourceBlockNumber - maxReorgDepth | 0);
106
+ return progressBlockNumber > ((sourceBlockNumber - maxReorgDepth | 0) - 1 | 0);
103
107
  } else {
104
108
  return false;
105
109
  }
@@ -111,7 +115,7 @@ function makeFromDbState(config, logger, persistence, initialState, registration
111
115
  let chainConfig = ChainMap.get(config.chainMap, chainId);
112
116
  chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, initialState.contractMapping, registrationsByChainId, logger, sourceTiming);
113
117
  });
114
- let state = make(config, logger, persistence, chainStates, isRealtime, targetBufferSize, holdRealtime, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
118
+ let state = make(config, logger, persistence, chainStates, isRealtime, targetBufferSize, holdRealtime, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit, onArrivedAtHead, Stdlib_Option.flatMap(sourceTiming, timing => timing.clock));
115
119
  CrossChainState.markCaughtUpOnResume(state.crossChainState);
116
120
  Utils.Dict.forEach(initialState.cache, param => {
117
121
  let count = param.count;
@@ -401,6 +405,14 @@ function hasArrivedAtHead(state) {
401
405
  return CrossChainState.hasArrivedAtHead(state.crossChainState);
402
406
  }
403
407
 
408
+ function reportArrivalAtHead(state) {
409
+ let onArrivedAtHead = state.onArrivedAtHead;
410
+ if (onArrivedAtHead !== undefined && !state.reportedArrivalAtHead && CrossChainState.isHoldingRealtime(state.crossChainState) && CrossChainState.hasArrivedAtHead(state.crossChainState)) {
411
+ state.reportedArrivalAtHead = true;
412
+ return onArrivedAtHead();
413
+ }
414
+ }
415
+
404
416
  function releaseRealtime(state) {
405
417
  CrossChainState.releaseRealtime(state.crossChainState);
406
418
  state.scheduleProcessing();
@@ -917,6 +929,7 @@ export {
917
929
  bindScheduleProcessing,
918
930
  isHoldingRealtime,
919
931
  hasArrivedAtHead,
932
+ reportArrivalAtHead,
920
933
  releaseRealtime,
921
934
  markReady,
922
935
  reportFinished,
@@ -24,6 +24,8 @@ let make: (
24
24
  ~exitAfterFirstEventBlock: bool=?,
25
25
  ~onError: ErrorHandling.t => unit,
26
26
  ~onExit: unit => unit=?,
27
+ ~onArrivedAtHead: unit => unit=?,
28
+ ~clock: Clock.t=?,
27
29
  ) => t
28
30
 
29
31
  let makeFromDbState: (
@@ -40,6 +42,7 @@ let makeFromDbState: (
40
42
  ~holdRealtime: bool=?,
41
43
  ~onError: ErrorHandling.t => unit,
42
44
  ~onExit: unit => unit=?,
45
+ ~onArrivedAtHead: unit => unit=?,
43
46
  ) => t
44
47
 
45
48
  let unexpectedErrorMsg: string
@@ -106,6 +109,7 @@ let shouldFinalizeIndexes: t => bool
106
109
  let bindScheduleProcessing: (t, unit => unit) => unit
107
110
  let isHoldingRealtime: t => bool
108
111
  let hasArrivedAtHead: t => bool
112
+ let reportArrivalAtHead: t => unit
109
113
  // The supervisor's go-ahead for a process driving part of a split run.
110
114
  let releaseRealtime: t => unit
111
115
  let markReady: (t, ~readyAt: Date.t) => unit