@apibara/protocol 0.2.0 → 0.3.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 +7 -3
- package/dist/client.js +13 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/stream.d.ts +32 -0
- package/dist/stream.js +95 -0
- package/dist/stream.js.map +1 -0
- package/dist/stream.test.d.ts +0 -0
- package/dist/stream.test.js +19 -0
- package/dist/stream.test.js.map +1 -0
- package/package.json +2 -1
- package/src/client.ts +20 -5
- package/src/index.ts +1 -0
- package/src/stream.test.ts +9 -0
- package/src/stream.ts +127 -0
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ClientOptions, ChannelCredentials
|
|
1
|
+
import { ClientOptions, ChannelCredentials } from '@grpc/grpc-js';
|
|
2
|
+
import { Retry, StreamMessagesStream } from './stream';
|
|
2
3
|
import { NodeClient as GrpcNodeClient } from './proto/apibara/node/v1alpha1/Node';
|
|
3
4
|
import { StatusResponse__Output } from './proto/apibara/node/v1alpha1/StatusResponse';
|
|
4
|
-
import { StreamMessagesResponse__Output } from './proto/apibara/node/v1alpha1/StreamMessagesResponse';
|
|
5
5
|
import { StreamMessagesRequest } from './proto/apibara/node/v1alpha1/StreamMessagesRequest';
|
|
6
6
|
export declare const Node: (new (address: string, credentials: ChannelCredentials, options?: ClientOptions | undefined) => GrpcNodeClient) & {
|
|
7
7
|
service: import("./proto/apibara/node/v1alpha1/Node").NodeDefinition;
|
|
@@ -16,9 +16,13 @@ export declare const credentials: {
|
|
|
16
16
|
createFromGoogleCredential: typeof import("@grpc/grpc-js").CallCredentials.createFromGoogleCredential;
|
|
17
17
|
createEmpty: typeof import("@grpc/grpc-js").CallCredentials.createEmpty;
|
|
18
18
|
};
|
|
19
|
+
export interface StreamMessagesOptions {
|
|
20
|
+
reconnect?: boolean;
|
|
21
|
+
onRetry?: (retryCount: number) => Retry;
|
|
22
|
+
}
|
|
19
23
|
export declare class NodeClient {
|
|
20
24
|
private readonly client;
|
|
21
25
|
constructor(address: string, credentials: ChannelCredentials, options?: ClientOptions);
|
|
22
26
|
status(): Promise<StatusResponse__Output | undefined>;
|
|
23
|
-
streamMessages(args: StreamMessagesRequest):
|
|
27
|
+
streamMessages(args: StreamMessagesRequest, options?: StreamMessagesOptions): StreamMessagesStream;
|
|
24
28
|
}
|
package/dist/client.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.NodeClient = exports.credentials = exports.Node = void 0;
|
|
|
13
13
|
const util_1 = require("util");
|
|
14
14
|
const grpc_js_1 = require("@grpc/grpc-js");
|
|
15
15
|
const proto_loader_1 = require("@grpc/proto-loader");
|
|
16
|
+
const stream_1 = require("./stream");
|
|
16
17
|
const __NODE_PROTO_PATH = __dirname + '/proto/node.proto';
|
|
17
18
|
const packageDefinition = (0, proto_loader_1.loadSync)(__NODE_PROTO_PATH, {});
|
|
18
19
|
const protoDescriptor = (0, grpc_js_1.loadPackageDefinition)(packageDefinition);
|
|
@@ -27,8 +28,18 @@ class NodeClient {
|
|
|
27
28
|
return (0, util_1.promisify)(this.client.Status.bind(this.client, {}))();
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
|
-
streamMessages(args) {
|
|
31
|
-
|
|
31
|
+
streamMessages(args, options) {
|
|
32
|
+
// only reconnect if user has opted-in
|
|
33
|
+
let onRetry = stream_1.neverRetry;
|
|
34
|
+
if (options === null || options === void 0 ? void 0 : options.reconnect) {
|
|
35
|
+
if (options.onRetry) {
|
|
36
|
+
onRetry = options.onRetry;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
onRetry = stream_1.defaultOnRetry;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return new stream_1.StreamMessagesStream({ args, onRetry, client: this.client });
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
exports.NodeClient = NodeClient;
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+BAAgC;AAChC,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+BAAgC;AAChC,2CAKsB;AACtB,qDAA6C;AAC7C,qCAAkF;AAMlF,MAAM,iBAAiB,GAAG,SAAS,GAAG,mBAAmB,CAAA;AAEzD,MAAM,iBAAiB,GAAG,IAAA,uBAAQ,EAAC,iBAAiB,EAAE,EAAE,CAAC,CAAA;AACzD,MAAM,eAAe,GAAG,IAAA,+BAAqB,EAAC,iBAAiB,CAA6B,CAAA;AAE/E,QAAA,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAA;AAEjD,QAAA,WAAW,GAAG,qBAAe,CAAA;AAO1C,MAAa,UAAU;IAGrB,YAAY,OAAe,EAAE,WAA+B,EAAE,OAAuB;QACnF,IAAI,CAAC,MAAM,GAAG,IAAI,YAAI,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;IACvD,CAAC;IAEY,MAAM;;YACjB,OAAO,IAAA,gBAAS,EAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAA;QAC9D,CAAC;KAAA;IAEM,cAAc,CACnB,IAA2B,EAC3B,OAA+B;QAE/B,sCAAsC;QACtC,IAAI,OAAO,GAAG,mBAAU,CAAA;QACxB,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,SAAS,EAAE;YACtB,IAAI,OAAO,CAAC,OAAO,EAAE;gBACnB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;aAC1B;iBAAM;gBACL,OAAO,GAAG,uBAAc,CAAA;aACzB;SACF;QAED,OAAO,IAAI,6BAAoB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;IACzE,CAAC;CACF;AA3BD,gCA2BC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,5 +29,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.proto = void 0;
|
|
30
30
|
__exportStar(require("./client"), exports);
|
|
31
31
|
__exportStar(require("./buffer"), exports);
|
|
32
|
+
__exportStar(require("./stream"), exports);
|
|
32
33
|
exports.proto = __importStar(require("./proto"));
|
|
33
34
|
//# 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,2CAAwB;AACxB,2CAAwB;AACxB,iDAAgC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwB;AACxB,2CAAwB;AACxB,2CAAwB;AACxB,iDAAgC"}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Readable } from 'stream';
|
|
3
|
+
import { NodeClient as GrpcNodeClient } from './proto/apibara/node/v1alpha1/Node';
|
|
4
|
+
import { StreamMessagesResponse__Output } from './proto/apibara/node/v1alpha1/StreamMessagesResponse';
|
|
5
|
+
import { StreamMessagesRequest } from './proto/apibara/node/v1alpha1/StreamMessagesRequest';
|
|
6
|
+
export declare function neverRetry(_retryCount: number): Retry;
|
|
7
|
+
export declare function defaultOnRetry(retryCount: number): Retry;
|
|
8
|
+
export interface Retry {
|
|
9
|
+
retry: boolean;
|
|
10
|
+
startingSequence?: number;
|
|
11
|
+
delay?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class StreamMessagesStream extends Readable {
|
|
14
|
+
private readonly client;
|
|
15
|
+
private readonly initialRequest;
|
|
16
|
+
private readonly onRetry;
|
|
17
|
+
private source?;
|
|
18
|
+
private currentSequence?;
|
|
19
|
+
private retryCount;
|
|
20
|
+
constructor({ args, onRetry, client, }: {
|
|
21
|
+
args: StreamMessagesRequest;
|
|
22
|
+
onRetry: (retryCount: number) => Retry;
|
|
23
|
+
client: GrpcNodeClient;
|
|
24
|
+
});
|
|
25
|
+
_construct(callback: (error?: Error | null | undefined) => void): void;
|
|
26
|
+
_read(_size: number): void;
|
|
27
|
+
setupSource(startingSequence?: number): void;
|
|
28
|
+
cleanupSource(): void;
|
|
29
|
+
onData(chunk: StreamMessagesResponse__Output): void;
|
|
30
|
+
onError(_err: any): void;
|
|
31
|
+
onEnd(): void;
|
|
32
|
+
}
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamMessagesStream = exports.defaultOnRetry = exports.neverRetry = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
const defaultMaxRetry = 3;
|
|
6
|
+
const defaultDelaySeconds = 5;
|
|
7
|
+
function neverRetry(_retryCount) {
|
|
8
|
+
return {
|
|
9
|
+
retry: false,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
exports.neverRetry = neverRetry;
|
|
13
|
+
function defaultOnRetry(retryCount) {
|
|
14
|
+
return {
|
|
15
|
+
retry: retryCount < defaultMaxRetry,
|
|
16
|
+
delay: defaultDelaySeconds,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
exports.defaultOnRetry = defaultOnRetry;
|
|
20
|
+
class StreamMessagesStream extends stream_1.Readable {
|
|
21
|
+
constructor({ args, onRetry, client, }) {
|
|
22
|
+
super({ objectMode: true });
|
|
23
|
+
this.initialRequest = args;
|
|
24
|
+
this.client = client;
|
|
25
|
+
this.onRetry = onRetry;
|
|
26
|
+
this.retryCount = 0;
|
|
27
|
+
}
|
|
28
|
+
_construct(callback) {
|
|
29
|
+
this.setupSource();
|
|
30
|
+
callback();
|
|
31
|
+
}
|
|
32
|
+
_read(_size) {
|
|
33
|
+
if (this.source) {
|
|
34
|
+
// resume source data if not flowing
|
|
35
|
+
if (!this.source.readableFlowing) {
|
|
36
|
+
this.source.resume();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// create source stream
|
|
41
|
+
this.setupSource(this.currentSequence);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
setupSource(startingSequence) {
|
|
45
|
+
const request = startingSequence !== undefined
|
|
46
|
+
? Object.assign(Object.assign({}, this.initialRequest), { startingSequence }) : this.initialRequest;
|
|
47
|
+
this.source = this.client.streamMessages(request);
|
|
48
|
+
this.source.on('data', this.onData.bind(this));
|
|
49
|
+
this.source.once('error', this.onError.bind(this));
|
|
50
|
+
this.source.once('end', this.onEnd.bind(this));
|
|
51
|
+
}
|
|
52
|
+
cleanupSource() {
|
|
53
|
+
if (!this.source) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.source.off('data', this.onData.bind(this));
|
|
57
|
+
this.source.off('error', this.onData.bind(this));
|
|
58
|
+
this.source.off('end', this.onEnd.bind(this));
|
|
59
|
+
}
|
|
60
|
+
onData(chunk) {
|
|
61
|
+
var _a;
|
|
62
|
+
// reset retry count
|
|
63
|
+
this.retryCount = 0;
|
|
64
|
+
if (chunk.data) {
|
|
65
|
+
this.currentSequence = +chunk.data.sequence;
|
|
66
|
+
}
|
|
67
|
+
else if (chunk.invalidate) {
|
|
68
|
+
this.currentSequence = +chunk.invalidate;
|
|
69
|
+
}
|
|
70
|
+
// apply backpressure on stream
|
|
71
|
+
if (!this.push(chunk)) {
|
|
72
|
+
(_a = this.source) === null || _a === void 0 ? void 0 : _a.pause();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
onError(_err) {
|
|
76
|
+
this.cleanupSource();
|
|
77
|
+
this.retryCount += 1;
|
|
78
|
+
const { retry, delay, startingSequence } = this.onRetry(this.retryCount);
|
|
79
|
+
if (retry) {
|
|
80
|
+
const intervalDelayMs = (delay !== null && delay !== void 0 ? delay : defaultDelaySeconds) * 1000;
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
this.setupSource(startingSequence);
|
|
83
|
+
}, intervalDelayMs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
onEnd() {
|
|
87
|
+
// block streams never end
|
|
88
|
+
if (this.retryCount > 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.cleanupSource();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.StreamMessagesStream = StreamMessagesStream;
|
|
95
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
describe('StreamMessagesStream', () => {
|
|
12
|
+
it('produces data', () => __awaiter(void 0, void 0, void 0, function* () { }));
|
|
13
|
+
describe('on error', () => {
|
|
14
|
+
it('produces an error by default', () => { });
|
|
15
|
+
it('reconnects', () => { });
|
|
16
|
+
it('produces an error after trying to reconnect', () => { });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=stream.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.test.js","sourceRoot":"","sources":["../src/stream.test.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,eAAe,EAAE,GAAS,EAAE,kDAAE,CAAC,CAAA,CAAC,CAAA;IAEnC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC5C,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC1B,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"prebuild": "pnpm run protobuf && pnpm run gen-types",
|
|
22
22
|
"build": "tsc",
|
|
23
|
+
"test": "jest",
|
|
23
24
|
"lint": "eslint src --ext .ts",
|
|
24
25
|
"lint:fix": "eslint --fix src --ext .ts",
|
|
25
26
|
"protobuf": "mkdir -p dist/proto && cp src/proto/*.proto dist/proto",
|
package/src/client.ts
CHANGED
|
@@ -4,13 +4,12 @@ import {
|
|
|
4
4
|
credentials as grpcCredentials,
|
|
5
5
|
ClientOptions,
|
|
6
6
|
ChannelCredentials,
|
|
7
|
-
ClientReadableStream,
|
|
8
7
|
} from '@grpc/grpc-js'
|
|
9
8
|
import { loadSync } from '@grpc/proto-loader'
|
|
9
|
+
import { Retry, neverRetry, defaultOnRetry, StreamMessagesStream } from './stream'
|
|
10
10
|
import { NodeClient as GrpcNodeClient } from './proto/apibara/node/v1alpha1/Node'
|
|
11
11
|
import { ProtoGrpcType } from './proto/node'
|
|
12
12
|
import { StatusResponse__Output } from './proto/apibara/node/v1alpha1/StatusResponse'
|
|
13
|
-
import { StreamMessagesResponse__Output } from './proto/apibara/node/v1alpha1/StreamMessagesResponse'
|
|
14
13
|
import { StreamMessagesRequest } from './proto/apibara/node/v1alpha1/StreamMessagesRequest'
|
|
15
14
|
|
|
16
15
|
const __NODE_PROTO_PATH = __dirname + '/proto/node.proto'
|
|
@@ -22,6 +21,11 @@ export const Node = protoDescriptor.apibara.node.v1alpha1.Node
|
|
|
22
21
|
|
|
23
22
|
export const credentials = grpcCredentials
|
|
24
23
|
|
|
24
|
+
export interface StreamMessagesOptions {
|
|
25
|
+
reconnect?: boolean
|
|
26
|
+
onRetry?: (retryCount: number) => Retry
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
export class NodeClient {
|
|
26
30
|
private readonly client: GrpcNodeClient
|
|
27
31
|
|
|
@@ -34,8 +38,19 @@ export class NodeClient {
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
public streamMessages(
|
|
37
|
-
args: StreamMessagesRequest
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
args: StreamMessagesRequest,
|
|
42
|
+
options?: StreamMessagesOptions
|
|
43
|
+
): StreamMessagesStream {
|
|
44
|
+
// only reconnect if user has opted-in
|
|
45
|
+
let onRetry = neverRetry
|
|
46
|
+
if (options?.reconnect) {
|
|
47
|
+
if (options.onRetry) {
|
|
48
|
+
onRetry = options.onRetry
|
|
49
|
+
} else {
|
|
50
|
+
onRetry = defaultOnRetry
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new StreamMessagesStream({ args, onRetry, client: this.client })
|
|
40
55
|
}
|
|
41
56
|
}
|
package/src/index.ts
CHANGED
package/src/stream.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Readable } from 'stream'
|
|
2
|
+
import { ClientReadableStream } from '@grpc/grpc-js'
|
|
3
|
+
import { NodeClient as GrpcNodeClient } from './proto/apibara/node/v1alpha1/Node'
|
|
4
|
+
import { StreamMessagesResponse__Output } from './proto/apibara/node/v1alpha1/StreamMessagesResponse'
|
|
5
|
+
import { StreamMessagesRequest } from './proto/apibara/node/v1alpha1/StreamMessagesRequest'
|
|
6
|
+
|
|
7
|
+
const defaultMaxRetry = 3
|
|
8
|
+
const defaultDelaySeconds = 5
|
|
9
|
+
|
|
10
|
+
export function neverRetry(_retryCount: number): Retry {
|
|
11
|
+
return {
|
|
12
|
+
retry: false,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function defaultOnRetry(retryCount: number): Retry {
|
|
17
|
+
return {
|
|
18
|
+
retry: retryCount < defaultMaxRetry,
|
|
19
|
+
delay: defaultDelaySeconds,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Retry {
|
|
24
|
+
retry: boolean
|
|
25
|
+
startingSequence?: number
|
|
26
|
+
delay?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class StreamMessagesStream extends Readable {
|
|
30
|
+
private readonly client: GrpcNodeClient
|
|
31
|
+
private readonly initialRequest: StreamMessagesRequest
|
|
32
|
+
private readonly onRetry: (retryCount: number) => Retry
|
|
33
|
+
private source?: ClientReadableStream<StreamMessagesResponse__Output>
|
|
34
|
+
private currentSequence?: number
|
|
35
|
+
private retryCount: number
|
|
36
|
+
|
|
37
|
+
constructor({
|
|
38
|
+
args,
|
|
39
|
+
onRetry,
|
|
40
|
+
client,
|
|
41
|
+
}: {
|
|
42
|
+
args: StreamMessagesRequest
|
|
43
|
+
onRetry: (retryCount: number) => Retry
|
|
44
|
+
client: GrpcNodeClient
|
|
45
|
+
}) {
|
|
46
|
+
super({ objectMode: true })
|
|
47
|
+
this.initialRequest = args
|
|
48
|
+
this.client = client
|
|
49
|
+
this.onRetry = onRetry
|
|
50
|
+
this.retryCount = 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_construct(callback: (error?: Error | null | undefined) => void): void {
|
|
54
|
+
this.setupSource()
|
|
55
|
+
callback()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_read(_size: number): void {
|
|
59
|
+
if (this.source) {
|
|
60
|
+
// resume source data if not flowing
|
|
61
|
+
if (!this.source.readableFlowing) {
|
|
62
|
+
this.source.resume()
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// create source stream
|
|
66
|
+
this.setupSource(this.currentSequence)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setupSource(startingSequence?: number) {
|
|
71
|
+
const request =
|
|
72
|
+
startingSequence !== undefined
|
|
73
|
+
? { ...this.initialRequest, startingSequence }
|
|
74
|
+
: this.initialRequest
|
|
75
|
+
this.source = this.client.streamMessages(request)
|
|
76
|
+
|
|
77
|
+
this.source.on('data', this.onData.bind(this))
|
|
78
|
+
this.source.once('error', this.onError.bind(this))
|
|
79
|
+
this.source.once('end', this.onEnd.bind(this))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cleanupSource() {
|
|
83
|
+
if (!this.source) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.source.off('data', this.onData.bind(this))
|
|
88
|
+
this.source.off('error', this.onData.bind(this))
|
|
89
|
+
this.source.off('end', this.onEnd.bind(this))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onData(chunk: StreamMessagesResponse__Output) {
|
|
93
|
+
// reset retry count
|
|
94
|
+
this.retryCount = 0
|
|
95
|
+
if (chunk.data) {
|
|
96
|
+
this.currentSequence = +chunk.data.sequence
|
|
97
|
+
} else if (chunk.invalidate) {
|
|
98
|
+
this.currentSequence = +chunk.invalidate
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// apply backpressure on stream
|
|
102
|
+
if (!this.push(chunk)) {
|
|
103
|
+
this.source?.pause()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
onError(_err: any) {
|
|
108
|
+
this.cleanupSource()
|
|
109
|
+
this.retryCount += 1
|
|
110
|
+
|
|
111
|
+
const { retry, delay, startingSequence } = this.onRetry(this.retryCount)
|
|
112
|
+
if (retry) {
|
|
113
|
+
const intervalDelayMs = (delay ?? defaultDelaySeconds) * 1000
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
this.setupSource(startingSequence)
|
|
116
|
+
}, intervalDelayMs)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
onEnd() {
|
|
121
|
+
// block streams never end
|
|
122
|
+
if (this.retryCount > 0) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
this.cleanupSource()
|
|
126
|
+
}
|
|
127
|
+
}
|