@okikio/observables 1.0.2 → 1.3.0

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.
@@ -4,8 +4,13 @@ exports.isTransformStreamOptions = isTransformStreamOptions;
4
4
  exports.isTransformFunctionOptions = isTransformFunctionOptions;
5
5
  exports.applyOperator = applyOperator;
6
6
  exports.toStream = toStream;
7
+ exports.fromStreamPair = fromStreamPair;
8
+ exports.streamAsObservable = streamAsObservable;
9
+ exports.observableInputToStream = observableInputToStream;
10
+ exports.fromObservableOperator = fromObservableOperator;
7
11
  exports.injectError = injectError;
8
12
  const error_js_1 = require("../error.js");
13
+ const observable_js_1 = require("../observable.js");
9
14
  /**
10
15
  * Type guard to check if options is a TransformStreamOptions
11
16
  *
@@ -125,6 +130,173 @@ function toStream(iterable) {
125
130
  },
126
131
  });
127
132
  }
133
+ /**
134
+ * Adapts a readable/writable stream pair into an Observable operator.
135
+ *
136
+ * Some platform transforms, such as `CompressionStream`, expose a writable side
137
+ * and a readable side without being created through this library's operator
138
+ * builders. This helper turns that pair into a normal `Operator` so it can be
139
+ * used inside `pipe()` like any built-in operator.
140
+ *
141
+ * A fresh pair is created for each operator application. That preserves the
142
+ * cold semantics of the surrounding Observable pipeline and avoids reusing a
143
+ * consumed stream pair across subscriptions.
144
+ *
145
+ * @typeParam TIn - Chunk type written into the pair
146
+ * @typeParam TOut - Chunk type read from the pair
147
+ * @param createPair - Factory that returns a fresh readable/writable pair
148
+ * @returns An operator that pipes input through the created pair
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const gzip = fromStreamPair<Uint8Array, Uint8Array>(
153
+ * () => new CompressionStream('gzip')
154
+ * );
155
+ * ```
156
+ */
157
+ function fromStreamPair(createPair) {
158
+ return (source) => source.pipeThrough(createPair());
159
+ }
160
+ /**
161
+ * Wraps a `ReadableStream` as an Observable so foreign Observable-style
162
+ * operators can subscribe to it directly.
163
+ *
164
+ * This avoids first converting the stream into an async-iterable Observable via
165
+ * `Observable.from()`, which would add an extra adaptation layer before the
166
+ * foreign operator even starts running.
167
+ */
168
+ function streamAsObservable(stream) {
169
+ return new observable_js_1.Observable((observer) => {
170
+ const reader = stream.getReader();
171
+ let cancelled = false;
172
+ void (async () => {
173
+ try {
174
+ while (!cancelled && !observer.closed) {
175
+ const { done, value } = await reader.read();
176
+ if (done) {
177
+ break;
178
+ }
179
+ observer.next(value);
180
+ }
181
+ if (!cancelled && !observer.closed) {
182
+ observer.complete();
183
+ }
184
+ }
185
+ catch (err) {
186
+ if (!cancelled && !observer.closed) {
187
+ observer.error(err);
188
+ }
189
+ }
190
+ finally {
191
+ try {
192
+ reader.releaseLock();
193
+ }
194
+ catch {
195
+ // Releasing the reader is best-effort during teardown.
196
+ }
197
+ }
198
+ })();
199
+ return () => {
200
+ cancelled = true;
201
+ void reader.cancel();
202
+ };
203
+ });
204
+ }
205
+ /**
206
+ * Returns true when a value exposes a direct `subscribe()` method.
207
+ *
208
+ * Many Observable libraries return subscribable objects that are usable without
209
+ * first going through this library's `Observable.from()`. Detecting that shape
210
+ * lets interop stay direct for outputs such as RxJS Observables.
211
+ */
212
+ function hasSubscribe(input) {
213
+ return typeof input === "object" && input !== null &&
214
+ typeof input.subscribe === "function";
215
+ }
216
+ /**
217
+ * Subscribes to an Observable-like output and exposes it as a ReadableStream.
218
+ *
219
+ * Foreign operators are allowed to return either a normal `Observable.from()`
220
+ * input or a direct subscribable from another Observable implementation.
221
+ * Converting the result with a direct subscription avoids the extra
222
+ * async-generator and stream layers that `pull(...)+toStream()` would add.
223
+ */
224
+ function observableInputToStream(input, errorContext) {
225
+ let subscription;
226
+ return new ReadableStream({
227
+ start(controller) {
228
+ const source = hasSubscribe(input) ? input : observable_js_1.Observable.from(input);
229
+ subscription = source.subscribe({
230
+ next(value) {
231
+ try {
232
+ controller.enqueue(value);
233
+ }
234
+ catch {
235
+ // Downstream cancellation already decided the stream outcome.
236
+ }
237
+ },
238
+ error(error) {
239
+ try {
240
+ controller.enqueue(error_js_1.ObservableError.from(error, errorContext));
241
+ controller.close();
242
+ }
243
+ catch {
244
+ // Downstream cancellation already decided the stream outcome.
245
+ }
246
+ },
247
+ complete() {
248
+ try {
249
+ controller.close();
250
+ }
251
+ catch {
252
+ // Downstream cancellation already decided the stream outcome.
253
+ }
254
+ },
255
+ });
256
+ },
257
+ cancel() {
258
+ subscription?.unsubscribe?.();
259
+ subscription = undefined;
260
+ },
261
+ });
262
+ }
263
+ /**
264
+ * Adapts a foreign Observable-style operator into a stream operator.
265
+ *
266
+ * Libraries such as RxJS model operators as functions from one Observable-like
267
+ * source to another Observable-like result. This helper bridges that shape into
268
+ * this library's `Operator` contract by:
269
+ *
270
+ * 1. wrapping the input stream as an Observable-like source
271
+ * 2. calling the foreign operator
272
+ * 3. converting the resulting Observable-like output back into a stream
273
+ *
274
+ * The result keeps this library's buffered error behavior by converting the
275
+ * foreign output into a `ReadableStream` that enqueues wrapped
276
+ * `ObservableError` values instead of failing the readable side outright.
277
+ *
278
+ * @typeParam TIn - Value type accepted by the foreign operator
279
+ * @typeParam TOut - Value type produced by the foreign operator
280
+ * @param operator - Foreign operator function to adapt
281
+ * @returns An operator compatible with this library's `pipe()`
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * const foreignTakeOne = fromObservableOperator<number, number>((source) =>
286
+ * rxTake(1)(source)
287
+ * , { sourceAdapter: (source) => rxFrom(source) });
288
+ * ```
289
+ */
290
+ function fromObservableOperator(operator, options) {
291
+ return (source) => {
292
+ const observableSource = streamAsObservable(source);
293
+ const foreignSource = options?.sourceAdapter
294
+ ? options.sourceAdapter(observableSource)
295
+ : observableSource;
296
+ const output = operator(foreignSource);
297
+ return observableInputToStream(output, options?.errorContext ?? "operator:fromObservableOperator:output");
298
+ };
299
+ }
128
300
  /**
129
301
  * Creates a TransformStream that injects an error at the start and passes through all original data.
130
302
  *