envio 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/evm.schema.json +83 -11
  2. package/fuel.schema.json +83 -11
  3. package/index.d.ts +184 -3
  4. package/package.json +6 -6
  5. package/src/Batch.res +2 -2
  6. package/src/ChainFetcher.res +27 -3
  7. package/src/ChainFetcher.res.mjs +17 -3
  8. package/src/ChainManager.res +163 -0
  9. package/src/ChainManager.res.mjs +136 -0
  10. package/src/Config.res +213 -30
  11. package/src/Config.res.mjs +102 -41
  12. package/src/Core.res +16 -10
  13. package/src/Ecosystem.res +0 -3
  14. package/src/Env.res +2 -2
  15. package/src/Env.res.mjs +2 -2
  16. package/src/Envio.res +101 -2
  17. package/src/Envio.res.mjs +2 -3
  18. package/src/EventConfigBuilder.res +52 -0
  19. package/src/EventConfigBuilder.res.mjs +32 -0
  20. package/src/EventUtils.res +2 -2
  21. package/src/FetchState.res +23 -14
  22. package/src/FetchState.res.mjs +21 -15
  23. package/src/GlobalState.res +219 -363
  24. package/src/GlobalState.res.mjs +314 -491
  25. package/src/GlobalStateManager.res +49 -59
  26. package/src/GlobalStateManager.res.mjs +5 -4
  27. package/src/GlobalStateManager.resi +1 -1
  28. package/src/HandlerLoader.res +12 -1
  29. package/src/HandlerLoader.res.mjs +6 -1
  30. package/src/HandlerRegister.res +9 -9
  31. package/src/HandlerRegister.res.mjs +9 -9
  32. package/src/Hasura.res +102 -32
  33. package/src/Hasura.res.mjs +88 -34
  34. package/src/InMemoryStore.res +10 -1
  35. package/src/InMemoryStore.res.mjs +4 -1
  36. package/src/InMemoryTable.res +83 -136
  37. package/src/InMemoryTable.res.mjs +57 -86
  38. package/src/Internal.res +54 -5
  39. package/src/Internal.res.mjs +2 -8
  40. package/src/LazyLoader.res +2 -2
  41. package/src/LazyLoader.res.mjs +3 -3
  42. package/src/LoadLayer.res +47 -60
  43. package/src/LoadLayer.res.mjs +28 -50
  44. package/src/LoadLayer.resi +2 -5
  45. package/src/LogSelection.res +4 -4
  46. package/src/LogSelection.res.mjs +5 -7
  47. package/src/Logging.res +1 -1
  48. package/src/Main.res +61 -2
  49. package/src/Main.res.mjs +37 -1
  50. package/src/Persistence.res +3 -16
  51. package/src/PgStorage.res +125 -114
  52. package/src/PgStorage.res.mjs +112 -95
  53. package/src/Ports.res +5 -0
  54. package/src/Ports.res.mjs +9 -0
  55. package/src/Prometheus.res +3 -3
  56. package/src/Prometheus.res.mjs +4 -4
  57. package/src/ReorgDetection.res +4 -4
  58. package/src/ReorgDetection.res.mjs +4 -5
  59. package/src/SafeCheckpointTracking.res +16 -16
  60. package/src/SafeCheckpointTracking.res.mjs +2 -2
  61. package/src/SimulateItems.res +10 -14
  62. package/src/SimulateItems.res.mjs +5 -2
  63. package/src/Sink.res +1 -1
  64. package/src/Sink.res.mjs +1 -2
  65. package/src/SvmTypes.res +9 -0
  66. package/src/SvmTypes.res.mjs +14 -0
  67. package/src/TestIndexer.res +17 -57
  68. package/src/TestIndexer.res.mjs +14 -48
  69. package/src/TestIndexerProxyStorage.res +23 -23
  70. package/src/TestIndexerProxyStorage.res.mjs +12 -15
  71. package/src/Throttler.res +2 -2
  72. package/src/Time.res +2 -2
  73. package/src/Time.res.mjs +2 -2
  74. package/src/UserContext.res +19 -118
  75. package/src/UserContext.res.mjs +10 -66
  76. package/src/Utils.res +15 -15
  77. package/src/Utils.res.mjs +7 -8
  78. package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
  79. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
  80. package/src/bindings/BigDecimal.res +1 -1
  81. package/src/bindings/BigDecimal.res.mjs +2 -2
  82. package/src/bindings/ClickHouse.res +8 -6
  83. package/src/bindings/ClickHouse.res.mjs +5 -5
  84. package/src/bindings/Hrtime.res +1 -1
  85. package/src/bindings/Pino.res +2 -2
  86. package/src/bindings/Pino.res.mjs +3 -4
  87. package/src/db/EntityFilter.res +410 -0
  88. package/src/db/EntityFilter.res.mjs +424 -0
  89. package/src/db/EntityHistory.res +1 -1
  90. package/src/db/EntityHistory.res.mjs +1 -1
  91. package/src/db/InternalTable.res +10 -10
  92. package/src/db/InternalTable.res.mjs +41 -45
  93. package/src/db/Schema.res +2 -2
  94. package/src/db/Schema.res.mjs +3 -3
  95. package/src/db/Table.res +106 -22
  96. package/src/db/Table.res.mjs +84 -35
  97. package/src/sources/EventRouter.res +67 -2
  98. package/src/sources/EventRouter.res.mjs +45 -3
  99. package/src/sources/Evm.res +0 -7
  100. package/src/sources/Evm.res.mjs +0 -15
  101. package/src/sources/EvmChain.res +1 -1
  102. package/src/sources/EvmChain.res.mjs +1 -2
  103. package/src/sources/EvmRpcClient.res +42 -0
  104. package/src/sources/EvmRpcClient.res.mjs +64 -0
  105. package/src/sources/Fuel.res +0 -7
  106. package/src/sources/Fuel.res.mjs +0 -15
  107. package/src/sources/HyperFuelSource.res +5 -4
  108. package/src/sources/HyperFuelSource.res.mjs +2 -2
  109. package/src/sources/HyperSyncClient.res +9 -5
  110. package/src/sources/HyperSyncClient.res.mjs +2 -2
  111. package/src/sources/HyperSyncHeightStream.res +2 -2
  112. package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
  113. package/src/sources/HyperSyncSource.res +10 -9
  114. package/src/sources/HyperSyncSource.res.mjs +4 -4
  115. package/src/sources/Rpc.res +1 -5
  116. package/src/sources/Rpc.res.mjs +1 -9
  117. package/src/sources/RpcSource.res +57 -21
  118. package/src/sources/RpcSource.res.mjs +47 -20
  119. package/src/sources/RpcWebSocketHeightStream.res +1 -1
  120. package/src/sources/SourceManager.res +3 -2
  121. package/src/sources/SourceManager.res.mjs +1 -1
  122. package/src/sources/Svm.res +3 -10
  123. package/src/sources/Svm.res.mjs +4 -18
  124. package/src/sources/SvmHyperSyncClient.res +265 -0
  125. package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
  126. package/src/sources/SvmHyperSyncSource.res +638 -0
  127. package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
  128. package/src/tui/Tui.res +9 -2
  129. package/src/tui/Tui.res.mjs +18 -3
  130. package/src/tui/components/BufferedProgressBar.res +2 -2
  131. package/src/tui/components/TuiData.res +3 -0
  132. package/svm.schema.json +523 -14
  133. package/src/TableIndices.res +0 -115
  134. package/src/TableIndices.res.mjs +0 -144
