@apibara/protocol 0.4.0-next.0 → 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/dist/client.d.ts +111 -21
- package/dist/client.js +144 -38
- package/dist/client.js.map +1 -1
- package/dist/cursor.d.ts +22 -0
- package/dist/cursor.js +43 -0
- package/dist/cursor.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/proto/apibara/node/v1alpha2/Data.d.ts +2 -0
- package/dist/proto/apibara/node/v1alpha2/StreamDataResponse.d.ts +4 -4
- package/dist/proto/stream.proto +3 -1
- package/package.json +1 -1
- package/src/client.ts +228 -61
- package/src/cursor.ts +39 -0
- package/src/index.ts +1 -0
- package/src/proto/apibara/node/v1alpha2/Data.ts +2 -0
- package/src/proto/apibara/node/v1alpha2/StreamDataResponse.ts +4 -4
- package/src/proto/stream.proto +3 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,29 +1,119 @@
|
|
|
1
|
-
import { ChannelCredentials, ClientDuplexStream } from '@grpc/grpc-js';
|
|
1
|
+
import { ChannelCredentials, ClientDuplexStream, ClientOptions, StatusObject } from '@grpc/grpc-js';
|
|
2
2
|
import { v1alpha2 } from './proto';
|
|
3
|
-
export { ChannelCredentials } from '@grpc/grpc-js';
|
|
4
|
-
export declare type ErrorHandler = (client: StreamClient, err: Error) => void | Promise<void>;
|
|
5
|
-
export declare type CloseHandler = (client: StreamClient) => void | Promise<void>;
|
|
6
|
-
export declare type DataHandler = (client: StreamClient, data: v1alpha2.IData) => void | Promise<void>;
|
|
7
|
-
export declare type InvalidateHandler = (client: StreamClient, invalidate: v1alpha2.IInvalidate) => void | Promise<void>;
|
|
8
|
-
export declare type HeartbeatHandler = (client: StreamClient, invalidate: v1alpha2.IHeartbeat) => void | Promise<void>;
|
|
3
|
+
export { ChannelCredentials, StatusObject } from '@grpc/grpc-js';
|
|
9
4
|
export declare type DataStream = ClientDuplexStream<v1alpha2.IStreamDataRequest, v1alpha2.IStreamDataResponse>;
|
|
5
|
+
export declare type ConfigureArgs = {
|
|
6
|
+
/**
|
|
7
|
+
* Stream filter, encoded.
|
|
8
|
+
*/
|
|
9
|
+
filter: Uint8Array;
|
|
10
|
+
/**
|
|
11
|
+
* How much data in a single message.
|
|
12
|
+
*/
|
|
13
|
+
batchSize?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Starting cursor. This cursor is stream-specific.
|
|
16
|
+
*/
|
|
17
|
+
cursor?: v1alpha2.ICursor | null;
|
|
18
|
+
/**
|
|
19
|
+
* Data finality, e.g. finalized or accepted.
|
|
20
|
+
*/
|
|
21
|
+
finality?: v1alpha2.DataFinality | null;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Reconnect callback return value.
|
|
25
|
+
*/
|
|
26
|
+
export declare type OnReconnectResult = {
|
|
27
|
+
/**
|
|
28
|
+
* If `true`, reconnects to the stream.
|
|
29
|
+
*/
|
|
30
|
+
reconnect: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Stream configuration used when reconnecting.
|
|
33
|
+
*
|
|
34
|
+
* By default, the client uses the last configuration passed to
|
|
35
|
+
* `configure` and updates the `cursor` with the most recent one.
|
|
36
|
+
*/
|
|
37
|
+
args?: ConfigureArgs;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Reconnect callback.
|
|
41
|
+
*/
|
|
42
|
+
export declare type OnReconnect = (err: StatusObject, retryCount: number) => Promise<OnReconnectResult> | OnReconnectResult;
|
|
43
|
+
export declare type StreamClientArgs = {
|
|
44
|
+
/**
|
|
45
|
+
* The stream url.
|
|
46
|
+
*/
|
|
47
|
+
url: string;
|
|
48
|
+
/**
|
|
49
|
+
* Grpc credentials.
|
|
50
|
+
*
|
|
51
|
+
* Use `ChannelCredentials.createInsecure()` to disable SSL.
|
|
52
|
+
*/
|
|
53
|
+
credentials?: ChannelCredentials;
|
|
54
|
+
/**
|
|
55
|
+
* Grpc client options.
|
|
56
|
+
*/
|
|
57
|
+
clientOptions?: ClientOptions;
|
|
58
|
+
/**
|
|
59
|
+
* Callback to control reconnection after receiving an error from the stream.
|
|
60
|
+
*
|
|
61
|
+
* By default uses `defaultOnReconnect`, which only reconnects on internal grpc errors.
|
|
62
|
+
*/
|
|
63
|
+
onReconnect?: OnReconnect;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* A client to configure and stream data.
|
|
67
|
+
*/
|
|
10
68
|
export declare class StreamClient {
|
|
11
69
|
private readonly inner;
|
|
12
70
|
private stream?;
|
|
13
|
-
private configured;
|
|
14
71
|
private stream_id;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
72
|
+
private onReconnect;
|
|
73
|
+
private configuration?;
|
|
74
|
+
/**
|
|
75
|
+
* Create a new `StreamClient`.
|
|
76
|
+
*
|
|
77
|
+
* Notice that the stream is not connected until you start iterating over it.
|
|
78
|
+
* The stream should be used as an _async iterator_.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { StreamClient } from '@apibara/protocol'
|
|
83
|
+
*
|
|
84
|
+
* const client = new StreamClient({ url })
|
|
85
|
+
*
|
|
86
|
+
* client.configure({ filter, cursor })
|
|
87
|
+
*
|
|
88
|
+
* for await (const message of client) {
|
|
89
|
+
* // use message
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
constructor({ url, credentials, clientOptions, onReconnect }: StreamClientArgs);
|
|
94
|
+
/**
|
|
95
|
+
* Async iterator over messages in the stream.
|
|
96
|
+
*/
|
|
20
97
|
[Symbol.asyncIterator](): AsyncIterator<v1alpha2.IStreamDataResponse>;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Configure the stream to return the requested data.
|
|
100
|
+
*
|
|
101
|
+
* The stream can be reconfigured while streaming data, the client will
|
|
102
|
+
* take care of returning only data for the new configuration even if there
|
|
103
|
+
* are old messages in-flight.
|
|
104
|
+
*/
|
|
105
|
+
configure(args: ConfigureArgs): void;
|
|
106
|
+
private _configure;
|
|
107
|
+
private connect;
|
|
29
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* A `onReconnect` callback that never reconnects.
|
|
111
|
+
*/
|
|
112
|
+
export declare function neverReconnect(_err: StatusObject, _retryCount: number): OnReconnectResult;
|
|
113
|
+
/**
|
|
114
|
+
* A `onReconnect` callback that retries to reconnect up to 5 times.
|
|
115
|
+
*
|
|
116
|
+
* If the error is not an internal error, then it will not reconnect.
|
|
117
|
+
* This callback awaits for `1s * retryCount` before returning.
|
|
118
|
+
*/
|
|
119
|
+
export declare function defaultOnReconnect(err: StatusObject, retryCount: number): Promise<OnReconnectResult>;
|
package/dist/client.js
CHANGED
|
@@ -1,64 +1,170 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StreamClient = exports.ChannelCredentials = void 0;
|
|
3
|
+
exports.defaultOnReconnect = exports.neverReconnect = exports.StreamClient = exports.ChannelCredentials = void 0;
|
|
4
4
|
const grpc_js_1 = require("@grpc/grpc-js");
|
|
5
5
|
const proto_1 = require("./proto");
|
|
6
6
|
const request_1 = require("./request");
|
|
7
7
|
var grpc_js_2 = require("@grpc/grpc-js");
|
|
8
8
|
Object.defineProperty(exports, "ChannelCredentials", { enumerable: true, get: function () { return grpc_js_2.ChannelCredentials; } });
|
|
9
9
|
const StreamService = proto_1.v1alpha2.protoDescriptor.apibara.node.v1alpha2.Stream;
|
|
10
|
+
/**
|
|
11
|
+
* A client to configure and stream data.
|
|
12
|
+
*/
|
|
10
13
|
class StreamClient {
|
|
11
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Create a new `StreamClient`.
|
|
16
|
+
*
|
|
17
|
+
* Notice that the stream is not connected until you start iterating over it.
|
|
18
|
+
* The stream should be used as an _async iterator_.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { StreamClient } from '@apibara/protocol'
|
|
23
|
+
*
|
|
24
|
+
* const client = new StreamClient({ url })
|
|
25
|
+
*
|
|
26
|
+
* client.configure({ filter, cursor })
|
|
27
|
+
*
|
|
28
|
+
* for await (const message of client) {
|
|
29
|
+
* // use message
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
constructor({ url, credentials, clientOptions, onReconnect }) {
|
|
12
34
|
this.inner = new StreamService(url, credentials ?? grpc_js_1.ChannelCredentials.createSsl(), {
|
|
13
35
|
'grpc.keepalive_timeout_ms': 3600000,
|
|
36
|
+
...clientOptions,
|
|
14
37
|
});
|
|
15
|
-
this.configured = false;
|
|
16
38
|
this.stream_id = 0;
|
|
39
|
+
this.onReconnect = onReconnect ?? defaultOnReconnect;
|
|
17
40
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
41
|
+
/**
|
|
42
|
+
* Async iterator over messages in the stream.
|
|
43
|
+
*/
|
|
22
44
|
async *[Symbol.asyncIterator]() {
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
if (!this.configuration) {
|
|
46
|
+
throw new Error('StreamClient must be configured');
|
|
47
|
+
}
|
|
48
|
+
// connect if not connected.
|
|
49
|
+
if (!this.stream) {
|
|
50
|
+
this.connect();
|
|
51
|
+
this._configure(this.configuration);
|
|
52
|
+
}
|
|
53
|
+
while (true) {
|
|
54
|
+
let retryCount = 1;
|
|
55
|
+
let cursor = null;
|
|
56
|
+
try {
|
|
57
|
+
// this check is to make ts happy
|
|
58
|
+
if (!this.stream) {
|
|
59
|
+
throw new Error('Stream disconnected unexpectedly');
|
|
60
|
+
}
|
|
61
|
+
for await (const message of this.stream) {
|
|
62
|
+
const messageTyped = message;
|
|
63
|
+
// only return messages if they are with the most recently configured stream
|
|
64
|
+
if (messageTyped.streamId?.toString() == this.stream_id.toString()) {
|
|
65
|
+
// reset retry count on new message
|
|
66
|
+
retryCount = 1;
|
|
67
|
+
// keep cursor updated for use when reconnecting
|
|
68
|
+
if (messageTyped.data) {
|
|
69
|
+
cursor = messageTyped.data.cursor;
|
|
70
|
+
}
|
|
71
|
+
else if (messageTyped.invalidate) {
|
|
72
|
+
cursor = messageTyped.invalidate.cursor;
|
|
73
|
+
}
|
|
74
|
+
yield messageTyped;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const isGrpcError = err.hasOwnProperty('code') &&
|
|
80
|
+
err.hasOwnProperty('details') &&
|
|
81
|
+
err.hasOwnProperty('metadata');
|
|
82
|
+
// non-grpc error, so just bubble it up
|
|
83
|
+
if (!isGrpcError) {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
const { reconnect, args } = await Promise.resolve(this.onReconnect(err, retryCount));
|
|
87
|
+
retryCount += 1;
|
|
88
|
+
if (!reconnect) {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
this.connect();
|
|
92
|
+
if (args) {
|
|
93
|
+
this._configure(args);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// use same configuration specified by user, restarting from the
|
|
97
|
+
// latest ingested batch.
|
|
98
|
+
const configuration = {
|
|
99
|
+
...this.configuration,
|
|
100
|
+
cursor: cursor ?? this.configuration.cursor,
|
|
101
|
+
};
|
|
102
|
+
this._configure(configuration);
|
|
31
103
|
}
|
|
32
104
|
}
|
|
33
105
|
}
|
|
34
106
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
if (finality) {
|
|
47
|
-
builder.withFinality(finality);
|
|
48
|
-
}
|
|
49
|
-
const request = builder.encode();
|
|
50
|
-
this.stream?.write(request);
|
|
107
|
+
/**
|
|
108
|
+
* Configure the stream to return the requested data.
|
|
109
|
+
*
|
|
110
|
+
* The stream can be reconfigured while streaming data, the client will
|
|
111
|
+
* take care of returning only data for the new configuration even if there
|
|
112
|
+
* are old messages in-flight.
|
|
113
|
+
*/
|
|
114
|
+
configure(args) {
|
|
115
|
+
this.configuration = args;
|
|
116
|
+
this._configure(args);
|
|
51
117
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
118
|
+
_configure(args) {
|
|
119
|
+
const { filter, batchSize, cursor, finality } = args;
|
|
120
|
+
this.stream_id++;
|
|
121
|
+
// only send configuration if connected
|
|
122
|
+
if (this.stream) {
|
|
123
|
+
const builder = request_1.StreamDataRequest.create().withStreamId(this.stream_id).withFilter(filter);
|
|
124
|
+
if (batchSize) {
|
|
125
|
+
builder.withBatchSize(batchSize);
|
|
126
|
+
}
|
|
127
|
+
if (cursor) {
|
|
128
|
+
builder.withStartingCursor(cursor);
|
|
129
|
+
}
|
|
130
|
+
if (finality) {
|
|
131
|
+
builder.withFinality(finality);
|
|
132
|
+
}
|
|
133
|
+
const request = builder.encode();
|
|
134
|
+
this.stream?.write(request);
|
|
55
135
|
}
|
|
56
136
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
137
|
+
connect() {
|
|
138
|
+
this.stream = this.inner.streamData();
|
|
139
|
+
return this;
|
|
61
140
|
}
|
|
62
141
|
}
|
|
63
142
|
exports.StreamClient = StreamClient;
|
|
143
|
+
/**
|
|
144
|
+
* A `onReconnect` callback that never reconnects.
|
|
145
|
+
*/
|
|
146
|
+
function neverReconnect(_err, _retryCount) {
|
|
147
|
+
return {
|
|
148
|
+
reconnect: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
exports.neverReconnect = neverReconnect;
|
|
152
|
+
/**
|
|
153
|
+
* A `onReconnect` callback that retries to reconnect up to 5 times.
|
|
154
|
+
*
|
|
155
|
+
* If the error is not an internal error, then it will not reconnect.
|
|
156
|
+
* This callback awaits for `1s * retryCount` before returning.
|
|
157
|
+
*/
|
|
158
|
+
async function defaultOnReconnect(err, retryCount) {
|
|
159
|
+
if (err.code != 13) {
|
|
160
|
+
return {
|
|
161
|
+
reconnect: false,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
await new Promise((resolve) => setTimeout(resolve, retryCount * 1000));
|
|
165
|
+
return {
|
|
166
|
+
reconnect: retryCount < 5,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
exports.defaultOnReconnect = defaultOnReconnect;
|
|
64
170
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/cursor.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Long from 'long';
|
|
2
|
+
import { ICursor } from './proto/v1alpha2';
|
|
3
|
+
export declare const Cursor: {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new cursor with only the `orderKey` set.
|
|
6
|
+
*
|
|
7
|
+
* Notice that this cursor does not uniquely identify a message in the stream
|
|
8
|
+
* and may result in missing information.
|
|
9
|
+
*/
|
|
10
|
+
createWithOrderKey: (order: string | number | Long) => ICursor;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new cursor with both order and unique keys.
|
|
13
|
+
*
|
|
14
|
+
* This cursor uniquely identifies a message in the stream, even if it has
|
|
15
|
+
* been invalidated.
|
|
16
|
+
*/
|
|
17
|
+
create: (order: string | number | Long, unique: Uint8Array) => ICursor;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the cursor string representation.
|
|
20
|
+
*/
|
|
21
|
+
toString: (cursor?: ICursor | null) => string | undefined;
|
|
22
|
+
};
|
package/dist/cursor.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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.Cursor = void 0;
|
|
7
|
+
const long_1 = __importDefault(require("long"));
|
|
8
|
+
exports.Cursor = {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new cursor with only the `orderKey` set.
|
|
11
|
+
*
|
|
12
|
+
* Notice that this cursor does not uniquely identify a message in the stream
|
|
13
|
+
* and may result in missing information.
|
|
14
|
+
*/
|
|
15
|
+
createWithOrderKey: (order) => {
|
|
16
|
+
return {
|
|
17
|
+
orderKey: long_1.default.fromValue(order),
|
|
18
|
+
uniqueKey: new Uint8Array(),
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new cursor with both order and unique keys.
|
|
23
|
+
*
|
|
24
|
+
* This cursor uniquely identifies a message in the stream, even if it has
|
|
25
|
+
* been invalidated.
|
|
26
|
+
*/
|
|
27
|
+
create: (order, unique) => {
|
|
28
|
+
return {
|
|
29
|
+
orderKey: long_1.default.fromValue(order),
|
|
30
|
+
uniqueKey: unique,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* Returns the cursor string representation.
|
|
35
|
+
*/
|
|
36
|
+
toString: (cursor) => {
|
|
37
|
+
if (!cursor)
|
|
38
|
+
return;
|
|
39
|
+
let hash = Buffer.from(cursor.uniqueKey).toString('hex');
|
|
40
|
+
return `${cursor.orderKey.toString()}/0x${hash}`;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAuB;AAGV,QAAA,MAAM,GAAG;IACpB;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,KAA6B,EAAW,EAAE;QAC7D,OAAO;YACL,QAAQ,EAAE,cAAI,CAAC,SAAS,CAAC,KAAK,CAAC;YAC/B,SAAS,EAAE,IAAI,UAAU,EAAE;SAC5B,CAAA;IACH,CAAC;IAED;;;;;OAKG;IACH,MAAM,EAAE,CAAC,KAA6B,EAAE,MAAkB,EAAW,EAAE;QACrE,OAAO;YACL,QAAQ,EAAE,cAAI,CAAC,SAAS,CAAC,KAAK,CAAC;YAC/B,SAAS,EAAE,MAAM;SAClB,CAAA;IACH,CAAC;IAED;;OAEG;IACH,QAAQ,EAAE,CAAC,MAAuB,EAAsB,EAAE;QACxD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACxD,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAA;IAClD,CAAC;CACF,CAAA"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./proto"), exports);
|
|
18
18
|
__exportStar(require("./client"), exports);
|
|
19
19
|
__exportStar(require("./request"), exports);
|
|
20
|
+
__exportStar(require("./cursor"), exports);
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAuB;AACvB,2CAAwB;AACxB,4CAAyB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAuB;AACvB,2CAAwB;AACxB,4CAAyB;AACzB,2CAAwB"}
|
|
@@ -5,9 +5,11 @@ export interface Data {
|
|
|
5
5
|
'endCursor'?: (_apibara_node_v1alpha2_Cursor | null);
|
|
6
6
|
'finality'?: (_apibara_node_v1alpha2_DataFinality | keyof typeof _apibara_node_v1alpha2_DataFinality);
|
|
7
7
|
'data'?: (Buffer | Uint8Array | string)[];
|
|
8
|
+
'cursor'?: (_apibara_node_v1alpha2_Cursor | null);
|
|
8
9
|
}
|
|
9
10
|
export interface Data__Output {
|
|
10
11
|
'endCursor': (_apibara_node_v1alpha2_Cursor__Output | null);
|
|
11
12
|
'finality': (keyof typeof _apibara_node_v1alpha2_DataFinality);
|
|
12
13
|
'data': (Uint8Array)[];
|
|
14
|
+
'cursor': (_apibara_node_v1alpha2_Cursor__Output | null);
|
|
13
15
|
}
|
|
@@ -4,15 +4,15 @@ import type { Heartbeat as _apibara_node_v1alpha2_Heartbeat, Heartbeat__Output a
|
|
|
4
4
|
import type { Long } from '@grpc/proto-loader';
|
|
5
5
|
export interface StreamDataResponse {
|
|
6
6
|
'streamId'?: (number | string | Long);
|
|
7
|
-
'
|
|
7
|
+
'invalidate'?: (_apibara_node_v1alpha2_Invalidate | null);
|
|
8
8
|
'data'?: (_apibara_node_v1alpha2_Data | null);
|
|
9
9
|
'heartbeat'?: (_apibara_node_v1alpha2_Heartbeat | null);
|
|
10
|
-
'message'?: "
|
|
10
|
+
'message'?: "invalidate" | "data" | "heartbeat";
|
|
11
11
|
}
|
|
12
12
|
export interface StreamDataResponse__Output {
|
|
13
13
|
'streamId': (Long);
|
|
14
|
-
'
|
|
14
|
+
'invalidate'?: (_apibara_node_v1alpha2_Invalidate__Output | null);
|
|
15
15
|
'data'?: (_apibara_node_v1alpha2_Data__Output | null);
|
|
16
16
|
'heartbeat'?: (_apibara_node_v1alpha2_Heartbeat__Output | null);
|
|
17
|
-
'message': "
|
|
17
|
+
'message': "invalidate" | "data" | "heartbeat";
|
|
18
18
|
}
|
package/dist/proto/stream.proto
CHANGED
|
@@ -29,7 +29,7 @@ message StreamDataResponse {
|
|
|
29
29
|
// The stream id.
|
|
30
30
|
uint64 stream_id = 1;
|
|
31
31
|
oneof message {
|
|
32
|
-
Invalidate
|
|
32
|
+
Invalidate invalidate = 2;
|
|
33
33
|
Data data = 3;
|
|
34
34
|
Heartbeat heartbeat = 4;
|
|
35
35
|
}
|
|
@@ -68,6 +68,8 @@ message Data {
|
|
|
68
68
|
DataFinality finality = 2;
|
|
69
69
|
// The stream data.
|
|
70
70
|
repeated bytes data = 3;
|
|
71
|
+
// Cursor used to produced the batch.
|
|
72
|
+
Cursor cursor = 4;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
// Sent to clients to check if stream is still connected.
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { ChannelCredentials, ClientDuplexStream } from '@grpc/grpc-js'
|
|
1
|
+
import { ChannelCredentials, ClientDuplexStream, ClientOptions, StatusObject } from '@grpc/grpc-js'
|
|
2
2
|
import { v1alpha2 } from './proto'
|
|
3
3
|
import { StreamDataRequest } from './request'
|
|
4
4
|
|
|
5
|
-
export { ChannelCredentials } from '@grpc/grpc-js'
|
|
6
|
-
|
|
7
|
-
export type ErrorHandler = (client: StreamClient, err: Error) => void | Promise<void>
|
|
8
|
-
export type CloseHandler = (client: StreamClient) => void | Promise<void>
|
|
9
|
-
export type DataHandler = (client: StreamClient, data: v1alpha2.IData) => void | Promise<void>
|
|
10
|
-
export type InvalidateHandler = (
|
|
11
|
-
client: StreamClient,
|
|
12
|
-
invalidate: v1alpha2.IInvalidate
|
|
13
|
-
) => void | Promise<void>
|
|
14
|
-
export type HeartbeatHandler = (
|
|
15
|
-
client: StreamClient,
|
|
16
|
-
invalidate: v1alpha2.IHeartbeat
|
|
17
|
-
) => void | Promise<void>
|
|
5
|
+
export { ChannelCredentials, StatusObject } from '@grpc/grpc-js'
|
|
18
6
|
|
|
19
7
|
const StreamService = v1alpha2.protoDescriptor.apibara.node.v1alpha2.Stream
|
|
20
8
|
|
|
@@ -23,82 +11,261 @@ export type DataStream = ClientDuplexStream<
|
|
|
23
11
|
v1alpha2.IStreamDataResponse
|
|
24
12
|
>
|
|
25
13
|
|
|
14
|
+
export type ConfigureArgs = {
|
|
15
|
+
/**
|
|
16
|
+
* Stream filter, encoded.
|
|
17
|
+
*/
|
|
18
|
+
filter: Uint8Array
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* How much data in a single message.
|
|
22
|
+
*/
|
|
23
|
+
batchSize?: number
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Starting cursor. This cursor is stream-specific.
|
|
27
|
+
*/
|
|
28
|
+
cursor?: v1alpha2.ICursor | null
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Data finality, e.g. finalized or accepted.
|
|
32
|
+
*/
|
|
33
|
+
finality?: v1alpha2.DataFinality | null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Reconnect callback return value.
|
|
38
|
+
*/
|
|
39
|
+
export type OnReconnectResult = {
|
|
40
|
+
/**
|
|
41
|
+
* If `true`, reconnects to the stream.
|
|
42
|
+
*/
|
|
43
|
+
reconnect: boolean
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stream configuration used when reconnecting.
|
|
47
|
+
*
|
|
48
|
+
* By default, the client uses the last configuration passed to
|
|
49
|
+
* `configure` and updates the `cursor` with the most recent one.
|
|
50
|
+
*/
|
|
51
|
+
args?: ConfigureArgs
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reconnect callback.
|
|
56
|
+
*/
|
|
57
|
+
export type OnReconnect = (
|
|
58
|
+
err: StatusObject,
|
|
59
|
+
retryCount: number
|
|
60
|
+
) => Promise<OnReconnectResult> | OnReconnectResult
|
|
61
|
+
|
|
62
|
+
export type StreamClientArgs = {
|
|
63
|
+
/**
|
|
64
|
+
* The stream url.
|
|
65
|
+
*/
|
|
66
|
+
url: string
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Grpc credentials.
|
|
70
|
+
*
|
|
71
|
+
* Use `ChannelCredentials.createInsecure()` to disable SSL.
|
|
72
|
+
*/
|
|
73
|
+
credentials?: ChannelCredentials
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Grpc client options.
|
|
77
|
+
*/
|
|
78
|
+
clientOptions?: ClientOptions
|
|
79
|
+
/**
|
|
80
|
+
* Callback to control reconnection after receiving an error from the stream.
|
|
81
|
+
*
|
|
82
|
+
* By default uses `defaultOnReconnect`, which only reconnects on internal grpc errors.
|
|
83
|
+
*/
|
|
84
|
+
onReconnect?: OnReconnect
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A client to configure and stream data.
|
|
89
|
+
*/
|
|
26
90
|
export class StreamClient {
|
|
27
91
|
private readonly inner: v1alpha2.StreamClient
|
|
28
92
|
|
|
29
93
|
private stream?: DataStream
|
|
30
|
-
private configured: boolean
|
|
31
94
|
private stream_id: number
|
|
95
|
+
private onReconnect: OnReconnect
|
|
96
|
+
private configuration?: ConfigureArgs
|
|
32
97
|
|
|
33
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Create a new `StreamClient`.
|
|
100
|
+
*
|
|
101
|
+
* Notice that the stream is not connected until you start iterating over it.
|
|
102
|
+
* The stream should be used as an _async iterator_.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { StreamClient } from '@apibara/protocol'
|
|
107
|
+
*
|
|
108
|
+
* const client = new StreamClient({ url })
|
|
109
|
+
*
|
|
110
|
+
* client.configure({ filter, cursor })
|
|
111
|
+
*
|
|
112
|
+
* for await (const message of client) {
|
|
113
|
+
* // use message
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
constructor({ url, credentials, clientOptions, onReconnect }: StreamClientArgs) {
|
|
34
118
|
this.inner = new StreamService(url, credentials ?? ChannelCredentials.createSsl(), {
|
|
35
119
|
'grpc.keepalive_timeout_ms': 3_600_000,
|
|
120
|
+
...clientOptions,
|
|
36
121
|
})
|
|
37
|
-
this.configured = false
|
|
38
122
|
this.stream_id = 0
|
|
123
|
+
this.onReconnect = onReconnect ?? defaultOnReconnect
|
|
39
124
|
}
|
|
40
125
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Async iterator over messages in the stream.
|
|
128
|
+
*/
|
|
46
129
|
async *[Symbol.asyncIterator](): AsyncIterator<v1alpha2.IStreamDataResponse> {
|
|
47
|
-
this.
|
|
48
|
-
|
|
130
|
+
if (!this.configuration) {
|
|
131
|
+
throw new Error('StreamClient must be configured')
|
|
132
|
+
}
|
|
49
133
|
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
134
|
+
// connect if not connected.
|
|
135
|
+
if (!this.stream) {
|
|
136
|
+
this.connect()
|
|
137
|
+
this._configure(this.configuration)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
while (true) {
|
|
141
|
+
let retryCount = 1
|
|
142
|
+
let cursor = null
|
|
143
|
+
try {
|
|
144
|
+
// this check is to make ts happy
|
|
145
|
+
if (!this.stream) {
|
|
146
|
+
throw new Error('Stream disconnected unexpectedly')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for await (const message of this.stream) {
|
|
150
|
+
const messageTyped = message as v1alpha2.IStreamDataResponse
|
|
151
|
+
|
|
152
|
+
// only return messages if they are with the most recently configured stream
|
|
153
|
+
if (messageTyped.streamId?.toString() == this.stream_id.toString()) {
|
|
154
|
+
// reset retry count on new message
|
|
155
|
+
retryCount = 1
|
|
156
|
+
|
|
157
|
+
// keep cursor updated for use when reconnecting
|
|
158
|
+
if (messageTyped.data) {
|
|
159
|
+
cursor = messageTyped.data.cursor
|
|
160
|
+
} else if (messageTyped.invalidate) {
|
|
161
|
+
cursor = messageTyped.invalidate.cursor
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
yield messageTyped
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch (err: any) {
|
|
168
|
+
const isGrpcError =
|
|
169
|
+
err.hasOwnProperty('code') &&
|
|
170
|
+
err.hasOwnProperty('details') &&
|
|
171
|
+
err.hasOwnProperty('metadata')
|
|
172
|
+
|
|
173
|
+
// non-grpc error, so just bubble it up
|
|
174
|
+
if (!isGrpcError) {
|
|
175
|
+
throw err
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const { reconnect, args } = await Promise.resolve(this.onReconnect(err, retryCount))
|
|
179
|
+
retryCount += 1
|
|
180
|
+
if (!reconnect) {
|
|
181
|
+
break
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.connect()
|
|
185
|
+
|
|
186
|
+
if (args) {
|
|
187
|
+
this._configure(args)
|
|
188
|
+
} else {
|
|
189
|
+
// use same configuration specified by user, restarting from the
|
|
190
|
+
// latest ingested batch.
|
|
191
|
+
const configuration = {
|
|
192
|
+
...this.configuration,
|
|
193
|
+
cursor: cursor ?? this.configuration.cursor,
|
|
194
|
+
}
|
|
195
|
+
this._configure(configuration)
|
|
56
196
|
}
|
|
57
197
|
}
|
|
58
198
|
}
|
|
59
199
|
}
|
|
60
200
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
this.ensureConnected()
|
|
73
|
-
this.configured = true
|
|
201
|
+
/**
|
|
202
|
+
* Configure the stream to return the requested data.
|
|
203
|
+
*
|
|
204
|
+
* The stream can be reconfigured while streaming data, the client will
|
|
205
|
+
* take care of returning only data for the new configuration even if there
|
|
206
|
+
* are old messages in-flight.
|
|
207
|
+
*/
|
|
208
|
+
configure(args: ConfigureArgs) {
|
|
209
|
+
this.configuration = args
|
|
210
|
+
this._configure(args)
|
|
211
|
+
}
|
|
74
212
|
|
|
213
|
+
private _configure(args: ConfigureArgs) {
|
|
214
|
+
const { filter, batchSize, cursor, finality } = args
|
|
75
215
|
this.stream_id++
|
|
76
216
|
|
|
77
|
-
|
|
217
|
+
// only send configuration if connected
|
|
218
|
+
if (this.stream) {
|
|
219
|
+
const builder = StreamDataRequest.create().withStreamId(this.stream_id).withFilter(filter)
|
|
78
220
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
221
|
+
if (batchSize) {
|
|
222
|
+
builder.withBatchSize(batchSize)
|
|
223
|
+
}
|
|
224
|
+
if (cursor) {
|
|
225
|
+
builder.withStartingCursor(cursor)
|
|
226
|
+
}
|
|
227
|
+
if (finality) {
|
|
228
|
+
builder.withFinality(finality)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const request = builder.encode()
|
|
232
|
+
this.stream?.write(request)
|
|
87
233
|
}
|
|
234
|
+
}
|
|
88
235
|
|
|
89
|
-
|
|
90
|
-
this.stream
|
|
236
|
+
private connect() {
|
|
237
|
+
this.stream = this.inner.streamData()
|
|
238
|
+
return this
|
|
91
239
|
}
|
|
240
|
+
}
|
|
92
241
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
242
|
+
/**
|
|
243
|
+
* A `onReconnect` callback that never reconnects.
|
|
244
|
+
*/
|
|
245
|
+
export function neverReconnect(_err: StatusObject, _retryCount: number): OnReconnectResult {
|
|
246
|
+
return {
|
|
247
|
+
reconnect: false,
|
|
97
248
|
}
|
|
249
|
+
}
|
|
98
250
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
251
|
+
/**
|
|
252
|
+
* A `onReconnect` callback that retries to reconnect up to 5 times.
|
|
253
|
+
*
|
|
254
|
+
* If the error is not an internal error, then it will not reconnect.
|
|
255
|
+
* This callback awaits for `1s * retryCount` before returning.
|
|
256
|
+
*/
|
|
257
|
+
export async function defaultOnReconnect(
|
|
258
|
+
err: StatusObject,
|
|
259
|
+
retryCount: number
|
|
260
|
+
): Promise<OnReconnectResult> {
|
|
261
|
+
if (err.code != 13) {
|
|
262
|
+
return {
|
|
263
|
+
reconnect: false,
|
|
102
264
|
}
|
|
103
265
|
}
|
|
266
|
+
|
|
267
|
+
await new Promise((resolve) => setTimeout(resolve, retryCount * 1000))
|
|
268
|
+
return {
|
|
269
|
+
reconnect: retryCount < 5,
|
|
270
|
+
}
|
|
104
271
|
}
|
package/src/cursor.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Long from 'long'
|
|
2
|
+
import { ICursor } from './proto/v1alpha2'
|
|
3
|
+
|
|
4
|
+
export const Cursor = {
|
|
5
|
+
/**
|
|
6
|
+
* Creates a new cursor with only the `orderKey` set.
|
|
7
|
+
*
|
|
8
|
+
* Notice that this cursor does not uniquely identify a message in the stream
|
|
9
|
+
* and may result in missing information.
|
|
10
|
+
*/
|
|
11
|
+
createWithOrderKey: (order: string | number | Long): ICursor => {
|
|
12
|
+
return {
|
|
13
|
+
orderKey: Long.fromValue(order),
|
|
14
|
+
uniqueKey: new Uint8Array(),
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new cursor with both order and unique keys.
|
|
20
|
+
*
|
|
21
|
+
* This cursor uniquely identifies a message in the stream, even if it has
|
|
22
|
+
* been invalidated.
|
|
23
|
+
*/
|
|
24
|
+
create: (order: string | number | Long, unique: Uint8Array): ICursor => {
|
|
25
|
+
return {
|
|
26
|
+
orderKey: Long.fromValue(order),
|
|
27
|
+
uniqueKey: unique,
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the cursor string representation.
|
|
33
|
+
*/
|
|
34
|
+
toString: (cursor?: ICursor | null): string | undefined => {
|
|
35
|
+
if (!cursor) return
|
|
36
|
+
let hash = Buffer.from(cursor.uniqueKey).toString('hex')
|
|
37
|
+
return `${cursor.orderKey.toString()}/0x${hash}`
|
|
38
|
+
},
|
|
39
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,10 +7,12 @@ export interface Data {
|
|
|
7
7
|
'endCursor'?: (_apibara_node_v1alpha2_Cursor | null);
|
|
8
8
|
'finality'?: (_apibara_node_v1alpha2_DataFinality | keyof typeof _apibara_node_v1alpha2_DataFinality);
|
|
9
9
|
'data'?: (Buffer | Uint8Array | string)[];
|
|
10
|
+
'cursor'?: (_apibara_node_v1alpha2_Cursor | null);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export interface Data__Output {
|
|
13
14
|
'endCursor': (_apibara_node_v1alpha2_Cursor__Output | null);
|
|
14
15
|
'finality': (keyof typeof _apibara_node_v1alpha2_DataFinality);
|
|
15
16
|
'data': (Uint8Array)[];
|
|
17
|
+
'cursor': (_apibara_node_v1alpha2_Cursor__Output | null);
|
|
16
18
|
}
|
|
@@ -7,16 +7,16 @@ import type { Long } from '@grpc/proto-loader';
|
|
|
7
7
|
|
|
8
8
|
export interface StreamDataResponse {
|
|
9
9
|
'streamId'?: (number | string | Long);
|
|
10
|
-
'
|
|
10
|
+
'invalidate'?: (_apibara_node_v1alpha2_Invalidate | null);
|
|
11
11
|
'data'?: (_apibara_node_v1alpha2_Data | null);
|
|
12
12
|
'heartbeat'?: (_apibara_node_v1alpha2_Heartbeat | null);
|
|
13
|
-
'message'?: "
|
|
13
|
+
'message'?: "invalidate"|"data"|"heartbeat";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface StreamDataResponse__Output {
|
|
17
17
|
'streamId': (Long);
|
|
18
|
-
'
|
|
18
|
+
'invalidate'?: (_apibara_node_v1alpha2_Invalidate__Output | null);
|
|
19
19
|
'data'?: (_apibara_node_v1alpha2_Data__Output | null);
|
|
20
20
|
'heartbeat'?: (_apibara_node_v1alpha2_Heartbeat__Output | null);
|
|
21
|
-
'message': "
|
|
21
|
+
'message': "invalidate"|"data"|"heartbeat";
|
|
22
22
|
}
|
package/src/proto/stream.proto
CHANGED
|
@@ -29,7 +29,7 @@ message StreamDataResponse {
|
|
|
29
29
|
// The stream id.
|
|
30
30
|
uint64 stream_id = 1;
|
|
31
31
|
oneof message {
|
|
32
|
-
Invalidate
|
|
32
|
+
Invalidate invalidate = 2;
|
|
33
33
|
Data data = 3;
|
|
34
34
|
Heartbeat heartbeat = 4;
|
|
35
35
|
}
|
|
@@ -68,6 +68,8 @@ message Data {
|
|
|
68
68
|
DataFinality finality = 2;
|
|
69
69
|
// The stream data.
|
|
70
70
|
repeated bytes data = 3;
|
|
71
|
+
// Cursor used to produced the batch.
|
|
72
|
+
Cursor cursor = 4;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
// Sent to clients to check if stream is still connected.
|