quantam-async 0.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.
Files changed (49) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +198 -0
  3. package/dist/bench.d.ts +2 -0
  4. package/dist/bench.d.ts.map +1 -0
  5. package/dist/bench.js +25 -0
  6. package/dist/bench.js.map +1 -0
  7. package/dist/core.d.ts +27 -0
  8. package/dist/core.d.ts.map +1 -0
  9. package/dist/core.js +175 -0
  10. package/dist/core.js.map +1 -0
  11. package/dist/examples/basic.d.ts +2 -0
  12. package/dist/examples/basic.d.ts.map +1 -0
  13. package/dist/examples/basic.js +25 -0
  14. package/dist/examples/basic.js.map +1 -0
  15. package/dist/examples/batch.d.ts +2 -0
  16. package/dist/examples/batch.d.ts.map +1 -0
  17. package/dist/examples/batch.js +18 -0
  18. package/dist/examples/batch.js.map +1 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +11 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/tests/quantam.test.d.ts +2 -0
  24. package/dist/tests/quantam.test.d.ts.map +1 -0
  25. package/dist/tests/quantam.test.js +66 -0
  26. package/dist/tests/quantam.test.js.map +1 -0
  27. package/dist/types.d.ts +31 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +6 -0
  30. package/dist/types.js.map +1 -0
  31. package/examples/basic.d.ts +2 -0
  32. package/examples/basic.d.ts.map +1 -0
  33. package/examples/basic.js +25 -0
  34. package/examples/basic.js.map +1 -0
  35. package/examples/basic.ts +0 -0
  36. package/examples/batch.d.ts +2 -0
  37. package/examples/batch.d.ts.map +1 -0
  38. package/examples/batch.js +18 -0
  39. package/examples/batch.js.map +1 -0
  40. package/examples/batch.ts +0 -0
  41. package/package.json +29 -0
  42. package/src/bench.ts +28 -0
  43. package/src/core.ts +244 -0
  44. package/src/examples/basic.ts +42 -0
  45. package/src/examples/batch.ts +20 -0
  46. package/src/index.ts +7 -0
  47. package/src/tests/quantam.test.ts +74 -0
  48. package/src/types.ts +36 -0
  49. package/tsconfig.json +20 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Type definitions for Quantam
