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