@subsquid/portal-client 0.3.2 → 0.4.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 (131) hide show
  1. package/lib/client.d.ts +40 -18
  2. package/lib/client.d.ts.map +1 -1
  3. package/lib/client.js +166 -188
  4. package/lib/client.js.map +1 -1
  5. package/lib/query/common/data.d.ts +21 -0
  6. package/lib/query/common/data.d.ts.map +1 -0
  7. package/lib/query/common/data.js +3 -0
  8. package/lib/query/common/data.js.map +1 -0
  9. package/lib/query/common/query.d.ts +7 -0
  10. package/lib/query/common/query.d.ts.map +1 -0
  11. package/lib/query/common/query.js +3 -0
  12. package/lib/query/common/query.js.map +1 -0
  13. package/lib/query/evm/data.d.ts +159 -0
  14. package/lib/query/evm/data.d.ts.map +1 -0
  15. package/lib/query/evm/data.js +3 -0
  16. package/lib/query/evm/data.js.map +1 -0
  17. package/lib/query/evm/fields.d.ts +50 -0
  18. package/lib/query/evm/fields.d.ts.map +1 -0
  19. package/lib/query/evm/fields.js +3 -0
  20. package/lib/query/evm/fields.js.map +1 -0
  21. package/lib/query/evm/index.d.ts +4 -0
  22. package/lib/query/evm/index.d.ts.map +1 -0
  23. package/lib/query/evm/index.js +7 -0
  24. package/lib/query/evm/index.js.map +1 -0
  25. package/lib/query/evm/query.d.ts +54 -0
  26. package/lib/query/evm/query.d.ts.map +1 -0
  27. package/lib/query/evm/query.js +3 -0
  28. package/lib/query/evm/query.js.map +1 -0
  29. package/lib/query/evm/schema.d.ts +322 -0
  30. package/lib/query/evm/schema.d.ts.map +1 -0
  31. package/lib/query/evm/schema.js +224 -0
  32. package/lib/query/evm/schema.js.map +1 -0
  33. package/lib/query/index.d.ts +576 -8
  34. package/lib/query/index.d.ts.map +1 -1
  35. package/lib/query/index.js +15 -24
  36. package/lib/query/index.js.map +1 -1
  37. package/lib/query/solana/data.d.ts +115 -0
  38. package/lib/query/solana/data.d.ts.map +1 -0
  39. package/lib/query/solana/data.js +3 -0
  40. package/lib/query/solana/data.js.map +1 -0
  41. package/lib/query/solana/fields.d.ts +31 -0
  42. package/lib/query/solana/fields.d.ts.map +1 -0
  43. package/lib/query/solana/fields.js +3 -0
  44. package/lib/query/solana/fields.js.map +1 -0
  45. package/lib/query/solana/index.d.ts +4 -0
  46. package/lib/query/solana/index.d.ts.map +1 -0
  47. package/lib/query/solana/index.js +7 -0
  48. package/lib/query/solana/index.js.map +1 -0
  49. package/lib/query/solana/query.d.ts +74 -0
  50. package/lib/query/solana/query.d.ts.map +1 -0
  51. package/lib/query/solana/query.js +3 -0
  52. package/lib/query/solana/query.js.map +1 -0
  53. package/lib/query/solana/schema.d.ts +160 -0
  54. package/lib/query/solana/schema.d.ts.map +1 -0
  55. package/lib/query/solana/schema.js +130 -0
  56. package/lib/query/solana/schema.js.map +1 -0
  57. package/lib/query/substrate/data.d.ts +98 -0
  58. package/lib/query/substrate/data.d.ts.map +1 -0
  59. package/lib/query/substrate/data.js +3 -0
  60. package/lib/query/substrate/data.js.map +1 -0
  61. package/lib/query/substrate/fields.d.ts +22 -0
  62. package/lib/query/substrate/fields.d.ts.map +1 -0
  63. package/lib/query/substrate/fields.js +3 -0
  64. package/lib/query/substrate/fields.js.map +1 -0
  65. package/lib/query/substrate/index.d.ts +4 -0
  66. package/lib/query/substrate/index.d.ts.map +1 -0
  67. package/lib/query/substrate/index.js +7 -0
  68. package/lib/query/substrate/index.js.map +1 -0
  69. package/lib/query/substrate/query.d.ts +50 -0
  70. package/lib/query/substrate/query.d.ts.map +1 -0
  71. package/lib/query/substrate/query.js +3 -0
  72. package/lib/query/substrate/query.js.map +1 -0
  73. package/lib/query/substrate/schema.d.ts +102 -0
  74. package/lib/query/substrate/schema.d.ts.map +1 -0
  75. package/lib/query/substrate/schema.js +88 -0
  76. package/lib/query/substrate/schema.js.map +1 -0
  77. package/lib/query/type-util.d.ts +13 -0
  78. package/lib/query/type-util.d.ts.map +1 -0
  79. package/lib/query/type-util.js +3 -0
  80. package/lib/query/type-util.js.map +1 -0
  81. package/lib/query/util.d.ts +5 -0
  82. package/lib/query/util.d.ts.map +1 -0
  83. package/lib/query/util.js +23 -0
  84. package/lib/query/util.js.map +1 -0
  85. package/lib/util.d.ts +19 -0
  86. package/lib/util.d.ts.map +1 -0
  87. package/lib/util.js +57 -0
  88. package/lib/util.js.map +1 -0
  89. package/package.json +3 -3
  90. package/src/client.ts +231 -247
  91. package/src/query/common/data.ts +24 -0
  92. package/src/query/common/query.ts +6 -0
  93. package/src/query/evm/data.ts +182 -0
  94. package/src/query/evm/fields.ts +105 -0
  95. package/src/query/evm/index.ts +3 -0
  96. package/src/query/evm/query.ts +59 -0
  97. package/src/query/evm/schema.ts +277 -0
  98. package/src/query/index.ts +19 -36
  99. package/src/query/solana/data.ts +132 -0
  100. package/src/query/solana/fields.ts +42 -0
  101. package/src/query/solana/index.ts +3 -0
  102. package/src/query/solana/query.ts +89 -0
  103. package/src/query/solana/schema.ts +164 -0
  104. package/src/query/substrate/data.ts +101 -0
  105. package/src/query/substrate/fields.ts +30 -0
  106. package/src/query/substrate/index.ts +3 -0
  107. package/src/query/substrate/query.ts +60 -0
  108. package/src/query/substrate/schema.ts +114 -0
  109. package/src/query/type-util.ts +25 -0
  110. package/src/query/util.ts +23 -0
  111. package/src/util.ts +56 -0
  112. package/lib/query/common.d.ts +0 -56
  113. package/lib/query/common.d.ts.map +0 -1
  114. package/lib/query/common.js +0 -16
  115. package/lib/query/common.js.map +0 -1
  116. package/lib/query/evm.d.ts +0 -267
  117. package/lib/query/evm.d.ts.map +0 -1
  118. package/lib/query/evm.js +0 -245
  119. package/lib/query/evm.js.map +0 -1
  120. package/lib/query/solana.d.ts +0 -224
  121. package/lib/query/solana.d.ts.map +0 -1
  122. package/lib/query/solana.js +0 -121
  123. package/lib/query/solana.js.map +0 -1
  124. package/lib/query/substrate.d.ts +0 -173
  125. package/lib/query/substrate.d.ts.map +0 -1
  126. package/lib/query/substrate.js +0 -71
  127. package/lib/query/substrate.js.map +0 -1
  128. package/src/query/common.ts +0 -83
  129. package/src/query/evm.ts +0 -677
  130. package/src/query/solana.ts +0 -438
  131. package/src/query/substrate.ts +0 -288