3
+ */
4
+ export type AsyncFn<T = any, R = any> = (input: T, context?: ExecutionContext) => Promise<R>;
5
+ export interface ExecutionContext {
6
+ signal?: AbortSignal;
7
+ retryCount?: number;
8
+ stepIndex?: number;
9
+ }
10
+ export interface FlowOptions {
11
+ signal?: AbortSignal;
12
+ }
13
+ export interface RunManyOptions extends FlowOptions {
14
+ concurrency?: number;
15
+ }
16
+ export interface Flow<T = any> {
17
+ step<R>(fn: AsyncFn<T, R>): Flow<R>;
18
+ parallel<R>(fns: AsyncFn<T, any>[]): Flow<R[]>;
19
+ retry(count: number, delayMs?: number): Flow<T>;
20
+ timeout(ms: number): Flow<T>;
21
+ stepTimeout(ms: number): Flow<T>;
22
+ withSignal(signal: AbortSignal): Flow<T>;
23
+ run(input: T, options?: FlowOptions): Promise<T>;
24
+ runMany(inputs: T[], options?: RunManyOptions): Promise<T[]>;
25
+ }
26
+ export interface QuantamConfig {
27
+ retries?: number;
28
+ retryDelay?: number;
29
+ timeout?: number;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7F,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,IAAI,CAAC,CAAC,GAAG,GAAG;IAC3B,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/C,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACjC,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Type definitions for Quantam
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;GAEG"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=basic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["basic.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const src_1 = require("../src");
4
+ async function main() {
5
+ async function fetchUser(id) {
6
+ return { id, name: 'Alice' };
7
+ }
8
+ async function fetchOrders(user) {
9
+ return { user, orders: [1, 2, 3] };
10
+ }
11
+ async function enrichData(data) {
12
+ return { ...data, enriched: true };
13
+ }
14
+ const flow = (0, src_1.quantam)()
15
+ .step(fetchUser)
16
+ .step(fetchOrders)
17
+ .step(enrichData);
18
+ const result = await flow.run('user-123');
19
+ console.log(result);
20
+ }
21
+ main().catch((err) => {
22
+ console.error(err);
23
+ process.exitCode = 1;
24
+ });
25
+ //# sourceMappingURL=basic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basic.js","sourceRoot":"","sources":["basic.ts"],"names":[],"mappings":";;AAAA,gCAAiC;AAgBjC,KAAK,UAAU,IAAI;IACjB,KAAK,UAAU,SAAS,CAAC,EAAU;QACjC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC/B,CAAC;IAED,KAAK,UAAU,WAAW,CAAC,IAAU;QACnC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,UAAU,UAAU,CAAC,IAAoB;QAC5C,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IAED,MAAM,IAAI,GAAG,IAAA,aAAO,GAAU;SAC3B,IAAI,CAAC,SAAS,CAAC;SACf,IAAI,CAAC,WAAW,CAAC;SACjB,IAAI,CAAC,UAAU,CAAC,CAAC;IAEpB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
File without changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["batch.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const src_1 = require("../src");
4
+ async function main() {
5
+ const flow = (0, src_1.quantam)().step(async (n) => n + 1);
6
+ const inputs = Array.from({ length: 1000 }, (_, i) => i);
7
+ const results = await flow.runMany(inputs, { concurrency: 64 });
8
+ console.log({
9
+ length: results.length,
10
+ first: results[0],
11
+ last: results[results.length - 1],
12
+ });
13
+ }
14
+ main().catch((err) => {
15
+ console.error(err);
16
+ process.exitCode = 1;
17
+ });
18
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["batch.ts"],"names":[],"mappings":";;AAAA,gCAAiC;AAEjC,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,IAAA,aAAO,GAAU,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAExD,MAAM,MAAM,GAAa,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IAEhE,OAAO,CAAC,GAAG,CAAC;QACV,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;KAClC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
File without changes
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "quantam-async",
3
+ "version": "0.1.0",
4
+ "description": "A lightweight async workflow engine for composing, running, and controlling complex task pipelines with retries, parallelism, timeouts, and cancellation.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "test": "node --test dist/tests/*.test.js",
11
+ "clean": "rm -rf dist"
12
+ },
13
+ "keywords": [
14
+ "async",
15
+ "pipeline",
16
+ "workflow",
17
+ "retry",
18
+ "timeout",
19
+ "cancellation",
20
+ "parallel",
21
+ "orchestration"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "devDependencies": {
26
+ "@types/node": "^20.0.0",
27
+ "typescript": "^5.0.0"
28
+ }
29
+ }
package/src/bench.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { quantam } from './index';
2
+
3
+ async function main() {
4
+ const size = 100000;
5
+ const inputs = Array.from({ length: size }, (_, i) => i);
6
+ const flow = quantam<number>().step(async (n) => n + 1);
7
+ const start = Date.now();
8
+ const results = await flow.runMany(inputs, { concurrency: 512 });
9
+ const durationMs = Date.now() - start;
10
+ const opsPerSec = durationMs > 0 ? size / (durationMs / 1000) : 0;
11
+
12
+ console.log(
13
+ JSON.stringify({
14
+ size,
15
+ concurrency: 512,
16
+ durationMs,
17
+ opsPerSec,
18
+ first: results[0],
19
+ last: results[results.length - 1],
20
+ }),
21
+ );
22
+ }
23
+
24
+ main().catch((err) => {
25
+ console.error(err);
26
+ process.exitCode = 1;
27
+ });
28
+
package/src/core.ts ADDED
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Quantam Core Implementation
3
+ * Fluent API for composing async pipelines
4
+ */
5
+
6
+ import { AsyncFn, Flow, FlowOptions, ExecutionContext } from './types';
7
+
8
+ interface Step {
9
+ type: 'single' | 'parallel';
10
+ fn?: AsyncFn;
11
+ fns?: AsyncFn[];
12
+ retries?: number;
13
+ retryDelay?: number;
14
+ timeout?: number;
15
+ }
16
+
17
+ export class Quantam<T = any> implements Flow<T> {
18
+ private steps: Step[] = [];
19
+ private timeoutMs?: number;
20
+ private abortSignal?: AbortSignal;
21
+
22
+ step<R>(fn: AsyncFn<T, R>): Quantam<R> {
23
+ const newFlow = new Quantam<R>();
24
+ newFlow.steps = [...this.steps];
25
+ newFlow.timeoutMs = this.timeoutMs;
26
+ newFlow.abortSignal = this.abortSignal;
27
+ newFlow.steps.push({ type: 'single', fn });
28
+ return newFlow;
29
+ }
30
+
31
+ parallel<R>(fns: AsyncFn<T, any>[]): Quantam<R[]> {
32
+ const newFlow = new Quantam<R[]>();
33
+ newFlow.steps = [...this.steps];
34
+ newFlow.timeoutMs = this.timeoutMs;
35
+ newFlow.abortSignal = this.abortSignal;
36
+ newFlow.steps.push({ type: 'parallel', fns });
37
+ return newFlow;
38
+ }
39
+
40
+ retry(count: number, delayMs: number = 100): Quantam<T> {
41
+ const newFlow = new Quantam<T>();
42
+ newFlow.steps = [...this.steps];
43
+ newFlow.timeoutMs = this.timeoutMs;
44
+ newFlow.abortSignal = this.abortSignal;
45
+
46
+ if (newFlow.steps.length > 0) {
47
+ const lastStep = newFlow.steps[newFlow.steps.length - 1];
48
+ lastStep.retries = count;
49
+ lastStep.retryDelay = delayMs;
50
+ }
51
+
52
+ return newFlow;
53
+ }
54
+
55
+ stepTimeout(ms: number): Quantam<T> {
56
+ const newFlow = new Quantam<T>();
57
+ newFlow.steps = [...this.steps];
58
+ newFlow.timeoutMs = this.timeoutMs;
59
+ newFlow.abortSignal = this.abortSignal;
60
+
61
+ if (newFlow.steps.length > 0) {
62
+ const lastStep = newFlow.steps[newFlow.steps.length - 1];
63
+ lastStep.timeout = ms;
64
+ }
65
+
66
+ return newFlow;
67
+ }
68
+
69
+ withSignal(signal: AbortSignal): Quantam<T> {
70
+ const newFlow = new Quantam<T>();
71
+ newFlow.steps = [...this.steps];
72
+ newFlow.timeoutMs = this.timeoutMs;
73
+ newFlow.abortSignal = signal;
74
+ return newFlow;
75
+ }
76
+
77
+ timeout(ms: number): Quantam<T> {
78
+ const newFlow = new Quantam<T>();
79
+ newFlow.steps = [...this.steps];
80
+ newFlow.timeoutMs = ms;
81
+ newFlow.abortSignal = this.abortSignal;
82
+ return newFlow;
83
+ }
84
+
85
+ async runMany(inputs: T[], options?: FlowOptions & { concurrency?: number }): Promise<T[]> {
86
+ const configuredConcurrency = options?.concurrency;
87
+ const concurrency = configuredConcurrency != null ? configuredConcurrency : inputs.length;
88
+ if (inputs.length === 0) {
89
+ return [];
90
+ }
91
+
92
+ if (concurrency <= 0 || concurrency >= inputs.length) {
93
+ return Promise.all(inputs.map((input) => this.run(input, options)));
94
+ }
95
+
96
+ const results: T[] = new Array(inputs.length);
97
+ let index = 0;
98
+ const worker = async () => {
99
+ while (true) {
100
+ const currentIndex = index++;
101
+ if (currentIndex >= inputs.length) {
102
+ break;
103
+ }
104
+ results[currentIndex] = await this.run(inputs[currentIndex], options);
105
+ }
106
+ };
107
+
108
+ const workers: Promise<void>[] = [];
109
+ const workerCount = Math.min(concurrency, inputs.length);
110
+ for (let i = 0; i < workerCount; i++) {
111
+ workers.push(worker());
112
+ }
113
+
114
+ await Promise.all(workers);
115
+ return results;
116
+ }
117
+
118
+ async run(input: T, options?: FlowOptions): Promise<T> {
119
+ const signal = options?.signal || this.abortSignal;
120
+ let result: any = input;
121
+ const context: ExecutionContext = {
122
+ signal,
123
+ stepIndex: 0,
124
+ retryCount: 0,
125
+ };
126
+
127
+ for (let i = 0; i < this.steps.length; i++) {
128
+ this.checkAborted(signal);
129
+
130
+ const step = this.steps[i];
131
+ context.stepIndex = i;
132
+ context.retryCount = 0;
133
+
134
+ if (step.type === 'single' && step.fn) {
135
+ result = await this.executeSingleStep(
136
+ step.fn,
137
+ result,
138
+ context,
139
+ step.retries || 0,
140
+ step.retryDelay || 100,
141
+ step.timeout,
142
+ );
143
+ } else if (step.type === 'parallel' && step.fns) {
144
+ result = await this.executeParallelSteps(
145
+ step.fns,
146
+ result,
147
+ context,
148
+ step.retries || 0,
149
+ step.retryDelay || 100,
150
+ step.timeout,
151
+ );
152
+ }
153
+ }
154
+
155
+ return result;
156
+ }
157
+
158
+ private async executeSingleStep(
159
+ fn: AsyncFn,
160
+ input: any,
161
+ context: ExecutionContext,
162
+ retries: number,
163
+ retryDelay: number,
164
+ stepTimeout?: number,
165
+ ): Promise<any> {
166
+ let lastError: Error | null = null;
167
+
168
+ for (let attempt = 0; attempt <= retries; attempt++) {
169
+ try {
170
+ this.checkAborted(context.signal);
171
+ context.retryCount = attempt;
172
+
173
+ const promise = fn(input, context);
174
+ const effectiveTimeout = stepTimeout ?? this.timeoutMs;
175
+ const result = effectiveTimeout
176
+ ? await Promise.race([
177
+ promise,
178
+ this.createTimeout(effectiveTimeout),
179
+ ])
180
+ : await promise;
181
+
182
+ return result;
183
+ } catch (error) {
184
+ lastError = error as Error;
185
+
186
+ if (attempt < retries) {
187
+ const delay = retryDelay * (1 << attempt);
188
+ await this.sleep(delay, context.signal);
189
+ }
190
+ }
191
+ }
192
+
193
+ throw lastError || new Error('Step execution failed');
194
+ }
195
+
196
+ private async executeParallelSteps(
197
+ fns: AsyncFn[],
198
+ input: any,
199
+ context: ExecutionContext,
200
+ retries: number,
201
+ retryDelay: number,
202
+ stepTimeout?: number,
203
+ ): Promise<any[]> {
204
+ const promises = fns.map((fn) =>
205
+ this.executeSingleStep(fn, input, context, retries, retryDelay, stepTimeout),
206
+ );
207
+
208
+ return Promise.all(promises);
209
+ }
210
+
211
+ private checkAborted(signal?: AbortSignal): void {
212
+ if (signal?.aborted) {
213
+ throw new Error('Pipeline aborted');
214
+ }
215
+ }
216
+
217
+ private createTimeout(ms: number): Promise<never> {
218
+ return new Promise((_, reject) =>
219
+ setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms),
220
+ );
221
+ }
222
+
223
+ private sleep(ms: number, signal?: AbortSignal): Promise<void> {
224
+ return new Promise((resolve, reject) => {
225
+ const timeoutId = setTimeout(resolve, ms);
226
+
227
+ if (signal) {
228
+ const onAbort = () => {
229
+ clearTimeout(timeoutId);
230
+ reject(new Error('Sleep aborted'));
231
+ };
232
+ if (signal.aborted) {
233
+ onAbort();
234
+ return;
235
+ }
236
+ signal.addEventListener('abort', onAbort, { once: true });
237
+ }
238
+ });
239
+ }
240
+ }
241
+
242
+ export function quantam<T = any>(): Quantam<T> {
243
+ return new Quantam<T>();
244
+ }
@@ -0,0 +1,42 @@
1
+ import { quantam } from '../index';
2
+
3
+ interface User {
4
+ id: string;
5
+ name: string;
6
+ }
7
+
8
+ interface UserWithOrders {
9
+ user: User;
10
+ orders: number[];
11
+ }
12
+
13
+ interface EnrichedUser extends UserWithOrders {
14
+ enriched: boolean;
15
+ }
16
+
17
+ async function main(): Promise<void> {
18
+ async function fetchUser(id: string): Promise<User> {
19
+ return { id, name: 'Alice' };
20
+ }
21
+
22
+ async function fetchOrders(user: User): Promise<UserWithOrders> {
23
+ return { user, orders: [1, 2, 3] };
24
+ }
25
+
26
+ async function enrichData(data: UserWithOrders): Promise<EnrichedUser> {
27
+ return { ...data, enriched: true };
28
+ }
29
+
30
+ const flow = quantam()
31
+ .step(fetchUser)
32
+ .step(fetchOrders)
33
+ .step(enrichData);
34
+
35
+ const result = await flow.run('user-123' as any);
36
+ console.log(result);
37
+ }
38
+
39
+ main().catch((err) => {
40
+ console.error(err);
41
+ process.exitCode = 1;
42
+ });
@@ -0,0 +1,20 @@
1
+ import { quantam } from '../index';
2
+
3
+ async function main(): Promise<void> {
4
+ const flow = quantam<number>().step(async (n) => n + 1);
5
+
6
+ const inputs: number[] = Array.from({ length: 1000 }, (_, i) => i);
7
+ const results = await flow.runMany(inputs, { concurrency: 64 });
8
+
9
+ console.log({
10
+ length: results.length,
11
+ first: results[0],
12
+ last: results[results.length - 1],
13
+ });
14
+ }
15
+
16
+ main().catch((err) => {
17
+ console.error(err);
18
+ process.exitCode = 1;
19
+ });
20
+
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Quantam - A lightweight async workflow engine
3
+ * Core entry point
4
+ */
5
+
6
+ export { Quantam, quantam } from './core';
7
+ export type { Flow, FlowOptions, ExecutionContext } from './types';
@@ -0,0 +1,74 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { quantam } from '../index';
4
+
5
+ test('runs basic sequential pipeline', async () => {
6
+ const flow = quantam<number>()
7
+ .step(async (n) => n + 1)
8
+ .step(async (n) => n * 2);
9
+
10
+ const result = await flow.run(1);
11
+ assert.equal(result, 4);
12
+ });
13
+
14
+ test('retries and exposes retryCount', async () => {
15
+ let attempts = 0;
16
+
17
+ const flow = quantam<number>()
18
+ .step(async (n, ctx) => {
19
+ attempts++;
20
+ if ((ctx?.retryCount ?? 0) < 2) {
21
+ throw new Error('transient');
22
+ }
23
+ return n + (ctx?.retryCount ?? 0);
24
+ })
25
+ .retry(3, 1);
26
+
27
+ const result = await flow.run(1);
28
+ assert.equal(result, 3);
29
+ assert.equal(attempts, 3);
30
+ });
31
+
32
+ test('stepTimeout rejects slow step', async () => {
33
+ const flow = quantam<number>()
34
+ .step(async (n) => {
35
+ await new Promise((resolve) => setTimeout(resolve, 20));
36
+ return n + 1;
37
+ })
38
+ .stepTimeout(5);
39
+
40
+ let threw = false;
41
+ try {
42
+ await flow.run(1);
43
+ } catch (err) {
44
+ threw = true;
45
+ assert.match((err as Error).message, /Timeout/);
46
+ }
47
+ assert.equal(threw, true);
48
+ });
49
+
50
+ test('runMany respects concurrency limit', async () => {
51
+ const concurrencyLimit = 4;
52
+ let inFlight = 0;
53
+ let maxInFlight = 0;
54
+
55
+ const flow = quantam<number>().step(async (n) => {
56
+ inFlight++;
57
+ if (inFlight > maxInFlight) {
58
+ maxInFlight = inFlight;
59
+ }
60
+ await new Promise((resolve) => setTimeout(resolve, 5));
61
+ inFlight--;
62
+ return n + 1;
63
+ });
64
+
65
+ const inputs = Array.from({ length: 16 }, (_, i) => i);
66
+ const results = await flow.runMany(inputs, { concurrency: concurrencyLimit });
67
+
68
+ assert.deepEqual(
69
+ results,
70
+ inputs.map((n) => n + 1),
71
+ );
72
+ assert.ok(maxInFlight <= concurrencyLimit);
73
+ });
74
+
package/src/types.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Type definitions for Quantam
3
+ */
4
+
5
+ export type AsyncFn<T = any, R = any> = (input: T, context?: ExecutionContext) => Promise<R>;
6
+
7
+ export interface ExecutionContext {
8
+ signal?: AbortSignal;
9
+ retryCount?: number;
10
+ stepIndex?: number;
11
+ }
12
+
13
+ export interface FlowOptions {
14
+ signal?: AbortSignal;
15
+ }
16
+
17
+ export interface RunManyOptions extends FlowOptions {
18
+ concurrency?: number;
19
+ }
20
+
21
+ export interface Flow<T = any> {
22
+ step<R>(fn: AsyncFn<T, R>): Flow<R>;
23
+ parallel<R>(fns: AsyncFn<T, any>[]): Flow<R[]>;
24
+ retry(count: number, delayMs?: number): Flow<T>;
25
+ timeout(ms: number): Flow<T>;
26
+ stepTimeout(ms: number): Flow<T>;
27
+ withSignal(signal: AbortSignal): Flow<T>;
28
+ run(input: T, options?: FlowOptions): Promise<T>;
29
+ runMany(inputs: T[], options?: RunManyOptions): Promise<T[]>;
30
+ }
31
+
32
+ export interface QuantamConfig {
33
+ retries?: number;
34
+ retryDelay?: number;
35
+ timeout?: number;
36
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "moduleResolution": "node"
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }