envio 3.8.0 → 3.9.0-resolvers.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 (109) hide show
  1. package/README.md +2 -2
  2. package/evm.schema.json +7 -0
  3. package/fuel.schema.json +7 -0
  4. package/index.d.ts +433 -109
  5. package/index.js +10 -0
  6. package/package.json +6 -7
  7. package/src/AddressRows.res +121 -0
  8. package/src/AddressRows.res.mjs +143 -0
  9. package/src/Batch.res +11 -0
  10. package/src/Batch.res.mjs +2 -1
  11. package/src/Bin.res +47 -0
  12. package/src/Bin.res.mjs +34 -1
  13. package/src/ChainState.res +102 -100
  14. package/src/ChainState.res.mjs +81 -67
  15. package/src/ChainState.resi +28 -15
  16. package/src/Config.res +43 -122
  17. package/src/Config.res.mjs +33 -75
  18. package/src/ContractMapping.res +66 -0
  19. package/src/ContractMapping.res.mjs +73 -0
  20. package/src/Core.res +24 -0
  21. package/src/Core.res.mjs +4 -0
  22. package/src/Ecosystem.res +7 -2
  23. package/src/Env.res +128 -1
  24. package/src/Env.res.mjs +123 -1
  25. package/src/EventConfigBuilder.res +169 -28
  26. package/src/EventConfigBuilder.res.mjs +124 -17
  27. package/src/FetchState.res +59 -67
  28. package/src/FetchState.res.mjs +34 -44
  29. package/src/HandlerRegister.res +6 -4
  30. package/src/HandlerRegister.res.mjs +8 -5
  31. package/src/InMemoryStore.res +22 -32
  32. package/src/InMemoryStore.res.mjs +12 -24
  33. package/src/IndexerState.res +40 -3
  34. package/src/IndexerState.res.mjs +30 -4
  35. package/src/IndexerState.resi +4 -1
  36. package/src/Internal.res +16 -17
  37. package/src/Main.res +25 -15
  38. package/src/Main.res.mjs +19 -14
  39. package/src/Metrics.res +15 -1
  40. package/src/Metrics.res.mjs +5 -1
  41. package/src/Persistence.res +42 -19
  42. package/src/Persistence.res.mjs +8 -5
  43. package/src/PgStorage.res +232 -78
  44. package/src/PgStorage.res.mjs +114 -72
  45. package/src/PruneStaleHistory.res +1 -1
  46. package/src/PruneStaleHistory.res.mjs +1 -1
  47. package/src/Rollback.res +62 -41
  48. package/src/Rollback.res.mjs +61 -19
  49. package/src/RollbackCommit.res +7 -9
  50. package/src/RollbackCommit.res.mjs +5 -6
  51. package/src/SimulateItems.res +5 -12
  52. package/src/SimulateItems.res.mjs +6 -10
  53. package/src/Sink.res +35 -26
  54. package/src/Sink.res.mjs +20 -16
  55. package/src/TestIndexer.res +95 -113
  56. package/src/TestIndexer.res.mjs +63 -90
  57. package/src/Utils.res +38 -0
  58. package/src/Utils.res.mjs +39 -2
  59. package/src/Writing.res +4 -2
  60. package/src/Writing.res.mjs +3 -2
  61. package/src/bindings/ClickHouse.res +311 -683
  62. package/src/bindings/ClickHouse.res.mjs +294 -416
  63. package/src/bindings/ClickHouseSink.res +303 -0
  64. package/src/bindings/ClickHouseSink.res.mjs +216 -0
  65. package/src/bindings/NodeJs.res +9 -0
  66. package/src/bindings/NodeJs.res.mjs +8 -1
  67. package/src/bindings/Postgres.res +9 -0
  68. package/src/bindings/Postgres.res.mjs +3 -0
  69. package/src/db/EntityHistory.res +29 -24
  70. package/src/db/EntityHistory.res.mjs +11 -19
  71. package/src/db/InternalTable.res +247 -83
  72. package/src/db/InternalTable.res.mjs +216 -85
  73. package/src/db/RollbackScope.res +29 -0
  74. package/src/db/RollbackScope.res.mjs +31 -0
  75. package/src/db/Table.res +28 -1
  76. package/src/db/Table.res.mjs +26 -2
  77. package/src/resolvers/ResolverProcess.res +809 -0
  78. package/src/resolvers/ResolverProcess.res.mjs +667 -0
  79. package/src/resolvers/ResolverQuery.res +317 -0
  80. package/src/resolvers/ResolverQuery.res.mjs +220 -0
  81. package/src/resolvers/collisions.js +108 -0
  82. package/src/resolvers/db.js +363 -0
  83. package/src/resolvers/dispatch.js +165 -0
  84. package/src/resolvers/errors.js +30 -0
  85. package/src/resolvers/graphqlSelection.js +292 -0
  86. package/src/resolvers/hasuraAction.js +84 -0
  87. package/src/resolvers/hasuraApply.js +323 -0
  88. package/src/resolvers/hasuraMetadata.js +119 -0
  89. package/src/resolvers/index.js +95 -0
  90. package/src/resolvers/manifest.js +370 -0
  91. package/src/resolvers/server.js +270 -0
  92. package/src/sources/AddressSet.res +0 -22
  93. package/src/sources/AddressStore.res +48 -88
  94. package/src/sources/AddressStore.res.mjs +11 -22
  95. package/src/sources/Evm.res +2 -0
  96. package/src/sources/Evm.res.mjs +2 -0
  97. package/src/sources/Fuel.res +2 -0
  98. package/src/sources/Fuel.res.mjs +2 -0
  99. package/src/sources/Svm.res +2 -0
  100. package/src/sources/Svm.res.mjs +2 -0
  101. package/src/sources/SvmHyperSyncClient.res +13 -107
  102. package/src/sources/SvmHyperSyncClient.res.mjs +2 -5
  103. package/src/sources/SvmHyperSyncSource.res +5 -10
  104. package/src/sources/SvmHyperSyncSource.res.mjs +5 -13
  105. package/src/tui/Tui.res +1 -1
  106. package/src/tui/Tui.res.mjs +1 -1
  107. package/svm.schema.json +17 -78
  108. package/src/MemoryStorage.res +0 -756
  109. package/src/MemoryStorage.res.mjs +0 -623
