@nxtedition/scheduler 3.0.12 → 3.1.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nxtedition/scheduler
2
2
 
3
- A high-performance, priority-based task scheduler for Node.js with support for concurrency limiting and multi-worker coordination via `SharedArrayBuffer`.
3
+ A high-performance, priority-based task scheduler for Node.js with support for concurrency limiting, byte-rate throttling, and multi-worker coordination via `SharedArrayBuffer`.
4
4
 
5
5
  ## Install
6
6
 
@@ -157,6 +157,80 @@ Create shared state for cross-worker scheduling.
157
157
 
158
158
  Parse a string or number into a normalized priority value.
159
159
 
160
+ ---
161
+
162
+ ## Throttle
163
+
164
+ `Throttle` is a token-bucket rate limiter that controls how many **bytes per second** are processed. It shares the same priority system as `Scheduler`.
165
+
166
+ ### Basic
167
+
168
+ ```js
169
+ import { Throttle } from '@nxtedition/scheduler'
170
+
171
+ // Allow 1 MB/s
172
+ const throttle = new Throttle({ bytesPerSecond: 1_000_000 })
173
+
174
+ // Refill tokens every 10ms
175
+ setInterval(() => throttle.refill(), 10)
176
+ ```
177
+
178
+ ### Multi-Worker Coordination
179
+
180
+ ### Streaming
181
+
182
+ `throttle.stream()` returns a Node.js `Transform` stream that enforces backpressure — it won't call the next `write` until tokens are available:
183
+
184
+ ```js
185
+ import { createReadStream, createWriteStream } from 'node:fs'
186
+ import { pipeline } from 'node:stream/promises'
187
+
188
+ const throttle = new Throttle({ bytesPerSecond: 1_000_000 }) // 1 MB/s
189
+ setInterval(() => throttle.refill(), 10)
190
+
191
+ await pipeline(createReadStream('input.mp4'), throttle.stream(), createWriteStream('output.mp4'))
192
+ ```
193
+
194
+ ### Priority
195
+
196
+ Both `run()` and `stream()` accept a priority. Higher-priority work drains first when tokens are available:
197
+
198
+ ```js
199
+ // Low-priority stream (yields to higher-priority work)
200
+ const stream = throttle.stream('low')
201
+ ```
202
+
203
+ ### Low-Level API
204
+
205
+ ```js
206
+ throttle.acquire(
207
+ () => {
208
+ sendPacket(data)
209
+ },
210
+ data.byteLength,
211
+ 'normal',
212
+ )
213
+ ```
214
+
215
+ `acquire` returns `true` if the callback ran immediately (tokens were available), or `false` if it was queued.
216
+
217
+ ## API
218
+
219
+ ### `new Throttle(opts)`
220
+
221
+ - `opts.bytesPerSecond` — bytes per second (default: `Infinity`)
222
+ - `opts` may also be a `SharedArrayBuffer` created by `Throttle.makeSharedState()`
223
+
224
+ ### `throttle.acquire(fn, bytes, priority?): boolean`
225
+
226
+ Low-level acquisition. Returns `true` if `fn` ran immediately, `false` if queued.
227
+
228
+ ### `throttle.stream(priority?): Transform`
229
+
230
+ Returns a `Transform` stream that rate-limits data passing through it. Each chunk consumes `chunk.length` tokens.
231
+
232
+ ---
233
+
160
234
  ## License
161
235
 
162
236
  MIT
