@vielzeug/familiar 1.0.7 → 2.0.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.
Files changed (56) hide show
  1. package/README.md +24 -47
  2. package/dist/_pool.cjs +1 -1
  3. package/dist/_pool.cjs.map +1 -1
  4. package/dist/_pool.d.ts +4 -12
  5. package/dist/_pool.d.ts.map +1 -1
  6. package/dist/_pool.js +163 -222
  7. package/dist/_pool.js.map +1 -1
  8. package/dist/_queue.cjs +1 -1
  9. package/dist/_queue.cjs.map +1 -1
  10. package/dist/_queue.d.ts +3 -39
  11. package/dist/_queue.d.ts.map +1 -1
  12. package/dist/_queue.js +27 -28
  13. package/dist/_queue.js.map +1 -1
  14. package/dist/_stream-pool.cjs +2 -0
  15. package/dist/_stream-pool.cjs.map +1 -0
  16. package/dist/_stream-pool.d.ts +11 -0
  17. package/dist/_stream-pool.d.ts.map +1 -0
  18. package/dist/_stream-pool.js +152 -0
  19. package/dist/_stream-pool.js.map +1 -0
  20. package/dist/errors.cjs.map +1 -1
  21. package/dist/errors.d.ts +1 -1
  22. package/dist/errors.d.ts.map +1 -1
  23. package/dist/errors.js.map +1 -1
  24. package/dist/familiar.cjs +1 -27
  25. package/dist/familiar.cjs.map +1 -1
  26. package/dist/familiar.iife.js +1 -27
  27. package/dist/familiar.iife.js.map +1 -1
  28. package/dist/familiar.js +1 -27
  29. package/dist/familiar.js.map +1 -1
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.js +3 -2
  32. package/dist/protocol.cjs +1 -1
  33. package/dist/protocol.cjs.map +1 -1
  34. package/dist/protocol.d.ts +40 -44
  35. package/dist/protocol.d.ts.map +1 -1
  36. package/dist/protocol.js +42 -27
  37. package/dist/protocol.js.map +1 -1
  38. package/dist/testing/testing.cjs +1 -1
  39. package/dist/testing/testing.cjs.map +1 -1
  40. package/dist/testing/testing.d.ts +15 -22
  41. package/dist/testing/testing.d.ts.map +1 -1
  42. package/dist/testing/testing.js +53 -35
  43. package/dist/testing/testing.js.map +1 -1
  44. package/dist/types.d.ts +49 -119
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/worker.cjs +1 -27
  47. package/dist/worker.cjs.map +1 -1
  48. package/dist/worker.d.ts +11 -67
  49. package/dist/worker.d.ts.map +1 -1
  50. package/dist/worker.js +139 -187
  51. package/dist/worker.js.map +1 -1
  52. package/package.json +13 -6
  53. package/dist/_dev.cjs +0 -2
  54. package/dist/_dev.cjs.map +0 -1
  55. package/dist/_dev.js +0 -6
  56. package/dist/_dev.js.map +0 -1
package/dist/_pool.js CHANGED
@@ -1,265 +1,206 @@
1
- import { FamiliarQueueFullError as e, FamiliarRuntimeError as t, FamiliarTerminatedError as n, FamiliarTimeoutError as r } from "./errors.js";
2
- import { TaskQueue as i } from "./_queue.js";
3
- import { unrefTimer as a } from "./_timers.js";
4
- import { abortError as o } from "@vielzeug/arsenal/async";
1
+ import { FamiliarQueueFullError as e, FamiliarTerminatedError as t, FamiliarTimeoutError as n } from "./errors.js";
2
+ import { TaskQueue as r } from "./_queue.js";
3
+ import { unrefTimer as i } from "./_timers.js";
4
+ import { abortError as a } from "@vielzeug/arsenal/async";
5
5
  //#region src/_pool.ts
