@peerbit/stream 5.0.0 → 5.0.1-cba1bcc

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.
@@ -21,7 +21,7 @@
21
21
  // - Lane indices are 0..(lanes-1). Defaults to lane 0.
22
22
  import { AbortError } from "@peerbit/time";
23
23
  import GenericFIFO from "fast-fifo";
24
- import defer from "p-defer";
24
+ import defer, { type DeferredPromise } from "p-defer";
25
25
 
26
26
  export interface AbortOptions {
27
27
  signal?: AbortSignal;
@@ -56,6 +56,13 @@ export interface PushableLanes<T, R = void, N = unknown>
56
56
  */
57
57
  onEmpty(options?: AbortOptions): Promise<void>;
58
58
 
59
+ /**
60
+ * Resolves when the total buffered bytes are at or below `limitBytes`.
61
+ * If an AbortSignal is given and it aborts, only this promise rejects;
62
+ * the pushable itself is not ended.
63
+ */
64
+ onBufferedBelow(limitBytes: number, options?: AbortOptions): Promise<void>;
65
+
59
66
  /** Total number of bytes buffered (across all lanes). */
60
67
  get readableLength(): number;
61
68
 
@@ -90,6 +97,12 @@ export interface Options {
90
97
  */
91
98
  onPush?(value: { byteLength: number }, lane: number): void;
92
99
 
100
+ /**
101
+ * Optional hook invoked whenever the total buffered bytes change.
102
+ * Useful for external backpressure coordination.
103
+ */
104
+ onBufferSize?(bufferedBytes: number): void;
105
+
93
106
  /**
94
107
  * Fairness mode:
95
108
  * - 'priority': strict priority (original behavior).
@@ -320,9 +333,34 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
320
333
  let onNext: ((next: Next<PushType>, lane: number) => ReturnType) | null;
321
334
  let ended = false;
322
335
  let drain = defer<void>();
336
+ const bufferedBelowWaiters = new Set<{
337
+ limitBytes: number;
338
+ deferred: DeferredPromise<void>;
339
+ }>();
323
340
 
324
341
  const maxBytes = options.maxBufferedBytes;
325
342
  const overflow: OverflowPolicy = options.overflow ?? "throw";
343
+ const notifyBufferSize = () => {
344
+ options?.onBufferSize?.(totalBufferedBytes());
345
+ };
346
+
347
+ const notifyBufferedBelowWaiters = () => {
348
+ if (bufferedBelowWaiters.size === 0) return;
349
+ const size = totalBufferedBytes();
350
+ for (const waiter of [...bufferedBelowWaiters]) {
351
+ if (size <= waiter.limitBytes) {
352
+ bufferedBelowWaiters.delete(waiter);
353
+ waiter.deferred.resolve();
354
+ }
355
+ }
356
+ };
357
+
358
+ const resolveBufferedBelowWaiters = () => {
359
+ for (const waiter of [...bufferedBelowWaiters]) {
360
+ bufferedBelowWaiters.delete(waiter);
361
+ waiter.deferred.resolve();
362
+ }
363
+ };
326
364
 
327
365
  const getNext = (): NextResult<ValueType> => {
328
366
  const next: Next<PushType> | undefined = buffer.shift();
@@ -362,6 +400,8 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
362
400
  };
363
401
  });
364
402
  } finally {
403
+ notifyBufferSize();
404
+ notifyBufferedBelowWaiters();
365
405
  // If buffer is empty after this turn, resolve the drain promise (in a microtask)
366
406
  if (buffer.isEmpty()) {
367
407
  queueMicrotask(() => {
@@ -383,6 +423,8 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
383
423
  const bufferError = (err: Error): ReturnType => {
384
424
  // swap to ByteFifo to deliver a single terminal error
385
425
  buffer = new ByteFifo<PushType>();
426
+ notifyBufferSize();
427
+ notifyBufferedBelowWaiters();
386
428
  if (onNext != null) {
387
429
  return onNext({ error: err }, 0);
388
430
  }
@@ -421,18 +463,26 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
421
463
  value,
422
464
  clampLane(lane, isLaneQueue(buffer) ? buffer.lanes.length : 1),
423
465
  );
466
+ notifyBufferSize();
424
467
  return out;
425
468
  };
426
469
 
427
470
  const end = (err?: Error): ReturnType => {
428
471
  if (ended) return pushable;
429
472
  ended = true;
473
+ queueMicrotask(() => {
474
+ drain.resolve();
475
+ drain = defer<void>();
476
+ resolveBufferedBelowWaiters();
477
+ });
430
478
  return err != null ? bufferError(err) : bufferNext({ done: true }, 0);
431
479
  };
432
480
 
433
481
  const _return = (): DoneResult => {
434
482
  // Ensure prompt termination
435
483
  buffer = new ByteFifo<PushType>();
484
+ notifyBufferSize();
485
+ notifyBufferedBelowWaiters();
436
486
  end();
437
487
  return { done: true };
438
488
  };
@@ -470,7 +520,7 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
470
520
  const signal = opts?.signal;
471
521
  signal?.throwIfAborted?.();
472
522
 
473
- if (buffer.isEmpty()) return;
523
+ if (buffer.isEmpty() || ended) return;
474
524
 
475
525
  let cancel: Promise<void> | undefined;
476
526
  let listener: (() => void) | undefined;
@@ -483,8 +533,50 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
483
533
  }
484
534
 
485
535
  try {
486
- await Promise.race([drain.promise, cancel]);
536
+ await Promise.race(
537
+ cancel != null ? [drain.promise, cancel] : [drain.promise],
538
+ );
539
+ } finally {
540
+ if (listener != null) {
541
+ signal?.removeEventListener("abort", listener);
542
+ }
543
+ }
544
+ },
545
+
546
+ onBufferedBelow: async (limitBytes: number, opts?: AbortOptions) => {
547
+ const signal = opts?.signal;
548
+ signal?.throwIfAborted?.();
549
+ const normalizedLimit = Math.max(0, Math.floor(limitBytes));
550
+
551
+ if (totalBufferedBytes() <= normalizedLimit || ended) return;
552
+
553
+ const waiter = {
554
+ limitBytes: normalizedLimit,
555
+ deferred: defer<void>(),
556
+ };
557
+ bufferedBelowWaiters.add(waiter);
558
+
559
+ let cancel: Promise<void> | undefined;
560
+ let listener: (() => void) | undefined;
561
+
562
+ if (signal != null) {
563
+ cancel = new Promise<void>((_resolve, reject) => {
564
+ listener = () => {
565
+ bufferedBelowWaiters.delete(waiter);
566
+ reject(new AbortError());
567
+ };
568
+ signal.addEventListener("abort", listener!);
569
+ });
570
+ }
571
+
572
+ try {
573
+ await Promise.race(
574
+ cancel != null
575
+ ? [waiter.deferred.promise, cancel]
576
+ : [waiter.deferred.promise],
577
+ );
487
578
  } finally {
579
+ bufferedBelowWaiters.delete(waiter);
488
580
  if (listener != null) {
489
581
  signal?.removeEventListener("abort", listener);
490
582
  }
@@ -540,6 +632,9 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
540
632
  onEmpty(opts?: AbortOptions) {
541
633
  return _pushable.onEmpty(opts);
542
634
  },
635
+ onBufferedBelow(limitBytes: number, opts?: AbortOptions) {
636
+ return _pushable.onBufferedBelow(limitBytes, opts);
637
+ },
543
638
  };
544
639
 
545
640
  return pushable;