package/lib/index.d.ts CHANGED
@@ -1,30 +1,5 @@
1
- export type Priority = -3 | -2 | -1 | 0 | 1 | 2 | 3 | 'lowest' | 'lower' | 'low' | 'normal' | 'high' | 'higher' | 'highest';
2
- export declare function parsePriority(p: string | number): -3 | -2 | -1 | 0 | 1 | 2 | 3;
3
- export declare class Scheduler {
4
- #private;
5
- static readonly LOWEST = -3;
6
- static readonly LOWER = -2;
7
- static readonly LOW = -1;
8
- static readonly NORMAL = 0;
9
- static readonly HIGH = 1;
10
- static readonly HIGHER = 2;
11
- static readonly HIGHEST = 3;
12
- get concurrency(): number;
13
- get stats(): {
14
- deferred: number;
15
- running: number;
16
- pending: number;
17
- queues: {
18
- count: number;
19
- }[];
20
- };
21
- static makeSharedState(concurrency: number): SharedArrayBuffer;
22
- constructor(opts: SharedArrayBuffer | {
23
- concurrency?: number;
24
- });
25
- run<T>(fn: () => Promise<T> | T, priority?: Priority): Promise<T>;
26
- run<T, U>(fn: (opaque: U) => Promise<T> | T, priority: Priority, opaque: U): Promise<T>;
27
- acquire(fn: () => unknown, priority?: Priority): void;
28
- acquire<U>(fn: (opaque: U) => unknown, priority: Priority, opaque: U): void;
29
- release(): void;
30
- }
1
+ export { Scheduler } from './scheduler.ts';
2
+ export { Throttle } from './throttle.ts';
3
+ export type { Priority } from './queue.ts';
4
+ export { parsePriority } from './queue.ts';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
package/lib/index.js CHANGED
@@ -1,272 +1,4 @@
1
- const RUNNING_INDEX = 0
2
- const CONCURRENCY_INDEX = 1
3
-
4
- const maxInt = 2147483647
5
-
6
- class FastQueue {
7
- idx = 0
8
- cnt = 0
9
- arr = []
10
- }
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
- export function parsePriority(p ) {
29
- if (typeof p === 'number') {
30
- // Do nothing...
31
- } else if (p === 'lowest') {
32
- return -3
33
- } else if (p === 'lower') {
34
- return -2
35
- } else if (p === 'low') {
36
- return -1
37
- } else if (p === 'normal') {
38
- return 0
39
- } else if (p === 'high') {
40
- return 1
41
- } else if (p === 'higher') {
42
- return 2
43
- } else if (p === 'highest') {
44
- return 3
45
- } else {
46
- p = Number(p)
47
- }
48
-
49
- if (typeof p !== 'number' || Number.isNaN(p)) {
50
- return 0
51
- } else if (p < -3) {
52
- return -3
53
- } else if (p > 3) {
54
- return 3
55
- } else {
56
- return Math.trunc(p)
57
- }
58
- }
59
-
60
- export class Scheduler {
61
- static LOWEST = -3
62
- static LOWER = -2
63
- static LOW = -1
64
- static NORMAL = 0
65
- static HIGH = 1
66
- static HIGHER = 2
67
- static HIGHEST = 3
68
-
69
- #concurrency
70
- #stateView
71
-
72
- #running = 0
73
- #pending = 0
74
- #counter = 0
75
- #releasing = false
76
- #deferred = 0
77
-
78
- #queues = [
79
- new FastQueue(), // 0 lowest
80
- new FastQueue(), // 1 lower
81
- new FastQueue(), // 2 low
82
- new FastQueue(), // 3 normal
83
- new FastQueue(), // 4 high
84
- new FastQueue(), // 5 higher
85
- new FastQueue(), // 6 highest
86
- ]
87
-
88
- get concurrency() {
89
- return this.#concurrency
90
- }
91
-
92
- get stats() {
93
- return {
94
- deferred: this.#deferred,
95
- running: this.#running,
96
- pending: this.#pending,
97
- queues: this.#queues.map((q) => ({ count: q.cnt })),
98
- }
99
- }
100
-
101
- static makeSharedState(concurrency ) {
102
- if (concurrency != null && (concurrency < 0 || !Number.isInteger(concurrency))) {
103
- throw new Error('Invalid concurrency')
104
- }
105
- const stateBuffer = new SharedArrayBuffer(64)
106
- const stateView = new Int32Array(stateBuffer)
107
- Atomics.store(stateView, CONCURRENCY_INDEX, concurrency ?? -1)
108
- return stateBuffer
109
- }
110
-
111
- constructor(opts ) {
112
- if (opts instanceof SharedArrayBuffer) {
113
- this.#stateView = new Int32Array(opts)
114
- this.#concurrency = Atomics.load(this.#stateView, CONCURRENCY_INDEX)
115
- if (this.#concurrency === -1) {
116
- this.#concurrency = Infinity
117
- }
118
- } else {
119
- this.#concurrency = opts?.concurrency ?? Infinity
120
- }
121
- }
122
-
123
-
124
- run (
125
- fn ,
126
- priority = Scheduler.NORMAL,
127
- opaque ,
128
- ) {
129
- return new Promise ((resolve, reject) => {
130
- this.acquire(
131
- async (o) => {
132
- try {
133
- resolve(await fn(o))
134
- } catch (err) {
135
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
136
- reject(err)
137
- } finally {
138
- this.release()
139
- }
140
- },
141
- priority,
142
- opaque,
143
- )
144
- })
145
- }
146
-
147
-
148
-
149
- acquire (fn , priority = Scheduler.NORMAL, opaque ) {
150
- const p = parsePriority(priority)
151
- const queue = this.#queues[p + 3]
152
-
153
- if (this.#stateView) {
154
- // Make sure we are always running at least one local job even if we might globally over subscribe.
155
- if (this.#running < 1) {
156
- Atomics.add(this.#stateView, RUNNING_INDEX, 1)
157
- this.#running += 1
158
- fn(opaque)
159
- return
160
- }
161
-
162
- // We use non atomic access here as an optimization and treat the concurrency limit as a soft limit.
163
- if (this.#stateView[RUNNING_INDEX] < this.#concurrency) {
164
- Atomics.add(this.#stateView, RUNNING_INDEX, 1)
165
- this.#running += 1
166
- fn(opaque)
167
- return
168
- }
169
- } else if ((this.#running < 1 && this.#concurrency > 0) || this.#running < this.#concurrency) {
170
- this.#running += 1
171
- fn(opaque)
172
- return
173
- }
174
-
175
- queue.arr.push(fn, opaque)
176
- queue.cnt += 1
177
-
178
- this.#deferred += 1
179
- this.#pending += 1
180
- }
181
-
182
- release() {
183
- let running
184
- if (this.#running > 0) {
185
- running = this.#stateView
186
- ? Atomics.sub(this.#stateView, RUNNING_INDEX, 1) - 1
187
- : this.#running - 1
188
- this.#running -= 1
189
- } else {
190
- // Gracefully handle user error...
191
- running = this.#stateView ? Atomics.load(this.#stateView, RUNNING_INDEX) : this.#running
192
- }
193
-
194
- if (this.#pending === 0 || this.#releasing) {
195
- return
196
- }
197
-
198
- try {
199
- this.#releasing = true
200
- while (this.#pending > 0 && (this.#running === 0 || running < this.#concurrency)) {
201
- let queue = null
202
-
203
- let idx = this.#queues.length - 1
204
-
205
- // Avoid starvation by randomizing the starting queue.
206
- {
207
- this.#counter = (this.#counter + 1) & maxInt
208
- if (this.#counter & 0b0000001) {
209
- idx = 6 // highest: 50%
210
- } else if (this.#counter & 0b0000010) {
211
- idx = 5 // higher: 25%
212
- } else if (this.#counter & 0b0000100) {
213
- idx = 4 // high: 12.5%
214
- } else if (this.#counter & 0b0001000) {
215
- idx = 3 // normal: 6.25%
216
- } else if (this.#counter & 0b0010000) {
217
- idx = 2 // low: 3.125%
218
- } else if (this.#counter & 0b0100000) {
219
- idx = 1 // lower: 1.5625%
220
- } else if (this.#counter & 0b1000000) {
221
- idx = 0 // lowest: 0.78%
222
- }
223
- }
224
-
225
- for (let n = idx; n >= 0 && (queue == null || queue.cnt === 0); n--) {
226
- queue = this.#queues[n]
227
- }
228
-
229
- for (let n = this.#queues.length - 1; n > idx && (queue == null || queue.cnt === 0); n--) {
230
- queue = this.#queues[n]
231
- }
232
-
233
- if (queue == null || queue.cnt === 0) {
234
- throw new Error('Invariant violation: pending > 0 but no tasks in queues')
235
- }
236
-
237
- const fn = queue.arr[queue.idx]
238
- queue.arr[queue.idx++] = null
239
- const opaque = queue.arr[queue.idx]
240
- queue.arr[queue.idx++] = null
241
- queue.cnt -= 1
242
-
243
- if (queue.cnt === 0) {
244
- queue.idx = 0
245
- queue.arr.length = 0
246
- } else if (queue.idx > 1024) {
247
- queue.arr.splice(0, queue.idx)
248
- queue.idx = 0
249
- }
250
-
251
- this.#pending -= 1
252
- this.#running += 1
253
-
254
- if (this.#stateView) {
255
- Atomics.add(this.#stateView, RUNNING_INDEX, 1)
256
- }
257
-
258
- fn(opaque)
259
-
260
- // Re-read running after fn() in case it synchronously called release()
261
- running = this.#stateView ? Atomics.load(this.#stateView, RUNNING_INDEX) : this.#running
262
- }
263
- } catch (err) {
264
- // Throwing here is undefined behavior...
265
- queueMicrotask(() => {
266
- throw new Error('Scheduler task error', { cause: err })
267
- })
268
- } finally {
269
- this.#releasing = false
270
- }
271
- }
272
- }
1
+ export { Scheduler } from "./scheduler.js";
2
+ export { Throttle } from "./throttle.js";
3
+ export { parsePriority } from "./queue.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
package/lib/queue.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ export declare class FastQueue {
2
+ idx: number;
3
+ cnt: number;
4
+ arr: Array<unknown>;
5
+ }
6
+ export type NumberPriority = -3 | -2 | -1 | 0 | 1 | 2 | 3;
7
+ export type Priority = NumberPriority | 'lowest' | 'lower' | 'low' | 'normal' | 'high' | 'higher' | 'highest';
8
+ export declare function parsePriority(p: string | number): NumberPriority;
9
+ export declare class Queue {
10
+ #private;
11
+ static readonly LOWEST = -3;
12
+ static readonly LOWER = -2;
13
+ static readonly LOW = -1;
14
+ static readonly NORMAL = 0;
15
+ static readonly HIGH = 1;
16
+ static readonly HIGHER = 2;
17
+ static readonly HIGHEST = 3;
18
+ protected stateView?: Int32Array;
19
+ protected running: number;
20
+ protected pending: number;
21
+ protected counter: number;
22
+ protected releasing: boolean;
23
+ protected deferred: number;
24
+ protected queues: FastQueue[];
25
+ get concurrency(): number;
26
+ get stats(): {
27
+ pending: number;
28
+ queues: {
29
+ count: number;
30
+ }[];
31
+ };
32
+ protected getNextQueue(): FastQueue | null;
33
+ static makeSharedState(concurrency: number): SharedArrayBuffer;
34
+ constructor(opts: SharedArrayBuffer | {
35
+ concurrency?: number;
36
+ });
37
+ }
38
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAEA,qBAAa,SAAS;IACpB,GAAG,SAAI;IACP,GAAG,SAAI;IACP,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAK;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAEzD,MAAM,MAAM,QAAQ,GAChB,cAAc,GACd,QAAQ,GACR,OAAO,GACP,KAAK,GACL,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,SAAS,CAAA;AAEb,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CA8BhE;AAID,qBAAa,KAAK;;IAChB,MAAM,CAAC,QAAQ,CAAC,MAAM,MAAK;IAC3B,MAAM,CAAC,QAAQ,CAAC,KAAK,MAAK;IAC1B,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAK;IACxB,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAI;IAC1B,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAI;IACxB,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAI;IAC1B,MAAM,CAAC,QAAQ,CAAC,OAAO,KAAI;IAG3B,SAAS,CAAC,SAAS,CAAC,EAAE,UAAU,CAAA;IAEhC,SAAS,CAAC,OAAO,SAAI;IACrB,SAAS,CAAC,OAAO,SAAI;IACrB,SAAS,CAAC,OAAO,SAAI;IACrB,SAAS,CAAC,SAAS,UAAQ;IAC3B,SAAS,CAAC,QAAQ,SAAI;IAGtB,SAAS,CAAC,MAAM,cAQf;IAED,IAAI,WAAW,WAEd;IAED,IAAI,KAAK;;;;;MAKR;IAED,SAAS,CAAC,YAAY;IAkCtB,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM;gBAU9B,IAAI,EAAE,iBAAiB,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;CAW/D"}
package/lib/queue.js ADDED
@@ -0,0 +1,138 @@
1
+ const CONCURRENCY_INDEX = 1;
2
+ export class FastQueue {
3
+ idx = 0;
4
+ cnt = 0;
5
+ arr = [];
6
+ }
7
+ export function parsePriority(p) {
8
+ if (typeof p === 'number') {
9
+ // Do nothing...
10
+ }
11
+ else if (p === 'lowest') {
12
+ return -3;
13
+ }
14
+ else if (p === 'lower') {
15
+ return -2;
16
+ }
17
+ else if (p === 'low') {
18
+ return -1;
19
+ }
20
+ else if (p === 'normal') {
21
+ return 0;
22
+ }
23
+ else if (p === 'high') {
24
+ return 1;
25
+ }
26
+ else if (p === 'higher') {
27
+ return 2;
28
+ }
29
+ else if (p === 'highest') {
30
+ return 3;
31
+ }
32
+ else {
33
+ p = Number(p);
34
+ }
35
+ if (typeof p !== 'number' || Number.isNaN(p)) {
36
+ return 0;
37
+ }
38
+ else if (p < -3) {
39
+ return -3;
40
+ }
41
+ else if (p > 3) {
42
+ return 3;
43
+ }
44
+ else {
45
+ return Math.trunc(p);
46
+ }
47
+ }
48
+ const maxInt = 2147483647;
49
+ export class Queue {
50
+ static LOWEST = -3;
51
+ static LOWER = -2;
52
+ static LOW = -1;
53
+ static NORMAL = 0;
54
+ static HIGH = 1;
55
+ static HIGHER = 2;
56
+ static HIGHEST = 3;
57
+ #concurrency;
58
+ stateView;
59
+ running = 0;
60
+ pending = 0;
61
+ counter = 0;
62
+ releasing = false;
63
+ deferred = 0;
64
+ // Queues are stored in order of priority, so index == priority + 3
65
+ queues = [
66
+ new FastQueue(), // 0 lowest
67
+ new FastQueue(), // 1 lower
68
+ new FastQueue(), // 2 low
69
+ new FastQueue(), // 3 normal
70
+ new FastQueue(), // 4 high
71
+ new FastQueue(), // 5 higher
72
+ new FastQueue(), // 6 highest
73
+ ];
74
+ get concurrency() {
75
+ return this.#concurrency;
76
+ }
77
+ get stats() {
78
+ return {
79
+ pending: this.pending,
80
+ queues: this.queues.map((q) => ({ count: q.cnt })),
81
+ };
82
+ }
83
+ getNextQueue() {
84
+ let queue = null;
85
+ this.counter = (this.counter + 1) & maxInt;
86
+ let idx = this.queues.length - 1;
87
+ if (this.counter & 0b0000001) {
88
+ idx = 6; // highest: 50%
89
+ }
90
+ else if (this.counter & 0b0000010) {
91
+ idx = 5; // higher: 25%
92
+ }
93
+ else if (this.counter & 0b0000100) {
94
+ idx = 4; // high: 12.5%
95
+ }
96
+ else if (this.counter & 0b0001000) {
97
+ idx = 3; // normal: 6.25%
98
+ }
99
+ else if (this.counter & 0b0010000) {
100
+ idx = 2; // low: 3.125%
101
+ }
102
+ else if (this.counter & 0b0100000) {
103
+ idx = 1; // lower: 1.5625%
104
+ }
105
+ else if (this.counter & 0b1000000) {
106
+ idx = 0; // lowest: 0.78%
107
+ }
108
+ for (let n = idx; n >= 0 && (queue == null || queue.cnt === 0); n--) {
109
+ queue = this.queues[n];
110
+ }
111
+ for (let n = this.queues.length - 1; n > idx && (queue == null || queue.cnt === 0); n--) {
112
+ queue = this.queues[n];
113
+ }
114
+ return queue;
115
+ }
116
+ static makeSharedState(concurrency) {
117
+ if (concurrency != null && (concurrency < 0 || !Number.isInteger(concurrency))) {
118
+ throw new Error('Invalid concurrency');
119
+ }
120
+ const stateBuffer = new SharedArrayBuffer(64);
121
+ const stateView = new Int32Array(stateBuffer);
122
+ Atomics.store(stateView, CONCURRENCY_INDEX, concurrency ?? -1);
123
+ return stateBuffer;
124
+ }
125
+ constructor(opts) {
126
+ if (opts instanceof SharedArrayBuffer) {
127
+ this.stateView = new Int32Array(opts);
128
+ this.#concurrency = Atomics.load(this.stateView, CONCURRENCY_INDEX);
129
+ if (this.#concurrency === -1) {
130
+ this.#concurrency = Infinity;
131
+ }
132
+ }
133
+ else {
134
+ this.#concurrency = opts?.concurrency ?? Infinity;
135
+ }
136
+ }
137
+ }
138
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { Queue, type Priority } from './queue.ts';
2
+ export declare class Scheduler extends Queue {
3
+ run<T>(fn: () => Promise<T> | T, priority?: Priority): Promise<T>;
4
+ run<T, U>(fn: (opaque: U) => Promise<T> | T, priority: Priority, opaque: U): Promise<T>;
5
+ acquire(fn: () => unknown, priority?: Priority): void;
6
+ acquire<U>(fn: (opaque: U) => unknown, priority: Priority, opaque: U): void;
7
+ release: () => void;
8
+ get stats(): {
9
+ deferred: number;
10
+ running: number;
11
+ pending: number;
12
+ queues: {
13
+ count: number;
14
+ }[];
15
+ };
16
+ }
17
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI3E,qBAAa,SAAU,SAAQ,KAAK;IAClC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;IACjE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA4BvF,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI;IACrD,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI;IAkC3E,OAAO,aA2DN;IAED,IAAI,KAAK;;;;;;;MAOR;CACF"}
@@ -0,0 +1,118 @@
1
+ import { FastQueue, parsePriority, Queue } from "./queue.js";
2
+ const RUNNING_INDEX = 0;
3
+ export class Scheduler extends Queue {
4
+ run(fn, priority = Queue.NORMAL, opaque) {
5
+ return new Promise((resolve, reject) => {
6
+ this.acquire((o) => {
7
+ try {
8
+ const result = fn(o);
9
+ if (result && typeof result.then === 'function') {
10
+ ;
11
+ result.then(resolve, reject).then(this.release);
12
+ }
13
+ else {
14
+ resolve(result);
15
+ this.release();
16
+ }
17
+ }
18
+ catch (err) {
19
+ reject(err); // eslint-disable-line @typescript-eslint/prefer-promise-reject-errors
20
+ this.release();
21
+ }
22
+ }, priority, opaque);
23
+ });
24
+ }
25
+ acquire(fn, priority = Queue.NORMAL, opaque) {
26
+ const p = parsePriority(priority);
27
+ const queue = this.queues[p + 3];
28
+ if (this.stateView) {
29
+ // Make sure we are always running at least one local job even if we might globally over subscribe.
30
+ if (this.running < 1) {
31
+ Atomics.add(this.stateView, RUNNING_INDEX, 1);
32
+ this.running += 1;
33
+ fn(opaque);
34
+ return;
35
+ }
36
+ // We use non atomic access here as an optimization and treat the concurrency limit as a soft limit.
37
+ if (this.stateView[RUNNING_INDEX] < this.concurrency) {
38
+ Atomics.add(this.stateView, RUNNING_INDEX, 1);
39
+ this.running += 1;
40
+ fn(opaque);
41
+ return;
42
+ }
43
+ }
44
+ else if (this.running < this.concurrency) {
45
+ this.running += 1;
46
+ fn(opaque);
47
+ return;
48
+ }
49
+ queue.arr.push(fn, opaque);
50
+ queue.cnt += 1;
51
+ this.deferred += 1;
52
+ this.pending += 1;
53
+ }
54
+ release = () => {
55
+ let running;
56
+ if (this.running > 0) {
57
+ running = this.stateView
58
+ ? Atomics.sub(this.stateView, RUNNING_INDEX, 1) - 1
59
+ : this.running - 1;
60
+ this.running -= 1;
61
+ }
62
+ else {
63
+ // Gracefully handle user error...
64
+ running = this.stateView ? Atomics.load(this.stateView, RUNNING_INDEX) : this.running;
65
+ }
66
+ if (this.pending === 0 || this.releasing) {
67
+ return;
68
+ }
69
+ try {
70
+ this.releasing = true;
71
+ while (this.pending > 0 && (this.running === 0 || running < this.concurrency)) {
72
+ const queue = this.getNextQueue();
73
+ if (queue == null || queue.cnt === 0) {
74
+ throw new Error('Invariant violation: pending > 0 but no tasks in queues');
75
+ }
76
+ const fn = queue.arr[queue.idx];
77
+ queue.arr[queue.idx++] = null;
78
+ const opaque = queue.arr[queue.idx];
79
+ queue.arr[queue.idx++] = null;
80
+ queue.cnt -= 1;
81
+ if (queue.cnt === 0) {
82
+ queue.idx = 0;
83
+ queue.arr.length = 0;
84
+ }
85
+ else if (queue.idx > 1024) {
86
+ queue.arr.splice(0, queue.idx);
87
+ queue.idx = 0;
88
+ }
89
+ this.pending -= 1;
90
+ this.running += 1;
91
+ if (this.stateView) {
92
+ Atomics.add(this.stateView, RUNNING_INDEX, 1);
93
+ }
94
+ fn(opaque);
95
+ // Re-read running after fn() in case it synchronously called release()
96
+ running = this.stateView ? Atomics.load(this.stateView, RUNNING_INDEX) : this.running;
97
+ }
98
+ }
99
+ catch (err) {
100
+ // Throwing here is undefined behavior...
101
+ queueMicrotask(() => {
102
+ throw new Error('Scheduler task error', { cause: err });
103
+ });
104
+ }
105
+ finally {
106
+ this.releasing = false;
107
+ }
108
+ };
109
+ get stats() {
110
+ return {
111
+ deferred: this.deferred,
112
+ running: this.running,
113
+ pending: this.pending,
114
+ queues: this.queues.map((q) => ({ count: q.cnt })),
115
+ };
116
+ }
117
+ }
118
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { Transform } from 'node:stream';
2
+ import { Queue, type Priority } from './queue.ts';
3
+ export declare class Throttle extends Queue {
4
+ #private;
5
+ constructor(opts: SharedArrayBuffer | {
6
+ bytesPerSecond?: number;
7
+ });
8
+ static makeSharedState(bytesPerSecond: number): SharedArrayBuffer;
9
+ stream(priority?: Priority): Transform;
10
+ acquire(fn: () => unknown, bytes: number, priority?: Priority): boolean;
11
+ get stats(): {
12
+ tokens: number;
13
+ pending: number;
14
+ queues: {
15
+ count: number;
16
+ }[];
17
+ };
18
+ refill(): void;
19
+ }
20
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAA4B,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI3E,qBAAa,QAAS,SAAQ,KAAK;;gBAMrB,IAAI,EAAE,iBAAiB,GAAG;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;IAIjE,MAAM,CAAC,eAAe,CAAC,cAAc,EAAE,MAAM;IAI7C,MAAM,CAAC,QAAQ,GAAE,QAAuB;IAcxC,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,QAAuB,GAAG,OAAO;IA2BrF,IAAI,KAAK;;;;;;MAMR;IAcD,MAAM;CA6DP"}
@@ -0,0 +1,115 @@
1
+ import { Transform } from 'node:stream';
2
+ import { FastQueue, parsePriority, Queue } from "./queue.js";
3
+ const TOKENS_INDEX = 2;
4
+ export class Throttle extends Queue {
5
+ #singleThreadTokens = 0;
6
+ // Set last refill time to -Infinity so first refill() always fills a full bucket.
7
+ #lastRefillTime = -Infinity;
8
+ #draining = false;
9
+ constructor(opts) {
10
+ super(opts instanceof SharedArrayBuffer ? opts : { concurrency: opts?.bytesPerSecond });
11
+ }
12
+ static makeSharedState(bytesPerSecond) {
13
+ return super.makeSharedState(bytesPerSecond);
14
+ }
15
+ stream(priority = Queue.NORMAL) {
16
+ return new Transform({
17
+ transform: (chunk, _encoding, callback) => {
18
+ this.acquire(() => {
19
+ callback(null, chunk);
20
+ }, chunk.length, priority);
21
+ },
22
+ });
23
+ }
24
+ acquire(fn, bytes, priority = Queue.NORMAL) {
25
+ // There is a possiblity that this check passes for two threads, but should only have passed for one of them.
26
+ // Given that the concurrency limit is a soft limit, this is not a problem and will just mean that we might briefly exceed the concurrency limit until the next refill.
27
+ if (this.pending === 0 && this.#tokens >= bytes) {
28
+ // Nothing in the queue, and enough tokens to run immediately.
29
+ this.#tokens -= bytes;
30
+ fn();
31
+ return true;
32
+ }
33
+ const p = parsePriority(priority);
34
+ const queue = this.queues[p + 3];
35
+ this.pending += 1;
36
+ this.deferred += 1;
37
+ queue.arr.push(fn, bytes);
38
+ queue.cnt += 1;
39
+ if (!this.#draining) {
40
+ this.#draining = true;
41
+ setImmediate(this.#drain);
42
+ }
43
+ return false;
44
+ }
45
+ get stats() {
46
+ return {
47
+ tokens: this.#tokens,
48
+ pending: this.pending,
49
+ queues: this.queues.map((q) => ({ count: q.cnt })),
50
+ };
51
+ }
52
+ get #tokens() {
53
+ return this.stateView ? this.stateView[TOKENS_INDEX] : this.#singleThreadTokens;
54
+ }
55
+ set #tokens(value) {
56
+ if (this.stateView) {
57
+ Atomics.store(this.stateView, TOKENS_INDEX, value);
58
+ }
59
+ else {
60
+ this.#singleThreadTokens = value;
61
+ }
62
+ }
63
+ refill() {
64
+ const currentToken = this.#tokens;
65
+ if (currentToken >= this.concurrency) {
66
+ // We are already at or above capacity, so just update the last refill time and return.
67
+ this.#lastRefillTime = performance.now();
68
+ return;
69
+ }
70
+ const now = performance.now();
71
+ const elapsed = (now - this.#lastRefillTime) / 1000;
72
+ this.#tokens = Math.min(this.concurrency, currentToken + elapsed * this.concurrency);
73
+ this.#lastRefillTime = now;
74
+ if (!this.#draining && this.pending > 0) {
75
+ this.#draining = true;
76
+ setImmediate(this.#drain);
77
+ }
78
+ }
79
+ #drain = () => {
80
+ while (this.pending > 0) {
81
+ const queue = this.getNextQueue();
82
+ if (queue == null || queue.cnt === 0) {
83
+ break;
84
+ }
85
+ const bytes = queue.arr[queue.idx + 1];
86
+ const currentTokens = this.#tokens;
87
+ if (currentTokens < bytes) {
88
+ if (this.stateView) {
89
+ // SharedArrayBuffer mode: another thread may refill tokens,
90
+ // so we need to poll since there's no cross-thread signaling.
91
+ setTimeout(this.#drain, 10);
92
+ return;
93
+ }
94
+ break;
95
+ }
96
+ const fn = queue.arr[queue.idx];
97
+ queue.arr[queue.idx++] = null;
98
+ queue.arr[queue.idx++] = null;
99
+ queue.cnt -= 1;
100
+ this.#tokens -= bytes;
101
+ this.pending -= 1;
102
+ if (queue.cnt === 0) {
103
+ queue.idx = 0;
104
+ queue.arr.length = 0;
105
+ }
106
+ else if (queue.idx > 1024) {
107
+ queue.arr.splice(0, queue.idx);
108
+ queue.idx = 0;
109
+ }
110
+ fn();
111
+ }
112
+ this.#draining = false;
113
+ };
114
+ }
115
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@nxtedition/scheduler",
3
- "version": "3.0.12",
3
+ "version": "3.1.1",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
+ "exports": {
8
+ ".": "./lib/index.js"
9
+ },
7
10
  "files": [
8
11
  "lib",
9
12
  "README.md",
@@ -14,7 +17,7 @@
14
17
  "access": "public"
15
18
  },
16
19
  "scripts": {
17
- "build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/",
20
+ "build": "rimraf lib && tsc",
18
21
  "prepublishOnly": "yarn build",
19
22
  "typecheck": "tsc --noEmit",
20
23
  "test": "yarn build && node --test",
@@ -28,5 +31,5 @@
28
31
  "rimraf": "^6.1.3",
29
32
  "typescript": "^5.9.3"
30
33
  },
31
- "gitHead": "5c3858acb895ff6ee82b196487a9e2d9ee46f645"
34
+ "gitHead": "f6592f0be62fecc161237610ae6454ec6585548b"
32
35
  }