@peerbit/stream 3.0.11 → 3.1.1
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/lib/esm/index.d.ts +7 -7
- package/lib/esm/index.js +28 -26
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/pushable-lanes.d.ts +70 -0
- package/lib/esm/pushable-lanes.js +262 -0
- package/lib/esm/pushable-lanes.js.map +1 -0
- package/package.json +5 -3
- package/src/index.ts +34 -33
- package/src/pushable-lanes.ts +385 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import defer from "p-defer";
|
|
2
|
+
import GenericFIFO from "fast-fifo";
|
|
3
|
+
|
|
4
|
+
export class AbortError extends Error {
|
|
5
|
+
type: string;
|
|
6
|
+
code: string;
|
|
7
|
+
|
|
8
|
+
constructor(message?: string, code?: string) {
|
|
9
|
+
super(message ?? "The operation was aborted");
|
|
10
|
+
this.type = "aborted";
|
|
11
|
+
this.code = code ?? "ABORT_ERR";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AbortOptions {
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An iterable that you can push values into.
|
|
21
|
+
*/
|
|
22
|
+
export interface PushableLanes<T, R = void, N = unknown>
|
|
23
|
+
extends AsyncGenerator<T, R, N> {
|
|
24
|
+
/**
|
|
25
|
+
* End the iterable after all values in the buffer (if any) have been yielded. If an
|
|
26
|
+
* error is passed the buffer is cleared immediately and the next iteration will
|
|
27
|
+
* throw the passed error
|
|
28
|
+
*/
|
|
29
|
+
end(err?: Error): this;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Push a value into the iterable. Values are yielded from the iterable in the order
|
|
33
|
+
* they are pushed. Values not yet consumed from the iterable are buffered.
|
|
34
|
+
*/
|
|
35
|
+
push(value: T, lane?: number): this;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a promise that resolves when the underlying queue becomes empty (e.g.
|
|
39
|
+
* this.readableLength === 0).
|
|
40
|
+
*
|
|
41
|
+
* If an AbortSignal is passed as an option and that signal aborts, it only
|
|
42
|
+
* causes the returned promise to reject - it does not end the pushable.
|
|
43
|
+
*/
|
|
44
|
+
onEmpty(options?: AbortOptions): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* This property contains the total number of bytes in the queue ready to be read.
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
get readableLength(): number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get readable length for specific lane
|
|
55
|
+
* @param lane
|
|
56
|
+
*/
|
|
57
|
+
getReadableLength(lane?: number): number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Options {
|
|
61
|
+
/**
|
|
62
|
+
* A function called after *all* values have been yielded from the iterator (including
|
|
63
|
+
* buffered values). In the case when the iterator is ended with an error it will be
|
|
64
|
+
* passed the error as a parameter.
|
|
65
|
+
*/
|
|
66
|
+
onEnd?(err?: Error): void;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* How many lanes, lane 0 is fastest and will drain before lane 1 is consumed
|
|
70
|
+
*/
|
|
71
|
+
lanes?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface DoneResult {
|
|
75
|
+
done: true;
|
|
76
|
+
}
|
|
77
|
+
export interface ValueResult<T> {
|
|
78
|
+
done: false;
|
|
79
|
+
value: T;
|
|
80
|
+
}
|
|
81
|
+
export type NextResult<T> = ValueResult<T> | DoneResult;
|
|
82
|
+
|
|
83
|
+
export interface Next<T> {
|
|
84
|
+
done?: boolean;
|
|
85
|
+
error?: Error;
|
|
86
|
+
value?: T;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fifo but with total readableLength counter
|
|
91
|
+
*/
|
|
92
|
+
class Uint8ArrayFifo<T extends { byteLength: number }> extends GenericFIFO<
|
|
93
|
+
Next<T>
|
|
94
|
+
> {
|
|
95
|
+
size: number = 0;
|
|
96
|
+
push(val: Next<T>): void {
|
|
97
|
+
if (val.value) {
|
|
98
|
+
this.size += val.value.byteLength;
|
|
99
|
+
}
|
|
100
|
+
return super.push(val);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
shift(): Next<T> | undefined {
|
|
104
|
+
const shifted = super.shift();
|
|
105
|
+
if (shifted?.value) {
|
|
106
|
+
this.size -= shifted.value.byteLength;
|
|
107
|
+
}
|
|
108
|
+
return shifted;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A queue consisting of multiple 'lanes' with different priority to be emptied.
|
|
114
|
+
* The lane with index 0 will empty before lane with index 1 etc..
|
|
115
|
+
* TODO add an additional proprty to control whether we we pick objects from slower lanes
|
|
116
|
+
* so no lane get really "stuck"
|
|
117
|
+
*/
|
|
118
|
+
class Uint8arrayPriorityQueue<T extends { byteLength: number }> {
|
|
119
|
+
lanes: Uint8ArrayFifo<T>[];
|
|
120
|
+
constructor(options: { lanes: number } = { lanes: 1 }) {
|
|
121
|
+
this.lanes = new Array(options.lanes);
|
|
122
|
+
for (let i = 0; i < this.lanes.length; i++) {
|
|
123
|
+
this.lanes[i] = new Uint8ArrayFifo();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get size() {
|
|
128
|
+
let sum = 0;
|
|
129
|
+
for (const lane of this.lanes) {
|
|
130
|
+
sum += lane.size;
|
|
131
|
+
}
|
|
132
|
+
return sum;
|
|
133
|
+
}
|
|
134
|
+
push(val: Next<T>, lane: number) {
|
|
135
|
+
return this.lanes[lane].push(val);
|
|
136
|
+
}
|
|
137
|
+
shift(): Next<T> | undefined {
|
|
138
|
+
// fetch the first non undefined item.
|
|
139
|
+
// by iterating from index 0 up we define that lanes with lower index have higher prioirity
|
|
140
|
+
for (const lane of this.lanes) {
|
|
141
|
+
const element = lane.shift();
|
|
142
|
+
if (element) {
|
|
143
|
+
return element;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
isEmpty(): boolean {
|
|
149
|
+
for (const lane of this.lanes) {
|
|
150
|
+
if (!lane.isEmpty()) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function pushableLanes<T extends { byteLength: number } = Uint8Array>(
|
|
159
|
+
options: Options = {}
|
|
160
|
+
): PushableLanes<T> {
|
|
161
|
+
return _pushable<Uint8Array, T, PushableLanes<T>>(options);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Modified from https://github.com/alanshaw/it-pushable
|
|
165
|
+
function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
166
|
+
options?: Options
|
|
167
|
+
): ReturnType {
|
|
168
|
+
options = options ?? {};
|
|
169
|
+
let onEnd = options.onEnd;
|
|
170
|
+
let buffer: Uint8arrayPriorityQueue<PushType> | Uint8ArrayFifo<PushType> =
|
|
171
|
+
new Uint8arrayPriorityQueue<PushType>(
|
|
172
|
+
options.lanes ? { lanes: options.lanes } : undefined
|
|
173
|
+
);
|
|
174
|
+
let pushable: any;
|
|
175
|
+
let onNext: ((next: Next<PushType>, lane: number) => ReturnType) | null;
|
|
176
|
+
let ended: boolean;
|
|
177
|
+
let drain = defer();
|
|
178
|
+
|
|
179
|
+
const getNext = (): NextResult<ValueType> => {
|
|
180
|
+
const next: Next<PushType> | undefined = buffer.shift();
|
|
181
|
+
|
|
182
|
+
if (next == null) {
|
|
183
|
+
return { done: true };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (next.error != null) {
|
|
187
|
+
throw next.error;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
done: next.done === true,
|
|
192
|
+
// @ts-expect-error if done is false, value will be present
|
|
193
|
+
value: next.value
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const waitNext = async (): Promise<NextResult<ValueType>> => {
|
|
198
|
+
try {
|
|
199
|
+
if (!buffer.isEmpty()) {
|
|
200
|
+
return getNext();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (ended) {
|
|
204
|
+
return { done: true };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return await new Promise<NextResult<ValueType>>((resolve, reject) => {
|
|
208
|
+
onNext = (next: Next<PushType>, lane: number) => {
|
|
209
|
+
onNext = null;
|
|
210
|
+
buffer.push(next, lane);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
resolve(getNext());
|
|
214
|
+
} catch (err) {
|
|
215
|
+
reject(err);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return pushable;
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
} finally {
|
|
222
|
+
if (buffer.isEmpty()) {
|
|
223
|
+
// settle promise in the microtask queue to give consumers a chance to
|
|
224
|
+
// await after calling .push
|
|
225
|
+
queueMicrotask(() => {
|
|
226
|
+
drain.resolve();
|
|
227
|
+
drain = defer();
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const bufferNext = (next: Next<PushType>, lane: number): ReturnType => {
|
|
234
|
+
if (onNext != null) {
|
|
235
|
+
return onNext(next, lane);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
buffer.push(next, lane);
|
|
239
|
+
return pushable;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const bufferError = (err: Error): ReturnType => {
|
|
243
|
+
buffer = new Uint8ArrayFifo();
|
|
244
|
+
|
|
245
|
+
if (onNext != null) {
|
|
246
|
+
return onNext({ error: err }, 0);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
buffer.push({ error: err });
|
|
250
|
+
return pushable;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const push = (value: PushType, lane: number = 0): ReturnType => {
|
|
254
|
+
if (ended) {
|
|
255
|
+
return pushable;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return bufferNext({ done: false, value }, lane);
|
|
259
|
+
};
|
|
260
|
+
const end = (err?: Error): ReturnType => {
|
|
261
|
+
if (ended) return pushable;
|
|
262
|
+
ended = true;
|
|
263
|
+
|
|
264
|
+
return err != null ? bufferError(err) : bufferNext({ done: true }, 0);
|
|
265
|
+
};
|
|
266
|
+
const _return = (): DoneResult => {
|
|
267
|
+
buffer = new Uint8ArrayFifo();
|
|
268
|
+
end();
|
|
269
|
+
|
|
270
|
+
return { done: true };
|
|
271
|
+
};
|
|
272
|
+
const _throw = (err: Error): DoneResult => {
|
|
273
|
+
end(err);
|
|
274
|
+
|
|
275
|
+
return { done: true };
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
pushable = {
|
|
279
|
+
[Symbol.asyncIterator]() {
|
|
280
|
+
return this;
|
|
281
|
+
},
|
|
282
|
+
next: waitNext,
|
|
283
|
+
return: _return,
|
|
284
|
+
throw: _throw,
|
|
285
|
+
push,
|
|
286
|
+
end,
|
|
287
|
+
get readableLength(): number {
|
|
288
|
+
return buffer.size;
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
getReadableLength(lane?: number): number {
|
|
292
|
+
if (lane == null) {
|
|
293
|
+
return buffer.size;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (buffer instanceof Uint8arrayPriorityQueue) {
|
|
297
|
+
return buffer.lanes[lane].size;
|
|
298
|
+
}
|
|
299
|
+
return buffer.size; // we can only arrive here if we are "done" or "err" or "end" where we reasign the buffer to a simple one and put 1 message into it
|
|
300
|
+
},
|
|
301
|
+
onEmpty: async (options?: AbortOptions) => {
|
|
302
|
+
const signal = options?.signal;
|
|
303
|
+
signal?.throwIfAborted();
|
|
304
|
+
|
|
305
|
+
if (buffer.isEmpty()) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let cancel: Promise<void> | undefined;
|
|
310
|
+
let listener: (() => void) | undefined;
|
|
311
|
+
|
|
312
|
+
if (signal != null) {
|
|
313
|
+
cancel = new Promise((resolve, reject) => {
|
|
314
|
+
listener = () => {
|
|
315
|
+
reject(new AbortError());
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
signal.addEventListener("abort", listener);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
await Promise.race([drain.promise, cancel]);
|
|
324
|
+
} finally {
|
|
325
|
+
if (listener != null && signal != null) {
|
|
326
|
+
signal?.removeEventListener("abort", listener);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (onEnd == null) {
|
|
333
|
+
return pushable;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const _pushable = pushable;
|
|
337
|
+
|
|
338
|
+
pushable = {
|
|
339
|
+
[Symbol.asyncIterator]() {
|
|
340
|
+
return this;
|
|
341
|
+
},
|
|
342
|
+
next() {
|
|
343
|
+
return _pushable.next();
|
|
344
|
+
},
|
|
345
|
+
throw(err: Error) {
|
|
346
|
+
_pushable.throw(err);
|
|
347
|
+
|
|
348
|
+
if (onEnd != null) {
|
|
349
|
+
onEnd(err);
|
|
350
|
+
onEnd = undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return { done: true };
|
|
354
|
+
},
|
|
355
|
+
return() {
|
|
356
|
+
_pushable.return();
|
|
357
|
+
|
|
358
|
+
if (onEnd != null) {
|
|
359
|
+
onEnd();
|
|
360
|
+
onEnd = undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { done: true };
|
|
364
|
+
},
|
|
365
|
+
push,
|
|
366
|
+
end(err: Error) {
|
|
367
|
+
_pushable.end(err);
|
|
368
|
+
|
|
369
|
+
if (onEnd != null) {
|
|
370
|
+
onEnd(err);
|
|
371
|
+
onEnd = undefined;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return pushable;
|
|
375
|
+
},
|
|
376
|
+
get readableLength() {
|
|
377
|
+
return _pushable.readableLength;
|
|
378
|
+
},
|
|
379
|
+
onEmpty: (opts?: AbortOptions) => {
|
|
380
|
+
return _pushable.onEmpty(opts);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
return pushable;
|
|
385
|
+
}
|