@praxisjs/concurrent 0.2.3 → 1.1.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.
- package/CHANGELOG.md +65 -0
- package/dist/__tests__/decorators.test.js +68 -0
- package/dist/__tests__/decorators.test.js.map +1 -1
- package/dist/__tests__/pool.test.js +43 -6
- package/dist/__tests__/pool.test.js.map +1 -1
- package/dist/__tests__/queue.test.js +77 -3
- package/dist/__tests__/queue.test.js.map +1 -1
- package/dist/__tests__/task.test.js +47 -0
- package/dist/__tests__/task.test.js.map +1 -1
- package/dist/decorators.d.ts +3 -3
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +37 -34
- package/dist/decorators.js.map +1 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +1 -0
- package/dist/pool.js.map +1 -1
- package/dist/queue.d.ts +3 -0
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +14 -2
- package/dist/queue.js.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/decorators.test.ts +91 -0
- package/src/__tests__/pool.test.ts +54 -7
- package/src/__tests__/queue.test.ts +103 -3
- package/src/__tests__/task.test.ts +62 -0
- package/src/decorators.ts +38 -46
- package/src/index.ts +0 -9
- package/src/pool.ts +1 -0
- package/src/queue.ts +15 -2
package/dist/pool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7C,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;CAC/B;AAED,wBAAgB,IAAI,CAAC,CAAC,EACpB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7C,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;CAC/B;AAED,wBAAgB,IAAI,CAAC,CAAC,EACpB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,YAAY,CAAC,CAAC,CAAC,CA0CjB"}
|
package/dist/pool.js
CHANGED
package/dist/pool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAW3D,MAAM,UAAU,IAAI,CAClB,WAAmB,EACnB,EAAsC;IAEtC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAe,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAGP,EAAE,CAAC;IAER,KAAK,UAAU,MAAM;QACnB,IAAI,OAAO,EAAE,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChE,OAAO,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,KAAK,MAAM,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,SAAS,GAAG,CAAC,GAAG,IAAe;QAC7B,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,KAAK,MAAM,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5C,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACvC,GAAG,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IACzC,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAErC,OAAO,GAAsB,CAAC;AAChC,CAAC"}
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAW3D,MAAM,UAAU,IAAI,CAClB,WAAmB,EACnB,EAAsC;IAEtC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAe,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAGP,EAAE,CAAC;IAER,KAAK,UAAU,MAAM;QACnB,IAAI,OAAO,EAAE,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChE,OAAO,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,KAAK,MAAM,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,SAAS,GAAG,CAAC,GAAG,IAAe;QAC7B,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,KAAK,MAAM,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5C,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACvC,GAAG,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IACzC,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAErC,OAAO,GAAsB,CAAC;AAChC,CAAC"}
|
package/dist/queue.d.ts
CHANGED
package/dist/queue.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,KAAK,CAAC,CAAC,EACrB,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,aAAa,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,KAAK,CAAC,CAAC,EACrB,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,aAAa,CAAC,CAAC,CAAC,CA0DlB"}
|
package/dist/queue.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { signal, computed } from "@praxisjs/core/internal";
|
|
2
|
+
export class QueueClearedError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("Queue cleared");
|
|
5
|
+
this.name = "QueueClearedError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
2
8
|
export function queue(fn) {
|
|
3
9
|
const _loading = signal(false);
|
|
4
10
|
const _pending = signal(0);
|
|
@@ -11,7 +17,7 @@ export function queue(fn) {
|
|
|
11
17
|
return;
|
|
12
18
|
_running = true;
|
|
13
19
|
_loading.set(true);
|
|
14
|
-
while (_queue.length > 0
|
|
20
|
+
while (_queue.length > 0) {
|
|
15
21
|
const item = _queue.shift();
|
|
16
22
|
if (!item)
|
|
17
23
|
break;
|
|
@@ -31,6 +37,9 @@ export function queue(fn) {
|
|
|
31
37
|
_loading.set(false);
|
|
32
38
|
}
|
|
33
39
|
function enqueue(...args) {
|
|
40
|
+
if (_cleared) {
|
|
41
|
+
_cleared = false;
|
|
42
|
+
}
|
|
34
43
|
return new Promise((resolve, reject) => {
|
|
35
44
|
_queue.push({ args, resolve, reject });
|
|
36
45
|
_pending.update((n) => n + 1);
|
|
@@ -41,7 +50,10 @@ export function queue(fn) {
|
|
|
41
50
|
enqueue.pending = computed(() => _pending());
|
|
42
51
|
enqueue.error = computed(() => _error());
|
|
43
52
|
enqueue.clear = () => {
|
|
44
|
-
|
|
53
|
+
// Reject all currently queued (not-yet-started) items
|
|
54
|
+
while (_queue.length > 0) {
|
|
55
|
+
_queue.shift()?.reject(new QueueClearedError());
|
|
56
|
+
}
|
|
45
57
|
_pending.set(0);
|
|
46
58
|
_cleared = true;
|
|
47
59
|
};
|
package/dist/queue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/concurrent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"typescript": "^5.9.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "
|
|
17
|
+
"@praxisjs/core": "1.1.0",
|
|
18
|
+
"@praxisjs/decorators": "0.6.0",
|
|
18
19
|
"@praxisjs/shared": "0.2.0"
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
@@ -75,6 +75,35 @@ describe("Queue decorator", () => {
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// ── Queue (additional) ────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
describe("Queue decorator (additional)", () => {
|
|
81
|
+
it("clear() is accessible on the instance via method_clear property", async () => {
|
|
82
|
+
const { ctx, run } = methodCtx("save");
|
|
83
|
+
let resolveFirst!: () => void;
|
|
84
|
+
const original = async (_: unknown, idx: unknown) =>
|
|
85
|
+
idx === 0
|
|
86
|
+
? new Promise<void>((r) => { resolveFirst = r; })
|
|
87
|
+
: Promise.resolve();
|
|
88
|
+
Queue()(original, ctx);
|
|
89
|
+
|
|
90
|
+
const instance: Record<string, unknown> = {};
|
|
91
|
+
run(instance);
|
|
92
|
+
|
|
93
|
+
expect(typeof instance.save_clear).toBe("function");
|
|
94
|
+
|
|
95
|
+
const save = instance.save as (...args: unknown[]) => Promise<unknown>;
|
|
96
|
+
save(null, 0);
|
|
97
|
+
const p1 = save(null, 1);
|
|
98
|
+
|
|
99
|
+
// Call clear via the exposed property
|
|
100
|
+
(instance.save_clear as () => void)();
|
|
101
|
+
|
|
102
|
+
await expect(p1).rejects.toThrow("Queue cleared");
|
|
103
|
+
resolveFirst();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
78
107
|
// ── Pool ──────────────────────────────────────────────────────────────────────
|
|
79
108
|
|
|
80
109
|
describe("Pool decorator", () => {
|
|
@@ -116,4 +145,66 @@ describe("Pool decorator", () => {
|
|
|
116
145
|
resolvers.forEach((r) => { r(); });
|
|
117
146
|
await Promise.all([t1, t2]);
|
|
118
147
|
});
|
|
148
|
+
|
|
149
|
+
it("Pool(-1, fn) — clamps to 1, does not allow unlimited concurrency", async () => {
|
|
150
|
+
const { ctx, run } = methodCtx("work");
|
|
151
|
+
const resolvers: Array<() => void> = [];
|
|
152
|
+
const original = async () => new Promise<void>((r) => resolvers.push(r));
|
|
153
|
+
Pool(-1)(original, ctx);
|
|
154
|
+
|
|
155
|
+
const instance: Record<string, unknown> = {};
|
|
156
|
+
run(instance);
|
|
157
|
+
|
|
158
|
+
const work = instance.work as () => Promise<void>;
|
|
159
|
+
const t1 = work();
|
|
160
|
+
const t2 = work();
|
|
161
|
+
|
|
162
|
+
// With concurrency clamped to 1, only one task should be active at a time
|
|
163
|
+
expect((instance.work_active as () => number)()).toBe(1);
|
|
164
|
+
expect((instance.work_pending as () => number)()).toBe(1);
|
|
165
|
+
|
|
166
|
+
resolvers[0]();
|
|
167
|
+
await t1;
|
|
168
|
+
resolvers[1]();
|
|
169
|
+
await t2;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("decorated method called concurrently up to pool limit — active() signal is accurate", async () => {
|
|
173
|
+
const { ctx, run } = methodCtx("process");
|
|
174
|
+
const resolvers: Array<() => void> = [];
|
|
175
|
+
const original = async () => new Promise<void>((r) => resolvers.push(r));
|
|
176
|
+
Pool(3)(original, ctx);
|
|
177
|
+
|
|
178
|
+
const instance: Record<string, unknown> = {};
|
|
179
|
+
run(instance);
|
|
180
|
+
|
|
181
|
+
const process = instance.process as () => Promise<void>;
|
|
182
|
+
const active = instance.process_active as () => number;
|
|
183
|
+
|
|
184
|
+
const t1 = process();
|
|
185
|
+
expect(active()).toBe(1);
|
|
186
|
+
|
|
187
|
+
const t2 = process();
|
|
188
|
+
expect(active()).toBe(2);
|
|
189
|
+
|
|
190
|
+
const t3 = process();
|
|
191
|
+
expect(active()).toBe(3);
|
|
192
|
+
|
|
193
|
+
// 4th task exceeds limit, goes to pending
|
|
194
|
+
const t4 = process();
|
|
195
|
+
expect(active()).toBe(3);
|
|
196
|
+
expect((instance.process_pending as () => number)()).toBe(1);
|
|
197
|
+
|
|
198
|
+
// Resolve t1 first — this will allow t4 to start and push its resolver
|
|
199
|
+
resolvers[0]();
|
|
200
|
+
await t1;
|
|
201
|
+
|
|
202
|
+
// Now t4 has started and pushed a resolver
|
|
203
|
+
resolvers[1]();
|
|
204
|
+
resolvers[2]();
|
|
205
|
+
resolvers[3]();
|
|
206
|
+
await Promise.all([t2, t3, t4]);
|
|
207
|
+
|
|
208
|
+
expect(active()).toBe(0);
|
|
209
|
+
});
|
|
119
210
|
});
|
|
@@ -129,16 +129,15 @@ describe("pool", () => {
|
|
|
129
129
|
expect(results[1]).toBe(1); // subsequent task ran
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it("concurrency=0 —
|
|
132
|
+
it("concurrency=0 — clamps to 1, tasks execute serially", async () => {
|
|
133
133
|
const fn = vi.fn(async () => "result");
|
|
134
134
|
const p = pool(0, fn);
|
|
135
135
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
expect(
|
|
141
|
-
expect(p.pending()).toBe(1);
|
|
136
|
+
// concurrency=0 is clamped to 1, so the task runs immediately
|
|
137
|
+
const result = await p();
|
|
138
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
139
|
+
expect(result).toBe("result");
|
|
140
|
+
expect(p.pending()).toBe(0);
|
|
142
141
|
expect(p.loading()).toBe(false);
|
|
143
142
|
});
|
|
144
143
|
|
|
@@ -154,4 +153,52 @@ describe("pool", () => {
|
|
|
154
153
|
await Promise.all([p(), p(), p(), p()]);
|
|
155
154
|
expect(maxObservedActive).toBeLessThanOrEqual(concurrency);
|
|
156
155
|
});
|
|
156
|
+
|
|
157
|
+
it("pool(-5, fn) — clamps to 1, runs tasks serially", async () => {
|
|
158
|
+
const order: number[] = [];
|
|
159
|
+
let resolveFirst!: () => void;
|
|
160
|
+
|
|
161
|
+
const p = pool(-5, async (n: unknown) => {
|
|
162
|
+
if (n === 0) await new Promise<void>((r) => { resolveFirst = r; });
|
|
163
|
+
order.push(n as number);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const t1 = p(0);
|
|
167
|
+
const t2 = p(1);
|
|
168
|
+
|
|
169
|
+
expect(p.active()).toBe(1);
|
|
170
|
+
expect(p.pending()).toBe(1);
|
|
171
|
+
|
|
172
|
+
resolveFirst();
|
|
173
|
+
await t1;
|
|
174
|
+
await t2;
|
|
175
|
+
|
|
176
|
+
expect(order).toEqual([0, 1]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("multiple tasks error — error signal reflects last error", async () => {
|
|
180
|
+
let callCount = 0;
|
|
181
|
+
const p = pool(1, async () => {
|
|
182
|
+
callCount++;
|
|
183
|
+
throw new Error(`error ${callCount}`);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await p();
|
|
187
|
+
await p();
|
|
188
|
+
await p();
|
|
189
|
+
|
|
190
|
+
expect(p.error()?.message).toBe("error 3");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("active() never goes negative even if task throws synchronously", async () => {
|
|
194
|
+
const p = pool(2, async () => {
|
|
195
|
+
throw new Error("sync-ish throw");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await p();
|
|
199
|
+
await p();
|
|
200
|
+
|
|
201
|
+
expect(p.active()).toBeGreaterThanOrEqual(0);
|
|
202
|
+
expect(p.active()).toBe(0);
|
|
203
|
+
});
|
|
157
204
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { queue } from "../queue";
|
|
3
|
+
import { queue, QueueClearedError } from "../queue";
|
|
4
4
|
|
|
5
5
|
describe("queue", () => {
|
|
6
6
|
it("executes the task and resolves the promise", async () => {
|
|
@@ -96,8 +96,12 @@ describe("queue", () => {
|
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
q(null, 0);
|
|
99
|
-
q(null, 1);
|
|
100
|
-
q(null, 2);
|
|
99
|
+
const p1 = q(null, 1);
|
|
100
|
+
const p2 = q(null, 2);
|
|
101
|
+
|
|
102
|
+
// Suppress expected rejections from clear()
|
|
103
|
+
p1.catch(() => undefined);
|
|
104
|
+
p2.catch(() => undefined);
|
|
101
105
|
|
|
102
106
|
q.clear();
|
|
103
107
|
resolveFirst();
|
|
@@ -105,4 +109,100 @@ describe("queue", () => {
|
|
|
105
109
|
// After clearing, pending count should be 0
|
|
106
110
|
expect(q.pending()).toBe(0);
|
|
107
111
|
});
|
|
112
|
+
|
|
113
|
+
it("clear() with pending tasks rejects their promises with QueueClearedError", async () => {
|
|
114
|
+
let resolveFirst!: () => void;
|
|
115
|
+
const q = queue(
|
|
116
|
+
(_: unknown, idx: unknown) =>
|
|
117
|
+
idx === 0
|
|
118
|
+
? new Promise<void>((r) => { resolveFirst = r; })
|
|
119
|
+
: Promise.resolve(),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
q(null, 0); // running — not in _queue
|
|
123
|
+
const p1 = q(null, 1); // pending — in _queue
|
|
124
|
+
const p2 = q(null, 2); // pending — in _queue
|
|
125
|
+
|
|
126
|
+
q.clear();
|
|
127
|
+
|
|
128
|
+
await expect(p1).rejects.toBeInstanceOf(QueueClearedError);
|
|
129
|
+
await expect(p2).rejects.toBeInstanceOf(QueueClearedError);
|
|
130
|
+
|
|
131
|
+
resolveFirst();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("clear() then new enqueue() — queue works normally after being cleared", async () => {
|
|
135
|
+
let resolveFirst!: () => void;
|
|
136
|
+
const q = queue(
|
|
137
|
+
(n: unknown, stall: unknown) =>
|
|
138
|
+
stall
|
|
139
|
+
? new Promise<number>((r) => { resolveFirst = () => r(n as number); })
|
|
140
|
+
: Promise.resolve(n as number),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
q(0, true); // running — stalls
|
|
144
|
+
const p1 = q(1, false); // pending
|
|
145
|
+
q.clear();
|
|
146
|
+
|
|
147
|
+
await expect(p1).rejects.toBeInstanceOf(QueueClearedError);
|
|
148
|
+
resolveFirst();
|
|
149
|
+
|
|
150
|
+
// Queue should work normally after clearing
|
|
151
|
+
const result = await q(42, false);
|
|
152
|
+
expect(result).toBe(42);
|
|
153
|
+
expect(q.pending()).toBe(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("clear() with no pending tasks — no-op, no crash", () => {
|
|
157
|
+
const q = queue(async () => "ok");
|
|
158
|
+
expect(() => q.clear()).not.toThrow();
|
|
159
|
+
expect(q.pending()).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("error in task does not corrupt pending counter", async () => {
|
|
163
|
+
const q = queue(async (n: unknown) => {
|
|
164
|
+
if (n === 0) throw new Error("bad");
|
|
165
|
+
return n as number;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const p0 = q(0);
|
|
169
|
+
const p1 = q(1);
|
|
170
|
+
const p2 = q(2);
|
|
171
|
+
|
|
172
|
+
await expect(p0).rejects.toThrow("bad");
|
|
173
|
+
expect(await p1).toBe(1);
|
|
174
|
+
expect(await p2).toBe(2);
|
|
175
|
+
expect(q.pending()).toBe(0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("multiple tasks error — only last error stored", async () => {
|
|
179
|
+
const q = queue(async (n: unknown) => {
|
|
180
|
+
throw new Error(`error ${n as number}`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const p0 = q(0);
|
|
184
|
+
const p1 = q(1);
|
|
185
|
+
const p2 = q(2);
|
|
186
|
+
|
|
187
|
+
await expect(p0).rejects.toThrow("error 0");
|
|
188
|
+
await expect(p1).rejects.toThrow("error 1");
|
|
189
|
+
await expect(p2).rejects.toThrow("error 2");
|
|
190
|
+
|
|
191
|
+
// Only the last error is stored
|
|
192
|
+
expect(q.error()?.message).toBe("error 2");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("very large queue (100 tasks, concurrency=1) — all execute in order", async () => {
|
|
196
|
+
const order: number[] = [];
|
|
197
|
+
const q = queue(async (n: unknown) => {
|
|
198
|
+
order.push(n as number);
|
|
199
|
+
return n as number;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const tasks = Array.from({ length: 100 }, (_, i) => q(i));
|
|
203
|
+
await Promise.all(tasks);
|
|
204
|
+
|
|
205
|
+
expect(order).toEqual(Array.from({ length: 100 }, (_, i) => i));
|
|
206
|
+
expect(q.pending()).toBe(0);
|
|
207
|
+
});
|
|
108
208
|
});
|
|
@@ -134,4 +134,66 @@ describe("task", () => {
|
|
|
134
134
|
expect(result).toBe("fresh");
|
|
135
135
|
expect(t.loading()).toBe(false);
|
|
136
136
|
});
|
|
137
|
+
|
|
138
|
+
it("two concurrent calls — second result wins when first is slower", async () => {
|
|
139
|
+
let resolveFirst!: (v: string) => void;
|
|
140
|
+
let resolveSecond!: (v: string) => void;
|
|
141
|
+
|
|
142
|
+
const t = task((which: unknown) =>
|
|
143
|
+
which === "first"
|
|
144
|
+
? new Promise<string>((r) => { resolveFirst = r; })
|
|
145
|
+
: new Promise<string>((r) => { resolveSecond = r; }),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const p1 = t("first");
|
|
149
|
+
const p2 = t("second");
|
|
150
|
+
|
|
151
|
+
// second resolves before first
|
|
152
|
+
resolveSecond("second-result");
|
|
153
|
+
resolveFirst("first-result");
|
|
154
|
+
|
|
155
|
+
const [r1, r2] = await Promise.all([p1, p2]);
|
|
156
|
+
expect(r2).toBe("second-result");
|
|
157
|
+
expect(r1).toBeUndefined(); // stale
|
|
158
|
+
expect(t.lastResult()).toBe("second-result");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("cancelAll() called, then new call — new call works normally", async () => {
|
|
162
|
+
let resolveStale!: (v: string) => void;
|
|
163
|
+
const t = task((which: unknown) =>
|
|
164
|
+
which === "stale"
|
|
165
|
+
? new Promise<string>((r) => { resolveStale = r; })
|
|
166
|
+
: Promise.resolve("fresh"),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const staleP = t("stale");
|
|
170
|
+
t.cancelAll();
|
|
171
|
+
resolveStale("stale-value");
|
|
172
|
+
await staleP;
|
|
173
|
+
|
|
174
|
+
const freshResult = await t("fresh");
|
|
175
|
+
expect(freshResult).toBe("fresh");
|
|
176
|
+
expect(t.loading()).toBe(false);
|
|
177
|
+
expect(t.lastResult()).toBe("fresh");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("lastResult is null when task throws (not stale from previous run)", async () => {
|
|
181
|
+
let shouldThrow = false;
|
|
182
|
+
const t = task(async () => {
|
|
183
|
+
if (shouldThrow) throw new Error("fail");
|
|
184
|
+
return "success";
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await t();
|
|
188
|
+
expect(t.lastResult()).toBe("success");
|
|
189
|
+
|
|
190
|
+
shouldThrow = true;
|
|
191
|
+
await t();
|
|
192
|
+
|
|
193
|
+
// After a throw, lastResult should remain the previous value
|
|
194
|
+
// (the error path does not update lastResult)
|
|
195
|
+
expect(t.error()?.message).toBe("fail");
|
|
196
|
+
// lastResult is not reset to null on error — it keeps the last successful value
|
|
197
|
+
expect(t.lastResult()).toBe("success");
|
|
198
|
+
});
|
|
137
199
|
});
|
package/src/decorators.ts
CHANGED
|
@@ -1,58 +1,50 @@
|
|
|
1
|
+
import { createMethodDecorator } from "@praxisjs/decorators";
|
|
2
|
+
|
|
1
3
|
import { pool } from "./pool";
|
|
2
4
|
import { queue } from "./queue";
|
|
3
5
|
import { task } from "./task";
|
|
4
6
|
|
|
5
7
|
export function Task() {
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
self[methodKey] = (...args: unknown[]) => t(...args);
|
|
19
|
-
});
|
|
20
|
-
};
|
|
8
|
+
return createMethodDecorator({
|
|
9
|
+
wrap(original, instance, name) {
|
|
10
|
+
const self = instance as Record<string, unknown>;
|
|
11
|
+
const t = task((original as (...args: unknown[]) => Promise<unknown>).bind(instance));
|
|
12
|
+
self[`${name}_loading`] = t.loading;
|
|
13
|
+
self[`${name}_error`] = t.error;
|
|
14
|
+
self[`${name}_lastResult`] = t.lastResult;
|
|
15
|
+
return (...args: unknown[]) => t(...args);
|
|
16
|
+
},
|
|
17
|
+
// Concurrent decorators work on any class, not just StatefulComponent
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
}) as unknown as (value: (...args: unknown[]) => Promise<unknown>, context: ClassMethodDecoratorContext<any>) => void;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
export function Queue() {
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
self[methodKey] = (...args: unknown[]) => q(...args);
|
|
37
|
-
});
|
|
38
|
-
};
|
|
23
|
+
return createMethodDecorator({
|
|
24
|
+
wrap(original, instance, name) {
|
|
25
|
+
const self = instance as Record<string, unknown>;
|
|
26
|
+
const q = queue((original as (...args: unknown[]) => Promise<unknown>).bind(instance));
|
|
27
|
+
self[`${name}_loading`] = q.loading;
|
|
28
|
+
self[`${name}_pending`] = q.pending;
|
|
29
|
+
self[`${name}_error`] = q.error;
|
|
30
|
+
self[`${name}_clear`] = () => { q.clear(); };
|
|
31
|
+
return (...args: unknown[]) => q(...args);
|
|
32
|
+
},
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
}) as unknown as (value: (...args: unknown[]) => Promise<unknown>, context: ClassMethodDecoratorContext<any>) => void;
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
export function Pool(concurrency: number) {
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
self[`${methodKey}_error`] = p.error;
|
|
55
|
-
self[methodKey] = (...args: unknown[]) => p(...args);
|
|
56
|
-
});
|
|
57
|
-
};
|
|
38
|
+
return createMethodDecorator({
|
|
39
|
+
wrap(original, instance, name) {
|
|
40
|
+
const self = instance as Record<string, unknown>;
|
|
41
|
+
const p = pool(concurrency, (original as (...args: unknown[]) => Promise<unknown>).bind(instance));
|
|
42
|
+
self[`${name}_loading`] = p.loading;
|
|
43
|
+
self[`${name}_active`] = p.active;
|
|
44
|
+
self[`${name}_pending`] = p.pending;
|
|
45
|
+
self[`${name}_error`] = p.error;
|
|
46
|
+
return (...args: unknown[]) => p(...args);
|
|
47
|
+
},
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
}) as unknown as (value: (...args: unknown[]) => Promise<unknown>, context: ClassMethodDecoratorContext<any>) => void;
|
|
58
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1 @@
|
|
|
1
|
-
export { task } from "./task";
|
|
2
|
-
export type { TaskInstance } from "./task";
|
|
3
|
-
|
|
4
|
-
export { queue } from "./queue";
|
|
5
|
-
export type { QueueInstance } from "./queue";
|
|
6
|
-
|
|
7
|
-
export { pool } from "./pool";
|
|
8
|
-
export type { PoolInstance } from "./pool";
|
|
9
|
-
|
|
10
1
|
export { Task, Queue, Pool } from "./decorators";
|
package/src/pool.ts
CHANGED
|
@@ -13,6 +13,7 @@ export function pool<T>(
|
|
|
13
13
|
concurrency: number,
|
|
14
14
|
fn: (...args: unknown[]) => Promise<T>,
|
|
15
15
|
): PoolInstance<T> {
|
|
16
|
+
concurrency = Math.max(1, concurrency);
|
|
16
17
|
const _active = signal(0);
|
|
17
18
|
const _pending = signal(0);
|
|
18
19
|
const _error = signal<Error | null>(null);
|
package/src/queue.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { signal, computed } from "@praxisjs/core/internal";
|
|
2
2
|
import type { Computed } from "@praxisjs/shared";
|
|
3
3
|
|
|
4
|
+
export class QueueClearedError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("Queue cleared");
|
|
7
|
+
this.name = "QueueClearedError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
export interface QueueInstance<T> {
|
|
5
12
|
(...args: unknown[]): Promise<T>;
|
|
6
13
|
loading: Computed<boolean>;
|
|
@@ -27,7 +34,7 @@ export function queue<T>(
|
|
|
27
34
|
if (_running || _queue.length === 0) return;
|
|
28
35
|
_running = true;
|
|
29
36
|
_loading.set(true);
|
|
30
|
-
while (_queue.length > 0
|
|
37
|
+
while (_queue.length > 0) {
|
|
31
38
|
const item = _queue.shift();
|
|
32
39
|
if (!item) break;
|
|
33
40
|
const { args, resolve, reject } = item;
|
|
@@ -46,6 +53,9 @@ export function queue<T>(
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
function enqueue(...args: unknown[]): Promise<T> {
|
|
56
|
+
if (_cleared) {
|
|
57
|
+
_cleared = false;
|
|
58
|
+
}
|
|
49
59
|
return new Promise<T>((resolve, reject) => {
|
|
50
60
|
_queue.push({ args, resolve, reject });
|
|
51
61
|
_pending.update((n) => n + 1);
|
|
@@ -57,7 +67,10 @@ export function queue<T>(
|
|
|
57
67
|
enqueue.pending = computed(() => _pending());
|
|
58
68
|
enqueue.error = computed(() => _error());
|
|
59
69
|
enqueue.clear = () => {
|
|
60
|
-
|
|
70
|
+
// Reject all currently queued (not-yet-started) items
|
|
71
|
+
while (_queue.length > 0) {
|
|
72
|
+
_queue.shift()?.reject(new QueueClearedError());
|
|
73
|
+
}
|
|
61
74
|
_pending.set(0);
|
|
62
75
|
_cleared = true;
|
|
63
76
|
};
|