6
- function s(s, c) {
7
- let { concurrency: l, defaultTimeout: u, maxQueue: d, onFull: f } = c, p = [...s], m = new i(), h = new AbortController(), g = [], _ = [], v = 0, y, b = 0, x = 0, S = 0, C = !1;
8
- function w() {
9
- return v === 0 && m.size === 0;
6
+ function o(o, s) {
7
+ let c = [...o], l = new r(), u = new AbortController(), d = [], f = [], p = 0, m = 0, h = 0, g, _ = !1, v = !1;
8
+ function y() {
9
+ return p === 0 && l.size === 0;
10
10
  }
11
- function T() {
12
- if (!w() || g.length === 0) return;
13
- let e = g.splice(0);
14
- for (let t of e) t();
11
+ function b() {
12
+ if (y()) for (let e of d.splice(0)) e.timer && clearTimeout(e.timer), e.resolve();
15
13
  }
16
- function E(e) {
17
- return w() ? Promise.resolve() : new Promise((t, n) => {
18
- if (g.push(t), e !== void 0) {
19
- let i = setTimeout(() => {
20
- let i = g.indexOf(t);
21
- i !== -1 && g.splice(i, 1), n(new r(e));
22
- }, e);
23
- a(i);
24
- }
14
+ function x(e = {}) {
15
+ return y() ? Promise.resolve() : new Promise((t, r) => {
16
+ let a = {
17
+ reject: r,
18
+ resolve: t
19
+ };
20
+ e.timeout !== void 0 && (a.timer = setTimeout(() => {
21
+ let t = d.indexOf(a);
22
+ t !== -1 && d.splice(t, 1), r(new n(e.timeout));
23
+ }, e.timeout), i(a.timer)), d.push(a);
25
24
  });
26
25
  }
27
- function D() {
28
- let e = _.shift();
29
- e && e();
26
+ function S() {
27
+ f.shift()?.resolve();
30
28
  }
31
- let O = !1;
32
- function k() {
33
- if (!(C || O)) {
34
- for (O = !0; !C && p.length > 0 && m.size > 0;) {
35
- let e = A();
36
- if (!e) break;
37
- let t = p.pop();
38
- e.cleanupAbort?.(), v += 1, D();
39
- let r = e.timeout ?? u;
40
- t.run(e.input, e.transferables, r).then((n) => {
41
- p.push(t), --v, b += 1, e.resolve(n), k(), w() && T();
42
- }, (r) => {
43
- p.push(t), --v, r instanceof n || (x += 1), e.reject(r), k(), w() && T();
44
- });
45
- }
46
- O = !1;
47
- }
29
+ function C(e) {
30
+ for (let t of f.splice(0)) t.reject(e);
48
31
  }
49
- function A() {
50
- for (; m.size > 0;) {
51
- let e = m.shift();
32
+ function w(e) {
33
+ return e?.aborted ? Promise.reject(a(e)) : new Promise((t, n) => {
34
+ let r = !1, i = {
35
+ cleanup() {
36
+ e?.removeEventListener("abort", o);
37
+ },
38
+ reject(e) {
39
+ if (r) return;
40
+ r = !0, i.cleanup();
41
+ let t = f.indexOf(i);
42
+ t !== -1 && f.splice(t, 1), n(e);
43
+ },
44
+ resolve() {
45
+ r || (r = !0, i.cleanup(), t());
46
+ }
47
+ }, o = () => i.reject(a(e));
48
+ e?.addEventListener("abort", o, { once: !0 }), f.push(i);
49
+ });
50
+ }
51
+ function T() {
52
+ for (; l.size > 0;) {
53
+ let e = l.shift();
52
54
  if (!e) break;
53
55
  if (e.signal?.aborted) {
54
- e.cleanupAbort?.(), e.reject(o(e.signal));
56
+ e.cleanupAbort?.(), e.reject(a(e.signal)), S();
55
57
  continue;
56
58
  }
57
59
  return e;
58
60
  }
59
- T();
61
+ b();
60
62
  }
61
- function j() {
62
- if (C) return;
63
- C = !0, h.abort();
64
- for (let e of s) e.terminate();
65
- for (; m.size > 0;) {
66
- let e = m.shift();
67
- if (!e) break;
68
- e.cleanupAbort?.(), e.reject(new n());
63
+ function E() {
64
+ if (!(_ || v)) {
65
+ for (_ = !0; !v && c.length > 0 && l.size > 0;) {
66
+ let e = T();
67
+ if (!e) break;
68
+ let n = e, r = c.pop(), i = n.timeout ?? s.defaultTimeout;
69
+ p += 1, S();
70
+ let o = () => r.cancel(a(n.signal));
71
+ n.cleanupAbort = () => {
72
+ n.signal?.removeEventListener("abort", o), n.cleanupAbort = void 0;
73
+ }, n.signal?.addEventListener("abort", o, { once: !0 }), r.run(n.input, n.transferables, i).then((e) => l(e), (e) => u(e));
74
+ function l(e) {
75
+ n.cleanupAbort?.(), c.push(r), --p, m += 1, n.resolve(e), E(), b();
76
+ }
77
+ function u(e) {
78
+ n.cleanupAbort?.(), c.push(r), --p, !(e instanceof t) && e?.constructor?.name !== "AbortError" && (h += 1), n.reject(e), E(), b();
79
+ }
80
+ }
81
+ _ = !1;
69
82
  }
70
- let e = g.splice(0);
71
- for (let t of e) t();
72
- for (let e of _.splice(0)) e();
73
- }
74
- function M(e) {
75
- return C ? Promise.resolve() : y || (y = E(e).then(j, (e) => {
76
- throw j(), e;
77
- }), y);
78
83
  }
79
- async function N(t, r = {}) {
80
- let { priority: i = 0, signal: a, timeout: s, transferables: c = [] } = r;
81
- if (C) throw new n();
82
- if (y) throw new n("Worker is draining");
83
- if (a?.aborted) throw o(a);
84
- if (f === "wait" && d !== void 0) {
85
- for (; !C && !y && m.size >= d;) await new Promise((e) => _.push(e));
86
- if (C) throw new n();
87
- if (y) throw new n("Worker is draining");
84
+ async function D(n, r = {}) {
85
+ let { priority: i = 0, signal: o, timeout: c, transferables: u = [] } = r;
86
+ if (v) throw new t();
87
+ if (g) throw new t("Worker is draining");
88
+ if (o?.aborted) throw a(o);
89
+ for (; s.onFull === "wait" && s.maxQueue !== void 0 && l.size >= s.maxQueue;) {
90
+ if (await w(o), v) throw new t();
91
+ if (g) throw new t("Worker is draining");
92
+ if (o?.aborted) throw a(o);
88
93
  }
89
- let l, u, p = new Promise((e, t) => {
90
- l = e, u = t;
91
- }), h = {
92
- input: t,
94
+ let d, f, p = new Promise((e, t) => {
95
+ d = e, f = t;
96
+ }), m = {
97
+ input: n,
93
98
  priority: i,
94
- reject: u,
95
- resolve: l,
96
- signal: a,
97
- timeout: s,
98
- transferables: c
99
+ reject: f,
100
+ resolve: d,
101
+ signal: o,
102
+ timeout: c,
103
+ transferables: u
99
104
  };
100
- if (!m.enqueue(h, f === "wait" ? void 0 : d)) throw new e(d);
101
- if (a) {
105
+ if (!l.enqueue(m, s.onFull === "wait" ? void 0 : s.maxQueue)) throw new e(s.maxQueue);
106
+ if (o) {
102
107
  let e = () => {
103
- m.remove(h) && (h.cleanupAbort?.(), u(o(a)), T());
108
+ l.remove(m) && (m.cleanupAbort?.(), f(a(o)), S(), b());
104
109
  };
105
- h.cleanupAbort = () => {
106
- a.removeEventListener("abort", e), h.cleanupAbort = void 0;
107
- }, a.addEventListener("abort", e, { once: !0 });
110
+ m.cleanupAbort = () => {
111
+ o.removeEventListener("abort", e), m.cleanupAbort = void 0;
112
+ }, o.addEventListener("abort", e, { once: !0 });
108
113
  }
109
- return k(), p;
110
- }
111
- function P(e, n = {}) {
112
- if (C) throw new t("Worker was terminated");
113
- let r = p.pop();
114
- if (!r) throw new t(`runStream() requires a free worker slot; all ${s.length} slot${s.length === 1 ? "" : "s"} are busy`);
115
- let { timeout: i, transferables: a = [] } = n, o = r.runStream(e, a, i);
116
- return { [Symbol.asyncIterator]() {
117
- let e = o[Symbol.asyncIterator](), t = !1, n = () => {
118
- t || (t = !0, p.push(r));
119
- };
120
- return {
121
- async next() {
122
- let t = await e.next();
123
- return t.done && n(), t;
124
- },
125
- async return(t) {
126
- return r.cancel(), n(), e.return?.(t) ?? {
127
- done: !0,
128
- value: t
129
- };
130
- },
131
- async throw(t) {
132
- if (r.cancel(), n(), e.throw) return e.throw(t);
133
- throw t;
134
- }
135
- };
136
- } };
114
+ return E(), p;
137
115
  }
138
- async function* F(e, t = {}) {
139
- if (e.length === 0) return;
140
- let { ordered: n = !0, ...r } = t, i = new AbortController();
141
- try {
142
- if (n) {
143
- let t = e.map((e) => N(e, {
144
- ...r,
145
- signal: i.signal
146
- }));
147
- for (let e of t) yield await e;
148
- } else {
149
- let t = [], n = null, a = 0, o = Math.max(1, l);
150
- function s() {
151
- if (a >= e.length) return;
152
- let o = e[a++];
153
- N(o, {
154
- ...r,
155
- signal: i.signal
156
- }).then((e) => {
157
- t.push({ value: e }), n?.(), n = null;
158
- }, (e) => {
159
- t.push({ error: e }), n?.(), n = null;
160
- });
161
- }
162
- for (let t = 0; t < o && t < e.length; t++) s();
163
- for (let r = 0; r < e.length; r++) {
164
- for (; t.length === 0;) await new Promise((e) => {
165
- n = e;
166
- });
167
- let e = t.shift();
168
- if ("error" in e) throw e.error;
169
- yield e.value, s();
170
- }
116
+ function O() {
117
+ if (!v) {
118
+ v = !0, u.abort();
119
+ for (let e of o) e.terminate();
120
+ for (; l.size > 0;) {
121
+ let e = l.shift();
122
+ if (!e) break;
123
+ e.cleanupAbort?.(), e.reject(new t());
171
124
  }
172
- } finally {
173
- i.abort();
174
- }
175
- }
176
- function I(e, t = {}) {
177
- let n = new AbortController();
178
- if (t.signal) {
179
- let e = t.signal;
180
- e.aborted ? n.abort(e.reason) : e.addEventListener("abort", () => n.abort(e.reason), { once: !0 });
125
+ C(new t()), b();
181
126
  }
182
- S += 1;
183
- let r = 0, i = 0, a = !1, o = [];
184
- function s() {
185
- !a && r > 0 && r === i && (a = !0, --S);
186
- }
187
- return {
188
- abort(e) {
189
- n.abort(e);
190
- },
191
- drain() {
192
- let e = o.splice(0);
193
- return a || (a = !0, --S), Promise.allSettled(e);
194
- },
195
- get name() {
196
- return e;
197
- },
198
- get pending() {
199
- return r - i;
200
- },
201
- run(e, t = {}) {
202
- r += 1;
203
- let a = N(e, {
204
- ...t,
205
- signal: n.signal
206
- });
207
- return o.push(a), a.then(() => {
208
- i += 1, s();
209
- }, () => {
210
- i += 1, s();
211
- }), a;
212
- },
213
- get size() {
214
- return r;
215
- }
216
- };
217
127
  }
218
- function L() {
219
- return C ? "terminated" : v > 0 || m.size > 0 ? "running" : "idle";
128
+ function k(e = {}) {
129
+ return v ? Promise.resolve() : g || (C(new t("Worker is draining")), g = x(e).then(() => O(), (e) => {
130
+ throw O(), e;
131
+ }), g);
220
132
  }
221
133
  return {
222
- get active() {
223
- return v;
134
+ get disposalSignal() {
135
+ return u.signal;
224
136
  },
225
- batch: F,
226
- get completed() {
227
- return b;
137
+ dispose: O,
138
+ get disposed() {
139
+ return v;
228
140
  },
229
- get concurrency() {
230
- return l;
141
+ drain: k,
142
+ async prime() {
143
+ await Promise.all(o.map((e) => e.prime()));
231
144
  },
232
- get disposalSignal() {
233
- return h.signal;
145
+ run: D,
146
+ get stats() {
147
+ return {
148
+ active: p,
149
+ completed: m,
150
+ failed: h,
151
+ queued: l.size
152
+ };
234
153
  },
235
- dispose: j,
236
- get disposed() {
237
- return C;
154
+ get status() {
155
+ return v ? "terminated" : p === 0 ? "idle" : "running";
238
156
  },
239
- drain: M,
240
- get failed() {
241
- return x;
157
+ [Symbol.asyncDispose]: () => k(),
158
+ [Symbol.dispose]: O
159
+ };
160
+ }
161
+ async function* s(e, t, n = {}) {
162
+ let r = new AbortController(), i = () => r.abort(n.signal.reason);
163
+ n.signal?.addEventListener("abort", i, { once: !0 });
164
+ let a = t.map((t) => e.run(t, {
165
+ ...n,
166
+ signal: r.signal
167
+ }));
168
+ try {
169
+ for (let e of a) yield await e;
170
+ } catch (e) {
171
+ throw r.abort(e), await Promise.allSettled(a), e;
172
+ } finally {
173
+ n.signal?.removeEventListener("abort", i), r.abort(), await Promise.allSettled(a);
174
+ }
175
+ }
176
+ function c(e, t = void 0, n = {}) {
177
+ let r = new AbortController(), i = /* @__PURE__ */ new Set(), a = 0;
178
+ return n.signal?.addEventListener("abort", () => r.abort(n.signal.reason), { once: !0 }), {
179
+ abort(e) {
180
+ r.abort(e);
242
181
  },
243
- group: I,
244
- get groupCount() {
245
- return S;
182
+ async drain() {
183
+ return Promise.allSettled([...i]);
246
184
  },
247
- prime() {
248
- return Promise.all(s.map((e) => e.prime())).then(() => {});
185
+ get name() {
186
+ return t;
249
187
  },
250
- get queued() {
251
- return m.size;
188
+ get pending() {
189
+ return i.size;
252
190
  },
253
- run: N,
254
- runStream: P,
255
- get status() {
256
- return L();
191
+ run(t, n = {}) {
192
+ let o = e.run(t, {
193
+ ...n,
194
+ signal: r.signal
195
+ });
196
+ return a += 1, i.add(o), o.finally(() => i.delete(o)), o;
257
197
  },
258
- [Symbol.asyncDispose]: () => M(),
259
- [Symbol.dispose]: j
198
+ get size() {
199
+ return a;
200
+ }
260
201
  };
261
202
  }
262
203
  //#endregion
263
- export { s as createPool };
204
+ export { s as batch, o as createPool, c as createTaskGroup };
264
205
 
265
206
  //# sourceMappingURL=_pool.js.map
package/dist/_pool.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_pool.js","names":[],"sources":["../src/_pool.ts"],"sourcesContent":["/**\n * Shared pool orchestration engine used by both the real worker implementation and the test\n * double. Handles queue management, iterative drain loop, abort handling, lifecycle (drain /\n * dispose), metrics, batch streaming, task groups, and backpressure.\n *\n * Callers provide a SlotStrategy array — each slot encapsulates run/runStream/prime/terminate.\n * The pool does not care whether slots use a Web Worker or run in-process.\n *\n * Not part of the public API surface.\n */\n\nimport { abortError } from '@vielzeug/arsenal/async';\n\nimport type {\n BatchOptions,\n GroupOptions,\n RunOptions,\n SlotStrategy,\n TaskGroup,\n WorkerHandle,\n WorkerStatus,\n} from './types';\n\nimport { type QueueItem, TaskQueue } from './_queue';\nimport { unrefTimer } from './_timers';\nimport { FamiliarQueueFullError, FamiliarRuntimeError, FamiliarTerminatedError, FamiliarTimeoutError } from './errors';\n\nexport type PoolOptions = {\n concurrency: number;\n defaultTimeout: number | undefined;\n maxQueue: number | undefined;\n onFull: 'reject' | 'wait';\n};\n\nexport function createPool<TInput, TOutput>(\n slots: SlotStrategy<TInput, TOutput>[],\n options: PoolOptions,\n): WorkerHandle<TInput, TOutput> {\n const { concurrency, defaultTimeout, maxQueue, onFull } = options;\n const freeSlots = [...slots];\n const queue = new TaskQueue<TInput, TOutput>();\n const disposeController = new AbortController();\n const idleResolvers: Array<() => void> = [];\n /** Waiters blocked on run() because the queue is full (onFull='wait' mode). */\n const fullWaiters: Array<() => void> = [];\n\n let activeCount = 0;\n let drainPromise: Promise<void> | undefined;\n let completedCount = 0;\n let failedCount = 0;\n let groupActiveCount = 0;\n let terminated = false;\n\n // ─── Idle tracking ────────────────────────────────────────────────────────────\n\n function isIdle(): boolean {\n return activeCount === 0 && queue.size === 0;\n }\n\n function notifyIdle(): void {\n if (!isIdle() || idleResolvers.length === 0) return;\n\n const resolvers = idleResolvers.splice(0);\n\n for (const resolve of resolvers) resolve();\n }\n\n function waitForIdle(timeoutMs?: number): Promise<void> {\n if (isIdle()) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n idleResolvers.push(resolve);\n\n if (timeoutMs !== undefined) {\n const timer = setTimeout(() => {\n const idx = idleResolvers.indexOf(resolve);\n\n if (idx !== -1) idleResolvers.splice(idx, 1);\n\n reject(new FamiliarTimeoutError(timeoutMs));\n }, timeoutMs);\n\n unrefTimer(timer);\n }\n });\n }\n\n // ─── Full-queue backpressure (onFull='wait') ──────────────────────────────────\n\n function releaseOneFullWaiter(): void {\n const waiter = fullWaiters.shift();\n\n if (waiter) waiter();\n }\n\n // ─── Drain loop (iterative) ───────────────────────────────────────────────────\n\n let draining = false;\n\n function drainLoop(): void {\n if (terminated || draining) return;\n\n draining = true;\n\n while (!terminated && freeSlots.length > 0 && queue.size > 0) {\n const item = nextItem();\n\n if (!item) break;\n\n const slot = freeSlots.pop()!;\n\n item.cleanupAbort?.();\n activeCount += 1;\n releaseOneFullWaiter();\n\n const taskTimeout = item.timeout ?? defaultTimeout;\n\n slot.run(item.input, item.transferables, taskTimeout).then(\n (result) => {\n freeSlots.push(slot);\n activeCount -= 1;\n completedCount += 1;\n item.resolve(result);\n drainLoop();\n\n if (isIdle()) notifyIdle();\n },\n (error: unknown) => {\n freeSlots.push(slot);\n activeCount -= 1;\n\n if (!(error instanceof FamiliarTerminatedError)) failedCount += 1;\n\n item.reject(error);\n drainLoop();\n\n if (isIdle()) notifyIdle();\n },\n );\n }\n\n draining = false;\n }\n\n // ─── Queue helpers ────────────────────────────────────────────────────────────\n\n function nextItem(): QueueItem<TInput, TOutput> | undefined {\n while (queue.size > 0) {\n const item = queue.shift();\n\n if (!item) break;\n\n if (item.signal?.aborted) {\n item.cleanupAbort?.();\n item.reject(abortError(item.signal));\n continue;\n }\n\n return item;\n }\n\n // All remaining items were aborted and rejected above — the pool may now be idle.\n // Notify any drain() waiter so it does not hang.\n notifyIdle();\n\n return undefined;\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────────\n\n function dispose(): void {\n if (terminated) return;\n\n terminated = true;\n disposeController.abort();\n\n for (const slot of slots) slot.terminate();\n\n while (queue.size > 0) {\n const item = queue.shift();\n\n if (!item) break;\n\n item.cleanupAbort?.();\n item.reject(new FamiliarTerminatedError());\n }\n\n const resolvers = idleResolvers.splice(0);\n\n for (const resolve of resolvers) resolve();\n\n for (const waiter of fullWaiters.splice(0)) waiter();\n }\n\n function drain(timeoutMs?: number): Promise<void> {\n if (terminated) return Promise.resolve();\n\n if (drainPromise) return drainPromise;\n\n drainPromise = waitForIdle(timeoutMs).then(dispose, (err) => {\n dispose();\n throw err;\n });\n\n return drainPromise;\n }\n\n // ─── run() ───────────────────────────────────────────────────────────────────\n\n async function run(input: TInput, runOptions: RunOptions = {}): Promise<TOutput> {\n const { priority = 0, signal, timeout, transferables = [] } = runOptions;\n\n if (terminated) {\n throw new FamiliarTerminatedError();\n }\n\n if (drainPromise) {\n throw new FamiliarTerminatedError('Worker is draining');\n }\n\n if (signal?.aborted) {\n throw abortError(signal);\n }\n\n if (onFull === 'wait' && maxQueue !== undefined) {\n while (!terminated && !drainPromise && queue.size >= maxQueue) {\n await new Promise<void>((resolve) => fullWaiters.push(resolve));\n }\n\n if (terminated) throw new FamiliarTerminatedError();\n\n if (drainPromise) throw new FamiliarTerminatedError('Worker is draining');\n }\n\n let resolve!: (value: TOutput) => void;\n let reject!: (reason: unknown) => void;\n\n const promise = new Promise<TOutput>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n const item: QueueItem<TInput, TOutput> = {\n input,\n priority,\n reject,\n resolve,\n signal,\n timeout,\n transferables,\n };\n\n if (!queue.enqueue(item, onFull === 'wait' ? undefined : maxQueue)) {\n // maxQueue is guaranteed defined here: enqueue() only returns false when maxQueue is set.\n throw new FamiliarQueueFullError(maxQueue as number);\n }\n\n if (signal) {\n const onAbort = () => {\n if (!queue.remove(item)) return;\n\n item.cleanupAbort?.();\n reject(abortError(signal));\n notifyIdle();\n };\n\n item.cleanupAbort = () => {\n signal.removeEventListener('abort', onAbort);\n item.cleanupAbort = undefined;\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n }\n\n drainLoop();\n\n return promise;\n }\n\n // ─── runStream() ─────────────────────────────────────────────────────────────\n\n function runStream(input: TInput, options: Omit<RunOptions, 'signal'> = {}): AsyncIterable<TOutput> {\n if (terminated) {\n throw new FamiliarRuntimeError('Worker was terminated');\n }\n\n const slot = freeSlots.pop();\n\n if (!slot) {\n throw new FamiliarRuntimeError(\n `runStream() requires a free worker slot; all ${slots.length} slot${slots.length === 1 ? '' : 's'} are busy`,\n );\n }\n\n const { timeout, transferables = [] } = options;\n const iter = slot.runStream(input, transferables, timeout);\n\n return {\n [Symbol.asyncIterator]() {\n const inner = iter[Symbol.asyncIterator]();\n let released = false;\n\n const releaseSlot = () => {\n if (!released) {\n released = true;\n freeSlots.push(slot);\n }\n };\n\n return {\n async next() {\n const result = await inner.next();\n\n if (result.done) releaseSlot();\n\n return result;\n },\n\n async return(value?: unknown) {\n slot.cancel();\n releaseSlot();\n\n return inner.return?.(value) ?? { done: true as const, value };\n },\n\n async throw(error?: unknown) {\n slot.cancel();\n releaseSlot();\n\n if (inner.throw) return inner.throw(error);\n\n throw error;\n },\n };\n },\n };\n }\n\n // ─── batch() ─────────────────────────────────────────────────────────────────\n\n async function* batch(inputs: TInput[], batchOptions: BatchOptions = {}): AsyncIterable<TOutput> {\n if (inputs.length === 0) return;\n\n const { ordered = true, ...runOpts } = batchOptions;\n const ac = new AbortController();\n\n try {\n if (ordered) {\n const promises = inputs.map((input) => run(input, { ...runOpts, signal: ac.signal }));\n\n for (const p of promises) {\n yield await p;\n }\n } else {\n // As-completed: yield results in the order tasks finish, not submission order.\n // A single-slot notification channel wakes the consumer when the next result is ready.\n // Submission is windowed to `concurrency` outstanding (in-flight or buffered-but-unread)\n // tasks at a time — releasing capacity for one more submission each time the consumer\n // reads a result — so a slow consumer can't let an entire large batch settle in memory.\n type Completion = { error: unknown } | { value: TOutput };\n\n const completions: Completion[] = [];\n let notifier: (() => void) | null = null;\n let nextIndex = 0;\n const windowSize = Math.max(1, concurrency);\n\n function submitNext(): void {\n if (nextIndex >= inputs.length) return;\n\n const input = inputs[nextIndex++]!;\n\n run(input, { ...runOpts, signal: ac.signal }).then(\n (value) => {\n completions.push({ value });\n notifier?.();\n notifier = null;\n },\n (error: unknown) => {\n completions.push({ error });\n notifier?.();\n notifier = null;\n },\n );\n }\n\n for (let i = 0; i < windowSize && i < inputs.length; i++) submitNext();\n\n for (let i = 0; i < inputs.length; i++) {\n while (completions.length === 0) {\n await new Promise<void>((resolve) => {\n notifier = resolve;\n });\n }\n\n const next = completions.shift()!;\n\n if ('error' in next) throw next.error;\n\n yield next.value;\n\n // Only release capacity for another submission once the consumer has actually resumed\n // past this yield (i.e. did not break/return early) — submitting here unconditionally\n // would over-submit by one task on every early exit.\n submitNext();\n }\n }\n } finally {\n // Abort remaining in-flight tasks on any exit path (normal, consumer break, or error).\n ac.abort();\n }\n }\n\n // ─── group() ─────────────────────────────────────────────────────────────────\n\n function group(name?: string, options: GroupOptions = {}): TaskGroup<TInput, TOutput> {\n const ac = new AbortController();\n\n if (options.signal) {\n const sig = options.signal;\n\n if (sig.aborted) {\n ac.abort(sig.reason);\n } else {\n sig.addEventListener('abort', () => ac.abort(sig.reason), { once: true });\n }\n }\n\n groupActiveCount += 1;\n\n let submittedCount = 0;\n let settledCount = 0;\n let groupClosed = false;\n const pendingPromises: Promise<TOutput>[] = [];\n\n function decrementGroupIfDone(): void {\n if (!groupClosed && submittedCount > 0 && submittedCount === settledCount) {\n groupClosed = true;\n groupActiveCount -= 1;\n }\n }\n\n return {\n abort(reason?: unknown): void {\n ac.abort(reason);\n },\n\n drain(): Promise<PromiseSettledResult<TOutput>[]> {\n const snapshot = pendingPromises.splice(0);\n\n if (!groupClosed) {\n groupClosed = true;\n groupActiveCount -= 1;\n }\n\n return Promise.allSettled(snapshot);\n },\n\n get name() {\n return name;\n },\n\n get pending() {\n return submittedCount - settledCount;\n },\n\n run(input: TInput, runOpts: Omit<RunOptions, 'signal'> = {}): Promise<TOutput> {\n submittedCount += 1;\n\n const p = run(input, { ...runOpts, signal: ac.signal });\n\n pendingPromises.push(p);\n p.then(\n () => {\n settledCount += 1;\n decrementGroupIfDone();\n },\n () => {\n settledCount += 1;\n decrementGroupIfDone();\n },\n );\n\n return p;\n },\n\n get size() {\n return submittedCount;\n },\n };\n }\n\n // ─── Status ──────────────────────────────────────────────────────────────────\n\n function getStatus(): WorkerStatus {\n if (terminated) return 'terminated';\n\n return activeCount > 0 || queue.size > 0 ? 'running' : 'idle';\n }\n\n return {\n get active(): number {\n return activeCount;\n },\n batch,\n get completed(): number {\n return completedCount;\n },\n get concurrency(): number {\n return concurrency;\n },\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n dispose,\n get disposed(): boolean {\n return terminated;\n },\n drain,\n get failed(): number {\n return failedCount;\n },\n group,\n get groupCount(): number {\n return groupActiveCount;\n },\n prime(): Promise<void> {\n return Promise.all(slots.map((s) => s.prime())).then(() => {});\n },\n get queued(): number {\n return queue.size;\n },\n run,\n runStream,\n get status(): WorkerStatus {\n return getStatus();\n },\n [Symbol.asyncDispose]: () => drain(),\n [Symbol.dispose]: dispose,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"_pool.js","names":[],"sources":["../src/_pool.ts"],"sourcesContent":["import { abortError } from '@vielzeug/arsenal/async';\n\nimport type {\n BatchOptions,\n DrainOptions,\n RunOptions,\n SlotStrategy,\n TaskGroup,\n TaskGroupOptions,\n WorkerPool,\n WorkerStats,\n WorkerStatus,\n} from './types';\n\nimport { type QueueItem, TaskQueue } from './_queue';\nimport { unrefTimer } from './_timers';\nimport { FamiliarQueueFullError, FamiliarTerminatedError, FamiliarTimeoutError } from './errors';\n\nexport type PoolOptions = {\n concurrency: number;\n defaultTimeout: number | undefined;\n maxQueue: number | undefined;\n onFull: 'reject' | 'wait';\n};\n\ntype IdleWaiter = {\n reject: (reason: unknown) => void;\n resolve: () => void;\n timer?: ReturnType<typeof setTimeout>;\n};\n\ntype CapacityWaiter = {\n cleanup(): void;\n reject(reason: unknown): void;\n resolve(): void;\n};\n\nexport function createPool<TInput, TOutput>(\n slots: SlotStrategy<TInput, TOutput>[],\n options: PoolOptions,\n): WorkerPool<TInput, TOutput> {\n const freeSlots = [...slots];\n const queue = new TaskQueue<TInput, TOutput>();\n const disposalController = new AbortController();\n const idleWaiters: IdleWaiter[] = [];\n const capacityWaiters: CapacityWaiter[] = [];\n let active = 0;\n let completed = 0;\n let failed = 0;\n let drainPromise: Promise<void> | undefined;\n let draining = false;\n let terminated = false;\n\n function isIdle(): boolean {\n return active === 0 && queue.size === 0;\n }\n\n function settleIdle(): void {\n if (!isIdle()) return;\n\n for (const waiter of idleWaiters.splice(0)) {\n if (waiter.timer) clearTimeout(waiter.timer);\n\n waiter.resolve();\n }\n }\n\n function waitForIdle(drainOptions: DrainOptions = {}): Promise<void> {\n if (isIdle()) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n const waiter: IdleWaiter = { reject, resolve };\n\n if (drainOptions.timeout !== undefined) {\n waiter.timer = setTimeout(() => {\n const index = idleWaiters.indexOf(waiter);\n\n if (index !== -1) idleWaiters.splice(index, 1);\n\n reject(new FamiliarTimeoutError(drainOptions.timeout!));\n }, drainOptions.timeout);\n unrefTimer(waiter.timer);\n }\n\n idleWaiters.push(waiter);\n });\n }\n\n function releaseCapacity(): void {\n capacityWaiters.shift()?.resolve();\n }\n\n function rejectCapacity(reason: unknown): void {\n for (const waiter of capacityWaiters.splice(0)) waiter.reject(reason);\n }\n\n function waitForCapacity(signal: AbortSignal | undefined): Promise<void> {\n if (signal?.aborted) return Promise.reject(abortError(signal));\n\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const waiter: CapacityWaiter = {\n cleanup() {\n signal?.removeEventListener('abort', onAbort);\n },\n reject(reason) {\n if (settled) return;\n\n settled = true;\n waiter.cleanup();\n\n const index = capacityWaiters.indexOf(waiter);\n\n if (index !== -1) capacityWaiters.splice(index, 1);\n\n reject(reason);\n },\n resolve() {\n if (settled) return;\n\n settled = true;\n waiter.cleanup();\n resolve();\n },\n };\n const onAbort = () => waiter.reject(abortError(signal!));\n\n signal?.addEventListener('abort', onAbort, { once: true });\n capacityWaiters.push(waiter);\n });\n }\n\n function nextItem(): QueueItem<TInput, TOutput> | undefined {\n while (queue.size > 0) {\n const item = queue.shift();\n\n if (!item) break;\n\n if (item.signal?.aborted) {\n item.cleanupAbort?.();\n item.reject(abortError(item.signal));\n releaseCapacity();\n continue;\n }\n\n return item;\n }\n\n settleIdle();\n\n return undefined;\n }\n\n function drainQueue(): void {\n if (draining || terminated) return;\n\n draining = true;\n\n while (!terminated && freeSlots.length > 0 && queue.size > 0) {\n const next = nextItem();\n\n if (!next) break;\n\n const item = next;\n const slot = freeSlots.pop()!;\n const timeout = item.timeout ?? options.defaultTimeout;\n\n active += 1;\n releaseCapacity();\n\n const onAbort = () => slot.cancel(abortError(item.signal!));\n\n item.cleanupAbort = () => {\n item.signal?.removeEventListener('abort', onAbort);\n item.cleanupAbort = undefined;\n };\n item.signal?.addEventListener('abort', onAbort, { once: true });\n\n slot.run(item.input, item.transferables, timeout).then(\n (value) => finish(value),\n (error: unknown) => fail(error),\n );\n\n function finish(value: TOutput): void {\n item.cleanupAbort?.();\n freeSlots.push(slot);\n active -= 1;\n completed += 1;\n item.resolve(value);\n drainQueue();\n settleIdle();\n }\n\n function fail(error: unknown): void {\n item.cleanupAbort?.();\n freeSlots.push(slot);\n active -= 1;\n\n if (!(error instanceof FamiliarTerminatedError) && error?.constructor?.name !== 'AbortError') failed += 1;\n\n item.reject(error);\n drainQueue();\n settleIdle();\n }\n }\n\n draining = false;\n }\n\n async function run(input: TInput, runOptions: RunOptions = {}): Promise<TOutput> {\n const { priority = 0, signal, timeout, transferables = [] } = runOptions;\n\n if (terminated) throw new FamiliarTerminatedError();\n\n if (drainPromise) throw new FamiliarTerminatedError('Worker is draining');\n\n if (signal?.aborted) throw abortError(signal);\n\n while (options.onFull === 'wait' && options.maxQueue !== undefined && queue.size >= options.maxQueue) {\n await waitForCapacity(signal);\n\n if (terminated) throw new FamiliarTerminatedError();\n\n if (drainPromise) throw new FamiliarTerminatedError('Worker is draining');\n\n if (signal?.aborted) throw abortError(signal);\n }\n\n let resolve!: (value: TOutput) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<TOutput>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n const item: QueueItem<TInput, TOutput> = { input, priority, reject, resolve, signal, timeout, transferables };\n\n if (!queue.enqueue(item, options.onFull === 'wait' ? undefined : options.maxQueue)) {\n throw new FamiliarQueueFullError(options.maxQueue!);\n }\n\n if (signal) {\n const onAbort = () => {\n if (!queue.remove(item)) return;\n\n item.cleanupAbort?.();\n reject(abortError(signal));\n releaseCapacity();\n settleIdle();\n };\n\n item.cleanupAbort = () => {\n signal.removeEventListener('abort', onAbort);\n item.cleanupAbort = undefined;\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n\n drainQueue();\n\n return promise;\n }\n\n function dispose(): void {\n if (terminated) return;\n\n terminated = true;\n disposalController.abort();\n for (const slot of slots) slot.terminate();\n\n while (queue.size > 0) {\n const item = queue.shift();\n\n if (!item) break;\n\n item.cleanupAbort?.();\n item.reject(new FamiliarTerminatedError());\n }\n\n rejectCapacity(new FamiliarTerminatedError());\n settleIdle();\n }\n\n function drain(drainOptions: DrainOptions = {}): Promise<void> {\n if (terminated) return Promise.resolve();\n\n if (drainPromise) return drainPromise;\n\n rejectCapacity(new FamiliarTerminatedError('Worker is draining'));\n drainPromise = waitForIdle(drainOptions).then(\n () => dispose(),\n (error: unknown) => {\n dispose();\n throw error;\n },\n );\n\n return drainPromise;\n }\n\n return {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose,\n get disposed() {\n return terminated;\n },\n drain,\n async prime(): Promise<void> {\n await Promise.all(slots.map((slot) => slot.prime()));\n },\n run,\n get stats(): WorkerStats {\n return { active, completed, failed, queued: queue.size };\n },\n get status(): WorkerStatus {\n if (terminated) return 'terminated';\n\n return active === 0 ? 'idle' : 'running';\n },\n [Symbol.asyncDispose]: () => drain(),\n [Symbol.dispose]: dispose,\n };\n}\n\nexport async function* batch<TInput, TOutput>(\n pool: WorkerPool<TInput, TOutput>,\n inputs: readonly TInput[],\n options: BatchOptions = {},\n): AsyncIterable<TOutput> {\n const controller = new AbortController();\n const onAbort = () => controller.abort(options.signal!.reason);\n\n options.signal?.addEventListener('abort', onAbort, { once: true });\n\n const tasks = inputs.map((input) => pool.run(input, { ...options, signal: controller.signal }));\n\n try {\n for (const task of tasks) yield await task;\n } catch (error) {\n controller.abort(error);\n await Promise.allSettled(tasks);\n throw error;\n } finally {\n options.signal?.removeEventListener('abort', onAbort);\n controller.abort();\n await Promise.allSettled(tasks);\n }\n}\n\nexport function createTaskGroup<TInput, TOutput>(\n pool: WorkerPool<TInput, TOutput>,\n name: string | undefined = undefined,\n options: TaskGroupOptions = {},\n): TaskGroup<TInput, TOutput> {\n const controller = new AbortController();\n const tasks = new Set<Promise<TOutput>>();\n let size = 0;\n const onAbort = () => controller.abort(options.signal!.reason);\n\n options.signal?.addEventListener('abort', onAbort, { once: true });\n\n return {\n abort(reason?: unknown): void {\n controller.abort(reason);\n },\n async drain(): Promise<PromiseSettledResult<TOutput>[]> {\n return Promise.allSettled([...tasks]);\n },\n get name() {\n return name;\n },\n get pending() {\n return tasks.size;\n },\n run(input, runOptions = {}): Promise<TOutput> {\n const task = pool.run(input, { ...runOptions, signal: controller.signal });\n\n size += 1;\n tasks.add(task);\n void task.finally(() => tasks.delete(task));\n\n return task;\n },\n get size() {\n return size;\n },\n };\n}\n"],"mappings":"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"}
package/dist/_queue.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class{heap=[];liveCount=0;seq=0;get size(){return this.liveCount}enqueue(e,t){return t!==void 0&&this.liveCount>=t?!1:(this.heap.push({_seq:this.seq++,item:e}),this.siftUp(this.heap.length-1),this.liveCount++,!0)}shift(){for(;this.heap.length>0;){let{item:e}=this.extractTop();if(!e.cancelled)return this.liveCount--,e}}remove(e){if(e.cancelled)return!1;for(let t=0;t<this.heap.length;t++)if(this.heap[t].item===e)return e.cancelled=!0,this.liveCount--,!0;return!1}extractTop(){let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&(this.heap[0]=t,this.siftDown(0)),e}higher(e,t){return e.item.priority===t.item.priority?e._seq<t._seq:e.item.priority>t.item.priority}siftUp(e){for(;e>0;){let t=e-1>>1;if(this.higher(this.heap[e],this.heap[t]))[this.heap[e],this.heap[t]]=[this.heap[t],this.heap[e]],e=t;else break}}siftDown(e){let t=this.heap.length;for(;;){let n=e,r=2*e+1,i=2*e+2;if(r<t&&this.higher(this.heap[r],this.heap[n])&&(n=r),i<t&&this.higher(this.heap[i],this.heap[n])&&(n=i),n===e)break;[this.heap[e],this.heap[n]]=[this.heap[n],this.heap[e]],e=n}}};exports.TaskQueue=e;
1
+ var e=class{#e=[];#t=0;#n=0;get size(){return this.#t}enqueue(e,t){return t!==void 0&&this.#t>=t?!1:(this.#e.push({item:e,sequence:this.#n++}),this.#a(this.#e.length-1),this.#t+=1,!0)}remove(e){if(e.cancelled)return!1;for(let t of this.#e)if(t.item===e)return e.cancelled=!0,--this.#t,!0;return!1}shift(){for(;this.#e.length>0;){let e=this.#r().item;if(!e.cancelled)return--this.#t,e}}#r(){let e=this.#e[0],t=this.#e.pop();return this.#e.length>0&&(this.#e[0]=t,this.#o(0)),e}#i(e,t){return e.item.priority===t.item.priority?e.sequence<t.sequence:e.item.priority>t.item.priority}#a(e){for(;e>0;){let t=e-1>>1;if(!this.#i(this.#e[e],this.#e[t]))return;[this.#e[e],this.#e[t]]=[this.#e[t],this.#e[e]],e=t}}#o(e){for(;;){let t=e*2+1,n=t+1,r=e;if(t<this.#e.length&&this.#i(this.#e[t],this.#e[r])&&(r=t),n<this.#e.length&&this.#i(this.#e[n],this.#e[r])&&(r=n),r===e)return;[this.#e[e],this.#e[r]]=[this.#e[r],this.#e[e]],e=r}}};exports.TaskQueue=e;
2
2
  //# sourceMappingURL=_queue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_queue.cjs","names":[],"sources":["../src/_queue.ts"],"sourcesContent":["/**\n * Internal priority-queue for @vielzeug/familiar.\n * Not part of the public API surface.\n *\n * Uses a binary max-heap ordered by priority (higher value = runs first), with\n * insertion-sequence tiebreaking to ensure FIFO ordering within the same priority.\n * Cancelled items are marked lazily and silently skipped by shift().\n *\n * Complexity:\n * enqueue O(log n)\n * shift O(log n) amortised (lazy cancellation may scan multiple cancelled tops)\n * remove O(n) scan + O(1) mark — adequate for typical queue depths\n */\n\nexport type QueueItem<TInput, TOutput> = {\n /** Marked true by remove(); the next shift() discards the item silently. */\n cancelled?: boolean;\n cleanupAbort?: () => void;\n input: TInput;\n /** Scheduling priority. Higher value = runs first. Default: 0. */\n priority: number;\n reject: (reason: unknown) => void;\n resolve: (value: TOutput) => void;\n signal?: AbortSignal;\n timeout?: number;\n transferables: Transferable[];\n};\n\n/**\n * Internal heap node: wraps a QueueItem with an insertion sequence for FIFO tiebreaking.\n * Using a wrapper instead of mutating QueueItem keeps queue items free of internal\n * scheduling metadata (_seq).\n */\ntype HeapEntry<TInput, TOutput> = { readonly _seq: number; readonly item: QueueItem<TInput, TOutput> };\n\nexport class TaskQueue<TInput, TOutput> {\n private readonly heap: HeapEntry<TInput, TOutput>[] = [];\n /** Accurate count of live (non-cancelled) items. */\n private liveCount = 0;\n private seq = 0;\n\n /** Number of live (non-cancelled) items. Always accurate. */\n get size(): number {\n return this.liveCount;\n }\n\n /**\n * Enqueue item. Returns false (without modifying state) when maxQueue is set and\n * liveCount has already reached it; returns true on success.\n */\n enqueue(item: QueueItem<TInput, TOutput>, maxQueue: number | undefined): boolean {\n if (maxQueue !== undefined && this.liveCount >= maxQueue) return false;\n\n this.heap.push({ _seq: this.seq++, item });\n this.siftUp(this.heap.length - 1);\n this.liveCount++;\n\n return true;\n }\n\n /**\n * Dequeue the highest-priority live item. Silently discards cancelled tombstones.\n * Returns undefined only when there are no live items left.\n */\n shift(): QueueItem<TInput, TOutput> | undefined {\n while (this.heap.length > 0) {\n const { item } = this.extractTop();\n\n if (!item.cancelled) {\n this.liveCount--;\n\n return item;\n }\n // Cancelled: liveCount was already decremented in remove(). Just discard and continue.\n }\n\n return undefined;\n }\n\n /**\n * Mark item as cancelled (O(n) scan). The heap slot is reclaimed lazily by the next shift().\n * Returns true when found and marked, false when the item is not in the queue.\n */\n remove(item: QueueItem<TInput, TOutput>): boolean {\n if (item.cancelled) return false;\n\n for (let i = 0; i < this.heap.length; i++) {\n if (this.heap[i]!.item === item) {\n item.cancelled = true;\n this.liveCount--;\n\n return true;\n }\n }\n\n return false;\n }\n\n // ─── Heap internals ────────────────────────────────────────────────────────\n\n private extractTop(): HeapEntry<TInput, TOutput> {\n const top = this.heap[0]!;\n const last = this.heap.pop()!;\n\n if (this.heap.length > 0) {\n this.heap[0] = last;\n this.siftDown(0);\n }\n\n return top;\n }\n\n /** Returns true when entry a should be dispatched before entry b. */\n private higher(a: HeapEntry<TInput, TOutput>, b: HeapEntry<TInput, TOutput>): boolean {\n if (a.item.priority !== b.item.priority) return a.item.priority > b.item.priority;\n\n return a._seq < b._seq; // earlier insertion wins for equal priority (FIFO)\n }\n\n private siftUp(i: number): void {\n while (i > 0) {\n const parent = (i - 1) >> 1;\n\n if (this.higher(this.heap[i]!, this.heap[parent]!)) {\n [this.heap[i], this.heap[parent]] = [this.heap[parent]!, this.heap[i]!];\n i = parent;\n } else {\n break;\n }\n }\n }\n\n private siftDown(i: number): void {\n const n = this.heap.length;\n\n for (;;) {\n let top = i;\n const left = 2 * i + 1;\n const right = 2 * i + 2;\n\n if (left < n && this.higher(this.heap[left]!, this.heap[top]!)) top = left;\n\n if (right < n && this.higher(this.heap[right]!, this.heap[top]!)) top = right;\n\n if (top === i) break;\n\n [this.heap[i], this.heap[top]] = [this.heap[top]!, this.heap[i]!];\n i = top;\n }\n }\n}\n"],"mappings":"AAmCA,IAAa,EAAb,KAAwC,CACtC,KAAsD,CAAC,EAEvD,UAAoB,EACpB,IAAc,EAGd,IAAI,MAAe,CACjB,OAAO,KAAK,SACd,CAMA,QAAQ,EAAkC,EAAuC,CAO/E,OANI,IAAa,IAAA,IAAa,KAAK,WAAa,EAAiB,IAEjE,KAAK,KAAK,KAAK,CAAE,KAAM,KAAK,MAAO,MAAK,CAAC,EACzC,KAAK,OAAO,KAAK,KAAK,OAAS,CAAC,EAChC,KAAK,YAEE,GACT,CAMA,OAAgD,CAC9C,KAAO,KAAK,KAAK,OAAS,GAAG,CAC3B,GAAM,CAAE,QAAS,KAAK,WAAW,EAEjC,GAAI,CAAC,EAAK,UAGR,MAFA,MAAK,YAEE,CAGX,CAGF,CAMA,OAAO,EAA2C,CAChD,GAAI,EAAK,UAAW,MAAO,GAE3B,IAAK,IAAI,EAAI,EAAG,EAAI,KAAK,KAAK,OAAQ,IACpC,GAAI,KAAK,KAAK,EAAE,CAAE,OAAS,EAIzB,MAHA,GAAK,UAAY,GACjB,KAAK,YAEE,GAIX,MAAO,EACT,CAIA,YAAiD,CAC/C,IAAM,EAAM,KAAK,KAAK,GAChB,EAAO,KAAK,KAAK,IAAI,EAO3B,OALI,KAAK,KAAK,OAAS,IACrB,KAAK,KAAK,GAAK,EACf,KAAK,SAAS,CAAC,GAGV,CACT,CAGA,OAAe,EAA+B,EAAwC,CAGpF,OAFI,EAAE,KAAK,WAAa,EAAE,KAAK,SAExB,EAAE,KAAO,EAAE,KAF8B,EAAE,KAAK,SAAW,EAAE,KAAK,QAG3E,CAEA,OAAe,EAAiB,CAC9B,KAAO,EAAI,GAAG,CACZ,IAAM,EAAU,EAAI,GAAM,EAE1B,GAAI,KAAK,OAAO,KAAK,KAAK,GAAK,KAAK,KAAK,EAAQ,EAC/C,CAAC,KAAK,KAAK,GAAI,KAAK,KAAK,IAAW,CAAC,KAAK,KAAK,GAAU,KAAK,KAAK,EAAG,EACtE,EAAI,OAEJ,KAEJ,CACF,CAEA,SAAiB,EAAiB,CAChC,IAAM,EAAI,KAAK,KAAK,OAEpB,OAAS,CACP,IAAI,EAAM,EACJ,EAAO,EAAI,EAAI,EACf,EAAQ,EAAI,EAAI,EAMtB,GAJI,EAAO,GAAK,KAAK,OAAO,KAAK,KAAK,GAAQ,KAAK,KAAK,EAAK,IAAG,EAAM,GAElE,EAAQ,GAAK,KAAK,OAAO,KAAK,KAAK,GAAS,KAAK,KAAK,EAAK,IAAG,EAAM,GAEpE,IAAQ,EAAG,MAEf,CAAC,KAAK,KAAK,GAAI,KAAK,KAAK,IAAQ,CAAC,KAAK,KAAK,GAAO,KAAK,KAAK,EAAG,EAChE,EAAI,CACN,CACF,CACF"}
1
+ {"version":3,"file":"_queue.cjs","names":["#heap","#live","#sequence","#siftUp","#extract","#siftDown","#before"],"sources":["../src/_queue.ts"],"sourcesContent":["export type QueueItem<TInput, TOutput> = {\n cancelled?: boolean;\n cleanupAbort?: () => void;\n input: TInput;\n priority: number;\n reject: (reason: unknown) => void;\n resolve: (value: TOutput) => void;\n signal?: AbortSignal;\n timeout?: number;\n transferables: Transferable[];\n};\n\ntype Entry<TInput, TOutput> = { readonly item: QueueItem<TInput, TOutput>; readonly sequence: number };\n\nexport class TaskQueue<TInput, TOutput> {\n readonly #heap: Entry<TInput, TOutput>[] = [];\n #live = 0;\n #sequence = 0;\n\n get size(): number {\n return this.#live;\n }\n\n enqueue(item: QueueItem<TInput, TOutput>, maxSize: number | undefined): boolean {\n if (maxSize !== undefined && this.#live >= maxSize) return false;\n\n this.#heap.push({ item, sequence: this.#sequence++ });\n this.#siftUp(this.#heap.length - 1);\n this.#live += 1;\n\n return true;\n }\n\n remove(item: QueueItem<TInput, TOutput>): boolean {\n if (item.cancelled) return false;\n\n for (const entry of this.#heap) {\n if (entry.item === item) {\n item.cancelled = true;\n this.#live -= 1;\n\n return true;\n }\n }\n\n return false;\n }\n\n shift(): QueueItem<TInput, TOutput> | undefined {\n while (this.#heap.length > 0) {\n const item = this.#extract().item;\n\n if (!item.cancelled) {\n this.#live -= 1;\n\n return item;\n }\n }\n\n return undefined;\n }\n\n #extract(): Entry<TInput, TOutput> {\n const top = this.#heap[0]!;\n const last = this.#heap.pop()!;\n\n if (this.#heap.length > 0) {\n this.#heap[0] = last;\n this.#siftDown(0);\n }\n\n return top;\n }\n\n #before(a: Entry<TInput, TOutput>, b: Entry<TInput, TOutput>): boolean {\n return a.item.priority === b.item.priority ? a.sequence < b.sequence : a.item.priority > b.item.priority;\n }\n\n #siftUp(index: number): void {\n while (index > 0) {\n const parent = (index - 1) >> 1;\n\n if (!this.#before(this.#heap[index]!, this.#heap[parent]!)) return;\n\n [this.#heap[index], this.#heap[parent]] = [this.#heap[parent]!, this.#heap[index]!];\n index = parent;\n }\n }\n\n #siftDown(index: number): void {\n for (;;) {\n const left = index * 2 + 1;\n const right = left + 1;\n let next = index;\n\n if (left < this.#heap.length && this.#before(this.#heap[left]!, this.#heap[next]!)) next = left;\n\n if (right < this.#heap.length && this.#before(this.#heap[right]!, this.#heap[next]!)) next = right;\n\n if (next === index) return;\n\n [this.#heap[index], this.#heap[next]] = [this.#heap[next]!, this.#heap[index]!];\n index = next;\n }\n }\n}\n"],"mappings":"AAcA,IAAa,EAAb,KAAwC,CACtC,GAA2C,CAAC,EAC5C,GAAQ,EACR,GAAY,EAEZ,IAAI,MAAe,CACjB,OAAO,KAAKC,EACd,CAEA,QAAQ,EAAkC,EAAsC,CAO9E,OANI,IAAY,IAAA,IAAa,KAAKA,IAAS,EAAgB,IAE3D,KAAKD,GAAM,KAAK,CAAE,OAAM,SAAU,KAAKE,IAAY,CAAC,EACpD,KAAKC,GAAQ,KAAKH,GAAM,OAAS,CAAC,EAClC,KAAKC,IAAS,EAEP,GACT,CAEA,OAAO,EAA2C,CAChD,GAAI,EAAK,UAAW,MAAO,GAE3B,IAAK,IAAM,KAAS,KAAKD,GACvB,GAAI,EAAM,OAAS,EAIjB,MAHA,GAAK,UAAY,GACjB,OAAKC,GAEE,GAIX,MAAO,EACT,CAEA,OAAgD,CAC9C,KAAO,KAAKD,GAAM,OAAS,GAAG,CAC5B,IAAM,EAAO,KAAKI,GAAS,CAAC,CAAC,KAE7B,GAAI,CAAC,EAAK,UAGR,MAFA,OAAKH,GAEE,CAEX,CAGF,CAEA,IAAmC,CACjC,IAAM,EAAM,KAAKD,GAAM,GACjB,EAAO,KAAKA,GAAM,IAAI,EAO5B,OALI,KAAKA,GAAM,OAAS,IACtB,KAAKA,GAAM,GAAK,EAChB,KAAKK,GAAU,CAAC,GAGX,CACT,CAEA,GAAQ,EAA2B,EAAoC,CACrE,OAAO,EAAE,KAAK,WAAa,EAAE,KAAK,SAAW,EAAE,SAAW,EAAE,SAAW,EAAE,KAAK,SAAW,EAAE,KAAK,QAClG,CAEA,GAAQ,EAAqB,CAC3B,KAAO,EAAQ,GAAG,CAChB,IAAM,EAAU,EAAQ,GAAM,EAE9B,GAAI,CAAC,KAAKC,GAAQ,KAAKN,GAAM,GAAS,KAAKA,GAAM,EAAQ,EAAG,OAE5D,CAAC,KAAKA,GAAM,GAAQ,KAAKA,GAAM,IAAW,CAAC,KAAKA,GAAM,GAAU,KAAKA,GAAM,EAAO,EAClF,EAAQ,CACV,CACF,CAEA,GAAU,EAAqB,CAC7B,OAAS,CACP,IAAM,EAAO,EAAQ,EAAI,EACnB,EAAQ,EAAO,EACjB,EAAO,EAMX,GAJI,EAAO,KAAKA,GAAM,QAAU,KAAKM,GAAQ,KAAKN,GAAM,GAAQ,KAAKA,GAAM,EAAM,IAAG,EAAO,GAEvF,EAAQ,KAAKA,GAAM,QAAU,KAAKM,GAAQ,KAAKN,GAAM,GAAS,KAAKA,GAAM,EAAM,IAAG,EAAO,GAEzF,IAAS,EAAO,OAEpB,CAAC,KAAKA,GAAM,GAAQ,KAAKA,GAAM,IAAS,CAAC,KAAKA,GAAM,GAAQ,KAAKA,GAAM,EAAO,EAC9E,EAAQ,CACV,CACF,CACF"}
package/dist/_queue.d.ts CHANGED
@@ -1,22 +1,7 @@
1
- /**
2
- * Internal priority-queue for @vielzeug/familiar.
3
- * Not part of the public API surface.
4
- *
5
- * Uses a binary max-heap ordered by priority (higher value = runs first), with
6
- * insertion-sequence tiebreaking to ensure FIFO ordering within the same priority.
7
- * Cancelled items are marked lazily and silently skipped by shift().
8
- *
9
- * Complexity:
10
- * enqueue O(log n)
11
- * shift O(log n) amortised (lazy cancellation may scan multiple cancelled tops)
12
- * remove O(n) scan + O(1) mark — adequate for typical queue depths
13
- */
14
1
  export type QueueItem<TInput, TOutput> = {
15
- /** Marked true by remove(); the next shift() discards the item silently. */
16
2
  cancelled?: boolean;
17
3
  cleanupAbort?: () => void;
18
4
  input: TInput;
19
- /** Scheduling priority. Higher value = runs first. Default: 0. */
20
5
  priority: number;
21
6
  reject: (reason: unknown) => void;
22
7
  resolve: (value: TOutput) => void;
@@ -25,31 +10,10 @@ export type QueueItem<TInput, TOutput> = {
25
10
  transferables: Transferable[];
26
11
  };
27
12
  export declare class TaskQueue<TInput, TOutput> {
28
- private readonly heap;
29
- /** Accurate count of live (non-cancelled) items. */
30
- private liveCount;
31
- private seq;
32
- /** Number of live (non-cancelled) items. Always accurate. */
13
+ #private;
33
14
  get size(): number;
34
- /**
35
- * Enqueue item. Returns false (without modifying state) when maxQueue is set and
36
- * liveCount has already reached it; returns true on success.
37
- */
38
- enqueue(item: QueueItem<TInput, TOutput>, maxQueue: number | undefined): boolean;
39
- /**
40
- * Dequeue the highest-priority live item. Silently discards cancelled tombstones.
41
- * Returns undefined only when there are no live items left.
42
- */
43
- shift(): QueueItem<TInput, TOutput> | undefined;
44
- /**
45
- * Mark item as cancelled (O(n) scan). The heap slot is reclaimed lazily by the next shift().
46
- * Returns true when found and marked, false when the item is not in the queue.
47
- */
15
+ enqueue(item: QueueItem<TInput, TOutput>, maxSize: number | undefined): boolean;
48
16
  remove(item: QueueItem<TInput, TOutput>): boolean;
49
- private extractTop;
50
- /** Returns true when entry a should be dispatched before entry b. */
51
- private higher;
52
- private siftUp;
53
- private siftDown;
17
+ shift(): QueueItem<TInput, TOutput> | undefined;
54
18
  }
55
19
  //# sourceMappingURL=_queue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_queue.d.ts","sourceRoot":"","sources":["../src/_queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,IAAI;IACvC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,YAAY,EAAE,CAAC;CAC/B,CAAC;AASF,qBAAa,SAAS,CAAC,MAAM,EAAE,OAAO;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,oDAAoD;IACpD,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,GAAG,CAAK;IAEhB,6DAA6D;IAC7D,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;IAUhF;;;OAGG;IACH,KAAK,IAAI,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAe/C;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAiBjD,OAAO,CAAC,UAAU;IAYlB,qEAAqE;IACrE,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,MAAM;IAad,OAAO,CAAC,QAAQ;CAkBjB"}
1
+ {"version":3,"file":"_queue.d.ts","sourceRoot":"","sources":["../src/_queue.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,IAAI;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,YAAY,EAAE,CAAC;CAC/B,CAAC;AAIF,qBAAa,SAAS,CAAC,MAAM,EAAE,OAAO;;IAKpC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;IAU/E,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAejD,KAAK,IAAI,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;CAyDhD"}