package/src/Config.res CHANGED
@@ -94,6 +94,9 @@ type t = {
94
94
  name: string,
95
95
  description: option<string>,
96
96
  handlers: string,
97
+ // Relative path to the module declaring custom GraphQL resolvers, when the
98
+ // project ships them. None means entity fields only.
99
+ resolvers: option<string>,
97
100
  contractHandlers: array<contractHandler>,
98
101
  shouldRollbackOnReorg: bool,
99
102
  shouldSaveFullHistory: bool,
@@ -107,6 +110,9 @@ type t = {
107
110
  // they can express fits an INTEGER.
108
111
  chainIdMode: ChainId.mode,
109
112
  chainMap: ChainMap.t<chain>,
113
+ // Derived from every chain's contracts, so an id means the same contract on
114
+ // every chain and on every restart.
115
+ contractMapping: ContractMapping.t,
110
116
  defaultChain: option<chain>,
111
117
  ecosystem: Ecosystem.t,
112
118
  enableRawEvents: bool,
@@ -123,76 +129,9 @@ type t = {
123
129
  isDev: bool,
124
130
  userEntitiesByName: dict<Internal.entityConfig>,
125
131
  userEntities: array<Internal.entityConfig>,
126
- allEntities: array<Internal.entityConfig>,
127
132
  allEnums: array<Table.enumConfig<Table.enum>>,
128
133
  }
129
134
 
130
- module EnvioAddresses = {
131
- let name = "envio_addresses"
132
- let index = -1
133
-
134
- let makeId = (~chainId: ChainId.t, ~address) => {
135
- chainId->ChainId.toString ++ "-" ++ address->Address.toString
136
- }
137
-
138
- type t = {
139
- id: string,
140
- @as("chain_id") chainId: ChainId.t,
141
- @as("registration_block") registrationBlock: int,
142
- // Vestigial: always written as -1 and never read back. The column stays so
143
- // an existing schema doesn't need a migration.
144
- @as("registration_log_index") registrationLogIndex: int,
145
- @as("contract_name") contractName: string,
146
- }
147
-
148
- // Extract the raw contract address from the composite id ({chainId}-{address}).
149
- // Inverse of makeId. Keep in sync with makeId above and the SUBSTRING SQL in
150
- // InternalTable.Chains.makeGetInitialStateQuery.
151
- let getAddress = (entity: t): Address.t => {
152
- let sepIdx = entity.id->String.indexOf("-")
153
- entity.id
154
- ->String.slice(~start=sepIdx + 1, ~end=entity.id->String.length)
155
- ->Address.unsafeFromString
156
- }
157
-
158
- let schema = S.schema(s => {
159
- id: s.matches(S.string),
160
- chainId: s.matches(ChainId.schema),
161
- registrationBlock: s.matches(S.int),
162
- registrationLogIndex: s.matches(S.int),
163
- contractName: s.matches(S.string),
164
- })
165
-
166
- let table = Table.mkTable(
167
- name,
168
- ~fields=[
169
- Table.mkField("id", String, ~isPrimaryKey=true, ~fieldSchema=S.string),
170
- Table.mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema),
171
- Table.mkField("registration_block", Int32, ~fieldSchema=S.int),
172
- // -1 sentinel when registered from a block handler (no log index)
173
- Table.mkField("registration_log_index", Int32, ~fieldSchema=S.int),
174
- Table.mkField("contract_name", String, ~fieldSchema=S.string),
175
- ],
176
- )
177
-
178
- external castToInternal: t => Internal.entity = "%identity"
179
-
180
- let entityConfig = {
181
- Internal.name,
182
- index,
183
- schema,
184
- table,
185
- // Internal address tracking is Postgres-only; the global config is
186
- // always required to have Postgres enabled (Storage::resolve forbids
187
- // a Postgres-disabled global), so this is safe regardless of mode.
188
- storage: {postgres: true, clickhouse: false},
189
- // The table already keys rows by chain through the composite `id`, so the
190
- // per-chain mode must not append a second chain-id column to it.
191
- crossChain: true,
192
- internal: true,
193
- }->Internal.fromGenericEntityConfig
194
- }
195
-
196
135
  type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
197
136
 
198
137
  let rpcSourceForSchema = S.enum([Sync, Fallback, Realtime])
@@ -241,24 +180,6 @@ let publicConfigChainSchema = S.schema(s =>
241
180
  let svmEventDescriptorSchema = S.schema(s =>
242
181
  {
243
182
  "discriminator": s.matches(S.option(S.string)),
244
- "discriminatorByteLen": s.matches(S.int),
245
- // An array of AND-groups OR-ed together: the CLI normalizes both the flat
246
- // and `any_of` YAML shapes to `Vec<Vec<SvmAccountFilterJson>>`.
247
- "accountFilters": s.matches(
248
- S.option(
249
- S.array(
250
- S.array(
251
- S.schema(s =>
252
- {
253
- "position": s.matches(S.int),
254
- "values": s.matches(S.array(S.string)),
255
- }
256
- ),
257
- ),
258
- ),
259
- ),
260
- ),
261
- "isInner": s.matches(S.option(S.bool)),
262
183
  "accounts": s.matches(S.option(S.array(S.string))),
263
184
  "args": s.matches(S.option(S.json(~validate=false))),
264
185
  }
@@ -444,7 +365,7 @@ let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Tab
444
365
  baseSchema
445
366
  }
446
367
  let fieldSchema = if isNullable {
447
- S.null(fieldSchema)->S.toUnknown
368
+ Utils.Schema.nullTolerant(fieldSchema)->S.toUnknown
448
369
  } else {
449
370
  fieldSchema
450
371
  }
@@ -614,6 +535,7 @@ let publicConfigSchema = S.schema(s =>
614
535
  "name": s.matches(S.string),
615
536
  "description": s.matches(S.option(S.string)),
616
537
  "handlers": s.matches(S.option(S.string)),
538
+ "resolvers": s.matches(S.option(S.string)),
617
539
  "isDev": s.matches(S.option(S.bool)),
618
540
  "fullBatchSize": s.matches(S.option(S.int)),
619
541
  "rollbackOnReorg": s.matches(S.option(S.bool)),
@@ -630,6 +552,14 @@ let publicConfigSchema = S.schema(s =>
630
552
  }
631
553
  )
632
554
 
555
+ let contractMappingOf = (~chainConfigs: array<chain>): ContractMapping.t => {
556
+ let names = []
557
+ chainConfigs->Array.forEach(chain =>
558
+ chain.contracts->Array.forEach(contract => names->Array.push(contract.name)->ignore)
559
+ )
560
+ ContractMapping.make(~names)
561
+ }
562
+
633
563
  let fromPublic = (publicConfigJson: JSON.t) => {
634
564
  let maxAddrInPartition = Env.maxAddrInPartition
635
565
  // Parse public config
@@ -809,11 +739,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
809
739
  Utils.magic: _ => {
810
740
  "svm": option<{
811
741
  "discriminator": option<string>,
812
- "discriminatorByteLen": int,
813
- "accountFilters": option<
814
- array<array<{"position": int, "values": array<string>}>>,
815
- >,
816
- "isInner": option<bool>,
817
742
  "accounts": option<array<string>>,
818
743
  "args": option<JSON.t>,
819
744
  }>,
@@ -826,25 +751,11 @@ let fromPublic = (publicConfigJson: JSON.t) => {
826
751
  `SVM instruction ${contractName}.${eventName} is missing the "svm" descriptor in internal config`,
827
752
  )
828
753
  }
829
- let accountFilters =
830
- svm["accountFilters"]
831
- ->Option.getOr([])
832
- ->Array.map(group =>
833
- group->Array.map(
834
- af => {
835
- Internal.position: af["position"],
836
- values: af["values"]->SvmTypes.Pubkey.fromStringsUnsafe,
837
- },
838
- )
839
- )
840
754
  (EventConfigBuilder.buildSvmInstructionEventConfig(
841
755
  ~contractName,
842
756
  ~instructionName=eventName,
843
757
  ~programId,
844
758
  ~discriminator=svm["discriminator"],
845
- ~discriminatorByteLen=svm["discriminatorByteLen"],
846
- ~accountFilters,
847
- ~isInner=svm["isInner"],
848
759
  ~accounts=svm["accounts"]->Option.getOr([]),
849
760
  ~args=svm["args"]->Option.getOr(JSON.Null),
850
761
  ~definedTypes=svmDefinedTypes,
@@ -931,25 +842,24 @@ let fromPublic = (publicConfigJson: JSON.t) => {
931
842
  }
932
843
  })
933
844
 
934
- // The same address under two contract definitions (or twice under one)
935
- // would later violate the (chainId, address) primary key of
936
- // envio_addresses with an opaque Postgres error — fail fast with the
937
- // offending pair instead. parseAddress already canonicalizes casing
938
- // (checksum or lowercase), so an exact match catches case variants too.
939
- let contractNameByAddress = Dict.make()
845
+ // One address listed twice for the same contract would violate the
846
+ // (chainId, address, contract) primary key of envio_addresses with an
847
+ // opaque Postgres error — fail fast instead. Two contracts may share an
848
+ // address: each indexes it with its own events. parseAddress already
849
+ // canonicalizes casing (checksum or lowercase), so an exact match
850
+ // catches case variants too.
851
+ let seen = Utils.Set.make()
940
852
  contracts->Array.forEach(contract => {
941
853
  contract.addresses->Array.forEach(
942
854
  address => {
943
855
  let addressString = address->Address.toString
944
- switch contractNameByAddress->Dict.get(addressString) {
945
- | Some(existingContractName) =>
856
+ let key = contract.name ++ "-" ++ addressString
857
+ if seen->Utils.Set.has(key) {
946
858
  JsError.throwWithMessage(
947
- existingContractName === contract.name
948
- ? `Address ${addressString} is listed multiple times for the contract ${contract.name} on chain ${chainId->ChainId.toString}. Please remove the duplicate from your config.`
949
- : `Address ${addressString} on chain ${chainId->ChainId.toString} is configured for multiple contracts: ${existingContractName} and ${contract.name}. Indexing the same address with multiple contract definitions is not supported. Please define the events on a single contract definition instead.`,
859
+ `Address ${addressString} is listed multiple times for the contract ${contract.name} on chain ${chainId->ChainId.toString}. Please remove the duplicate from your config.`,
950
860
  )
951
- | None => contractNameByAddress->Dict.set(addressString, contract.name)
952
861
  }
862
+ seen->Utils.Set.add(key)->ignore
953
863
  },
954
864
  )
955
865
  })
@@ -1075,8 +985,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1075
985
  ->Option.getOr([])
1076
986
  ->parseEntitiesFromJson(~enumConfigsByName, ~globalStorage, ~defaultCrossChain)
1077
987
 
1078
- let allEntities = userEntities->Array.concat([EnvioAddresses.entityConfig])
1079
-
1080
988
  // Keyed by the capitalized entity name to match the handler-context
1081
989
  // accessor (`context.Pool_snapshots`) the generated types expose, while
1082
990
  // entityConfig.name stays the original schema name used for the physical
@@ -1106,6 +1014,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1106
1014
  name: publicConfig["name"],
1107
1015
  description: publicConfig["description"],
1108
1016
  handlers: publicConfig["handlers"]->Option.getOr("src/handlers"),
1017
+ resolvers: publicConfig["resolvers"],
1109
1018
  contractHandlers,
1110
1019
  shouldRollbackOnReorg: publicConfig["rollbackOnReorg"]->Option.getOr(true),
1111
1020
  shouldSaveFullHistory: publicConfig["saveFullHistory"]->Option.getOr(false),
@@ -1113,6 +1022,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1113
1022
  storage: globalStorage,
1114
1023
  chainIdMode: publicConfig["chainIdMode"]->Option.getOr(Int32),
1115
1024
  chainMap,
1025
+ contractMapping: contractMappingOf(~chainConfigs=chains),
1116
1026
  defaultChain: chains->Array.get(0),
1117
1027
  enableRawEvents: publicConfig["rawEvents"]->Option.getOr(false),
1118
1028
  ecosystem,
@@ -1124,11 +1034,18 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1124
1034
  isDev: publicConfig["isDev"]->Option.getOr(false),
1125
1035
  userEntitiesByName,
1126
1036
  userEntities,
1127
- allEntities,
1128
1037
  allEnums,
1129
1038
  }
1130
1039
  }
1131
1040
 
1041
+ // With no cross-chain entity, a reorg on one chain can never have changed a row
1042
+ // another chain owns, so its rollback stays isolated to that chain instead of
1043
+ // dragging every sibling back with it. A single chain has no sibling to spare,
1044
+ // and narrowing its rollback would only buy it a predicate that always holds.
1045
+ let isIsolatedMultichain = (config: t) =>
1046
+ config.chainMap->ChainMap.keys->Array.length > 1 &&
1047
+ config.userEntities->Array.every(entityConfig => !entityConfig.crossChain)
1048
+
1132
1049
  // Canonicalize a user-provided address to the configured casing so it matches
1133
1050
  // addresses parsed from config.yaml during routing. HyperSync/RPC data arrives
1134
1051
  // already canonical; only user-land input (simulate srcAddress, contractRegister
@@ -1231,8 +1148,11 @@ let getPublicConfigJson = () =>
1231
1148
 
1232
1149
  // Drops source URLs from each chain so RPC/hypersync edits don't trigger
1233
1150
  // the resume-time compat check (and don't end up in `envio_info`). Also
1234
- // drops `isDev`, which toggles between `envio dev` and `envio start` and
1235
- // has no bearing on schema/indexing compatibility.
1151
+ // drops two fields that have no bearing on schema/indexing compatibility:
1152
+ // `isDev`, which toggles between `envio dev` and `envio start`, and
1153
+ // `resolvers`, which names where the custom GraphQL resolvers live. Without
1154
+ // the latter, adding `resolvers:` to a running indexer reads as an
1155
+ // incompatible config change and it refuses to resume.
1236
1156
  let stripSensitiveData = (json: JSON.t): JSON.t => {
1237
1157
  let cloned = json->JSON.stringify->JSON.parseOrThrow
1238
1158
  let stripChains = (ecosystem: option<JSON.t>) =>
@@ -1259,6 +1179,7 @@ let stripSensitiveData = (json: JSON.t): JSON.t => {
1259
1179
  switch cloned {
1260
1180
  | Object(obj) => {
1261
1181
  obj->Utils.Dict.deleteInPlace("isDev")
1182
+ obj->Utils.Dict.deleteInPlace("resolvers")
1262
1183
  stripChains(obj->Dict.get("evm"))
1263
1184
  stripChains(obj->Dict.get("fuel"))
1264
1185
  stripChains(obj->Dict.get("svm"))
@@ -16,6 +16,7 @@ import * as BigDecimal from "./bindings/BigDecimal.res.mjs";
16
16
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
17
17
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
18
18
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
19
+ import * as ContractMapping from "./ContractMapping.res.mjs";
19
20
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
20
21
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
21
22
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -32,58 +33,6 @@ function chainIdColumnName(format) {
32
33
  }
33
34
  }
34
35
 
35
- let name = "envio_addresses";
36
-
37
- function makeId(chainId, address) {
38
- return ChainId.toString(chainId) + "-" + address;
39
- }
40
-
41
- function getAddress(entity) {
42
- let sepIdx = entity.id.indexOf("-");
43
- return entity.id.slice(sepIdx + 1 | 0, entity.id.length);
44
- }
45
-
46
- let schema = S$RescriptSchema.schema(s => ({
47
- id: s.m(S$RescriptSchema.string),
48
- chain_id: s.m(ChainId.schema),
49
- registration_block: s.m(S$RescriptSchema.int),
50
- registration_log_index: s.m(S$RescriptSchema.int),
51
- contract_name: s.m(S$RescriptSchema.string)
52
- }));
53
-
54
- let table = Table.mkTable(name, undefined, [
55
- Table.mkField("id", "String", S$RescriptSchema.string, undefined, undefined, undefined, true, undefined, undefined, undefined, undefined, undefined, undefined),
56
- Table.mkField("chain_id", "ChainId", ChainId.schema, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
57
- Table.mkField("registration_block", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
58
- Table.mkField("registration_log_index", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
59
- Table.mkField("contract_name", "String", S$RescriptSchema.string, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined)
60
- ], undefined);
61
-
62
- let entityConfig_storage = {
63
- postgres: true,
64
- clickhouse: false
65
- };
66
-
67
- let entityConfig = {
68
- name: name,
69
- index: -1,
70
- schema: schema,
71
- table: table,
72
- storage: entityConfig_storage,
73
- crossChain: true,
74
- internal: true
75
- };
76
-
77
- let EnvioAddresses = {
78
- name: name,
79
- index: -1,
80
- makeId: makeId,
81
- getAddress: getAddress,
82
- schema: schema,
83
- table: table,
84
- entityConfig: entityConfig
85
- };
86
-
87
36
  let rpcSourceForSchema = S$RescriptSchema.$$enum([
88
37
  "sync",
89
38
  "fallback",
@@ -124,12 +73,6 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
124
73
 
125
74
  let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
126
75
  discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
127
- discriminatorByteLen: s.m(S$RescriptSchema.int),
128
- accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
129
- position: s.m(S$RescriptSchema.int),
130
- values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
131
- })))))),
132
- isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
133
76
  accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
134
77
  args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
135
78
  }));
