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