envio 3.5.0 → 3.5.1-svm-alpha.2

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 (59) hide show
  1. package/evm.schema.json +7 -0
  2. package/fuel.schema.json +7 -0
  3. package/index.d.ts +33 -10
  4. package/package.json +6 -6
  5. package/src/ChainFetching.res +11 -21
  6. package/src/ChainFetching.res.mjs +12 -20
  7. package/src/ChainState.res +41 -4
  8. package/src/ChainState.res.mjs +39 -5
  9. package/src/ChainState.resi +10 -1
  10. package/src/Config.res +62 -3
  11. package/src/Config.res.mjs +49 -13
  12. package/src/EffectState.res +2 -2
  13. package/src/EffectState.res.mjs +2 -2
  14. package/src/EntityTables.res +32 -0
  15. package/src/EntityTables.res.mjs +44 -0
  16. package/src/Envio.res +7 -7
  17. package/src/Envio.res.mjs +5 -6
  18. package/src/FetchState.res +38 -75
  19. package/src/FetchState.res.mjs +20 -62
  20. package/src/Hasura.res +37 -3
  21. package/src/Hasura.res.mjs +23 -5
  22. package/src/InMemoryStore.res +95 -43
  23. package/src/InMemoryStore.res.mjs +69 -38
  24. package/src/IndexerState.res +30 -24
  25. package/src/IndexerState.res.mjs +20 -35
  26. package/src/IndexerState.resi +8 -6
  27. package/src/Internal.res +29 -18
  28. package/src/Internal.res.mjs +27 -10
  29. package/src/LoadLayer.res +39 -8
  30. package/src/LoadLayer.res.mjs +36 -9
  31. package/src/LoadLayer.resi +2 -0
  32. package/src/Persistence.res +12 -1
  33. package/src/PgStorage.res +183 -37
  34. package/src/PgStorage.res.mjs +149 -34
  35. package/src/PruneStaleHistory.res +1 -0
  36. package/src/PruneStaleHistory.res.mjs +2 -1
  37. package/src/Sink.res +3 -3
  38. package/src/Sink.res.mjs +2 -2
  39. package/src/TestIndexer.res +128 -17
  40. package/src/TestIndexer.res.mjs +75 -9
  41. package/src/UserContext.res +19 -4
  42. package/src/UserContext.res.mjs +15 -9
  43. package/src/Writing.res +8 -16
  44. package/src/Writing.res.mjs +10 -10
  45. package/src/bindings/ClickHouse.res +40 -4
  46. package/src/bindings/ClickHouse.res.mjs +37 -5
  47. package/src/bindings/Vitest.res +0 -3
  48. package/src/bindings/Vitest.res.mjs +2 -12
  49. package/src/db/EntityHistory.res +64 -21
  50. package/src/db/EntityHistory.res.mjs +43 -22
  51. package/src/db/InternalTable.res.mjs +31 -31
  52. package/src/db/Table.res +24 -1
  53. package/src/db/Table.res.mjs +26 -4
  54. package/src/sources/AddressStore.res +77 -26
  55. package/src/sources/AddressStore.res.mjs +21 -10
  56. package/src/sources/SvmHyperSyncClient.res +8 -7
  57. package/src/sources/SvmHyperSyncSource.res +38 -1
  58. package/src/sources/SvmHyperSyncSource.res.mjs +24 -0
  59. package/svm.schema.json +17 -4
@@ -16,11 +16,12 @@ function makeEnumConfig(name, variants) {
16
16
  };
17
17
  }
18
18
 