package/evm.schema.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "minimum": 0
40
40
  },
41
41
  "storage": {
42
- "description": "Configuration for the storage backends the indexer writes to. Defaults to `postgres: true` when omitted. ClickHouse requires Postgres to be enabled (it is not supported as a single storage yet), and at least one backend must be enabled.",
42
+ "description": "Storage backends the indexer writes data to. Defaults to Postgres when omitted. Set `clickhouse: true` to additionally sync the indexed data to ClickHouse. Mark a backend with `default: true` to store entities that don't have an @storage directive in the schema, e.g. `clickhouse: {default: true}`.",
43
43
  "anyOf": [
44
44
  {
45
45
  "$ref": "#/$defs/StorageConfig"
@@ -131,17 +131,75 @@
131
131
  "type": "object",
132
132
  "properties": {
133
133
  "postgres": {
134
- "description": "Whether to use Postgres as a storage backend (default: true).",
135
- "type": [
136
- "boolean",
137
- "null"
134
+ "description": "Whether to use Postgres as a storage backend (default: true). Accepts a boolean or an options object (the object form implies the backend is enabled).",
135
+ "anyOf": [
136
+ {
137
+ "type": [
138
+ "boolean",
139
+ "null"
140
+ ]
141
+ },
142
+ {
143
+ "type": "object",
144
+ "properties": {
145
+ "default": {
146
+ "description": "Whether entities without an @storage directive are stored in this backend (default: true when Postgres is the only enabled backend, false otherwise).",
147
+ "type": [
148
+ "boolean",
149
+ "null"
150
+ ]
151
+ },
152
+ "column_name_format": {
153
+ "description": "How entity fields are reflected in the storage column names. `original` keeps the schema.graphql field names as is, `snake_case` converts them to snake_case in the database while keeping the original casing in the exposed APIs. (default: original)",
154
+ "type": [
155
+ "string",
156
+ "null"
157
+ ],
158
+ "enum": [
159
+ "original",
160
+ "snake_case",
161
+ null
162
+ ]
163
+ }
164
+ },
165
+ "additionalProperties": false
166
+ }
138
167
  ]
139
168
  },
140
169
  "clickhouse": {
141
- "description": "Whether to additionally sync the indexed data to ClickHouse. Requires Postgres to be enabled (default: false).",
142
- "type": [
143
- "boolean",
144
- "null"
170
+ "description": "Whether to additionally sync the indexed data to ClickHouse. Requires Postgres to be enabled (default: false). Accepts a boolean or an options object (the object form implies the backend is enabled).",
171
+ "anyOf": [
172
+ {
173
+ "type": [
174
+ "boolean",
175
+ "null"
176
+ ]
177
+ },
178
+ {
179
+ "type": "object",
180
+ "properties": {
181
+ "default": {
182
+ "description": "Whether entities without an @storage directive are stored in this backend (default: false).",
183
+ "type": [
184
+ "boolean",
185
+ "null"
186
+ ]
187
+ },
188
+ "column_name_format": {
189
+ "description": "How entity fields are reflected in the storage column names. `original` keeps the schema.graphql field names as is, `snake_case` converts them to snake_case in the database while keeping the original casing in the exposed APIs. (default: original)",
190
+ "type": [
191
+ "string",
192
+ "null"
193
+ ],
194
+ "enum": [
195
+ "original",
196
+ "snake_case",
197
+ null
198
+ ]
199
+ }
200
+ },
201
+ "additionalProperties": false
202
+ }
145
203
  ]
146
204
  }
147
205
  },
@@ -163,7 +221,14 @@
163
221
  "if": {
164
222
  "properties": {
165
223
  "clickhouse": {
166
- "const": true
224
+ "anyOf": [
225
+ {
226
+ "const": true
227
+ },
228
+ {
229
+ "type": "object"
230
+ }
231
+ ]
167
232
  }
168
233
  },
169
234
  "required": [
@@ -173,7 +238,14 @@
173
238
  "then": {
174
239
  "properties": {
175
240
  "postgres": {
176
- "const": true
241
+ "anyOf": [
242
+ {
243
+ "const": true
244
+ },
245
+ {
246
+ "type": "object"
247
+ }
248
+ ]
177
249
  }
178
250
  },
179
251
  "required": [
package/fuel.schema.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "minimum": 0
40
40
  },
41
41
  "storage": {
42
- "description": "Configuration for the storage backends the indexer writes to. Defaults to `postgres: true` when omitted. ClickHouse requires Postgres to be enabled (it is not supported as a single storage yet), and at least one backend must be enabled.",
42
+ "description": "Storage backends the indexer writes data to. Defaults to Postgres when omitted. Set `clickhouse: true` to additionally sync the indexed data to ClickHouse. Mark a backend with `default: true` to store entities that don't have an @storage directive in the schema, e.g. `clickhouse: {default: true}`.",
43
43
  "anyOf": [
44
44
  {
45
45
  "$ref": "#/$defs/StorageConfig"
@@ -89,17 +89,75 @@
89
89
  "type": "object",
90
90
  "properties": {
91
91
  "postgres": {
92
- "description": "Whether to use Postgres as a storage backend (default: true).",
93
- "type": [
94
- "boolean",
95
- "null"
92
+ "description": "Whether to use Postgres as a storage backend (default: true). Accepts a boolean or an options object (the object form implies the backend is enabled).",
93
+ "anyOf": [
94
+ {
95
+ "type": [
96
+ "boolean",
97
+ "null"
98
+ ]
99
+ },
100
+ {
101
+ "type": "object",
102
+ "properties": {
103
+ "default": {
104
+ "description": "Whether entities without an @storage directive are stored in this backend (default: true when Postgres is the only enabled backend, false otherwise).",
105
+ "type": [
106
+ "boolean",
107
+ "null"
108
+ ]
109
+ },
110
+ "column_name_format": {
111
+ "description": "How entity fields are reflected in the storage column names. `original` keeps the schema.graphql field names as is, `snake_case` converts them to snake_case in the database while keeping the original casing in the exposed APIs. (default: original)",
112
+ "type": [
113
+ "string",
114
+ "null"
115
+ ],
116
+ "enum": [
117
+ "original",
118
+ "snake_case",
119
+ null
120
+ ]
121
+ }
122
+ },
123
+ "additionalProperties": false
124
+ }
96
125
  ]
97
126
  },
98
127
  "clickhouse": {
99
- "description": "Whether to additionally sync the indexed data to ClickHouse. Requires Postgres to be enabled (default: false).",
100
- "type": [
101
- "boolean",
102
- "null"
128
+ "description": "Whether to additionally sync the indexed data to ClickHouse. Requires Postgres to be enabled (default: false). Accepts a boolean or an options object (the object form implies the backend is enabled).",
129
+ "anyOf": [
130
+ {
131
+ "type": [
132
+ "boolean",
133
+ "null"
134
+ ]
135
+ },
136
+ {
137
+ "type": "object",
138
+ "properties": {
139
+ "default": {
140
+ "description": "Whether entities without an @storage directive are stored in this backend (default: false).",
141
+ "type": [
142
+ "boolean",
143
+ "null"
144
+ ]
145
+ },
146
+ "column_name_format": {
147
+ "description": "How entity fields are reflected in the storage column names. `original` keeps the schema.graphql field names as is, `snake_case` converts them to snake_case in the database while keeping the original casing in the exposed APIs. (default: original)",
148
+ "type": [
149
+ "string",
150
+ "null"
151
+ ],
152
+ "enum": [
153
+ "original",
154
+ "snake_case",
155
+ null
156
+ ]
157
+ }
158
+ },
159
+ "additionalProperties": false
160
+ }
103
161
  ]
104
162
  }
105
163
  },
@@ -121,7 +179,14 @@
121
179
  "if": {
122
180
  "properties": {
123
181
  "clickhouse": {
124
- "const": true
182
+ "anyOf": [
183
+ {
184
+ "const": true
185
+ },
186
+ {
187
+ "type": "object"
188
+ }
189
+ ]
125
190
  }
126
191
  },
127
192
  "required": [
@@ -131,7 +196,14 @@
131
196
  "then": {
132
197
  "properties": {
133
198
  "postgres": {
134
- "const": true
199
+ "anyOf": [
200
+ {
201
+ "const": true
202
+ },
203
+ {
204
+ "type": "object"
205
+ }
206
+ ]
135
207
  }
136
208
  },
137
209
  "required": [
package/index.d.ts CHANGED
@@ -107,7 +107,7 @@ export type Prettify<T> = { [K in keyof T]: T[K] } & {};
107
107
 
108
108
  /**
109
109
  * Operator for filtering entity fields in getWhere queries.
110
- * Only fields with `@index` in the schema can be queried at runtime.
110
+ * Only `id` and fields with `@index` in the schema can be queried at runtime.
111
111
  */
112
112
  export type GetWhereOperator<T> = {
113
113
  /** Matches entities where the field equals the given value. */
@@ -128,7 +128,7 @@ export type GetWhereOperator<T> = {
128
128
  * Constructs a getWhere filter type from an entity type.
129
129
  * Each field can be filtered using {@link GetWhereOperator} (`_eq`, `_gt`, `_lt`, `_gte`, `_lte`, `_in`).
130
130
  *
131
- * Note: only fields with `@index` in the schema can be queried at runtime.
131
+ * Note: only `id` and fields with `@index` in the schema can be queried at runtime.
132
132
  * Attempting to filter on a non-indexed field will throw a descriptive error.
133
133
  */
134
134
  export type GetWhereFilter<E> = {
@@ -959,6 +959,144 @@ export type SvmOnSlotOptions<Config extends IndexerConfigTypes = GlobalConfig> =
959
959
  readonly where?: (args: SvmOnSlotWhereArgs<Config>) => SvmOnSlotWhereResult;
960
960
  };
961
961
 
962
+ // ============== SVM onInstruction types ==============
963
+
964
+ /** Borsh-decoded params view of an instruction. Present whenever a
965
+ * `ProgramSchema` was attached to the program (bundled, Anchor IDL, or
966
+ * hand-written `accounts`/`args` in YAML). Absent when no schema applies or
967
+ * the discriminator didn't match any registered instruction. */
968
+ export type SvmInstructionParams = {
969
+ /** Schema-declared instruction name. */
970
+ readonly name: string;
971
+ /** Borsh-decoded args object. POC types this as `unknown`; narrow with a
972
+ * locally-declared type until the typed-args codegen lands. */
973
+ readonly args: unknown;
974
+ /** Named accounts in schema order. Keys are exactly the schema-declared
975
+ * names; values are base58 pubkeys. */
976
+ readonly accounts: Readonly<Record<string, string>>;
977
+ /** Accounts beyond the schema's named list (Anchor `remaining_accounts`,
978
+ * IDL drift). Empty when counts match the schema. */
979
+ readonly extraAccounts: readonly string[];
980
+ };
981
+
982
+ /** Block context for a matched instruction. */
983
+ export type SvmInstructionBlock = {
984
+ /** Slot this instruction's block was matched in. */
985
+ readonly slot: number;
986
+ readonly time: number;
987
+ /** Always empty for now — reserved for the future reorg-guard route. */
988
+ readonly hash: string;
989
+ };
990
+
991
+ export type SvmTokenBalance = {
992
+ readonly account?: string;
993
+ readonly mint?: string;
994
+ readonly owner?: string;
995
+ /** u64 decimal string. Cast with BigInt(...) for arithmetic. */
996
+ readonly preAmount?: string;
997
+ readonly postAmount?: string;
998
+ };
999
+
1000
+ /** Parent transaction surfaced when an instruction's
1001
+ * `include_transaction` flag is `true`. */
1002
+ export type SvmTransaction = {
1003
+ readonly signatures: readonly string[];
1004
+ readonly feePayer?: string;
1005
+ readonly success?: boolean;
1006
+ readonly err?: string;
1007
+ /** Lamports. */
1008
+ readonly fee?: bigint;
1009
+ readonly computeUnitsConsumed?: bigint;
1010
+ readonly accountKeys: readonly string[];
1011
+ readonly recentBlockhash?: string;
1012
+ readonly version?: string;
1013
+ /** SPL Token / Token-2022 balance snapshots for this transaction.
1014
+ * Present when `include_token_balances` is `true`. */
1015
+ readonly tokenBalances?: readonly SvmTokenBalance[];
1016
+ };
1017
+
1018
+ export type SvmLog = {
1019
+ readonly kind: string;
1020
+ readonly message: string;
1021
+ };
1022
+
1023
+ /** A single Solana instruction delivered to an `onInstruction` handler.
1024
+ *
1025
+ * Carries the matched instruction's own fields (`programId`, `data`,
1026
+ * `accounts`, discriminator prefixes, `params`) plus the program/instruction
1027
+ * names, parent transaction, scoped logs, and block context. Parameterised
1028
+ * over `Params` so the per-(program, instruction) overload of
1029
+ * `onInstruction` can narrow `instruction.params` to the codegen-generated
1030
+ * `{ args, accounts }` shape.
1031
+ *
1032
+ * `data` and discriminator prefixes are `0x`-prefixed hex strings; accounts
1033
+ * are base58 strings. */
1034
+ export type SvmInstruction<
1035
+ Params extends SvmInstructionParams = SvmInstructionParams,
1036
+ > = {
1037
+ /** Program name as declared under `programs[].name` in `config.yaml`. */
1038
+ readonly programName: string;
1039
+ /** Instruction name as declared under `instructions[].name` in
1040
+ * `config.yaml`. */
1041
+ readonly instructionName: string;
1042
+ readonly programId: string;
1043
+ readonly data: string;
1044
+ readonly accounts: readonly string[];
1045
+ readonly instructionAddress: readonly number[];
1046
+ readonly isInner: boolean;
1047
+ readonly d1?: string;
1048
+ readonly d2?: string;
1049
+ readonly d4?: string;
1050
+ readonly d8?: string;
1051
+ /** Borsh-decoded params. Present when a schema is configured and matched. */
1052
+ readonly params?: Params;
1053
+ /** Present when the instruction's `include_transaction` is `true`. */
1054
+ readonly transaction?: SvmTransaction;
1055
+ /** Present when the instruction's `include_logs` is `true`; only logs
1056
+ * scoped to this exact instruction (matching `instruction_address`). */
1057
+ readonly logs?: readonly SvmLog[];
1058
+ readonly block: SvmInstructionBlock;
1059
+ };
1060
+
1061
+ /** Arguments passed to handlers registered via `indexer.onInstruction`. */
1062
+ export type SvmOnInstructionHandlerArgs<
1063
+ Config extends IndexerConfigTypes = GlobalConfig,
1064
+ Instr extends SvmInstruction = SvmInstruction,
1065
+ > = {
1066
+ readonly instruction: Instr;
1067
+ readonly context: SvmOnSlotContext<Config>;
1068
+ };
1069
+
1070
+ /** Shape extracted from `Global.config.svm.programs[P][I]`. The codegen
1071
+ * emits `{ args: ...; accounts: ... }` per (program, instruction); this
1072
+ * helper turns that into a `SvmInstructionParams`-compatible record. */
1073
+ type SvmParamsFromProgramTable<TInstr> = TInstr extends {
1074
+ args: infer A;
1075
+ accounts: infer Acc extends Readonly<Record<string, string>>;
1076
+ }
1077
+ ? {
1078
+ readonly name: string;
1079
+ readonly args: A;
1080
+ readonly accounts: Acc;
1081
+ readonly extraAccounts: readonly string[];
1082
+ }
1083
+ : SvmInstructionParams;
1084
+
1085
+ /** Options for an SVM `indexer.onInstruction` registration. */
1086
+ export type SvmOnInstructionOptions<P extends string = string, I extends string = string> = {
1087
+ /** Program name as declared under `chains[].programs[].name` in
1088
+ * `config.yaml`. */
1089
+ readonly program: P;
1090
+ /** Instruction name as declared under
1091
+ * `chains[].programs[].instructions[].name` in `config.yaml`. */
1092
+ readonly instruction: I;
1093
+ };
1094
+
1095
+ /** Handler function for an SVM `indexer.onInstruction` registration. */
1096
+ export type SvmOnInstructionHandler<
1097
+ Config extends IndexerConfigTypes = GlobalConfig,
1098
+ > = (args: SvmOnInstructionHandlerArgs<Config>) => Promise<void>;
1099
+
962
1100
  // ============== Indexer Types ==============
963
1101
 
964
1102
  // Helper: Check if an ecosystem is configured. Single-ecosystem indexers only
@@ -1136,7 +1274,14 @@ type FuelEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1136
1274
  : never
1137
1275
  : never;
1138
1276
 
1139
- // SVM ecosystem type chains plus onSlot handler method. SVM has no onEvent yet.
1277
+ // Surfaced when an SVM indexer configures no `programs`, so there's no
1278
+ // instruction to register a handler for. Mirrors `CodegenRequiredHint`: the
1279
+ // string literal becomes the argument type, so any `onInstruction` call fails
1280
+ // with this text naming the fix.
1281
+ type SvmNoProgramsHint =
1282
+ "Add at least one entry under `svm.programs` in config.yaml to register instruction handlers with onInstruction.";
1283
+
1284
+ // SVM ecosystem type — chains plus instruction + slot handler methods.
1140
1285
  type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1141
1286
  "svm" extends keyof Config
1142
1287
  ? Config["svm"] extends { chains: infer Chains }
@@ -1160,7 +1305,42 @@ type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1160
1305
  options: SvmOnSlotOptions<Config>,
1161
1306
  handler: SvmOnSlotHandler<Config>,
1162
1307
  ) => void;
1308
+ } & (Config["svm"] extends {
1309
+ programs: infer Programs extends Record<string, Record<string, any>>;
1163
1310
  }
1311
+ ? {
1312
+ /**
1313
+ * Register an instruction handler. Dispatch matches on
1314
+ * `(programId, discriminator)` from the YAML config.
1315
+ * `instruction.params.args` and
1316
+ * `instruction.params.accounts` are typed from the
1317
+ * program's Borsh schema (Anchor IDL, bundled, or
1318
+ * hand-written `accounts`/`args` in YAML). `params` stays
1319
+ * optional at runtime because schema-matching can fail on
1320
+ * IDL drift or unknown discriminators.
1321
+ */
1322
+ readonly onInstruction: <
1323
+ P extends keyof Programs & string,
1324
+ I extends keyof Programs[P] & string,
1325
+ >(
1326
+ options: SvmOnInstructionOptions<P, I>,
1327
+ handler: (
1328
+ args: SvmOnInstructionHandlerArgs<
1329
+ Config,
1330
+ SvmInstruction<SvmParamsFromProgramTable<Programs[P][I]>>
1331
+ >,
1332
+ ) => Promise<void>,
1333
+ ) => void;
1334
+ }
1335
+ : {
1336
+ /** No `programs` configured under `svm` in config.yaml, so
1337
+ * there's nothing to register an instruction handler for. The
1338
+ * rest parameter is typed as a string-literal hint so any call
1339
+ * site fails with a message naming the fix. */
1340
+ readonly onInstruction: (
1341
+ ...hint: SvmNoProgramsHint[]
1342
+ ) => void;
1343
+ })
1164
1344
  : never
1165
1345
  : never
1166
1346
  : never;
@@ -1176,6 +1356,7 @@ type CodegenRequiredHint =
1176
1356
  "Run 'envio codegen' to generate handler types from config.yaml. Without codegen, the indexer has no contracts, chains, or events to register handlers for.";
1177
1357
  type CodegenRequiredFallback = {
1178
1358
  readonly onEvent: (...hint: CodegenRequiredHint[]) => void;
1359
+ readonly onInstruction: (...hint: CodegenRequiredHint[]) => void;
1179
1360
  readonly onBlock: (...hint: CodegenRequiredHint[]) => void;
1180
1361
  readonly onSlot: (...hint: CodegenRequiredHint[]) => void;
1181
1362
  readonly contractRegister: (...hint: CodegenRequiredHint[]) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -70,10 +70,10 @@
70
70
  "tsx": "4.21.0"
71
71
  },
72
72
  "optionalDependencies": {
73
- "envio-linux-x64": "3.1.2",
74
- "envio-linux-x64-musl": "3.1.2",
75
- "envio-linux-arm64": "3.1.2",
76
- "envio-darwin-x64": "3.1.2",
77
- "envio-darwin-arm64": "3.1.2"
73
+ "envio-linux-x64": "3.2.0",
74
+ "envio-linux-x64-musl": "3.2.0",
75
+ "envio-linux-arm64": "3.2.0",
76
+ "envio-darwin-x64": "3.2.0",
77
+ "envio-darwin-arm64": "3.2.0"
78
78
  }
79
79
  }
package/src/Batch.res CHANGED
@@ -207,7 +207,7 @@ let prepareBatch = (
207
207
  let prevBlockNumber = ref(chainBeforeBatch.progressBlockNumber)
208
208
  if chainBatchSize > 0 {
209
209
  for idx in 0 to chainBatchSize - 1 {
210
- let item = fetchState.buffer->Belt.Array.getUnsafe(idx)
210
+ let item = fetchState.buffer->Array.getUnsafe(idx)
211
211
  let blockNumber = item->Internal.getItemBlockNumber
212
212
 
213
213
  // Every new block we should create a new checkpoint
@@ -243,7 +243,7 @@ let prepareBatch = (
243
243
  } else {
244
244
  let lastIndex = checkpointEventsProcessed->Array.length - 1
245
245
  checkpointEventsProcessed
246
- ->Belt.Array.setUnsafe(
246
+ ->Array.setUnsafe(
247
247
  lastIndex,
248
248
  checkpointEventsProcessed->Array.getUnsafe(lastIndex) + 1,
249
249
  )
@@ -153,14 +153,14 @@ let make = (
153
153
  onBlockConfigs->Array.forEach(onBlockConfig => {
154
154
  if onBlockConfig.startBlock->Option.getOr(startBlock) < startBlock {
155
155
  JsError.throwWithMessage(
156
- `The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Belt.Int.toString}). This is not supported yet.`,
156
+ `The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Int.toString}). This is not supported yet.`,
157
157
  )
158
158
  }
159
159
  switch endBlock {
160
160
  | Some(chainEndBlock) =>
161
161
  if onBlockConfig.endBlock->Option.getOr(chainEndBlock) > chainEndBlock {
162
162
  JsError.throwWithMessage(
163
- `The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->Belt.Int.toString}). This is not supported yet.`,
163
+ `The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->Int.toString}). This is not supported yet.`,
164
164
  )
165
165
  }
166
166
  | None => ()
@@ -229,7 +229,31 @@ let make = (
229
229
  ~lowercaseAddresses,
230
230
  )
231
231
  | Config.FuelSourceConfig({hypersync}) => [HyperFuelSource.make({chain, endpointUrl: hypersync})]
232
- | Config.SvmSourceConfig({rpc}) => [Svm.makeRPCSource(~chain, ~rpc)]
232
+ | Config.SvmSourceConfig({hypersync, rpc}) =>
233
+ switch (hypersync, rpc) {
234
+ | (None, None) =>
235
+ JsError.throwWithMessage(
236
+ `Chain ${chain->ChainMap.Chain.toChainId->Int.toString} has no SVM data source`,
237
+ )
238
+ | (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)]
239
+ | (Some(hypersyncUrl), _) =>
240
+ // HyperSync drives instruction sync. A configured RPC is ignored for now
241
+ // (RPC fallback isn't wired up yet).
242
+ let svmEventConfigs =
243
+ chainConfig.contracts
244
+ ->Array.flatMap(contract => contract.events)
245
+ ->(Utils.magic: array<Internal.eventConfig> => array<Internal.svmInstructionEventConfig>)
246
+ let apiToken = Env.envioApiToken
247
+ [
248
+ SvmHyperSyncSource.make({
249
+ chain,
250
+ endpointUrl: hypersyncUrl,
251
+ apiToken,
252
+ eventConfigs: svmEventConfigs,
253
+ clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
254
+ }),
255
+ ]
256
+ }
233
257
  // For tests: use ready-to-use sources directly
234
258
  | Config.CustomSources(sources) => sources
235
259
  }
@@ -20,6 +20,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
20
20
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
21
21
  import * as HyperFuelSource from "./sources/HyperFuelSource.res.mjs";
22
22
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
23
+ import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
23
24
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
24
25
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
25
26
 
@@ -89,10 +90,10 @@ function make(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBl
89
90
  if (onBlockConfigs !== undefined) {
90
91
  onBlockConfigs.forEach(onBlockConfig => {
91
92
  if (Stdlib_Option.getOr(onBlockConfig.startBlock, startBlock) < startBlock) {
92
- Stdlib_JsError.throwWithMessage(`The start block for onBlock handler "` + onBlockConfig.name + `" is less than the chain start block (` + String(startBlock) + `). This is not supported yet.`);
93
+ Stdlib_JsError.throwWithMessage(`The start block for onBlock handler "` + onBlockConfig.name + `" is less than the chain start block (` + startBlock.toString() + `). This is not supported yet.`);
93
94
  }
94
95
  if (endBlock !== undefined && Stdlib_Option.getOr(onBlockConfig.endBlock, endBlock) > endBlock) {
95
- return Stdlib_JsError.throwWithMessage(`The end block for onBlock handler "` + onBlockConfig.name + `" is greater than the chain end block (` + String(endBlock) + `). This is not supported yet.`);
96
+ return Stdlib_JsError.throwWithMessage(`The end block for onBlock handler "` + onBlockConfig.name + `" is greater than the chain end block (` + endBlock.toString() + `). This is not supported yet.`);
96
97
  }
97
98
  });
98
99
  }
@@ -132,7 +133,20 @@ function make(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBl
132
133
  })];
133
134
  break;
134
135
  case "SvmSourceConfig" :
135
- sources$1 = [Svm.makeRPCSource(chain, sources.rpc)];
136
+ let rpc = sources.rpc;
137
+ let hypersync = sources.hypersync;
138
+ if (hypersync !== undefined) {
139
+ let svmEventConfigs = chainConfig.contracts.flatMap(contract => contract.events);
140
+ sources$1 = [SvmHyperSyncSource.make({
141
+ chain: chain,
142
+ endpointUrl: hypersync,
143
+ apiToken: Env.envioApiToken,
144
+ eventConfigs: svmEventConfigs,
145
+ clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis
146
+ })];
147
+ } else {
148
+ sources$1 = rpc !== undefined ? [Svm.makeRPCSource(chain, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + chain.toString() + ` has no SVM data source`);
149
+ }
136
150
  break;
137
151
  case "CustomSources" :
138
152
  sources$1 = sources._0;