package/src/client.ts CHANGED
@@ -1,16 +1,8 @@
1
1
  import {HttpBody, HttpClient, HttpClientOptions, HttpError, HttpResponse, RequestOptions} from '@subsquid/http-client'
2
- import {
3
- addErrorContext,
4
- createFuture,
5
- Future,
6
- last,
7
- unexpectedCase,
8
- wait,
9
- withErrorContext,
10
- } from '@subsquid/util-internal'
11
- import {Readable} from 'stream'
12
- import {GetBlock, evm, getBlockSchema, PortalBlock, PortalQuery, Query, solana, substrate} from './query'
2
+ import {addErrorContext, last, unexpectedCase, wait, withErrorContext} from '@subsquid/util-internal'
13
3
  import {cast} from '@subsquid/util-internal-validation'
4
+ import {Semaphore, Timer} from './util'
5
+ import {getQuery, type AnyQuery, type GetQueryBlock} from './query'
14
6
 
15
7
  export * from './query'
16
8
 
@@ -29,19 +21,19 @@ export interface PortalClientOptions {
29
21
 
30
22
  /**
31
23
  * Minimum number of bytes to return.
32
- * @default 10_485_760 (10MB)
24
+ * @deprecated not used
33
25
  */
34
26
  minBytes?: number
35
27
 
36
28
  /**
37
29
  * Maximum number of bytes to return.
38
- * @default minBytes
30
+ * @default 52_428_800 (50MB)
39
31
  */
40
32
  maxBytes?: number
41
33
 
42
34
  /**
43
35
  * Maximum time between stream data in milliseconds for return.
44
- * @default 300
36
+ * @default 500
45
37
  */
46
38
  maxIdleTime?: number
47
39
 
@@ -70,7 +62,6 @@ export interface PortalRequestOptions {
70
62
  export interface PortalStreamOptions {
71
63
  request?: Omit<PortalRequestOptions, 'abort'>
72
64
 
73
- minBytes?: number
74
65
  maxBytes?: number
75
66
  maxIdleTime?: number
76
67
  maxWaitTime?: number
@@ -78,10 +69,34 @@ export interface PortalStreamOptions {
78
69
  headPollInterval?: number
79
70
  }
80
71
 
72
+ export interface PortalStreamBaseResponse {
73
+ headNumber?: number
74
+ finalizedHeadNumber?: number
75
+ finalizedHeadHash?: string
76
+ }
77
+
78
+ export interface PortalStreamDataResponse extends PortalStreamBaseResponse {
79
+ type: 'data'
80
+ data: AsyncIterable<Uint8Array> | null
81
+ }
82
+
83
+ export interface PortalStreamNoDataResponse extends PortalStreamBaseResponse {
84
+ type: 'no-data'
85
+ }
86
+
87
+ export interface PortalStreamForkResponse extends PortalStreamBaseResponse {
88
+ type: 'fork'
89
+ previousBlocks: BlockRef[]
90
+ }
91
+
92
+ export type PortalStreamResponse = PortalStreamDataResponse | PortalStreamNoDataResponse | PortalStreamForkResponse
93
+
81
94
  export type PortalStreamData<B> = {
82
95
  blocks: B[]
83
- finalizedHead?: BlockRef
84
96
  meta: {
97
+ finalizedHeadNumber?: number
98
+ finalizedHeadHash?: string
99
+ headNumber?: number
85
100
  bytes: number
86
101
  }
87
102
  }
@@ -97,7 +112,6 @@ export class PortalClient {
97
112
  private url: URL
98
113
  private client: HttpClient
99
114
  private headPollInterval: number
100
- private minBytes: number
101
115
  private maxBytes: number
102
116
  private maxIdleTime: number
103
117
  private maxWaitTime: number
@@ -106,9 +120,8 @@ export class PortalClient {
106
120
  this.url = new URL(options.url)
107
121
  this.client = options.http instanceof HttpClient ? options.http : new HttpClient(options.http)
108
122
  this.headPollInterval = options.headPollInterval ?? 0
109
- this.minBytes = options.minBytes ?? 10 * 1024 * 1024
110
- this.maxBytes = options.maxBytes ?? this.minBytes
111
- this.maxIdleTime = options.maxIdleTime ?? 300
123
+ this.maxBytes = options.maxBytes ?? 50 * 1024 * 1024
124
+ this.maxIdleTime = options.maxIdleTime ?? 500
112
125
  this.maxWaitTime = options.maxWaitTime ?? 5_000
113
126
  }
114
127
 
@@ -122,92 +135,56 @@ export class PortalClient {
122
135
  return u.toString()
123
136
  }
124
137
 
125
- async getHead(options?: PortalRequestOptions): Promise<BlockRef | undefined> {
126
- const res = await this.request('GET', this.getDatasetUrl('head'), options)
138
+ async getHead(options?: PortalRequestOptions, finalized?: boolean): Promise<BlockRef | undefined> {
139
+ const res = await this.request('GET', this.getDatasetUrl(!finalized ? 'head' : 'finalized-head'), options)
127
140
  return res.body ?? undefined
128
141
  }
129
142
 
130
- async getFinalizedHead(options?: PortalRequestOptions): Promise<BlockRef | undefined> {
131
- const res = await this.request('GET', this.getDatasetUrl('finalized-head'), options)
132
- return res.body ?? undefined
143
+ getFinalizedHead(options?: PortalRequestOptions): Promise<BlockRef | undefined> {
144
+ return this.getHead(options, true)
133
145
  }
134
146
 
135
147
  /**
136
- * @deprecated
148
+ * Waits until the finalized head reaches or exceeds the specified block number.
149
+ * @param blockNumber - The block number to wait for finalization
150
+ * @param options - Request options including abort signal and poll interval
151
+ * @returns The finalized head once it reaches the target block number
137
152
  */
138
- async getFinalizedHeight(options?: PortalRequestOptions): Promise<number> {
139
- let {body} = await this.request<string>('GET', this.getDatasetUrl('finalized-stream/height'), options)
140
- let height = parseInt(body)
141
- return height
142
- }
143
-
144
- getFinalizedQuery<Q extends Query>(query: Q, options?: PortalRequestOptions): Promise<GetBlock<Q>[]> {
145
- // FIXME: is it needed or it is better to always use stream?
146
- return this.request<Buffer>('POST', this.getDatasetUrl(`finalized-stream`), {
147
- ...options,
148
- json: query,
149
- })
150
- .catch(
151
- withErrorContext({
152
- archiveQuery: query,
153
- })
154
- )
155
- .then((res) => {
156
- let blocks = res.body
157
- .toString('utf8')
158
- .trimEnd()
159
- .split('\n')
160
- .map((line) => JSON.parse(line))
161
- return blocks
162
- })
163
- }
153
+ async waitForFinalization(
154
+ blockNumber: number,
155
+ options?: PortalRequestOptions & {pollInterval?: number}
156
+ ): Promise<BlockRef> {
157
+ let {pollInterval = this.headPollInterval || 1000, ...requestOptions} = options ?? {}
158
+
159
+ while (true) {
160
+ options?.abort?.throwIfAborted()
161
+
162
+ let head = await this.getFinalizedHead(requestOptions)
163
+ if (head && head.number >= blockNumber) {
164
+ return head
165
+ }
164
166
 
165
- getQuery<Q extends PortalQuery = PortalQuery, R extends PortalBlock = PortalBlock>(
166
- query: Q,
167
- options?: PortalRequestOptions
168
- ): Promise<R[]> {
169
- // FIXME: is it needed or it is better to always use stream?
170
- return this.request<Buffer>('POST', this.getDatasetUrl(`stream`), {
171
- ...options,
172
- json: query,
173
- })
174
- .catch(
175
- withErrorContext({
176
- archiveQuery: query,
177
- })
178
- )
179
- .then((res) => {
180
- let blocks = res.body
181
- .toString('utf8')
182
- .trimEnd()
183
- .split('\n')
184
- .map((line) => JSON.parse(line))
185
- return blocks
186
- })
167
+ await wait(pollInterval, options?.abort)
168
+ }
187
169
  }
188
170
 
189
- getFinalizedStream<Q extends evm.Query | solana.Query | substrate.Query>(
171
+ getStream<Q extends AnyQuery>(
190
172
  query: Q,
191
- options?: PortalStreamOptions
192
- ): PortalStream<GetBlock<Q>> {
173
+ options?: PortalStreamOptions,
174
+ finalized?: boolean
175
+ ): PortalStream<GetQueryBlock<Q>> {
193
176
  return createPortalStream(query, this.getStreamOptions(options), async (q, o) =>
194
- this.getStreamRequest('finalized-stream', q, o)
177
+ this.getStreamRequest(!finalized ? 'stream' : 'finalized-stream', q, o)
195
178
  )
196
179
  }
197
180
 
198
- getStream<Q extends evm.Query | solana.Query | substrate.Query>(
199
- query: Q,
200
- options?: PortalStreamOptions
201
- ): PortalStream<GetBlock<Q>> {
202
- return createPortalStream(query, this.getStreamOptions(options), async (q, o) =>
203
- this.getStreamRequest('stream', q, o)
204
- )
181
+ getFinalizedStream<Q extends AnyQuery>(query: Q, options?: PortalStreamOptions): PortalStream<GetQueryBlock<Q>> {
182
+ return this.getStream(query, options, true)
205
183
  }
206
184
 
207
185
  private getStreamOptions(options?: PortalStreamOptions) {
208
186
  let {
209
187
  headPollInterval = this.headPollInterval,
210
- minBytes = this.minBytes,
211
188
  maxBytes = this.maxBytes,
212
189
  maxIdleTime = this.maxIdleTime,
213
190
  maxWaitTime = this.maxWaitTime,
@@ -216,7 +193,6 @@ export class PortalClient {
216
193
 
217
194
  return {
218
195
  headPollInterval,
219
- minBytes,
220
196
  maxBytes,
221
197
  maxIdleTime,
222
198
  maxWaitTime,
@@ -224,9 +200,13 @@ export class PortalClient {
224
200
  }
225
201
  }
226
202
 
227
- private async getStreamRequest(path: string, query: PortalQuery, options?: PortalRequestOptions) {
203
+ private async getStreamRequest(
204
+ path: string,
205
+ query: AnyQuery,
206
+ options?: PortalRequestOptions
207
+ ): Promise<PortalStreamResponse> {
228
208
  try {
229
- let res = await this.request<Readable | undefined>('POST', this.getDatasetUrl(path), {
209
+ let res = await this.request<AsyncIterable<Uint8Array> | undefined>('POST', this.getDatasetUrl(path), {
230
210
  ...options,
231
211
  json: query,
232
212
  stream: true,
@@ -236,28 +216,29 @@ export class PortalClient {
236
216
  })
237
217
  )
238
218
 
219
+ let headers = getPortalStreamHeaders(res.headers)
220
+
239
221
  switch (res.status) {
240
222
  case 200:
241
- let finalizedHead = getFinalizedHeadHeader(res.headers)
242
- let stream = res.body ? splitLines(res.body) : undefined
243
-
244
223
  return {
245
- finalizedHead,
246
- stream,
224
+ ...headers,
225
+ type: 'data',
226
+ data: res.body ?? null,
247
227
  }
248
228
  case 204:
249
229
  return {
250
- finalizedHead: getFinalizedHeadHeader(res.headers),
230
+ ...headers,
231
+ type: 'no-data',
251
232
  }
252
233
  default:
253
234
  throw unexpectedCase(res.status)
254
235
  }
255
236
  } catch (e: any) {
256
- if (isForkHttpError(e) && query.fromBlock != null && query.parentBlockHash != null) {
257
- e = new ForkException(e.response.body.previousBlocks, {
258
- number: query.fromBlock - 1,
259
- hash: query.parentBlockHash,
260
- })
237
+ if (isForkHttpError(e)) {
238
+ return {
239
+ type: 'fork',
240
+ previousBlocks: e.response.body.previousBlocks,
241
+ }
261
242
  }
262
243
 
263
244
  throw addErrorContext(e, {
@@ -284,20 +265,17 @@ function isForkHttpError(err: unknown): err is HttpError<{previousBlocks: BlockR
284
265
  return true
285
266
  }
286
267
 
287
- function createPortalStream<Q extends Query>(
268
+ function createPortalStream<Q extends AnyQuery>(
288
269
  query: Q,
289
270
  options: Required<PortalStreamOptions>,
290
- requestStream: (
291
- query: Q,
292
- options?: PortalRequestOptions
293
- ) => Promise<{finalizedHead?: BlockRef; stream?: AsyncIterable<string[]> | null | undefined}>
294
- ): PortalStream<GetBlock<Q>> {
271
+ requestStream: (query: Q, options?: PortalRequestOptions) => Promise<PortalStreamResponse>
272
+ ): PortalStream<GetQueryBlock<Q>> {
295
273
  let {headPollInterval, request, ...bufferOptions} = options
296
274
 
297
- let buffer = new PortalStreamBuffer<GetBlock<Q>>(bufferOptions)
275
+ let buffer = new PortalStreamBuffer<GetQueryBlock<Q>>(bufferOptions)
298
276
 
299
- let schema = getBlockSchema(query)
300
- let {fromBlock = 0, toBlock, parentBlockHash} = query
277
+ let [normalizedQuery, schema] = getQuery(query)
278
+ let {fromBlock = 0, toBlock, parentBlockHash} = normalizedQuery
301
279
 
302
280
  const ingest = async () => {
303
281
  while (!buffer.signal.aborted) {
@@ -305,7 +283,7 @@ function createPortalStream<Q extends Query>(
305
283
 
306
284
  let res = await requestStream(
307
285
  {
308
- ...query,
286
+ ...normalizedQuery,
309
287
  fromBlock,
310
288
  parentBlockHash,
311
289
  },
@@ -315,11 +293,13 @@ function createPortalStream<Q extends Query>(
315
293
  }
316
294
  )
317
295
 
318
- const finalizedHead = res.finalizedHead
296
+ if (res.type === 'fork') {
297
+ throw new ForkException(fromBlock, parentBlockHash!, res.previousBlocks)
298
+ }
299
+
300
+ buffer.updateHead(res)
319
301
 
320
- // we are on head
321
- if (!('stream' in res)) {
322
- await buffer.put({blocks: [], meta: {bytes: 0}, finalizedHead})
302
+ if (res.type === 'no-data') {
323
303
  buffer.flush()
324
304
  if (headPollInterval > 0) {
325
305
  await wait(headPollInterval, buffer.signal)
@@ -327,18 +307,17 @@ function createPortalStream<Q extends Query>(
327
307
  continue
328
308
  }
329
309
 
330
- // no data left on this range
331
- if (res.stream == null) break
310
+ if (!res.data) break
332
311
 
333
312
  try {
334
- for await (let data of res.stream) {
313
+ for await (let data of splitLines(res.data)) {
335
314
  buffer.signal.throwIfAborted()
336
315
 
337
- let blocks: GetBlock<Q>[] = []
316
+ let blocks: GetQueryBlock<Q>[] = []
338
317
  let bytes = 0
339
318
 
340
319
  for (let line of data) {
341
- let block = cast(schema, JSON.parse(line))
320
+ let block = cast(schema, JSON.parse(line)) as GetQueryBlock<Q>
342
321
  blocks.push(block)
343
322
  bytes += line.length
344
323
 
@@ -346,8 +325,10 @@ function createPortalStream<Q extends Query>(
346
325
  parentBlockHash = block.header.hash
347
326
  }
348
327
 
349
- await buffer.put({blocks, finalizedHead, meta: {bytes}})
328
+ await buffer.put(blocks, bytes)
350
329
  }
330
+
331
+ buffer.flush()
351
332
  } catch (err) {
352
333
  if (buffer.signal.aborted) return
353
334
  if (!isStreamAbortedError(err)) {
@@ -355,6 +336,8 @@ function createPortalStream<Q extends Query>(
355
336
  }
356
337
  }
357
338
  }
339
+
340
+ buffer.flush()
358
341
  }
359
342
 
360
343
  ingest().then(
@@ -362,174 +345,170 @@ function createPortalStream<Q extends Query>(
362
345
  (err) => buffer.fail(err)
363
346
  )
364
347
 
365
- return buffer.iterate()
348
+ return buffer
366
349
  }
367
350
 
368
351
  class PortalStreamBuffer<B> {
369
- private buffer: PortalStreamData<B> | undefined
370
- private state: 'pending' | 'ready' | 'failed' | 'closed' = 'pending'
371
- private error: unknown
352
+ private blocks: B[] = []
353
+ private bytes = 0
372
354
 
373
- private readyFuture: Future<void> = createFuture()
374
- private takeFuture: Future<void> = createFuture()
375
- private putFuture: Future<void> = createFuture()
355
+ private headNumber?: number
356
+ private finalizedHeadNumber?: number
357
+ private finalizedHeadHash?: string
358
+ private headUpdated = false
376
359
 
377
- private idleTimeout: ReturnType<typeof setTimeout> | undefined
378
- private waitTimeout: ReturnType<typeof setTimeout> | undefined
360
+ private state: 'open' | 'failed' | 'closed' = 'open'
361
+ private error: unknown
379
362
 
380
- private minBytes: number
381
- private maxBytes: number
382
- private maxIdleTime: number
383
- private maxWaitTime: number
363
+ private waitTimeouted = false
364
+ private putMutex = new Semaphore(false)
365
+ private takeMutex = new Semaphore(true)
384
366
 
367
+ private maxBytes: number
368
+ private idleTimer: Timer
369
+ private waitTimer: Timer
385
370
  private abortController = new AbortController()
386
371
 
387
372
  get signal() {
388
373
  return this.abortController.signal
389
374
  }
390
375
 
391
- constructor(options: {maxWaitTime: number; maxBytes: number; maxIdleTime: number; minBytes: number}) {
392
- this.maxWaitTime = options.maxWaitTime
393
- this.minBytes = options.minBytes
394
- this.maxBytes = Math.max(options.maxBytes, options.minBytes)
395
- this.maxIdleTime = options.maxIdleTime
376
+ constructor(options: {maxWaitTime: number; maxBytes: number; maxIdleTime: number}) {
377
+ this.maxBytes = options.maxBytes
378
+ this.idleTimer = new Timer(options.maxIdleTime, () => this.flush())
379
+ this.waitTimer = new Timer(options.maxWaitTime, () => (this.waitTimeouted = true))
396
380
  }
397
381
 
398
382
  async take(): Promise<PortalStreamData<B> | undefined> {
399
- if (this.state === 'pending') {
400
- this.waitTimeout = setTimeout(() => this._ready('wait'), this.maxWaitTime)
383
+ if (this.isTerminated()) {
384
+ return this.collect()
401
385
  }
402
386
 
403
- await Promise.all([this.readyFuture.promise(), this.putFuture.promise()])
387
+ this.waitTimer.start()
388
+ await this.putMutex.wait()
389
+ this.waitTimer.stop()
404
390
 
405
- if (this.state === 'failed') {
406
- throw this.error
407
- }
408
-
409
- let result = this.buffer
410
- this.buffer = undefined
411
-
412
- if (this.state === 'closed') {
413
- return result
414
- }
415
-
416
- if (result == null) {
417
- throw new Error('Buffer is empty')
418
- }
391
+ let result = this.collect()
419
392
 
420
- this.takeFuture.resolve()
421
-
422
- this.readyFuture = createFuture()
423
- this.putFuture = createFuture()
424
- this.takeFuture = createFuture()
425
- this.state = 'pending'
393
+ this.putMutex.unready()
394
+ this.takeMutex.ready()
395
+ this.waitTimeouted = false
426
396
 
427
397
  return result
428
398
  }
429
399
 
430
- async put(data: PortalStreamData<B>) {
431
- if (this.state === 'closed' || this.state === 'failed') {
432
- throw new Error('Buffer is closed')
433
- }
434
-
435
- if (this.idleTimeout != null) {
436
- clearTimeout(this.idleTimeout)
437
- this.idleTimeout = undefined
438
- }
400
+ updateHead(head: {headNumber?: number; finalizedHeadNumber?: number; finalizedHeadHash?: string}) {
401
+ this.headNumber = head.headNumber
402
+ this.finalizedHeadNumber = head.finalizedHeadNumber
403
+ this.finalizedHeadHash = head.finalizedHeadHash
404
+ this.headUpdated = true
405
+ }
439
406
 
440
- if (this.buffer == null) {
441
- this.buffer = {blocks: [], meta: {bytes: 0}}
407
+ async put(blocks: B[], bytes: number) {
408
+ if (this.isTerminated()) {
409
+ throw new Error('Buffer is closed')
442
410
  }
443
411
 
444
- this.buffer.blocks.push(...data.blocks)
445
- this.buffer.finalizedHead = data.finalizedHead
446
- this.buffer.meta.bytes += data.meta.bytes
412
+ this.idleTimer.stop()
447
413
 
448
- this.putFuture.resolve()
414
+ this.blocks.push(...blocks)
415
+ this.bytes += bytes
449
416
 
450
- if (this.buffer.meta.bytes >= this.minBytes) {
451
- this._ready('minBytes')
417
+ if (this.waitTimeouted || this.bytes >= this.maxBytes) {
418
+ this.flush()
452
419
  }
453
420
 
454
- if (this.buffer.meta.bytes >= this.maxBytes) {
455
- await this.takeFuture.promise()
421
+ if (this.bytes >= this.maxBytes) {
422
+ this.takeMutex.unready()
423
+ await this.takeMutex.wait()
456
424
  }
457
425
 
458
- if (this.state === 'pending') {
459
- this.idleTimeout = setTimeout(() => this._ready('idle'), this.maxIdleTime)
460
- }
426
+ this.idleTimer.start()
461
427
  }
462
428
 
463
429
  flush() {
464
- if (this.buffer == null) return
465
- this._ready('flush')
430
+ if (!this.hasData()) return
431
+ this.stopTimers()
432
+ this.putMutex.ready()
466
433
  }
467
434
 
468
435
  close() {
469
- if (this.state === 'closed' || this.state === 'failed') return
436
+ if (this.isTerminated()) return
470
437
  this.state = 'closed'
471
- this._cleanup()
438
+ this.stopTimers()
439
+ this.putMutex.ready()
440
+ this.takeMutex.reject(new Error('Buffer closed'))
441
+ this.abortController.abort()
472
442
  }
473
443
 
474
444
  fail(err: any) {
475
- if (this.state === 'closed' || this.state === 'failed') return
445
+ if (this.isTerminated()) return
476
446
  this.state = 'failed'
477
447
  this.error = err
478
- this._cleanup()
448
+ this.stopTimers()
449
+ this.putMutex.ready()
450
+ this.takeMutex.reject(new Error('Buffer closed'))
451
+ this.abortController.abort()
479
452
  }
480
453
 
481
- iterate() {
454
+ [Symbol.asyncIterator](): AsyncIterator<PortalStreamData<B>> {
482
455
  return {
483
- [Symbol.asyncIterator]: (): AsyncIterator<PortalStreamData<B>> => {
484
- return {
485
- next: async (): Promise<IteratorResult<PortalStreamData<B>>> => {
486
- const value = await this.take()
487
- if (value == null) {
488
- return {done: true, value: undefined}
489
- }
490
- return {done: false, value}
491
- },
492
- return: async (): Promise<IteratorResult<PortalStreamData<B>>> => {
493
- this.close()
494
- return {done: true, value: undefined}
495
- },
496
- throw: async (error?: any): Promise<IteratorResult<PortalStreamData<B>>> => {
497
- this.fail(error)
498
- throw error
499
- },
456
+ next: async () => {
457
+ let value = await this.take()
458
+ if (value == null) {
459
+ return {done: true, value: undefined}
500
460
  }
461
+ return {done: false, value}
462
+ },
463
+ return: async () => {
464
+ this.close()
465
+ return {done: true, value: undefined}
466
+ },
467
+ throw: async (error?: unknown) => {
468
+ this.fail(error)
469
+ throw error
501
470
  },
502
471
  }
503
472
  }
504
473
 
505
- private _ready(reason?: string) {
506
- if (this.state === 'pending') {
507
- this.state = 'ready'
508
- this.readyFuture.resolve()
509
- }
510
- if (this.idleTimeout != null) {
511
- clearTimeout(this.idleTimeout)
512
- this.idleTimeout = undefined
513
- }
514
- if (this.waitTimeout != null) {
515
- clearTimeout(this.waitTimeout)
516
- this.waitTimeout = undefined
517
- }
474
+ private hasData(): boolean {
475
+ return this.blocks.length > 0 || this.headUpdated
476
+ }
477
+
478
+ private isTerminated(): boolean {
479
+ return this.state !== 'open'
480
+ }
481
+
482
+ private isFailed(): boolean {
483
+ return this.state === 'failed'
518
484
  }
519
485
 
520
- private _cleanup() {
521
- if (this.idleTimeout != null) {
522
- clearTimeout(this.idleTimeout)
523
- this.idleTimeout = undefined
486
+ private collect(): PortalStreamData<B> | undefined {
487
+ if (!this.hasData()) {
488
+ if (this.isFailed()) throw this.error
489
+ return undefined
524
490
  }
525
- if (this.waitTimeout != null) {
526
- clearTimeout(this.waitTimeout)
527
- this.waitTimeout = undefined
491
+
492
+ let result: PortalStreamData<B> = {
493
+ blocks: this.blocks,
494
+ meta: {
495
+ bytes: this.bytes,
496
+ headNumber: this.headNumber,
497
+ finalizedHeadNumber: this.finalizedHeadNumber,
498
+ finalizedHeadHash: this.finalizedHeadHash,
499
+ },
528
500
  }
529
- this.readyFuture.resolve()
530
- this.putFuture.resolve()
531
- this.takeFuture.resolve()
532
- this.abortController.abort()
501
+
502
+ this.blocks = []
503
+ this.bytes = 0
504
+ this.headUpdated = false
505
+
506
+ return result
507
+ }
508
+
509
+ private stopTimers() {
510
+ this.idleTimer.stop()
511
+ this.waitTimer.stop()
533
512
  }
534
513
  }
535
514
 
@@ -573,16 +552,21 @@ class LineSplitter {
573
552
  }
574
553
 
575
554
  export class ForkException extends Error {
576
- readonly name = 'ForkError'
577
-
578
- constructor(readonly previousBlocks: BlockRef[], readonly head: BlockRef) {
579
- let parent = last(previousBlocks)
555
+ constructor(public blockNumber: number, public parentBlockHash: string, public previousBlocks: BlockRef[]) {
556
+ let base = last(previousBlocks)
580
557
  super(
581
- `expected ${head.number + 1} to have parent ${parent.number}#${parent.hash}, but got ${head.number}#${
582
- head.hash
583
- }`
558
+ `expected block ${blockNumber} to have parent ${base.number}#${parentBlockHash}, ` +
559
+ `but got ${base.number}#${base.hash} as a parent instead`
584
560
  )
585
561
  }
562
+
563
+ get name(): string {
564
+ return 'ForkException'
565
+ }
566
+
567
+ get isSubsquidForkException(): true {
568
+ return true
569
+ }
586
570
  }
587
571
 
588
572
  export function isForkException(err: unknown): err is ForkException {
@@ -591,16 +575,16 @@ export function isForkException(err: unknown): err is ForkException {
591
575
  return false
592
576
  }
593
577
 
594
- function getFinalizedHeadHeader(headers: HttpResponse['headers']) {
595
- let finalizedHeadHash = headers.get('X-Sqd-Finalized-Head-Hash')
596
- let finalizedHeadNumber = headers.get('X-Sqd-Finalized-Head-Number')
578
+ function getPortalStreamHeaders(headers: HttpResponse['headers']) {
579
+ let finalizedHeadHash = headers.get('x-sqd-finalized-head-hash')
580
+ let finalizedHeadNumber = headers.get('x-sqd-finalized-head-number')
581
+ let headNumber = headers.get('x-sqd-head-number')
597
582
 
598
- return finalizedHeadHash != null && finalizedHeadNumber != null
599
- ? {
600
- hash: finalizedHeadHash,
601
- number: parseInt(finalizedHeadNumber),
602
- }
603
- : undefined
583
+ return {
584
+ headNumber: headNumber ? parseInt(headNumber) : undefined,
585
+ finalizedHeadNumber: finalizedHeadNumber ? parseInt(finalizedHeadNumber) : undefined,
586
+ finalizedHeadHash: finalizedHeadHash ?? undefined,
587
+ }
604
588
  }
605
589
 
606
590
  function isStreamAbortedError(err: unknown) {