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