@apibara/protocol 0.4.9 → 2.0.0-beta.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/CHANGELOG.md +103 -0
- package/buf.gen.yaml +14 -0
- package/build.config.ts +8 -0
- package/dist/index.d.mts +610 -0
- package/dist/index.d.ts +610 -4
- package/dist/index.mjs +1172 -0
- package/package.json +44 -24
- package/proto/common.proto +22 -0
- package/proto/stream.proto +83 -0
- package/proto/testing.proto +11 -0
- package/src/client.ts +113 -333
- package/src/common.test.ts +67 -0
- package/src/common.ts +65 -0
- package/src/config.ts +38 -0
- package/src/index.ts +10 -3
- package/src/proto/common.ts +279 -0
- package/src/proto/index.ts +3 -1
- package/src/proto/stream.ts +728 -17
- package/src/proto/testing.ts +143 -8
- package/src/rate.ts +54 -0
- package/src/status.test.ts +51 -0
- package/src/status.ts +22 -0
- package/src/stream.test-d.ts +33 -0
- package/src/stream.test.ts +238 -0
- package/src/stream.ts +128 -0
- package/src/testing/client.test.ts +71 -0
- package/src/testing/client.ts +49 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/mock.test.ts +35 -0
- package/src/testing/mock.ts +71 -0
- package/tsconfig.json +12 -0
- package/dist/client.d.ts +0 -130
- package/dist/client.js +0 -215
- package/dist/client.js.map +0 -1
- package/dist/cursor.d.ts +0 -35
- package/dist/cursor.js +0 -67
- package/dist/cursor.js.map +0 -1
- package/dist/cursor.test.d.ts +0 -1
- package/dist/cursor.test.js +0 -22
- package/dist/cursor.test.js.map +0 -1
- package/dist/index.js +0 -21
- package/dist/index.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/Cursor.d.ts +0 -10
- package/dist/proto/apibara/node/v1alpha2/Cursor.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/Cursor.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/Data.d.ts +0 -15
- package/dist/proto/apibara/node/v1alpha2/Data.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/Data.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/DataFinality.d.ts +0 -8
- package/dist/proto/apibara/node/v1alpha2/DataFinality.js +0 -11
- package/dist/proto/apibara/node/v1alpha2/DataFinality.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/Heartbeat.d.ts +0 -4
- package/dist/proto/apibara/node/v1alpha2/Heartbeat.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/Heartbeat.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/Invalidate.d.ts +0 -7
- package/dist/proto/apibara/node/v1alpha2/Invalidate.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/Invalidate.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/Stream.d.ts +0 -16
- package/dist/proto/apibara/node/v1alpha2/Stream.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/Stream.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/StreamDataRequest.d.ts +0 -24
- package/dist/proto/apibara/node/v1alpha2/StreamDataRequest.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/StreamDataRequest.js.map +0 -1
- package/dist/proto/apibara/node/v1alpha2/StreamDataResponse.d.ts +0 -18
- package/dist/proto/apibara/node/v1alpha2/StreamDataResponse.js +0 -4
- package/dist/proto/apibara/node/v1alpha2/StreamDataResponse.js.map +0 -1
- package/dist/proto/index.d.ts +0 -1
- package/dist/proto/index.js +0 -28
- package/dist/proto/index.js.map +0 -1
- package/dist/proto/stream.d.ts +0 -25
- package/dist/proto/stream.js +0 -3
- package/dist/proto/stream.js.map +0 -1
- package/dist/proto/stream.proto +0 -76
- package/dist/proto/testing.d.ts +0 -5
- package/dist/proto/testing.js +0 -24
- package/dist/proto/testing.js.map +0 -1
- package/dist/proto/v1alpha2.d.ts +0 -11
- package/dist/proto/v1alpha2.js +0 -15
- package/dist/proto/v1alpha2.js.map +0 -1
- package/dist/request.d.ts +0 -36
- package/dist/request.js +0 -58
- package/dist/request.js.map +0 -1
- package/dist/request.test.d.ts +0 -1
- package/dist/request.test.js +0 -28
- package/dist/request.test.js.map +0 -1
- package/src/cursor.test.ts +0 -21
- package/src/cursor.ts +0 -67
- package/src/proto/apibara/node/v1alpha2/Cursor.ts +0 -13
- package/src/proto/apibara/node/v1alpha2/Data.ts +0 -18
- package/src/proto/apibara/node/v1alpha2/DataFinality.ts +0 -20
- package/src/proto/apibara/node/v1alpha2/Heartbeat.ts +0 -8
- package/src/proto/apibara/node/v1alpha2/Invalidate.ts +0 -11
- package/src/proto/apibara/node/v1alpha2/Stream.ts +0 -23
- package/src/proto/apibara/node/v1alpha2/StreamDataRequest.ts +0 -27
- package/src/proto/apibara/node/v1alpha2/StreamDataResponse.ts +0 -22
- package/src/proto/stream.proto +0 -76
- package/src/proto/v1alpha2.ts +0 -24
- package/src/request.test.ts +0 -30
- package/src/request.ts +0 -64
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { MockBlock } from "../proto/testing";
|
|
3
|
+
import { MockClient } from "./client";
|
|
4
|
+
import type { MockFilter } from "./mock";
|
|
5
|
+
|
|
6
|
+
describe("MockClient", () => {
|
|
7
|
+
it("returns a stream of messages", async () => {
|
|
8
|
+
const client = new MockClient<MockFilter, MockBlock>(() => {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
_tag: "data",
|
|
12
|
+
data: { finality: "finalized", data: [{ data: "hello" }] },
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const output = [];
|
|
18
|
+
for await (const m of client.streamData({ filter: [] })) {
|
|
19
|
+
output.push(m);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
expect(output).toMatchInlineSnapshot(`
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
"_tag": "data",
|
|
26
|
+
"data": {
|
|
27
|
+
"data": [
|
|
28
|
+
{
|
|
29
|
+
"data": "hello",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
"finality": "finalized",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("supports factory messages", async () => {
|
|
40
|
+
const client = new MockClient<MockFilter, MockBlock>(() => {
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
_tag: "data",
|
|
44
|
+
data: { finality: "finalized", data: [{ data: "hello" }, null] },
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const output = [];
|
|
50
|
+
for await (const m of client.streamData({ filter: [] })) {
|
|
51
|
+
output.push(m);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
expect(output).toMatchInlineSnapshot(`
|
|
55
|
+
[
|
|
56
|
+
{
|
|
57
|
+
"_tag": "data",
|
|
58
|
+
"data": {
|
|
59
|
+
"data": [
|
|
60
|
+
{
|
|
61
|
+
"data": "hello",
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
],
|
|
65
|
+
"finality": "finalized",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
]
|
|
69
|
+
`);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Client, ClientCallOptions, StreamDataOptions } from "../client";
|
|
2
|
+
import type { StatusRequest, StatusResponse } from "../status";
|
|
3
|
+
import type { StreamDataRequest, StreamDataResponse } from "../stream";
|
|
4
|
+
|
|
5
|
+
export class MockClient<TFilter, TBlock> implements Client<TFilter, TBlock> {
|
|
6
|
+
constructor(
|
|
7
|
+
private messageFactory: (
|
|
8
|
+
request: StreamDataRequest<TFilter>,
|
|
9
|
+
options?: StreamDataOptions,
|
|
10
|
+
) => (StreamDataResponse<TBlock> | Error)[],
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
async status(
|
|
14
|
+
request?: StatusRequest,
|
|
15
|
+
options?: ClientCallOptions,
|
|
16
|
+
): Promise<StatusResponse> {
|
|
17
|
+
throw new Error("Client.status is not implemented for VcrClient");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
streamData(request: StreamDataRequest<TFilter>, options?: StreamDataOptions) {
|
|
21
|
+
const messages = this.messageFactory(request, options);
|
|
22
|
+
|
|
23
|
+
return new StreamDataIterable(messages);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class StreamDataIterable<TBlock> {
|
|
28
|
+
constructor(private messages: (StreamDataResponse<TBlock> | Error)[]) {}
|
|
29
|
+
|
|
30
|
+
[Symbol.asyncIterator](): AsyncIterator<StreamDataResponse<TBlock>> {
|
|
31
|
+
let index = 0;
|
|
32
|
+
const messages = this.messages;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
async next() {
|
|
36
|
+
if (index >= messages.length) {
|
|
37
|
+
return { done: true, value: undefined };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const message = messages[index++];
|
|
41
|
+
if (message instanceof Error) {
|
|
42
|
+
throw message;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { done: false, value: message };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Schema } from "@effect/schema";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { type MockBlock, MockBlockFromBytes, MockStream } from "./mock";
|
|
5
|
+
|
|
6
|
+
describe("MockBlock", () => {
|
|
7
|
+
const encode = Schema.encodeSync(MockBlockFromBytes);
|
|
8
|
+
const decode = Schema.decodeSync(MockBlockFromBytes);
|
|
9
|
+
|
|
10
|
+
it("can be encoded and decoded", () => {
|
|
11
|
+
const block = { data: "hello" } satisfies MockBlock;
|
|
12
|
+
|
|
13
|
+
const proto = encode(block);
|
|
14
|
+
const back = decode(proto);
|
|
15
|
+
|
|
16
|
+
expect(back).toEqual(block);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("encodes null as empty data", () => {
|
|
20
|
+
const proto = encode(null);
|
|
21
|
+
expect(proto).toEqual(new Uint8Array());
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("decodes empty data as null", () => {
|
|
25
|
+
const block = decode(new Uint8Array());
|
|
26
|
+
expect(block).toBe(null);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("MockStream", () => {
|
|
31
|
+
it("allow filters to be merged", () => {
|
|
32
|
+
const f = MockStream.mergeFilter({ filter: "hello" }, { filter: "world" });
|
|
33
|
+
expect(f).toEqual({ filter: "helloworld" });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Schema } from "@effect/schema";
|
|
2
|
+
import { StreamConfig } from "../config";
|
|
3
|
+
import * as proto from "../proto";
|
|
4
|
+
import { StreamDataResponse } from "../stream";
|
|
5
|
+
|
|
6
|
+
export const MockFilter = Schema.Struct({
|
|
7
|
+
filter: Schema.optional(Schema.String),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export type MockFilter = typeof MockFilter.Type;
|
|
11
|
+
|
|
12
|
+
export const MockFilterFromBytes = Schema.transform(
|
|
13
|
+
Schema.Uint8ArrayFromSelf,
|
|
14
|
+
MockFilter,
|
|
15
|
+
{
|
|
16
|
+
strict: false,
|
|
17
|
+
decode(value) {
|
|
18
|
+
return proto.testing.MockFilter.decode(value);
|
|
19
|
+
},
|
|
20
|
+
encode(value) {
|
|
21
|
+
return proto.testing.MockFilter.encode(value).finish();
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const MockBlock = Schema.Struct({
|
|
27
|
+
data: Schema.optional(Schema.String),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type MockBlock = typeof MockBlock.Type;
|
|
31
|
+
|
|
32
|
+
export const MockBlockFromBytes = Schema.transform(
|
|
33
|
+
Schema.Uint8ArrayFromSelf,
|
|
34
|
+
Schema.NullOr(MockBlock),
|
|
35
|
+
{
|
|
36
|
+
strict: false,
|
|
37
|
+
decode(value) {
|
|
38
|
+
if (value.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return proto.testing.MockBlock.decode(value);
|
|
42
|
+
},
|
|
43
|
+
encode(value) {
|
|
44
|
+
if (value === null) {
|
|
45
|
+
return new Uint8Array();
|
|
46
|
+
}
|
|
47
|
+
return proto.testing.MockBlock.encode(value).finish();
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/** For testing, simply concatenate the values of `.filter` */
|
|
53
|
+
function mergeMockFilter(a: MockFilter, b: MockFilter): MockFilter {
|
|
54
|
+
let filter = "";
|
|
55
|
+
if (a.filter) {
|
|
56
|
+
filter += a.filter;
|
|
57
|
+
}
|
|
58
|
+
if (b.filter) {
|
|
59
|
+
filter += b.filter;
|
|
60
|
+
}
|
|
61
|
+
return { filter };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const MockStream = new StreamConfig(
|
|
65
|
+
MockFilterFromBytes,
|
|
66
|
+
MockBlockFromBytes,
|
|
67
|
+
mergeMockFilter,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const MockStreamResponse = StreamDataResponse(MockBlockFromBytes);
|
|
71
|
+
export type MockStreamResponse = typeof MockStreamResponse.Type;
|
package/tsconfig.json
ADDED
package/dist/client.d.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { ChannelCredentials, ClientDuplexStream, ClientOptions, StatusObject } from "@grpc/grpc-js";
|
|
2
|
-
import { v1alpha2 } from "./proto";
|
|
3
|
-
export { ChannelCredentials, StatusObject } from "@grpc/grpc-js";
|
|
4
|
-
export type DataStream = ClientDuplexStream<v1alpha2.IStreamDataRequest, v1alpha2.IStreamDataResponse>;
|
|
5
|
-
export 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 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 type OnReconnect = (err: StatusObject, retryCount: number) => Promise<OnReconnectResult> | OnReconnectResult;
|
|
43
|
-
export type StreamClientArgs = {
|
|
44
|
-
/**
|
|
45
|
-
* The stream url.
|
|
46
|
-
*/
|
|
47
|
-
url: string;
|
|
48
|
-
/**
|
|
49
|
-
* Override 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
|
-
* Authorization bearer token, used to authenticate with the server.
|
|
60
|
-
*/
|
|
61
|
-
token?: string;
|
|
62
|
-
/**
|
|
63
|
-
* Callback to control reconnection after receiving an error from the stream.
|
|
64
|
-
*
|
|
65
|
-
* By default uses `defaultOnReconnect`, which only reconnects on internal grpc errors.
|
|
66
|
-
*/
|
|
67
|
-
onReconnect?: OnReconnect;
|
|
68
|
-
/**
|
|
69
|
-
* Maximum time to wait for a message before timing out, in milliseconds.
|
|
70
|
-
*
|
|
71
|
-
* Defaults to 45 seconds.
|
|
72
|
-
*/
|
|
73
|
-
timeout?: number;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* A client to configure and stream data.
|
|
77
|
-
*/
|
|
78
|
-
export declare class StreamClient {
|
|
79
|
-
private readonly inner;
|
|
80
|
-
private stream?;
|
|
81
|
-
private stream_id;
|
|
82
|
-
private onReconnect;
|
|
83
|
-
private configuration?;
|
|
84
|
-
private timeout;
|
|
85
|
-
/**
|
|
86
|
-
* Create a new `StreamClient`.
|
|
87
|
-
*
|
|
88
|
-
* Notice that the stream is not connected until you start iterating over it.
|
|
89
|
-
* The stream should be used as an _async iterator_.
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```ts
|
|
93
|
-
* import { StreamClient } from '@apibara/protocol'
|
|
94
|
-
*
|
|
95
|
-
* const client = new StreamClient({ url })
|
|
96
|
-
*
|
|
97
|
-
* client.configure({ filter, cursor })
|
|
98
|
-
*
|
|
99
|
-
* for await (const message of client) {
|
|
100
|
-
* // use message
|
|
101
|
-
* }
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
|
-
constructor({ url, credentials, clientOptions, token, onReconnect, timeout, }: StreamClientArgs);
|
|
105
|
-
/**
|
|
106
|
-
* Async iterator over messages in the stream.
|
|
107
|
-
*/
|
|
108
|
-
[Symbol.asyncIterator](): AsyncIterator<v1alpha2.IStreamDataResponse>;
|
|
109
|
-
/**
|
|
110
|
-
* Configure the stream to return the requested data.
|
|
111
|
-
*
|
|
112
|
-
* The stream can be reconfigured while streaming data, the client will
|
|
113
|
-
* take care of returning only data for the new configuration even if there
|
|
114
|
-
* are old messages in-flight.
|
|
115
|
-
*/
|
|
116
|
-
configure(args: ConfigureArgs): void;
|
|
117
|
-
private _configure;
|
|
118
|
-
private connect;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* A `onReconnect` callback that never reconnects.
|
|
122
|
-
*/
|
|
123
|
-
export declare function neverReconnect(_err: StatusObject, _retryCount: number): OnReconnectResult;
|
|
124
|
-
/**
|
|
125
|
-
* A `onReconnect` callback that retries to reconnect up to 5 times.
|
|
126
|
-
*
|
|
127
|
-
* If the error is not an internal error, then it will not reconnect.
|
|
128
|
-
* This callback awaits for `1s * retryCount` before returning.
|
|
129
|
-
*/
|
|
130
|
-
export declare function defaultOnReconnect(err: StatusObject, retryCount: number): Promise<OnReconnectResult>;
|
package/dist/client.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defaultOnReconnect = exports.neverReconnect = exports.StreamClient = exports.ChannelCredentials = void 0;
|
|
4
|
-
const grpc_js_1 = require("@grpc/grpc-js");
|
|
5
|
-
const proto_1 = require("./proto");
|
|
6
|
-
const request_1 = require("./request");
|
|
7
|
-
var grpc_js_2 = require("@grpc/grpc-js");
|
|
8
|
-
Object.defineProperty(exports, "ChannelCredentials", { enumerable: true, get: function () { return grpc_js_2.ChannelCredentials; } });
|
|
9
|
-
const StreamService = proto_1.v1alpha2.protoDescriptor.apibara.node.v1alpha2.Stream;
|
|
10
|
-
// Server produces an heartbeat every 30 seconds, so we use 45 seconds as a timeout.
|
|
11
|
-
const MESSAGE_TIMEOUT_MS = 45000;
|
|
12
|
-
// Increase the default message length to 128 MiB.
|
|
13
|
-
const DEFAULT_MESSAGE_LENGTH = 128 * 1048576; // 128 MiB
|
|
14
|
-
/**
|
|
15
|
-
* A client to configure and stream data.
|
|
16
|
-
*/
|
|
17
|
-
class StreamClient {
|
|
18
|
-
inner;
|
|
19
|
-
stream;
|
|
20
|
-
stream_id;
|
|
21
|
-
onReconnect;
|
|
22
|
-
configuration;
|
|
23
|
-
timeout;
|
|
24
|
-
/**
|
|
25
|
-
* Create a new `StreamClient`.
|
|
26
|
-
*
|
|
27
|
-
* Notice that the stream is not connected until you start iterating over it.
|
|
28
|
-
* The stream should be used as an _async iterator_.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```ts
|
|
32
|
-
* import { StreamClient } from '@apibara/protocol'
|
|
33
|
-
*
|
|
34
|
-
* const client = new StreamClient({ url })
|
|
35
|
-
*
|
|
36
|
-
* client.configure({ filter, cursor })
|
|
37
|
-
*
|
|
38
|
-
* for await (const message of client) {
|
|
39
|
-
* // use message
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
constructor({ url, credentials, clientOptions, token, onReconnect, timeout, }) {
|
|
44
|
-
const baseCredentials = credentials ?? grpc_js_1.ChannelCredentials.createSsl();
|
|
45
|
-
// only secure credentials can be composed with metadata generators.
|
|
46
|
-
const credentialsWithMetadata = baseCredentials._isSecure()
|
|
47
|
-
? baseCredentials.compose(grpc_js_1.CallCredentials.createFromMetadataGenerator(createMetadataGenerator(token)))
|
|
48
|
-
: baseCredentials;
|
|
49
|
-
this.inner = new StreamService(url, credentialsWithMetadata, {
|
|
50
|
-
"grpc.keepalive_timeout_ms": 3600000,
|
|
51
|
-
"grpc.max_receive_message_length": DEFAULT_MESSAGE_LENGTH,
|
|
52
|
-
...clientOptions,
|
|
53
|
-
});
|
|
54
|
-
this.stream_id = 0;
|
|
55
|
-
this.onReconnect = onReconnect ?? defaultOnReconnect;
|
|
56
|
-
this.timeout = timeout ?? MESSAGE_TIMEOUT_MS;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Async iterator over messages in the stream.
|
|
60
|
-
*/
|
|
61
|
-
async *[Symbol.asyncIterator]() {
|
|
62
|
-
if (!this.configuration) {
|
|
63
|
-
throw new Error("StreamClient must be configured");
|
|
64
|
-
}
|
|
65
|
-
// connect if not connected.
|
|
66
|
-
if (!this.stream) {
|
|
67
|
-
this.connect();
|
|
68
|
-
this._configure(this.configuration);
|
|
69
|
-
}
|
|
70
|
-
while (true) {
|
|
71
|
-
let retryCount = 1;
|
|
72
|
-
let cursor = null;
|
|
73
|
-
let clock;
|
|
74
|
-
try {
|
|
75
|
-
// this check is to make ts happy
|
|
76
|
-
if (!this.stream) {
|
|
77
|
-
throw new Error("Stream disconnected unexpectedly");
|
|
78
|
-
}
|
|
79
|
-
const streamIter = this.stream[Symbol.asyncIterator]();
|
|
80
|
-
while (true) {
|
|
81
|
-
const timeout = new Promise((_, reject) => {
|
|
82
|
-
clock = setTimeout(() => {
|
|
83
|
-
reject(new Error("Stream timed out"));
|
|
84
|
-
}, this.timeout);
|
|
85
|
-
});
|
|
86
|
-
const message = (await Promise.race([streamIter.next(), timeout]));
|
|
87
|
-
const messageTyped = message.value;
|
|
88
|
-
clearTimeout(clock);
|
|
89
|
-
if (messageTyped.message === "heartbeat") {
|
|
90
|
-
yield messageTyped;
|
|
91
|
-
}
|
|
92
|
-
else if (messageTyped.streamId?.toString() === this.stream_id.toString()) {
|
|
93
|
-
// only return messages if they are with the most recently configured stream
|
|
94
|
-
// reset retry count on new message
|
|
95
|
-
retryCount = 1;
|
|
96
|
-
// keep cursor updated for use when reconnecting
|
|
97
|
-
if (messageTyped.data) {
|
|
98
|
-
cursor = messageTyped.data.cursor;
|
|
99
|
-
}
|
|
100
|
-
else if (messageTyped.invalidate) {
|
|
101
|
-
cursor = messageTyped.invalidate.cursor;
|
|
102
|
-
}
|
|
103
|
-
yield messageTyped;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// rome-ignore lint: any is needed for catch
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
clearTimeout(clock);
|
|
110
|
-
const isGrpcError = Object.hasOwn(err, "code") &&
|
|
111
|
-
Object.hasOwn(err, "details") &&
|
|
112
|
-
Object.hasOwn(err, "metadata");
|
|
113
|
-
// non-grpc error, so just bubble it up
|
|
114
|
-
if (!isGrpcError) {
|
|
115
|
-
throw err;
|
|
116
|
-
}
|
|
117
|
-
const { reconnect, args } = await Promise.resolve(this.onReconnect(err, retryCount));
|
|
118
|
-
retryCount += 1;
|
|
119
|
-
if (!reconnect) {
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
this.connect();
|
|
123
|
-
if (args) {
|
|
124
|
-
this._configure(args);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
// use same configuration specified by user, restarting from the
|
|
128
|
-
// latest ingested batch.
|
|
129
|
-
const configuration = {
|
|
130
|
-
...this.configuration,
|
|
131
|
-
cursor: cursor ?? this.configuration.cursor,
|
|
132
|
-
};
|
|
133
|
-
this._configure(configuration);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Configure the stream to return the requested data.
|
|
140
|
-
*
|
|
141
|
-
* The stream can be reconfigured while streaming data, the client will
|
|
142
|
-
* take care of returning only data for the new configuration even if there
|
|
143
|
-
* are old messages in-flight.
|
|
144
|
-
*/
|
|
145
|
-
configure(args) {
|
|
146
|
-
this.configuration = args;
|
|
147
|
-
this._configure(args);
|
|
148
|
-
}
|
|
149
|
-
_configure(args) {
|
|
150
|
-
const { filter, batchSize, cursor, finality } = args;
|
|
151
|
-
this.stream_id++;
|
|
152
|
-
// only send configuration if connected
|
|
153
|
-
if (this.stream) {
|
|
154
|
-
const builder = request_1.StreamDataRequest.create()
|
|
155
|
-
.withStreamId(this.stream_id)
|
|
156
|
-
.withFilter(filter);
|
|
157
|
-
if (batchSize) {
|
|
158
|
-
builder.withBatchSize(batchSize);
|
|
159
|
-
}
|
|
160
|
-
if (cursor) {
|
|
161
|
-
builder.withStartingCursor(cursor);
|
|
162
|
-
}
|
|
163
|
-
if (finality) {
|
|
164
|
-
builder.withFinality(finality);
|
|
165
|
-
}
|
|
166
|
-
const request = builder.encode();
|
|
167
|
-
this.stream?.write(request);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
connect() {
|
|
171
|
-
this.stream = this.inner.streamData();
|
|
172
|
-
return this;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
exports.StreamClient = StreamClient;
|
|
176
|
-
/**
|
|
177
|
-
* A `onReconnect` callback that never reconnects.
|
|
178
|
-
*/
|
|
179
|
-
function neverReconnect(_err, _retryCount) {
|
|
180
|
-
return {
|
|
181
|
-
reconnect: false,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
exports.neverReconnect = neverReconnect;
|
|
185
|
-
/**
|
|
186
|
-
* A `onReconnect` callback that retries to reconnect up to 5 times.
|
|
187
|
-
*
|
|
188
|
-
* If the error is not an internal error, then it will not reconnect.
|
|
189
|
-
* This callback awaits for `1s * retryCount` before returning.
|
|
190
|
-
*/
|
|
191
|
-
async function defaultOnReconnect(err, retryCount) {
|
|
192
|
-
if (err.code !== 13) {
|
|
193
|
-
return {
|
|
194
|
-
reconnect: false,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
await new Promise((resolve) => setTimeout(resolve, retryCount * 1000));
|
|
198
|
-
return {
|
|
199
|
-
reconnect: retryCount < 5,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
exports.defaultOnReconnect = defaultOnReconnect;
|
|
203
|
-
/*
|
|
204
|
-
* Returns a generator that adds the given `token` to request metadata.
|
|
205
|
-
*/
|
|
206
|
-
function createMetadataGenerator(token) {
|
|
207
|
-
const metadata = new grpc_js_1.Metadata();
|
|
208
|
-
if (token) {
|
|
209
|
-
metadata.add("authorization", `bearer ${token}`);
|
|
210
|
-
}
|
|
211
|
-
return (_options, cb) => {
|
|
212
|
-
cb(null, metadata);
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/cursor.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
* Creates a new cursor from a plain Javascript object.
|
|
20
|
-
*/
|
|
21
|
-
fromObject({ orderKey, uniqueKey }: ReturnType<typeof _toObject>): ICursor;
|
|
22
|
-
/**
|
|
23
|
-
* Returns the cursor string representation.
|
|
24
|
-
*/
|
|
25
|
-
toString: (cursor?: ICursor | null) => string | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* Returns the cursor as plain Javascript object.
|
|
28
|
-
*/
|
|
29
|
-
toObject: (cursor?: ICursor | null) => ReturnType<typeof _toObject> | undefined;
|
|
30
|
-
};
|
|
31
|
-
declare function _toObject(cursor: ICursor): {
|
|
32
|
-
orderKey: string;
|
|
33
|
-
uniqueKey: string;
|
|
34
|
-
};
|
|
35
|
-
export {};
|