@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,1970 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _SubscriptionObserver_state, _SubscriptionObserver_subscription, _Observable_subscribeFn;
13
+ // @filename: observable.ts
14
+ /**
15
+ * A **spec-faithful** yet ergonomic TC39-inspired Observable implementation with detailed TSDocs and examples.
16
+ *
17
+ * A **push‑based stream abstraction** for events, data, and long‑running
18
+ * operations. Think of it as a **multi‑value Promise** that keeps sending
19
+ * values until you tell it to stop.
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
+ * ## ✨ Feature Highlights
29
+ * - **Unified push + pull** – use callbacks *or* `for await … of` on the same
30
+ * stream.
31
+ * - **Cold by default** – each subscriber gets an independent execution (great
32
+ * for predictable side‑effects).
33
+ * - **Deterministic teardown** – return a function/`unsubscribe`/`[Symbol.dispose]`
34
+ * and it *always* runs once, even if the observable errors synchronously.
35
+ * - **Back‑pressure helper** – `pull()` converts to an `AsyncGenerator` backed
36
+ * by `ReadableStream` so the producer slows down when the consumer lags.
37
+ * - **Tiny surface** – <1 kB min+gzip of logic; treeshakes cleanly.
38
+ *
39
+ * ## Error Propagation Policy
40
+ * 1. **Local catch** – If your observer supplies an `error` callback, **all**
41
+ * upstream errors funnel there.
42
+ * 2. **Unhandled‑rejection style** – If no `error` handler is provided the
43
+ * exception is re‑thrown on the micro‑task queue (same timing semantics as
44
+ * an unhandled Promise rejection).
45
+ * 3. **Observer callback failures** – Exceptions thrown inside `next()` or
46
+ * `complete()` are routed to `error()` if present, otherwise bubble as in
47
+ * (2).
48
+ * 4. **Errors inside `error()`** – A second‑level failure is *always* queued to
49
+ * the micro‑task queue to avoid infinite recursion.
50
+ *
51
+ * ## Edge‑Cases & Gotchas
52
+ * - `subscribe()` can synchronously call `complete()`/`error()` and still have
53
+ * its teardown captured – **ordering is guaranteed**.
54
+ * - Subscribing twice to a *cold* observable triggers two side‑effects (e.g.
55
+ * two HTTP requests). Share the source if you want fan‑out.
56
+ * - Infinite streams leak unless you call `unsubscribe()` or wrap them in a
57
+ * `using` block.
58
+ * - The helper `pull()` encodes thrown errors as `ObservableError` *values* so
59
+ * buffered items are not lost – remember to `instanceof` check if you rely
60
+ * on it.
61
+ *
62
+ * @example Common Patterns
63
+ * ```ts
64
+ * // DOM events → Observable
65
+ * const clicks = new Observable<Event>(obs => {
66
+ * const h = (e: Event) => obs.next(e);
67
+ * button.addEventListener("click", h);
68
+ * return () => button.removeEventListener("click", h);
69
+ * });
70
+ *
71
+ * // HTTP polling every 5 s
72
+ * const poll = new Observable<Response>(obs => {
73
+ * const id = setInterval(async () => {
74
+ * try { obs.next(await fetch("/api/data")); }
75
+ * catch (e) { obs.error(e); }
76
+ * }, 5000);
77
+ * return () => clearInterval(id);
78
+ * });
79
+ *
80
+ * // WebSocket stream with graceful close
81
+ * const live = new Observable<string>(obs => {
82
+ * const ws = new WebSocket("wss://example.com");
83
+ * ws.onmessage = e => obs.next(e.data);
84
+ * ws.onerror = e => obs.error(e);
85
+ * ws.onclose = () => obs.complete();
86
+ * return () => ws.close();
87
+ * });
88
+ * ```
89
+ *
90
+ * @example Basic subscription:
91
+ * ```ts
92
+ * import { Observable } from './observable.ts';
93
+ *
94
+ * // Emit 1,2,3 then complete
95
+ * const subscription = Observable.of(1, 2, 3).subscribe({
96
+ * start(sub) { console.log('Subscribed'); },
97
+ * next(val) { console.log('Value:', val); },
98
+ * complete() { console.log('Complete'); }
99
+ * });
100
+ *
101
+ * // Cancel manually if needed
102
+ * subscription.unsubscribe();
103
+ * ```
104
+ *
105
+ * @example Resource-safe usage with `using` statement:
106
+ * ```ts
107
+ * import { Observable } from './observable.ts';
108
+ *
109
+ * {
110
+ * using subscription = Observable.of(1, 2, 3).subscribe({
111
+ * next(val) { console.log('Value:', val); }
112
+ * });
113
+ *
114
+ * // Code that uses the subscription
115
+ * doSomething();
116
+ *
117
+ * } // Subscription automatically unsubscribed at block end
118
+ * ```
119
+ *
120
+ * @example Simple async iteration:
121
+ * ```ts
122
+ * import { Observable } from './observable.ts';
123
+ *
124
+ * (async () => {
125
+ * for await (const x of Observable.of('a', 'b', 'c')) {
126
+ * console.log(x);
127
+ * }
128
+ * })();
129
+ * ```
130
+ *
131
+ * @example Pull with backpressure:
132
+ * ```ts
133
+ * import { Observable } from './observable.ts';
134
+ *
135
+ * const nums = Observable.from([1,2,3,4,5]);
136
+ * (async () => {
137
+ * for await (const n of nums.pull({ strategy: { highWaterMark: 2 } })) {
138
+ * console.log('Pulled:', n);
139
+ * await new Promise(r => setTimeout(r, 1000)); // Slow consumer
140
+ * }
141
+ * })();
142
+ * ```
143
+ *
144
+ * ## Spec Compliance & Notable Deviations
145
+ * | Area | Proposal Behaviour | This Library |
146
+ * |----------------------------|----------------------------------------|-----------------------------------------------------------------------------------------|
147
+ * | `subscribe` parameters | Only **observer object** | Adds `(next, error?, complete?)` triple‑param overload. |
148
+ * | Teardown shape | Function or `{ unsubscribe() }` | Also honours `[Symbol.dispose]` **and** `[Symbol.asyncDispose]`. |
149
+ * | Pull‑mode iteration | *Not in spec* | `pull()` helper returns an `AsyncGenerator` with `ReadableStream`‑backed back‑pressure. |
150
+ * | Error propagation in pull | Stream **error** ends iteration | Error encoded as `ObservableError` value so buffered items drain first. |
151
+ * | `Symbol.toStringTag` | Optional | Provided for `Observable` and `SubscriptionObserver`. |
152
+ *
153
+ * Anything not listed above matches the TC39 draft (**May 2025**).
154
+ *
155
+ * ## Lifecycle State Machine
156
+ * ```text
157
+ * (inactive) --subscribe()--> [ active ]
158
+ * ^ | next()
159
+ * | unsubscribe()/error() | complete()
160
+ * |<------------------------| (closed)
161
+ * ```
162
+ * *Teardown executes exactly once on the leftward arrow.*
163
+ *
164
+ * @example Type‑Parameter Primer
165
+ * ```ts
166
+ * Observable<number> // counter
167
+ * Observable<Response> // fetch responses
168
+ * Observable<{x:number;y:number}> // mouse coords
169
+ * Observable<never> // signal‑only (no payload)
170
+ * Observable<string | ErrorPayload> // unions are fine
171
+ * ```
172
+ *
173
+ * @example Interop Cheat‑Sheet
174
+ * ```ts
175
+ * // Promise → Observable (single value then complete)
176
+ * Observable.from(fetch("/api"));
177
+ *
178
+ * // Observable → async iterator (back‑pressure aware)
179
+ * for await (const chunk of obs) {
180
+ * processChunk(chunk);
181
+ * }
182
+ *
183
+ * // Observable → Promise (first value only)
184
+ * const first = (await obs.pull().next()).value;
185
+ * ```
186
+ *
187
+ * ## Performance Cookbook (pull())
188
+ * | Producer speed | Consumer speed | Suggested `highWaterMark` | Notes |
189
+ * |---------------:|---------------:|--------------------------:|-----------------------------------------|
190
+ * | 🔥 Very fast | 🐢 Slow | 1‑8 | Minimal RAM; heavy throttling. |
191
+ * | ⚡ Fast | 🚶 Moderate | 16‑64 (default 64) | Good balance for most apps. |
192
+ * | 🚀 Bursty | 🚀 Bursty | 128‑512 | Smooths spikes at the cost of memory. |
193
+ *
194
+ * ➜ If RSS climbs steadily, halve `highWaterMark`; if you’re dropping messages
195
+ * under load, raise it (RAM permitting).
196
+ *
197
+ * ## Memory Management
198
+ *
199
+ * **Critical**: Infinite Observables need manual cleanup via `unsubscribe()` or `using` blocks
200
+ * to prevent memory leaks. Finite Observables auto-cleanup on complete/error.
201
+ *
202
+ * @example Quick start - DOM events
203
+ * ```ts
204
+ * const clicks = new Observable(observer => {
205
+ * const handler = e => observer.next(e);
206
+ * button.addEventListener('click', handler);
207
+ * return () => button.removeEventListener('click', handler);
208
+ * });
209
+ *
210
+ * using subscription = clicks.subscribe(event => console.log('Clicked!'));
211
+ * // Auto-cleanup when leaving scope
212
+ * ```
213
+ *
214
+ * @example Network with backpressure
215
+ * ```ts
216
+ * const dataStream = new Observable(observer => {
217
+ * const ws = new WebSocket('ws://api.com/live');
218
+ * ws.onmessage = e => observer.next(JSON.parse(e.data));
219
+ * ws.onerror = e => observer.error(e);
220
+ * return () => ws.close();
221
+ * });
222
+ *
223
+ * // Consume at controlled pace
224
+ * for await (const data of dataStream.pull({ strategy: { highWaterMark: 10 } })) {
225
+ * await processSlowly(data); // Producer pauses when buffer fills
226
+ * }
227
+ * ```
228
+ *
229
+ * @example Testing & Debugging Tips
230
+ * ```ts
231
+ * import { expect, test } from "jsr:@libs/testing@^5";
232
+ *
233
+ * test("emits three ticks then completes", async () => {
234
+ * const ticks = Observable.of(1, 2, 3);
235
+ * const out: number[] = [];
236
+ * for await (const n of ticks) out.push(n);
237
+ * expect(out).toEqual([1, 2, 3]);
238
+ * });
239
+ *
240
+ * // Quick console probe
241
+ * obs.subscribe(v => console.log("[OBS]", v));
242
+ * ```
243
+ *
244
+ * ## FAQ
245
+ * - **Why does my network request fire twice?** Cold observables run once per
246
+ * subscribe. Reuse a single subscription or share the source.
247
+ * - **Why does `next()` throw after `complete()`?** The stream is closed; calls
248
+ * are ignored by design.
249
+ * - **Memory leak on interval** , Infinite streams require `unsubscribe()` or
250
+ * `using`.
251
+ *
252
+ * @module
253
+ */
254
+ import "./_dnt.polyfills.js";
255
+ import { assertObservableError, ObservableError } from "./error.js";
256
+ import { Symbol } from "./symbol.js";
257
+ /**
258
+ * Central registry of subscription state.
259
+ *
260
+ * Using a WeakMap allows us to:
261
+ * 1. Associate state with subscription objects without extending them
262
+ * 2. Let the garbage collector automatically clean up entries when subscriptions are no longer referenced
263
+ * 3. Hide implementation details from users
264
+ */
265
+ export const SubscriptionStateMap = new WeakMap();
266
+ /**
267
+ * Creates a new Subscription object with properly initialized state.
268
+ *
269
+ * We validate observer methods early, ensuring type errors are caught
270
+ * at subscription time rather than during event emission.
271
+ *
272
+ * The returned Subscription includes support for:
273
+ * - Manual cancellation via `unsubscribe()`
274
+ * - Automatic cleanup via `using` blocks (Symbol.dispose)
275
+ * - Async cleanup contexts (Symbol.asyncDispose)
276
+ *
277
+ * @throws TypeError if observer methods are present but not functions
278
+ * @internal
279
+ */
280
+ export function createSubscription(observer, opts) {
281
+ // Observer's methods should be functions if they exist
282
+ if (observer.next !== undefined && typeof observer.next !== "function") {
283
+ throw new TypeError("Observer.next must be a function");
284
+ }
285
+ if (observer.error !== undefined && typeof observer.error !== "function") {
286
+ throw new TypeError("Observer.error must be a function");
287
+ }
288
+ if (observer.complete !== undefined && typeof observer.complete !== "function") {
289
+ throw new TypeError("Observer.complete must be a function");
290
+ }
291
+ // Create a local statemap to speed up access during hot-paths
292
+ const stateMap = {
293
+ closed: false,
294
+ observer,
295
+ cleanup: null,
296
+ removeAbortHandler: null,
297
+ };
298
+ /* -------------------------------------------------------------------
299
+ * Create the Subscription facade (spec: CreateSubscription()).
300
+ * ------------------------------------------------------------------- */
301
+ const subscription = {
302
+ get [Symbol.toStringTag]() {
303
+ return "Subscription";
304
+ },
305
+ /**
306
+ * Returns whether this subscription is closed.
307
+ *
308
+ * A subscription becomes closed after:
309
+ * - Explicit call to unsubscribe()
310
+ * - Error notification
311
+ * - Complete notification
312
+ *
313
+ * Once closed, no further events will be delivered to the observer,
314
+ * and resources associated with the subscription are released.
315
+ */
316
+ get closed() {
317
+ return stateMap.closed;
318
+ },
319
+ /**
320
+ * Cancels the subscription and releases resources.
321
+ *
322
+ * - Safe to call multiple times (idempotent)
323
+ * - Synchronously performs cleanup
324
+ * - Marks subscription as closed
325
+ * - Prevents further observer notifications
326
+ *
327
+ * This is the primary method for consumers to explicitly
328
+ * terminate a subscription when they no longer need it.
329
+ */
330
+ unsubscribe() {
331
+ closeSubscription(this, stateMap);
332
+ },
333
+ // Support `using` disposal for automatic resource management
334
+ [Symbol.dispose]() {
335
+ this.unsubscribe();
336
+ },
337
+ // Support async disposal patterns
338
+ [Symbol.asyncDispose]() {
339
+ return Promise.resolve(this.unsubscribe());
340
+ },
341
+ };
342
+ // Adds support for unsubscribing via AbortSignals
343
+ const abortHandler = () => subscription?.unsubscribe();
344
+ const removeAbortHandler = () => opts?.signal?.removeEventListener("abort", abortHandler);
345
+ opts?.signal?.addEventListener?.("abort", abortHandler, { once: true });
346
+ stateMap.removeAbortHandler = removeAbortHandler;
347
+ // Initialize shared state
348
+ SubscriptionStateMap.set(subscription, stateMap);
349
+ return subscription;
350
+ }
351
+ export function markSubscriptionClosed(state, returnObserver = false) {
352
+ if (!state || state.closed)
353
+ return null;
354
+ // Capture observer BEFORE marking closed (for spec compliance)
355
+ const observer = state.observer;
356
+ // Mark as closed (this is what SubscriptionClosed checks)
357
+ state.closed = true;
358
+ state.observer = null;
359
+ // Return observer if requested (enables spec-compliant error/complete)
360
+ if (returnObserver)
361
+ return observer;
362
+ }
363
+ /**
364
+ * Perform cleanup if available. Safe to call multiple times.
365
+ */
366
+ export function performSubscriptionCleanup(subscription, state) {
367
+ if (!state)
368
+ return;
369
+ // Cache cleanup, abort signal and the abort handler before clearing
370
+ let cleanup = state.cleanup;
371
+ let removeAbortHandler = state.removeAbortHandler;
372
+ // Only clean if we have something to clean
373
+ if (!cleanup && !removeAbortHandler)
374
+ return;
375
+ // Clear references first
376
+ state.cleanup = null;
377
+ state.removeAbortHandler = null;
378
+ // Remove the abort handler
379
+ removeAbortHandler?.();
380
+ // Run teardown (existing logic preserved)
381
+ try {
382
+ cleanupSubscription(cleanup);
383
+ }
384
+ finally {
385
+ SubscriptionStateMap.delete(subscription);
386
+ cleanup = null;
387
+ removeAbortHandler = null;
388
+ }
389
+ }
390
+ /**
391
+ * Marks a subscription as closed and schedules necessary cleanup.
392
+ *
393
+ * This is the centralized implementation for all subscription termination paths:
394
+ * - Manual unsubscribe()
395
+ * - Observer.error()
396
+ * - Observer.complete()
397
+ *
398
+ * The function ensures:
399
+ * 1. Idempotency (safe to call multiple times)
400
+ * 2. Cleanup happens exactly once
401
+ * 3. State is properly cleared to prevent memory leaks
402
+ * 4. WeakMap entry is removed to aid garbage collection
403
+ *
404
+ * @param subscription - The subscription to close
405
+ * @internal
406
+ */
407
+ export function closeSubscription(subscription, stateMap) {
408
+ const state = stateMap ?? SubscriptionStateMap.get(subscription);
409
+ const closed = markSubscriptionClosed(state);
410
+ if (closed === null)
411
+ return;
412
+ // If we have an observer, perform cleanup
413
+ performSubscriptionCleanup(subscription, state);
414
+ }
415
+ /**
416
+ * Handles the actual cleanup process for a subscription.
417
+ *
418
+ * The spec allows three different types of cleanup values:
419
+ * 1. Function: Called directly
420
+ * 2. Object with unsubscribe method: unsubscribe() is called
421
+ * 3. (deviate from spec) Object with Symbol.dispose/asyncDispose: dispose() is called
422
+ *
423
+ * Any errors during cleanup are reported asynchronously to prevent
424
+ * them from disrupting the unsubscribe flow.
425
+ *
426
+ * @param cleanup - Function or object to perform cleanup
427
+ * @internal
428
+ */
429
+ function cleanupSubscription(cleanup) {
430
+ let temp = cleanup;
431
+ cleanup = null;
432
+ if (!temp)
433
+ return;
434
+ try {
435
+ if (typeof temp === "function")
436
+ temp();
437
+ else if (typeof temp === "object") {
438
+ if (typeof temp.unsubscribe === "function") {
439
+ temp.unsubscribe();
440
+ }
441
+ else if (typeof temp[Symbol.asyncDispose] === "function") {
442
+ temp[Symbol.asyncDispose]();
443
+ }
444
+ else if (typeof temp[Symbol.dispose] === "function") {
445
+ temp[Symbol.dispose]();
446
+ }
447
+ }
448
+ }
449
+ catch (err) {
450
+ // Report cleanup errors asynchronously to avoid disrupting the unsubscribe flow
451
+ queueMicrotask(() => {
452
+ throw err;
453
+ });
454
+ }
455
+ temp = null;
456
+ }
457
+ /**
458
+ * Wraps an observer with key guarantees required by the Observable specification.
459
+ *
460
+ * SubscriptionObserver is a critical component that ensures:
461
+ *
462
+ * 1. The observer contract is honored correctly
463
+ * 2. Notifications stop after a subscription is closed
464
+ * 3. Error/complete notifications properly terminate the subscription
465
+ * 4. Observer methods are called with the correct `this` context
466
+ * 5. Errors are properly propagated according to spec
467
+ *
468
+ * This wrapper acts as the intermediary between the Observable producer
469
+ * and the consumer-provided Observer.
470
+ *
471
+ * @typeParam T - The type of values delivered by the parent Observable.
472
+ */
473
+ export class SubscriptionObserver {
474
+ /**
475
+ * Returns whether this observer's subscription is closed.
476
+ *
477
+ * Uses the single source of truth for closed state from SubscriptionStateMap.
478
+ * This property is used by subscriber functions to check if they should
479
+ * continue delivering events.
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * const timer = new Observable(observer => {
484
+ * const id = setInterval(() => {
485
+ * if (!observer.closed) {
486
+ * observer.next(Date.now());
487
+ * }
488
+ * }, 1000);
489
+ * return () => clearInterval(id);
490
+ * });
491
+ * ```
492
+ */
493
+ get closed() {
494
+ const state = __classPrivateFieldGet(this, _SubscriptionObserver_state, "f");
495
+ if (!state)
496
+ return true;
497
+ return state.closed ?? true;
498
+ }
499
+ /**
500
+ * Creates a new SubscriptionObserver attached to the given subscription.
501
+ *
502
+ * @param subscription - The subscription that created this observer
503
+ */
504
+ constructor(subscription) {
505
+ /** Cached state map to improve perf. */
506
+ _SubscriptionObserver_state.set(this, void 0);
507
+ /** Reference to the subscription that created this observer */
508
+ _SubscriptionObserver_subscription.set(this, null);
509
+ __classPrivateFieldSet(this, _SubscriptionObserver_subscription, subscription, "f");
510
+ if (subscription) {
511
+ __classPrivateFieldSet(this, _SubscriptionObserver_state, SubscriptionStateMap.get(subscription), "f");
512
+ if (!__classPrivateFieldGet(this, _SubscriptionObserver_state, "f"))
513
+ throw new Error("Subscription state not found");
514
+ }
515
+ }
516
+ /**
517
+ * Delivers the next value to the observer if the subscription is open.
518
+ *
519
+ * This is typically the "hot path" in an Observable implementation,
520
+ * as it's called for every emitted value. Key behaviors:
521
+ *
522
+ * 1. Silently returns if subscription is closed (no errors)
523
+ * 2. Properly preserves observer's `this` context
524
+ * 3. Catches and handles errors thrown from observer.next
525
+ * 4. Forwards errors to observer.error when available
526
+ *
527
+ * Performance Considerations:
528
+ * - Minimizes property access chains
529
+ * - Early returns for closed subscriptions
530
+ * - Type checking to avoid calling non-functions
531
+ *
532
+ * @param value - The value to deliver to the observer
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * // Inside a subscriber function:
537
+ * observer.next(42); // Delivers value to consumer
538
+ * ```
539
+ *
540
+ * > Note: Error-propagation policy
541
+ * > ─────────────────────────────
542
+ * > * If the *observer supplies its own `error()` handler*,
543
+ * > that handler is considered the “catch-block” for the stream.
544
+ * > ↳ Any exception that happens *inside* the user’s `next()` /
545
+ * > `complete()` callbacks is forwarded to `error(err)` **once**.
546
+ * > ↳ If `error()` itself throws, we still delegate to `HostReportErrors` (≈ “unhandled-promise rejection”)
547
+ * > (i.e. `queueMicrotask`), exactly as the proposal specifies.
548
+ * >
549
+ * > * If the observer does **not** implement `error()`, we fall back to the
550
+ * > spec’s `HostReportErrors` behaviour (queueMicrotask + throw) so the host
551
+ * > surfaces the error just like an uncaught Promise rejection.
552
+ * >
553
+ * > Rationale – Think of `error()` as the moral equivalent of a `.catch()`
554
+ * > on a Promise. Once a catch exists, the host no longer warns about
555
+ * > “unhandled” rejections; we mirror that mental model here.
556
+ * >
557
+ * > Spec reference – This diverges slightly from stage-1, which still
558
+ * > invokes HostReportErrors if the *error handler itself* throws. We
559
+ * > intentionally suppress that extra surfacing for the reasons above.
560
+ */
561
+ next(value) {
562
+ const state = __classPrivateFieldGet(this, _SubscriptionObserver_state, "f");
563
+ if (!state || state.closed)
564
+ return;
565
+ // Fast-path optimization to avoid long request chains
566
+ const observer = state.observer;
567
+ if (!observer)
568
+ return;
569
+ const nextFn = observer.next;
570
+ if (typeof nextFn !== "function")
571
+ return;
572
+ try {
573
+ nextFn.call(observer, value);
574
+ }
575
+ catch (err) {
576
+ const errorFn = observer.error;
577
+ if (typeof errorFn === "function") {
578
+ try {
579
+ errorFn.call(observer, err);
580
+ }
581
+ catch (err) {
582
+ queueMicrotask(() => {
583
+ throw err;
584
+ });
585
+ }
586
+ } // Either a user callback or HostReportErrors emulation (queueMicrotask).
587
+ else {
588
+ queueMicrotask(() => {
589
+ throw err;
590
+ });
591
+ }
592
+ }
593
+ }
594
+ /**
595
+ * Delivers an error notification to the observer, then closes the subscription.
596
+ *
597
+ * Error is a terminal operation - after calling it:
598
+ * 1. The subscription is immediately marked as closed
599
+ * 2. Resources are released via unsubscribe()
600
+ * 3. No further notifications will be delivered
601
+ *
602
+ * Error Handling:
603
+ * - If observer.error exists, the error is delivered there
604
+ * - If observer.error throws, the error is reported asynchronously
605
+ * - If no error handler exists, the error is reported asynchronously
606
+ *
607
+ * > Note: Even for "silent" errors (no error handler), we still close
608
+ * the subscription and report the error to the host.
609
+ *
610
+ * ##
611
+ *
612
+ * @example Important Timing Consideration
613
+ * When this method is called during the subscriber function execution (before it returns),
614
+ * there's a potential race condition with cleanup functions.
615
+ *
616
+ * Consider:
617
+ * ```ts
618
+ * new Observable(observer => {
619
+ * observer.error(new Error()); // Triggers unsubscribe here
620
+ * return () => cleanupResources(); // But this hasn't been returned yet!
621
+ * });
622
+ * ```
623
+ *
624
+ * Our implementation handles this by:
625
+ * 1. Marking the subscription as closed immediately
626
+ * 2. Scheduling actual cleanup in a microtask to ensure the teardown function
627
+ * has time to be captured and stored
628
+ *
629
+ * This ensures resources are properly cleaned up even when error/complete
630
+ * is called synchronously during subscription setup.
631
+ *
632
+ * @param err - The error to deliver
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * // Inside a subscriber function:
637
+ * try {
638
+ * doRiskyOperation();
639
+ * } catch (err) {
640
+ * observer.error(err); // Terminates the subscription with error
641
+ * }
642
+ * ```
643
+ *
644
+ * > Note: {@link SubscriptionObserver.next | Review the error propagation policy in `next()` on how errors propagate, the behaviour is not obvious on first glance.}
645
+ */
646
+ error(err) {
647
+ const state = __classPrivateFieldGet(this, _SubscriptionObserver_state, "f");
648
+ // Mark closed and get observer in one call
649
+ const observer = markSubscriptionClosed(state, true);
650
+ if (observer === null)
651
+ return;
652
+ const errorFn = observer?.error;
653
+ if (typeof errorFn === "function") {
654
+ try {
655
+ errorFn.call(observer, err);
656
+ }
657
+ catch (innerErr) {
658
+ queueMicrotask(() => {
659
+ throw innerErr;
660
+ });
661
+ }
662
+ } // No error handler, delegate to host
663
+ else {
664
+ queueMicrotask(() => {
665
+ throw err;
666
+ });
667
+ }
668
+ // Perform cleanup after marking closed
669
+ performSubscriptionCleanup(__classPrivateFieldGet(this, _SubscriptionObserver_subscription, "f"), state);
670
+ // Clear reference
671
+ __classPrivateFieldSet(this, _SubscriptionObserver_subscription, null, "f");
672
+ }
673
+ /**
674
+ * Signals successful completion of the observable sequence.
675
+ *
676
+ * Complete is a terminal operation - after calling it:
677
+ * 1. The subscription is immediately marked as closed
678
+ * 2. Resources are released via unsubscribe()
679
+ * 3. No further notifications will be delivered
680
+ *
681
+ * If observer.complete throws an error:
682
+ * - The error is forwarded to observer.error if available
683
+ * - Otherwise, it's reported asynchronously to the host
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * // Inside a subscriber function:
688
+ * observer.next(1);
689
+ * observer.next(2);
690
+ * observer.complete(); // Terminates the subscription normally
691
+ * ```
692
+ *
693
+ * > Note: {@link SubscriptionObserver.next | Review the error propagation policy in `next()` on how errors propagate, the behaviour is not obvious on first glance.}
694
+ */
695
+ complete() {
696
+ const state = __classPrivateFieldGet(this, _SubscriptionObserver_state, "f");
697
+ if (!state || state.closed)
698
+ return;
699
+ // Mark closed and get observer in one call
700
+ const observer = markSubscriptionClosed(state, true);
701
+ if (observer === null)
702
+ return;
703
+ const completeFn = observer?.complete;
704
+ if (typeof completeFn === "function") {
705
+ try {
706
+ completeFn.call(observer);
707
+ }
708
+ catch (err) {
709
+ const errorFn = observer?.error;
710
+ if (typeof errorFn === "function") {
711
+ try {
712
+ errorFn.call(observer, err);
713
+ }
714
+ catch (innerErr) {
715
+ queueMicrotask(() => {
716
+ throw innerErr;
717
+ });
718
+ }
719
+ } // Either a user callback or HostReportErrors emulation (queueMicrotask).
720
+ else {
721
+ queueMicrotask(() => {
722
+ throw err;
723
+ });
724
+ }
725
+ }
726
+ }
727
+ // Perform cleanup after marking closed
728
+ performSubscriptionCleanup(__classPrivateFieldGet(this, _SubscriptionObserver_subscription, "f"), state);
729
+ // Clear reference
730
+ __classPrivateFieldSet(this, _SubscriptionObserver_subscription, null, "f");
731
+ }
732
+ /**
733
+ * Returns a standard string tag for the object.
734
+ * Used by Object.prototype.toString.
735
+ */
736
+ get [(_SubscriptionObserver_state = new WeakMap(), _SubscriptionObserver_subscription = new WeakMap(), Symbol.toStringTag)]() {
737
+ return "Subscription Observer";
738
+ }
739
+ }
740
+ /**
741
+ * Observale - A push-based stream for handling async data over time.
742
+ *
743
+ * **What it is**: Like a "smart Promise" that can emit multiple values and provides
744
+ * unified patterns for resource management, error handling, and subscription lifecycle.
745
+ *
746
+ * Observable is the central type in this library, representing a push-based
747
+ * source of values that can be subscribed to. It delivers values to observers
748
+ * and provides lifecycle guarantees around subscription and cleanup.
749
+ *
750
+ * Key guarantees:
751
+ * 1. Lazy execution - nothing happens until `subscribe()` is called
752
+ * 2. Multiple independent subscriptions to the same Observable
753
+ * 3. Each subscriber executes and cleans up independently.
754
+ * 4. Cleanups are deterministic one‑time resource disposal, the occur when subscriptions are cancelled, error or complete
755
+ *
756
+ * Extensions beyond the TC39 proposal:
757
+ * - Pull API via AsyncIterable interface
758
+ * - Using/await using support via Symbol.dispose/asyncDispose
759
+ *
760
+ * Gotchas:
761
+ * - Two subscribers → two side‑effects on a cold stream.
762
+ * - Remember to cancel infinite observables.
763
+ * - Calling `next()` after `complete()` is a no‑op.
764
+ * - Errors in observer callbacks go to error handler if provided, else global reporting.
765
+ * - Synchronous completion during subscribe still captures cleanup functions.
766
+ *
767
+ * @typeParam T - Type of values emitted by this Observable
768
+ */
769
+ export class Observable {
770
+ /**
771
+ * Creates a new Observable with the given subscriber function.
772
+ *
773
+ * **Important**: This just stores your function - nothing executes until `subscribe()` is called.
774
+ * Think of it like writing a recipe vs actually cooking.
775
+ *
776
+ * The subscriber function is the heart of an Observable. It:
777
+ * 1. Is called once per subscription (not at Observable creation time)
778
+ * 2. Receives a SubscriptionObserver to send values through
779
+ * 3. Can optionally return a cleanup function or subscription
780
+ *
781
+ * Nothing happens when an Observable is created - execution only
782
+ * begins when subscribe() is called.
783
+ *
784
+ * @param subscribeFn - Function that implements the Observable's behavior
785
+ *
786
+ * Your subscriber function receives a `SubscriptionObserver` to:
787
+ * - `observer.next(value)` - Emit a value
788
+ * - `observer.error(err)` - Emit error (terminates)
789
+ * - `observer.complete()` - Signal completion (terminates)
790
+ * - `observer.closed` - Check if subscription is still active
791
+ *
792
+ * @throws TypeError if subscribeFn is not a function
793
+ * @throws TypeError if Observable is called without "new"
794
+ *
795
+ * @example Timer with cleanup
796
+ * ```ts
797
+ * // Timer that emits the current timestamp every second
798
+ * const timer = new Observable(observer => {
799
+ * console.log('Subscription started!');
800
+ * const id = setInterval(() => {
801
+ * observer.next(Date.now());
802
+ * }, 1000);
803
+ *
804
+ * // Return cleanup function
805
+ * return () => {
806
+ * console.log('Cleaning up timer');
807
+ * clearInterval(id);
808
+ * };
809
+ * });
810
+ * ```
811
+ *
812
+ * @example Async operation with error handling
813
+ * ```ts
814
+ * const fetch = new Observable(observer => {
815
+ * const controller = new AbortController();
816
+ *
817
+ * fetch('/api/data', { signal: controller.signal })
818
+ * .then(res => res.json())
819
+ * .then(data => {
820
+ * observer.next(data);
821
+ * observer.complete();
822
+ * })
823
+ * .catch(err => observer.error(err));
824
+ *
825
+ * return () => controller.abort(); // Cleanup
826
+ * });
827
+ * ```
828
+ */
829
+ constructor(subscribeFn) {
830
+ /** The subscriber function provided when the Observable was created */
831
+ _Observable_subscribeFn.set(this, void 0);
832
+ if (typeof subscribeFn !== "function") {
833
+ throw new TypeError("Observable initializer must be a function");
834
+ }
835
+ // Add check for constructor invocation
836
+ if (!(this instanceof Observable)) {
837
+ throw new TypeError("Observable must be called with new");
838
+ }
839
+ __classPrivateFieldSet(this, _Observable_subscribeFn, subscribeFn, "f");
840
+ }
841
+ /**
842
+ * Returns this Observable (required for interoperability).
843
+ *
844
+ * This method implements the TC39 Symbol.observable protocol,
845
+ * which allows foreign Observable implementations to recognize
846
+ * and interoperate with this implementation.
847
+ *
848
+ * @returns This Observable instance
849
+ */
850
+ [(_Observable_subscribeFn = new WeakMap(), Symbol.observable)]() {
851
+ return this;
852
+ }
853
+ /**
854
+ * Implementation of subscribe method (handles both overloads).
855
+ */
856
+ subscribe(observerOrNext, errorOrOpts, complete, _opts) {
857
+ // Check for invalid this context
858
+ if (this === null || this === undefined) {
859
+ throw new TypeError('Cannot read property "subscribe" of null or undefined');
860
+ }
861
+ /* -------------------------------------------------------------------
862
+ * 1. Normalise the observer – mirrors spec step 4.
863
+ * ------------------------------------------------------------------- */
864
+ const observer = (typeof observerOrNext === "function"
865
+ ? {
866
+ next: observerOrNext,
867
+ error: errorOrOpts,
868
+ complete,
869
+ }
870
+ : observerOrNext) ?? {}; // ← spec-compliant fallback for null / primitives
871
+ // Additional options to pass along AbortSignal (part of the WCIG Observables Spec., thought to implement it for convinence reasons)
872
+ const opts = (typeof observerOrNext === "function"
873
+ ? _opts
874
+ : errorOrOpts) ?? {};
875
+ /* -------------------------------------------------------------------
876
+ * 2. Create the Subscription facade (spec: CreateSubscription()).
877
+ * ------------------------------------------------------------------- */
878
+ const subscription = createSubscription(observer, opts);
879
+ /* -------------------------------------------------------------------
880
+ * 3. Wrap user observer so we enforce closed-state.
881
+ * ------------------------------------------------------------------- */
882
+ const subObserver = new SubscriptionObserver(subscription);
883
+ /* -------------------------------------------------------------------
884
+ * 4. Call observer.start(subscription) – (spec step 10).
885
+ * ------------------------------------------------------------------- */
886
+ try {
887
+ observer.start?.(subscription);
888
+ if (subscription?.closed)
889
+ return subscription; // spec step 10.d
890
+ }
891
+ catch (err) {
892
+ // WarnIfAbrupt: report, but return closed subscription
893
+ // Queue in a micro-task so it surfaces *after* current job,
894
+ // matching the spec’s “report later” intent.
895
+ queueMicrotask(() => {
896
+ // 1. Print to console for visibility
897
+ console.error(err);
898
+ // 2. Re-throw so debuggers break (optional, but common)
899
+ throw err;
900
+ });
901
+ subscription?.unsubscribe?.();
902
+ return subscription;
903
+ }
904
+ /* -------------------------------------------------------------------
905
+ * 5. Execute the user subscriber and capture its cleanup (spec step 12-16).
906
+ * ------------------------------------------------------------------- */
907
+ try {
908
+ let cleanup = __classPrivateFieldGet(this, _Observable_subscribeFn, "f")?.call(undefined, subObserver) ?? null;
909
+ // Validate the cleanup value if provided
910
+ if (cleanup !== undefined && cleanup !== null) {
911
+ if (!(typeof cleanup === "function" ||
912
+ typeof cleanup?.unsubscribe === "function" ||
913
+ typeof cleanup?.[Symbol.dispose] === "function" ||
914
+ typeof cleanup?.[Symbol.asyncDispose] ===
915
+ "function")) {
916
+ throw new TypeError("Expected subscriber to return a function, an unsubscribe object, a disposable with a [Symbol.dispose] method, an async-disposable with a [Symbol.asyncDispose] method, or undefined/null");
917
+ }
918
+ }
919
+ // Store the cleanup function in the subscription state
920
+ const state = SubscriptionStateMap.get(subscription);
921
+ if (state && cleanup)
922
+ state.cleanup = cleanup;
923
+ /**
924
+ * Handle the case where complete/error was called synchronously during the subscribe function.
925
+ * This is a critical edge case that requires special handling - when the observer
926
+ * calls `error()` or `complete()` before the subscribe function returns, we need to ensure
927
+ * that any teardown function returned by the subscriber is still executed properly.
928
+ *
929
+ * The returned teardown wouldn't have been available when `unsubscribe()` was initially
930
+ * triggered by error/complete, so we need to handle it manually here.
931
+ *
932
+ * @example
933
+ * ```ts
934
+ * const errorObservable = new Observable(observer => {
935
+ * observer.error(new Error("test error")); // Will auto-unsubscribe (but teardown hasn't been defined yet)
936
+ * log.push("after error"); // This should still run
937
+ *
938
+ * // Teardown now defined but now the subscription has been closedn
939
+ * // but resources being used haven't actually been disposed yet
940
+ * return () => {
941
+ * log.push("error teardown");
942
+ * };
943
+ * });
944
+ * ```
945
+ *
946
+ * `observer.error` fires before the teardown function is defined, so we would need to manually cleanup ourselves
947
+ * by manually running the teardown function
948
+ */
949
+ if (subscription.closed && cleanup) {
950
+ cleanupSubscription(cleanup);
951
+ }
952
+ cleanup = null;
953
+ }
954
+ catch (err) {
955
+ // 6) If their subscribeFn throws, send that as an error notification
956
+ subObserver.error(err);
957
+ }
958
+ // 7) Finally, hand back the Subscription so callers can cancel whenever they like
959
+ return subscription;
960
+ }
961
+ /**
962
+ * Enables `for await ... of observable` syntax for direct async iteration.
963
+ *
964
+ * This method allows Observables to be used in any context that accepts an AsyncIterable,
965
+ * implementing the "pull" mode of consuming an Observable.
966
+ *
967
+ * Uses default buffer size of 64 items.
968
+ *
969
+ * The implementation delegates to the `pull()` function which:
970
+ * 1. Converts push-based events to pull-based async iteration
971
+ * 2. Applies backpressure with ReadableStream
972
+ * 3. Handles proper cleanup on early termination
973
+ *
974
+ * @returns An AsyncIterator that yields values from this Observable
975
+ *
976
+ * @example
977
+ * ```ts
978
+ * const observable = Observable.of(1, 2, 3);
979
+ *
980
+ * // Using for-await-of directly on an Observable
981
+ * for await (const value of observable) {
982
+ * console.log(value); // Logs 1, 2, 3
983
+ * }
984
+ * ```
985
+ */
986
+ async *[Symbol.asyncIterator]() {
987
+ yield* pull(this);
988
+ }
989
+ pull(opts) {
990
+ return pull(this, opts);
991
+ }
992
+ /**
993
+ * Standard string tag for the object.
994
+ * Used by Object.prototype.toString.
995
+ */
996
+ get [Symbol.toStringTag]() {
997
+ return "Observable";
998
+ }
999
+ }
1000
+ /**
1001
+ * Converts Promise, an iterable, async iterable, or Observable-like object to an Observable.
1002
+ *
1003
+ * This static method is a key part of the Observable interoperability mechanism,
1004
+ * handling multiple input types in a consistent way.
1005
+ *
1006
+ * **Handles**:
1007
+ * - Arrays, Sets, Maps → sync emission
1008
+ * - Async generators → values over time
1009
+ * - Symbol.observable objects → delegates to their implementation
1010
+ *
1011
+ * Behavior depends on the input type:
1012
+ * 1. Objects with Symbol.observable - Delegates to their implementation
1013
+ * 2. Synchronous iterables - Emits all values then completes
1014
+ * 3. Asynchronous iterables - Emits values as they arrive then completes
1015
+ * 4. Promise - Emits a single value (the resovled value) then completes
1016
+ *
1017
+ * Unlike Promise.resolve, Observable.from will not return the input unchanged
1018
+ * if it's already an Observable, unless it's an instance of the exact same
1019
+ * constructor. This ensures consistent behavior across different Observable
1020
+ * implementations.
1021
+ *
1022
+ * @param input - The object to convert to an Observable
1023
+ * @returns A new Observable that emits values from the input
1024
+ *
1025
+ * @example
1026
+ * ```ts
1027
+ * // From an array
1028
+ * Observable.from([1, 2, 3]).subscribe({
1029
+ * next: val => console.log(val) // 1, 2, 3
1030
+ * });
1031
+ *
1032
+ * // From a Promise
1033
+ * Observable.from(Promise.resolve("result")).subscribe({
1034
+ * next: val => console.log(val) // "result"
1035
+ * });
1036
+ *
1037
+ * // From another Observable-like object
1038
+ * const foreign = {
1039
+ * [Symbol.observable]() {
1040
+ * return new Observable(obs => {
1041
+ * obs.next("hello");
1042
+ * obs.complete();
1043
+ * });
1044
+ * }
1045
+ * };
1046
+ * Observable.from(foreign).subscribe({
1047
+ * next: val => console.log(val) // "hello"
1048
+ * });
1049
+ * ```
1050
+ */
1051
+ Object.defineProperty(Observable, "from", {
1052
+ enumerable: true,
1053
+ configurable: true,
1054
+ writable: true,
1055
+ value: from
1056
+ });
1057
+ /**
1058
+ * Creates an Observable that synchronously emits the given values then completes.
1059
+ *
1060
+ * This is a convenience method for creating simple Observables that:
1061
+ * 1. Emit a fixed set of values synchronously
1062
+ * 2. Complete immediately after emitting all values
1063
+ * 3. Never error
1064
+ *
1065
+ * It's the Observable equivalent of `Promise.resolve()` for single values
1066
+ * or `[].values()` for multiple values.
1067
+ *
1068
+ * @param items - Values to emit
1069
+ * @returns A new Observable that emits the given values then completes
1070
+ *
1071
+ * @example
1072
+ * ```ts
1073
+ * // Create and subscribe
1074
+ * Observable.of(1, 2, 3).subscribe({
1075
+ * next: val => console.log(val), // Logs 1, 2, 3
1076
+ * complete: () => console.log('Done!')
1077
+ * });
1078
+ *
1079
+ * // Output:
1080
+ * // 1
1081
+ * // 2
1082
+ * // 3
1083
+ * // Done!
1084
+ * ```
1085
+ */
1086
+ Object.defineProperty(Observable, "of", {
1087
+ enumerable: true,
1088
+ configurable: true,
1089
+ writable: true,
1090
+ value: of
1091
+ });
1092
+ /**
1093
+ * Converts a Observable into an AsyncGenerator with backpressure control.
1094
+ *
1095
+ * This method provides more control over async iteration than the default
1096
+ * Symbol.asyncIterator implementation, allowing consumers to:
1097
+ *
1098
+ * 1. Specify a queuing strategy with a custom highWaterMark
1099
+ * 2. Control buffering behavior when the producer is faster than the consumer
1100
+ * 3. Apply backpressure to prevent memory issues with fast producers
1101
+ *
1102
+ * The implementation uses ReadableStream internally to manage buffering
1103
+ * and backpressure, pausing the producer when the buffer fills up.
1104
+ *
1105
+ * @param options - Configuration options for the pull operation
1106
+ * @returns An AsyncGenerator that yields values from this Observable
1107
+ *
1108
+ * @example
1109
+ * ```ts
1110
+ * // Buffer up to 5 items before applying backpressure
1111
+ * for await (const value of observable.pull({
1112
+ * strategy: { highWaterMark: 5 }
1113
+ * })) {
1114
+ * console.log(value);
1115
+ * // Slow consumer - producer will pause when buffer fills
1116
+ * await new Promise(r => setTimeout(r, 1000));
1117
+ * }
1118
+ * ```
1119
+ */
1120
+ Object.defineProperty(Observable, "pull", {
1121
+ enumerable: true,
1122
+ configurable: true,
1123
+ writable: true,
1124
+ value: pull
1125
+ });
1126
+ /**
1127
+ * Cached empty observable handler
1128
+ * @internal
1129
+ */
1130
+ function EMPTY(obs) {
1131
+ obs.complete();
1132
+ }
1133
+ /**
1134
+ * Creates an Observable that synchronously emits the given values then completes.
1135
+ *
1136
+ * This standalone function implements the Observable.of static method while
1137
+ * properly supporting subclassing. It's the Observable equivalent of:
1138
+ * - `Array.of()` for collections
1139
+ * - `Promise.resolve()` for single values
1140
+ *
1141
+ * Key behaviors:
1142
+ * 1. Emits values synchronously when subscribed
1143
+ * 2. Completes immediately after all values are emitted
1144
+ * 3. Never errors
1145
+ * 4. Respects the constructor it was called on for subclassing
1146
+ *
1147
+ * @param items - Values to emit
1148
+ * @returns A new Observable that emits the given values then completes
1149
+ *
1150
+ * @example
1151
+ * ```ts
1152
+ * // Basic usage
1153
+ * of(1, 2, 3).subscribe({
1154
+ * next: x => console.log(x),
1155
+ * complete: () => console.log('Done!')
1156
+ * });
1157
+ * // Output: 1, 2, 3, Done!
1158
+ *
1159
+ * // Subclassing support
1160
+ * class MyObservable extends Observable<number> {
1161
+ * // Custom methods...
1162
+ * }
1163
+ *
1164
+ * // Creates a MyObservable instance
1165
+ * const mine = MyObservable.of(1, 2, 3);
1166
+ * ```
1167
+ */
1168
+ export function of(...items) {
1169
+ const Constructor = typeof this === "function"
1170
+ ? this
1171
+ : Observable;
1172
+ const len = items.length;
1173
+ // Pre-defined handlers for common cases to avoid creating new closures
1174
+ switch (len) {
1175
+ case 0:
1176
+ return new Constructor(EMPTY);
1177
+ case 1:
1178
+ return new Constructor((obs) => {
1179
+ obs.next(items[0]);
1180
+ obs.complete();
1181
+ });
1182
+ case 2:
1183
+ return new Constructor((obs) => {
1184
+ obs.next(items[0]);
1185
+ obs.next(items[1]);
1186
+ obs.complete();
1187
+ });
1188
+ case 3:
1189
+ return new Constructor((obs) => {
1190
+ obs.next(items[0]);
1191
+ obs.next(items[1]);
1192
+ obs.next(items[2]);
1193
+ obs.complete();
1194
+ });
1195
+ default:
1196
+ // For arrays > 3 items, balance between code size and performance
1197
+ return new Constructor((obs) => {
1198
+ // Based on benchmarking:
1199
+ // - Arrays < 100: simple loop is fine (method call dominates)
1200
+ // - Arrays >= 100: unrolling provides measurable benefit
1201
+ if (len < 100) {
1202
+ for (let i = 0; i < len; i++) {
1203
+ obs.next(items[i]);
1204
+ }
1205
+ }
1206
+ else {
1207
+ // Unroll by 8 for large arrays (2.8x speedup)
1208
+ let i = 0;
1209
+ const limit = len - (len % 8);
1210
+ for (; i < limit; i += 8) {
1211
+ obs.next(items[i]);
1212
+ obs.next(items[i + 1]);
1213
+ obs.next(items[i + 2]);
1214
+ obs.next(items[i + 3]);
1215
+ obs.next(items[i + 4]);
1216
+ obs.next(items[i + 5]);
1217
+ obs.next(items[i + 6]);
1218
+ obs.next(items[i + 7]);
1219
+ }
1220
+ // Handle remainder
1221
+ for (; i < len; i++) {
1222
+ obs.next(items[i]);
1223
+ }
1224
+ }
1225
+ obs.complete();
1226
+ });
1227
+ }
1228
+ }
1229
+ /**
1230
+ * Converts an Observable-like, sync iterable, or async iterable into an Observable.
1231
+ *
1232
+ * This is the standalone implementation of Observable.from, supporting:
1233
+ * - Objects with Symbol.observable (Observable-like)
1234
+ * - Regular iterables (arrays, Maps, Sets, generators)
1235
+ * - Async iterables (async generators, ReadableStreams)
1236
+ *
1237
+ * Conversion follows these rules:
1238
+ * 1. For Symbol.observable objects: delegates to their implementation
1239
+ * 2. For Promises: resolves and emits the promise's value
1240
+ * 3. For iterables: synchronously emits all values, then completes
1241
+ * 4. For async iterables: emits values as they arrive, then completes
1242
+ *
1243
+ * This function properly supports subclassing, preserving the constructor
1244
+ * it was called on.
1245
+ *
1246
+ * @throws TypeError if input is null, undefined, or not convertible
1247
+ *
1248
+ * @example
1249
+ * ```ts
1250
+ * // From array
1251
+ * from([1, 2, 3]).subscribe(x => console.log(x));
1252
+ * // Output: 1, 2, 3
1253
+ *
1254
+ * // From Promise
1255
+ * from(Promise.resolve('done')).subscribe(x => console.log(x));
1256
+ * // Output: 'done'
1257
+ *
1258
+ * // From Map
1259
+ * from(new Map([['a', 1], ['b', 2]])).subscribe(x => console.log(x));
1260
+ * // Output: ['a', 1], ['b', 2]
1261
+ *
1262
+ * // From another Observable implementation
1263
+ * const foreign = {
1264
+ * [Symbol.observable]() {
1265
+ * return { subscribe: observer => {
1266
+ * observer.next('hello');
1267
+ * observer.complete();
1268
+ * return { unsubscribe() {} };
1269
+ * }};
1270
+ * }
1271
+ * };
1272
+ * from(foreign).subscribe(x => console.log(x));
1273
+ * // Output: 'hello'
1274
+ * ```
1275
+ */
1276
+ export function from(input, { throwError = true } = {}) {
1277
+ if (input === null || input === undefined) {
1278
+ throw new TypeError("Cannot convert undefined or null to Observable");
1279
+ }
1280
+ const Constructor = typeof this === "function"
1281
+ ? this
1282
+ : Observable;
1283
+ // Faster implementation of iteration for array-like values
1284
+ const arr = input;
1285
+ if (Array.isArray(input) || typeof arr.length === "number") {
1286
+ const len = arr.length;
1287
+ // Optimize for small arrays
1288
+ if (len === 0)
1289
+ return new Constructor(EMPTY);
1290
+ if (len === 1) {
1291
+ return new Constructor((obs) => {
1292
+ if (throwError)
1293
+ assertObservableError(arr[0], obs);
1294
+ obs.next(arr[0]);
1295
+ obs.complete();
1296
+ });
1297
+ }
1298
+ // Type check to ensure it's actually array-like
1299
+ return new Constructor((obs) => {
1300
+ try {
1301
+ // Typed arrays: no bounds checking needed, direct iteration
1302
+ // Small arrays: simple loop with early exit checks
1303
+ if (len < 100 || ArrayBuffer.isView(arr)) {
1304
+ for (let i = 0; i < len; i++) {
1305
+ if (throwError)
1306
+ assertObservableError(arr[i]);
1307
+ obs.next(arr[i]);
1308
+ if (obs.closed)
1309
+ return;
1310
+ }
1311
+ }
1312
+ else {
1313
+ // Large arrays: unroll with less frequent closed checks
1314
+ let i = 0;
1315
+ const limit = len - (len % 8);
1316
+ // Check closed once per 8 items (balanced approach)
1317
+ for (; i < limit; i += 8) {
1318
+ if (throwError) {
1319
+ assertObservableError(arr[i]);
1320
+ assertObservableError(arr[i + 1]);
1321
+ assertObservableError(arr[i + 2]);
1322
+ assertObservableError(arr[i + 3]);
1323
+ assertObservableError(arr[i + 4]);
1324
+ assertObservableError(arr[i + 5]);
1325
+ assertObservableError(arr[i + 6]);
1326
+ assertObservableError(arr[i + 7]);
1327
+ }
1328
+ obs.next(arr[i]);
1329
+ obs.next(arr[i + 1]);
1330
+ obs.next(arr[i + 2]);
1331
+ obs.next(arr[i + 3]);
1332
+ obs.next(arr[i + 4]);
1333
+ obs.next(arr[i + 5]);
1334
+ obs.next(arr[i + 6]);
1335
+ obs.next(arr[i + 7]);
1336
+ if (obs.closed)
1337
+ return;
1338
+ }
1339
+ // Handle remainder with checks
1340
+ for (; i < len; i++) {
1341
+ if (throwError)
1342
+ assertObservableError(arr[i]);
1343
+ obs.next(arr[i]);
1344
+ if (obs.closed)
1345
+ return;
1346
+ }
1347
+ }
1348
+ }
1349
+ catch (err) {
1350
+ obs.error(err);
1351
+ }
1352
+ obs.complete();
1353
+ });
1354
+ }
1355
+ // Case 1 – object with @@observable
1356
+ const observableFn = input[Symbol.observable];
1357
+ if (typeof observableFn === "function") {
1358
+ const observable = observableFn.call(input);
1359
+ // Validate the result has a subscribe method
1360
+ if (!observable || typeof observable.subscribe !== "function") {
1361
+ throw new TypeError("Object returned from [Symbol.observable]() does not implement subscribe method");
1362
+ }
1363
+ // Return directly if it's already an instance of the target constructor
1364
+ if (observable instanceof Constructor)
1365
+ return observable;
1366
+ // Otherwise, wrap it to ensure consistent behavior
1367
+ return new Constructor((observer) => {
1368
+ const sub = observable.subscribe(observer);
1369
+ return () => sub?.unsubscribe?.();
1370
+ });
1371
+ }
1372
+ // Fast implementation for Set & Maps which are generally optimized
1373
+ // by the runtime when using `for..of` loops
1374
+ if (input instanceof Set || input instanceof Map) {
1375
+ const collection = input;
1376
+ const size = collection.size;
1377
+ if (size === 0)
1378
+ return new Constructor(EMPTY);
1379
+ return new Constructor((obs) => {
1380
+ // For...of is optimized for Sets in V8
1381
+ for (const item of collection) {
1382
+ if (throwError)
1383
+ assertObservableError(item, obs);
1384
+ obs.next(item);
1385
+ if (obs.closed)
1386
+ return;
1387
+ }
1388
+ obs.complete();
1389
+ });
1390
+ }
1391
+ // Case 2 – promise
1392
+ const promise = input;
1393
+ if (typeof promise.then === "function") {
1394
+ return new Constructor((obs) => {
1395
+ promise.then((value) => {
1396
+ if (throwError)
1397
+ assertObservableError(value, obs);
1398
+ obs.next(value);
1399
+ obs.complete();
1400
+ },
1401
+ // Error during iteration
1402
+ (err) => obs.error(err));
1403
+ });
1404
+ }
1405
+ // Case 3 – synchronous iterable
1406
+ const iteratorFn = input[Symbol.iterator];
1407
+ if (typeof iteratorFn === "function") {
1408
+ return new Constructor((obs) => {
1409
+ const iterator = iteratorFn.call(input);
1410
+ try {
1411
+ for (let step = iterator.next(); !step.done; step = iterator.next()) {
1412
+ if (throwError)
1413
+ assertObservableError(step.value);
1414
+ obs.next(step.value);
1415
+ // If subscription was closed during iteration, clean up and exit
1416
+ if (obs.closed)
1417
+ break;
1418
+ }
1419
+ obs.complete();
1420
+ }
1421
+ catch (err) {
1422
+ obs.error(err);
1423
+ }
1424
+ return () => {
1425
+ if (typeof iterator?.return === "function") {
1426
+ try {
1427
+ iterator.return(); // IteratorClose
1428
+ }
1429
+ catch (err) {
1430
+ queueMicrotask(() => {
1431
+ throw err;
1432
+ });
1433
+ }
1434
+ }
1435
+ };
1436
+ });
1437
+ }
1438
+ // Case 4 – async iterable
1439
+ const asyncIteratorFn = input[Symbol.asyncIterator];
1440
+ if (typeof asyncIteratorFn === "function") {
1441
+ return new Constructor((obs) => {
1442
+ const asyncIterator = asyncIteratorFn.call(input);
1443
+ // Start consuming the async iterable
1444
+ (async () => {
1445
+ try {
1446
+ for (let step = await asyncIterator.next(); !step.done; step = await asyncIterator.next()) {
1447
+ if (throwError)
1448
+ assertObservableError(step.value);
1449
+ obs.next(step.value);
1450
+ // If subscription was closed during iteration, clean up and exit
1451
+ if (obs.closed)
1452
+ break;
1453
+ }
1454
+ // Normal completion
1455
+ obs.complete();
1456
+ }
1457
+ catch (err) {
1458
+ // Error during iteration
1459
+ obs.error(err);
1460
+ }
1461
+ })();
1462
+ return () => {
1463
+ if (typeof asyncIterator?.return === "function") {
1464
+ try {
1465
+ asyncIterator.return(); // IteratorClose
1466
+ }
1467
+ catch (err) {
1468
+ queueMicrotask(() => {
1469
+ throw err;
1470
+ });
1471
+ }
1472
+ }
1473
+ };
1474
+ });
1475
+ }
1476
+ throw new TypeError("Input is not Observable, Iterable, AsyncIterable, Promise, or ReadableStream");
1477
+ }
1478
+ export async function* pull(observable, { strategy = { highWaterMark: 64 }, throwError = true } = {}) {
1479
+ const obs = observable?.[Symbol.observable]?.();
1480
+ let sub = null;
1481
+ // Create a ReadableStream that will buffer values from the Observable
1482
+ const stream = new ReadableStream({
1483
+ start: (ctrl) => {
1484
+ // Subscribe to the Observable and connect it to the stream
1485
+ sub = obs?.subscribe({
1486
+ // Normal values flow directly into the stream
1487
+ next: (v) => ctrl.enqueue(v),
1488
+ // Errors are wrapped as special values rather than using stream.error()
1489
+ // This ensures values emitted before the error are still processed
1490
+ error: (e) => {
1491
+ ctrl.enqueue(ObservableError.from(e, "observable:pull"));
1492
+ sub = null;
1493
+ },
1494
+ // Close the stream when the Observable completes
1495
+ complete: () => {
1496
+ ctrl.close();
1497
+ sub = null;
1498
+ },
1499
+ });
1500
+ },
1501
+ // Clean up the subscription if the stream is cancelled
1502
+ // This happens when the AsyncGenerator is terminated early
1503
+ cancel: () => {
1504
+ sub?.unsubscribe();
1505
+ sub = null;
1506
+ },
1507
+ }, strategy);
1508
+ // Get a reader for the stream and yield values as they become available
1509
+ const reader = stream.getReader();
1510
+ try {
1511
+ while (true) {
1512
+ // Wait for the next value (with backpressure automatically applied)
1513
+ const { value, done } = await reader.read();
1514
+ // If we received a wrapped error, unwrap and throw it
1515
+ if (throwError)
1516
+ assertObservableError(value);
1517
+ // If the stream is done (Observable completed), exit the loop
1518
+ if (done)
1519
+ break;
1520
+ // Otherwise, yield the value to the consumer
1521
+ yield value;
1522
+ }
1523
+ }
1524
+ finally {
1525
+ // Ensure resources are cleaned up even if iteration is terminated early
1526
+ // This guarantees no memory leaks, even with break or thrown exceptions
1527
+ reader.releaseLock();
1528
+ await stream.cancel();
1529
+ }
1530
+ }
1531
+ /**
1532
+ * Checks if a value is an Observable instance from this library.
1533
+ *
1534
+ * When working with different Observable implementations or mixed data types, you often need
1535
+ * to verify what kind of object you're dealing with. This function provides a reliable way to
1536
+ * check if something is specifically an instance of our Observable class, which is helpful
1537
+ * for type safety and ensuring you can use all the methods available on our implementation.
1538
+ *
1539
+ * **Why This Function Exists**:
1540
+ *
1541
+ * In JavaScript ecosystems, you might encounter different Observable implementations - RxJS,
1542
+ * this library, custom implementations, or objects that just happen to have a `subscribe` method.
1543
+ * Without a proper way to distinguish between them, you'd have to either:
1544
+ * - Risk calling methods that don't exist (crashes your app)
1545
+ * - Write defensive code with lots of property checks (clutters your logic)
1546
+ * - Use duck typing that might give false positives (unreliable)
1547
+ *
1548
+ * This function eliminates those problems by giving you a definitive answer: "Is this an
1549
+ * Observable from our library?" If yes, you know exactly what methods and properties are
1550
+ * available.
1551
+ *
1552
+ * **How It Relates to Other Checks**:
1553
+ *
1554
+ * Think of this as the strict cousin of `isSpecObservable()`. While `isSpecObservable()`
1555
+ * asks "can I subscribe to this?" this function asks "is this specifically our Observable?"
1556
+ *
1557
+ * Use `isObservable()` when you need to ensure you're working with our exact implementation,
1558
+ * and `isSpecObservable()` when you just need something subscribable.
1559
+ *
1560
+ * **Performance Story**:
1561
+ *
1562
+ * This function uses `instanceof`, which modern JavaScript engines optimize very well. It's
1563
+ * essentially a pointer comparison under the hood, making it extremely fast and suitable for
1564
+ * use in performance-critical code paths.
1565
+ *
1566
+ * The performance characteristics are:
1567
+ * - Single `instanceof` check (optimized by JavaScript engines)
1568
+ * - No method calls or property access required
1569
+ * - Safe to use in tight loops or frequently called functions
1570
+ * - Memory efficient (no allocations, just a boolean return)
1571
+ *
1572
+ * **Common Ways to Use This Function**:
1573
+ *
1574
+ * ```typescript
1575
+ * // Scenario 1: Type-safe method access
1576
+ * function processObservable(input: unknown) {
1577
+ * if (isObservable(input)) {
1578
+ * // TypeScript now knows input is Observable<unknown>
1579
+ * const generator = input.pull({ strategy: { highWaterMark: 10 } });
1580
+ * return generator; // Can safely use our specific methods
1581
+ * }
1582
+ *
1583
+ * throw new Error('Expected an Observable from this library');
1584
+ * }
1585
+ *
1586
+ * // Scenario 2: Library interoperability
1587
+ * function convertToOurObservable(source: unknown): Observable<any> {
1588
+ * if (isObservable(source)) {
1589
+ * return source; // Already our type, no conversion needed
1590
+ * }
1591
+ *
1592
+ * if (isSpecObservable(source)) {
1593
+ * return Observable.from(source); // Convert from other implementation
1594
+ * }
1595
+ *
1596
+ * throw new Error('Cannot convert to Observable');
1597
+ * }
1598
+ *
1599
+ * // Scenario 3: Filtering mixed arrays
1600
+ * const mixedSources = [rxjsObservable, ourObservable, promise, array];
1601
+ * const ourObservables = mixedSources.filter(isObservable);
1602
+ * // ourObservables is now Observable[] with full type safety
1603
+ *
1604
+ * // Scenario 4: Defensive programming
1605
+ * function subscribeToSource(source: unknown) {
1606
+ * if (isObservable(source)) {
1607
+ * // We know exactly what methods are available
1608
+ * return source.subscribe({ next: console.log });
1609
+ * } else if (isSpecObservable(source)) {
1610
+ * // Different Observable implementation, but still subscribable
1611
+ * return source.subscribe({ next: console.log });
1612
+ * } else {
1613
+ * throw new Error('Source is not observable');
1614
+ * }
1615
+ * }
1616
+ * ```
1617
+ *
1618
+ * **What Makes This Function Reliable**:
1619
+ *
1620
+ * Unlike duck typing (checking for the presence of methods), this function is precise:
1621
+ * - Returns true only for actual instances of our Observable class
1622
+ * - Handles inheritance correctly (subclasses return true)
1623
+ * - Never gives false positives from look-alike objects
1624
+ * - Works correctly across different module loading scenarios
1625
+ *
1626
+ * **Edge Cases Handled**:
1627
+ * - `null` and `undefined` → false (not Observables)
1628
+ * - Objects with `subscribe` methods → false (unless they're actually our Observable)
1629
+ * - Subclasses of Observable → true (proper inheritance support)
1630
+ * - Cross-frame instances → true (same constructor reference)
1631
+ *
1632
+ * **When to Use This vs Other Options**:
1633
+ *
1634
+ * Choose `isObservable()` when:
1635
+ * - You need to access methods specific to our Observable implementation
1636
+ * - You're building type guards for strict type checking
1637
+ * - You need to distinguish between different Observable libraries
1638
+ * - You're doing performance-critical filtering of mixed object types
1639
+ *
1640
+ * Choose `isSpecObservable()` instead when:
1641
+ * - You just need something that can be subscribed to
1642
+ * - You want maximum compatibility with other Observable implementations
1643
+ * - You're building generic utilities that work with any Observable-like object
1644
+ *
1645
+ * @template T - The expected type for the Observable's emitted values
1646
+ * @param value - Any value that might or might not be our Observable
1647
+ * @returns true if the value is an instance of our Observable class, false otherwise
1648
+ *
1649
+ * @example Simple type checking
1650
+ * ```typescript
1651
+ * const maybeObservable: unknown = getDataSource();
1652
+ *
1653
+ * if (isObservable(maybeObservable)) {
1654
+ * // TypeScript knows maybeObservable is Observable<unknown>
1655
+ * const subscription = maybeObservable.subscribe(console.log);
1656
+ *
1657
+ * // Can also use our specific methods
1658
+ * for await (const value of maybeObservable.pull()) {
1659
+ * console.log('Pulled:', value);
1660
+ * }
1661
+ * } else {
1662
+ * console.log('Not our Observable implementation');
1663
+ * }
1664
+ * ```
1665
+ *
1666
+ * @example Building a conversion utility
1667
+ * ```typescript
1668
+ * function ensureOurObservable<T>(source: unknown): Observable<T> {
1669
+ * if (isObservable<T>(source)) {
1670
+ * return source; // Already the right type
1671
+ * }
1672
+ *
1673
+ * if (isSpecObservable<T>(source)) {
1674
+ * // Convert from another Observable implementation
1675
+ * return new Observable<T>(observer => {
1676
+ * const sub = source.subscribe(observer);
1677
+ * return () => sub.unsubscribe();
1678
+ * });
1679
+ * }
1680
+ *
1681
+ * // Try to convert from other types
1682
+ * return Observable.from(source as any);
1683
+ * }
1684
+ * ```
1685
+ *
1686
+ * @example Library integration
1687
+ * ```typescript
1688
+ * // Function that works with any Observable but optimizes for ours
1689
+ * function processStream<T>(stream: unknown): AsyncGenerator<T> {
1690
+ * if (isObservable<T>(stream)) {
1691
+ * // Use our optimized pull method
1692
+ * return stream.pull();
1693
+ * } else if (isSpecObservable<T>(stream)) {
1694
+ * // Convert and then use our method
1695
+ * return Observable.from(stream).pull();
1696
+ * } else {
1697
+ * throw new Error('Expected an Observable-like object');
1698
+ * }
1699
+ * }
1700
+ * ```
1701
+ */
1702
+ export function isObservable(value) {
1703
+ // This is a straightforward instanceof check
1704
+ // Works reliably across module boundaries and handles inheritance correctly
1705
+ return value instanceof Observable;
1706
+ }
1707
+ /**
1708
+ * Checks if a value conforms to the Observable specification protocol.
1709
+ *
1710
+ * When building applications that work with multiple Observable implementations, you need a way
1711
+ * to identify objects that can be subscribed to, regardless of which specific library created
1712
+ * them. This function provides that capability by checking for the core Observable protocol
1713
+ * rather than specific implementation details.
1714
+ *
1715
+ * **Why This Function Exists**:
1716
+ *
1717
+ * The Observable ecosystem includes many implementations - RxJS, this library, Zen Observable,
1718
+ * and others. Each has its own class structure, but they all follow the same basic protocol:
1719
+ * having a `[Symbol.observable]()` method that returns an object with a `subscribe()` method.
1720
+ *
1721
+ * Without this function, you'd need to write complex checks to determine if something is
1722
+ * subscribable, leading to:
1723
+ * - Fragile duck typing that breaks with edge cases
1724
+ * - Verbose property checking that clutters your code
1725
+ * - Missing compatibility with new Observable implementations
1726
+ * - Inconsistent behavior across different parts of your application
1727
+ *
1728
+ * This function solves those problems by implementing the official Observable protocol check.
1729
+ *
1730
+ * **How It Relates to Other Checks**:
1731
+ *
1732
+ * Think of this as the diplomatic cousin of `isObservable()`. While `isObservable()` checks
1733
+ * for our specific implementation, this function asks "do you speak the Observable protocol?"
1734
+ * It's designed for interoperability and maximum compatibility.
1735
+ *
1736
+ * The relationship between these functions is:
1737
+ * - `isObservable()` → "Are you our exact Observable class?"
1738
+ * - `isSpecObservable()` → "Can I subscribe to you using the standard protocol?"
1739
+ *
1740
+ * **Performance Story**:
1741
+ *
1742
+ * This function is more complex than `isObservable()` because it needs to check multiple
1743
+ * properties and call a method. However, it's still quite efficient:
1744
+ *
1745
+ * - Fast property access for Symbol.observable
1746
+ * - Single method call to get the subscribable object
1747
+ * - Type checking for the subscribe method
1748
+ * - Early returns for non-objects to avoid unnecessary work
1749
+ *
1750
+ * While not as fast as `instanceof`, it's still suitable for most use cases. If you're in a
1751
+ * performance-critical path and know you're only dealing with our Observable implementation,
1752
+ * prefer `isObservable()`.
1753
+ *
1754
+ * **Common Ways to Use This Function**:
1755
+ *
1756
+ * ```typescript
1757
+ * // Scenario 1: Cross-library compatibility
1758
+ * import { Observable as RxObservable } from 'rxjs';
1759
+ * import { Observable as OurObservable } from './observable.ts';
1760
+ *
1761
+ * function processAnyObservable<T>(source: unknown): Promise<T[]> {
1762
+ * if (isSpecObservable<T>(source)) {
1763
+ * // Works with RxJS, our Observable, or any other spec-compliant implementation
1764
+ * const results: T[] = [];
1765
+ *
1766
+ * return new Promise((resolve, reject) => {
1767
+ * source.subscribe({
1768
+ * next: value => results.push(value),
1769
+ * error: reject,
1770
+ * complete: () => resolve(results)
1771
+ * });
1772
+ * });
1773
+ * }
1774
+ *
1775
+ * throw new Error('Source must be Observable-like');
1776
+ * }
1777
+ *
1778
+ * // Scenario 2: Building generic utilities
1779
+ * function toArray<T>(source: unknown): Promise<T[]> {
1780
+ * if (isSpecObservable<T>(source)) {
1781
+ * return new Promise((resolve, reject) => {
1782
+ * const items: T[] = [];
1783
+ * source.subscribe({
1784
+ * next: item => items.push(item),
1785
+ * error: reject,
1786
+ * complete: () => resolve(items)
1787
+ * });
1788
+ * });
1789
+ * }
1790
+ *
1791
+ * // Fallback for other iterable types
1792
+ * if (Array.isArray(source)) return Promise.resolve([...source]);
1793
+ *
1794
+ * throw new Error('Cannot convert to array');
1795
+ * }
1796
+ *
1797
+ * // Scenario 3: Input validation in APIs
1798
+ * function subscribeToStream<T>(
1799
+ * stream: unknown,
1800
+ * handler: (value: T) => void
1801
+ * ): () => void {
1802
+ * if (!isSpecObservable<T>(stream)) {
1803
+ * throw new TypeError('Expected an Observable-like object');
1804
+ * }
1805
+ *
1806
+ * const subscription = stream.subscribe({ next: handler });
1807
+ * return () => subscription.unsubscribe();
1808
+ * }
1809
+ *
1810
+ * // Scenario 4: Filtering and type narrowing
1811
+ * const mixedSources: unknown[] = [
1812
+ * rxjsObservable,
1813
+ * ourObservable,
1814
+ * { subscribe() { return { unsubscribe() {} }; } }, // Custom implementation
1815
+ * "not observable",
1816
+ * 42
1817
+ * ];
1818
+ *
1819
+ * const observableSources = mixedSources.filter(isSpecObservable);
1820
+ * // observableSources is now Array<{ subscribe: Function, [Symbol.observable]: Function }>
1821
+ * ```
1822
+ *
1823
+ * **What Makes This Function Robust**:
1824
+ *
1825
+ * This function implements the official Observable protocol checking:
1826
+ * 1. Verifies the object has `Symbol.observable` method
1827
+ * 2. Calls that method to get the subscribable object
1828
+ * 3. Ensures the result has a working `subscribe` method
1829
+ * 4. Handles errors gracefully (returns false rather than throwing)
1830
+ *
1831
+ * **Edge Cases Handled**:
1832
+ * - `null` and `undefined` → false (not objects)
1833
+ * - Objects without `Symbol.observable` → false (not Observable protocol)
1834
+ * - `Symbol.observable` that throws → false (graceful error handling)
1835
+ * - `Symbol.observable` returning non-objects → false (invalid protocol)
1836
+ * - Objects with `subscribe` but no `Symbol.observable` → false (incomplete protocol)
1837
+ *
1838
+ * **When to Use This vs Other Options**:
1839
+ *
1840
+ * Choose `isSpecObservable()` when:
1841
+ * - Building libraries that should work with any Observable implementation
1842
+ * - You need maximum compatibility across the Observable ecosystem
1843
+ * - You're creating utilities for consuming streams regardless of their origin
1844
+ * - You want to follow the official Observable specification strictly
1845
+ *
1846
+ * Choose `isObservable()` instead when:
1847
+ * - You need methods specific to our Observable implementation
1848
+ * - Performance is critical and you know the expected types
1849
+ * - You're working within a single Observable implementation ecosystem
1850
+ * - You need compile-time guarantees about available methods
1851
+ *
1852
+ * @template T - The expected type for values emitted by the Observable
1853
+ * @param value - Any value that might conform to the Observable protocol
1854
+ * @returns true if the value implements the Observable specification, false otherwise
1855
+ *
1856
+ * @example Cross-library compatibility
1857
+ * ```typescript
1858
+ * import { Observable as RxObservable } from 'rxjs';
1859
+ * import { Observable as OurObservable } from './observable.ts';
1860
+ *
1861
+ * const sources = [
1862
+ * new RxObservable(sub => sub.next(1)),
1863
+ * new OurObservable(obs => obs.next(2)),
1864
+ * { subscribe() { return { unsubscribe() {} }; } } // Custom
1865
+ * ];
1866
+ *
1867
+ * // Process any Observable-like object
1868
+ * sources.forEach(source => {
1869
+ * if (isSpecObservable(source)) {
1870
+ * console.log('Can subscribe to this source');
1871
+ * source.subscribe({ next: console.log });
1872
+ * }
1873
+ * });
1874
+ * ```
1875
+ *
1876
+ * @example Building a universal Observable utility
1877
+ * ```typescript
1878
+ * function first<T>(source: unknown): Promise<T> {
1879
+ * if (!isSpecObservable<T>(source)) {
1880
+ * return Promise.reject(new Error('Source must be Observable'));
1881
+ * }
1882
+ *
1883
+ * return new Promise((resolve, reject) => {
1884
+ * const subscription = source.subscribe({
1885
+ * next: value => {
1886
+ * subscription.unsubscribe();
1887
+ * resolve(value);
1888
+ * },
1889
+ * error: reject,
1890
+ * complete: () => reject(new Error('Observable completed without emitting'))
1891
+ * });
1892
+ * });
1893
+ * }
1894
+ *
1895
+ * // Works with any Observable implementation
1896
+ * const result1 = await first(rxjsObservable);
1897
+ * const result2 = await first(ourObservable);
1898
+ * ```
1899
+ *
1900
+ * @example Input validation for APIs
1901
+ * ```typescript
1902
+ * interface StreamProcessor<T> {
1903
+ * process(stream: unknown): AsyncGenerator<T>;
1904
+ * }
1905
+ *
1906
+ * class UniversalProcessor<T> implements StreamProcessor<T> {
1907
+ * async* process(stream: unknown): AsyncGenerator<T> {
1908
+ * if (isSpecObservable<T>(stream)) {
1909
+ * // Convert any Observable to async generator
1910
+ * const observable = stream[Symbol.observable]();
1911
+ *
1912
+ * let resolve: (value: IteratorResult<T>) => void;
1913
+ * let reject: (error: any) => void;
1914
+ * let promise = new Promise<IteratorResult<T>>((res, rej) => {
1915
+ * resolve = res;
1916
+ * reject = rej;
1917
+ * });
1918
+ *
1919
+ * const subscription = observable.subscribe({
1920
+ * next: value => {
1921
+ * resolve({ value, done: false });
1922
+ * promise = new Promise<IteratorResult<T>>((res, rej) => {
1923
+ * resolve = res;
1924
+ * reject = rej;
1925
+ * });
1926
+ * },
1927
+ * error: reject,
1928
+ * complete: () => resolve({ value: undefined as any, done: true })
1929
+ * });
1930
+ *
1931
+ * try {
1932
+ * while (true) {
1933
+ * const result = await promise;
1934
+ * if (result.done) break;
1935
+ * yield result.value;
1936
+ * }
1937
+ * } finally {
1938
+ * subscription.unsubscribe();
1939
+ * }
1940
+ * } else {
1941
+ * throw new Error('Input must implement Observable protocol');
1942
+ * }
1943
+ * }
1944
+ * }
1945
+ * ```
1946
+ */
1947
+ export function isSpecObservable(value) {
1948
+ // Early return for non-objects
1949
+ if (value === null || value === undefined || typeof value !== "object") {
1950
+ return false;
1951
+ }
1952
+ try {
1953
+ // Check if the object has the Symbol.observable method
1954
+ const observableMethod = value[Symbol.observable];
1955
+ if (typeof observableMethod !== "function") {
1956
+ return false;
1957
+ }
1958
+ // Call the method to get the subscribable object
1959
+ const subscribable = observableMethod.call(value);
1960
+ // Verify the result has a subscribe method
1961
+ return (subscribable !== null &&
1962
+ subscribable !== undefined &&
1963
+ typeof subscribable === "object" &&
1964
+ typeof subscribable.subscribe === "function");
1965
+ }
1966
+ catch {
1967
+ // If any step throws, it's not a valid Observable
1968
+ return false;
1969
+ }
1970
+ }