@okikio/observables 1.0.2
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/LICENSE +21 -0
- package/README.md +578 -0
- package/esm/_dnt.polyfills.d.ts +20 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +12 -0
- package/esm/_spec.d.ts +260 -0
- package/esm/_spec.d.ts.map +1 -0
- package/esm/_spec.js +1 -0
- package/esm/_types.d.ts +141 -0
- package/esm/_types.d.ts.map +1 -0
- package/esm/_types.js +20 -0
- package/esm/error.d.ts +331 -0
- package/esm/error.d.ts.map +1 -0
- package/esm/error.js +408 -0
- package/esm/events.d.ts +320 -0
- package/esm/events.d.ts.map +1 -0
- package/esm/events.js +451 -0
- package/esm/helpers/_types.d.ts +188 -0
- package/esm/helpers/_types.d.ts.map +1 -0
- package/esm/helpers/_types.js +1 -0
- package/esm/helpers/mod.d.ts +90 -0
- package/esm/helpers/mod.d.ts.map +1 -0
- package/esm/helpers/mod.js +90 -0
- package/esm/helpers/operations/batch.d.ts +109 -0
- package/esm/helpers/operations/batch.d.ts.map +1 -0
- package/esm/helpers/operations/batch.js +140 -0
- package/esm/helpers/operations/combination.d.ts +162 -0
- package/esm/helpers/operations/combination.d.ts.map +1 -0
- package/esm/helpers/operations/combination.js +350 -0
- package/esm/helpers/operations/conditional.d.ts +211 -0
- package/esm/helpers/operations/conditional.d.ts.map +1 -0
- package/esm/helpers/operations/conditional.js +280 -0
- package/esm/helpers/operations/core.d.ts +198 -0
- package/esm/helpers/operations/core.d.ts.map +1 -0
- package/esm/helpers/operations/core.js +264 -0
- package/esm/helpers/operations/errors.d.ts +277 -0
- package/esm/helpers/operations/errors.d.ts.map +1 -0
- package/esm/helpers/operations/errors.js +378 -0
- package/esm/helpers/operations/mod.d.ts +26 -0
- package/esm/helpers/operations/mod.d.ts.map +1 -0
- package/esm/helpers/operations/mod.js +25 -0
- package/esm/helpers/operations/timing.d.ts +206 -0
- package/esm/helpers/operations/timing.d.ts.map +1 -0
- package/esm/helpers/operations/timing.js +457 -0
- package/esm/helpers/operators.d.ts +520 -0
- package/esm/helpers/operators.d.ts.map +1 -0
- package/esm/helpers/operators.js +563 -0
- package/esm/helpers/pipe.d.ts +118 -0
- package/esm/helpers/pipe.d.ts.map +1 -0
- package/esm/helpers/pipe.js +129 -0
- package/esm/helpers/utils.d.ts +142 -0
- package/esm/helpers/utils.d.ts.map +1 -0
- package/esm/helpers/utils.js +193 -0
- package/esm/mod.d.ts +863 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +861 -0
- package/esm/observable.d.ts +1610 -0
- package/esm/observable.d.ts.map +1 -0
- package/esm/observable.js +1970 -0
- package/esm/package.json +3 -0
- package/esm/queue.d.ts +201 -0
- package/esm/queue.d.ts.map +1 -0
- package/esm/queue.js +273 -0
- package/esm/symbol.d.ts +60 -0
- package/esm/symbol.d.ts.map +1 -0
- package/esm/symbol.js +132 -0
- package/package.json +96 -0
- package/script/_dnt.polyfills.d.ts +20 -0
- package/script/_dnt.polyfills.d.ts.map +1 -0
- package/script/_dnt.polyfills.js +13 -0
- package/script/_spec.d.ts +260 -0
- package/script/_spec.d.ts.map +1 -0
- package/script/_spec.js +2 -0
- package/script/_types.d.ts +141 -0
- package/script/_types.d.ts.map +1 -0
- package/script/_types.js +22 -0
- package/script/error.d.ts +331 -0
- package/script/error.d.ts.map +1 -0
- package/script/error.js +414 -0
- package/script/events.d.ts +320 -0
- package/script/events.d.ts.map +1 -0
- package/script/events.js +458 -0
- package/script/helpers/_types.d.ts +188 -0
- package/script/helpers/_types.d.ts.map +1 -0
- package/script/helpers/_types.js +2 -0
- package/script/helpers/mod.d.ts +90 -0
- package/script/helpers/mod.d.ts.map +1 -0
- package/script/helpers/mod.js +106 -0
- package/script/helpers/operations/batch.d.ts +109 -0
- package/script/helpers/operations/batch.d.ts.map +1 -0
- package/script/helpers/operations/batch.js +144 -0
- package/script/helpers/operations/combination.d.ts +162 -0
- package/script/helpers/operations/combination.d.ts.map +1 -0
- package/script/helpers/operations/combination.js +355 -0
- package/script/helpers/operations/conditional.d.ts +211 -0
- package/script/helpers/operations/conditional.d.ts.map +1 -0
- package/script/helpers/operations/conditional.js +286 -0
- package/script/helpers/operations/core.d.ts +198 -0
- package/script/helpers/operations/core.d.ts.map +1 -0
- package/script/helpers/operations/core.js +272 -0
- package/script/helpers/operations/errors.d.ts +277 -0
- package/script/helpers/operations/errors.d.ts.map +1 -0
- package/script/helpers/operations/errors.js +387 -0
- package/script/helpers/operations/mod.d.ts +26 -0
- package/script/helpers/operations/mod.d.ts.map +1 -0
- package/script/helpers/operations/mod.js +41 -0
- package/script/helpers/operations/timing.d.ts +206 -0
- package/script/helpers/operations/timing.d.ts.map +1 -0
- package/script/helpers/operations/timing.js +464 -0
- package/script/helpers/operators.d.ts +520 -0
- package/script/helpers/operators.d.ts.map +1 -0
- package/script/helpers/operators.js +570 -0
- package/script/helpers/pipe.d.ts +118 -0
- package/script/helpers/pipe.d.ts.map +1 -0
- package/script/helpers/pipe.js +132 -0
- package/script/helpers/utils.d.ts +142 -0
- package/script/helpers/utils.d.ts.map +1 -0
- package/script/helpers/utils.js +200 -0
- package/script/mod.d.ts +863 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +877 -0
- package/script/observable.d.ts +1610 -0
- package/script/observable.d.ts.map +1 -0
- package/script/observable.js +1984 -0
- package/script/package.json +3 -0
- package/script/queue.d.ts +201 -0
- package/script/queue.d.ts.map +1 -0
- package/script/queue.js +286 -0
- package/script/symbol.d.ts +60 -0
- package/script/symbol.d.ts.map +1 -0
- package/script/symbol.js +135 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operators that turn each source value into another stream and combine the
|
|
3
|
+
* results.
|
|
4
|
+
*
|
|
5
|
+
* This entrypoint covers the "flattening" family of operators such as
|
|
6
|
+
* `mergeMap`, `concatMap`, and `switchMap`. Use it when one input value needs
|
|
7
|
+
* to start follow-up async work, for example fetching related records, reading
|
|
8
|
+
* files, or switching to the latest search request.
|
|
9
|
+
*
|
|
10
|
+
* The operators in this module mainly differ in concurrency and cancellation
|
|
11
|
+
* behavior. `mergeMap` keeps multiple inner streams alive at once, `concatMap`
|
|
12
|
+
* preserves order by running one at a time, and `switchMap` cancels older work
|
|
13
|
+
* when a newer source value arrives.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import "../../_dnt.polyfills.js";
|
|
18
|
+
import { createStatefulOperator } from "../operators.js";
|
|
19
|
+
import { isObservableError, ObservableError } from "../../error.js";
|
|
20
|
+
import { pull } from "../../observable.js";
|
|
21
|
+
/**
|
|
22
|
+
* Transforms each item into a new stream and merges their outputs, running
|
|
23
|
+
* them in parallel.
|
|
24
|
+
*
|
|
25
|
+
* Like `Promise.all(items.map(project))` but for streams, with control over
|
|
26
|
+
* concurrency. It's designed for high-throughput parallel processing.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { pipe, mergeMap, from } from "./helpers/mod.ts";
|
|
31
|
+
* import { of } from "../../observable.ts";
|
|
32
|
+
*
|
|
33
|
+
* // Promise.all behavior
|
|
34
|
+
* const ids = [1, 2, 3];
|
|
35
|
+
* const promises = ids.map(id => Promise.resolve(`User ${id}`));
|
|
36
|
+
* const users = await Promise.all(promises); // ["User 1", "User 2", "User 3"]
|
|
37
|
+
*
|
|
38
|
+
* // Stream behavior
|
|
39
|
+
* const idStream = from(ids);
|
|
40
|
+
* const userStream = pipe(
|
|
41
|
+
* idStream,
|
|
42
|
+
* mergeMap(id => of(`User ${id}`), 2) // Process 2 at a time
|
|
43
|
+
* );
|
|
44
|
+
*
|
|
45
|
+
* // Results may arrive in any order, e.g., "User 2", "User 1", "User 3"
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Practical Use Case
|
|
49
|
+
*
|
|
50
|
+
* Use `mergeMap` to fetch data for multiple items concurrently. For example,
|
|
51
|
+
* given a stream of user IDs, you can fetch each user's profile in parallel.
|
|
52
|
+
* This is much faster than fetching them one by one.
|
|
53
|
+
*
|
|
54
|
+
* ## Key Insight
|
|
55
|
+
*
|
|
56
|
+
* `mergeMap` is for parallel, high-throughput operations where the order of
|
|
57
|
+
* results doesn't matter. It's the go-to for maximizing concurrency.
|
|
58
|
+
*
|
|
59
|
+
* @typeParam T - Type of values from the source Observable
|
|
60
|
+
* @typeParam R - Type of values in the result Observable
|
|
61
|
+
* @param project - Function that maps a source value to an Observable
|
|
62
|
+
* @param concurrent - Maximum number of inner Observables being subscribed
|
|
63
|
+
* to concurrently. Default is Infinity.
|
|
64
|
+
* @returns An operator function that maps and flattens values
|
|
65
|
+
*/
|
|
66
|
+
export function mergeMap(project, concurrent = Infinity) {
|
|
67
|
+
return createStatefulOperator({
|
|
68
|
+
name: "mergeMap",
|
|
69
|
+
// Initialize state
|
|
70
|
+
createState: () => ({
|
|
71
|
+
activeSubscriptions: new Map(),
|
|
72
|
+
buffer: [],
|
|
73
|
+
sourceCompleted: false,
|
|
74
|
+
index: 0,
|
|
75
|
+
activeCount: 0,
|
|
76
|
+
}),
|
|
77
|
+
// Process each incoming chunk
|
|
78
|
+
async transform(chunk, state, controller) {
|
|
79
|
+
// If we're under the concurrency limit, process immediately
|
|
80
|
+
if (state.activeCount < concurrent) {
|
|
81
|
+
await subscribeToProjection(chunk, state.index++);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Otherwise, buffer for later
|
|
85
|
+
state.buffer.push([chunk, state.index++]);
|
|
86
|
+
}
|
|
87
|
+
// Helper function to subscribe to inner Observable
|
|
88
|
+
async function subscribeToProjection(value, innerIndex) {
|
|
89
|
+
let innerObservable;
|
|
90
|
+
try {
|
|
91
|
+
// Apply projection function to get inner Observable
|
|
92
|
+
innerObservable = project(value, innerIndex);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
// Forward any errors from the projection function
|
|
96
|
+
controller.enqueue(ObservableError.from(err, "operator:stateful:mergeMap:project", value));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
state.activeCount++;
|
|
100
|
+
// Use pull to iterate asynchronously
|
|
101
|
+
try {
|
|
102
|
+
for await (const innerValue of pull(innerObservable, { throwError: false })) {
|
|
103
|
+
controller.enqueue(innerValue);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
controller.enqueue(ObservableError.from(err, "operator:stateful:mergeMap:innerObservable", value));
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
// Clean up after inner Observable completes
|
|
111
|
+
state.activeSubscriptions.delete(innerIndex);
|
|
112
|
+
state.activeCount--;
|
|
113
|
+
// Process the buffer if we have space
|
|
114
|
+
processBuffer();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Helper function to process the buffer
|
|
118
|
+
async function processBuffer() {
|
|
119
|
+
// While we have room for more inner Observables and
|
|
120
|
+
// there are items in the buffer, subscribe to inner Observables
|
|
121
|
+
while (state.activeCount < concurrent && state.buffer.length > 0) {
|
|
122
|
+
const [value, bufferIndex] = state.buffer.shift();
|
|
123
|
+
await subscribeToProjection(value, bufferIndex);
|
|
124
|
+
}
|
|
125
|
+
// If the source is completed and we have no active inner
|
|
126
|
+
// subscriptions, complete the output stream
|
|
127
|
+
// Nothing more to do, transformation is complete
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
// Handle the end of the source stream
|
|
131
|
+
flush(state) {
|
|
132
|
+
// Mark the source as completed
|
|
133
|
+
state.sourceCompleted = true;
|
|
134
|
+
// If no active inner subscriptions, we're done
|
|
135
|
+
// Stream will close naturally
|
|
136
|
+
// Otherwise, let the active subscriptions complete
|
|
137
|
+
},
|
|
138
|
+
// Handle cancellation
|
|
139
|
+
cancel(state) {
|
|
140
|
+
// Clean up all inner subscriptions
|
|
141
|
+
for (const subscription of state.activeSubscriptions.values()) {
|
|
142
|
+
subscription.unsubscribe();
|
|
143
|
+
}
|
|
144
|
+
// Clear the buffer and state
|
|
145
|
+
state.buffer.length = 0;
|
|
146
|
+
state.activeSubscriptions.clear();
|
|
147
|
+
state.activeCount = 0;
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Transforms each item into a new stream and runs them one after another, in
|
|
153
|
+
* strict order.
|
|
154
|
+
*
|
|
155
|
+
* Like a series of `await` calls in a `for...of` loop, this ensures that each
|
|
156
|
+
* new stream completes before the next one begins.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* import { pipe, concatMap, from } from "./helpers/mod.ts";
|
|
161
|
+
* import { of } from "../../observable.ts";
|
|
162
|
+
*
|
|
163
|
+
* // Sequential awaits in a loop
|
|
164
|
+
* async function processSequentially() {
|
|
165
|
+
* const results = [];
|
|
166
|
+
* for (const id of [1, 2, 3]) {
|
|
167
|
+
* const result = await Promise.resolve(`Step ${id}`);
|
|
168
|
+
* results.push(result);
|
|
169
|
+
* }
|
|
170
|
+
* return results; // ["Step 1", "Step 2", "Step 3"]
|
|
171
|
+
* }
|
|
172
|
+
*
|
|
173
|
+
* // Stream behavior
|
|
174
|
+
* const idStream = from([1, 2, 3]);
|
|
175
|
+
* const processStream = pipe(
|
|
176
|
+
* idStream,
|
|
177
|
+
* concatMap(id => of(`Step ${id}`))
|
|
178
|
+
* );
|
|
179
|
+
*
|
|
180
|
+
* // Results are guaranteed to be in order: "Step 1", "Step 2", "Step 3"
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* ## Practical Use Case
|
|
184
|
+
*
|
|
185
|
+
* Use `concatMap` for sequential operations where order matters, such as a
|
|
186
|
+
* multi-step process where each step depends on the previous one (e.g., create
|
|
187
|
+
* user, then create their profile, then send a welcome email).
|
|
188
|
+
*
|
|
189
|
+
* ## Key Insight
|
|
190
|
+
*
|
|
191
|
+
* `concatMap` guarantees order by waiting for each inner stream to complete
|
|
192
|
+
* before starting the next. It's perfect for sequential tasks but is slower
|
|
193
|
+
* than `mergeMap` because it doesn't run in parallel.
|
|
194
|
+
*
|
|
195
|
+
* @typeParam T - Type of values from the source Observable
|
|
196
|
+
* @typeParam R - Type of values in the result Observable
|
|
197
|
+
* @param project - Function that maps a source value to an Observable
|
|
198
|
+
* @returns An operator function that maps and concatenates values
|
|
199
|
+
*/
|
|
200
|
+
export function concatMap(project) {
|
|
201
|
+
// concatMap is just mergeMap with concurrency = 1
|
|
202
|
+
return mergeMap(project, 1);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Transforms items into new streams, but cancels the previous stream when a new
|
|
206
|
+
* item arrives.
|
|
207
|
+
*
|
|
208
|
+
* Like an auto-cancelling search input, it only cares about the latest value
|
|
209
|
+
* and discards any pending work from previous values.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { pipe, switchMap, from } from "./helpers/mod.ts";
|
|
214
|
+
* import { of } from "../../observable.ts";
|
|
215
|
+
*
|
|
216
|
+
* // No direct Array equivalent, as it's about handling events over time.
|
|
217
|
+
*
|
|
218
|
+
* // Stream behavior for a search input
|
|
219
|
+
* const queryStream = from(["cat", "cats", "cats rul"]);
|
|
220
|
+
*
|
|
221
|
+
* const searchResultStream = pipe(
|
|
222
|
+
* queryStream,
|
|
223
|
+
* switchMap(query => of(`Results for "${query}"`))
|
|
224
|
+
* );
|
|
225
|
+
*
|
|
226
|
+
* // Assuming each query arrives before the last one "completes":
|
|
227
|
+
* // The first two searches for "cat" and "cats" would be cancelled.
|
|
228
|
+
* // The final output would only be: 'Results for "cats rul"'
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* ## Practical Use Case
|
|
232
|
+
*
|
|
233
|
+
* `switchMap` is essential for live search bars or any UI element that
|
|
234
|
+
* triggers frequent events. It ensures that only the results for the most
|
|
235
|
+
* recent event are processed, preventing outdated or out-of-order results.
|
|
236
|
+
*
|
|
237
|
+
* ## Key Insight
|
|
238
|
+
*
|
|
239
|
+
* `switchMap` is the operator of choice for handling rapid-fire events where
|
|
240
|
+
* only the latest matters. It prevents race conditions and keeps your UI
|
|
241
|
+
* responsive by cancelling stale, in-flight operations.
|
|
242
|
+
*
|
|
243
|
+
* @typeParam T - Type of values from the source Observable
|
|
244
|
+
* @typeParam R - Type of values in the result Observable
|
|
245
|
+
* @param project - Function that maps a source value to an Observable
|
|
246
|
+
* @returns An operator function that maps and switches between values
|
|
247
|
+
*/
|
|
248
|
+
export function switchMap(project) {
|
|
249
|
+
return createStatefulOperator({
|
|
250
|
+
name: "switchMap",
|
|
251
|
+
// Initialize state
|
|
252
|
+
createState: () => ({
|
|
253
|
+
currentController: null,
|
|
254
|
+
currentTask: null,
|
|
255
|
+
currentTaskToken: null,
|
|
256
|
+
sourceCompleted: false,
|
|
257
|
+
index: 0,
|
|
258
|
+
}),
|
|
259
|
+
// Process each incoming chunk
|
|
260
|
+
transform(chunk, state, controller) {
|
|
261
|
+
if (isObservableError(chunk)) {
|
|
262
|
+
// If the chunk is an error, we can immediately enqueue it
|
|
263
|
+
controller.enqueue(chunk);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Cancel any existing inner subscription
|
|
267
|
+
if (state.currentController) {
|
|
268
|
+
state.currentController.abort();
|
|
269
|
+
state.currentController = null;
|
|
270
|
+
}
|
|
271
|
+
let innerObservable;
|
|
272
|
+
try {
|
|
273
|
+
// Apply projection function to get inner Observable
|
|
274
|
+
innerObservable = project(chunk, state.index++);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
// Forward any errors from the projection function
|
|
278
|
+
controller.enqueue(ObservableError.from(err, "operator:stateful:switchMap:project", chunk));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Create a new abort controller for this inner subscription
|
|
282
|
+
const abortController = new AbortController();
|
|
283
|
+
state.currentController = abortController;
|
|
284
|
+
// Subscribe to the new inner Observable
|
|
285
|
+
const currentTaskToken = {};
|
|
286
|
+
const currentTask = (async () => {
|
|
287
|
+
const enqueueIfActive = (value) => {
|
|
288
|
+
if (abortController.signal.aborted ||
|
|
289
|
+
state.currentController !== abortController) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
controller.enqueue(value);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
abortController.abort();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
try {
|
|
300
|
+
const iterator = pull(innerObservable, { throwError: false })[Symbol.asyncIterator]();
|
|
301
|
+
while (!abortController.signal.aborted) {
|
|
302
|
+
const { value, done } = await iterator.next();
|
|
303
|
+
// If the controller is aborted or we're done, exit the loop
|
|
304
|
+
if (abortController.signal.aborted || done)
|
|
305
|
+
break;
|
|
306
|
+
// Forward the value
|
|
307
|
+
enqueueIfActive(value);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (!abortController.signal.aborted) {
|
|
312
|
+
enqueueIfActive(ObservableError.from(err, "operator:stateful:switchMap:innerObservable", chunk));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
if (state.currentTaskToken === currentTaskToken) {
|
|
317
|
+
state.currentTask = null;
|
|
318
|
+
state.currentTaskToken = null;
|
|
319
|
+
}
|
|
320
|
+
// Only handle completion if this is still the current controller
|
|
321
|
+
if (state.currentController === abortController) {
|
|
322
|
+
state.currentController = null;
|
|
323
|
+
// If source is completed and we have no active inner, we're done
|
|
324
|
+
// Stream will close naturally
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
})();
|
|
328
|
+
state.currentTask = currentTask;
|
|
329
|
+
state.currentTaskToken = currentTaskToken;
|
|
330
|
+
},
|
|
331
|
+
// Handle the end of the source stream
|
|
332
|
+
async flush(state) {
|
|
333
|
+
// Mark the source as completed
|
|
334
|
+
state.sourceCompleted = true;
|
|
335
|
+
if (state.currentTask) {
|
|
336
|
+
await state.currentTask;
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
// Handle cancellation
|
|
340
|
+
cancel(state) {
|
|
341
|
+
// Cancel the current inner subscription
|
|
342
|
+
if (state.currentController) {
|
|
343
|
+
state.currentController.abort();
|
|
344
|
+
state.currentController = null;
|
|
345
|
+
}
|
|
346
|
+
state.currentTask = null;
|
|
347
|
+
state.currentTaskToken = null;
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predicate and decision-oriented operators for Observable streams.
|
|
3
|
+
*
|
|
4
|
+
* This entrypoint exports the operators that answer questions about a stream or
|
|
5
|
+
* gate values based on a condition. These are the Observable equivalents of
|
|
6
|
+
* array helpers such as `every()`, `some()`, and `find()`, plus utilities that
|
|
7
|
+
* stop early once a decision has been reached.
|
|
8
|
+
*
|
|
9
|
+
* Reach for this module when you care about whether a stream contains a match,
|
|
10
|
+
* whether every value passes a rule, or when processing should stop as soon as
|
|
11
|
+
* the answer is known.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import "../../_dnt.polyfills.js";
|
|
16
|
+
import type { ExcludeError, Operator } from "../_types.js";
|
|
17
|
+
import type { ObservableError } from "../../error.js";
|
|
18
|
+
/**
|
|
19
|
+
* Checks if every item in the stream passes a test.
|
|
20
|
+
*
|
|
21
|
+
* Like `Array.prototype.every()`, it stops and returns `false` on the first
|
|
22
|
+
* failure. If the stream completes without any failures, it returns `true`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { pipe, every, from } from "./helpers/mod.ts";
|
|
27
|
+
*
|
|
28
|
+
* // Array behavior
|
|
29
|
+
* const allEven = [2, 4, 6].every(n => n % 2 === 0); // true
|
|
30
|
+
* const someOdd = [2, 4, 5].every(n => n % 2 === 0); // false
|
|
31
|
+
*
|
|
32
|
+
* // Stream behavior
|
|
33
|
+
* const allEvenStream = from([2, 4, 6]);
|
|
34
|
+
* const result1 = await pipe(allEvenStream, every(n => n % 2 === 0)).toPromise(); // true
|
|
35
|
+
*
|
|
36
|
+
* const someOddStream = from([2, 4, 5]);
|
|
37
|
+
* const result2 = await pipe(someOddStream, every(n => n % 2 === 0)).toPromise(); // false
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* ## Practical Use Case
|
|
41
|
+
*
|
|
42
|
+
* Use `every` to verify that all items in a stream meet a certain condition,
|
|
43
|
+
* such as ensuring all uploaded files are of the correct type or that all
|
|
44
|
+
* API responses were successful.
|
|
45
|
+
*
|
|
46
|
+
* ## Key Insight
|
|
47
|
+
*
|
|
48
|
+
* `every` provides a definitive boolean answer for the entire stream, making
|
|
49
|
+
* it a final, summary operation.
|
|
50
|
+
*
|
|
51
|
+
* @typeParam T - Type of values from the source stream
|
|
52
|
+
* @param predicate - Function to test each value
|
|
53
|
+
* @returns A stream operator that tests all values
|
|
54
|
+
*/
|
|
55
|
+
export declare function every<T>(predicate: (value: ExcludeError<T>, index: number) => boolean): Operator<T | ObservableError, boolean | ObservableError>;
|
|
56
|
+
/**
|
|
57
|
+
* Checks if at least one item in the stream passes a test.
|
|
58
|
+
*
|
|
59
|
+
* Like `Array.prototype.some()`, it stops and returns `true` on the first
|
|
60
|
+
* success. If the stream completes without any successes, it returns `false`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { pipe, some, from } from "./helpers/mod.ts";
|
|
65
|
+
*
|
|
66
|
+
* // Array behavior
|
|
67
|
+
* const hasEven = [1, 3, 4].some(n => n % 2 === 0); // true
|
|
68
|
+
* const noEven = [1, 3, 5].some(n => n % 2 === 0); // false
|
|
69
|
+
*
|
|
70
|
+
* // Stream behavior
|
|
71
|
+
* const hasEvenStream = from([1, 3, 4]);
|
|
72
|
+
* const result1 = await pipe(hasEvenStream, some(n => n % 2 === 0)).toPromise(); // true
|
|
73
|
+
*
|
|
74
|
+
* const noEvenStream = from([1, 3, 5]);
|
|
75
|
+
* const result2 = await pipe(noEvenStream, some(n => n % 2 === 0)).toPromise(); // false
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* ## Practical Use Case
|
|
79
|
+
*
|
|
80
|
+
* Use `some` to quickly determine if a condition is met by any item in a
|
|
81
|
+
* stream, such as checking for the existence of a specific user permission or
|
|
82
|
+
* detecting if any item in a batch has an error.
|
|
83
|
+
*
|
|
84
|
+
* ## Key Insight
|
|
85
|
+
*
|
|
86
|
+
* `some` is an efficient way to get a "yes" or "no" answer from a stream
|
|
87
|
+
* without processing all the items.
|
|
88
|
+
*
|
|
89
|
+
* @typeParam T - Type of values from the source stream
|
|
90
|
+
* @param predicate - Function to test each value
|
|
91
|
+
* @returns A stream operator that tests for any matching value
|
|
92
|
+
*/
|
|
93
|
+
export declare function some<T>(predicate: (value: ExcludeError<T>, index: number) => boolean): Operator<T | ObservableError, boolean | ObservableError>;
|
|
94
|
+
/**
|
|
95
|
+
* Finds the first item in the stream that passes a test.
|
|
96
|
+
*
|
|
97
|
+
* Like `Array.prototype.find()`, it stops and emits the first matching item,
|
|
98
|
+
* then immediately completes the stream.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { pipe, find, from } from "./helpers/mod.ts";
|
|
103
|
+
*
|
|
104
|
+
* // Array behavior
|
|
105
|
+
* const firstEven = [1, 3, 4, 6].find(n => n % 2 === 0); // 4
|
|
106
|
+
*
|
|
107
|
+
* // Stream behavior
|
|
108
|
+
* const numberStream = from([1, 3, 4, 6]);
|
|
109
|
+
* const result = await pipe(numberStream, find(n => n % 2 === 0)).toPromise(); // 4
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* ## Practical Use Case
|
|
113
|
+
*
|
|
114
|
+
* Use `find` to locate a specific record or event in a stream without needing
|
|
115
|
+
* to process the entire dataset, such as finding the first available time slot
|
|
116
|
+
* or the first user to log in.
|
|
117
|
+
*
|
|
118
|
+
* ## Key Insight
|
|
119
|
+
*
|
|
120
|
+
* `find` is an efficient shortcut to get the first item you care about from a
|
|
121
|
+
* potentially long stream.
|
|
122
|
+
*
|
|
123
|
+
* @typeParam T - Type of values from the source stream
|
|
124
|
+
* @param predicate - Function to test each value
|
|
125
|
+
* @returns A stream operator that finds the first matching value
|
|
126
|
+
*/
|
|
127
|
+
export declare function find<T>(predicate: (value: ExcludeError<T>, index: number) => boolean): Operator<T | ObservableError, T | ObservableError>;
|
|
128
|
+
/**
|
|
129
|
+
* Removes duplicate values from the stream, keeping only the first occurrence.
|
|
130
|
+
*
|
|
131
|
+
* Like creating a `new Set()` from an array, but for async streams. You can
|
|
132
|
+
* provide a `keySelector` to determine uniqueness based on an object property.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { pipe, unique, from } from "./helpers/mod.ts";
|
|
137
|
+
*
|
|
138
|
+
* // Array behavior
|
|
139
|
+
* const uniqueNumbers = [...new Set([1, 2, 2, 3, 1])]; // [1, 2, 3]
|
|
140
|
+
*
|
|
141
|
+
* // Stream behavior
|
|
142
|
+
* const numberStream = from([1, 2, 2, 3, 1]);
|
|
143
|
+
* const uniqueStream = pipe(numberStream, unique()); // Emits 1, 2, 3
|
|
144
|
+
*
|
|
145
|
+
* // With a key selector
|
|
146
|
+
* const users = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 1, name: 'C' }];
|
|
147
|
+
* const userStream = from(users);
|
|
148
|
+
* const uniqueUserStream = pipe(userStream, unique(user => user.id)); // Emits { id: 1, name: 'A' }, { id: 2, name: 'B' }
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* ## Practical Use Case
|
|
152
|
+
*
|
|
153
|
+
* Use `unique` to de-duplicate a stream of events or data, such as processing
|
|
154
|
+
* a list of user IDs where some may appear multiple times, or handling event
|
|
155
|
+
* streams that might emit the same event more than once.
|
|
156
|
+
*
|
|
157
|
+
* ## Key Insight
|
|
158
|
+
*
|
|
159
|
+
* `unique` simplifies de-duplication in asynchronous pipelines, ensuring that
|
|
160
|
+
* downstream operations only run once for each unique item.
|
|
161
|
+
*
|
|
162
|
+
* @typeParam T The type of items in the stream.
|
|
163
|
+
* @typeParam K The type of the key used for uniqueness checks.
|
|
164
|
+
* @param keySelector An optional function to extract a key for uniqueness comparison.
|
|
165
|
+
* @returns An operator that filters out duplicate values.
|
|
166
|
+
*/
|
|
167
|
+
export declare function unique<T, K = T>(keySelector?: (value: ExcludeError<T>) => K): Operator<T | ObservableError, ExcludeError<T> | ObservableError>;
|
|
168
|
+
/**
|
|
169
|
+
* Emits an item only if it is different from the previous one.
|
|
170
|
+
*
|
|
171
|
+
* This is useful for streams where values can be emitted repeatedly, but you
|
|
172
|
+
* only care about the changes.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* import { pipe, changed, from } from "./helpers/mod.ts";
|
|
177
|
+
*
|
|
178
|
+
* // No direct Array equivalent, as it depends on sequence.
|
|
179
|
+
*
|
|
180
|
+
* // Stream behavior
|
|
181
|
+
* const valueStream = from([1, 1, 2, 2, 2, 1, 3]);
|
|
182
|
+
* const changedStream = pipe(valueStream, changed()); // Emits 1, 2, 1, 3
|
|
183
|
+
*
|
|
184
|
+
* // With a key selector
|
|
185
|
+
* const userStream = from([
|
|
186
|
+
* { id: 1, status: 'active' },
|
|
187
|
+
* { id: 1, status: 'active' },
|
|
188
|
+
* { id: 1, status: 'inactive' }
|
|
189
|
+
* ]);
|
|
190
|
+
* const statusChangeStream = pipe(userStream, changed(user => user.status));
|
|
191
|
+
* // Emits { id: 1, status: 'active' }, { id: 1, status: 'inactive' }
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* ## Practical Use Case
|
|
195
|
+
*
|
|
196
|
+
* Use `changed` to monitor a stream of state updates and only react when the
|
|
197
|
+
* state actually changes. This is common in UI programming for tracking user
|
|
198
|
+
* input or state management.
|
|
199
|
+
*
|
|
200
|
+
* ## Key Insight
|
|
201
|
+
*
|
|
202
|
+
* `changed` filters out noise from repetitive values, allowing you to focus
|
|
203
|
+
* on the moments when something actually changes.
|
|
204
|
+
*
|
|
205
|
+
* @typeParam T The type of items in the stream.
|
|
206
|
+
* @typeParam K The type of the key used for comparison.
|
|
207
|
+
* @param keySelector An optional function to extract a key for comparison.
|
|
208
|
+
* @param compare An optional function to compare two keys for equality.
|
|
209
|
+
* @returns An operator that filters out consecutive duplicate values.
|
|
210
|
+
*/
|
|
211
|
+
//# sourceMappingURL=conditional.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditional.d.ts","sourceRoot":"","sources":["../../../src/helpers/operations/conditional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,KAAK,CAAC,CAAC,EACrB,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAC5D,QAAQ,CAAC,CAAC,GAAG,eAAe,EAAE,OAAO,GAAG,eAAe,CAAC,CA2B1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAC5D,QAAQ,CAAC,CAAC,GAAG,eAAe,EAAE,OAAO,GAAG,eAAe,CAAC,CA2B1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAC5D,QAAQ,CAAC,CAAC,GAAG,eAAe,EAAE,CAAC,GAAG,eAAe,CAAC,CAcpD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAC7B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,GAC1C,QAAQ,CAAC,CAAC,GAAG,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAqBlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG"}
|