@@ -329,7 +272,7 @@ function getFieldTypeAndSchema(prop, enumConfigsByName) {
329
272
  }
330
273
  let baseSchema = match[1];
331
274
  let fieldSchema = isArray ? S$RescriptSchema.array(baseSchema) : baseSchema;
332
- let fieldSchema$1 = isNullable ? S$RescriptSchema.$$null(fieldSchema) : fieldSchema;
275
+ let fieldSchema$1 = isNullable ? Utils.Schema.nullTolerant(fieldSchema) : fieldSchema;
333
276
  return [
334
277
  match[0],
335
278
  fieldSchema$1,
@@ -429,6 +372,7 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
429
372
  name: s.m(S$RescriptSchema.string),
430
373
  description: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
431
374
  handlers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
375
+ resolvers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
432
376
  isDev: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
433
377
  fullBatchSize: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
434
378
  rollbackOnReorg: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
@@ -444,6 +388,16 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
444
388
  entities: s.m(S$RescriptSchema.option(S$RescriptSchema.array(entityJsonSchema)))
445
389
  }));
446
390
 
391
+ function contractMappingOf(chainConfigs) {
392
+ let names = [];
393
+ chainConfigs.forEach(chain => {
394
+ chain.contracts.forEach(contract => {
395
+ names.push(contract.name);
396
+ });
397
+ });
398
+ return ContractMapping.make(names);
399
+ }
400
+
447
401
  function fromPublic(publicConfigJson) {
448
402
  let publicConfig;
449
403
  try {
@@ -568,11 +522,7 @@ function fromPublic(publicConfigJson) {
568
522
  ) : addresses[0];
569
523
  let s = eventItem.svm;
570
524
  let svm = s !== undefined ? Primitive_option.valFromOption(s) : Stdlib_JsError.throwWithMessage(`SVM instruction ` + contractName + `.` + eventName + ` is missing the "svm" descriptor in internal config`);
571
- let accountFilters = Stdlib_Option.getOr(svm.accountFilters, []).map(group => group.map(af => ({
572
- position: af.position,
573
- values: af.values
574
- })));
575
- return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, accountFilters, svm.isInner, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
525
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
576
526
  }
577
527
  });
