@nlozgachev/pipelined 0.49.0 → 0.50.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,7 +4,7 @@ import {
4
4
  Result,
5
5
  Task,
6
6
  isNonEmptyArr
7
- } from "./chunk-IHLH2XYQ.mjs";
7
+ } from "./chunk-U64ASY7P.mjs";
8
8
 
9
9
  // src/Data/Arr.ts
10
10
  var ArrMaybe;
@@ -1737,22 +1737,35 @@ var Stream;
1737
1737
  ((Stream2) => {
1738
1738
  Stream2.make = (options) => ({
1739
1739
  options,
1740
- _listeners: /* @__PURE__ */ new Set()
1740
+ _listeners: /* @__PURE__ */ new Set(),
1741
+ _queue: [],
1742
+ _isEmitting: false
1741
1743
  });
1742
1744
  Stream2.emit = (target, message) => {
1743
1745
  const targets = Array.isArray(target) ? target : [target];
1744
1746
  const msg = message;
1745
1747
  for (const stream of targets) {
1746
- const listeners = Array.from(stream._listeners);
1747
- for (const listener of listeners) {
1748
+ stream._queue.push(msg);
1749
+ if (!stream._isEmitting) {
1750
+ stream._isEmitting = true;
1748
1751
  try {
1749
- listener(msg);
1750
- } catch (err2) {
1751
- if (stream.options?.onError) {
1752
- stream.options.onError(err2);
1753
- } else {
1754
- throw err2;
1752
+ while (stream._queue.length > 0) {
1753
+ const nextMsg = stream._queue.shift();
1754
+ const listeners = Array.from(stream._listeners);
1755
+ for (const listener of listeners) {
1756
+ try {
1757
+ listener(nextMsg);
1758
+ } catch (err2) {
1759
+ if (stream.options?.onError) {
1760
+ stream.options.onError(err2);
1761
+ } else {
1762
+ throw err2;
1763
+ }
1764
+ }
1765
+ }
1755
1766
  }
1767
+ } finally {
1768
+ stream._isEmitting = false;
1756
1769
  }
1757
1770
  }
1758
1771
  }
@@ -1779,6 +1792,7 @@ var Stream;
1779
1792
  const isStrict = options?.strict ?? false;
1780
1793
  const isOnce = options?.once ?? false;
1781
1794
  const resetKinds = options?.reset ? new Set(Array.isArray(options.reset) ? options.reset : [options.reset]) : null;
1795
+ const optionalKinds = options?.optional ? new Set(Array.isArray(options.optional) ? options.optional : [options.optional]) : null;
1782
1796
  const createMatcher = (onMatch) => {
1783
1797
  let sequenceIndex = 0;
1784
1798
  return (msg) => {
@@ -1792,7 +1806,17 @@ var Stream;
1792
1806
  }
1793
1807
  return;
1794
1808
  }
1795
- const expectedKind = eventList[sequenceIndex];
1809
+ let expectedKind = eventList[sequenceIndex];
1810
+ if (expectedKind !== msg.kind && optionalKinds !== null) {
1811
+ let lookaheadIndex = sequenceIndex;
1812
+ while (lookaheadIndex < eventList.length && optionalKinds.has(eventList[lookaheadIndex]) && eventList[lookaheadIndex] !== msg.kind) {
1813
+ lookaheadIndex++;
1814
+ }
1815
+ if (lookaheadIndex < eventList.length && eventList[lookaheadIndex] === msg.kind) {
1816
+ sequenceIndex = lookaheadIndex;
1817
+ expectedKind = eventList[sequenceIndex];
1818
+ }
1819
+ }
1796
1820
  if (msg.kind === expectedKind) {
1797
1821
  sequenceIndex++;
1798
1822
  if (sequenceIndex === eventList.length) {
package/dist/core.d.mts CHANGED
@@ -2490,6 +2490,10 @@ type Stream<S extends Record<string, unknown>> = {
2490
2490
  readonly options?: Stream.Options;
2491
2491
  /** @internal */
2492
2492
  readonly _listeners: Set<(msg: Stream.Message<S>) => void>;
2493
+ /** @internal */
2494
+ readonly _queue: Array<Stream.Message<S>>;
2495
+ /** @internal */
2496
+ _isEmitting: boolean;
2493
2497
  };
2494
2498
  declare namespace Stream {
2495
2499
  /**
@@ -2517,6 +2521,8 @@ declare namespace Stream {
2517
2521
  readonly once?: boolean;
2518
2522
  /** Event kind(s) that reset sequence tracking to index 0. */
2519
2523
  readonly reset?: (keyof S & string) | ReadonlyArray<keyof S & string>;
2524
+ /** Event kind(s) in the sequence that may be present or skipped. */
2525
+ readonly optional?: (keyof S & string) | ReadonlyArray<keyof S & string>;
2520
2526
  };
2521
2527
  /**
2522
2528
  * Handle for an active stateful subscription.
@@ -2558,6 +2564,8 @@ declare namespace Stream {
2558
2564
  /**
2559
2565
  * Emits a message payload to one or more target streams.
2560
2566
  *
2567
+ * Uses a synchronous breadth-first trampoline queue to handle re-entrant emissions deterministically.
2568
+ *
2561
2569
  * @example
2562
2570
  * ```ts
2563
2571
  * Stream.emit(streamA, {
package/dist/core.d.ts CHANGED
@@ -2490,6 +2490,10 @@ type Stream<S extends Record<string, unknown>> = {
2490
2490
  readonly options?: Stream.Options;
2491
2491
  /** @internal */
2492
2492
  readonly _listeners: Set<(msg: Stream.Message<S>) => void>;
2493
+ /** @internal */
2494
+ readonly _queue: Array<Stream.Message<S>>;
2495
+ /** @internal */
2496
+ _isEmitting: boolean;
2493
2497
  };
2494
2498
  declare namespace Stream {
2495
2499
  /**
@@ -2517,6 +2521,8 @@ declare namespace Stream {
2517
2521
  readonly once?: boolean;
2518
2522
  /** Event kind(s) that reset sequence tracking to index 0. */
2519
2523
  readonly reset?: (keyof S & string) | ReadonlyArray<keyof S & string>;
2524
+ /** Event kind(s) in the sequence that may be present or skipped. */
2525
+ readonly optional?: (keyof S & string) | ReadonlyArray<keyof S & string>;
2520
2526
  };
2521
2527
  /**
2522
2528
  * Handle for an active stateful subscription.
@@ -2558,6 +2564,8 @@ declare namespace Stream {
2558
2564
  /**
2559
2565
  * Emits a message payload to one or more target streams.
2560
2566
  *
2567
+ * Uses a synchronous breadth-first trampoline queue to handle re-entrant emissions deterministically.
2568
+ *
2561
2569
  * @example
2562
2570
  * ```ts
2563
2571
  * Stream.emit(streamA, {
package/dist/core.js CHANGED
@@ -1811,22 +1811,35 @@ var Stream;
1811
1811
  ((Stream2) => {
1812
1812
  Stream2.make = (options) => ({
1813
1813
  options,
1814
- _listeners: /* @__PURE__ */ new Set()
1814
+ _listeners: /* @__PURE__ */ new Set(),
1815
+ _queue: [],
1816
+ _isEmitting: false
1815
1817
  });
1816
1818
  Stream2.emit = (target, message) => {
1817
1819
  const targets = Array.isArray(target) ? target : [target];
1818
1820
  const msg = message;
1819
1821
  for (const stream of targets) {
1820
- const listeners = Array.from(stream._listeners);
1821
- for (const listener of listeners) {
1822
+ stream._queue.push(msg);
1823
+ if (!stream._isEmitting) {
1824
+ stream._isEmitting = true;
1822
1825
  try {
1823
- listener(msg);
1824
- } catch (err2) {
1825
- if (stream.options?.onError) {
1826
- stream.options.onError(err2);
1827
- } else {
1828
- throw err2;
1826
+ while (stream._queue.length > 0) {
1827
+ const nextMsg = stream._queue.shift();
1828
+ const listeners = Array.from(stream._listeners);
1829
+ for (const listener of listeners) {
1830
+ try {
1831
+ listener(nextMsg);
1832
+ } catch (err2) {
1833
+ if (stream.options?.onError) {
1834
+ stream.options.onError(err2);
1835
+ } else {
1836
+ throw err2;
1837
+ }
1838
+ }
1839
+ }
1829
1840
  }
1841
+ } finally {
1842
+ stream._isEmitting = false;
1830
1843
  }
1831
1844
  }
1832
1845
  }
@@ -1853,6 +1866,7 @@ var Stream;
1853
1866
  const isStrict = options?.strict ?? false;
1854
1867
  const isOnce = options?.once ?? false;
1855
1868
  const resetKinds = options?.reset ? new Set(Array.isArray(options.reset) ? options.reset : [options.reset]) : null;
1869
+ const optionalKinds = options?.optional ? new Set(Array.isArray(options.optional) ? options.optional : [options.optional]) : null;
1856
1870
  const createMatcher = (onMatch) => {
1857
1871
  let sequenceIndex = 0;
1858
1872
  return (msg) => {
@@ -1866,7 +1880,17 @@ var Stream;
1866
1880
  }
1867
1881
  return;
1868
1882
  }
1869
- const expectedKind = eventList[sequenceIndex];
1883
+ let expectedKind = eventList[sequenceIndex];
1884
+ if (expectedKind !== msg.kind && optionalKinds !== null) {
1885
+ let lookaheadIndex = sequenceIndex;
1886
+ while (lookaheadIndex < eventList.length && optionalKinds.has(eventList[lookaheadIndex]) && eventList[lookaheadIndex] !== msg.kind) {
1887
+ lookaheadIndex++;
1888
+ }
1889
+ if (lookaheadIndex < eventList.length && eventList[lookaheadIndex] === msg.kind) {
1890
+ sequenceIndex = lookaheadIndex;
1891
+ expectedKind = eventList[sequenceIndex];
1892
+ }
1893
+ }
1870
1894
  if (msg.kind === expectedKind) {
1871
1895
  sequenceIndex++;
1872
1896
  if (sequenceIndex === eventList.length) {
package/dist/core.mjs CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  These,
25
25
  Tuple,
26
26
  Validation
27
- } from "./chunk-IHLH2XYQ.mjs";
27
+ } from "./chunk-U64ASY7P.mjs";
28
28
  import "./chunk-DENXUTKL.mjs";
29
29
  export {
30
30
  Combinable,
package/dist/data.mjs CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  Rec,
8
8
  Str,
9
9
  Uniq
10
- } from "./chunk-EPT3GYGN.mjs";
11
- import "./chunk-IHLH2XYQ.mjs";
10
+ } from "./chunk-6N27QZFK.mjs";
11
+ import "./chunk-U64ASY7P.mjs";
12
12
  import "./chunk-DENXUTKL.mjs";
13
13
  export {
14
14
  Arr,
package/dist/index.js CHANGED
@@ -2293,22 +2293,35 @@ var Stream;
2293
2293
  ((Stream2) => {
2294
2294
  Stream2.make = (options) => ({
2295
2295
  options,
2296
- _listeners: /* @__PURE__ */ new Set()
2296
+ _listeners: /* @__PURE__ */ new Set(),
2297
+ _queue: [],
2298
+ _isEmitting: false
2297
2299
  });
2298
2300
  Stream2.emit = (target, message) => {
2299
2301
  const targets = Array.isArray(target) ? target : [target];
2300
2302
  const msg = message;
2301
2303
  for (const stream of targets) {
2302
- const listeners = Array.from(stream._listeners);
2303
- for (const listener of listeners) {
2304
+ stream._queue.push(msg);
2305
+ if (!stream._isEmitting) {
2306
+ stream._isEmitting = true;
2304
2307
  try {
2305
- listener(msg);
2306
- } catch (err2) {
2307
- if (stream.options?.onError) {
2308
- stream.options.onError(err2);
2309
- } else {
2310
- throw err2;
2308
+ while (stream._queue.length > 0) {
2309
+ const nextMsg = stream._queue.shift();
2310
+ const listeners = Array.from(stream._listeners);
2311
+ for (const listener of listeners) {
2312
+ try {
2313
+ listener(nextMsg);
2314
+ } catch (err2) {
2315
+ if (stream.options?.onError) {
2316
+ stream.options.onError(err2);
2317
+ } else {
2318
+ throw err2;
2319
+ }
2320
+ }
2321
+ }
2311
2322
  }
2323
+ } finally {
2324
+ stream._isEmitting = false;
2312
2325
  }
2313
2326
  }
2314
2327
  }
@@ -2335,6 +2348,7 @@ var Stream;
2335
2348
  const isStrict = options?.strict ?? false;
2336
2349
  const isOnce = options?.once ?? false;
2337
2350
  const resetKinds = options?.reset ? new Set(Array.isArray(options.reset) ? options.reset : [options.reset]) : null;
2351
+ const optionalKinds = options?.optional ? new Set(Array.isArray(options.optional) ? options.optional : [options.optional]) : null;
2338
2352
  const createMatcher = (onMatch) => {
2339
2353
  let sequenceIndex = 0;
2340
2354
  return (msg) => {
@@ -2348,7 +2362,17 @@ var Stream;
2348
2362
  }
2349
2363
  return;
2350
2364
  }
2351
- const expectedKind = eventList[sequenceIndex];
2365
+ let expectedKind = eventList[sequenceIndex];
2366
+ if (expectedKind !== msg.kind && optionalKinds !== null) {
2367
+ let lookaheadIndex = sequenceIndex;
2368
+ while (lookaheadIndex < eventList.length && optionalKinds.has(eventList[lookaheadIndex]) && eventList[lookaheadIndex] !== msg.kind) {
2369
+ lookaheadIndex++;
2370
+ }
2371
+ if (lookaheadIndex < eventList.length && eventList[lookaheadIndex] === msg.kind) {
2372
+ sequenceIndex = lookaheadIndex;
2373
+ expectedKind = eventList[sequenceIndex];
2374
+ }
2375
+ }
2352
2376
  if (msg.kind === expectedKind) {
2353
2377
  sequenceIndex++;
2354
2378
  if (sequenceIndex === eventList.length) {
package/dist/index.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  Rec,
40
40
  Str,
41
41
  Uniq
42
- } from "./chunk-EPT3GYGN.mjs";
42
+ } from "./chunk-6N27QZFK.mjs";
43
43
  import {
44
44
  Combinable,
45
45
  Deferred,
@@ -66,7 +66,7 @@ import {
66
66
  These,
67
67
  Tuple,
68
68
  Validation
69
- } from "./chunk-IHLH2XYQ.mjs";
69
+ } from "./chunk-U64ASY7P.mjs";
70
70
  import {
71
71
  Brand,
72
72
  Duration,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nlozgachev/pipelined",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "Opinionated functional abstractions for TypeScript",
5
5
  "license": "BSD-3-Clause",
6
6
  "homepage": "https://pipelined.lozgachev.dev",
@@ -18,7 +18,7 @@
18
18
  "types": "./dist/index.d.ts",
19
19
  "sideEffects": false,
20
20
  "engines": {
21
- "node": ">=24"
21
+ "node": ">=24.19.0"
22
22
  },
23
23
  "files": [
24
24
  "dist"
@@ -68,24 +68,44 @@
68
68
  "docs:build": "pnpm --filter pipelined-docs build"
69
69
  },
70
70
  "devDependencies": {
71
- "@size-limit/file": "12.1.0",
72
- "@types/node": "25.9.3",
73
- "@vitest/coverage-v8": "4.1.8",
74
- "bumpp": "11.1.0",
75
- "dprint": "0.54.0",
76
- "fast-check": "4.8.0",
77
- "oxlint": "1.69.0",
78
- "size-limit": "12.1.0",
71
+ "@size-limit/file": "13.0.3",
72
+ "@types/node": "26.2.0",
73
+ "@vitest/coverage-v8": "4.1.10",
74
+ "bumpp": "12.2.0",
75
+ "dprint": "0.55.2",
76
+ "fast-check": "4.9.0",
77
+ "oxlint": "1.78.0",
78
+ "size-limit": "13.0.3",
79
79
  "tsup": "8.5.1",
80
80
  "typescript": "6.0.3",
81
- "vitest": "4.1.8"
81
+ "vitest": "4.1.10"
82
82
  },
83
83
  "size-limit": [
84
- { "path": "dist/index.js", "limit": "25 KB", "gzip": true },
85
- { "path": "dist/core.js", "limit": "16 KB", "gzip": true },
86
- { "path": "dist/composition.js", "limit": "4 KB", "gzip": true },
87
- { "path": "dist/data.js", "limit": "13 KB", "gzip": true },
88
- { "path": "dist/types.js", "limit": "2 KB", "gzip": true }
84
+ {
85
+ "path": "dist/index.js",
86
+ "limit": "25 KB",
87
+ "gzip": true
88
+ },
89
+ {
90
+ "path": "dist/core.js",
91
+ "limit": "16 KB",
92
+ "gzip": true
93
+ },
94
+ {
95
+ "path": "dist/composition.js",
96
+ "limit": "4 KB",
97
+ "gzip": true
98
+ },
99
+ {
100
+ "path": "dist/data.js",
101
+ "limit": "13 KB",
102
+ "gzip": true
103
+ },
104
+ {
105
+ "path": "dist/types.js",
106
+ "limit": "2 KB",
107
+ "gzip": true
108
+ }
89
109
  ],
90
110
  "packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f"
91
111
  }