@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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +578 -0
  3. package/esm/_dnt.polyfills.d.ts +20 -0
  4. package/esm/_dnt.polyfills.d.ts.map +1 -0
  5. package/esm/_dnt.polyfills.js +12 -0
  6. package/esm/_spec.d.ts +260 -0
  7. package/esm/_spec.d.ts.map +1 -0
  8. package/esm/_spec.js +1 -0
  9. package/esm/_types.d.ts +141 -0
  10. package/esm/_types.d.ts.map +1 -0
  11. package/esm/_types.js +20 -0
  12. package/esm/error.d.ts +331 -0
  13. package/esm/error.d.ts.map +1 -0
  14. package/esm/error.js +408 -0
  15. package/esm/events.d.ts +320 -0
  16. package/esm/events.d.ts.map +1 -0
  17. package/esm/events.js +451 -0
  18. package/esm/helpers/_types.d.ts +188 -0
  19. package/esm/helpers/_types.d.ts.map +1 -0
  20. package/esm/helpers/_types.js +1 -0
  21. package/esm/helpers/mod.d.ts +90 -0
  22. package/esm/helpers/mod.d.ts.map +1 -0
  23. package/esm/helpers/mod.js +90 -0
  24. package/esm/helpers/operations/batch.d.ts +109 -0
  25. package/esm/helpers/operations/batch.d.ts.map +1 -0
  26. package/esm/helpers/operations/batch.js +140 -0
  27. package/esm/helpers/operations/combination.d.ts +162 -0
  28. package/esm/helpers/operations/combination.d.ts.map +1 -0
  29. package/esm/helpers/operations/combination.js +350 -0
  30. package/esm/helpers/operations/conditional.d.ts +211 -0
  31. package/esm/helpers/operations/conditional.d.ts.map +1 -0
  32. package/esm/helpers/operations/conditional.js +280 -0
  33. package/esm/helpers/operations/core.d.ts +198 -0
  34. package/esm/helpers/operations/core.d.ts.map +1 -0
  35. package/esm/helpers/operations/core.js +264 -0
  36. package/esm/helpers/operations/errors.d.ts +277 -0
  37. package/esm/helpers/operations/errors.d.ts.map +1 -0
  38. package/esm/helpers/operations/errors.js +378 -0
  39. package/esm/helpers/operations/mod.d.ts +26 -0
  40. package/esm/helpers/operations/mod.d.ts.map +1 -0
  41. package/esm/helpers/operations/mod.js +25 -0
  42. package/esm/helpers/operations/timing.d.ts +206 -0
  43. package/esm/helpers/operations/timing.d.ts.map +1 -0
  44. package/esm/helpers/operations/timing.js +457 -0
  45. package/esm/helpers/operators.d.ts +520 -0
  46. package/esm/helpers/operators.d.ts.map +1 -0
  47. package/esm/helpers/operators.js +563 -0
  48. package/esm/helpers/pipe.d.ts +118 -0
  49. package/esm/helpers/pipe.d.ts.map +1 -0
  50. package/esm/helpers/pipe.js +129 -0
  51. package/esm/helpers/utils.d.ts +142 -0
  52. package/esm/helpers/utils.d.ts.map +1 -0
  53. package/esm/helpers/utils.js +193 -0
  54. package/esm/mod.d.ts +863 -0
  55. package/esm/mod.d.ts.map +1 -0
  56. package/esm/mod.js +861 -0
  57. package/esm/observable.d.ts +1610 -0
  58. package/esm/observable.d.ts.map +1 -0
  59. package/esm/observable.js +1970 -0
  60. package/esm/package.json +3 -0
  61. package/esm/queue.d.ts +201 -0
  62. package/esm/queue.d.ts.map +1 -0
  63. package/esm/queue.js +273 -0
  64. package/esm/symbol.d.ts +60 -0
  65. package/esm/symbol.d.ts.map +1 -0
  66. package/esm/symbol.js +132 -0
  67. package/package.json +96 -0
  68. package/script/_dnt.polyfills.d.ts +20 -0
  69. package/script/_dnt.polyfills.d.ts.map +1 -0
  70. package/script/_dnt.polyfills.js +13 -0
  71. package/script/_spec.d.ts +260 -0
  72. package/script/_spec.d.ts.map +1 -0
  73. package/script/_spec.js +2 -0
  74. package/script/_types.d.ts +141 -0
  75. package/script/_types.d.ts.map +1 -0
  76. package/script/_types.js +22 -0
  77. package/script/error.d.ts +331 -0
  78. package/script/error.d.ts.map +1 -0
  79. package/script/error.js +414 -0
  80. package/script/events.d.ts +320 -0
  81. package/script/events.d.ts.map +1 -0
  82. package/script/events.js +458 -0
  83. package/script/helpers/_types.d.ts +188 -0
  84. package/script/helpers/_types.d.ts.map +1 -0
  85. package/script/helpers/_types.js +2 -0
  86. package/script/helpers/mod.d.ts +90 -0
  87. package/script/helpers/mod.d.ts.map +1 -0
  88. package/script/helpers/mod.js +106 -0
  89. package/script/helpers/operations/batch.d.ts +109 -0
  90. package/script/helpers/operations/batch.d.ts.map +1 -0
  91. package/script/helpers/operations/batch.js +144 -0
  92. package/script/helpers/operations/combination.d.ts +162 -0
  93. package/script/helpers/operations/combination.d.ts.map +1 -0
  94. package/script/helpers/operations/combination.js +355 -0
  95. package/script/helpers/operations/conditional.d.ts +211 -0
  96. package/script/helpers/operations/conditional.d.ts.map +1 -0
  97. package/script/helpers/operations/conditional.js +286 -0
  98. package/script/helpers/operations/core.d.ts +198 -0
  99. package/script/helpers/operations/core.d.ts.map +1 -0
  100. package/script/helpers/operations/core.js +272 -0
  101. package/script/helpers/operations/errors.d.ts +277 -0
  102. package/script/helpers/operations/errors.d.ts.map +1 -0
  103. package/script/helpers/operations/errors.js +387 -0
  104. package/script/helpers/operations/mod.d.ts +26 -0
  105. package/script/helpers/operations/mod.d.ts.map +1 -0
  106. package/script/helpers/operations/mod.js +41 -0
  107. package/script/helpers/operations/timing.d.ts +206 -0
  108. package/script/helpers/operations/timing.d.ts.map +1 -0
  109. package/script/helpers/operations/timing.js +464 -0
  110. package/script/helpers/operators.d.ts +520 -0
  111. package/script/helpers/operators.d.ts.map +1 -0
  112. package/script/helpers/operators.js +570 -0
  113. package/script/helpers/pipe.d.ts +118 -0
  114. package/script/helpers/pipe.d.ts.map +1 -0
  115. package/script/helpers/pipe.js +132 -0
  116. package/script/helpers/utils.d.ts +142 -0
  117. package/script/helpers/utils.d.ts.map +1 -0
  118. package/script/helpers/utils.js +200 -0
  119. package/script/mod.d.ts +863 -0
  120. package/script/mod.d.ts.map +1 -0
  121. package/script/mod.js +877 -0
  122. package/script/observable.d.ts +1610 -0
  123. package/script/observable.d.ts.map +1 -0
  124. package/script/observable.js +1984 -0
  125. package/script/package.json +3 -0
  126. package/script/queue.d.ts +201 -0
  127. package/script/queue.d.ts.map +1 -0
  128. package/script/queue.js +286 -0
  129. package/script/symbol.d.ts +60 -0
  130. package/script/symbol.d.ts.map +1 -0
  131. package/script/symbol.js +135 -0
