@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.
@@ -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,CAyCjB"}
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
@@ -1,5 +1,6 @@
1
1
  import { signal, computed } from "@praxisjs/core/internal";
2
2
  export function pool(concurrency, fn) {
3
+ concurrency = Math.max(1, concurrency);
3
4
  const _active = signal(0);
4
5
  const _pending = signal(0);
5
6
  const _error = signal(null);
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
@@ -1,4 +1,7 @@
1
1
  import type { Computed } from "@praxisjs/shared";
2
+ export declare class QueueClearedError extends Error {
3
+ constructor();
4
+ }
2
5
  export interface QueueInstance<T> {
3
6
  (...args: unknown[]): Promise<T>;
4
7
  loading: Computed<boolean>;
@@ -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,CAoDlB"}
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 && !_cleared) {
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
- _queue.length = 0;
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;AAW3D,MAAM,UAAU,KAAK,CACnB,EAAsC;IAEtC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAe,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAIP,EAAE,CAAC;IACR,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,UAAU,KAAK;QAClB,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC5C,QAAQ,GAAG,IAAI,CAAC;QAChB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI;gBAAE,MAAM;YACjB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;YACvC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,CAAC;gBACH,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YAC7B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC9D,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACd,MAAM,CAAC,CAAC,CAAC,CAAC;YACZ,CAAC;QACH,CAAC;QACD,QAAQ,GAAG,KAAK,CAAC;QACjB,QAAQ,GAAG,KAAK,CAAC;QACjB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,SAAS,OAAO,CAAC,GAAG,IAAe;QACjC,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACvC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,KAAK,KAAK,EAAE,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,OAAO,CAAC,KAAK,GAAG,GAAG,EAAE;QACnB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAClB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChB,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,OAA2B,CAAC;AACrC,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": "0.2.3",
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": "0.4.2",
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 — tasks are enqueued but never executed", async () => {
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
- // Queue a task tryRun() bails immediately because _active(0) >= concurrency(0)
137
- void p();
138
- await new Promise((r) => setTimeout(r, 0));
139
-
140
- expect(fn).not.toHaveBeenCalled();
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 function (
7
- value: (...args: unknown[]) => Promise<unknown>,
8
- context: ClassMethodDecoratorContext,
9
- ): void {
10
- const methodKey = String(context.name);
11
-
12
- context.addInitializer(function (this: unknown) {
13
- const self = this as Record<string, unknown>;
14
- const t = task(value.bind(this));
15
- self[`${methodKey}_loading`] = t.loading;
16
- self[`${methodKey}_error`] = t.error;
17
- self[`${methodKey}_lastResult`] = t.lastResult;
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 function (
25
- value: (...args: unknown[]) => Promise<unknown>,
26
- context: ClassMethodDecoratorContext,
27
- ): void {
28
- const methodKey = String(context.name);
29
-
30
- context.addInitializer(function (this: unknown) {
31
- const self = this as Record<string, unknown>;
32
- const q = queue(value.bind(this));
33
- self[`${methodKey}_loading`] = q.loading;
34
- self[`${methodKey}_pending`] = q.pending;
35
- self[`${methodKey}_error`] = q.error;
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 function (
43
- value: (...args: unknown[]) => Promise<unknown>,
44
- context: ClassMethodDecoratorContext,
45
- ): void {
46
- const methodKey = String(context.name);
47
-
48
- context.addInitializer(function (this: unknown) {
49
- const self = this as Record<string, unknown>;
50
- const p = pool(concurrency, value.bind(this));
51
- self[`${methodKey}_loading`] = p.loading;
52
- self[`${methodKey}_active`] = p.active;
53
- self[`${methodKey}_pending`] = p.pending;
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 && !_cleared) {
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
- _queue.length = 0;
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
  };