@subsquid/portal-client 0.0.1-portal-api.4494d2
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/README.md +3 -0
- package/lib/client.d.ts +35 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +126 -0
- package/lib/client.js.map +1 -0
- package/package.json +38 -0
- package/src/client.ts +173 -0
package/README.md
ADDED
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { HttpClient } from '@subsquid/http-client';
|
|
2
|
+
import type { Logger } from '@subsquid/logger';
|
|
3
|
+
export interface PortalQuery {
|
|
4
|
+
fromBlock: number;
|
|
5
|
+
toBlock?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Block {
|
|
8
|
+
header: {
|
|
9
|
+
number: number;
|
|
10
|
+
hash: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface Metadata {
|
|
14
|
+
isRealTime: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface PortalClientOptions {
|
|
17
|
+
url: string;
|
|
18
|
+
http?: HttpClient;
|
|
19
|
+
log?: Logger;
|
|
20
|
+
queryTimeout?: number;
|
|
21
|
+
bufferThreshold?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class PortalClient {
|
|
24
|
+
private url;
|
|
25
|
+
private http;
|
|
26
|
+
private queryTimeout;
|
|
27
|
+
private bufferThreshold;
|
|
28
|
+
constructor(options: PortalClientOptions);
|
|
29
|
+
private getDatasetUrl;
|
|
30
|
+
getHeight(): Promise<number>;
|
|
31
|
+
getMetadata(): Promise<Metadata>;
|
|
32
|
+
query<B extends Block = Block, Q extends PortalQuery = PortalQuery>(query: Q): Promise<B[]>;
|
|
33
|
+
stream<B extends Block = Block, Q extends PortalQuery = PortalQuery>(query: Q): AsyncIterable<B[]>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAA;AAChD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,kBAAkB,CAAA;AAM5C,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAGD,MAAM,WAAW,KAAK;IAClB,MAAM,EAAE;QACJ,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,MAAM,CAAA;KACf,CAAA;CACJ;AAGD,MAAM,WAAW,QAAQ;IACrB,UAAU,EAAE,OAAO,CAAA;CACtB;AAGD,MAAM,WAAW,mBAAmB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAGD,qBAAa,YAAY;IACrB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,eAAe,CAAQ;gBAEnB,OAAO,EAAE,mBAAmB;IAOxC,OAAO,CAAC,aAAa;IAUf,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAU5B,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;IAUtC,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAuBpF,MAAM,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,KAAK,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC;CAuE5G"}
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PortalClient = void 0;
|
|
7
|
+
const http_client_1 = require("@subsquid/http-client");
|
|
8
|
+
const util_internal_1 = require("@subsquid/util-internal");
|
|
9
|
+
const util_internal_archive_layout_1 = require("@subsquid/util-internal-archive-layout");
|
|
10
|
+
const assert_1 = __importDefault(require("assert"));
|
|
11
|
+
class PortalClient {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.url = new URL(options.url);
|
|
14
|
+
this.http = options.http || new http_client_1.HttpClient({ log: options.log });
|
|
15
|
+
this.queryTimeout = options.queryTimeout ?? 180000;
|
|
16
|
+
this.bufferThreshold = options.bufferThreshold ?? 10 * 1024 * 1024;
|
|
17
|
+
}
|
|
18
|
+
getDatasetUrl(path) {
|
|
19
|
+
let u = new URL(this.url);
|
|
20
|
+
if (this.url.pathname.endsWith('/')) {
|
|
21
|
+
u.pathname += path;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
u.pathname += '/' + path;
|
|
25
|
+
}
|
|
26
|
+
return u.toString();
|
|
27
|
+
}
|
|
28
|
+
async getHeight() {
|
|
29
|
+
let res = await this.http.get(this.getDatasetUrl('height'), {
|
|
30
|
+
retryAttempts: 3,
|
|
31
|
+
httpTimeout: 10000,
|
|
32
|
+
});
|
|
33
|
+
let height = parseInt(res);
|
|
34
|
+
(0, assert_1.default)(Number.isSafeInteger(height));
|
|
35
|
+
return height;
|
|
36
|
+
}
|
|
37
|
+
async getMetadata() {
|
|
38
|
+
let res = await this.http.get(this.getDatasetUrl('metadata'), {
|
|
39
|
+
retryAttempts: 3,
|
|
40
|
+
httpTimeout: 10000,
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
isRealTime: !!res.real_time
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
query(query) {
|
|
47
|
+
return this.http
|
|
48
|
+
.request('POST', this.getDatasetUrl(`stream`), {
|
|
49
|
+
json: query,
|
|
50
|
+
retryAttempts: 3,
|
|
51
|
+
httpTimeout: this.queryTimeout,
|
|
52
|
+
})
|
|
53
|
+
.catch((0, util_internal_1.withErrorContext)({
|
|
54
|
+
archiveQuery: query,
|
|
55
|
+
}))
|
|
56
|
+
.then((res) => {
|
|
57
|
+
// TODO: move the conversion to the server
|
|
58
|
+
let blocks = res.body
|
|
59
|
+
.toString('utf8')
|
|
60
|
+
.trimEnd()
|
|
61
|
+
.split('\n')
|
|
62
|
+
.map((line) => JSON.parse(line));
|
|
63
|
+
return blocks;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async *stream(query) {
|
|
67
|
+
let queue = new util_internal_1.AsyncQueue(1);
|
|
68
|
+
const ingest = async () => {
|
|
69
|
+
let bufferSize = 0;
|
|
70
|
+
let fromBlock = query.fromBlock;
|
|
71
|
+
let toBlock = query.toBlock ?? Infinity;
|
|
72
|
+
while (fromBlock <= toBlock) {
|
|
73
|
+
let archiveQuery = { ...query, fromBlock };
|
|
74
|
+
let res = await this.http
|
|
75
|
+
.request('POST', this.getDatasetUrl(`stream`), {
|
|
76
|
+
json: archiveQuery,
|
|
77
|
+
retryAttempts: 3,
|
|
78
|
+
httpTimeout: this.queryTimeout,
|
|
79
|
+
stream: true,
|
|
80
|
+
})
|
|
81
|
+
.catch((0, util_internal_1.withErrorContext)({
|
|
82
|
+
archiveQuery,
|
|
83
|
+
}));
|
|
84
|
+
for await (let lines of (0, util_internal_archive_layout_1.splitLines)(res.body)) {
|
|
85
|
+
let batch = queue.peek();
|
|
86
|
+
if (batch instanceof Error)
|
|
87
|
+
return;
|
|
88
|
+
if (!batch) {
|
|
89
|
+
bufferSize = 0;
|
|
90
|
+
}
|
|
91
|
+
let blocks = lines.map((line) => {
|
|
92
|
+
bufferSize += line.length;
|
|
93
|
+
return JSON.parse(line);
|
|
94
|
+
});
|
|
95
|
+
if (batch) {
|
|
96
|
+
// FIXME: won't it overflow stack?
|
|
97
|
+
batch.push(...blocks);
|
|
98
|
+
if (bufferSize > this.bufferThreshold) {
|
|
99
|
+
await queue.wait();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await queue.put(blocks);
|
|
104
|
+
}
|
|
105
|
+
fromBlock = (0, util_internal_1.last)(blocks).header.number + 1;
|
|
106
|
+
}
|
|
107
|
+
// no blocks left
|
|
108
|
+
if (res.status == 204) {
|
|
109
|
+
await (0, util_internal_1.wait)(1000);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
ingest().then(() => queue.close(), (err) => {
|
|
114
|
+
if (!queue.isClosed()) {
|
|
115
|
+
queue.forcePut((0, util_internal_1.ensureError)(err));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
for await (let valueOrError of queue.iterate()) {
|
|
119
|
+
if (valueOrError instanceof Error)
|
|
120
|
+
throw valueOrError;
|
|
121
|
+
yield valueOrError;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.PortalClient = PortalClient;
|
|
126
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@subsquid/portal-client",
|
|
3
|
+
"version": "0.0.1-portal-api.4494d2",
|
|
4
|
+
"description": "SQD Portal API",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"repository": "git@github.com:subsquid/squid.git",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"main": "lib/client.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"lib",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@subsquid/util-internal": "3.3.0-portal-api.4494d2",
|
|
17
|
+
"@subsquid/util-internal-range": "^0.3.0",
|
|
18
|
+
"@subsquid/util-internal-archive-layout": "1.1.0-portal-api.4494d2"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@subsquid/http-client": "^1.5.0",
|
|
22
|
+
"@subsquid/logger": "^1.3.3"
|
|
23
|
+
},
|
|
24
|
+
"peerDependenciesMeta": {
|
|
25
|
+
"@subsquid/logger": {
|
|
26
|
+
"optional": true
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@subsquid/http-client": "^1.5.0",
|
|
31
|
+
"@subsquid/logger": "^1.3.3",
|
|
32
|
+
"@types/node": "^18.18.14",
|
|
33
|
+
"typescript": "~5.3.2"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "rm -rf lib && tsc"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {HttpClient} from '@subsquid/http-client'
|
|
2
|
+
import type {Logger} from '@subsquid/logger'
|
|
3
|
+
import {AsyncQueue, ensureError, last, wait, withErrorContext} from '@subsquid/util-internal'
|
|
4
|
+
import {splitLines} from '@subsquid/util-internal-archive-layout'
|
|
5
|
+
import assert from 'assert'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export interface PortalQuery {
|
|
9
|
+
fromBlock: number
|
|
10
|
+
toBlock?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export interface Block {
|
|
15
|
+
header: {
|
|
16
|
+
number: number
|
|
17
|
+
hash: string
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export interface Metadata {
|
|
23
|
+
isRealTime: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export interface PortalClientOptions {
|
|
28
|
+
url: string
|
|
29
|
+
http?: HttpClient
|
|
30
|
+
log?: Logger
|
|
31
|
+
queryTimeout?: number
|
|
32
|
+
bufferThreshold?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export class PortalClient {
|
|
37
|
+
private url: URL
|
|
38
|
+
private http: HttpClient
|
|
39
|
+
private queryTimeout: number
|
|
40
|
+
private bufferThreshold: number
|
|
41
|
+
|
|
42
|
+
constructor(options: PortalClientOptions) {
|
|
43
|
+
this.url = new URL(options.url)
|
|
44
|
+
this.http = options.http || new HttpClient({log: options.log})
|
|
45
|
+
this.queryTimeout = options.queryTimeout ?? 180_000
|
|
46
|
+
this.bufferThreshold = options.bufferThreshold ?? 10 * 1024 * 1024
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private getDatasetUrl(path: string): string {
|
|
50
|
+
let u = new URL(this.url)
|
|
51
|
+
if (this.url.pathname.endsWith('/')) {
|
|
52
|
+
u.pathname += path
|
|
53
|
+
} else {
|
|
54
|
+
u.pathname += '/' + path
|
|
55
|
+
}
|
|
56
|
+
return u.toString()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getHeight(): Promise<number> {
|
|
60
|
+
let res: string = await this.http.get(this.getDatasetUrl('height'), {
|
|
61
|
+
retryAttempts: 3,
|
|
62
|
+
httpTimeout: 10_000,
|
|
63
|
+
})
|
|
64
|
+
let height = parseInt(res)
|
|
65
|
+
assert(Number.isSafeInteger(height))
|
|
66
|
+
return height
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async getMetadata(): Promise<Metadata> {
|
|
70
|
+
let res: {real_time: boolean} = await this.http.get(this.getDatasetUrl('metadata'), {
|
|
71
|
+
retryAttempts: 3,
|
|
72
|
+
httpTimeout: 10_000,
|
|
73
|
+
})
|
|
74
|
+
return {
|
|
75
|
+
isRealTime: !!res.real_time
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
query<B extends Block = Block, Q extends PortalQuery = PortalQuery>(query: Q): Promise<B[]> {
|
|
80
|
+
return this.http
|
|
81
|
+
.request<Buffer>('POST', this.getDatasetUrl(`stream`), {
|
|
82
|
+
json: query,
|
|
83
|
+
retryAttempts: 3,
|
|
84
|
+
httpTimeout: this.queryTimeout,
|
|
85
|
+
})
|
|
86
|
+
.catch(
|
|
87
|
+
withErrorContext({
|
|
88
|
+
archiveQuery: query,
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
.then((res) => {
|
|
92
|
+
// TODO: move the conversion to the server
|
|
93
|
+
let blocks = res.body
|
|
94
|
+
.toString('utf8')
|
|
95
|
+
.trimEnd()
|
|
96
|
+
.split('\n')
|
|
97
|
+
.map((line) => JSON.parse(line))
|
|
98
|
+
return blocks
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async *stream<B extends Block = Block, Q extends PortalQuery = PortalQuery>(query: Q): AsyncIterable<B[]> {
|
|
103
|
+
let queue = new AsyncQueue<B[] | Error>(1)
|
|
104
|
+
|
|
105
|
+
const ingest = async () => {
|
|
106
|
+
let bufferSize = 0
|
|
107
|
+
let fromBlock = query.fromBlock
|
|
108
|
+
let toBlock = query.toBlock ?? Infinity
|
|
109
|
+
|
|
110
|
+
while (fromBlock <= toBlock) {
|
|
111
|
+
let archiveQuery = {...query, fromBlock}
|
|
112
|
+
|
|
113
|
+
let res = await this.http
|
|
114
|
+
.request<NodeJS.ReadableStream>('POST', this.getDatasetUrl(`stream`), {
|
|
115
|
+
json: archiveQuery,
|
|
116
|
+
retryAttempts: 3,
|
|
117
|
+
httpTimeout: this.queryTimeout,
|
|
118
|
+
stream: true,
|
|
119
|
+
})
|
|
120
|
+
.catch(
|
|
121
|
+
withErrorContext({
|
|
122
|
+
archiveQuery,
|
|
123
|
+
})
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
for await (let lines of splitLines(res.body as AsyncIterable<Buffer>)) {
|
|
127
|
+
let batch = queue.peek()
|
|
128
|
+
if (batch instanceof Error) return
|
|
129
|
+
|
|
130
|
+
if (!batch) {
|
|
131
|
+
bufferSize = 0
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let blocks = lines.map((line) => {
|
|
135
|
+
bufferSize += line.length
|
|
136
|
+
return JSON.parse(line) as B
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
if (batch) {
|
|
140
|
+
// FIXME: won't it overflow stack?
|
|
141
|
+
batch.push(...blocks)
|
|
142
|
+
if (bufferSize > this.bufferThreshold) {
|
|
143
|
+
await queue.wait()
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
await queue.put(blocks)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fromBlock = last(blocks).header.number + 1
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// no blocks left
|
|
153
|
+
if (res.status == 204) {
|
|
154
|
+
await wait(1000)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
ingest().then(
|
|
160
|
+
() => queue.close(),
|
|
161
|
+
(err) => {
|
|
162
|
+
if (!queue.isClosed()) {
|
|
163
|
+
queue.forcePut(ensureError(err))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
for await (let valueOrError of queue.iterate()) {
|
|
169
|
+
if (valueOrError instanceof Error) throw valueOrError
|
|
170
|
+
yield valueOrError
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|