@anabranch/queue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/esm/anabranch/index.d.ts +44 -0
  4. package/esm/anabranch/index.d.ts.map +1 -0
  5. package/esm/anabranch/index.js +41 -0
  6. package/esm/anabranch/streams/channel.d.ts +15 -0
  7. package/esm/anabranch/streams/channel.d.ts.map +1 -0
  8. package/esm/anabranch/streams/channel.js +122 -0
  9. package/esm/anabranch/streams/source.d.ts +75 -0
  10. package/esm/anabranch/streams/source.d.ts.map +1 -0
  11. package/esm/anabranch/streams/source.js +77 -0
  12. package/esm/anabranch/streams/stream.d.ts +431 -0
  13. package/esm/anabranch/streams/stream.d.ts.map +1 -0
  14. package/esm/anabranch/streams/stream.js +627 -0
  15. package/esm/anabranch/streams/task.d.ts +117 -0
  16. package/esm/anabranch/streams/task.d.ts.map +1 -0
  17. package/esm/anabranch/streams/task.js +419 -0
  18. package/esm/anabranch/streams/util.d.ts +33 -0
  19. package/esm/anabranch/streams/util.d.ts.map +1 -0
  20. package/esm/anabranch/streams/util.js +18 -0
  21. package/esm/package.json +3 -0
  22. package/esm/queue/adapter.d.ts +98 -0
  23. package/esm/queue/adapter.d.ts.map +1 -0
  24. package/esm/queue/adapter.js +1 -0
  25. package/esm/queue/errors.d.ts +45 -0
  26. package/esm/queue/errors.d.ts.map +1 -0
  27. package/esm/queue/errors.js +113 -0
  28. package/esm/queue/in-memory.d.ts +61 -0
  29. package/esm/queue/in-memory.d.ts.map +1 -0
  30. package/esm/queue/in-memory.js +291 -0
  31. package/esm/queue/index.d.ts +79 -0
  32. package/esm/queue/index.d.ts.map +1 -0
  33. package/esm/queue/index.js +74 -0
  34. package/esm/queue/queue.d.ts +166 -0
  35. package/esm/queue/queue.d.ts.map +1 -0
  36. package/esm/queue/queue.js +284 -0
  37. package/package.json +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Frodi Karlsson
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
@@ -0,0 +1,159 @@
1
+ # @anabranch/queue
2
+
3
+ Queue primitives with Task/Stream semantics for error-tolerant message
4
+ processing.
5
+
6
+ ## Description
7
+
8
+ A queue abstraction that integrates with anabranch's Task and Stream types for
9
+ composable error handling, concurrent processing, and automatic resource
10
+ management.
11
+
12
+ ## Features
13
+
14
+ - **Task/Stream Integration**: Leverage Task's retry/timeout and Stream's error
15
+ collection
16
+ - **Multiple Adapters**: In-memory implementation included, Redis/RabbitMQ/SQS
17
+ coming soon
18
+ - **Delayed Messages**: Support for scheduled/delayed message delivery
19
+ - **Dead Letter Queues**: Automatic routing of failed messages after max
20
+ attempts
21
+ - **Batch Operations**: Send multiple messages, acknowledge multiple at once
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ # JSR
27
+ jsr add @anabranch/queue
28
+
29
+ # Deno
30
+ deno add @anabranch/queue
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```ts
36
+ import { createInMemory, Queue } from "@anabranch/queue";
37
+
38
+ const connector = createInMemory();
39
+
40
+ // Send a message
41
+ const id = await Queue.withConnection(
42
+ connector,
43
+ (queue) => queue.send("notifications", { type: "welcome", userId: 123 }),
44
+ ).run();
45
+
46
+ // Process messages with error collection
47
+ const { successes, errors } = await Queue.withConnection(
48
+ connector,
49
+ (queue) =>
50
+ Task.of(() =>
51
+ queue.stream("notifications", { concurrency: 5 })
52
+ .map(async (msg) => await sendNotification(msg.data))
53
+ .tapErr((err) => logError(err))
54
+ ),
55
+ ).partition();
56
+ ```
57
+
58
+ ## API
59
+
60
+ ### Queue.send
61
+
62
+ Send a message to a queue with optional delay:
63
+
64
+ ```ts
65
+ await Queue.withConnection(
66
+ connector,
67
+ (queue) => queue.send("my-queue", { key: "value" }, { delayMs: 30_000 }),
68
+ ).run();
69
+ ```
70
+
71
+ ### Queue.stream
72
+
73
+ Stream messages with concurrent processing:
74
+
75
+ ```ts
76
+ const { successes, errors } = await Queue.withConnection(
77
+ connector,
78
+ (queue) =>
79
+ Task.of(() =>
80
+ queue.stream("orders", { count: 10, concurrency: 10 })
81
+ .map(async (msg) => await processOrder(msg.data))
82
+ ),
83
+ ).partition();
84
+ ```
85
+
86
+ ### Queue.ack / Queue.nack
87
+
88
+ Acknowledge successful processing or negative acknowledge with requeue:
89
+
90
+ ```ts
91
+ await Queue.withConnection(
92
+ connector,
93
+ (queue) => queue.nack("orders", msg.id, { requeue: true, delay: 5_000 }),
94
+ ).run();
95
+
96
+ // Or route to dead letter queue
97
+ await Queue.withConnection(
98
+ connector,
99
+ (queue) => queue.nack("orders", msg.id, { deadLetter: true }),
100
+ ).run();
101
+ ```
102
+
103
+ ### Queue.sendBatch
104
+
105
+ Send multiple messages efficiently:
106
+
107
+ ```ts
108
+ const ids = await Queue.withConnection(
109
+ connector,
110
+ (queue) =>
111
+ queue.sendBatch("notifications", [
112
+ { to: "user1@example.com" },
113
+ { to: "user2@example.com" },
114
+ ]),
115
+ ).run();
116
+ ```
117
+
118
+ ## Configuration
119
+
120
+ ### In-Memory Queue Options
121
+
122
+ ```ts
123
+ const connector = createInMemory({
124
+ maxBufferSize: 1000,
125
+ queues: {
126
+ orders: {
127
+ maxAttempts: 3,
128
+ deadLetterQueue: "orders-failed",
129
+ },
130
+ },
131
+ });
132
+ ```
133
+
134
+ ## Error Handling
135
+
136
+ All errors are typed for catchable handling:
137
+
138
+ - `QueueConnectionFailed` - Connection establishment failed
139
+ - `QueueSendFailed` - Send operation failed
140
+ - `QueueReceiveFailed` - Receive operation failed
141
+ - `QueueAckFailed` - Acknowledgment failed
142
+
143
+ ```ts
144
+ try {
145
+ await Queue.withConnection(connector, (queue) => queue.send("my-queue", data))
146
+ .run();
147
+ } catch (error) {
148
+ if (error instanceof QueueSendFailed) {
149
+ console.error("Failed to send:", error.message);
150
+ }
151
+ }
152
+ ```
153
+
154
+ ## Related
155
+
156
+ - [@anabranch/anabranch](https://jsr.io/@anabranch/anabranch) - Core Task/Stream
157
+ primitives
158
+ - [@anabranch/db](https://jsr.io/@anabranch/db) - Database adapter pattern
159
+ (inspiration for this package)
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Anabranch is a TypeScript library for error-tolerant async streams.
3
+ *
4
+ * Instead of throwing on the first error, it collects errors alongside
5
+ * successful values so you can process a stream to completion and deal with
6
+ * failures at the end — or not at all.
7
+ *
8
+ * The entry point is {@link Source}. Once you have a stream, chain operations
9
+ * like {@link Stream.map map}, {@link Stream.filter filter},
10
+ * {@link Stream.flatMap flatMap}, and {@link Stream.fold fold} to transform it,
11
+ * then consume it with {@link Stream.collect collect} or
12
+ * {@link Stream.partition partition}.
13
+ *
14
+ * @example Fetch a list of URLs concurrently, collect results and failures separately
15
+ * ```ts
16
+ * import { Source } from "anabranch";
17
+ *
18
+ * const { successes, errors } = await Source.from<string, Error>(
19
+ * async function* () {
20
+ * yield "https://example.com/1";
21
+ * yield "https://example.com/2";
22
+ * yield "https://example.com/3";
23
+ * },
24
+ * )
25
+ * .withConcurrency(3)
26
+ * .map(async (url) => {
27
+ * const res = await fetch(url);
28
+ * if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
29
+ * return res.json();
30
+ * })
31
+ * .partition();
32
+ *
33
+ * console.log(`${successes.length} succeeded, ${errors.length} failed`);
34
+ * ```
35
+ *
36
+ * @module
37
+ */
38
+ export { Source } from "./streams/source.js";
39
+ export { Task } from "./streams/task.js";
40
+ export { Channel } from "./streams/channel.js";
41
+ export type { Stream } from "./streams/stream.js";
42
+ export { AggregateError } from "./streams/util.js";
43
+ export type { ErrorResult, Promisable, Result, SuccessResult, } from "./streams/util.js";
44
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anabranch/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EACV,WAAW,EACX,UAAU,EACV,MAAM,EACN,aAAa,GACd,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Anabranch is a TypeScript library for error-tolerant async streams.
3
+ *
4
+ * Instead of throwing on the first error, it collects errors alongside
5
+ * successful values so you can process a stream to completion and deal with
6
+ * failures at the end — or not at all.
7
+ *
8
+ * The entry point is {@link Source}. Once you have a stream, chain operations
9
+ * like {@link Stream.map map}, {@link Stream.filter filter},
10
+ * {@link Stream.flatMap flatMap}, and {@link Stream.fold fold} to transform it,
11
+ * then consume it with {@link Stream.collect collect} or
12
+ * {@link Stream.partition partition}.
13
+ *
14
+ * @example Fetch a list of URLs concurrently, collect results and failures separately
15
+ * ```ts
16
+ * import { Source } from "anabranch";
17
+ *
18
+ * const { successes, errors } = await Source.from<string, Error>(
19
+ * async function* () {
20
+ * yield "https://example.com/1";
21
+ * yield "https://example.com/2";
22
+ * yield "https://example.com/3";
23
+ * },
24
+ * )
25
+ * .withConcurrency(3)
26
+ * .map(async (url) => {
27
+ * const res = await fetch(url);
28
+ * if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
29
+ * return res.json();
30
+ * })
31
+ * .partition();
32
+ *
33
+ * console.log(`${successes.length} succeeded, ${errors.length} failed`);
34
+ * ```
35
+ *
36
+ * @module
37
+ */
38
+ export { Source } from "./streams/source.js";
39
+ export { Task } from "./streams/task.js";
40
+ export { Channel } from "./streams/channel.js";
41
+ export { AggregateError } from "./streams/util.js";
@@ -0,0 +1,15 @@
1
+ import { _StreamImpl } from "./stream.js";
2
+ interface ChannelOptions<T> {
3
+ bufferSize?: number;
4
+ onDrop?: (value: T) => void;
5
+ onClose?: () => void;
6
+ }
7
+ export declare class Channel<T, E = never> extends _StreamImpl<T, E> {
8
+ private sourceImpl;
9
+ constructor(options?: ChannelOptions<T>);
10
+ send(value: T): void;
11
+ fail(error: E): void;
12
+ close(): void;
13
+ }
14
+ export {};
15
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/anabranch/streams/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,UAAU,cAAc,CAAC,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAgFD,qBAAa,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAE,SAAQ,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,UAAU,CAAsB;gBAE5B,OAAO,GAAE,cAAc,CAAC,CAAC,CAAM;IAM3C,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAIpB,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAIpB,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,122 @@
1
+ import { _StreamImpl } from "./stream.js";
2
+ class ChannelSource {
3
+ constructor(options = {}) {
4
+ Object.defineProperty(this, "queue", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: []
9
+ });
10
+ Object.defineProperty(this, "closed", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: false
15
+ });
16
+ Object.defineProperty(this, "consumers", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: []
21
+ });
22
+ Object.defineProperty(this, "bufferSize", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ Object.defineProperty(this, "onDrop", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: void 0
33
+ });
34
+ Object.defineProperty(this, "onClose", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ });
40
+ this.bufferSize = Number.isFinite(options.bufferSize)
41
+ ? Math.max(1, options.bufferSize)
42
+ : Infinity;
43
+ this.onDrop = options.onDrop;
44
+ this.onClose = options.onClose;
45
+ }
46
+ send(value) {
47
+ if (this.closed) {
48
+ return;
49
+ }
50
+ if (this.queue.length >= this.bufferSize && this.bufferSize !== Infinity) {
51
+ this.onDrop?.(value);
52
+ return;
53
+ }
54
+ this.queue.push({ type: "success", value });
55
+ this.wake();
56
+ }
57
+ fail(error) {
58
+ if (this.closed) {
59
+ return;
60
+ }
61
+ this.queue.push({ type: "error", error });
62
+ this.wake();
63
+ }
64
+ close() {
65
+ if (this.closed) {
66
+ return;
67
+ }
68
+ this.closed = true;
69
+ this.wake();
70
+ }
71
+ wake() {
72
+ while (this.consumers.length > 0) {
73
+ const consumer = this.consumers.shift();
74
+ if (consumer)
75
+ consumer();
76
+ }
77
+ }
78
+ async *generator() {
79
+ try {
80
+ while (true) {
81
+ while (this.queue.length > 0) {
82
+ yield this.queue.shift();
83
+ }
84
+ if (this.closed && this.queue.length === 0) {
85
+ return;
86
+ }
87
+ if (this.queue.length === 0) {
88
+ await new Promise((resolve) => {
89
+ this.consumers.push(resolve);
90
+ });
91
+ if (this.queue.length > 0)
92
+ continue;
93
+ }
94
+ }
95
+ }
96
+ finally {
97
+ this.onClose?.();
98
+ }
99
+ }
100
+ }
101
+ export class Channel extends _StreamImpl {
102
+ constructor(options = {}) {
103
+ const sourceImpl = new ChannelSource(options);
104
+ super(() => sourceImpl.generator(), Infinity, Infinity);
105
+ Object.defineProperty(this, "sourceImpl", {
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true,
109
+ value: void 0
110
+ });
111
+ this.sourceImpl = sourceImpl;
112
+ }
113
+ send(value) {
114
+ this.sourceImpl.send(value);
115
+ }
116
+ fail(error) {
117
+ this.sourceImpl.fail(error);
118
+ }
119
+ close() {
120
+ this.sourceImpl.close();
121
+ }
122
+ }
@@ -0,0 +1,75 @@
1
+ import { _StreamImpl } from "./stream.js";
2
+ import type { Result } from "./util.js";
3
+ /**
4
+ * The entry point for creating a {@link Stream}. Wraps an async generator so
5
+ * that yielded values become success results and any thrown error becomes a
6
+ * single error result.
7
+ *
8
+ * Use {@link Source.from} to create a source from an existing `AsyncIterable`
9
+ * or generator function. Use {@link Source.withConcurrency} and
10
+ * {@link Source.withBufferSize} to configure parallel execution.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { Source } from "anabranch";
15
+ *
16
+ * const stream = Source.from<number, Error>(async function* () {
17
+ * yield 1;
18
+ * yield 2;
19
+ * yield 3;
20
+ * });
21
+ *
22
+ * const results = await stream.collect();
23
+ * ```
24
+ */
25
+ export declare class Source<T, E> extends _StreamImpl<T, E> {
26
+ private readonly resultSource;
27
+ /**
28
+ * @param source An async generator function. Each yielded value becomes a
29
+ * success result; any thrown error becomes an error result and terminates
30
+ * the source.
31
+ * @param concurrency Maximum number of concurrent operations. Defaults to `Infinity`.
32
+ * @param bufferSize Maximum number of buffered results before backpressure is applied. Defaults to `Infinity`.
33
+ */
34
+ private constructor();
35
+ /**
36
+ * Creates a {@link Source} from an existing `AsyncIterable` or async
37
+ * generator function. Each value emitted becomes a success result; any
38
+ * thrown error becomes an error result.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { Source } from "anabranch";
43
+ *
44
+ * // From a generator function
45
+ * const stream = Source.from<number, Error>(async function* () {
46
+ * yield 1;
47
+ * yield 2;
48
+ * });
49
+ *
50
+ * // From an AsyncIterable
51
+ * async function* generate() {
52
+ * yield 1;
53
+ * yield 2;
54
+ * }
55
+ * const stream2 = Source.from<number, Error>(generate());
56
+ * ```
57
+ */
58
+ static from<T, E>(source: AsyncIterable<T>): Source<T, E>;
59
+ static from<T, E>(fn: () => AsyncGenerator<T>): Source<T, E>;
60
+ /**
61
+ * Creates a {@link Source} from an async generator that yields {@link Result}
62
+ * values directly. This is useful when you want to yield both successes and
63
+ * errors from the source without terminating it on the first error.
64
+ */
65
+ static fromResults<T, E>(source: () => AsyncGenerator<Result<T, E>>): Source<T, E>;
66
+ /**
67
+ * Sets the maximum number of concurrent operations for the stream.
68
+ */
69
+ withConcurrency(n: number): Source<T, E>;
70
+ /**
71
+ * Sets the maximum number of buffered results before backpressure is applied to the stream. If the buffer is full, the stream will pause until there is space in the buffer for new results.
72
+ */
73
+ withBufferSize(n: number): Source<T, E>;
74
+ }
75
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../src/anabranch/streams/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,MAAM,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAS/C,OAAO,CAAC,QAAQ,CAAC,YAAY;IAR/B;;;;;;OAMG;IACH,OAAO;IAQP;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IACzD,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IAoB5D;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EACrB,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACzC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IAIf;;OAEG;IACH,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IAIxC;;OAEG;IACH,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;CAGxC"}
@@ -0,0 +1,77 @@
1
+ import { _StreamImpl } from "./stream.js";
2
+ /**
3
+ * The entry point for creating a {@link Stream}. Wraps an async generator so
4
+ * that yielded values become success results and any thrown error becomes a
5
+ * single error result.
6
+ *
7
+ * Use {@link Source.from} to create a source from an existing `AsyncIterable`
8
+ * or generator function. Use {@link Source.withConcurrency} and
9
+ * {@link Source.withBufferSize} to configure parallel execution.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Source } from "anabranch";
14
+ *
15
+ * const stream = Source.from<number, Error>(async function* () {
16
+ * yield 1;
17
+ * yield 2;
18
+ * yield 3;
19
+ * });
20
+ *
21
+ * const results = await stream.collect();
22
+ * ```
23
+ */
24
+ export class Source extends _StreamImpl {
25
+ /**
26
+ * @param source An async generator function. Each yielded value becomes a
27
+ * success result; any thrown error becomes an error result and terminates
28
+ * the source.
29
+ * @param concurrency Maximum number of concurrent operations. Defaults to `Infinity`.
30
+ * @param bufferSize Maximum number of buffered results before backpressure is applied. Defaults to `Infinity`.
31
+ */
32
+ constructor(resultSource, concurrency = Infinity, bufferSize = Infinity) {
33
+ super(resultSource, concurrency, bufferSize);
34
+ Object.defineProperty(this, "resultSource", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: resultSource
39
+ });
40
+ }
41
+ static from(source) {
42
+ const fn = typeof source === "function" ? source : async function* () {
43
+ yield* source;
44
+ };
45
+ const resultSource = async function* () {
46
+ try {
47
+ for await (const value of fn()) {
48
+ yield { type: "success", value };
49
+ }
50
+ }
51
+ catch (error) {
52
+ yield { type: "error", error: error };
53
+ }
54
+ };
55
+ return new Source(resultSource);
56
+ }
57
+ /**
58
+ * Creates a {@link Source} from an async generator that yields {@link Result}
59
+ * values directly. This is useful when you want to yield both successes and
60
+ * errors from the source without terminating it on the first error.
61
+ */
62
+ static fromResults(source) {
63
+ return new Source(source);
64
+ }
65
+ /**
66
+ * Sets the maximum number of concurrent operations for the stream.
67
+ */
68
+ withConcurrency(n) {
69
+ return new Source(this.resultSource, n, this.bufferSize);
70
+ }
71
+ /**
72
+ * Sets the maximum number of buffered results before backpressure is applied to the stream. If the buffer is full, the stream will pause until there is space in the buffer for new results.
73
+ */
74
+ withBufferSize(n) {
75
+ return new Source(this.resultSource, this.concurrency, n);
76
+ }
77
+ }