@rocicorp/zero 1.11.0-canary.11 → 1.11.0-canary.13
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/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/shared/src/ring-buffer.d.ts +2 -0
- package/out/shared/src/ring-buffer.d.ts.map +1 -1
- package/out/shared/src/ring-buffer.js +4 -0
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +26 -13
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +169 -19
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +45 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +118 -33
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +10 -17
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +15 -4
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts +92 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js +42 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +7 -89
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +26 -98
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +358 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.js +68 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -0
- package/out/zero-cache/src/services/litestream/commands.d.ts +2 -3
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +3 -2
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +12 -1
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +13 -7
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +2 -2
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +19 -2
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +8 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +19 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +34 -11
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +6 -2
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +9 -5
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +44 -11
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +21 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +14 -6
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +28 -4
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +33 -7
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +93 -50
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +11 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +56 -35
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/types/ws.js +1 -31
- package/out/zero-cache/src/types/ws.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +5 -5
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +6 -4
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts +2 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +3 -0
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts +2 -0
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +3 -0
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +5 -1
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js +7 -0
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +3 -0
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js +4 -0
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +2 -0
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +9 -0
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +4 -1
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js +3 -0
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts +4 -3
- package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +73 -22
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join-utils.d.ts +24 -7
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +87 -4
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.d.ts +4 -4
- package/out/zql/src/ivm/join.d.ts.map +1 -1
- package/out/zql/src/ivm/join.js +55 -64
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +33 -23
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +39 -10
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js +9 -4
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.d.ts +6 -1
- package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +47 -2
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/skip.d.ts +2 -1
- package/out/zql/src/ivm/skip.d.ts.map +1 -1
- package/out/zql/src/ivm/skip.js +3 -0
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/take-gate.d.ts +24 -2
- package/out/zql/src/ivm/take-gate.d.ts.map +1 -1
- package/out/zql/src/ivm/take-gate.js +26 -1
- package/out/zql/src/ivm/take-gate.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts +2 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +59 -40
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +3 -0
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +56 -5
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/union-fan-out.d.ts +2 -1
- package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-out.js +5 -0
- package/out/zql/src/ivm/union-fan-out.js.map +1 -1
- package/out/zql/src/query/measure-push-operator.d.ts +2 -1
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +7 -0
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/package.json +1 -1
|
@@ -45,9 +45,23 @@ export type Source<T> = AsyncIterable<T> & {
|
|
|
45
45
|
export type Sink<T> = {
|
|
46
46
|
push(message: T): void;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Utility method for reading the first items from a {@link Source} and passing
|
|
50
|
+
* the `rest` to a separate consumer. The caller should call `head.next()` to
|
|
51
|
+
* pull items out of the Source, but should not call the
|
|
52
|
+
* {@link AsyncIterator.return()} method, as that will cancel the Source.
|
|
53
|
+
*/
|
|
54
|
+
export declare function readHead<T>(source: Source<T>): {
|
|
55
|
+
head: AsyncIterator<T, T, T>;
|
|
56
|
+
rest: Source<T>;
|
|
57
|
+
};
|
|
48
58
|
/**
|
|
49
59
|
* Back-pressure-aware transformation of a WebSocket into
|
|
50
60
|
* upstream and downstream {@link Subscription} objects.
|
|
61
|
+
*
|
|
62
|
+
* This is used for connections with external servers
|
|
63
|
+
* (i.e. custom change sources). For inter-zero-cache communication,
|
|
64
|
+
* see {@link streamInternal}.
|
|
51
65
|
*/
|
|
52
66
|
export declare function stream<In extends JSONValue, Out extends JSONValue>(lc: LogContext, ws: WebSocket, inSchema: v.Type<In>, outOptions?: Options<Out>, inOptions?: Options<In>, streamOptions?: DuplexOptions): {
|
|
53
67
|
outstream: Sink<Out>;
|
|
@@ -74,16 +88,28 @@ export type PreSerialized = {
|
|
|
74
88
|
readonly byteLength: number;
|
|
75
89
|
};
|
|
76
90
|
export declare function isPreSerialized(val: unknown): val is PreSerialized;
|
|
77
|
-
export declare function streamOut<T extends JSONValue>(lc: LogContext, source: Source<T>, sink: WebSocket, options?: StreamOutOptions | undefined): Promise<void>;
|
|
78
91
|
/**
|
|
79
|
-
*
|
|
92
|
+
* Establishes a bidirectional stream over an internal zero-cache to zero-cache
|
|
93
|
+
* WebSocket. Both peers call this: each may send an outbound {@link Source}
|
|
94
|
+
* (`outSource`) and receives an inbound {@link Source} (the return value).
|
|
95
|
+
* Transport-level `{id, msg}` / `{ack}` framing is multiplexed in both
|
|
96
|
+
* directions over the single socket.
|
|
97
|
+
*
|
|
98
|
+
* `inSchema` validates inbound application frames; it may be omitted by a peer
|
|
99
|
+
* that expects no inbound application messages (any that arrive are a protocol
|
|
100
|
+
* error).
|
|
101
|
+
*/
|
|
102
|
+
export declare function streamInternal<In extends JSONValue, Out extends JSONValue = JSONValue>(lc: LogContext, ws: WebSocket, inSchema: v.Type<In> | undefined, outSource?: Source<Out>, options?: StreamOutOptions): Promise<Source<In>>;
|
|
103
|
+
/**
|
|
104
|
+
* {@link streamInternal} whose outbound `Source` carries messages that are
|
|
105
|
+
* already stringified JSON or pre-serialized Buffers.
|
|
80
106
|
*/
|
|
81
|
-
export declare function
|
|
82
|
-
export declare function streamIn<T extends JSONValue>(lc: LogContext, source: WebSocket, schema: v.Type<T>): Promise<Source<T>>;
|
|
107
|
+
export declare function streamInternalStringified<In extends JSONValue>(lc: LogContext, ws: WebSocket, inSchema: v.Type<In> | undefined, outSource?: Source<string | PreSerialized>, options?: StreamOutOptions): Promise<Source<In>>;
|
|
83
108
|
/**
|
|
84
|
-
*
|
|
85
|
-
* The size bounds downstream batching without keeping or
|
|
109
|
+
* {@link streamInternal} that retains only the transport-frame size of each
|
|
110
|
+
* inbound message. The size bounds downstream batching without keeping or
|
|
111
|
+
* copying the JSON.
|
|
86
112
|
*/
|
|
87
|
-
export declare function
|
|
113
|
+
export declare function streamInternalWithSize<In extends JSONValue, Out extends JSONValue = JSONValue>(lc: LogContext, ws: WebSocket, inSchema: v.Type<In>, outSource?: Source<Out>, options?: StreamOutOptions): Promise<Source<Sized<In>>>;
|
|
88
114
|
export {};
|
|
89
115
|
//# sourceMappingURL=streams.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/streams.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAGR,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,IAAI,CAAC;AAEZ,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAE9E,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/streams.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
-
import { parse, valita_exports } from "../../../shared/src/valita.js";
|
|
2
|
+
import { parse, test, valita_exports } from "../../../shared/src/valita.js";
|
|
3
3
|
import { BigIntJSON } from "../../../shared/src/bigint-json.js";
|
|
4
|
-
import { closeWithError,
|
|
4
|
+
import { closeWithError, sendPingsForLiveness } from "./ws.js";
|
|
5
5
|
import { Queue } from "../../../shared/src/queue.js";
|
|
6
6
|
import { Subscription } from "./subscription.js";
|
|
7
7
|
import { resolver } from "@rocicorp/resolver";
|
|
@@ -10,8 +10,29 @@ import { Readable, Transform, Writable, pipeline } from "node:stream";
|
|
|
10
10
|
//#region ../zero-cache/src/types/streams.ts
|
|
11
11
|
var PING_INTERVAL_MS = 3e4;
|
|
12
12
|
/**
|
|
13
|
+
* Utility method for reading the first items from a {@link Source} and passing
|
|
14
|
+
* the `rest` to a separate consumer. The caller should call `head.next()` to
|
|
15
|
+
* pull items out of the Source, but should not call the
|
|
16
|
+
* {@link AsyncIterator.return()} method, as that will cancel the Source.
|
|
17
|
+
*/
|
|
18
|
+
function readHead(source) {
|
|
19
|
+
const iter = source[Symbol.asyncIterator]();
|
|
20
|
+
return {
|
|
21
|
+
head: iter,
|
|
22
|
+
rest: {
|
|
23
|
+
[Symbol.asyncIterator]: () => iter,
|
|
24
|
+
cancel: (err) => source.cancel(err),
|
|
25
|
+
signal: source.signal
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
13
30
|
* Back-pressure-aware transformation of a WebSocket into
|
|
14
31
|
* upstream and downstream {@link Subscription} objects.
|
|
32
|
+
*
|
|
33
|
+
* This is used for connections with external servers
|
|
34
|
+
* (i.e. custom change sources). For inter-zero-cache communication,
|
|
35
|
+
* see {@link streamInternal}.
|
|
15
36
|
*/
|
|
16
37
|
function stream(lc, ws, inSchema, outOptions = {}, inOptions = {}, streamOptions = {}) {
|
|
17
38
|
const endpoint = ws.url ?? "client";
|
|
@@ -109,28 +130,46 @@ function sendTextFrame(sink, data) {
|
|
|
109
130
|
if (typeof data === "string") sink.send(data);
|
|
110
131
|
else sink.send(data, { binary: false });
|
|
111
132
|
}
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Establishes a bidirectional stream over an internal zero-cache to zero-cache
|
|
135
|
+
* WebSocket. Both peers call this: each may send an outbound {@link Source}
|
|
136
|
+
* (`outSource`) and receives an inbound {@link Source} (the return value).
|
|
137
|
+
* Transport-level `{id, msg}` / `{ack}` framing is multiplexed in both
|
|
138
|
+
* directions over the single socket.
|
|
139
|
+
*
|
|
140
|
+
* `inSchema` validates inbound application frames; it may be omitted by a peer
|
|
141
|
+
* that expects no inbound application messages (any that arrive are a protocol
|
|
142
|
+
* error).
|
|
143
|
+
*/
|
|
144
|
+
function streamInternal(lc, ws, inSchema, outSource, options) {
|
|
145
|
+
return streamInternalCore(lc, ws, inSchema, outSource, BigIntJSON.stringify, (data) => data, options);
|
|
114
146
|
}
|
|
115
147
|
/**
|
|
116
|
-
*
|
|
148
|
+
* {@link streamInternal} whose outbound `Source` carries messages that are
|
|
149
|
+
* already stringified JSON or pre-serialized Buffers.
|
|
117
150
|
*/
|
|
118
|
-
function
|
|
119
|
-
return
|
|
151
|
+
function streamInternalStringified(lc, ws, inSchema, outSource, options) {
|
|
152
|
+
return streamInternalCore(lc, ws, inSchema, outSource, (msg) => typeof msg === "string" ? msg : msg.payload.toString("utf8"), (data) => data, options);
|
|
120
153
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
/**
|
|
155
|
+
* {@link streamInternal} that retains only the transport-frame size of each
|
|
156
|
+
* inbound message. The size bounds downstream batching without keeping or
|
|
157
|
+
* copying the JSON.
|
|
158
|
+
*/
|
|
159
|
+
function streamInternalWithSize(lc, ws, inSchema, outSource, options) {
|
|
160
|
+
return streamInternalCore(lc, ws, inSchema, outSource, BigIntJSON.stringify, (data, _frame, _id, size) => ({
|
|
161
|
+
data,
|
|
162
|
+
size
|
|
163
|
+
}), options);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Runs the outbound send loop of a {@link streamInternal} connection: reads
|
|
167
|
+
* messages from `source`, frames them as `{id, msg}` / `{id, batch}` (or
|
|
168
|
+
* pre-serialized Buffers), and awaits the corresponding `{ack}` frames — which
|
|
169
|
+
* are demuxed off the socket by the shared message handler in
|
|
170
|
+
* {@link streamInternalCore} and delivered via the `acks` queue.
|
|
171
|
+
*/
|
|
172
|
+
async function runOutbound(lc, source, sink, stringify, acks, closer, options) {
|
|
134
173
|
try {
|
|
135
174
|
let nextID = 0;
|
|
136
175
|
const { pipeline } = source;
|
|
@@ -220,41 +259,39 @@ async function streamOutInternal(lc, source, sink, stringify, options) {
|
|
|
220
259
|
closer.close(e);
|
|
221
260
|
}
|
|
222
261
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
* Streams in parsed messages while retaining only the transport-frame size.
|
|
228
|
-
* The size bounds downstream batching without keeping or copying the JSON.
|
|
229
|
-
*/
|
|
230
|
-
function streamInWithSize(lc, source, schema) {
|
|
231
|
-
return streamInInternal(lc, source, schema, (data, _frame, _id, size) => ({
|
|
232
|
-
data,
|
|
233
|
-
size
|
|
234
|
-
}));
|
|
235
|
-
}
|
|
236
|
-
async function streamInInternal(lc, source, schema, transform) {
|
|
237
|
-
expectPingsForLiveness(lc, source, PING_INTERVAL_MS);
|
|
238
|
-
const streamedSchema = valita_exports.object({
|
|
262
|
+
async function streamInternalCore(lc, ws, inSchema, outSource, stringify, transform, options) {
|
|
263
|
+
sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);
|
|
264
|
+
const acks = new Queue();
|
|
265
|
+
const streamedSchema = inSchema ? valita_exports.object({
|
|
239
266
|
id: valita_exports.number(),
|
|
240
|
-
msg:
|
|
241
|
-
batch: valita_exports.array(
|
|
242
|
-
});
|
|
267
|
+
msg: inSchema.optional(),
|
|
268
|
+
batch: valita_exports.array(inSchema).optional()
|
|
269
|
+
}) : void 0;
|
|
243
270
|
const sink = new Subscription({
|
|
244
271
|
consumed: ({ consumed }) => consumed(),
|
|
245
272
|
cleanup: () => closer.close()
|
|
246
273
|
}, ({ data }) => data);
|
|
247
|
-
const closer = WebSocketCloser.
|
|
274
|
+
const closer = WebSocketCloser.forDuplex(lc, ws, outSource, sink, handleMessage);
|
|
248
275
|
function handleMessage(event) {
|
|
249
276
|
const data = event.data.toString();
|
|
250
277
|
if (!sink.active) {
|
|
251
|
-
lc.warn?.("dropping ws message received after close"
|
|
278
|
+
lc.warn?.("dropping ws message received after close");
|
|
252
279
|
return;
|
|
253
280
|
}
|
|
254
281
|
try {
|
|
255
|
-
const
|
|
282
|
+
const value = BigIntJSON.parse(data);
|
|
283
|
+
const parsedAck = test(value, ackSchema);
|
|
284
|
+
if (parsedAck.ok) {
|
|
285
|
+
acks.enqueue(parsedAck.value);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!streamedSchema) {
|
|
289
|
+
closer.close(/* @__PURE__ */ new Error(`unexpected inbound message: ${data}`));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const { id, msg, batch } = parse(value, streamedSchema, "passthrough");
|
|
256
293
|
const sendAck = () => {
|
|
257
|
-
if (
|
|
294
|
+
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify({ ack: id }));
|
|
258
295
|
};
|
|
259
296
|
if (batch !== void 0 && msg !== void 0) throw new Error(`Message ${id} has both "msg" and "batch"`);
|
|
260
297
|
if (batch !== void 0) {
|
|
@@ -281,6 +318,7 @@ async function streamInInternal(lc, source, schema, transform) {
|
|
|
281
318
|
}
|
|
282
319
|
}
|
|
283
320
|
await closer.connected;
|
|
321
|
+
if (outSource) runOutbound(lc, outSource, ws, stringify, acks, closer, options);
|
|
284
322
|
return sink;
|
|
285
323
|
}
|
|
286
324
|
var WebSocketCloser = class WebSocketCloser {
|
|
@@ -292,13 +330,18 @@ var WebSocketCloser = class WebSocketCloser {
|
|
|
292
330
|
get connected() {
|
|
293
331
|
return this.#connected.promise;
|
|
294
332
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Closer for a {@link streamInternalCore} duplex. On socket close it cancels
|
|
335
|
+
* the outbound `Source` (notifying it of unconsumed messages, as the old
|
|
336
|
+
* `forSource` did) and fails/ends the inbound `Sink` (as the old `forSink`
|
|
337
|
+
* did): fail() on error so consumers throw, end() otherwise so pending
|
|
338
|
+
* messages finish. Either stream may be absent for a one-directional peer.
|
|
339
|
+
*/
|
|
340
|
+
static forDuplex(lc, ws, outSource, inSink, messageHandler) {
|
|
299
341
|
return new WebSocketCloser(lc, ws, (err) => {
|
|
300
|
-
|
|
301
|
-
|
|
342
|
+
outSource?.cancel(err instanceof Error ? err : void 0);
|
|
343
|
+
if (err) inSink.fail(err instanceof Error ? err : new Error(String(err)));
|
|
344
|
+
else inSink.end();
|
|
302
345
|
}, messageHandler);
|
|
303
346
|
}
|
|
304
347
|
constructor(lc, ws, closeStream, messageHandler) {
|
|
@@ -351,6 +394,6 @@ var WebSocketCloser = class WebSocketCloser {
|
|
|
351
394
|
}
|
|
352
395
|
};
|
|
353
396
|
//#endregion
|
|
354
|
-
export { pipe,
|
|
397
|
+
export { pipe, readHead, stream, streamInternal, streamInternalStringified, streamInternalWithSize };
|
|
355
398
|
|
|
356
399
|
//# sourceMappingURL=streams.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.js","names":["#lc","#ws","#closeStream","#messageHandler","#connected","#handleOpen","#handleClose","#handleError","#conn"],"sources":["../../../../../zero-cache/src/types/streams.ts"],"sourcesContent":["import {\n pipeline,\n Readable,\n Transform,\n Writable,\n type DuplexOptions,\n} from 'node:stream';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {\n createWebSocketStream,\n type CloseEvent,\n type ErrorEvent,\n type MessageEvent,\n type WebSocket,\n} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Subscription, type Options} from './subscription.ts';\nimport {\n closeWithError,\n expectPingsForLiveness,\n sendPingsForLiveness,\n} from './ws.ts';\n\n// Consistent with Postgres keepalives, and shorter than the\n// commonly used default idle timeout of 1 minute.\nconst PING_INTERVAL_MS = 30_000;\n\nexport type Source<T> = AsyncIterable<T> & {\n /**\n * Immediately terminates all current iterations (i.e. {@link AsyncIterator.next next()})\n * will return `{value: undefined, done: true}`), and prevents any subsequent iterations\n * from yielding any values.\n *\n * @param err Terminate the iteration by throwing the `err` instead.\n */\n cancel: (err?: Error) => void;\n\n /**\n * An AbortSignal that can be used to listen for termination or\n * race a short-lived promise against the termination of the iterable\n * (via `promiseOrAbort()`)\n */\n readonly signal: AbortSignal;\n\n /**\n * The presence of a `pipeline` iterable allows the usual \"consumed-on-iterate\" semantics\n * to be overridden.\n *\n * This is suitable for transport layers that serialize messages across processes, such\n * as the {@link streamOut()} method; pipelining allows the transport to send messages\n * as they arrive without waiting for the previous message to be acked, streaming\n * them to the receiving process where they are presumably queued and processed without\n * a per-message ack delay. The receiving end of the transport then responds with acks\n * asynchronously as the receiving end processes the messages.\n */\n pipeline?: AsyncIterable<{value: T; consumed: () => void}> | undefined;\n\n /**\n * Pipelined batching support: eagerly drains available queued messages up to `maxBatch`.\n */\n pipelineBatched?:\n | ((\n maxBatch?: number,\n ) => AsyncIterable<{values: T[]; consumed: () => void}> | undefined)\n | undefined;\n};\n\nexport type Sink<T> = {\n push(message: T): void;\n};\n\n/**\n * Back-pressure-aware transformation of a WebSocket into\n * upstream and downstream {@link Subscription} objects.\n */\n// TODO: Change {@link streamIn} and {@link streamOut} to use this\n// under the covers so that internal communication is also\n// responsive to backpressure.\nexport function stream<In extends JSONValue, Out extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outOptions: Options<Out> = {},\n inOptions: Options<In> = {},\n streamOptions: DuplexOptions = {},\n): {outstream: Sink<Out>; instream: Source<In>} {\n const endpoint = ws.url ?? 'client';\n function close(err?: unknown) {\n if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {\n if (err) {\n closeWithError(lc, ws, err);\n } else {\n lc.info?.(`closing connection to ${endpoint}`);\n ws.close();\n }\n }\n }\n\n const instream = Subscription.create<In>({\n ...inOptions,\n cleanup: (unconsumed, err) => {\n inOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n const outstream = Subscription.create<Out>({\n ...outOptions,\n cleanup: (unconsumed, err) => {\n outOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n\n const duplex = createWebSocketStream(ws, {\n ...streamOptions,\n decodeStrings: false,\n });\n\n // Outgoing transform.\n function streamOut() {\n // Mainly used for verifying that back-pressure kicks in tests.\n duplex.on('drain', () => lc.debug?.(`drained messages to ${endpoint}`));\n\n pipeline(\n Readable.from(outstream),\n new Transform({\n objectMode: true,\n transform: (msg, _encoding, callback) =>\n callback(null, BigIntJSON.stringify(msg)),\n }),\n duplex,\n err => (err ? outstream.fail(err) : outstream.cancel()),\n );\n }\n\n if (ws.readyState === ws.CONNECTING) {\n ws.on('open', () => {\n lc.info?.(`connected to ${endpoint}`);\n streamOut();\n });\n } else {\n streamOut();\n }\n\n // Incoming transform.\n pipe({\n source: duplex,\n sink: instream,\n parse: chunk => {\n const json = BigIntJSON.parse(chunk.toString());\n return v.parse(json, inSchema, 'passthrough');\n },\n });\n\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n return {outstream, instream};\n}\n\ntype PipeOptions<T> = {\n source: Readable;\n sink: Subscription<T>;\n parse: (buffer: Buffer) => T | null;\n bufferMessages?: number;\n};\n\nexport function pipe<T>({source, sink, parse, bufferMessages}: PipeOptions<T>) {\n bufferMessages ??= 0;\n assert(bufferMessages >= 0, 'bufferMessages must be non-negative');\n const pending: Promise<unknown>[] = [];\n\n pipeline(\n source,\n new Writable({\n decodeStrings: false,\n write: (chunk, _encoding, callback) => {\n let msg: T | null;\n try {\n if ((msg = parse(chunk)) === null) {\n callback();\n return;\n }\n } catch (err) {\n callback(ensureError(err));\n return;\n }\n // Inbound backpressure is exerted by unconsumed messages in the\n // subscription. A buffer can be used to allow messages to queue up in\n // in the Subscription object, which allows the consumer to \"peek\" at\n // whether there are more messages immediately available\n // (via {@link Subscription.queued}.\n const {result} = sink.push(msg);\n pending.push(result);\n void result.then(() => pending.shift());\n\n if (pending.length <= bufferMessages) {\n // immediately allow more messages\n callback();\n } else {\n // wait for the oldest result in the pending queue\n pending[0].then(\n () => callback(),\n err => callback(ensureError(err)),\n );\n }\n },\n destroy: (err, callback) => {\n if (err) {\n sink.fail(ensureError(err));\n }\n // Otherwise, final will handle the cancel.\n callback();\n },\n final: callback => {\n sink.cancel();\n callback();\n },\n }),\n err => (err ? sink.fail(err) : sink.cancel()),\n );\n}\n\nfunction ensureError(err: unknown) {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nconst ackSchema = v.object({ack: v.number()});\n\ntype Ack = v.Infer<typeof ackSchema>;\n\n/** A parsed value paired with its approximate serialized transport size. */\nexport type Sized<T> = {\n data: T;\n size: number;\n};\n\nexport type StreamOutOptions = {\n batched?: boolean | undefined;\n maxBatchSize?: number | undefined;\n};\n\nexport type PreSerialized = {\n readonly payload: Buffer;\n readonly byteLength: number;\n};\n\nexport function isPreSerialized(val: unknown): val is PreSerialized {\n return (\n typeof val === 'object' &&\n val !== null &&\n 'payload' in val &&\n Buffer.isBuffer((val as PreSerialized).payload)\n );\n}\n\nfunction sendTextFrame(sink: WebSocket, data: Buffer | string) {\n if (typeof data === 'string') {\n sink.send(data);\n } else {\n (sink as unknown as {send: (data: unknown, opts?: unknown) => void}).send(\n data,\n {binary: false},\n );\n }\n}\n\nexport function streamOut<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n options?: StreamOutOptions | undefined,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, BigIntJSON.stringify, options);\n}\n\n/**\n * Streams out a `Source` for which messages are already stringified JSON or pre-serialized Buffers.\n */\nexport function streamOutStringified(\n lc: LogContext,\n source: Source<string | PreSerialized>,\n sink: WebSocket,\n options?: StreamOutOptions | undefined,\n): Promise<void> {\n return streamOutInternal(\n lc,\n source,\n sink,\n msg => (typeof msg === 'string' ? msg : msg.payload.toString('utf8')),\n options,\n );\n}\n\nasync function streamOutInternal<T extends JSONValue | PreSerialized>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n stringify: (payload: T) => string,\n options?: StreamOutOptions | undefined,\n): Promise<void> {\n sendPingsForLiveness(lc, sink, PING_INTERVAL_MS);\n\n const closer = WebSocketCloser.forSource(lc, sink, source);\n\n const acks = new Queue<Ack>();\n sink.addEventListener('message', ({data}) => {\n try {\n const text = typeof data === 'string' ? data : data.toString();\n acks.enqueue(v.parse(JSON.parse(text), ackSchema));\n } catch (e) {\n lc.error?.(`error parsing ack`, e);\n closer.close(e);\n }\n });\n\n try {\n let nextID = 0;\n const {pipeline} = source;\n const batched = options?.batched ?? false;\n const maxBatchSize = Math.max(1, Math.floor(options?.maxBatchSize ?? 64));\n\n if (batched && source.pipelineBatched) {\n const batchedIterable = source.pipelineBatched(maxBatchSize);\n if (batchedIterable) {\n lc.debug?.(\n `started batched outbound stream (maxBatchSize=${maxBatchSize})`,\n );\n for await (const {values, consumed} of batchedIterable) {\n if (values.length === 1 && isPreSerialized(values[0])) {\n const id = ++nextID;\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, values[0].payload]);\n sendTextFrame(sink, data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })().catch(e => closer.close(e));\n } else if (values.some(isPreSerialized)) {\n let remaining = values.length;\n const onConsumed = () => {\n if (--remaining === 0) {\n consumed();\n }\n };\n for (const val of values) {\n const id = ++nextID;\n if (isPreSerialized(val)) {\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, val.payload]);\n sendTextFrame(sink, data);\n } else {\n const data = `{\"id\":${id},\"msg\":${stringify(val)}}`;\n sink.send(data);\n }\n void (async () => {\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n onConsumed();\n })().catch(e => closer.close(e));\n }\n } else {\n const id = ++nextID;\n const data =\n values.length === 1\n ? `{\"id\":${id},\"msg\":${stringify(values[0])}}`\n : `{\"id\":${id},\"batch\":[${values.map(stringify).join(',')}]}`;\n sink.send(data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })().catch(e => closer.close(e));\n }\n }\n closer.close();\n return;\n }\n }\n\n if (pipeline) {\n lc.debug?.(`started pipelined outbound stream`);\n for await (const {value: msg, consumed} of pipeline) {\n const id = ++nextID;\n if (isPreSerialized(msg)) {\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, msg.payload]);\n sendTextFrame(sink, data);\n } else {\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`pipelining`, data);\n sink.send(data);\n }\n\n // The ack is awaited off the send loop so that the next message can be\n // sent without waiting for it. A bad ack is a protocol error like in\n // the synchronous path below: close the socket (which cancels the\n // source) rather than leaving the rejection unhandled.\n void (async () => {\n const {ack} = await acks.dequeue();\n // lc.debug?.(`received ack`, ack);\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })().catch(e => closer.close(e));\n }\n } else {\n lc.debug?.(`started synchronous outbound stream`);\n for await (const msg of source) {\n const id = ++nextID;\n if (isPreSerialized(msg)) {\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, msg.payload]);\n sendTextFrame(sink, data);\n } else {\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`sending`, data);\n sink.send(data);\n }\n\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n }\n }\n closer.close();\n } catch (e) {\n closer.close(e);\n }\n}\n\nexport function streamIn<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<T>> {\n return streamInInternal(lc, source, schema, data => data);\n}\n\n/**\n * Streams in parsed messages while retaining only the transport-frame size.\n * The size bounds downstream batching without keeping or copying the JSON.\n */\nexport function streamInWithSize<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<Sized<T>>> {\n return streamInInternal(lc, source, schema, (data, _frame, _id, size) => ({\n data,\n size,\n }));\n}\n\nasync function streamInInternal<T extends JSONValue, Out>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n transform: (data: T, frame: string, id: number, size: number) => Out,\n): Promise<Source<Out>> {\n expectPingsForLiveness(lc, source, PING_INTERVAL_MS);\n\n const streamedSchema = v.object({\n id: v.number(),\n msg: schema.optional(),\n batch: v.array(schema).optional(),\n });\n\n type SinkEntry = {\n consumed: () => void;\n data: Out;\n };\n\n const sink: Subscription<Out, SinkEntry> = new Subscription<Out, SinkEntry>(\n {\n consumed: ({consumed}) => consumed(),\n cleanup: () => closer.close(),\n },\n ({data}) => data,\n );\n\n const closer = WebSocketCloser.forSink(lc, source, sink, handleMessage);\n\n function handleMessage(event: MessageEvent) {\n const data = event.data.toString();\n if (!sink.active) {\n lc.warn?.('dropping ws message received after close', data);\n return;\n }\n try {\n const value = BigIntJSON.parse(data);\n const parsed = v.parse(value, streamedSchema, 'passthrough');\n const {id, msg, batch} = parsed;\n\n const sendAck = () => {\n if (source.readyState === source.OPEN) {\n source.send(JSON.stringify({ack: id} satisfies Ack));\n }\n };\n\n if (batch !== undefined && msg !== undefined) {\n throw new Error(`Message ${id} has both \"msg\" and \"batch\"`);\n }\n\n if (batch !== undefined) {\n let remaining = batch.length;\n if (remaining === 0) {\n sendAck();\n return;\n }\n const onConsumed = () => {\n if (--remaining === 0) {\n sendAck();\n }\n };\n const itemSize = Math.max(1, Math.round(data.length / batch.length));\n for (const item of batch) {\n sink.push({\n consumed: onConsumed,\n data: transform(item, data, id, itemSize),\n });\n }\n } else if (msg !== undefined) {\n sink.push({\n consumed: sendAck,\n data: transform(msg, data, id, data.length),\n });\n } else {\n throw new Error(`Message ${id} has neither \"msg\" nor \"batch\"`);\n }\n } catch (e) {\n closer.close(e);\n }\n }\n\n await closer.connected;\n return sink;\n}\n\nclass WebSocketCloser {\n readonly #lc: LogContext;\n readonly #ws: WebSocket;\n readonly #closeStream: (err?: unknown) => void;\n readonly #messageHandler: ((e: MessageEvent) => void | undefined) | null;\n readonly #connected = resolver();\n\n get connected(): Promise<void> {\n return this.#connected.promise;\n }\n\n static forSource<T>(lc: LogContext, ws: WebSocket, stream: Source<T>) {\n // If the websocket is closed, call cancel() to notify the Source of\n // any unconsumed messages.\n return new WebSocketCloser(lc, ws, (err?: unknown) =>\n stream.cancel(err instanceof Error ? err : undefined),\n );\n }\n\n static forSink<T, Input>(\n lc: LogContext,\n ws: WebSocket,\n stream: Subscription<T, Input>,\n messageHandler: (e: MessageEvent) => void | undefined,\n ) {\n // If the websocket is closed with an error, fail() the downstream Sink\n // so consumers catch the error. Otherwise, call end() to allow pending\n // messages to finish.\n return new WebSocketCloser(\n lc,\n ws,\n (err?: unknown) => {\n if (err) {\n stream.fail(err instanceof Error ? err : new Error(String(err)));\n } else {\n stream.end();\n }\n },\n messageHandler,\n );\n }\n\n private constructor(\n lc: LogContext,\n ws: WebSocket,\n closeStream: (err?: unknown) => void,\n messageHandler?: (e: MessageEvent) => void | undefined,\n ) {\n this.#lc = lc;\n this.#ws = ws;\n this.#closeStream = closeStream;\n this.#messageHandler = messageHandler ?? null;\n\n ws.addEventListener('open', this.#handleOpen);\n ws.addEventListener('close', this.#handleClose);\n ws.addEventListener('error', this.#handleError);\n if (this.#messageHandler) {\n ws.addEventListener('message', this.#messageHandler);\n }\n\n switch (ws.readyState) {\n case ws.CONNECTING:\n break; // expected for new connections. resolve or reject in handlers.\n case ws.OPEN:\n this.#connected.resolve();\n break;\n default:\n this.#connected.reject(\n new Error(`websocket already in state ${ws.readyState}`),\n );\n break;\n }\n }\n\n get #conn(): string {\n return 'connection' + (this.#ws.url ? ` to ${this.#ws.url}` : '');\n }\n\n #handleOpen = () => {\n this.#lc.info?.(`${this.#conn} established`);\n this.#connected.resolve();\n };\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.#lc.info?.(`${this.#conn} closed`, {\n code,\n reason,\n wasClean,\n });\n this.close();\n this.#connected.reject(`${this.#conn} closed with code ${code}`);\n };\n\n #handleError = ({message, error}: ErrorEvent) => {\n if (this.#ws.readyState === this.#ws.OPEN) {\n this.#lc.error?.(`error in ${this.#conn}`, message, error);\n }\n this.#connected.reject(error);\n };\n\n close(err?: unknown) {\n if (err) {\n this.#lc.error?.(`closing stream with error`, err);\n }\n this.#closeStream(err);\n if (!this.closed()) {\n this.#ws.close();\n }\n }\n\n closed() {\n return (\n this.#ws.readyState === this.#ws.CLOSED ||\n this.#ws.readyState === this.#ws.CLOSING\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"streams.js","names":["#lc","#ws","#closeStream","#messageHandler","#connected","#handleOpen","#handleClose","#handleError","#conn"],"sources":["../../../../../zero-cache/src/types/streams.ts"],"sourcesContent":["import {\n pipeline,\n Readable,\n Transform,\n Writable,\n type DuplexOptions,\n} from 'node:stream';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {\n createWebSocketStream,\n type CloseEvent,\n type ErrorEvent,\n type MessageEvent,\n type WebSocket,\n} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Subscription, type Options} from './subscription.ts';\nimport {closeWithError, sendPingsForLiveness} from './ws.ts';\n\n// Consistent with Postgres keepalives, and shorter than the\n// commonly used default idle timeout of 1 minute.\nconst PING_INTERVAL_MS = 30_000;\n\nexport type Source<T> = AsyncIterable<T> & {\n /**\n * Immediately terminates all current iterations (i.e. {@link AsyncIterator.next next()})\n * will return `{value: undefined, done: true}`), and prevents any subsequent iterations\n * from yielding any values.\n *\n * @param err Terminate the iteration by throwing the `err` instead.\n */\n cancel: (err?: Error) => void;\n\n /**\n * An AbortSignal that can be used to listen for termination or\n * race a short-lived promise against the termination of the iterable\n * (via `promiseOrAbort()`)\n */\n readonly signal: AbortSignal;\n\n /**\n * The presence of a `pipeline` iterable allows the usual \"consumed-on-iterate\" semantics\n * to be overridden.\n *\n * This is suitable for transport layers that serialize messages across processes, such\n * as the {@link streamOut()} method; pipelining allows the transport to send messages\n * as they arrive without waiting for the previous message to be acked, streaming\n * them to the receiving process where they are presumably queued and processed without\n * a per-message ack delay. The receiving end of the transport then responds with acks\n * asynchronously as the receiving end processes the messages.\n */\n pipeline?: AsyncIterable<{value: T; consumed: () => void}> | undefined;\n\n /**\n * Pipelined batching support: eagerly drains available queued messages up to `maxBatch`.\n */\n pipelineBatched?:\n | ((\n maxBatch?: number,\n ) => AsyncIterable<{values: T[]; consumed: () => void}> | undefined)\n | undefined;\n};\n\nexport type Sink<T> = {\n push(message: T): void;\n};\n\n/**\n * Utility method for reading the first items from a {@link Source} and passing\n * the `rest` to a separate consumer. The caller should call `head.next()` to\n * pull items out of the Source, but should not call the\n * {@link AsyncIterator.return()} method, as that will cancel the Source.\n */\nexport function readHead<T>(source: Source<T>): {\n head: AsyncIterator<T, T, T>;\n rest: Source<T>;\n} {\n const iter = source[Symbol.asyncIterator]();\n const rest: Source<T> = {\n [Symbol.asyncIterator]: () => iter,\n cancel: err => source.cancel(err),\n signal: source.signal,\n };\n return {head: iter, rest};\n}\n\n/**\n * Back-pressure-aware transformation of a WebSocket into\n * upstream and downstream {@link Subscription} objects.\n *\n * This is used for connections with external servers\n * (i.e. custom change sources). For inter-zero-cache communication,\n * see {@link streamInternal}.\n */\nexport function stream<In extends JSONValue, Out extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outOptions: Options<Out> = {},\n inOptions: Options<In> = {},\n streamOptions: DuplexOptions = {},\n): {outstream: Sink<Out>; instream: Source<In>} {\n const endpoint = ws.url ?? 'client';\n function close(err?: unknown) {\n if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {\n if (err) {\n closeWithError(lc, ws, err);\n } else {\n lc.info?.(`closing connection to ${endpoint}`);\n ws.close();\n }\n }\n }\n\n const instream = Subscription.create<In>({\n ...inOptions,\n cleanup: (unconsumed, err) => {\n inOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n const outstream = Subscription.create<Out>({\n ...outOptions,\n cleanup: (unconsumed, err) => {\n outOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n\n const duplex = createWebSocketStream(ws, {\n ...streamOptions,\n decodeStrings: false,\n });\n\n // Outgoing transform.\n function streamOut() {\n // Mainly used for verifying that back-pressure kicks in tests.\n duplex.on('drain', () => lc.debug?.(`drained messages to ${endpoint}`));\n\n pipeline(\n Readable.from(outstream),\n new Transform({\n objectMode: true,\n transform: (msg, _encoding, callback) =>\n callback(null, BigIntJSON.stringify(msg)),\n }),\n duplex,\n err => (err ? outstream.fail(err) : outstream.cancel()),\n );\n }\n\n if (ws.readyState === ws.CONNECTING) {\n ws.on('open', () => {\n lc.info?.(`connected to ${endpoint}`);\n streamOut();\n });\n } else {\n streamOut();\n }\n\n // Incoming transform.\n pipe({\n source: duplex,\n sink: instream,\n parse: chunk => {\n const json = BigIntJSON.parse(chunk.toString());\n return v.parse(json, inSchema, 'passthrough');\n },\n });\n\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n return {outstream, instream};\n}\n\ntype PipeOptions<T> = {\n source: Readable;\n sink: Subscription<T>;\n parse: (buffer: Buffer) => T | null;\n bufferMessages?: number;\n};\n\nexport function pipe<T>({source, sink, parse, bufferMessages}: PipeOptions<T>) {\n bufferMessages ??= 0;\n assert(bufferMessages >= 0, 'bufferMessages must be non-negative');\n const pending: Promise<unknown>[] = [];\n\n pipeline(\n source,\n new Writable({\n decodeStrings: false,\n write: (chunk, _encoding, callback) => {\n let msg: T | null;\n try {\n if ((msg = parse(chunk)) === null) {\n callback();\n return;\n }\n } catch (err) {\n callback(ensureError(err));\n return;\n }\n // Inbound backpressure is exerted by unconsumed messages in the\n // subscription. A buffer can be used to allow messages to queue up in\n // in the Subscription object, which allows the consumer to \"peek\" at\n // whether there are more messages immediately available\n // (via {@link Subscription.queued}.\n const {result} = sink.push(msg);\n pending.push(result);\n void result.then(() => pending.shift());\n\n if (pending.length <= bufferMessages) {\n // immediately allow more messages\n callback();\n } else {\n // wait for the oldest result in the pending queue\n pending[0].then(\n () => callback(),\n err => callback(ensureError(err)),\n );\n }\n },\n destroy: (err, callback) => {\n if (err) {\n sink.fail(ensureError(err));\n }\n // Otherwise, final will handle the cancel.\n callback();\n },\n final: callback => {\n sink.cancel();\n callback();\n },\n }),\n err => (err ? sink.fail(err) : sink.cancel()),\n );\n}\n\nfunction ensureError(err: unknown) {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nconst ackSchema = v.object({ack: v.number()});\n\ntype Ack = v.Infer<typeof ackSchema>;\n\n/** A parsed value paired with its approximate serialized transport size. */\nexport type Sized<T> = {\n data: T;\n size: number;\n};\n\nexport type StreamOutOptions = {\n batched?: boolean | undefined;\n maxBatchSize?: number | undefined;\n};\n\nexport type PreSerialized = {\n readonly payload: Buffer;\n readonly byteLength: number;\n};\n\nexport function isPreSerialized(val: unknown): val is PreSerialized {\n return (\n typeof val === 'object' &&\n val !== null &&\n 'payload' in val &&\n Buffer.isBuffer((val as PreSerialized).payload)\n );\n}\n\nfunction sendTextFrame(sink: WebSocket, data: Buffer | string) {\n if (typeof data === 'string') {\n sink.send(data);\n } else {\n (sink as unknown as {send: (data: unknown, opts?: unknown) => void}).send(\n data,\n {binary: false},\n );\n }\n}\n\n/**\n * Establishes a bidirectional stream over an internal zero-cache to zero-cache\n * WebSocket. Both peers call this: each may send an outbound {@link Source}\n * (`outSource`) and receives an inbound {@link Source} (the return value).\n * Transport-level `{id, msg}` / `{ack}` framing is multiplexed in both\n * directions over the single socket.\n *\n * `inSchema` validates inbound application frames; it may be omitted by a peer\n * that expects no inbound application messages (any that arrive are a protocol\n * error).\n */\nexport function streamInternal<\n In extends JSONValue,\n Out extends JSONValue = JSONValue,\n>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In> | undefined,\n outSource?: Source<Out>,\n options?: StreamOutOptions,\n): Promise<Source<In>> {\n return streamInternalCore(\n lc,\n ws,\n inSchema,\n outSource,\n BigIntJSON.stringify,\n data => data,\n options,\n );\n}\n\n/**\n * {@link streamInternal} whose outbound `Source` carries messages that are\n * already stringified JSON or pre-serialized Buffers.\n */\nexport function streamInternalStringified<In extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In> | undefined,\n outSource?: Source<string | PreSerialized>,\n options?: StreamOutOptions,\n): Promise<Source<In>> {\n return streamInternalCore(\n lc,\n ws,\n inSchema,\n outSource,\n msg => (typeof msg === 'string' ? msg : msg.payload.toString('utf8')),\n data => data,\n options,\n );\n}\n\n/**\n * {@link streamInternal} that retains only the transport-frame size of each\n * inbound message. The size bounds downstream batching without keeping or\n * copying the JSON.\n */\nexport function streamInternalWithSize<\n In extends JSONValue,\n Out extends JSONValue = JSONValue,\n>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outSource?: Source<Out>,\n options?: StreamOutOptions,\n): Promise<Source<Sized<In>>> {\n return streamInternalCore(\n lc,\n ws,\n inSchema,\n outSource,\n BigIntJSON.stringify,\n (data, _frame, _id, size) => ({data, size}),\n options,\n );\n}\n\n/**\n * Runs the outbound send loop of a {@link streamInternal} connection: reads\n * messages from `source`, frames them as `{id, msg}` / `{id, batch}` (or\n * pre-serialized Buffers), and awaits the corresponding `{ack}` frames — which\n * are demuxed off the socket by the shared message handler in\n * {@link streamInternalCore} and delivered via the `acks` queue.\n */\nasync function runOutbound<T extends JSONValue | PreSerialized>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n stringify: (payload: T) => string,\n acks: Queue<Ack>,\n closer: WebSocketCloser,\n options?: StreamOutOptions | undefined,\n): Promise<void> {\n try {\n let nextID = 0;\n const {pipeline} = source;\n const batched = options?.batched ?? false;\n const maxBatchSize = Math.max(1, Math.floor(options?.maxBatchSize ?? 64));\n\n if (batched && source.pipelineBatched) {\n const batchedIterable = source.pipelineBatched(maxBatchSize);\n if (batchedIterable) {\n lc.debug?.(\n `started batched outbound stream (maxBatchSize=${maxBatchSize})`,\n );\n for await (const {values, consumed} of batchedIterable) {\n if (values.length === 1 && isPreSerialized(values[0])) {\n const id = ++nextID;\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, values[0].payload]);\n sendTextFrame(sink, data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })().catch(e => closer.close(e));\n } else if (values.some(isPreSerialized)) {\n let remaining = values.length;\n const onConsumed = () => {\n if (--remaining === 0) {\n consumed();\n }\n };\n for (const val of values) {\n const id = ++nextID;\n if (isPreSerialized(val)) {\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, val.payload]);\n sendTextFrame(sink, data);\n } else {\n const data = `{\"id\":${id},\"msg\":${stringify(val)}}`;\n sink.send(data);\n }\n void (async () => {\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n onConsumed();\n })().catch(e => closer.close(e));\n }\n } else {\n const id = ++nextID;\n const data =\n values.length === 1\n ? `{\"id\":${id},\"msg\":${stringify(values[0])}}`\n : `{\"id\":${id},\"batch\":[${values.map(stringify).join(',')}]}`;\n sink.send(data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })().catch(e => closer.close(e));\n }\n }\n closer.close();\n return;\n }\n }\n\n if (pipeline) {\n lc.debug?.(`started pipelined outbound stream`);\n for await (const {value: msg, consumed} of pipeline) {\n const id = ++nextID;\n if (isPreSerialized(msg)) {\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, msg.payload]);\n sendTextFrame(sink, data);\n } else {\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`pipelining`, data);\n sink.send(data);\n }\n\n // The ack is awaited off the send loop so that the next message can be\n // sent without waiting for it. A bad ack is a protocol error like in\n // the synchronous path below: close the socket (which cancels the\n // source) rather than leaving the rejection unhandled.\n void (async () => {\n const {ack} = await acks.dequeue();\n // lc.debug?.(`received ack`, ack);\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })().catch(e => closer.close(e));\n }\n } else {\n lc.debug?.(`started synchronous outbound stream`);\n for await (const msg of source) {\n const id = ++nextID;\n if (isPreSerialized(msg)) {\n const prefix = Buffer.from(`{\"id\":${id}`);\n const data = Buffer.concat([prefix, msg.payload]);\n sendTextFrame(sink, data);\n } else {\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`sending`, data);\n sink.send(data);\n }\n\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n }\n }\n closer.close();\n } catch (e) {\n closer.close(e);\n }\n}\n\nasync function streamInternalCore<\n In extends JSONValue,\n Out extends JSONValue | PreSerialized,\n TIn,\n>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In> | undefined,\n outSource: Source<Out> | undefined,\n stringify: (payload: Out) => string,\n transform: (data: In, frame: string, id: number, size: number) => TIn,\n options: StreamOutOptions | undefined,\n): Promise<Source<TIn>> {\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n // Acks received from the peer for the messages we send on `outSource`.\n const acks = new Queue<Ack>();\n\n const streamedSchema = inSchema\n ? v.object({\n id: v.number(),\n msg: inSchema.optional(),\n batch: v.array(inSchema).optional(),\n })\n : undefined;\n\n type SinkEntry = {\n consumed: () => void;\n data: TIn;\n };\n\n const sink: Subscription<TIn, SinkEntry> = new Subscription<TIn, SinkEntry>(\n {\n consumed: ({consumed}) => consumed(),\n cleanup: () => closer.close(),\n },\n ({data}) => data,\n );\n\n const closer = WebSocketCloser.forDuplex(\n lc,\n ws,\n outSource,\n sink,\n handleMessage,\n );\n\n function handleMessage(event: MessageEvent) {\n const data = event.data.toString();\n if (!sink.active) {\n lc.warn?.('dropping ws message received after close');\n return;\n }\n try {\n const value = BigIntJSON.parse(data);\n\n // Demux: an `{ack}` frame acknowledges a message we sent on `outSource`.\n const parsedAck = v.test(value, ackSchema);\n if (parsedAck.ok) {\n acks.enqueue(parsedAck.value);\n return;\n }\n\n // Otherwise it is an inbound application frame.\n if (!streamedSchema) {\n closer.close(new Error(`unexpected inbound message: ${data}`));\n return;\n }\n const parsed = v.parse(value, streamedSchema, 'passthrough');\n const {id, msg, batch} = parsed;\n\n const sendAck = () => {\n if (ws.readyState === ws.OPEN) {\n ws.send(JSON.stringify({ack: id} satisfies Ack));\n }\n };\n\n if (batch !== undefined && msg !== undefined) {\n throw new Error(`Message ${id} has both \"msg\" and \"batch\"`);\n }\n\n if (batch !== undefined) {\n let remaining = batch.length;\n if (remaining === 0) {\n sendAck();\n return;\n }\n const onConsumed = () => {\n if (--remaining === 0) {\n sendAck();\n }\n };\n const itemSize = Math.max(1, Math.round(data.length / batch.length));\n for (const item of batch) {\n sink.push({\n consumed: onConsumed,\n data: transform(item, data, id, itemSize),\n });\n }\n } else if (msg !== undefined) {\n sink.push({\n consumed: sendAck,\n data: transform(msg, data, id, data.length),\n });\n } else {\n throw new Error(`Message ${id} has neither \"msg\" nor \"batch\"`);\n }\n } catch (e) {\n closer.close(e);\n }\n }\n\n await closer.connected;\n\n // Start the outbound send loop only after the connection is open, so that a\n // client whose socket begins in CONNECTING can push before it connects.\n if (outSource) {\n void runOutbound(lc, outSource, ws, stringify, acks, closer, options);\n }\n\n return sink;\n}\n\nclass WebSocketCloser {\n readonly #lc: LogContext;\n readonly #ws: WebSocket;\n readonly #closeStream: (err?: unknown) => void;\n readonly #messageHandler: ((e: MessageEvent) => void | undefined) | null;\n readonly #connected = resolver();\n\n get connected(): Promise<void> {\n return this.#connected.promise;\n }\n\n /**\n * Closer for a {@link streamInternalCore} duplex. On socket close it cancels\n * the outbound `Source` (notifying it of unconsumed messages, as the old\n * `forSource` did) and fails/ends the inbound `Sink` (as the old `forSink`\n * did): fail() on error so consumers throw, end() otherwise so pending\n * messages finish. Either stream may be absent for a one-directional peer.\n */\n static forDuplex<In, InInput, Out>(\n lc: LogContext,\n ws: WebSocket,\n outSource: Source<Out> | undefined,\n inSink: Subscription<In, InInput>,\n messageHandler: (e: MessageEvent) => void | undefined,\n ) {\n return new WebSocketCloser(\n lc,\n ws,\n (err?: unknown) => {\n outSource?.cancel(err instanceof Error ? err : undefined);\n if (err) {\n inSink.fail(err instanceof Error ? err : new Error(String(err)));\n } else {\n inSink.end();\n }\n },\n messageHandler,\n );\n }\n\n private constructor(\n lc: LogContext,\n ws: WebSocket,\n closeStream: (err?: unknown) => void,\n messageHandler?: (e: MessageEvent) => void | undefined,\n ) {\n this.#lc = lc;\n this.#ws = ws;\n this.#closeStream = closeStream;\n this.#messageHandler = messageHandler ?? null;\n\n ws.addEventListener('open', this.#handleOpen);\n ws.addEventListener('close', this.#handleClose);\n ws.addEventListener('error', this.#handleError);\n if (this.#messageHandler) {\n ws.addEventListener('message', this.#messageHandler);\n }\n\n switch (ws.readyState) {\n case ws.CONNECTING:\n break; // expected for new connections. resolve or reject in handlers.\n case ws.OPEN:\n this.#connected.resolve();\n break;\n default:\n this.#connected.reject(\n new Error(`websocket already in state ${ws.readyState}`),\n );\n break;\n }\n }\n\n get #conn(): string {\n return 'connection' + (this.#ws.url ? ` to ${this.#ws.url}` : '');\n }\n\n #handleOpen = () => {\n this.#lc.info?.(`${this.#conn} established`);\n this.#connected.resolve();\n };\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.#lc.info?.(`${this.#conn} closed`, {\n code,\n reason,\n wasClean,\n });\n this.close();\n this.#connected.reject(`${this.#conn} closed with code ${code}`);\n };\n\n #handleError = ({message, error}: ErrorEvent) => {\n if (this.#ws.readyState === this.#ws.OPEN) {\n this.#lc.error?.(`error in ${this.#conn}`, message, error);\n }\n this.#connected.reject(error);\n };\n\n close(err?: unknown) {\n if (err) {\n this.#lc.error?.(`closing stream with error`, err);\n }\n this.#closeStream(err);\n if (!this.closed()) {\n this.#ws.close();\n }\n }\n\n closed() {\n return (\n this.#ws.readyState === this.#ws.CLOSED ||\n this.#ws.readyState === this.#ws.CLOSING\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -71,6 +71,17 @@ export declare class Subscription<T, M = T> implements Source<T>, Sink<M> {
|
|
|
71
71
|
* of type `M` to the external type `T` exposed via async iteration.
|
|
72
72
|
*/
|
|
73
73
|
constructor(options: Options<M> | undefined, publish: (m: M) => T);
|
|
74
|
+
/**
|
|
75
|
+
* Dynamically adds a function that is called when the Subscription
|
|
76
|
+
* terminates. This will be called immediately if the Subscription is
|
|
77
|
+
* already closed.
|
|
78
|
+
*
|
|
79
|
+
* Unlike the `cleanup` handler specified in the Subscription options,
|
|
80
|
+
* this a close handler can be added to an existing Subscription object
|
|
81
|
+
* thereby avoiding a cyclic dependency, and allowing Subscription
|
|
82
|
+
* receivers to add their own cleanup logic.
|
|
83
|
+
*/
|
|
84
|
+
addCloseHandler(onClose: (reason?: unknown) => void): this;
|
|
74
85
|
/**
|
|
75
86
|
* Pushes the next message to be consumed, and returns a `result` that resolves to the
|
|
76
87
|
* eventual {@link Result} of the `value`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/subscription.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/subscription.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,IAAI,EAAE,MAAM,EAAC,MAAM,cAAc,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,qBAAa,YAAY,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAE,YAAW,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;;IAC/D;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,EAC9B,OAAO,GAAE,OAAO,CAAC,CAAC,CAAM,EACxB,OAAO,GAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAU,sBAG9B;IAoBD;;;OAGG;IACH,YAAY,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,YAAK,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAazD;IAyBD;;;;;;;;;OASG;IACH,eAAe,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAOzD;IAED;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,aAAa,CA0B5B;IAED,iEAAiE;IACjE,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,qDAAqD;IACrD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,6DAA6D;IAC7D,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,sCAAsC;IACtC,IAAI,MAAM,gBAET;IAED;;;;;;;;;;;OAWG;IACH,GAAG,SAQF;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,QAEjB;IAED,wEAAwE;IACxE,IAAI,CAAC,GAAG,EAAE,KAAK,QAEd;IA6BD,IAAI,QAAQ,IAAI,aAAa,CAAC;QAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAC,CAAC,GAAG,SAAS,CAI1E;IAED,eAAe,yBAEZ,aAAa,CAAC;QAAC,MAAM,EAAE,CAAC,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAC,CAAC,GAAG,SAAS,CAQ/D;IAwIF,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAyBzC;CACF;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI;IACvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IAEnC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAE7B;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjD;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,yCAAyC;AACzC,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAAC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
+
import { RingBuffer } from "../../../shared/src/ring-buffer.js";
|
|
2
3
|
import { resolver } from "@rocicorp/resolver";
|
|
3
4
|
//#region ../zero-cache/src/types/subscription.ts
|
|
4
5
|
/**
|
|
@@ -70,14 +71,14 @@ var Subscription = class Subscription {
|
|
|
70
71
|
return new Subscription(options, publish);
|
|
71
72
|
}
|
|
72
73
|
#abortController = new AbortController();
|
|
73
|
-
#consumers =
|
|
74
|
-
#messages =
|
|
74
|
+
#consumers = new RingBuffer();
|
|
75
|
+
#messages = new RingBuffer();
|
|
75
76
|
#consuming = /* @__PURE__ */ new Set();
|
|
76
77
|
#pipelineEnabled;
|
|
77
78
|
#sentinel = void 0;
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
79
|
+
#coalesceFunc;
|
|
80
|
+
#consumedFunc;
|
|
81
|
+
#cleanupFunc;
|
|
81
82
|
#publish;
|
|
82
83
|
/**
|
|
83
84
|
* @param publish function for converting the internally pushed / coalesced message
|
|
@@ -85,25 +86,44 @@ var Subscription = class Subscription {
|
|
|
85
86
|
*/
|
|
86
87
|
constructor(options = {}, publish) {
|
|
87
88
|
const { coalesce, consumed = () => {}, cleanup = () => {}, pipeline = coalesce === void 0 } = options;
|
|
88
|
-
this.#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} finally {
|
|
92
|
-
prev.resolve("coalesced");
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
this.#consumed = (entry) => {
|
|
96
|
-
consumed(entry.value);
|
|
97
|
-
this.#consuming.delete(entry);
|
|
98
|
-
entry.resolve("consumed");
|
|
99
|
-
};
|
|
100
|
-
this.#cleanup = (entries, err) => {
|
|
101
|
-
cleanup(entries.map((e) => e.value), err);
|
|
102
|
-
entries.forEach((e) => e.resolve("unconsumed"));
|
|
103
|
-
};
|
|
89
|
+
this.#coalesceFunc = coalesce;
|
|
90
|
+
this.#consumedFunc = consumed;
|
|
91
|
+
this.#cleanupFunc = cleanup;
|
|
104
92
|
this.#publish = publish;
|
|
105
93
|
this.#pipelineEnabled = pipeline;
|
|
106
94
|
}
|
|
95
|
+
#coalesce(curr, prev) {
|
|
96
|
+
assert(this.#coalesceFunc, "expected a coalesce function");
|
|
97
|
+
try {
|
|
98
|
+
return this.#coalesceFunc(curr.value, prev.value);
|
|
99
|
+
} finally {
|
|
100
|
+
prev.resolve("coalesced");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
#consumed(prev) {
|
|
104
|
+
this.#consumedFunc(prev.value);
|
|
105
|
+
this.#consuming.delete(prev);
|
|
106
|
+
prev.resolve("consumed");
|
|
107
|
+
}
|
|
108
|
+
#cleanup(entries, err) {
|
|
109
|
+
this.#cleanupFunc(entries.map((e) => e.value), err);
|
|
110
|
+
entries.forEach((e) => e.resolve("unconsumed"));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Dynamically adds a function that is called when the Subscription
|
|
114
|
+
* terminates. This will be called immediately if the Subscription is
|
|
115
|
+
* already closed.
|
|
116
|
+
*
|
|
117
|
+
* Unlike the `cleanup` handler specified in the Subscription options,
|
|
118
|
+
* this a close handler can be added to an existing Subscription object
|
|
119
|
+
* thereby avoiding a cyclic dependency, and allowing Subscription
|
|
120
|
+
* receivers to add their own cleanup logic.
|
|
121
|
+
*/
|
|
122
|
+
addCloseHandler(onClose) {
|
|
123
|
+
if (this.signal.aborted) onClose(this.signal.reason);
|
|
124
|
+
else this.signal.addEventListener("abort", () => onClose(this.signal.reason));
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
107
127
|
/**
|
|
108
128
|
* Pushes the next message to be consumed, and returns a `result` that resolves to the
|
|
109
129
|
* eventual {@link Result} of the `value`.
|
|
@@ -128,13 +148,13 @@ var Subscription = class Subscription {
|
|
|
128
148
|
}
|
|
129
149
|
const consumer = this.#consumers.shift();
|
|
130
150
|
if (consumer) consumer.resolve(entry);
|
|
131
|
-
else if (this.#
|
|
132
|
-
const prev = this.#messages.
|
|
133
|
-
assert(prev !== "terminus", "
|
|
134
|
-
this.#messages
|
|
151
|
+
else if (this.#coalesceFunc && this.#messages.size && this.#messages.last() !== "terminus") {
|
|
152
|
+
const prev = this.#messages.last();
|
|
153
|
+
assert(prev !== void 0 && prev !== "terminus", "expected an entry");
|
|
154
|
+
this.#messages.replaceLast({
|
|
135
155
|
value: this.#coalesce(entry, prev),
|
|
136
156
|
resolve
|
|
137
|
-
};
|
|
157
|
+
});
|
|
138
158
|
} else this.#messages.push(entry);
|
|
139
159
|
return { result };
|
|
140
160
|
}
|
|
@@ -144,7 +164,7 @@ var Subscription = class Subscription {
|
|
|
144
164
|
}
|
|
145
165
|
/** The number of messages waiting to be dequeued. */
|
|
146
166
|
get queued() {
|
|
147
|
-
return this.#messages.
|
|
167
|
+
return this.#messages.size;
|
|
148
168
|
}
|
|
149
169
|
/** The number of messages dequeued but not yet "consumed" */
|
|
150
170
|
get consuming() {
|
|
@@ -167,7 +187,7 @@ var Subscription = class Subscription {
|
|
|
167
187
|
*
|
|
168
188
|
*/
|
|
169
189
|
end() {
|
|
170
|
-
if (this.#sentinel) {} else if (this.#messages.
|
|
190
|
+
if (this.#sentinel) {} else if (this.#messages.size === 0) this.cancel();
|
|
171
191
|
else this.#messages.push("terminus");
|
|
172
192
|
}
|
|
173
193
|
/**
|
|
@@ -189,9 +209,10 @@ var Subscription = class Subscription {
|
|
|
189
209
|
#terminate(sentinel) {
|
|
190
210
|
if (!this.#sentinel) {
|
|
191
211
|
this.#sentinel = sentinel;
|
|
192
|
-
this.#cleanup([...this.#consuming, ...this.#messages.filter((m) => m !== "terminus")], sentinel instanceof Error ? sentinel : void 0);
|
|
193
|
-
this.#messages.
|
|
194
|
-
for (let consumer = this.#consumers.shift(); consumer; consumer = this.#consumers.shift()) sentinel === "canceled"
|
|
212
|
+
this.#cleanup([...this.#consuming, ...this.#messages.toArray().filter((m) => m !== "terminus")], sentinel instanceof Error ? sentinel : void 0);
|
|
213
|
+
this.#messages.clear();
|
|
214
|
+
for (let consumer = this.#consumers.shift(); consumer; consumer = this.#consumers.shift()) if (sentinel === "canceled") consumer.resolve(null);
|
|
215
|
+
else consumer.reject(sentinel);
|
|
195
216
|
this.#abortController.abort(sentinel);
|
|
196
217
|
}
|
|
197
218
|
}
|
|
@@ -206,8 +227,8 @@ var Subscription = class Subscription {
|
|
|
206
227
|
return {
|
|
207
228
|
next: async () => {
|
|
208
229
|
const entries = [];
|
|
209
|
-
while (this.#messages.
|
|
210
|
-
if (this.#messages
|
|
230
|
+
while (this.#messages.size > 0 && entries.length < maxBatch) {
|
|
231
|
+
if (this.#messages.first() === "terminus") {
|
|
211
232
|
if (entries.length > 0) break;
|
|
212
233
|
this.#messages.shift();
|
|
213
234
|
this.cancel();
|
|
@@ -244,8 +265,8 @@ var Subscription = class Subscription {
|
|
|
244
265
|
done: true
|
|
245
266
|
};
|
|
246
267
|
entries.push(result);
|
|
247
|
-
while (this.#messages.
|
|
248
|
-
if (this.#messages
|
|
268
|
+
while (this.#messages.size > 0 && entries.length < maxBatch) {
|
|
269
|
+
if (this.#messages.first() === "terminus") break;
|
|
249
270
|
const entry = this.#messages.shift();
|
|
250
271
|
entries.push(entry);
|
|
251
272
|
}
|