clutchit 0.0.3

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -0
  4. package/dist/circuit/circuit-breaker.d.ts +47 -0
  5. package/dist/circuit/circuit-breaker.js +114 -0
  6. package/dist/circuit/index.d.ts +7 -0
  7. package/dist/circuit/index.js +7 -0
  8. package/dist/concurrency/bulkhead.d.ts +27 -0
  9. package/dist/concurrency/bulkhead.js +62 -0
  10. package/dist/concurrency/index.d.ts +16 -0
  11. package/dist/concurrency/index.js +13 -0
  12. package/dist/concurrency/rate-limiter.d.ts +25 -0
  13. package/dist/concurrency/rate-limiter.js +40 -0
  14. package/dist/concurrency/ref.d.ts +11 -0
  15. package/dist/concurrency/ref.js +26 -0
  16. package/dist/concurrency/semaphore.d.ts +15 -0
  17. package/dist/concurrency/semaphore.js +48 -0
  18. package/dist/queue/base.queue.d.ts +18 -0
  19. package/dist/queue/base.queue.js +66 -0
  20. package/dist/queue/bounded.queue.d.ts +11 -0
  21. package/dist/queue/bounded.queue.js +64 -0
  22. package/dist/queue/dropping.queue.d.ts +7 -0
  23. package/dist/queue/dropping.queue.js +20 -0
  24. package/dist/queue/faults.d.ts +20 -0
  25. package/dist/queue/faults.js +17 -0
  26. package/dist/queue/index.d.ts +11 -0
  27. package/dist/queue/index.js +11 -0
  28. package/dist/queue/sliding.queue.d.ts +7 -0
  29. package/dist/queue/sliding.queue.js +19 -0
  30. package/dist/retry/index.d.ts +6 -0
  31. package/dist/retry/index.js +7 -0
  32. package/dist/retry/retry.d.ts +21 -0
  33. package/dist/retry/retry.js +58 -0
  34. package/dist/schedule/index.d.ts +30 -0
  35. package/dist/schedule/index.js +29 -0
  36. package/dist/schedule/operators.d.ts +22 -0
  37. package/dist/schedule/operators.js +106 -0
  38. package/dist/schedule/runner.d.ts +30 -0
  39. package/dist/schedule/runner.js +57 -0
  40. package/dist/schedule/schedule.d.ts +31 -0
  41. package/dist/schedule/schedule.js +94 -0
  42. package/dist/timeout/index.d.ts +16 -0
  43. package/dist/timeout/index.js +9 -0
  44. package/dist/timeout/timeout.d.ts +14 -0
  45. package/dist/timeout/timeout.js +37 -0
  46. package/dist/unthrow/do.d.ts +5 -0
  47. package/dist/unthrow/do.js +23 -0
  48. package/dist/unthrow/fault.d.ts +37 -0
  49. package/dist/unthrow/fault.js +22 -0
  50. package/dist/unthrow/helpers.d.ts +9 -0
  51. package/dist/unthrow/helpers.js +56 -0
  52. package/dist/unthrow/index.d.ts +32 -0
  53. package/dist/unthrow/index.js +55 -0
  54. package/dist/unthrow/result.async.d.ts +19 -0
  55. package/dist/unthrow/result.async.js +63 -0
  56. package/dist/unthrow/result.d.ts +38 -0
  57. package/dist/unthrow/result.js +80 -0
  58. package/package.json +109 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hexac
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
File without changes
@@ -0,0 +1,47 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ import { Schedule } from '../schedule/index.js';
3
+ export type CircuitState = 'closed' | 'open' | 'half-open';
4
+ export declare const CircuitOpenFault: (remainingTimeout: number) => Readonly<{
5
+ code: "CIRCUIT_OPEN";
6
+ _category: "infrastructure";
7
+ _transient: false;
8
+ message: string;
9
+ } & Omit<{
10
+ message: string;
11
+ remainingTimeout: number;
12
+ }, "message">>;
13
+ export type CircuitOpenFault = ReturnType<typeof CircuitOpenFault>;
14
+ export interface CircuitBreakerOptions {
15
+ /** Number of consecutive failures before opening. Default: 5 */
16
+ failureThreshold?: number;
17
+ /** Schedule for reset timing. Default: Schedule.constant(30_000) */
18
+ resetSchedule?: Schedule;
19
+ /** Number of trial requests allowed in half-open state. Default: 1 */
20
+ halfOpenAttempts?: number;
21
+ /** Predicate to determine if an error should count as a failure. Default: all errors count. */
22
+ isFailure?: (error: unknown) => boolean;
23
+ /** Called when circuit state changes (for logging/monitoring). */
24
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
25
+ }
26
+ export declare class CircuitBreaker {
27
+ private _state;
28
+ private _failureCount;
29
+ private _resetAttempt;
30
+ private _halfOpenTrials;
31
+ private _nextResetTime;
32
+ private readonly _failureThreshold;
33
+ private readonly _resetSchedule;
34
+ private readonly _maxHalfOpenAttempts;
35
+ private readonly _isFailure;
36
+ private readonly _onStateChange?;
37
+ constructor(options?: CircuitBreakerOptions);
38
+ protect<T, E>(fn: () => ResultAsync<T, E>): ResultAsync<T, E | CircuitOpenFault>;
39
+ get state(): CircuitState;
40
+ get failureCount(): number;
41
+ reset(): void;
42
+ private onSuccess;
43
+ private onFailure;
44
+ private scheduleReset;
45
+ private transition;
46
+ }
47
+ //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -0,0 +1,114 @@
1
+ import { Fault, ResultAsync } from '../unthrow/index.js';
2
+ import { Schedule } from '../schedule/index.js';
3
+ export const CircuitOpenFault = Fault.define({
4
+ code: 'CIRCUIT_OPEN',
5
+ category: 'infrastructure',
6
+ create: (remainingTimeout) => ({
7
+ message: `Circuit breaker is open. Retry after ${remainingTimeout}ms`,
8
+ remainingTimeout,
9
+ }),
10
+ });
11
+ export class CircuitBreaker {
12
+ _state = 'closed';
13
+ _failureCount = 0;
14
+ _resetAttempt = 0;
15
+ _halfOpenTrials = 0;
16
+ _nextResetTime = null;
17
+ _failureThreshold;
18
+ _resetSchedule;
19
+ _maxHalfOpenAttempts;
20
+ _isFailure;
21
+ _onStateChange;
22
+ constructor(options) {
23
+ this._failureThreshold = options?.failureThreshold ?? 5;
24
+ this._resetSchedule = options?.resetSchedule ?? Schedule.constant(30_000);
25
+ this._maxHalfOpenAttempts = options?.halfOpenAttempts ?? 1;
26
+ this._isFailure = options?.isFailure ?? (() => true);
27
+ this._onStateChange = options?.onStateChange;
28
+ }
29
+ protect(fn) {
30
+ if (this._state === 'open') {
31
+ if (this._nextResetTime !== null && Date.now() >= this._nextResetTime) {
32
+ this.transition('half-open');
33
+ this._halfOpenTrials = 0;
34
+ }
35
+ else {
36
+ const remaining = this._nextResetTime
37
+ ? Math.max(0, this._nextResetTime - Date.now())
38
+ : 0;
39
+ return ResultAsync.err(CircuitOpenFault(remaining));
40
+ }
41
+ }
42
+ if (this._state === 'half-open') {
43
+ if (this._halfOpenTrials >= this._maxHalfOpenAttempts) {
44
+ return ResultAsync.err(CircuitOpenFault(0));
45
+ }
46
+ this._halfOpenTrials++;
47
+ }
48
+ return ResultAsync.fromPromise((async () => {
49
+ const result = await fn();
50
+ if (result.isOk()) {
51
+ this.onSuccess();
52
+ return result.value;
53
+ }
54
+ if (this._isFailure(result.error)) {
55
+ this.onFailure();
56
+ }
57
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
58
+ throw result.error;
59
+ })(), (e) => e);
60
+ }
61
+ get state() {
62
+ return this._state;
63
+ }
64
+ get failureCount() {
65
+ return this._failureCount;
66
+ }
67
+ reset() {
68
+ this._failureCount = 0;
69
+ this._resetAttempt = 0;
70
+ this._halfOpenTrials = 0;
71
+ this._nextResetTime = null;
72
+ if (this._state !== 'closed') {
73
+ this.transition('closed');
74
+ }
75
+ }
76
+ onSuccess() {
77
+ if (this._state === 'closed') {
78
+ this._failureCount = 0;
79
+ }
80
+ else if (this._state === 'half-open') {
81
+ this._failureCount = 0;
82
+ this._resetAttempt = 0;
83
+ this._halfOpenTrials = 0;
84
+ this.transition('closed');
85
+ }
86
+ }
87
+ onFailure() {
88
+ if (this._state === 'closed') {
89
+ this._failureCount++;
90
+ if (this._failureCount >= this._failureThreshold) {
91
+ this.transition('open');
92
+ this.scheduleReset();
93
+ }
94
+ }
95
+ else if (this._state === 'half-open') {
96
+ this._halfOpenTrials = 0;
97
+ this.transition('open');
98
+ this.scheduleReset();
99
+ }
100
+ }
101
+ scheduleReset() {
102
+ const step = this._resetSchedule.next(undefined, this._resetAttempt);
103
+ this._nextResetTime = Date.now() + step.delay;
104
+ this._resetAttempt++;
105
+ }
106
+ transition(to) {
107
+ const from = this._state;
108
+ if (from !== to) {
109
+ this._state = to;
110
+ this._onStateChange?.(from, to);
111
+ }
112
+ }
113
+ }
114
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1,7 @@
1
+ import { CircuitBreaker as _CircuitBreaker } from './circuit-breaker.js';
2
+ export type { CircuitState, CircuitBreakerOptions } from './circuit-breaker.js';
3
+ export type { CircuitOpenFault } from './circuit-breaker.js';
4
+ export declare namespace Circuit {
5
+ const Breaker: typeof _CircuitBreaker;
6
+ }
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { CircuitBreaker as _CircuitBreaker } from './circuit-breaker.js';
2
+ // eslint-disable-next-line @typescript-eslint/no-namespace
3
+ export var Circuit;
4
+ (function (Circuit) {
5
+ Circuit.Breaker = _CircuitBreaker;
6
+ })(Circuit || (Circuit = {}));
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ export declare const BulkheadRejectedFault: () => Readonly<{
3
+ code: "BULKHEAD_REJECTED";
4
+ _category: "infrastructure";
5
+ _transient: false;
6
+ message: string;
7
+ } & Omit<{
8
+ message: string;
9
+ }, "message">>;
10
+ export type BulkheadRejectedFault = ReturnType<typeof BulkheadRejectedFault>;
11
+ export interface BulkheadOptions {
12
+ concurrency: number;
13
+ queue?: number;
14
+ }
15
+ export declare class Bulkhead {
16
+ private _active;
17
+ private readonly _maxConcurrency;
18
+ private readonly _maxQueue;
19
+ private readonly _queue;
20
+ constructor(options: BulkheadOptions);
21
+ execute<T, E>(fn: () => ResultAsync<T, E>): ResultAsync<T, E | BulkheadRejectedFault>;
22
+ get activeCount(): number;
23
+ get queueSize(): number;
24
+ private acquire;
25
+ private release;
26
+ }
27
+ //# sourceMappingURL=bulkhead.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { Fault, ResultAsync } from '../unthrow/index.js';
2
+ export const BulkheadRejectedFault = Fault.define({
3
+ code: 'BULKHEAD_REJECTED',
4
+ category: 'infrastructure',
5
+ create: () => ({
6
+ message: 'Bulkhead capacity exceeded',
7
+ }),
8
+ });
9
+ export class Bulkhead {
10
+ _active = 0;
11
+ _maxConcurrency;
12
+ _maxQueue;
13
+ _queue = [];
14
+ constructor(options) {
15
+ this._maxConcurrency = options.concurrency;
16
+ this._maxQueue = options.queue ?? 0;
17
+ }
18
+ execute(fn) {
19
+ if (this._active >= this._maxConcurrency &&
20
+ this._queue.length >= this._maxQueue) {
21
+ return ResultAsync.err(BulkheadRejectedFault());
22
+ }
23
+ return ResultAsync.fromPromise((async () => {
24
+ await this.acquire();
25
+ try {
26
+ const result = await fn();
27
+ if (result.isOk())
28
+ return result.value;
29
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
30
+ throw result.error;
31
+ }
32
+ finally {
33
+ this.release();
34
+ }
35
+ })(), (e) => e);
36
+ }
37
+ get activeCount() {
38
+ return this._active;
39
+ }
40
+ get queueSize() {
41
+ return this._queue.length;
42
+ }
43
+ acquire() {
44
+ if (this._active < this._maxConcurrency) {
45
+ this._active++;
46
+ return Promise.resolve();
47
+ }
48
+ return new Promise((resolve) => {
49
+ this._queue.push(resolve);
50
+ });
51
+ }
52
+ release() {
53
+ const next = this._queue.shift();
54
+ if (next) {
55
+ next();
56
+ }
57
+ else {
58
+ this._active--;
59
+ }
60
+ }
61
+ }
62
+ //# sourceMappingURL=bulkhead.js.map
@@ -0,0 +1,16 @@
1
+ import { Semaphore as _Semaphore } from './semaphore.js';
2
+ import { RateLimiter as _RateLimiter } from './rate-limiter.js';
3
+ import { Bulkhead as _Bulkhead } from './bulkhead.js';
4
+ import { Ref as _Ref } from './ref.js';
5
+ export type { SemaphoreOptions } from './semaphore.js';
6
+ export type { RateLimiterOptions } from './rate-limiter.js';
7
+ export type { RateLimitFault } from './rate-limiter.js';
8
+ export type { BulkheadOptions } from './bulkhead.js';
9
+ export type { BulkheadRejectedFault } from './bulkhead.js';
10
+ export declare namespace Concurrency {
11
+ const Semaphore: typeof _Semaphore;
12
+ const RateLimiter: typeof _RateLimiter;
13
+ const Bulkhead: typeof _Bulkhead;
14
+ const Ref: typeof _Ref;
15
+ }
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { Semaphore as _Semaphore } from './semaphore.js';
2
+ import { RateLimiter as _RateLimiter } from './rate-limiter.js';
3
+ import { Bulkhead as _Bulkhead } from './bulkhead.js';
4
+ import { Ref as _Ref } from './ref.js';
5
+ // eslint-disable-next-line @typescript-eslint/no-namespace
6
+ export var Concurrency;
7
+ (function (Concurrency) {
8
+ Concurrency.Semaphore = _Semaphore;
9
+ Concurrency.RateLimiter = _RateLimiter;
10
+ Concurrency.Bulkhead = _Bulkhead;
11
+ Concurrency.Ref = _Ref;
12
+ })(Concurrency || (Concurrency = {}));
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,25 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ export declare const RateLimitFault: (retryAfter: number) => Readonly<{
3
+ code: "RATE_LIMITED";
4
+ _category: "infrastructure";
5
+ _transient: true;
6
+ message: string;
7
+ } & Omit<{
8
+ message: string;
9
+ retryAfter: number;
10
+ }, "message">>;
11
+ export type RateLimitFault = ReturnType<typeof RateLimitFault>;
12
+ export interface RateLimiterOptions {
13
+ limit: number;
14
+ window: number;
15
+ }
16
+ export declare class RateLimiter {
17
+ private readonly _limit;
18
+ private readonly _window;
19
+ private readonly _timestamps;
20
+ constructor(options: RateLimiterOptions);
21
+ execute<T, E>(fn: () => ResultAsync<T, E>): ResultAsync<T, E | RateLimitFault>;
22
+ get remaining(): number;
23
+ private cleanup;
24
+ }
25
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { Fault, ResultAsync } from '../unthrow/index.js';
2
+ export const RateLimitFault = Fault.define({
3
+ code: 'RATE_LIMITED',
4
+ category: 'infrastructure',
5
+ transient: true,
6
+ create: (retryAfter) => ({
7
+ message: `Rate limit exceeded. Retry after ${retryAfter}ms`,
8
+ retryAfter,
9
+ }),
10
+ });
11
+ export class RateLimiter {
12
+ _limit;
13
+ _window;
14
+ _timestamps = [];
15
+ constructor(options) {
16
+ this._limit = options.limit;
17
+ this._window = options.window;
18
+ }
19
+ execute(fn) {
20
+ this.cleanup();
21
+ if (this._timestamps.length >= this._limit) {
22
+ const oldest = this._timestamps[0];
23
+ const retryAfter = Math.max(0, oldest + this._window - Date.now());
24
+ return ResultAsync.err(RateLimitFault(retryAfter));
25
+ }
26
+ this._timestamps.push(Date.now());
27
+ return fn();
28
+ }
29
+ get remaining() {
30
+ this.cleanup();
31
+ return Math.max(0, this._limit - this._timestamps.length);
32
+ }
33
+ cleanup() {
34
+ const cutoff = Date.now() - this._window;
35
+ while (this._timestamps.length > 0 && this._timestamps[0] <= cutoff) {
36
+ this._timestamps.shift();
37
+ }
38
+ }
39
+ }
40
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1,11 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ export declare class Ref<T> {
3
+ private _value;
4
+ private readonly _mutex;
5
+ constructor(initial: T);
6
+ get(): T;
7
+ modify<R>(fn: (current: T) => [next: T, result: R]): ResultAsync<R, never>;
8
+ update(fn: (current: T) => T): ResultAsync<void, never>;
9
+ set(value: T): ResultAsync<void, never>;
10
+ }
11
+ //# sourceMappingURL=ref.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ import { Semaphore } from './semaphore.js';
3
+ export class Ref {
4
+ _value;
5
+ _mutex = new Semaphore({ permits: 1 });
6
+ constructor(initial) {
7
+ this._value = initial;
8
+ }
9
+ get() {
10
+ return this._value;
11
+ }
12
+ modify(fn) {
13
+ return this._mutex.execute(() => {
14
+ const [next, result] = fn(this._value);
15
+ this._value = next;
16
+ return ResultAsync.ok(result);
17
+ });
18
+ }
19
+ update(fn) {
20
+ return this.modify((v) => [fn(v), undefined]);
21
+ }
22
+ set(value) {
23
+ return this.modify(() => [value, undefined]);
24
+ }
25
+ }
26
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1,15 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ export interface SemaphoreOptions {
3
+ permits: number;
4
+ }
5
+ export declare class Semaphore {
6
+ private _available;
7
+ private readonly _queue;
8
+ constructor(options: SemaphoreOptions);
9
+ execute<T, E>(fn: () => ResultAsync<T, E>): ResultAsync<T, E>;
10
+ get available(): number;
11
+ get waiting(): number;
12
+ private acquire;
13
+ private release;
14
+ }
15
+ //# sourceMappingURL=semaphore.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ export class Semaphore {
3
+ _available;
4
+ _queue = [];
5
+ constructor(options) {
6
+ this._available = options.permits;
7
+ }
8
+ execute(fn) {
9
+ return ResultAsync.fromPromise((async () => {
10
+ await this.acquire();
11
+ try {
12
+ const result = await fn();
13
+ if (result.isOk())
14
+ return result.value;
15
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
16
+ throw result.error;
17
+ }
18
+ finally {
19
+ this.release();
20
+ }
21
+ })(), (e) => e);
22
+ }
23
+ get available() {
24
+ return this._available;
25
+ }
26
+ get waiting() {
27
+ return this._queue.length;
28
+ }
29
+ acquire() {
30
+ if (this._available > 0) {
31
+ this._available--;
32
+ return Promise.resolve();
33
+ }
34
+ return new Promise((resolve) => {
35
+ this._queue.push(resolve);
36
+ });
37
+ }
38
+ release() {
39
+ const next = this._queue.shift();
40
+ if (next) {
41
+ next();
42
+ }
43
+ else {
44
+ this._available++;
45
+ }
46
+ }
47
+ }
48
+ //# sourceMappingURL=semaphore.js.map
@@ -0,0 +1,18 @@
1
+ import { Result, ResultAsync } from '../unthrow/index.js';
2
+ import { QueueEmptyFault } from './faults.js';
3
+ export interface QueueOptions {
4
+ capacity: number;
5
+ }
6
+ export declare class BaseQueue<T> {
7
+ protected readonly _buffer: T[];
8
+ protected readonly _capacity: number;
9
+ protected readonly _takeWaiters: Array<(item: T) => void>;
10
+ constructor(options: QueueOptions);
11
+ protected _tryTake(): Result<T, QueueEmptyFault>;
12
+ take(signal?: AbortSignal): ResultAsync<T, never>;
13
+ takeBatch(n: number, signal?: AbortSignal): ResultAsync<T[], never>;
14
+ poll(): Result<T, QueueEmptyFault>;
15
+ get size(): number;
16
+ get capacity(): number;
17
+ }
18
+ //# sourceMappingURL=base.queue.d.ts.map
@@ -0,0 +1,66 @@
1
+ import { Result, ResultAsync } from '../unthrow/index.js';
2
+ import { QueueEmptyFault } from './faults.js';
3
+ export class BaseQueue {
4
+ _buffer = [];
5
+ _capacity;
6
+ _takeWaiters = [];
7
+ constructor(options) {
8
+ this._capacity = options.capacity;
9
+ }
10
+ _tryTake() {
11
+ if (this._buffer.length > 0) {
12
+ return Result.ok(this._buffer.shift());
13
+ }
14
+ return Result.err(QueueEmptyFault());
15
+ }
16
+ take(signal) {
17
+ const taken = this._tryTake();
18
+ if (taken.isOk())
19
+ return ResultAsync.ok(taken.value);
20
+ return ResultAsync.fromPromise(new Promise((resolve) => {
21
+ this._takeWaiters.push(resolve);
22
+ signal?.addEventListener('abort', () => {
23
+ const idx = this._takeWaiters.indexOf(resolve);
24
+ if (idx !== -1)
25
+ this._takeWaiters.splice(idx, 1);
26
+ }, { once: true });
27
+ }), () => undefined);
28
+ }
29
+ takeBatch(n, signal) {
30
+ return ResultAsync.fromPromise((async () => {
31
+ const taken = this._tryTake();
32
+ let first;
33
+ if (taken.isOk()) {
34
+ first = taken.value;
35
+ }
36
+ else {
37
+ first = await new Promise((resolve) => {
38
+ this._takeWaiters.push(resolve);
39
+ signal?.addEventListener('abort', () => {
40
+ const idx = this._takeWaiters.indexOf(resolve);
41
+ if (idx !== -1)
42
+ this._takeWaiters.splice(idx, 1);
43
+ }, { once: true });
44
+ });
45
+ }
46
+ const items = [first];
47
+ while (items.length < n) {
48
+ const next = this._tryTake();
49
+ if (next.isErr())
50
+ break;
51
+ items.push(next.value);
52
+ }
53
+ return items;
54
+ })(), () => undefined);
55
+ }
56
+ poll() {
57
+ return this._tryTake();
58
+ }
59
+ get size() {
60
+ return this._buffer.length;
61
+ }
62
+ get capacity() {
63
+ return this._capacity;
64
+ }
65
+ }
66
+ //# sourceMappingURL=base.queue.js.map
@@ -0,0 +1,11 @@
1
+ import { Result, ResultAsync } from '../unthrow/index.js';
2
+ import { QueueEmptyFault, QueueFullFault } from './faults.js';
3
+ import { BaseQueue, type QueueOptions } from './base.queue.js';
4
+ export declare class BoundedQueue<T> extends BaseQueue<T> {
5
+ private readonly _putWaiters;
6
+ constructor(options: QueueOptions);
7
+ protected _tryTake(): Result<T, ReturnType<typeof QueueEmptyFault>>;
8
+ offer(item: T): Result<void, ReturnType<typeof QueueFullFault>>;
9
+ put(item: T, signal?: AbortSignal): ResultAsync<void, never>;
10
+ }
11
+ //# sourceMappingURL=bounded.queue.d.ts.map
@@ -0,0 +1,64 @@
1
+ import { Result, ResultAsync } from '../unthrow/index.js';
2
+ import { QueueEmptyFault, QueueFullFault } from './faults.js';
3
+ import { BaseQueue } from './base.queue.js';
4
+ export class BoundedQueue extends BaseQueue {
5
+ _putWaiters = [];
6
+ constructor(options) {
7
+ super(options);
8
+ }
9
+ _tryTake() {
10
+ if (this._buffer.length > 0) {
11
+ const item = this._buffer.shift();
12
+ // Space freed — accept a put waiter if any
13
+ const pw = this._putWaiters.shift();
14
+ if (pw) {
15
+ this._buffer.push(pw.item);
16
+ pw.resolve();
17
+ }
18
+ return Result.ok(item);
19
+ }
20
+ // Buffer empty — try put waiters directly
21
+ const pw = this._putWaiters.shift();
22
+ if (pw) {
23
+ pw.resolve();
24
+ return Result.ok(pw.item);
25
+ }
26
+ return Result.err(QueueEmptyFault());
27
+ }
28
+ offer(item) {
29
+ // Give directly to a waiting consumer
30
+ const waiter = this._takeWaiters.shift();
31
+ if (waiter) {
32
+ waiter(item);
33
+ return Result.ok(undefined);
34
+ }
35
+ if (this._buffer.length >= this._capacity) {
36
+ return Result.err(QueueFullFault(this._capacity));
37
+ }
38
+ this._buffer.push(item);
39
+ return Result.ok(undefined);
40
+ }
41
+ put(item, signal) {
42
+ // Give directly to a waiting consumer
43
+ const waiter = this._takeWaiters.shift();
44
+ if (waiter) {
45
+ waiter(item);
46
+ return ResultAsync.ok(undefined);
47
+ }
48
+ if (this._buffer.length < this._capacity) {
49
+ this._buffer.push(item);
50
+ return ResultAsync.ok(undefined);
51
+ }
52
+ // Wait for space
53
+ return ResultAsync.fromPromise(new Promise((resolve) => {
54
+ const entry = { item, resolve };
55
+ this._putWaiters.push(entry);
56
+ signal?.addEventListener('abort', () => {
57
+ const idx = this._putWaiters.indexOf(entry);
58
+ if (idx !== -1)
59
+ this._putWaiters.splice(idx, 1);
60
+ }, { once: true });
61
+ }), () => undefined);
62
+ }
63
+ }
64
+ //# sourceMappingURL=bounded.queue.js.map
@@ -0,0 +1,7 @@
1
+ import { BaseQueue, type QueueOptions } from './base.queue.js';
2
+ export declare class DroppingQueue<T> extends BaseQueue<T> {
3
+ constructor(options: QueueOptions);
4
+ /** Always succeeds. Drops the item if full. Returns whether item was accepted. */
5
+ offer(item: T): boolean;
6
+ }
7
+ //# sourceMappingURL=dropping.queue.d.ts.map