package/esm/error.js ADDED
@@ -0,0 +1,408 @@
1
+ // @filename: error.ts
2
+ /**
3
+ * Error primitives and guards for Observable pipelines.
4
+ *
5
+ * This entrypoint explains the error values that travel through this library
6
+ * when an operator uses pass-through error handling. It exports the
7
+ * `ObservableError` class plus helper functions for narrowing and asserting
8
+ * those wrapped failures without losing the original error object, stack, or
9
+ * operator context.
10
+ *
11
+ * Reach for this module when you want to inspect failures as data, recover from
12
+ * an upstream step without throwing away buffered values, or surface richer
13
+ * debugging information than a plain `Error` can carry on its own.
14
+ *
15
+ * @module
16
+ */
17
+ import "./_dnt.polyfills.js";
18
+ /**
19
+ * Represents an error that occurred during Observable operations,
20
+ * with the ability to aggregate multiple underlying errors.
21
+ *
22
+ * This class extends AggregateError to provide additional context about
23
+ * where and how errors occurred in an Observable pipeline. It can collect
24
+ * multiple errors that occur during a chain of operations while preserving
25
+ * the contextual information about each error.
26
+ *
27
+ * In addition, this class solves a crucial problem with error handling in ReadableStreams. When we call
28
+ * `controller.error()` on a ReadableStream, it immediately puts the stream in an errored state,
29
+ * which can cause values emitted before the error to be lost. By wrapping errors as special
30
+ * values that flow through the normal value channel, we ensure all values emitted before an
31
+ * error are properly processed.
32
+ *
33
+ * Key features:
34
+ * - Tracks which operator caused the error
35
+ * - Captures the value being processed when the error occurred
36
+ * - Aggregates multiple errors from a pipeline
37
+ * - Preserves the original error objects
38
+ * - Builds an error chain showing the full path of error propagation
39
+ */
40
+ export class ObservableError extends AggregateError {
41
+ /**
42
+ * Creates a new ObservableError.
43
+ *
44
+ * @param errors - The error(s) that caused this error
45
+ * @param message - The error message
46
+ * @param options - Additional error context
47
+ */
48
+ constructor(errors, message, options) {
49
+ // Normalize errors to an array of Error objects
50
+ const errorArray = Array.isArray(errors) ? errors : [errors];
51
+ const normalizedErrors = errorArray.map((err) => err instanceof Error ? err : new Error(String(err)));
52
+ super(normalizedErrors, message, { cause: options?.cause });
53
+ /** The operator where the error occurred */
54
+ Object.defineProperty(this, "operator", {
55
+ enumerable: true,
56
+ configurable: true,
57
+ writable: true,
58
+ value: void 0
59
+ });
60
+ /** The value being processed when the error occurred */
61
+ Object.defineProperty(this, "value", {
62
+ enumerable: true,
63
+ configurable: true,
64
+ writable: true,
65
+ value: void 0
66
+ });
67
+ /** Helpful potential fixes for errors */
68
+ Object.defineProperty(this, "tip", {
69
+ enumerable: true,
70
+ configurable: true,
71
+ writable: true,
72
+ value: void 0
73
+ });
74
+ this.name = "ObservableError";
75
+ this.operator = options?.operator;
76
+ this.value = options?.value;
77
+ this.tip = options?.tip;
78
+ }
79
+ /**
80
+ * Returns a string representation of the error including the operator
81
+ * and value context if available.
82
+ */
83
+ toString() {
84
+ let result = `${this.name}: ${this.message}`;
85
+ if (this.operator) {
86
+ result += `\n in operator: ${this.operator}`;
87
+ }
88
+ if (this.value !== undefined) {
89
+ const valueStr = typeof this.value === "object"
90
+ ? JSON.stringify(this.value).slice(0, 100) // Truncate long objects
91
+ : String(this.value);
92
+ result += `\n processing value: ${valueStr}`;
93
+ }
94
+ if (this.errors.length > 0) {
95
+ result += "\n with errors:";
96
+ this.errors.forEach((err, i) => {
97
+ result += `\n ${i + 1}) ${err}`;
98
+ });
99
+ }
100
+ if (this.tip) {
101
+ result += `\n tip: ${this.tip}`;
102
+ }
103
+ return result;
104
+ }
105
+ /**
106
+ * Creates an ObservableError from any error that occurs during
107
+ * operator execution.
108
+ *
109
+ * @param error - The original error
110
+ * @param operator - The operator name
111
+ * @param value - The value being processed
112
+ * @returns An ObservableError
113
+ */
114
+ static from(error, operator, value, tip) {
115
+ if (error instanceof ObservableError) {
116
+ // If it's already an ObservableError, add context if not present
117
+ if (!error.operator && operator) {
118
+ return new ObservableError(error.errors, error.message, {
119
+ operator,
120
+ value: error.value || value,
121
+ cause: error.cause,
122
+ tip: error.tip,
123
+ });
124
+ }
125
+ return error;
126
+ }
127
+ // Create a new ObservableError
128
+ return new ObservableError(error, error instanceof Error ? error.message : String(error), { operator, value, cause: error, tip });
129
+ }
130
+ }
131
+ /**
132
+ * Asserts that a value is not an ObservableError and narrows the TypeScript type.
133
+ *
134
+ * This function acts as a TypeScript assertion function that:
135
+ * 1. **Type Narrowing**: If the function returns normally, TypeScript knows the value is definitely T (not T | ObservableError)
136
+ * 2. **Error Handling**: If the value is an ObservableError, either delegates to observer.error or throws
137
+ * 3. **Never Returns on Error**: When value is ObservableError, this function never returns normally
138
+ *
139
+ * **Key Behavior Changes**:
140
+ * - Now uses TypeScript's `asserts value is T` for proper type narrowing
141
+ * - When observer handles error, the function still doesn't return normally (assertion still fails)
142
+ * - Only returns normally when value is definitely not an ObservableError
143
+ *
144
+ * **Intent**: Provide type-safe error checking with automatic TypeScript type narrowing.
145
+ *
146
+ * **Usage Patterns**:
147
+ * ```typescript
148
+ * // Type narrowing in operator results
149
+ * const result: string | ObservableError = someOperation();
150
+ * assertObservableError(result); // Throws if error
151
+ * // TypeScript now knows result is string, not string | ObservableError
152
+ * console.log(result.toUpperCase()); // ✅ No type error
153
+ *
154
+ * // With observer error handling
155
+ * const observer = { error: err => console.error('Error:', err) };
156
+ * assertObservableError(result, observer);
157
+ * // Still throws/doesn't return normally on error, but observer is notified first
158
+ *
159
+ * // In subscribe callbacks
160
+ * observable.subscribe({
161
+ * next(value) { // value is T | ObservableError
162
+ * assertObservableError(value);
163
+ * // value is now narrowed to T
164
+ * processCleanValue(value);
165
+ * }
166
+ * });
167
+ * ```
168
+ *
169
+ * @template T - The expected type of the value when it's not an error
170
+ * @param value - The value to check, which may be either T or ObservableError
171
+ * @param obs - Optional observer that may contain an error handler function
172
+ *
173
+ * @throws {ObservableError} When value is an ObservableError (after notifying observer if provided)
174
+ *
175
+ * @example Basic type narrowing
176
+ * ```typescript
177
+ * const mixed: string | ObservableError = getValue();
178
+ * assertObservableError(mixed); // Throws if ObservableError
179
+ * console.log(mixed.length); // ✅ TypeScript knows mixed is string
180
+ * ```
181
+ *
182
+ * @example With error observer
183
+ * ```typescript
184
+ * const observer = {
185
+ * error: (err) => analytics.track('error', { message: err.message })
186
+ * };
187
+ *
188
+ * const mixed: User | ObservableError = fetchUser();
189
+ * assertObservableError(mixed, observer); // Observer notified, then throws
190
+ * console.log(mixed.name); // ✅ TypeScript knows mixed is User
191
+ * ```
192
+ *
193
+ * @example In operator pipeline
194
+ * ```typescript
195
+ * import { pipe, map, tap } from './helpers/mod.ts';
196
+ *
197
+ * pipe(
198
+ * source,
199
+ * map(x => processX(x)), // Returns T | ObservableError
200
+ * tap(result => {
201
+ * assertObservableError(result); // Type narrows from T | ObservableError to T
202
+ * sendAnalytics(result); // ✅ result is definitely T
203
+ * })
204
+ * )
205
+ * ```
206
+ */
207
+ export function assertObservableError(value, obs) {
208
+ if (value instanceof ObservableError) {
209
+ // Notify observer first if available
210
+ if (typeof obs?.error === "function") {
211
+ try {
212
+ obs.error(value);
213
+ }
214
+ catch (observerError) {
215
+ // If observer throws, still throw the original error
216
+ // but log the observer error to avoid losing it
217
+ console.error("Observer error handler threw:", observerError);
218
+ }
219
+ }
220
+ // Always throw when value is ObservableError
221
+ // This ensures the function never returns normally for errors
222
+ throw value;
223
+ }
224
+ // If we reach here, value is definitely T (not T | ObservableError)
225
+ // TypeScript assertion function automatically narrows the type
226
+ }
227
+ /**
228
+ * Checks if a value is an ObservableError without throwing exceptions.
229
+ *
230
+ * When working with Observable pipelines, you often receive values that could be either successful
231
+ * results or errors. This creates a dilemma: how do you safely check what you got without risking
232
+ * crashes or poor performance? That's exactly what this function solves.
233
+ *
234
+ * **Why This Function Exists**:
235
+ *
236
+ * Imagine you're processing a stream of data where some items might be errors. Without a proper
237
+ * way to distinguish between good data and errors, you'd have to either:
238
+ * - Risk calling methods on errors (which crashes your app)
239
+ * - Write repetitive `instanceof` checks everywhere (which clutters your code)
240
+ * - Use try/catch blocks for control flow (which hurts performance)
241
+ *
242
+ * This function eliminates all those problems by giving you a clean, fast way to ask:
243
+ * "Is this thing an error?" If yes, you can handle it appropriately. If no, you can
244
+ * safely process it as valid data.
245
+ *
246
+ * **How It Fits With Other Functions**:
247
+ *
248
+ * Think of this as the gentle cousin of `assertObservableError()`. While `assertObservableError()`
249
+ * says "this better not be an error or I'm throwing an exception," this function politely asks
250
+ * "excuse me, are you an error?" and gives you a yes/no answer.
251
+ *
252
+ * This makes it perfect for situations where you want to handle both success and error cases
253
+ * gracefully, rather than just crashing when something goes wrong.
254
+ *
255
+ * **Performance Story**:
256
+ *
257
+ * Under the hood, this function uses a simple `instanceof` check. Now, you might wonder:
258
+ * "Why not just use `instanceof` directly?" The answer lies in convenience and consistency.
259
+ *
260
+ * Here's what actually happens performance-wise:
261
+ * - This function IS an `instanceof` check (no performance difference there)
262
+ * - Modern JavaScript engines are extremely good at optimizing `instanceof`
263
+ * - The real performance win comes from avoiding try/catch blocks for control flow
264
+ * - When the engine inlines this function, the overhead becomes virtually zero
265
+ *
266
+ * **Performance Reality Check**:
267
+ * - `isObservableError(value)` and `value instanceof ObservableError` perform identically
268
+ * - Both are fast enough that you'll never notice the difference in real applications
269
+ * - The real performance benefit is architectural: avoiding exceptions for normal control flow
270
+ * - Exception throwing/catching can be 10-100x slower, but that's comparing apples to oranges
271
+ *
272
+ * In practice, use this function because it's clearer and more consistent with the library's
273
+ * design patterns, not because of micro-optimizations.
274
+ *
275
+ * **Common Ways to Use This Function**:
276
+ *
277
+ * Let's walk through the most common scenarios where this function shines:
278
+ *
279
+ * ```typescript
280
+ * // Scenario 1: Branching logic in data processing
281
+ * function processItem<T>(item: T | ObservableError) {
282
+ * if (isObservableError(item)) {
283
+ * // TypeScript now knows item is an ObservableError
284
+ * console.error('Something went wrong:', item.message);
285
+ * return null; // or however you want to handle errors
286
+ * }
287
+ *
288
+ * // TypeScript now knows item is T - no more type errors!
289
+ * return transformData(item);
290
+ * }
291
+ *
292
+ * // Scenario 2: Filtering out errors from a collection
293
+ * // Note: You need proper type predicates for TypeScript to understand
294
+ * const cleanData = mixedResults.filter((item): item is T => !isObservableError(item));
295
+ * // cleanData is now properly typed as T[]
296
+ *
297
+ * // Scenario 3: Separating errors from successes
298
+ * const errors = results.filter(isObservableError);
299
+ * const successes = results.filter((item): item is T => !isObservableError(item));
300
+ * // Now you can handle each group with proper typing
301
+ *
302
+ * // Scenario 4: Early exit optimization
303
+ * function expensiveCalculation<T, U>(input: T | ObservableError): U | ObservableError {
304
+ * if (isObservableError(input)) {
305
+ * return input; // Don't waste time processing errors
306
+ * }
307
+ *
308
+ * // Only do expensive work on valid data
309
+ * return performComplexOperation(input);
310
+ * }
311
+ * ```
312
+ *
313
+ * **What Makes This Function Safe**:
314
+ *
315
+ * Unlike functions that might throw exceptions, this one is designed to never fail.
316
+ * It gracefully handles all the weird edge cases you might encounter:
317
+ * - Null or undefined values? Returns false (they're not errors)
318
+ * - Numbers, strings, or other primitives? Returns false (can't be ObservableErrors)
319
+ * - Objects that aren't ObservableErrors? Returns false (not what we're looking for)
320
+ * - Subclasses of ObservableError? Returns true (proper inheritance support)
321
+ *
322
+ * This means you can safely call it on anything without worrying about crashes.
323
+ *
324
+ * **When to Use This vs Other Options**:
325
+ *
326
+ * Choose `isObservableError()` when:
327
+ * - You want to handle both error and success cases in your code
328
+ * - You're filtering or sorting mixed arrays of results and errors
329
+ * - You're building conditional logic that branches based on error state
330
+ * - You want to avoid exceptions and prefer explicit error handling
331
+ *
332
+ * Choose `assertObservableError()` instead when:
333
+ * - You expect the value to NOT be an error, and want to crash if it is
334
+ * - You're in a context where errors should stop processing immediately
335
+ * - You want TypeScript to automatically narrow types via assertion
336
+ *
337
+ * @template T - The expected type for successful (non-error) values
338
+ * @param value - Any value that might or might not be an ObservableError
339
+ * @returns true if the value is an ObservableError, false otherwise
340
+ *
341
+ * @example Simple error checking
342
+ * ```typescript
343
+ * const result: string | ObservableError = fetchData();
344
+ *
345
+ * if (isObservableError(result)) {
346
+ * console.error('Oops, something went wrong:', result.message);
347
+ * // result is typed as ObservableError here
348
+ * return;
349
+ * }
350
+ *
351
+ * // result is typed as string here
352
+ * console.log('Success! Got:', result.toUpperCase());
353
+ * ```
354
+ *
355
+ * @example Filtering errors from a list
356
+ * ```typescript
357
+ * const mixedResults: (User | ObservableError)[] = await fetchAllUsers();
358
+ *
359
+ * // Get only the successful results (with proper type predicate)
360
+ * const validUsers = mixedResults.filter((item): item is User => !isObservableError(item));
361
+ * // validUsers is now correctly typed as User[]
362
+ *
363
+ * // Get only the errors
364
+ * const errors = mixedResults.filter(isObservableError);
365
+ * // errors is now typed as ObservableError[]
366
+ *
367
+ * // Process each group separately
368
+ * if (errors.length > 0) {
369
+ * console.error(`Found ${errors.length} errors:`, errors);
370
+ * }
371
+ * console.log(`Processing ${validUsers.length} valid users`);
372
+ * ```
373
+ *
374
+ * @example Building error-resilient operators
375
+ * ```typescript
376
+ * import { pipe } from './helpers/mod.ts';
377
+ *
378
+ * function safeMap<T, U>(transform: (value: T) => U) {
379
+ * return (source: Observable<T | ObservableError>) => {
380
+ * return pipe(
381
+ * source,
382
+ * map(value => {
383
+ * // Skip processing errors - just pass them through
384
+ * if (isObservableError(value)) return value;
385
+ *
386
+ * // Only transform valid values
387
+ * try {
388
+ * return transform(value);
389
+ * } catch (error) {
390
+ * return ObservableError.from(error, 'safeMap', value);
391
+ * }
392
+ * })
393
+ * );
394
+ * };
395
+ * }
396
+ *
397
+ * // Usage
398
+ * const result = pipe(
399
+ * mixedDataStream,
400
+ * safeMap(user => user.name.toUpperCase())
401
+ * );
402
+ * ```
403
+ */
404
+ export function isObservableError(value) {
405
+ // This is just a straightforward instanceof check
406
+ // Modern JavaScript engines optimize this extremely well
407
+ return value instanceof ObservableError;
408
+ }