@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.
- package/lib/client.d.ts +40 -18
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +166 -188
- package/lib/client.js.map +1 -1
- package/lib/query/common/data.d.ts +21 -0
- package/lib/query/common/data.d.ts.map +1 -0
- package/lib/query/common/data.js +3 -0
- package/lib/query/common/data.js.map +1 -0
- package/lib/query/common/query.d.ts +7 -0
- package/lib/query/common/query.d.ts.map +1 -0
- package/lib/query/common/query.js +3 -0
- package/lib/query/common/query.js.map +1 -0
- package/lib/query/evm/data.d.ts +159 -0
- package/lib/query/evm/data.d.ts.map +1 -0
- package/lib/query/evm/data.js +3 -0
- package/lib/query/evm/data.js.map +1 -0
- package/lib/query/evm/fields.d.ts +50 -0
- package/lib/query/evm/fields.d.ts.map +1 -0
- package/lib/query/evm/fields.js +3 -0
- package/lib/query/evm/fields.js.map +1 -0
- package/lib/query/evm/index.d.ts +4 -0
- package/lib/query/evm/index.d.ts.map +1 -0
- package/lib/query/evm/index.js +7 -0
- package/lib/query/evm/index.js.map +1 -0
- package/lib/query/evm/query.d.ts +54 -0
- package/lib/query/evm/query.d.ts.map +1 -0
- package/lib/query/evm/query.js +3 -0
- package/lib/query/evm/query.js.map +1 -0
- package/lib/query/evm/schema.d.ts +322 -0
- package/lib/query/evm/schema.d.ts.map +1 -0
- package/lib/query/evm/schema.js +224 -0
- package/lib/query/evm/schema.js.map +1 -0
- package/lib/query/index.d.ts +576 -8
- package/lib/query/index.d.ts.map +1 -1
- package/lib/query/index.js +15 -24
- package/lib/query/index.js.map +1 -1
- package/lib/query/solana/data.d.ts +115 -0
- package/lib/query/solana/data.d.ts.map +1 -0
- package/lib/query/solana/data.js +3 -0
- package/lib/query/solana/data.js.map +1 -0
- package/lib/query/solana/fields.d.ts +31 -0
- package/lib/query/solana/fields.d.ts.map +1 -0
- package/lib/query/solana/fields.js +3 -0
- package/lib/query/solana/fields.js.map +1 -0
- package/lib/query/solana/index.d.ts +4 -0
- package/lib/query/solana/index.d.ts.map +1 -0
- package/lib/query/solana/index.js +7 -0
- package/lib/query/solana/index.js.map +1 -0
- package/lib/query/solana/query.d.ts +74 -0
- package/lib/query/solana/query.d.ts.map +1 -0
- package/lib/query/solana/query.js +3 -0
- package/lib/query/solana/query.js.map +1 -0
- package/lib/query/solana/schema.d.ts +160 -0
- package/lib/query/solana/schema.d.ts.map +1 -0
- package/lib/query/solana/schema.js +130 -0
- package/lib/query/solana/schema.js.map +1 -0
- package/lib/query/substrate/data.d.ts +98 -0
- package/lib/query/substrate/data.d.ts.map +1 -0
- package/lib/query/substrate/data.js +3 -0
- package/lib/query/substrate/data.js.map +1 -0
- package/lib/query/substrate/fields.d.ts +22 -0
- package/lib/query/substrate/fields.d.ts.map +1 -0
- package/lib/query/substrate/fields.js +3 -0
- package/lib/query/substrate/fields.js.map +1 -0
- package/lib/query/substrate/index.d.ts +4 -0
- package/lib/query/substrate/index.d.ts.map +1 -0
- package/lib/query/substrate/index.js +7 -0
- package/lib/query/substrate/index.js.map +1 -0
- package/lib/query/substrate/query.d.ts +50 -0
- package/lib/query/substrate/query.d.ts.map +1 -0
- package/lib/query/substrate/query.js +3 -0
- package/lib/query/substrate/query.js.map +1 -0
- package/lib/query/substrate/schema.d.ts +102 -0
- package/lib/query/substrate/schema.d.ts.map +1 -0
- package/lib/query/substrate/schema.js +88 -0
- package/lib/query/substrate/schema.js.map +1 -0
- package/lib/query/type-util.d.ts +13 -0
- package/lib/query/type-util.d.ts.map +1 -0
- package/lib/query/type-util.js +3 -0
- package/lib/query/type-util.js.map +1 -0
- package/lib/query/util.d.ts +5 -0
- package/lib/query/util.d.ts.map +1 -0
- package/lib/query/util.js +23 -0
- package/lib/query/util.js.map +1 -0
- package/lib/util.d.ts +19 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +57 -0
- package/lib/util.js.map +1 -0
- package/package.json +3 -3
- package/src/client.ts +231 -247
- package/src/query/common/data.ts +24 -0
- package/src/query/common/query.ts +6 -0
- package/src/query/evm/data.ts +182 -0
- package/src/query/evm/fields.ts +105 -0
- package/src/query/evm/index.ts +3 -0
- package/src/query/evm/query.ts +59 -0
- package/src/query/evm/schema.ts +277 -0
- package/src/query/index.ts +19 -36
- package/src/query/solana/data.ts +132 -0
- package/src/query/solana/fields.ts +42 -0
- package/src/query/solana/index.ts +3 -0
- package/src/query/solana/query.ts +89 -0
- package/src/query/solana/schema.ts +164 -0
- package/src/query/substrate/data.ts +101 -0
- package/src/query/substrate/fields.ts +30 -0
- package/src/query/substrate/index.ts +3 -0
- package/src/query/substrate/query.ts +60 -0
- package/src/query/substrate/schema.ts +114 -0
- package/src/query/type-util.ts +25 -0
- package/src/query/util.ts +23 -0
- package/src/util.ts +56 -0
- package/lib/query/common.d.ts +0 -56
- package/lib/query/common.d.ts.map +0 -1
- package/lib/query/common.js +0 -16
- package/lib/query/common.js.map +0 -1
- package/lib/query/evm.d.ts +0 -267
- package/lib/query/evm.d.ts.map +0 -1
- package/lib/query/evm.js +0 -245
- package/lib/query/evm.js.map +0 -1
- package/lib/query/solana.d.ts +0 -224
- package/lib/query/solana.d.ts.map +0 -1
- package/lib/query/solana.js +0 -121
- package/lib/query/solana.js.map +0 -1
- package/lib/query/substrate.d.ts +0 -173
- package/lib/query/substrate.d.ts.map +0 -1
- package/lib/query/substrate.js +0 -71
- package/lib/query/substrate.js.map +0 -1
- package/src/query/common.ts +0 -83
- package/src/query/evm.ts +0 -677
- package/src/query/solana.ts +0 -438
- 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
|
-
* @
|
|
24
|
+
* @deprecated not used
|
|
33
25
|
*/
|
|
34
26
|
minBytes?: number
|
|
35
27
|
|
|
36
28
|
/**
|
|
37
29
|
* Maximum number of bytes to return.
|
|
38
|
-
* @default
|
|
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
|
|
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.
|
|
110
|
-
this.
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
*
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
171
|
+
getStream<Q extends AnyQuery>(
|
|
190
172
|
query: Q,
|
|
191
|
-
options?: PortalStreamOptions
|
|
192
|
-
|
|
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
|
-
|
|
199
|
-
query
|
|
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(
|
|
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<
|
|
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
|
-
|
|
246
|
-
|
|
224
|
+
...headers,
|
|
225
|
+
type: 'data',
|
|
226
|
+
data: res.body ?? null,
|
|
247
227
|
}
|
|
248
228
|
case 204:
|
|
249
229
|
return {
|
|
250
|
-
|
|
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)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
268
|
+
function createPortalStream<Q extends AnyQuery>(
|
|
288
269
|
query: Q,
|
|
289
270
|
options: Required<PortalStreamOptions>,
|
|
290
|
-
requestStream: (
|
|
291
|
-
|
|
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<
|
|
275
|
+
let buffer = new PortalStreamBuffer<GetQueryBlock<Q>>(bufferOptions)
|
|
298
276
|
|
|
299
|
-
let schema =
|
|
300
|
-
let {fromBlock = 0, toBlock, parentBlockHash} =
|
|
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
|
-
...
|
|
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
|
-
|
|
296
|
+
if (res.type === 'fork') {
|
|
297
|
+
throw new ForkException(fromBlock, parentBlockHash!, res.previousBlocks)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
buffer.updateHead(res)
|
|
319
301
|
|
|
320
|
-
|
|
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
|
-
|
|
331
|
-
if (res.stream == null) break
|
|
310
|
+
if (!res.data) break
|
|
332
311
|
|
|
333
312
|
try {
|
|
334
|
-
for await (let data of res.
|
|
313
|
+
for await (let data of splitLines(res.data)) {
|
|
335
314
|
buffer.signal.throwIfAborted()
|
|
336
315
|
|
|
337
|
-
let blocks:
|
|
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(
|
|
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
|
|
348
|
+
return buffer
|
|
366
349
|
}
|
|
367
350
|
|
|
368
351
|
class PortalStreamBuffer<B> {
|
|
369
|
-
private
|
|
370
|
-
private
|
|
371
|
-
private error: unknown
|
|
352
|
+
private blocks: B[] = []
|
|
353
|
+
private bytes = 0
|
|
372
354
|
|
|
373
|
-
private
|
|
374
|
-
private
|
|
375
|
-
private
|
|
355
|
+
private headNumber?: number
|
|
356
|
+
private finalizedHeadNumber?: number
|
|
357
|
+
private finalizedHeadHash?: string
|
|
358
|
+
private headUpdated = false
|
|
376
359
|
|
|
377
|
-
private
|
|
378
|
-
private
|
|
360
|
+
private state: 'open' | 'failed' | 'closed' = 'open'
|
|
361
|
+
private error: unknown
|
|
379
362
|
|
|
380
|
-
private
|
|
381
|
-
private
|
|
382
|
-
private
|
|
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
|
|
392
|
-
this.
|
|
393
|
-
this.
|
|
394
|
-
this.
|
|
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.
|
|
400
|
-
|
|
383
|
+
if (this.isTerminated()) {
|
|
384
|
+
return this.collect()
|
|
401
385
|
}
|
|
402
386
|
|
|
403
|
-
|
|
387
|
+
this.waitTimer.start()
|
|
388
|
+
await this.putMutex.wait()
|
|
389
|
+
this.waitTimer.stop()
|
|
404
390
|
|
|
405
|
-
|
|
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.
|
|
421
|
-
|
|
422
|
-
this.
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
441
|
-
|
|
407
|
+
async put(blocks: B[], bytes: number) {
|
|
408
|
+
if (this.isTerminated()) {
|
|
409
|
+
throw new Error('Buffer is closed')
|
|
442
410
|
}
|
|
443
411
|
|
|
444
|
-
this.
|
|
445
|
-
this.buffer.finalizedHead = data.finalizedHead
|
|
446
|
-
this.buffer.meta.bytes += data.meta.bytes
|
|
412
|
+
this.idleTimer.stop()
|
|
447
413
|
|
|
448
|
-
this.
|
|
414
|
+
this.blocks.push(...blocks)
|
|
415
|
+
this.bytes += bytes
|
|
449
416
|
|
|
450
|
-
if (this.
|
|
451
|
-
this.
|
|
417
|
+
if (this.waitTimeouted || this.bytes >= this.maxBytes) {
|
|
418
|
+
this.flush()
|
|
452
419
|
}
|
|
453
420
|
|
|
454
|
-
if (this.
|
|
455
|
-
|
|
421
|
+
if (this.bytes >= this.maxBytes) {
|
|
422
|
+
this.takeMutex.unready()
|
|
423
|
+
await this.takeMutex.wait()
|
|
456
424
|
}
|
|
457
425
|
|
|
458
|
-
|
|
459
|
-
this.idleTimeout = setTimeout(() => this._ready('idle'), this.maxIdleTime)
|
|
460
|
-
}
|
|
426
|
+
this.idleTimer.start()
|
|
461
427
|
}
|
|
462
428
|
|
|
463
429
|
flush() {
|
|
464
|
-
if (this.
|
|
465
|
-
this.
|
|
430
|
+
if (!this.hasData()) return
|
|
431
|
+
this.stopTimers()
|
|
432
|
+
this.putMutex.ready()
|
|
466
433
|
}
|
|
467
434
|
|
|
468
435
|
close() {
|
|
469
|
-
if (this.
|
|
436
|
+
if (this.isTerminated()) return
|
|
470
437
|
this.state = 'closed'
|
|
471
|
-
this.
|
|
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.
|
|
445
|
+
if (this.isTerminated()) return
|
|
476
446
|
this.state = 'failed'
|
|
477
447
|
this.error = err
|
|
478
|
-
this.
|
|
448
|
+
this.stopTimers()
|
|
449
|
+
this.putMutex.ready()
|
|
450
|
+
this.takeMutex.reject(new Error('Buffer closed'))
|
|
451
|
+
this.abortController.abort()
|
|
479
452
|
}
|
|
480
453
|
|
|
481
|
-
|
|
454
|
+
[Symbol.asyncIterator](): AsyncIterator<PortalStreamData<B>> {
|
|
482
455
|
return {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
|
521
|
-
if (this.
|
|
522
|
-
|
|
523
|
-
|
|
486
|
+
private collect(): PortalStreamData<B> | undefined {
|
|
487
|
+
if (!this.hasData()) {
|
|
488
|
+
if (this.isFailed()) throw this.error
|
|
489
|
+
return undefined
|
|
524
490
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
this.
|
|
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
|
-
|
|
530
|
-
this.
|
|
531
|
-
this.
|
|
532
|
-
this.
|
|
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
|
-
|
|
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 ${
|
|
582
|
-
|
|
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
|
|
595
|
-
let finalizedHeadHash = headers.get('
|
|
596
|
-
let finalizedHeadNumber = headers.get('
|
|
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
|
|
599
|
-
?
|
|
600
|
-
|
|
601
|
-
|
|
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) {
|