@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Okiki Ojo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,578 @@
1
+ # @okikio/observables
2
+
3
+ [![CI](https://github.com/okikio/observables/actions/workflows/ci.yml/badge.svg)](https://github.com/okikio/observables/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/%40okikio%2Fobservables?logo=npm&label=npm)](https://www.npmjs.com/package/@okikio/observables)
5
+ [![Bundle Size](https://deno.bundlejs.com/badge?q=@okikio/observables&treeshake=[{+Observable,+pipe,+map,+filter+}]&style=flat)](https://bundlejs.com/?q=@okikio/observables&treeshake=[{+Observable,+pipe,+map,+filter+}])
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
+
8
+ [Documentation](https://jsr.io/@okikio/observables) •
9
+ [npm](https://www.npmjs.com/package/@okikio/observables) •
10
+ [GitHub](https://github.com/okikio/observables#readme) • [License](./LICENSE)
11
+
12
+ <!-- [![Open Bundle](https://bundlejs.com/badge-light.svg)](https://bundlejs.com/?q=@okikio/observables&bundle "Check the total bundle size of @okikio/observables") -->
13
+
14
+ A **spec-faithful** yet ergonomic TC39-inspired Observable implementation that
15
+ gives you one consistent way to handle all async data in JavaScript.
16
+
17
+ Built for Deno v2+, Node, Bun, and modern browsers, `@okikio/observables` keeps
18
+ the TC39 Observable proposal's mental model while adding the parts that make
19
+ day-to-day app code easier to write:
20
+
21
+ - **Observable pipelines that feel familiar** if you already know `Array.map()`
22
+ and `Array.filter()`
23
+ - **Web Streams-powered backpressure** so fast producers do not silently bloat
24
+ memory
25
+ - **Deterministic cleanup** via `unsubscribe()`, `using`, and `Symbol.dispose`
26
+ - **Built-in event primitives** for pub/sub and type-safe event dispatch
27
+ - **Four error modes** so you can choose between recovery, filtering, and
28
+ fail-fast behavior
29
+
30
+ **Start here:** [Installation](#installation) • [Quick Start](#quick-start) •
31
+ [API](#api) • [Advanced Usage](#advanced-usage) • [FAQ](#faq) •
32
+ [Contributing](#contributing)
33
+
34
+ ## Start Here
35
+
36
+ Install with the package manager that matches your runtime:
37
+
38
+ ```bash
39
+ # Deno / JSR
40
+ deno add jsr:@okikio/observables
41
+
42
+ # npm-compatible runtimes
43
+ npm install @okikio/observables
44
+ # pnpm add @okikio/observables
45
+ # yarn add @okikio/observables
46
+ # bun add @okikio/observables
47
+ ```
48
+
49
+ Then build a small pipeline:
50
+
51
+ ```ts
52
+ import { filter, map, Observable, pipe } from "@okikio/observables";
53
+
54
+ const values = pipe(
55
+ Observable.of(1, 2, 3, 4),
56
+ filter((value) => value % 2 === 0),
57
+ map((value) => value * 10),
58
+ );
59
+
60
+ values.subscribe((value) => console.log(value));
61
+ // 20
62
+ // 40
63
+ ```
64
+
65
+ **Observables** are a **push‑based stream abstraction** for events, data, and
66
+ long‑running operations. Think of them as a **multi‑value Promise** that keeps
67
+ sending values until you tell it to stop, where a Promise gives you one value
68
+ eventually, an Observable can give you many values over time: mouse clicks,
69
+ search results, chat messages, sensor readings.
70
+
71
+ If you've ever built a web app, you know this all too well: user clicks, API
72
+ responses, WebSocket messages, timers, file uploads, they all arrive at
73
+ different times and need different handling. Before Observables, we'd all end up
74
+ with a mess of callbacks, Promise chains, event listeners, and async/await
75
+ scattered throughout our code.
76
+
77
+ Let's say you're building a search box. You've probably written something like
78
+ this:
79
+
80
+ ```ts
81
+ // We've all been here: callbacks, timers, and manual cleanup 😫
82
+ let searchTimeout: number;
83
+ let lastRequest: Promise<any> | null = null;
84
+
85
+ searchInput.addEventListener("input", async (event) => {
86
+ const query = event.target.value;
87
+
88
+ // Debounce: wait 300ms after user stops typing
89
+ clearTimeout(searchTimeout);
90
+ searchTimeout = setTimeout(async () => {
91
+ // Cancel previous request somehow?
92
+ if (lastRequest) {
93
+ // How do you cancel a fetch? 🤔
94
+ }
95
+
96
+ if (query.length < 3) return; // Skip short queries
97
+
98
+ try {
99
+ lastRequest = fetch(`/search?q=${query}`);
100
+ const response = await lastRequest;
101
+ const results = await response.json();
102
+
103
+ // Update UI, but what if user already typed something new?
104
+ updateSearchResults(results);
105
+ } catch (error) {
106
+ // Handle errors, but which errors? Network? Parsing?
107
+ handleSearchError(error);
108
+ }
109
+ }, 300);
110
+ });
111
+
112
+ // Don't forget cleanup when component unmounts!
113
+ // (Spoiler: we all forget this and create memory leaks)
114
+ ```
115
+
116
+ This works but it's fragile, hard to test, and easy to mess up. Plus, you have
117
+ to remember to clean up event listeners, cancel timers, and handle edge cases
118
+ manually.
119
+
120
+ We've all felt this pain before:
121
+
122
+ - **Memory Leaks**: Forgot to remove an event listener? Your app slowly eats
123
+ memory
124
+ - **Race Conditions**: User clicks fast, requests arrive out of order, wrong
125
+ results appear
126
+ - **Error Handling**: Network failed? Now you need custom backoff and error
127
+ recovery
128
+ - **Backpressure**: Producer too fast for consumer? Memory bloats until crash
129
+ - **Testing**: Complex async flows become nearly impossible to test reliably
130
+ - **Maintenance**: Each async pattern needs its own cleanup and error handling
131
+
132
+ Here's the same search box with Observables:
133
+
134
+ ```ts
135
+ // Much cleaner: composable and robust ✨
136
+ import { debounce, filter, map, pipe, switchMap } from "@okikio/observables";
137
+
138
+ const searchResults = pipe(
139
+ inputEvents, // Stream of input events
140
+ debounce(300), // Wait 300ms after user stops typing
141
+ filter((query) => query.length >= 3), // Skip short queries
142
+ switchMap((query) =>
143
+ // Cancel previous requests automatically
144
+ Observable.from(fetch(`/search?q=${query}`))
145
+ ),
146
+ map((response) => response.json()), // Parse response
147
+ );
148
+
149
+ // Subscribe to results (with automatic cleanup!)
150
+ using subscription = searchResults.subscribe({
151
+ next: (results) => updateSearchResults(results),
152
+ error: (error) => handleSearchError(error),
153
+ });
154
+ // Subscription automatically cleaned up when leaving scope
155
+ ```
156
+
157
+ Notice the difference? No manual timers, no cancellation logic, no memory leaks.
158
+ The operators handle all the complex async coordination for you.
159
+
160
+ This library was built by developers who've felt these same frustrations. It
161
+ focuses on:
162
+
163
+ - **Familiarity**: If you know `Array.map()`, you already understand operators
164
+ - **Performance**: Built on Web Streams with pre-compiled error handling
165
+ - **Type Safety**: Full TypeScript support with intelligent inference
166
+ - **Standards**: Follows the TC39 Observable proposal for future compatibility
167
+ - **Practicality**: <4KB but includes everything you need for real apps
168
+ - **Flexibility**: 4 different error handling modes for different situations
169
+
170
+ ## Installation
171
+
172
+ ### Deno
173
+
174
+ ```ts
175
+ import { map, Observable, pipe } from "jsr:@okikio/observables";
176
+ ```
177
+
178
+ Or
179
+
180
+ ```bash
181
+ deno add jsr:@okikio/observables
182
+ ```
183
+
184
+ ### Node.js and Bun
185
+
186
+ ```bash
187
+ npm install @okikio/observables
188
+ # pnpm add @okikio/observables
189
+ # yarn add @okikio/observables
190
+ # bun add @okikio/observables
191
+ ```
192
+
193
+ If you prefer to install through the JSR bridge instead of the npm registry:
194
+
195
+ ```bash
196
+ npx jsr add @okikio/observables
197
+ ```
198
+
199
+ <details>
200
+ <summary>Others</summary>
201
+
202
+ ```bash
203
+ pnpm add jsr:@okikio/observables
204
+ ```
205
+
206
+ Or
207
+
208
+ ```bash
209
+ yarn add @okikio/observables@jsr:latest
210
+ ```
211
+
212
+ Or
213
+
214
+ ```bash
215
+ bunx jsr add @okikio/observables
216
+ ```
217
+
218
+ </details>
219
+
220
+ ### Web
221
+
222
+ You can also use it via a CDN:
223
+
224
+ ```ts ignore
225
+ import { map, Observable, pipe } from "https://esm.sh/jsr/@okikio/observables";
226
+ ```
227
+
228
+ ## Quick Start
229
+
230
+ ```ts
231
+ import { debounce, filter, map, Observable, pipe } from "@okikio/observables";
232
+
233
+ // Create from anything async
234
+ const clicks = new Observable((observer) => {
235
+ const handler = (e) => observer.next(e);
236
+ button.addEventListener("click", handler);
237
+ return () => button.removeEventListener("click", handler);
238
+ });
239
+
240
+ // Transform with operators (like Array.map, but for async data)
241
+ const doubleClicks = pipe(
242
+ clicks,
243
+ debounce(300), // Wait 300ms between clicks
244
+ filter((_, index) => index % 2), // Only odd-numbered clicks
245
+ map((event) => ({ x: event.clientX, y: event.clientY })),
246
+ );
247
+
248
+ // Subscribe to results
249
+ using subscription = doubleClicks.subscribe({
250
+ next: (coords) => console.log("Double click at:", coords),
251
+ error: (err) => console.error("Error:", err),
252
+ });
253
+ // Automatically cleaned up when leaving scope
254
+ ```
255
+
256
+ ## Showcase
257
+
258
+ A couple sites/projects that use `@okikio/observables`:
259
+
260
+ - Your site/project here...
261
+
262
+ ## API
263
+
264
+ The API of `@okikio/observables` provides everything you need for reactive
265
+ programming:
266
+
267
+ ### Core Observable
268
+
269
+ ```ts
270
+ import { Observable } from "@okikio/observables";
271
+
272
+ // Create observables
273
+ const timer = new Observable((observer) => {
274
+ const id = setInterval(() => observer.next(Date.now()), 1000);
275
+ return () => clearInterval(id);
276
+ });
277
+
278
+ // Factory methods
279
+ Observable.of(1, 2, 3); // From values
280
+ Observable.from(fetch("/api/data")); // From promises/iterables
281
+ ```
282
+
283
+ ### Operators (19+ included)
284
+
285
+ ```ts
286
+ import { debounce, filter, map, pipe, switchMap } from "@okikio/observables";
287
+
288
+ // Transform data as it flows
289
+ pipe(
290
+ source,
291
+ map((x) => x * 2), // Transform each value
292
+ filter((x) => x > 10), // Keep only values > 10
293
+ debounce(300), // Wait for quiet periods
294
+ switchMap((x) => fetchData(x)), // Cancel previous requests
295
+ );
296
+ ```
297
+
298
+ ### EventBus & EventDispatcher
299
+
300
+ ```ts
301
+ import { createEventDispatcher, EventBus } from "@okikio/observables";
302
+
303
+ // Simple pub/sub
304
+ const bus = new EventBus<string>();
305
+ bus.events.subscribe((msg) => console.log(msg));
306
+ bus.emit("Hello world!");
307
+
308
+ // Type-safe events
309
+ interface AppEvents {
310
+ userLogin: { userId: string };
311
+ cartUpdate: { items: number };
312
+ }
313
+
314
+ const events = createEventDispatcher<AppEvents>();
315
+ events.emit("userLogin", { userId: "123" });
316
+ events.on("cartUpdate", (data) => updateUI(data.items));
317
+ ```
318
+
319
+ ### Error Handling (4 modes)
320
+
321
+ ```ts
322
+ import { createOperator } from "@okikio/observables";
323
+
324
+ // Choose your error handling strategy
325
+ const processor = createOperator({
326
+ errorMode: "pass-through", // Errors become values (default)
327
+ // errorMode: 'ignore', // Skip errors silently
328
+ // errorMode: 'throw', // Fail fast
329
+ // errorMode: 'manual', // You handle everything
330
+
331
+ transform(value, controller) {
332
+ controller.enqueue(processValue(value));
333
+ },
334
+ });
335
+ ```
336
+
337
+ ### Resource Management
338
+
339
+ ```ts
340
+ // Automatic cleanup with 'using'
341
+ {
342
+ using subscription = observable.subscribe(handleData);
343
+ // Use subscription here...
344
+ } // Automatically cleaned up
345
+
346
+ // Async cleanup
347
+ async function example() {
348
+ await using bus = new EventBus();
349
+ // Do async work...
350
+ } // Awaits cleanup
351
+ ```
352
+
353
+ ### Pull API (Async Iteration)
354
+
355
+ ```ts
356
+ // Process large datasets with backpressure
357
+ for await (
358
+ const chunk of bigDataStream.pull({
359
+ strategy: { highWaterMark: 8 }, // Small buffer for large files
360
+ })
361
+ ) {
362
+ await processChunk(chunk);
363
+ }
364
+ ```
365
+
366
+ Look through the [tests/](./tests/) and [bench/](./bench/) folders for complex
367
+ examples and multiple usage patterns.
368
+
369
+ ## Advanced Usage
370
+
371
+ ### Smart Search with Cancellation
372
+
373
+ ```ts
374
+ import {
375
+ catchErrors,
376
+ debounce,
377
+ filter,
378
+ map,
379
+ pipe,
380
+ switchMap,
381
+ } from "@okikio/observables";
382
+
383
+ const searchResults = pipe(
384
+ searchInput,
385
+ debounce(300), // Wait for typing pause
386
+ filter((query) => query.length > 2), // Skip short queries
387
+ switchMap((query) =>
388
+ // Cancel old requests automatically
389
+ pipe(
390
+ Observable.from(fetch(`/search?q=${query}`)),
391
+ map((res) => res.json()),
392
+ catchErrors([]), // Return empty array on error
393
+ )
394
+ ),
395
+ );
396
+
397
+ searchResults.subscribe((results) => updateUI(results));
398
+ ```
399
+
400
+ ### Real-Time Dashboard
401
+
402
+ ```ts
403
+ import { filter, pipe, scan, throttle } from "@okikio/observables";
404
+
405
+ const dashboardData = pipe(
406
+ webSocketEvents,
407
+ filter((event) => event.type === "metric"), // Only metric events
408
+ scan((acc, event) => ({ // Build running totals
409
+ total: acc.total + event.value,
410
+ count: acc.count + 1,
411
+ average: (acc.total + event.value) / (acc.count + 1),
412
+ }), { total: 0, count: 0, average: 0 }),
413
+ throttle(1000), // Update UI max once per second
414
+ );
415
+
416
+ dashboardData.subscribe((stats) => updateDashboard(stats));
417
+ ```
418
+
419
+ ### Custom Operators
420
+
421
+ ```ts
422
+ import { createOperator, createStatefulOperator } from "@okikio/observables";
423
+
424
+ // Simple transformation
425
+ function double<T extends number>() {
426
+ return createOperator<T, T>({
427
+ name: "double",
428
+ transform(value, controller) {
429
+ controller.enqueue(value * 2);
430
+ },
431
+ });
432
+ }
433
+
434
+ // Stateful operation
435
+ function movingAverage(windowSize: number) {
436
+ return createStatefulOperator<number, number, number[]>({
437
+ name: "movingAverage",
438
+ createState: () => [],
439
+
440
+ transform(value, arr, controller) {
441
+ arr.push(value);
442
+ if (arr.length > windowSize) arr.shift();
443
+
444
+ const avg = arr.reduce((sum, n) => sum + n, 0) / arr.length;
445
+ controller.enqueue(avg);
446
+ },
447
+ });
448
+ }
449
+ ```
450
+
451
+ ## Performance
452
+
453
+ We built this on Web Streams for good reason, native backpressure and memory
454
+ efficiency come for free. Here's what you get:
455
+
456
+ - **Web Streams Foundation**: Handles backpressure automatically, no memory
457
+ bloat
458
+ - **Pre-compiled Error Modes**: Skip runtime checks in hot paths
459
+ - **Tree Shaking**: Import only what you use (most apps need <4KB)
460
+ - **TypeScript Native**: Zero runtime overhead for type safety
461
+
462
+ Performance varies by use case, but here's how different error modes stack up:
463
+
464
+ | Error Mode | Performance | When We Use It |
465
+ | -------------- | ----------- | ------------------------- |
466
+ | `manual` | Fastest | Hot paths, custom logic |
467
+ | `ignore` | Very fast | Filtering bad data |
468
+ | `pass-through` | Fast | Error recovery, debugging |
469
+ | `throw` | Good | Fail-fast validation |
470
+
471
+ ## Comparison
472
+
473
+ | Feature | @okikio/observables | RxJS | zen-observable |
474
+ | --------------- | ------------------- | ----------- | -------------- |
475
+ | Bundle Size | <4KB | ~35KB | ~2KB |
476
+ | Operators | 19+ | 100+ | 5 |
477
+ | Error Modes | 4 modes | 1 mode | 1 mode |
478
+ | EventBus | ✅ Built-in | ❌ Separate | ❌ None |
479
+ | TC39 Compliance | ✅ Yes | ⚠️ Partial | ✅ Yes |
480
+ | TypeScript | ✅ Native | ✅ Yes | ⚠️ Basic |
481
+ | Tree Shaking | ✅ Perfect | ⚠️ Partial | ✅ Yes |
482
+ | Learning Curve | 🟢 Gentle | 🔴 Steep | 🟢 Gentle |
483
+
484
+ ## Browser Support
485
+
486
+ | Chrome | Edge | Firefox | Safari | Node | Deno | Bun |
487
+ | ------ | ---- | ------- | ------ | ---- | ---- | ---- |
488
+ | 80+ | 80+ | 72+ | 13+ | 16+ | 1.0+ | 1.0+ |
489
+
490
+ > Native support for Observables is excellent. Some advanced features like
491
+ > `Symbol.dispose` require newer environments or polyfills.
492
+
493
+ ## FAQ
494
+
495
+ ### What are Observables exactly?
496
+
497
+ Think of them as Promises that can send multiple values over time. Where a
498
+ Promise gives you one result eventually, an Observable can keep sending values,
499
+ like a stream of search results, mouse movements, or WebSocket messages.
500
+
501
+ ### Why not just use RxJS?
502
+
503
+ RxJS is powerful but can be overwhelming. We've all been there, 100+ operators,
504
+ steep learning curve, 35KB bundle size. This library gives you the essential
505
+ Observable patterns you actually use day-to-day, following the TC39 proposal so
506
+ you're future-ready.
507
+
508
+ ### EventBus vs Observable, when do I use which?
509
+
510
+ Good question! Here's how we think about it:
511
+
512
+ - **Observable**: When you're transforming data one-to-one (API calls,
513
+ processing user input)
514
+ - **EventBus**: When you need one-to-many communication (notifications,
515
+ cross-component events)
516
+
517
+ ### How should I handle errors?
518
+
519
+ Pick the mode that fits your situation:
520
+
521
+ - **`pass-through`**: Errors become values you can recover from
522
+ - **`ignore`**: Skip errors silently (great for filtering noisy data)
523
+ - **`throw`**: Fail fast for validation
524
+ - **`manual`**: Handle everything yourself
525
+
526
+ ### Is this actually production ready?
527
+
528
+ We use it in production. It follows the TC39 proposal, has comprehensive tests,
529
+ and handles resource management properly. The Web Streams foundation is
530
+ battle-tested across browsers and runtimes.
531
+
532
+ ## Contributing
533
+
534
+ Contributions are welcome. This project targets Deno v2+ and keeps a tight
535
+ feedback loop around formatting, linting, docs, tests, and npm packaging, so a
536
+ good contribution usually starts by getting the local validation commands
537
+ working first.
538
+
539
+ Install Deno with [mise](https://mise.jdx.dev/) or by following the
540
+ [manual installation guide](https://deno.land/manual/getting_started/installation).
541
+
542
+ ### Setup with Mise
543
+
544
+ ```bash
545
+ curl https://mise.run | sh
546
+ echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
547
+ ```
548
+
549
+ Then install the toolchain:
550
+
551
+ ```bash
552
+ mise install
553
+ ```
554
+
555
+ ### Validate your change
556
+
557
+ ```bash
558
+ deno fmt
559
+ deno lint
560
+ deno check **/*.ts
561
+ deno doc --lint mod.ts
562
+ deno task test
563
+ deno task build:npm
564
+ ```
565
+
566
+ If your change is performance-sensitive, also run:
567
+
568
+ ```bash
569
+ deno task bench
570
+ ```
571
+
572
+ This repository uses
573
+ [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), so
574
+ please format commit messages accordingly.
575
+
576
+ ## License
577
+
578
+ See the [LICENSE](./LICENSE) file for license rights and limitations (MIT).
@@ -0,0 +1,20 @@
1
+ declare global {
2
+ interface Error {
3
+ cause?: unknown;
4
+ }
5
+ }
6
+ export {};
7
+ declare global {
8
+ interface PromiseConstructor {
9
+ /**
10
+ * Creates a Promise that can be resolved or rejected using provided functions.
11
+ * @returns An object containing `promise` promise object, `resolve` and `reject` functions.
12
+ */
13
+ withResolvers<T>(): {
14
+ promise: Promise<T>;
15
+ resolve: (value: T | PromiseLike<T>) => void;
16
+ reject: (reason?: any) => void;
17
+ };
18
+ }
19
+ }
20
+ //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CACF;AAED,OAAO,EAAE,CAAC;AACV,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,kBAAkB;QAC1B;;;WAGG;QACH,aAAa,CAAC,CAAC,KAAK;YAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;YAAC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;SAAE,CAAC;KAC3H;CACF"}
@@ -0,0 +1,12 @@
1
+ // https://github.com/tc39/proposal-promise-with-resolvers/blob/3a78801e073e99217dbeb2c43ba7212f3bdc8b83/polyfills.js#L1C1-L9C2
2
+ if (Promise.withResolvers === undefined) {
3
+ Promise.withResolvers = () => {
4
+ const out = {};
5
+ out.promise = new Promise((resolve_, reject_) => {
6
+ out.resolve = resolve_;
7
+ out.reject = reject_;
8
+ });
9
+ return out;
10
+ };
11
+ }
12
+ export {};