@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/script/mod.js ADDED
@@ -0,0 +1,877 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * A **spec-faithful** yet ergonomic TC39-inspired Observable implementation with
19
+ * detailed TSDocs and examples.
20
+ *
21
+ * Observables are a **push‑based stream abstraction** for events, data, and long‑running
22
+ * operations.
23
+ *
24
+ * If you've ever built a web app, you know the pain: user clicks, API responses, WebSocket messages,
25
+ * timers, file uploads, they all arrive at different times and need different handling. Before Observables,
26
+ * you'd end up with a mess of callbacks, Promise chains, event listeners, and async/await scattered
27
+ * throughout your code.
28
+ *
29
+ * **Observables solve this by giving you one consistent way to handle all async data.**
30
+ *
31
+ * Think of it as a **multi‑value Promise** that keeps sending values until you tell it to stop.
32
+ * Where a Promise gives you one value eventually, an Observable can give you many values over time,
33
+ * mouse clicks, search results, chat messages, sensor readings. And just like Promises have
34
+ * `.then()` and `.catch()`, Observables have operators like `map()`, `filter()`, and `debounce()`
35
+ * to transform data as it flows.
36
+ *
37
+ * ## Why This Exists
38
+ * Apps juggle many async sources, mouse clicks, HTTP requests, timers,
39
+ * WebSockets, file watchers. Before Observables you glued those together with a
40
+ * mish‑mash of callbacks, Promises, `EventTarget`s and async iterators, each
41
+ * with different rules for cleanup and error handling. **Observables give you
42
+ * one mental model** for subscription → cancellation → propagation → teardown.
43
+ *
44
+ * Let's say you're building a search box. Without Observables, you might write something like this:
45
+ *
46
+ * ```ts
47
+ * // The messy way: callbacks, timers, and manual cleanup 😫
48
+ * let searchTimeout: number;
49
+ * let lastRequest: Promise<any> | null = null;
50
+ *
51
+ * searchInput.addEventListener('input', async (event) => {
52
+ * const query = event.target.value;
53
+ *
54
+ * // Debounce: wait 300ms after user stops typing
55
+ * clearTimeout(searchTimeout);
56
+ * searchTimeout = setTimeout(async () => {
57
+ *
58
+ * // Cancel previous request somehow?
59
+ * if (lastRequest) {
60
+ * // How do you cancel a fetch? 🤔
61
+ * }
62
+ *
63
+ * if (query.length < 3) return; // Skip short queries
64
+ *
65
+ * try {
66
+ * lastRequest = fetch(`/search?q=${query}`);
67
+ * const response = await lastRequest;
68
+ * const results = await response.json();
69
+ *
70
+ * // Update UI, but what if user already typed something new?
71
+ * updateSearchResults(results);
72
+ * } catch (error) {
73
+ * // Handle errors, but which errors? Network? Parsing?
74
+ * handleSearchError(error);
75
+ * }
76
+ * }, 300);
77
+ * });
78
+ *
79
+ * // Don't forget cleanup when component unmounts!
80
+ * // (Spoiler: everyone forgets this and creates memory leaks)
81
+ * ```
82
+ *
83
+ * This works, but it's fragile, hard to test, and easy to mess up. Plus, you have to remember to
84
+ * clean up event listeners, cancel timers, and handle edge cases manually.
85
+ *
86
+ * ## The Solution: Observable Pipelines
87
+ *
88
+ * Here's the same search box with Observables:
89
+ *
90
+ * ```ts
91
+ * // The Observable way: clean, composable, and robust ✨
92
+ * import { pipe, debounce, filter, switchMap, map } from './mod.ts';
93
+ *
94
+ * const searchResults = pipe(
95
+ * inputEvents, // Stream of input events
96
+ * debounce(300), // Wait 300ms after user stops typing
97
+ * filter(query => query.length >= 3), // Skip short queries
98
+ * switchMap(query => // Cancel previous requests automatically
99
+ * Observable.from(fetch(`/search?q=${query}`))
100
+ * ),
101
+ * map(response => response.json()) // Parse response
102
+ * );
103
+ *
104
+ * // Subscribe to results (with automatic cleanup!)
105
+ * using subscription = searchResults.subscribe({
106
+ * next: results => updateSearchResults(results),
107
+ * error: error => handleSearchError(error)
108
+ * });
109
+ * // Subscription automatically cleaned up when leaving scope
110
+ * ```
111
+ *
112
+ * Notice how much cleaner this is? No manual timers, no cancellation logic, no memory leaks.
113
+ * **The operators handle all the complex async stuff for you.**
114
+ *
115
+ * Observables aren't just "nice to have", they solve real problems that bite every developer:
116
+ *
117
+ * - **🧹 Memory Leaks**: Forgot to remove an event listener? Observable subscriptions can clean themselves up.
118
+ * - **🏃‍♂️ Race Conditions**: User clicks fast, requests arrive out of order? `switchMap` cancels old requests.
119
+ * - **🔄 Retry Logic**: Network failed? Built-in retry operators handle backoff and error recovery.
120
+ * - **⚡ Backpressure**: Producer too fast for consumer? Built-in flow control prevents memory bloat.
121
+ * - **🧪 Testing**: Complex async flows become simple to test with predictable, pure operators.
122
+ * - **🎯 Composability**: Mix and match operators like Lego blocks to build exactly what you need.
123
+ *
124
+ * ## ✨ Feature Highlights
125
+ * - **Unified push + pull** – use callbacks *or* `for await … of` on the same
126
+ * stream.
127
+ * - **Cold by default** – each subscriber gets an independent execution (great
128
+ * for predictable side‑effects).
129
+ * - **Deterministic teardown** – return a function/`unsubscribe`/`[Symbol.dispose]`
130
+ * and it *always* runs once, even if the observable errors synchronously.
131
+ * - **Back‑pressure helper** – `pull()` converts to an `AsyncGenerator` backed
132
+ * by `ReadableStream` so the producer slows down when the consumer lags.
133
+ * - **Tiny surface** – <3 kB min+gzip of logic; treeshakes cleanly.
134
+ * - **Rich operator library** – functional composition via `pipe()` with full
135
+ * type safety and backpressure support.
136
+ * - **EventBus & EventDispatcher** – built-in multicast event buses for pub/sub patterns.
137
+ * - **Advanced error handling** – 4-mode error handling system (pass-through, ignore, throw, manual).
138
+ * - **High-performance operators** – Web Streams-based operators with pre-compiled error handling.
139
+ *
140
+ * ## What Makes This Observables Implementation Special
141
+ *
142
+ * `@okikio/observables` isn't just another Observable library. It's designed to be:
143
+ *
144
+ * - **Beginner-friendly**: If you know `Array.map()`, you already understand operators
145
+ * - **Performance-first**: Built on Web Streams with pre-compiled error handling for speed
146
+ * - **TypeScript-native**: Full type safety with intelligent inference
147
+ * - **Standards-compliant**: Follows the TC39 Observable proposal for future compatibility
148
+ * - **Tiny but complete**: <3KB but includes everything you need for real apps
149
+ * - **Error-resilient**: 4 different error handling modes for every situation
150
+ *
151
+ * ## Getting Started: Your First Observable
152
+ *
153
+ * Let's start simple. Here's how to create and use an Observable:
154
+ *
155
+ * @example Creating Observables
156
+ * ```ts
157
+ * import { Observable } from './observable.ts';
158
+ *
159
+ * // Method 1: From scratch (like creating a custom Promise)
160
+ * const timer = new Observable(observer => {
161
+ * let count = 0;
162
+ * const id = setInterval(() => {
163
+ * observer.next(count++); // Send values
164
+ * if (count > 5) {
165
+ * observer.complete(); // Finish
166
+ * }
167
+ * }, 1000);
168
+ *
169
+ * // Return cleanup function (like Promise.finally)
170
+ * return () => clearInterval(id);
171
+ * });
172
+ *
173
+ * // Method 2: From existing values (like Promise.resolve)
174
+ * const numbers = Observable.of(1, 2, 3, 4, 5);
175
+ *
176
+ * // Method 3: From promises, arrays, or other async sources
177
+ * const apiData = Observable.from(fetch('/api/users'));
178
+ * const listData = Observable.from([1, 2, 3, 4, 5]);
179
+ * ```
180
+ *
181
+ * @example Consuming Observables
182
+ * ```ts
183
+ * // Method 1: Subscribe with callbacks (like Promise.then)
184
+ * const subscription = timer.subscribe({
185
+ * next: value => console.log('Got:', value), // Handle each value
186
+ * error: err => console.error('Error:', err), // Handle errors
187
+ * complete: () => console.log('All done!') // Handle completion
188
+ * });
189
+ *
190
+ * // Don't forget to clean up! (or you'll get memory leaks)
191
+ * subscription.unsubscribe();
192
+ *
193
+ * // Method 2: Use modern "using" syntax for automatic cleanup
194
+ * {
195
+ * using sub = timer.subscribe(value => console.log(value));
196
+ * // Code here...
197
+ * } // Automatically unsubscribed at block end!
198
+ *
199
+ * // Method 3: Async iteration (like for-await with arrays)
200
+ * for await (const value of timer) {
201
+ * console.log('Value:', value);
202
+ * if (value > 3) break; // Stop early if needed
203
+ * } // Automatically cleaned up when loop exits
204
+ * ```
205
+ *
206
+ * That's it! You now know the basics. But the real power comes from **operators**...
207
+ *
208
+ * ## Operators
209
+ *
210
+ * If you've used `Array.map()` or `Array.filter()`, you already understand operators.
211
+ * They're just like array methods, but for data that arrives over time:
212
+ *
213
+ * ```ts
214
+ * // With arrays (data you already have):
215
+ * [1, 2, 3, 4, 5]
216
+ * .filter(x => x % 2 === 0) // Keep even numbers: [2, 4]
217
+ * .map(x => x * 10) // Multiply by 10: [20, 40]
218
+ * .slice(0, 1) // Take first: [20]
219
+ *
220
+ * // With Observables (data arriving over time):
221
+ * pipe(
222
+ * numberStream,
223
+ * filter(x => x % 2 === 0), // Keep even numbers
224
+ * map(x => x * 10), // Multiply by 10
225
+ * take(1) // Take first
226
+ * )
227
+ * ```
228
+ *
229
+ * The difference? Arrays process everything at once. Observables process data
230
+ * piece-by-piece as it arrives, without loading everything into memory.
231
+ *
232
+ * @example Real-World Example: User Search
233
+ * ```ts
234
+ * import { pipe, debounce, filter, switchMap, map } from './helpers/mod.ts';
235
+ *
236
+ * // Transform raw input events into search results
237
+ * const searchResults = pipe(
238
+ * userInput, // Raw keystrokes
239
+ * debounce(300), // Wait for typing pause
240
+ * filter(query => query.length > 2), // Skip short queries
241
+ * switchMap(query => // Cancel old searches
242
+ * Observable.from(fetch(`/search?q=${query}`))
243
+ * ),
244
+ * map(response => response.json()) // Parse JSON
245
+ * );
246
+ *
247
+ * // Use the results
248
+ * searchResults.subscribe(results => {
249
+ * updateUI(results);
250
+ * });
251
+ * ```
252
+ *
253
+ * Each operator transforms the data in some way, and you can chain as many as you need.
254
+ * It's like building a data processing pipeline where each step does one thing well.
255
+ *
256
+ * ## Operator Categories
257
+ *
258
+ * Operators enables powerful functional composition patterns using the `pipe()` function.
259
+ * All operators are **type-safe**, **tree-shakable**, **support automatic backpressure**, and feature
260
+ * **advanced error handling** with 4 distinct modes: pass-through, ignore, throw, and manual.
261
+ *
262
+ * There are many operators, but they fall into clear categories. You don't need to learn
263
+ * them all at once, start with the ones you need:
264
+ *
265
+ * ### 🔄 **Transformation**: Change data as it flows
266
+ * ```ts
267
+ * pipe(
268
+ * numbers,
269
+ * map(x => x * 2), // Transform each value: 1 → 2, 2 → 4
270
+ * scan((sum, x) => sum + x) // Running total: 2, 6, 12, 20...
271
+ * )
272
+ * ```
273
+ * - `map(fn)` – Transform each value (like `Array.map`)
274
+ * - `scan(fn, seed)` – Running accumulation (like `Array.reduce` over time)
275
+ * - `batch(size)` – Group values into arrays
276
+ * - `toArray()` – Collect everything into one array
277
+ *
278
+ * ### 🚰 **Filtering**: Control what data gets through
279
+ * ```ts
280
+ * pipe(
281
+ * allClicks,
282
+ * filter(event => event.target.matches('button')), // Only button clicks
283
+ * take(5) // Stop after 5 clicks
284
+ * )
285
+ * ```
286
+ * - `filter(predicate)` – Keep values that pass a test (like `Array.filter`)
287
+ * - `take(count)` – Take only the first N values
288
+ * - `drop(count)` – Skip the first N values
289
+ * - `find(predicate)` – Find first matching value and stop
290
+ *
291
+ * ### ⏰ **Timing**: Control when things happen
292
+ * ```ts
293
+ * pipe(
294
+ * keystrokes,
295
+ * debounce(300), // Wait 300ms after last keystroke
296
+ * delay(100) // Add 100ms delay to everything
297
+ * )
298
+ * ```
299
+ * - `debounce(ms)` – Wait for silence before emitting
300
+ * - `throttle(ms)` – Limit emission rate
301
+ * - `delay(ms)` – Delay all emissions by time
302
+ * - `timeout(ms)` – Cancel if nothing happens within time
303
+ *
304
+ * ### 🔀 **Combination**: Merge multiple streams
305
+ * ```ts
306
+ * pipe(
307
+ * searchQueries,
308
+ * switchMap(query => // For each query...
309
+ * fetch(`/search?q=${query}`) // ...start a request (cancel previous)
310
+ * )
311
+ * )
312
+ * ```
313
+ * - `mergeMap(fn)` – Start multiple operations, merge results
314
+ * - `concatMap(fn)` – Start operations one at a time
315
+ * - `switchMap(fn)` – Cancel previous operation when new one starts
316
+ *
317
+ * ### ⚠️ **Error Handling**: Deal with things going wrong
318
+ * ```ts
319
+ * pipe(
320
+ * riskyOperations,
321
+ * catchErrors('fallback'), // Replace errors with fallback
322
+ * ignoreErrors() // Skip errors, keep going
323
+ * )
324
+ * ```
325
+ * - `catchErrors(fallback)` – Replace errors with fallback values
326
+ * - `ignoreErrors()` – Skip errors silently
327
+ * - `tapError(fn)` – Log errors without changing the stream
328
+ * - `mapErrors(fn)` – Transform error values
329
+ *
330
+ * ### 🔧 **Utilities**: Side effects and debugging
331
+ * ```ts
332
+ * pipe(
333
+ * dataStream,
334
+ * tap(x => console.log('Debug:', x)), // Log without changing values
335
+ * tap(x => analytics.track(x)) // Send to analytics
336
+ * )
337
+ * ```
338
+ * - `tap(fn)` – Run side effects without changing values
339
+ *
340
+ * ## Real-World Examples: See It In Action
341
+ *
342
+ * Let's see how these operators solve actual problems you face every day:
343
+ *
344
+ * @example Smart Search with Debouncing
345
+ * ```ts
346
+ * import { pipe, debounce, filter, switchMap, map, catchErrors } from './helpers/mod.ts';
347
+ *
348
+ * // Problem: User types fast, you don't want to spam the API
349
+ * // Solution: Debounce + cancel previous requests
350
+ * const searchResults = pipe(
351
+ * searchInput,
352
+ * debounce(300), // Wait for typing pause
353
+ * filter(query => query.length > 2), // Skip short queries
354
+ * switchMap(query => // Cancel old requests automatically
355
+ * pipe(
356
+ * Observable.from(fetch(`/search?q=${query}`)),
357
+ * map(res => res.json()),
358
+ * catchErrors([]) // Return empty array on error
359
+ * )
360
+ * )
361
+ * );
362
+ *
363
+ * searchResults.subscribe(results => updateUI(results));
364
+ * ```
365
+ *
366
+ * @example Real-Time Data Dashboard
367
+ * ```ts
368
+ * import { pipe, filter, scan, throttle, batch } from './helpers/mod.ts';
369
+ *
370
+ * // Problem: WebSocket sends lots of data, UI can't keep up
371
+ * // Solution: Filter, accumulate, and throttle updates
372
+ * const dashboardData = pipe(
373
+ * webSocketEvents,
374
+ * filter(event => event.type === 'metric'), // Only metric events
375
+ * scan((acc, event) => ({ // Build running totals
376
+ * ...acc,
377
+ * total: acc.total + event.value,
378
+ * count: acc.count + 1,
379
+ * average: (acc.total + event.value) / (acc.count + 1)
380
+ * }), { total: 0, count: 0, average: 0 }),
381
+ * throttle(1000) // Update UI max once per second
382
+ * );
383
+ *
384
+ * dashboardData.subscribe(stats => updateDashboard(stats));
385
+ * ```
386
+ *
387
+ * @example File Upload with Progress
388
+ * ```ts
389
+ * import { pipe, map, scan, tap } from './helpers/mod.ts';
390
+ *
391
+ * // Problem: Show upload progress and handle completion
392
+ * // Solution: Transform progress events into UI updates
393
+ * const uploadProgress = pipe(
394
+ * fileUploadEvents,
395
+ * map(event => ({ // Extract useful info
396
+ * loaded: event.loaded,
397
+ * total: event.total,
398
+ * percent: Math.round((event.loaded / event.total) * 100)
399
+ * })),
400
+ * tap(progress => updateProgressBar(progress.percent)), // Update UI
401
+ * filter(progress => progress.percent === 100), // Only completion
402
+ * map(() => 'Upload complete!') // Success message
403
+ * );
404
+ *
405
+ * uploadProgress.subscribe(message => showSuccess(message));
406
+ * ```
407
+ *
408
+ * @example Background Data Sync
409
+ * ```ts
410
+ * import { pipe, mergeMap, delay, catchErrors, tap } from './helpers/mod.ts';
411
+ *
412
+ * // Problem: Sync data in background, retry on failure, don't overwhelm server
413
+ * // Solution: Batch processing with concurrency control and error recovery
414
+ * const syncResults = pipe(
415
+ * pendingItems,
416
+ * batch(10), // Process 10 items at a time
417
+ * mergeMap(batch => // Process up to 3 batches concurrently
418
+ * pipe(
419
+ * Observable.from(syncBatch(batch)),
420
+ * delay(100), // Be nice to the server
421
+ * catchErrors(null), // Don't fail everything on one error
422
+ * tap(result => updateSyncStatus(result))
423
+ * ),
424
+ * 3 // Max 3 concurrent operations
425
+ * ),
426
+ * filter(result => result !== null) // Skip failed syncs
427
+ * );
428
+ *
429
+ * syncResults.subscribe(result => logSyncSuccess(result));
430
+ * ```
431
+ *
432
+ * Notice the pattern? Each operator does one job well, and you combine them to solve
433
+ * complex problems. It's like having a Swiss Army knife for async data.
434
+ *
435
+ * ## Building Your Own Operators
436
+ *
437
+ * Sometimes the built-in operators aren't enough. That's fine! You can build your own.
438
+ * Think of it like creating custom functions, but for streams:
439
+ *
440
+ * @example Simple Custom Operator
441
+ * ```ts
442
+ * import { createOperator } from './helpers/mod.ts';
443
+ *
444
+ * // Create a "double" operator (like multiplying every array element by 2)
445
+ * function double<T extends number>() {
446
+ * return createOperator<T, T>({
447
+ * name: 'double', // For debugging
448
+ * transform(value, controller) {
449
+ * controller.enqueue(value * 2); // Send doubled value
450
+ * }
451
+ * });
452
+ * }
453
+ *
454
+ * // Use it like any other operator
455
+ * pipe(
456
+ * Observable.of(1, 2, 3),
457
+ * double()
458
+ * ).subscribe(console.log); // 2, 4, 6
459
+ * ```
460
+ *
461
+ * @example Stateful Custom Operator
462
+ * ```ts
463
+ * import { createStatefulOperator } from './helpers/mod.ts';
464
+ *
465
+ * // Create a "moving average" operator that remembers previous values
466
+ * function movingAverage(windowSize: number) {
467
+ * return createStatefulOperator<number, number, number[]>({
468
+ * name: 'movingAverage',
469
+ * createState: () => [], // Start with empty array
470
+ *
471
+ * transform(value, arr, controller) {
472
+ * arr.push(value); // Add new value
473
+ * if (arr.length > windowSize) {
474
+ * arr.shift(); // Remove old values
475
+ * }
476
+ *
477
+ * // Calculate and emit average
478
+ * const avg = arr.reduce((sum, n) => sum + n, 0) / arr.length;
479
+ * controller.enqueue(avg);
480
+ * }
481
+ * });
482
+ * }
483
+ *
484
+ * // Use it to smooth noisy sensor data
485
+ * pipe(
486
+ * noisySensorData,
487
+ * movingAverage(5) // 5-value moving average
488
+ * ).subscribe(smoothValue => updateDisplay(smoothValue));
489
+ * ```
490
+ *
491
+ * The beauty of this system is that your custom operators work exactly like the built-in ones.
492
+ * You can combine them, test them separately, and reuse them across projects.
493
+ *
494
+ * ## Error Handling: When Things Go Wrong
495
+ *
496
+ * Real-world data is messy. Networks fail, users input bad data, APIs return errors.
497
+ * This library gives you **four ways** to handle errors, so you can choose what makes
498
+ * sense for your situation:
499
+ *
500
+ * ```ts
501
+ * // 1. "pass-through" (default): Errors become values in the stream
502
+ * const safeParser = createOperator({
503
+ * errorMode: 'pass-through', // Errors become ObservableError values
504
+ * transform(jsonString, controller) {
505
+ * controller.enqueue(JSON.parse(jsonString)); // If this fails, error flows as value
506
+ * }
507
+ * });
508
+ *
509
+ * // 2. "ignore": Skip errors silently
510
+ * const lenientParser = createOperator({
511
+ * errorMode: 'ignore', // Errors are silently skipped
512
+ * transform(jsonString, controller) {
513
+ * controller.enqueue(JSON.parse(jsonString)); // Bad JSON just disappears
514
+ * }
515
+ * });
516
+ *
517
+ * // 3. "throw": Stop everything on first error
518
+ * const strictParser = createOperator({
519
+ * errorMode: 'throw', // Errors terminate the stream
520
+ * transform(jsonString, controller) {
521
+ * controller.enqueue(JSON.parse(jsonString)); // Bad JSON kills the stream
522
+ * }
523
+ * });
524
+ *
525
+ * // 4. "manual": You handle everything yourself
526
+ * const customParser = createOperator({
527
+ * errorMode: 'manual', // You're in control
528
+ * transform(jsonString, controller) {
529
+ * try {
530
+ * controller.enqueue(JSON.parse(jsonString));
531
+ * } catch (err) {
532
+ * // Your custom error logic here
533
+ * controller.enqueue({ error: err.message, input: jsonString });
534
+ * }
535
+ * }
536
+ * });
537
+ * ```
538
+ *
539
+ * **When to use which mode?**
540
+ * - **pass-through**: When you want to handle errors downstream (most common)
541
+ * - **ignore**: When bad data should just be filtered out
542
+ * - **throw**: When any error means the whole operation failed
543
+ * - **manual**: When you need custom error handling logic
544
+ *
545
+ * ## EventBus: For Pub/Sub Patterns
546
+ *
547
+ * Sometimes you need **one-to-many communication**, like a chat app where one message
548
+ * goes to multiple users, or a shopping cart that updates multiple UI components.
549
+ * That's where EventBus comes in:
550
+ *
551
+ * @example Simple EventBus
552
+ * ```ts
553
+ * import { EventBus } from './events.ts';
554
+ *
555
+ * // Create a bus for chat messages
556
+ * const chatBus = new EventBus<string>();
557
+ *
558
+ * // Multiple components can listen
559
+ * chatBus.events.subscribe(msg => updateChatWindow(msg));
560
+ * chatBus.events.subscribe(msg => updateNotificationBadge(msg));
561
+ * chatBus.events.subscribe(msg => logMessage(msg));
562
+ *
563
+ * // One emit reaches everyone
564
+ * chatBus.emit('Hello everyone!');
565
+ * // All three subscribers receive the message
566
+ *
567
+ * chatBus.close(); // Clean up when done
568
+ * ```
569
+ *
570
+ * @example EventBus with async iteration
571
+ * ```ts
572
+ * import { EventBus } from './events.ts';
573
+ *
574
+ * const statusBus = new EventBus<{ status: string; data: any }>();
575
+ *
576
+ * // Listen using for-await (great for async processing)
577
+ * async function handleStatusUpdates() {
578
+ * for await (const update of statusBus.events) {
579
+ * console.log('Status changed:', update.status);
580
+ *
581
+ * if (update.status === 'error') {
582
+ * await handleError(update.data);
583
+ * } else if (update.status === 'complete') {
584
+ * await finalizeProcess(update.data);
585
+ * break; // Exit the loop
586
+ * }
587
+ * }
588
+ * }
589
+ *
590
+ * // Start listening
591
+ * handleStatusUpdates();
592
+ *
593
+ * // Emit updates from anywhere in your app
594
+ * statusBus.emit({ status: 'processing', data: { progress: 50 } });
595
+ * statusBus.emit({ status: 'complete', data: { result: 'success' } });
596
+ * ```
597
+ *
598
+ * @example EventBus with operators
599
+ * ```ts
600
+ * import { EventBus } from './events.ts';
601
+ * import { pipe, filter, map, debounce } from './helpers/mod.ts';
602
+ *
603
+ * const clickBus = new EventBus<{ x: number; y: number; target: string }>();
604
+ *
605
+ * // Process clicks with operators
606
+ * const buttonClicks = pipe(
607
+ * clickBus.events,
608
+ * filter(click => click.target === 'button'), // Only button clicks
609
+ * debounce(100), // Prevent double-clicks
610
+ * map(click => ({ ...click, timestamp: Date.now() })) // Add timestamp
611
+ * );
612
+ *
613
+ * buttonClicks.subscribe(click => {
614
+ * console.log('Button clicked at', click.x, click.y);
615
+ * });
616
+ *
617
+ * // Emit clicks (maybe from a global click handler)
618
+ * document.addEventListener('click', (e) => {
619
+ * clickBus.emit({
620
+ * x: e.clientX,
621
+ * y: e.clientY,
622
+ * target: e.target.tagName.toLowerCase()
623
+ * });
624
+ * });
625
+ * ```
626
+ *
627
+ * @example Typed EventDispatcher
628
+ * ```ts
629
+ * import { createEventDispatcher } from './events.ts';
630
+ *
631
+ * // Define your event types (TypeScript ensures you use them correctly)
632
+ * interface AppEvents {
633
+ * userLogin: { userId: string; timestamp: number };
634
+ * userLogout: { userId: string };
635
+ * cartUpdate: { items: number; total: number };
636
+ * notification: { message: string; type: 'info' | 'warning' | 'error' };
637
+ * }
638
+ *
639
+ * const events = createEventDispatcher<AppEvents>();
640
+ *
641
+ * // Type-safe event emission
642
+ * events.emit('userLogin', { userId: '123', timestamp: Date.now() });
643
+ * events.emit('cartUpdate', { items: 3, total: 29.99 });
644
+ * events.emit('notification', { message: 'Welcome!', type: 'info' });
645
+ *
646
+ * // Type-safe event handling - listen to specific events
647
+ * events.on('userLogin', (data) => {
648
+ * analytics.track('login', data.userId); // TypeScript knows data.userId exists
649
+ * console.log('User logged in at', new Date(data.timestamp));
650
+ * });
651
+ *
652
+ * events.on('cartUpdate', (data) => {
653
+ * updateCartIcon(data.items); // TypeScript knows data.items is a number
654
+ * updateCartTotal(data.total);
655
+ * });
656
+ *
657
+ * // Listen to ALL events (useful for debugging or logging)
658
+ * events.events.subscribe(event => {
659
+ * console.log('Event:', event.type, 'Data:', event.payload);
660
+ * });
661
+ *
662
+ * // Use async iteration for event processing
663
+ * async function processNotifications() {
664
+ * for await (const event of events.events) {
665
+ * if (event.type === 'notification') {
666
+ * await showNotification(event.payload.message, event.payload.type);
667
+ * }
668
+ * }
669
+ * }
670
+ * ```
671
+ *
672
+ * @example Advanced EventBus patterns
673
+ * ```ts
674
+ * import { EventBus, withReplay, waitForEvent } from './events.ts';
675
+ *
676
+ * // Create a bus that replays the last 5 events to new subscribers
677
+ * const statusBus = new EventBus<string>();
678
+ * const replayableStatus = withReplay(statusBus.events, { count: 5 });
679
+ *
680
+ * // Emit some events
681
+ * statusBus.emit('initializing');
682
+ * statusBus.emit('loading data');
683
+ * statusBus.emit('processing');
684
+ *
685
+ * // New subscribers get the last 5 events immediately
686
+ * replayableStatus.subscribe(status => {
687
+ * console.log('Status:', status); // Will log all 3 previous events first
688
+ * });
689
+ *
690
+ * // Wait for a specific event (useful for async coordination)
691
+ * async function waitForCompletion() {
692
+ * try {
693
+ * const result = await waitForEvent(
694
+ * { events: statusBus.events },
695
+ * 'complete',
696
+ * { signal: AbortSignal.timeout(5000) } // 5 second timeout
697
+ * );
698
+ * console.log('Operation completed:', result);
699
+ * } catch (error) {
700
+ * console.log('Timed out or aborted');
701
+ * }
702
+ * }
703
+ *
704
+ * waitForCompletion();
705
+ * statusBus.emit('complete'); // This will resolve the waitForEvent promise
706
+ * ```
707
+ *
708
+ * @example EventBus resource management
709
+ * ```ts
710
+ * import { EventBus } from './events.ts';
711
+ *
712
+ * // EventBus supports using/await using for automatic cleanup
713
+ * {
714
+ * using messageBus = new EventBus<string>();
715
+ *
716
+ * // Set up listeners
717
+ * messageBus.events.subscribe(msg => console.log('Received:', msg));
718
+ *
719
+ * // Use the bus
720
+ * messageBus.emit('Hello world!');
721
+ *
722
+ * } // Bus automatically closed and all resources cleaned up
723
+ *
724
+ * // Also works with async using
725
+ * async function setupEventSystem() {
726
+ * await using eventSystem = new EventBus<any>();
727
+ *
728
+ * // Set up complex event handling
729
+ * eventSystem.events.subscribe(processEvents);
730
+ *
731
+ * // Do async work...
732
+ * await someAsyncOperation();
733
+ *
734
+ * } // Async cleanup happens automatically
735
+ * ```
736
+ *
737
+ * **When to use EventBus vs Observable?**
738
+ * - **Observable**: One-to-one, like transforming API data or handling user input
739
+ * - **EventBus**: One-to-many, like app-wide notifications, state updates, or cross-component communication
740
+ * - **EventDispatcher**: Type-safe pub/sub with multiple event types in one system
741
+ *
742
+ * **EventBus Consumption Patterns:**
743
+ * - **subscribe()**: For imperative event handling with callbacks
744
+ * - **for await**: For sequential async processing of events
745
+ * - **pipe() + operators**: For transforming and filtering events
746
+ * - **waitForEvent()**: For waiting for specific events in async functions
747
+ *
748
+ * ## Performance: Built for Speed
749
+ *
750
+ * This isn't just a learning library, it's built for production apps that need to handle
751
+ * lots of data efficiently:
752
+ *
753
+ * ### Web Streams Foundation
754
+ * Under the hood, operators use **Web Streams**, which gives you:
755
+ * - **Native backpressure**: Fast producers don't overwhelm slow consumers
756
+ * - **Memory efficiency**: Process data piece-by-piece, not all at once
757
+ * - **Browser optimization**: Built-in browser optimizations kick in
758
+ *
759
+ * ### Pre-compiled Error Handling
760
+ * Instead of checking error modes on every piece of data (slow), we generate
761
+ * optimized functions for each error mode (fast):
762
+ *
763
+ * | Error Mode | Performance | When to Use |
764
+ * |------------|-------------|-------------|
765
+ * | `manual` | Fastest | Hot paths where you handle errors yourself |
766
+ * | `ignore` | Very fast | Filtering bad data |
767
+ * | `pass-through` | Fast | Error recovery, debugging |
768
+ * | `throw` | Good | Fail-fast validation |
769
+ *
770
+ * ### Memory Management
771
+ * - **Automatic cleanup**: `using` syntax and `Symbol.dispose` prevent leaks
772
+ * - **Circular buffer queues**: O(1) operations for high-throughput data
773
+ * - **Smart resource management**: Resources freed immediately when streams end
774
+ *
775
+ * @example Performance Tuning
776
+ * ```ts
777
+ * // For high-throughput data processing
778
+ * const optimized = pipe(
779
+ * highVolumeStream,
780
+ *
781
+ * // Use manual error mode for maximum speed
782
+ * createOperator({
783
+ * errorMode: 'manual',
784
+ * transform(chunk, controller) {
785
+ * try {
786
+ * controller.enqueue(processChunk(chunk));
787
+ * } catch (err) {
788
+ * logError(err); // Handle as needed
789
+ * }
790
+ * }
791
+ * }),
792
+ *
793
+ * batch(100), // Process in efficient batches
794
+ * mergeMap(batch => processBatch(batch), 3) // Limit concurrency
795
+ * );
796
+ *
797
+ * // For memory-constrained environments
798
+ * for await (const chunk of bigDataStream.pull({
799
+ * strategy: { highWaterMark: 8 } // Small buffer
800
+ * })) {
801
+ * await processLargeChunk(chunk);
802
+ * }
803
+ * ```
804
+ *
805
+ * ## Common Gotchas & How to Avoid Them
806
+ *
807
+ * Even with great tools, there are some things that can trip you up. Here's how to avoid them:
808
+ *
809
+ * **🔥 Memory Leaks**: The #1 Observable mistake
810
+ * ```ts
811
+ * // ❌ Bad: Creates memory leak
812
+ * const timer = new Observable(obs => {
813
+ * setInterval(() => obs.next(Date.now()), 1000);
814
+ * // Missing cleanup function!
815
+ * });
816
+ * timer.subscribe(console.log); // This will run forever
817
+ *
818
+ * // ✅ Good: Always provide cleanup
819
+ * const timer = new Observable(obs => {
820
+ * const id = setInterval(() => obs.next(Date.now()), 1000);
821
+ * return () => clearInterval(id); // Cleanup function
822
+ * });
823
+ * using sub = timer.subscribe(console.log); // Auto-cleanup with 'using'
824
+ * ```
825
+ *
826
+ * **🏁 Race Conditions**: When requests finish out of order
827
+ * ```ts
828
+ * // ❌ Bad: Last request might not be latest
829
+ * searchInput.subscribe(query => {
830
+ * fetch(`/search?q=${query}`)
831
+ * .then(response => response.json())
832
+ * .then(results => updateUI(results)); // Wrong results might appear!
833
+ * });
834
+ *
835
+ * // ✅ Good: Use switchMap to cancel old requests
836
+ * pipe(
837
+ * searchInput,
838
+ * switchMap(query => Observable.from(fetch(`/search?q=${query}`)))
839
+ * ).subscribe(response => updateUI(response));
840
+ * ```
841
+ *
842
+ * **❄️ Cold vs Hot Confusion**: Understanding when side effects happen
843
+ *
844
+ * Observable (cold): Side effect runs once per subscription.
845
+ * EventBus (hot): Single source shared among multiple subscribers.
846
+ *
847
+ * **🚧 Operator Limits**: TypeScript has recursion limits
848
+ *
849
+ * Break into smaller, reusable functions for complex pipelines.
850
+ *
851
+ * ## Getting Started: Your First Steps
852
+ *
853
+ * 1. **Start simple**: Convert a Promise to an Observable
854
+ * 2. **Add operators**: Try transformation and filtering first
855
+ * 3. **Handle timing**: Add debouncing to a search input
856
+ * 4. **Manage errors**: Use error catching for graceful degradation
857
+ * 5. **Combine streams**: Use switching operators for request cancellation
858
+ *
859
+ * The operators work just like array methods, if you know transformation and filtering,
860
+ * you're already halfway there. The real power comes from combining operators to
861
+ * solve complex async problems with simple, composable code.
862
+ *
863
+ * **Implementation Notes:**
864
+ * - Follows TC39 Observable proposal for future compatibility
865
+ * - Built on Web Streams for performance and native backpressure
866
+ * - Fully tree-shakable - import only what you use
867
+ * - Comprehensive TypeScript support with intelligent inference
868
+ * - Multiple error handling modes for different use cases
869
+ * - Extensive test suite ensuring reliability
870
+ *
871
+ * @module
872
+ */
873
+ require("./_dnt.polyfills.js");
874
+ __exportStar(require("./observable.js"), exports);
875
+ __exportStar(require("./error.js"), exports);
876
+ __exportStar(require("./events.js"), exports);
877
+ __exportStar(require("./helpers/mod.js"), exports);