578
528
  } else {
@@ -622,16 +572,14 @@ function fromPublic(publicConfigJson) {
622
572
  startBlock: startBlock
623
573
  };
624
574
  });
625
- let contractNameByAddress = {};
575
+ let seen = new Set();
626
576
  contracts.forEach(contract => {
627
577
  contract.addresses.forEach(address => {
628
- let existingContractName = contractNameByAddress[address];
629
- if (existingContractName !== undefined) {
630
- return Stdlib_JsError.throwWithMessage(existingContractName === contract.name ? `Address ` + address + ` is listed multiple times for the contract ` + contract.name + ` on chain ` + ChainId.toString(chainId) + `. Please remove the duplicate from your config.` : `Address ` + address + ` on chain ` + ChainId.toString(chainId) + ` is configured for multiple contracts: ` + existingContractName + ` and ` + contract.name + `. Indexing the same address with multiple contract definitions is not supported. Please define the events on a single contract definition instead.`);
631
- } else {
632
- contractNameByAddress[address] = contract.name;
633
- return;
578
+ let key = contract.name + "-" + address;
579
+ if (seen.has(key)) {
580
+ Stdlib_JsError.throwWithMessage(`Address ` + address + ` is listed multiple times for the contract ` + contract.name + ` on chain ` + ChainId.toString(chainId) + `. Please remove the duplicate from your config.`);
634
581
  }
582
+ seen.add(key);
635
583
  });
636
584
  });
637
585
  let sourceConfig;
@@ -734,7 +682,6 @@ function fromPublic(publicConfigJson) {
734
682
  };
735
683
  let defaultCrossChain = Stdlib_Option.getOr(publicConfig.defaultCrossChain, true);
736
684
  let userEntities = parseEntitiesFromJson(Stdlib_Option.getOr(publicConfig.entities, []), enumConfigsByName, globalStorage, defaultCrossChain);
737
- let allEntities = userEntities.concat([entityConfig]);
738
685
  let userEntitiesByName = Object.fromEntries(userEntities.map(entityConfig => [
739
686
  Utils.$$String.capitalize(entityConfig.name),
740
687
  entityConfig
@@ -747,6 +694,7 @@ function fromPublic(publicConfigJson) {
747
694
  name: publicConfig.name,
748
695
  description: publicConfig.description,
749
696
  handlers: Stdlib_Option.getOr(publicConfig.handlers, "src/handlers"),
697
+ resolvers: publicConfig.resolvers,
750
698
  contractHandlers: contractHandlers,
751
699
  shouldRollbackOnReorg: Stdlib_Option.getOr(publicConfig.rollbackOnReorg, true),
752
700
  shouldSaveFullHistory: Stdlib_Option.getOr(publicConfig.saveFullHistory, false),
@@ -754,6 +702,7 @@ function fromPublic(publicConfigJson) {
754
702
  storage: globalStorage,
755
703
  chainIdMode: Stdlib_Option.getOr(publicConfig.chainIdMode, "int32"),
756
704
  chainMap: chainMap,
705
+ contractMapping: contractMappingOf(chains),
757
706
  defaultChain: chains[0],
758
707
  ecosystem: ecosystem,
759
708
  enableRawEvents: Stdlib_Option.getOr(publicConfig.rawEvents, false),
@@ -765,11 +714,18 @@ function fromPublic(publicConfigJson) {
765
714
  isDev: Stdlib_Option.getOr(publicConfig.isDev, false),
766
715
  userEntitiesByName: userEntitiesByName,
767
716
  userEntities: userEntities,
768
- allEntities: allEntities,
769
717
  allEnums: allEnums
770
718
  };
771
719
  }
772
720
 
721
+ function isIsolatedMultichain(config) {
722
+ if (ChainMap.keys(config.chainMap).length > 1) {
723
+ return config.userEntities.every(entityConfig => !entityConfig.crossChain);
724
+ } else {
725
+ return false;
726
+ }
727
+ }
728
+
773
729
  function normalizeUserAddress(config, address) {
774
730
  let match = config.ecosystem.name;
775
731
  switch (match) {
@@ -908,6 +864,7 @@ function stripSensitiveData(json) {
908
864
  };
909
865
  if (typeof cloned === "object" && cloned !== null && !Array.isArray(cloned)) {
910
866
  Utils.Dict.deleteInPlace(cloned, "isDev");
867
+ Utils.Dict.deleteInPlace(cloned, "resolvers");
911
868
  stripChains(cloned["evm"]);
912
869
  stripChains(cloned["fuel"]);
913
870
  stripChains(cloned["svm"]);
@@ -1092,7 +1049,6 @@ function getPgUserEntities(config) {
1092
1049
  export {
1093
1050
  chainIdFieldName,
1094
1051
  chainIdColumnName,
1095
- EnvioAddresses,
1096
1052
  rpcSourceForSchema,
1097
1053
  rpcConfigSchema,
1098
1054
  chainContractSchema,
@@ -1118,7 +1074,9 @@ export {
1118
1074
  columnNameFormatSchema,
1119
1075
  publicConfigStorageSchema,
1120
1076
  publicConfigSchema,
1077
+ contractMappingOf,
1121
1078
  fromPublic,
1079
+ isIsolatedMultichain,
1122
1080
  normalizeUserAddress,
1123
1081
  normalizeSimulateAddress,
1124
1082
  getEventConfig,
@@ -1134,4 +1092,4 @@ export {
1134
1092
  load,
1135
1093
  getPgUserEntities,
1136
1094
  }
1137
- /* schema Not a pure module */
1095
+ /* rpcSourceForSchema Not a pure module */
@@ -0,0 +1,66 @@
1
+ // The contract name <-> id mapping every stored address row references: a
2
+ // name's position in `names` is the id `envio_contracts` holds and
3
+ // `envio_addresses.contract_id` points at. Ids must mean the same thing on
4
+ // every chain and across restarts, so a mapping is built once — from the whole
5
+ // config, never from a filtered subset — and read everywhere else.
6
+ type t = {
7
+ // Id order. Index i is the name of contract id i.
8
+ names: array<string>,
9
+ idByName: dict<int>,
10
+ }
11
+
12
+ let indexNames = (names: array<string>): t => {
13
+ let idByName = Dict.make()
14
+ names->Array.forEachWithIndex((name, id) => idByName->Dict.set(name, id))
15
+ {names, idByName}
16
+ }
17
+
18
+ // Ids are 0-based positions in a smallint column, so 32768 contracts fit.
19
+ let maxContracts = 32768
20
+
21
+ // Names in any order; the codec puts them in byte order so the ids never depend
22
+ // on the order contracts happen to be declared in.
23
+ let make = (~names: array<string>): t => {
24
+ let canonical = Core.getAddon().canonicalContractNames(names)
25
+ if canonical->Array.length > maxContracts {
26
+ JsError.throwWithMessage(
27
+ `The indexer declares ${canonical
28
+ ->Array.length
29
+ ->Int.toString} contracts, more than the ${maxContracts->Int.toString} a smallint contract id can hold.`,
30
+ )
31
+ }
32
+ indexNames(canonical)
33
+ }
34
+
35
+ // What a storage holds before it has been initialized: no contract has an id
36
+ // yet, so every lookup fails rather than resolving against a stale mapping.
37
+ let empty = indexNames([])
38
+
39
+ // A mapping read back from storage. Taken verbatim: the stored order *is* the
40
+ // id order the stored rows were written against, so re-canonicalizing it would
41
+ // paper over a mapping that no longer matches those rows.
42
+ let fromStoredNames = indexNames
43
+
44
+ let names = (mapping: t) => mapping.names
45
+
46
+ let idOfOrThrow = (mapping: t, name, ~context="") =>
47
+ switch mapping.idByName->Utils.Dict.dangerouslyGetNonOption(name) {
48
+ | Some(id) => id
49
+ | None =>
50
+ JsError.throwWithMessage(
51
+ `Contract "${name}"${context} is missing from the indexer's contract list.`,
52
+ )
53
+ }
54
+
55
+ let nameOfOrThrow = (mapping: t, id) =>
56
+ switch mapping.names->Array.get(id) {
57
+ | Some(name) => name
58
+ | None =>
59
+ JsError.throwWithMessage(
60
+ `Contract id ${id->Int.toString} is outside the indexer's contract list.`,
61
+ )
62
+ }
63
+
64
+ let isEqual = (a: t, b: t) =>
65
+ a.names->Array.length === b.names->Array.length &&
66
+ a.names->Array.everyWithIndex((name, idx) => b.names->Array.getUnsafe(idx) === name)
@@ -0,0 +1,73 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Core from "./Core.res.mjs";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+
6
+ function indexNames(names) {
7
+ let idByName = {};
8
+ names.forEach((name, id) => {
9
+ idByName[name] = id;
10
+ });
11
+ return {
12
+ names: names,
13
+ idByName: idByName
14
+ };
15
+ }
16
+
17
+ function make(names) {
18
+ let canonical = Core.getAddon().canonicalContractNames(names);
19
+ if (canonical.length > 32768) {
20
+ Stdlib_JsError.throwWithMessage(`The indexer declares ` + canonical.length.toString() + ` contracts, more than the ` + (32768).toString() + ` a smallint contract id can hold.`);
21
+ }
22
+ return indexNames(canonical);
23
+ }
24
+
25
+ let empty = indexNames([]);
26
+
27
+ function names(mapping) {
28
+ return mapping.names;
29
+ }
30
+
31
+ function idOfOrThrow(mapping, name, contextOpt) {
32
+ let context = contextOpt !== undefined ? contextOpt : "";
33
+ let id = mapping.idByName[name];
34
+ if (id !== undefined) {
35
+ return id;
36
+ } else {
37
+ return Stdlib_JsError.throwWithMessage(`Contract "` + name + `"` + context + ` is missing from the indexer's contract list.`);
38
+ }
39
+ }
40
+
41
+ function nameOfOrThrow(mapping, id) {
42
+ let name = mapping.names[id];
43
+ if (name !== undefined) {
44
+ return name;
45
+ } else {
46
+ return Stdlib_JsError.throwWithMessage(`Contract id ` + id.toString() + ` is outside the indexer's contract list.`);
47
+ }
48
+ }
49
+
50
+ function isEqual(a, b) {
51
+ if (a.names.length === b.names.length) {
52
+ return a.names.every((name, idx) => b.names[idx] === name);
53
+ } else {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ let maxContracts = 32768;
59
+
60
+ let fromStoredNames = indexNames;
61
+
62
+ export {
63
+ indexNames,
64
+ maxContracts,
65
+ make,
66
+ empty,
67
+ fromStoredNames,
68
+ names,
69
+ idOfOrThrow,
70
+ nameOfOrThrow,
71
+ isEqual,
72
+ }
73
+ /* empty Not a pure module */