19
- function mkField(fieldName, fieldType, fieldSchema, $$default, isArrayOpt, isNullableOpt, isPrimaryKeyOpt, isIndexOpt, linkedEntity, description, postgresDbName, clickhouseDbName) {
19
+ function mkField(fieldName, fieldType, fieldSchema, $$default, isArrayOpt, isNullableOpt, isPrimaryKeyOpt, isIndexOpt, isChainIdOpt, linkedEntity, description, postgresDbName, clickhouseDbName) {
20
20
  let isArray = isArrayOpt !== undefined ? isArrayOpt : false;
21
21
  let isNullable = isNullableOpt !== undefined ? isNullableOpt : false;
22
22
  let isPrimaryKey = isPrimaryKeyOpt !== undefined ? isPrimaryKeyOpt : false;
23
23
  let isIndex = isIndexOpt !== undefined ? isIndexOpt : false;
24
+ let isChainId = isChainIdOpt !== undefined ? isChainIdOpt : false;
24
25
  return {
25
26
  TAG: "Field",
26
27
  _0: {
@@ -31,6 +32,7 @@ function mkField(fieldName, fieldType, fieldSchema, $$default, isArrayOpt, isNul
31
32
  isNullable: isNullable,
32
33
  isPrimaryKey: isPrimaryKey,
33
34
  isIndex: isIndex,
35
+ isChainId: isChainId,
34
36
  linkedEntity: linkedEntity,
35
37
  defaultValue: $$default,
36
38
  description: description,
@@ -225,6 +227,18 @@ function getDerivedFromFields(table) {
225
227
  });
226
228
  }
227
229
 
230
+ function getChainIdField(table) {
231
+ return Stdlib_Array.findMap(table.fields, field => {
232
+ if (field.TAG !== "Field") {
233
+ return;
234
+ }
235
+ let field$1 = field._0;
236
+ if (field$1.isChainId) {
237
+ return field$1;
238
+ }
239
+ });
240
+ }
241
+
228
242
  function getFieldByName(table, fieldName) {
229
243
  return table.fields.find(field => getUserDefinedFieldName(field) === fieldName);
230
244
  }
@@ -288,7 +302,8 @@ let queryFields = Utils.$$WeakMap.memoize(table => {
288
302
  return dict;
289
303
  });
290
304
 
291
- function makeRowsSchema(table, rowFieldName) {
305
+ function makeRowsSchema(table, rowFieldName, skipChainIdOpt) {
306
+ let skipChainId = skipChainIdOpt !== undefined ? skipChainIdOpt : false;
292
307
  return S$RescriptSchema.array(S$RescriptSchema.object(s => {
293
308
  let dict = {};
294
309
  table.fields.forEach(field => {
@@ -296,15 +311,20 @@ function makeRowsSchema(table, rowFieldName) {
296
311
  return;
297
312
  }
298
313
  let field$1 = field._0;
314
+ if (field$1.isChainId && skipChainId) {
315
+ return;
316
+ }
299
317
  dict[getApiFieldName(field$1)] = s.f(rowFieldName(field$1), field$1.fieldSchema);
300
318
  });
301
319
  return dict;
302
320
  }));
303
321
  }
304
322
 
305
- let rowsSchema = Utils.$$WeakMap.memoize(table => makeRowsSchema(table, getApiFieldName));
323
+ let rowsSchema = Utils.$$WeakMap.memoize(table => makeRowsSchema(table, getApiFieldName, undefined));
324
+
325
+ let pgRowsSchema = Utils.$$WeakMap.memoize(table => makeRowsSchema(table, getPgDbFieldName, undefined));
306
326
 
307
- let pgRowsSchema = Utils.$$WeakMap.memoize(table => makeRowsSchema(table, getPgDbFieldName));
327
+ let pgEntityRowsSchema = Utils.$$WeakMap.memoize(table => makeRowsSchema(table, getPgDbFieldName, true));
308
328
 
309
329
  let NonExistingTableField = /* @__PURE__ */Primitive_exceptions.create("Table.NonExistingTableField");
310
330
 
@@ -465,6 +485,7 @@ export {
465
485
  getNonDefaultFields,
466
486
  getLinkedEntityFields,
467
487
  getDerivedFromFields,
488
+ getChainIdField,
468
489
  getFieldByName,
469
490
  NoIdField,
470
491
  getIdFieldOrThrow,
@@ -477,6 +498,7 @@ export {
477
498
  makeRowsSchema,
478
499
  rowsSchema,
479
500
  pgRowsSchema,
501
+ pgEntityRowsSchema,
480
502
  NonExistingTableField,
481
503
  getUnfilteredCompositeIndexesUnsafe,
482
504
  toSqlParams,
@@ -7,10 +7,12 @@
7
7
  // (or address-valued param) isn't registered at the item's block.
8
8
  type t
9
9
 
10
- // A contract the chain has events for. An address registered for anything else
11
- // gets the `NoEvents` verdict: persisted so a future config that adds events
12
- // picks it up, but never fetched.
13
- type contract = {name: string, startBlock: option<int>}
10
+ // A contract an address may be registered for. Every config contract of every
11
+ // chain, since `context.chain.<Contract>.add` validates against that whole set
12
+ // — a name outside it makes the store throw. `dependsOnAddresses` is whether
13
+ // this chain fetches for the contract by address, which is what makes the store
14
+ // able to answer `fetchable` on a verdict.
15
+ type contract = {name: string, startBlock: option<int>, dependsOnAddresses: bool}
14
16
 
15
17
  type registration = {
16
18
  address: Address.t,
@@ -20,9 +22,10 @@ type registration = {
20
22
  }
21
23
 
22
24
  type verdict =
23
- | Added({effectiveStartBlock: int})
24
- // Contract has no events; tracked and persisted, never fetched.
25
- | NoEvents({effectiveStartBlock: int})
25
+ // `fetchable` is false when nothing on the chain is fetched by address for the
26
+ // contract: the address is still stored and persisted, so a config that later
27
+ // adds events picks it up on restart, but no partition is built from it.
28
+ | Added({effectiveStartBlock: int, fetchable: bool})
26
29
  // Already registered for the same contract.
27
30
  | Duplicate({effectiveStartBlock: int, existingEffectiveStartBlock: int})
28
31
  // Already registered for a different contract.
@@ -32,11 +35,22 @@ type verdict =
32
35
 
33
36
  type rawVerdict = {
34
37
  kind: string,
38
+ fetchable: bool,
35
39
  effectiveStartBlock: int,
36
40
  existingContractName: Null.t<string>,
37
41
  existingEffectiveStartBlock: Null.t<int>,
38
42
  }
39
43
 
44
+ // A registration the store handed over for persistence, paired with the
45
+ // checkpoint that owns its row. `checkpointIdx` indexes the block numbers
46
+ // passed to `drainForWrite` — the ids stay on the JS side.
47
+ type drainedAddress = {
48
+ address: Address.t,
49
+ contractName: string,
50
+ registrationBlock: int,
51
+ checkpointIdx: int,
52
+ }
53
+
40
54
  type makeSetOptions = {
41
55
  // Only addresses whose id is at or above this. Ids are handed out in
42
56
  // registration order, so a cursor read before a batch (`nextId`) selects
@@ -66,22 +80,37 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool, ~contracts: array
66
80
  }
67
81
  }
68
82
 
69
- // The contracts of a chain that have events, with the earliest block any of
70
- // their events may fire at. `None` means unrestricted, so it wins over any
71
- // `Some`: one registration without a start block makes the whole contract's
72
- // address gate open from the chain's start, and the per-registration start
73
- // block still holds back the registrations that declared one.
74
- let contractsOf = (~onEventRegistrations: array<Internal.onEventRegistration>): array<contract> => {
83
+ // Every contract an address may be registered for, with the earliest block any
84
+ // of this chain's events for it may fire at. `None` means unrestricted, so it
85
+ // wins over any `Some`: one registration without a start block makes the whole
86
+ // contract's address gate open from the chain's start, and the per-registration
87
+ // start block still holds back the registrations that declared one.
88
+ //
89
+ // A contract whose events are all wildcard is fetched without consulting
90
+ // addresses, so registering one changes nothing about what's queried — it
91
+ // carries `dependsOnAddresses: false` just like a contract named only by
92
+ // `configContractNames`. Either way the addresses are stored and persisted,
93
+ // never fetched.
94
+ let contractsOf = (
95
+ ~onEventRegistrations: array<Internal.onEventRegistration>,
96
+ ~configContractNames: array<string>,
97
+ ): array<contract> => {
75
98
  // Only ever holds a declared start block, so a missing key is unambiguous —
76
99
  // storing `None` in a dict would be indistinguishable from "not seen yet".
77
100
  let startBlocks: dict<int> = Dict.make()
78
101
  let unrestricted = Utils.Set.make()
102
+ let addressDependent = Utils.Set.make()
79
103
  let names = []
80
- onEventRegistrations->Array.forEach(reg => {
81
- let name = reg.eventConfig.contractName
104
+ let addName = name =>
82
105
  if !(names->Array.includes(name)) {
83
106
  names->Array.push(name)->ignore
84
107
  }
108
+ onEventRegistrations->Array.forEach(reg => {
109
+ let name = reg.eventConfig.contractName
110
+ addName(name)
111
+ if reg.dependsOnAddresses {
112
+ addressDependent->Utils.Set.add(name)->ignore
113
+ }
85
114
  switch reg.startBlock {
86
115
  | None => unrestricted->Utils.Set.add(name)->ignore
87
116
  | Some(startBlock) =>
@@ -94,11 +123,13 @@ let contractsOf = (~onEventRegistrations: array<Internal.onEventRegistration>):
94
123
  )
95
124
  }
96
125
  })
126
+ configContractNames->Array.forEach(addName)
97
127
  names->Array.map(name => {
98
128
  name,
99
129
  startBlock: unrestricted->Utils.Set.has(name)
100
130
  ? None
101
131
  : startBlocks->Utils.Dict.dangerouslyGetNonOption(name),
132
+ dependsOnAddresses: addressDependent->Utils.Set.has(name),
102
133
  })
103
134
  }
104
135
 
@@ -112,10 +143,24 @@ let contractsOf = (~onEventRegistrations: array<Internal.onEventRegistration>):
112
143
  // come from that chain's one store — ids are store-scoped, so sets from
113
144
  // different stores can't be merged.
114
145
  @send external makeSetOf: (t, array<Address.t>) => AddressSet.t = "makeSetOf"
115
- @send external registerBatchRaw: (t, array<registration>) => array<rawVerdict> = "registerBatch"
116
- @send external makeSetRaw: (t, string, makeSetOptions) => AddressSet.t = "makeSet"
117
146
  @send
118
- external startBlockGroups: (t, string) => array<AddressSet.startBlockGroup> = "startBlockGroups"
147
+ external registerBatchRaw: (t, array<registration>) => array<rawVerdict> = "registerBatch"
148
+ @send external seedBatchRaw: (t, array<registration>) => array<rawVerdict> = "seedBatch"
149
+
150
+ // Drains the registrations awaiting persistence at or below the given block —
151
+ // what the batch being written covers — pairing each with the checkpoint at its
152
+ // registration block. Later registrations stay pending. Throws, with the queue
153
+ // untouched, when a drained registration's block has no checkpoint in the batch.
154
+ @send
155
+ external drainForWrite: (t, int, array<int>) => array<drainedAddress> = "drainForWrite"
156
+
157
+ // How many registrations await persistence — lets a caller skip the work of
158
+ // assembling what `drainForWrite` needs.
159
+ @send external pendingCount: t => int = "pendingCount"
160
+
161
+ // The registrations still awaiting persistence. For assertions.
162
+ @send external pendingEntries: t => array<Internal.indexingContract> = "pendingEntries"
163
+ @send external makeSetRaw: (t, string, makeSetOptions) => AddressSet.t = "makeSet"
119
164
  @send external contractCount: (t, string) => int = "contractCount"
120
165
  @send external size: t => int = "size"
121
166
 
@@ -132,12 +177,9 @@ external startBlockGroups: (t, string) => array<AddressSet.startBlockGroup> = "s
132
177
 
133
178
  @send external contractAddresses: (t, string) => array<Address.t> = "contractAddresses"
134
179
 
135
- @send external entries: t => array<Internal.indexingContract> = "entries"
136
-
137
180
  let toVerdict = (raw: rawVerdict): verdict =>
138
181
  switch raw.kind {
139
- | "added" => Added({effectiveStartBlock: raw.effectiveStartBlock})
140
- | "noEvents" => NoEvents({effectiveStartBlock: raw.effectiveStartBlock})
182
+ | "added" => Added({effectiveStartBlock: raw.effectiveStartBlock, fetchable: raw.fetchable})
141
183
  | "duplicate" =>
142
184
  Duplicate({
143
185
  effectiveStartBlock: raw.effectiveStartBlock,
@@ -148,13 +190,22 @@ let toVerdict = (raw: rawVerdict): verdict =>
148
190
  | kind => JsError.throwWithMessage(`Unexpected address registration verdict "${kind}"`)
149
191
  }
150
192
 
151
- // Registers a batch, resolving each address against both the store and the
152
- // batch's own earlier entries — so two contracts claiming one address inside a
153
- // single batch conflict just as they would across batches. Verdicts come back
154
- // in the batch's order.
193
+ // Registers dynamic registrations, resolving each address against both the
194
+ // store and the batch's own earlier entries — so two contracts claiming one
195
+ // address inside a single batch conflict just as they would across batches.
196
+ // Verdicts come back in the batch's order. What it adds is pending persistence
197
+ // until a batch write drains it.
198
+ //
199
+ // An unknown contract name throws with none of the batch applied.
155
200
  let registerBatch = (store: t, registrations: array<registration>): array<verdict> =>
156
201
  store->registerBatchRaw(registrations)->Array.map(toVerdict)
157
202
 
203
+ // `registerBatch` for addresses the database already holds — config addresses
204
+ // and the dynamic ones a resume restores. Nothing is marked pending, so nothing
205
+ // is ever written back.
206
+ let seedBatch = (store: t, registrations: array<registration>): array<verdict> =>
207
+ store->seedBatchRaw(registrations)->Array.map(toVerdict)
208
+
158
209
  let makeSet = (store: t, ~contractName, ~options={}: makeSetOptions) =>
159
210
  store->makeSetRaw(contractName, options)
160
211
 
@@ -17,14 +17,22 @@ function make(ecosystem, shouldChecksum, contracts) {
17
17
  }
18
18
  }
19
19
 
20
- function contractsOf(onEventRegistrations) {
20
+ function contractsOf(onEventRegistrations, configContractNames) {
21
21
  let startBlocks = {};
22
22
  let unrestricted = new Set();
23
+ let addressDependent = new Set();
23
24
  let names = [];
24
- onEventRegistrations.forEach(reg => {
25
- let name = reg.eventConfig.contractName;
25
+ let addName = name => {
26
26
  if (!names.includes(name)) {
27
27
  names.push(name);
28
+ return;
29
+ }
30
+ };
31
+ onEventRegistrations.forEach(reg => {
32
+ let name = reg.eventConfig.contractName;
33
+ addName(name);
34
+ if (reg.dependsOnAddresses) {
35
+ addressDependent.add(name);
28
36
  }
29
37
  let startBlock = reg.startBlock;
30
38
  if (startBlock !== undefined) {
@@ -34,9 +42,11 @@ function contractsOf(onEventRegistrations) {
34
42
  }
35
43
  unrestricted.add(name);
36
44
  });
45
+ configContractNames.forEach(addName);
37
46
  return names.map(name => ({
38
47
  name: name,
39
- startBlock: unrestricted.has(name) ? undefined : startBlocks[name]
48
+ startBlock: unrestricted.has(name) ? undefined : startBlocks[name],
49
+ dependsOnAddresses: addressDependent.has(name)
40
50
  }));
41
51
  }
42
52
 
@@ -46,7 +56,8 @@ function toVerdict(raw) {
46
56
  case "added" :
47
57
  return {
48
58
  TAG: "Added",
49
- effectiveStartBlock: raw.effectiveStartBlock
59
+ effectiveStartBlock: raw.effectiveStartBlock,
60
+ fetchable: raw.fetchable
50
61
  };
51
62
  case "conflict" :
52
63
  return {
@@ -61,11 +72,6 @@ function toVerdict(raw) {
61
72
  };
62
73
  case "invalid" :
63
74
  return "Invalid";
64
- case "noEvents" :
65
- return {
66
- TAG: "NoEvents",
67
- effectiveStartBlock: raw.effectiveStartBlock
68
- };
69
75
  default:
70
76
  return Stdlib_JsError.throwWithMessage(`Unexpected address registration verdict "` + kind + `"`);
71
77
  }
@@ -75,6 +81,10 @@ function registerBatch(store, registrations) {
75
81
  return store.registerBatch(registrations).map(toVerdict);
76
82
  }
77
83
 
84
+ function seedBatch(store, registrations) {
85
+ return store.seedBatch(registrations).map(toVerdict);
86
+ }
87
+
78
88
  function makeSet(store, contractName, optionsOpt) {
79
89
  let options = optionsOpt !== undefined ? optionsOpt : ({});
80
90
  return store.makeSet(contractName, options);
@@ -89,6 +99,7 @@ export {
89
99
  contractsOf,
90
100
  toVerdict,
91
101
  registerBatch,
102
+ seedBatch,
92
103
  makeSet,
93
104
  get,
94
105
  }
@@ -110,13 +110,13 @@ module QueryTypes = {
110
110
 
111
111
  type fieldSelection = {block?: array<blockField>, transaction?: array<transactionField>}
112
112
 
113
- /** Filter for selecting instructions. All non-empty fields are AND-ed: an
114
- instruction must match at least one value in every non-empty field.
113
+ /** Filter for selecting instruction calls. All non-empty fields are AND-ed:
114
+ an instruction must match at least one value in every non-empty field.
115
115
 
116
116
  Discriminator filters (d1..d8) take hex-encoded byte prefixes ("0x" optional).
117
117
  Account filters (a0..a9) take base58 pubkey strings. */
118
118
  type instructionSelection = {
119
- programId?: array<string>,
119
+ executingAccount?: array<string>,
120
120
  d1?: array<string>,
121
121
  d2?: array<string>,
122
122
  d4?: array<string>,
@@ -129,7 +129,7 @@ module QueryTypes = {
129
129
  type query = {
130
130
  fromSlot: int,
131
131
  toSlot?: int,
132
- instructions?: array<instructionSelection>,
132
+ instructionCalls?: array<instructionSelection>,
133
133
  includeAllBlocks?: bool,
134
134
  fields?: fieldSelection,
135
135
  maxNumBlocks?: int,
@@ -159,15 +159,16 @@ module ResponseTypes = {
159
159
  slot: int,
160
160
  transactionIndex: int,
161
161
  instructionAddress: array<int>,
162
- programId: string,
163
- accounts: array<string>,
162
+ executingAccount: string,
163
+ accountArguments: array<string>,
164
164
  data: string,
165
165
  d1?: string,
166
166
  d2?: string,
167
167
  d4?: string,
168
168
  d8?: string,
169
169
  isInner: bool,
170
- isCommitted: bool,
170
+ // True when the parent transaction succeeded.
171
+ txSuccess: bool,
171
172
  }
172
173
 
173
174
  type queryResponseData = {
@@ -67,8 +67,20 @@ let toSvmInstruction = (
67
67
  ),
68
68
  }
69
69
 
70
+ // The server responds with next_slot == from_slot when the requested range is
71
+ // beyond its queryable tip (its /height can run ahead of query availability,
72
+ // routinely by a slot at the head and by much more during an ingest incident).
73
+ exception NonAdvancingResponse({fromSlot: int, nextSlot: int})
74
+
70
75
  let make = (
71
- {chainId, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis, addressStore}: options,
76
+ {
77
+ chainId,
78
+ endpointUrl,
79
+ apiToken,
80
+ onEventRegistrations,
81
+ clientTimeoutMillis,
82
+ addressStore,
83
+ }: options,
72
84
  ): t => {
73
85
  let name = "SvmHyperSync"
74
86
 
@@ -131,6 +143,31 @@ let make = (
131
143
  let pageFetchTime = pageFetchRef->Performance.secondsSince
132
144
  let requestStats = [{Source.method: "getInstructions", seconds: pageFetchTime}]
133
145
 
146
+ // Accepting a non-advancing page would re-issue the identical query in a
147
+ // hot loop with no backoff (and its zero-width range would poison the
148
+ // partition's event-density average), so retry it with a bounded backoff
149
+ // until the server tip moves past the requested slot.
150
+ if resp.nextSlot <= fromBlock {
151
+ throw(
152
+ Source.GetItemsError(
153
+ Source.FailedGettingItems({
154
+ exn: NonAdvancingResponse({fromSlot: fromBlock, nextSlot: resp.nextSlot}),
155
+ attemptedToBlock: toBlock->Option.getOr(knownHeight),
156
+ retry: WithBackoff({
157
+ message: `SVM HyperSync has no data for slot ${fromBlock->Int.toString} yet (server tip is behind the reported height). Retrying until the server catches up.`,
158
+ backoffMillis: Pervasives.min(
159
+ switch retry {
160
+ | 0 => 500
161
+ | _ => 1000 * retry
162
+ },
163
+ 5_000,
164
+ ),
165
+ }),
166
+ }),
167
+ ),
168
+ )
169
+ }
170
+
134
171
  let parsingRef = Performance.now()
135
172
 
136
173
  // Per-slot blockTime lookup from the response's `blocks` table, for the
@@ -3,6 +3,7 @@
3
3
  import * as Source from "./Source.res.mjs";
4
4
  import * as Performance from "../bindings/Performance.res.mjs";
5
5
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
6
7
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
8
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
9
  import * as SvmHyperSyncClient from "./SvmHyperSyncClient.res.mjs";
@@ -55,6 +56,8 @@ function toSvmInstruction(item, programName, instructionName) {
55
56
  };
56
57
  }
57
58
 
59
+ let NonAdvancingResponse = /* @__PURE__ */Primitive_exceptions.create("SvmHyperSyncSource.NonAdvancingResponse");
60
+
58
61
  function make(param) {
59
62
  let onEventRegistrations = param.onEventRegistrations;
60
63
  let chainId = param.chainId;
@@ -97,6 +100,26 @@ function make(param) {
97
100
  method: "getInstructions",
98
101
  seconds: pageFetchTime
99
102
  }];
103
+ if (resp.nextSlot <= fromBlock) {
104
+ throw {
105
+ RE_EXN_ID: Source.GetItemsError,
106
+ _1: {
107
+ TAG: "FailedGettingItems",
108
+ exn: {
109
+ RE_EXN_ID: NonAdvancingResponse,
110
+ fromSlot: fromBlock,
111
+ nextSlot: resp.nextSlot
112
+ },
113
+ attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
114
+ retry: {
115
+ TAG: "WithBackoff",
116
+ message: `SVM HyperSync has no data for slot ` + fromBlock.toString() + ` yet (server tip is behind the reported height). Retrying until the server catches up.`,
117
+ backoffMillis: Primitive_int.min(retry !== 0 ? 1000 * retry | 0 : 500, 5000)
118
+ }
119
+ },
120
+ Error: new Error()
121
+ };
122
+ }
100
123
  let parsingRef = Performance.now();
101
124
  let blockTimeBySlot = {};
102
125
  resp.blocks.forEach(b => {
@@ -267,6 +290,7 @@ export {
267
290
  synthLogIndex,
268
291
  parseDecoded,
269
292
  toSvmInstruction,
293
+ NonAdvancingResponse,
270
294
  make,
271
295
  }
272
296
  /* SvmHyperSyncClient Not a pure module */
package/svm.schema.json CHANGED
@@ -49,6 +49,13 @@
49
49
  }
50
50
  ]
51
51
  },
52
+ "disable_default_cross_chain": {
53
+ "description": "Make entities and effect caches per-chain instead of shared across every chain (recommended). Sharing then becomes explicit — add `@crossChain` to an entity in schema.graphql or `crossChain: true` to an effect. (default: false)",
54
+ "type": [
55
+ "boolean",
56
+ "null"
57
+ ]
58
+ },
52
59
  "ecosystem": {
53
60
  "description": "Ecosystem of the project.",
54
61
  "$ref": "#/$defs/EcosystemTag"
@@ -264,8 +271,15 @@
264
271
  "type": "object",
265
272
  "properties": {
266
273
  "hypersync_config": {
267
- "description": "HyperSync Config for fetching historical instructions on this chain.",
268
- "$ref": "#/$defs/HypersyncConfig"
274
+ "description": "HyperSync Config for fetching historical instructions on this chain. Defaults to the public Solana mainnet HyperSync endpoint.",
275
+ "anyOf": [
276
+ {
277
+ "$ref": "#/$defs/HypersyncConfig"
278
+ },
279
+ {
280
+ "type": "null"
281
+ }
282
+ ]
269
283
  },
270
284
  "programs": {
271
285
  "description": "Solana programs to index on this chain.",
@@ -277,7 +291,6 @@
277
291
  },
278
292
  "additionalProperties": false,
279
293
  "required": [
280
- "hypersync_config",
281
294
  "programs"
282
295
  ]
283
296
  },
@@ -285,7 +298,7 @@
285
298
  "type": "object",
286
299
  "properties": {
287
300
  "url": {
288
- "description": "URL of the HyperSync endpoint (default: the public Solana HyperSync endpoint at https://solana.hypersync.xyz)",
301
+ "description": "URL of the HyperSync endpoint. Defaults to the public Solana mainnet HyperSync endpoint when the hypersync_config block is omitted.",
289
302
  "type": "string"
290
303
  }
291
304
  },