codeweaver 2.3.1 → 3.0.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.
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { ResponseError } from "./error-handling";
3
+
4
+ /**
5
+ * Strictly assign obj (type T1) to T2 using a Zod schema.
6
+ *
7
+ * - Extras in source are ignored.
8
+ * - Validates fields with the schema; on failure, throws with a descriptive message.
9
+ * - Returns an object typed as T2 (inferred from the schema).
10
+ *
11
+ * @param source - Source object of type T1
12
+ * @param destination - Destination object to be populated (typed as T2)
13
+ * @param schema - Zod schema describing the target type T2
14
+ * @returns T2 representing the destination after assignment
15
+ */
16
+ export default async function assign<T1 extends object, T2 extends object>(
17
+ source: T1,
18
+ destination: T2,
19
+ schema?: z.ZodObject<any>
20
+ ): Promise<T2> {
21
+ let keys = Object.keys(schema?.shape ?? destination);
22
+
23
+ // Iterate schema keys
24
+ await Promise.all(
25
+ keys.map(async (key) => {
26
+ if (source.hasOwnProperty(key)) {
27
+ (destination as any)[key] = (source as any)[key];
28
+ }
29
+ })
30
+ );
31
+
32
+ if (schema != null) {
33
+ // Validate using the schema on the subset (this will also coerce if the schema has transforms)
34
+ const parseResult = await schema.safeParseAsync(destination);
35
+ if (parseResult.success == false) {
36
+ // Build a descriptive error message from the first issue
37
+ const issue = parseResult.error.issues?.[0];
38
+ const path = issue?.path?.length ? issue.path.join(".") : "value";
39
+ const message = issue?.message ?? "Schema validation failed";
40
+ throw new ResponseError(
41
+ `Validation failed for "${path}": ${message}`,
42
+ 500
43
+ );
44
+ }
45
+ }
46
+
47
+ return destination;
48
+ }
@@ -109,50 +109,57 @@ export function stringToNumber(input: string): number {
109
109
  * - Validates fields with the schema; on failure, throws with a descriptive message.
110
110
  * - Returns an object typed as T2 (inferred from the schema).
111
111
  *
112
- * @param obj - Source object of type T1
112
+ * @param data - Source object of type T1
113
113
  * @param schema - Zod schema describing the target type T2
114
114
  * @returns T2 inferred from the provided schema
115
115
  */
116
- export function convert<T1 extends object, T2 extends object>(
117
- obj: T1,
118
- schema: z.ZodObject<any>
119
- ): T2 {
120
- // 1) Derive the runtime keys from the schema's shape
116
+ export async function convert<T1 extends object, T2 extends object>(
117
+ data: T1,
118
+ schema: z.ZodObject<any>,
119
+ ignoreValidation: boolean = false
120
+ ): Promise<T2> {
121
+ // Derive the runtime keys from the schema's shape
121
122
  const shape = (schema as any)._def?.shape as ZodRawShape | undefined;
122
123
  if (!shape) {
123
- throw new ResponseError(
124
- "convertStrictlyFromSchema: provided schema has no shape.",
125
- 500
126
- );
124
+ throw new ResponseError("Provided schema has no shape.", 500);
127
125
  }
128
126
 
129
127
  const keysSchema = Object.keys(shape) as Array<keyof any>;
130
128
 
131
- // 2) Build a plain object to pass through Zod for validation
129
+ // Build a plain object to pass through Zod for validation
132
130
  // Include only keys that exist on the schema (ignore extras in obj)
133
131
  const candidate: any = {};
134
- for (const k of keysSchema) {
135
- if ((obj as any).hasOwnProperty(k)) {
136
- candidate[k] = (obj as any)[k];
132
+
133
+ // Iterate schema keys
134
+ await Promise.all(
135
+ keysSchema.map(async (key) => {
136
+ if ((data as any).hasOwnProperty(key)) {
137
+ candidate[key] = (data as any)[key];
138
+ }
139
+ })
140
+ );
141
+
142
+ // Validate against the schema
143
+ if (ignoreValidation) {
144
+ const result = await schema.safeParseAsync(candidate);
145
+ if (result.success == false) {
146
+ // Modern, non-format error reporting
147
+ const issues = result.error.issues.map((i) => ({
148
+ path: i.path, // where the issue occurred
149
+ message: i.message, // human-friendly message
150
+ code: i.code, // e.g., "too_small", "invalid_type"
151
+ }));
152
+
153
+ // You can log issues or throw a structured error
154
+ throw new ResponseError(
155
+ `Validation failed: ${JSON.stringify(issues)}`,
156
+ 500
157
+ );
137
158
  }
138
- }
139
159
 
140
- // 3) Validate against the schema
141
- const result = schema.safeParse(candidate);
142
- if (!result.success) {
143
- // Modern, non-format error reporting
144
- const issues = result.error.issues.map((i) => ({
145
- path: i.path, // where the issue occurred
146
- message: i.message, // human-friendly message
147
- code: i.code, // e.g., "too_small", "invalid_type"
148
- }));
149
- // You can log issues or throw a structured error
150
- throw new ResponseError(
151
- `convertStrictlyFromSchema: validation failed: ${JSON.stringify(issues)}`,
152
- 500
153
- );
160
+ // Return the validated data typed as T2
161
+ return result.data as T2;
154
162
  }
155
163
 
156
- // 4) Return the validated data typed as T2
157
- return result.data as T2;
164
+ return candidate as T2;
158
165
  }
@@ -64,10 +64,10 @@ export function sendHttpError(res: Response, error: ResponseError): void {
64
64
 
65
65
  /**
66
66
  * A generic alias representing a tuple of [result, error].
67
- * - result is either T or null if an error occurred
68
- * - error is either a ResponseError or null if the operation succeeded
67
+ * - result is either T or undefined if an error occurred
68
+ * - error is either a ResponseError or undefined if the operation succeeded
69
69
  */
70
- export type ReturnInfo<T> = [T | null, ResponseError | null];
70
+ export type ReturnInfo<T> = [T | undefined, ResponseError | undefined];
71
71
 
72
72
  /**
73
73
  * A Promise-wrapped version of ReturnInfo.
@@ -78,25 +78,25 @@ export type AsyncReturnInfo<T> = Promise<ReturnInfo<T>>;
78
78
  * Executes a function and captures a potential error as a ReturnInfo tuple.
79
79
  *
80
80
  * Returns a two-element tuple: [value, error]
81
- * - value: the function result if it succeeds; null if an exception is thrown
82
- * - error: the caught Error wrapped as a ResponseError (or the provided error) if the function throws; null if the function succeeds
81
+ * - value: the function result if it succeeds; undefined if an exception is thrown
82
+ * - error: the caught Error wrapped as a ResponseError (or the provided error) if the function throws; undefined if the function succeeds
83
83
  *
84
84
  * This utility helps avoid try/catch blocks at call sites by returning both the
85
85
  * result and any error in a single value.
86
86
  *
87
87
  * @template T
88
88
  * @param func - The function to execute
89
- * @param error - The error object to return when an exception occurs (typically a ResponseError). If no error is provided, null is used.
90
- * @returns ReturnInfo<T> A tuple: [value or null, error or null]
89
+ * @param error - The error object to return when an exception occurs (typically a ResponseError). If no error is provided, undefined is used.
90
+ * @returns ReturnInfo<T> A tuple: [value or undefined, error or undefined]
91
91
  */
92
92
  export function invoke<T>(
93
93
  func: () => T,
94
- error: ResponseError | null
94
+ error: ResponseError | undefined
95
95
  ): ReturnInfo<T> {
96
96
  try {
97
- return [func(), null];
97
+ return [func(), undefined];
98
98
  } catch {
99
- return [null, error];
99
+ return [undefined, error];
100
100
  }
101
101
  }
102
102
 
@@ -104,14 +104,14 @@ export function invoke<T>(
104
104
  * Creates a successful result from a ReturnInfo tuple.
105
105
  *
106
106
  * Given a ReturnInfo<T> of the form [value, error], this returns the value
107
- * when the operation succeeded, or null when there was an error.
107
+ * when the operation succeeded, or undefined when there was an error.
108
108
  *
109
109
  * @template T
110
110
  * @param input - The ReturnInfo tuple
111
- * @returns The successful value of type T, or null if there was an error
111
+ * @returns The successful value of type T, or undefined if there was an error
112
112
  */
113
- export function successfulResult<T>(input: ReturnInfo<T>): T | null {
114
- return input[0];
113
+ export function successfulResult<T>(result: ReturnInfo<T>): T | undefined {
114
+ return result[0];
115
115
  }
116
116
 
117
117
  /**
@@ -122,24 +122,24 @@ export function successfulResult<T>(input: ReturnInfo<T>): T | null {
122
122
  * If the error is already a ResponseError, it is returned as-is.
123
123
  *
124
124
  * @template T
125
- * @param responseError - The error to wrap, either as a ResponseError or as a ReturnInfo<T> where the error is at index 1
126
- * @returns The extracted or wrapped ResponseError, or null if there is no error
125
+ * @param result - The error to wrap, either as a ResponseError or as a ReturnInfo<T> where the error is at index 1
126
+ * @returns The extracted or wrapped ResponseError, or undefined if there is no error
127
127
  */
128
- export function error<T>(responseError: ReturnInfo<T>): ResponseError | null {
129
- return responseError[1];
128
+ export function error<T>(result: ReturnInfo<T>): ResponseError | undefined {
129
+ return result[1];
130
130
  }
131
131
 
132
132
  /**
133
133
  * Determines whether a ReturnInfo value represents a successful operation.
134
134
  *
135
- * A result is considered successful when there is no error (i.e., the error portion is null).
135
+ * A result is considered successful when there is no error (i.e., the error portion is undefined).
136
136
  *
137
137
  * @template T
138
- * @param result - The ReturnInfo tuple [value | null, error | null]
138
+ * @param result - The ReturnInfo tuple [value | undefined, error | undefined]
139
139
  * @returns true if there is no error; false otherwise
140
140
  */
141
141
  export function isSuccessful<T>(result: ReturnInfo<T>): boolean {
142
- return result[1] === null;
142
+ return result[1] === undefined;
143
143
  }
144
144
 
145
145
  /**
@@ -148,11 +148,11 @@ export function isSuccessful<T>(result: ReturnInfo<T>): boolean {
148
148
  * This is the logical negation of isSuccess for a given ReturnInfo.
149
149
  *
150
150
  * @template T
151
- * @param result - The ReturnInfo tuple [value | null, error | null]
152
- * @returns true if an error is present (i.e., error is not null); false otherwise
151
+ * @param result - The ReturnInfo tuple [value | undefined, error | undefined]
152
+ * @returns true if an error is present (i.e., error is not undefined); false otherwise
153
153
  */
154
154
  export function hasError<T>(result: ReturnInfo<T>): boolean {
155
- return result[1] !== null;
155
+ return result[1] !== undefined;
156
156
  }
157
157
 
158
158
  /**
@@ -161,12 +161,12 @@ export function hasError<T>(result: ReturnInfo<T>): boolean {
161
161
  * This is the logical negation of isSuccess for a given ReturnInfo.
162
162
  *
163
163
  * @template T
164
- * @param result - The ReturnInfo tuple [value | null, error | null]
165
- * @returns true if an error is present (i.e., error is not null); false otherwise
164
+ * @param result - The ReturnInfo tuple [value | undefined, error | undefined]
165
+ * @returns true if an error is present (i.e., error is not undefined); false otherwise
166
166
  */
167
167
  export function then<T>(
168
168
  result: ReturnInfo<T>,
169
- callback: (object: T) => void
169
+ callback: (value: T) => void
170
170
  ): void {
171
171
  if (isSuccessful(result)) {
172
172
  callback(successfulResult(result)!);
@@ -179,10 +179,10 @@ export function then<T>(
179
179
  * This is the logical negation of isSuccess for a given ReturnInfo.
180
180
  *
181
181
  * @template T
182
- * @param result - The ReturnInfo tuple [value | null, error | null]
183
- * @returns true if an error is present (i.e., error is not null); false otherwise
182
+ * @param result - The ReturnInfo tuple [value | undefined, error | undefined]
183
+ * @returns true if an error is present (i.e., error is not undefined); false otherwise
184
184
  */
185
- export function catchError<T>(
185
+ export function catchIfHasError<T>(
186
186
  result: ReturnInfo<T>,
187
187
  callback: (error: ResponseError) => void
188
188
  ): void {
@@ -190,3 +190,18 @@ export function catchError<T>(
190
190
  callback(error(result)!);
191
191
  }
192
192
  }
193
+
194
+ /**
195
+ * Indicates whether a ReturnInfo value represents an error.
196
+ *
197
+ * This is the logical negation of isSuccess for a given ReturnInfo.
198
+ *
199
+ * @template T
200
+ * @param result - The ReturnInfo tuple [value | undefined, error | undefined]
201
+ * @returns true if an error is present (i.e., error is not undefined); false otherwise
202
+ */
203
+ export function throwIfHasError<T>(result: ReturnInfo<T>): void {
204
+ if (hasError(result)) {
205
+ throw error(result);
206
+ }
207
+ }
@@ -0,0 +1,142 @@
1
+ import { ResponseError } from "../error-handling";
2
+
3
+ /**
4
+ * Event callback type for channel send event.
5
+ * @template T - Type of the value sent through the channel.
6
+ * @param {T} value - The value sent to the channel.
7
+ */
8
+ export type EventCallback<T> = (value: T) => void;
9
+
10
+ /**
11
+ * Channel class inspired by Golang channels.
12
+ * Supports asynchronous send and receive operations,
13
+ * optional event callback on send, and closing semantics.
14
+ *
15
+ * @template T - Type of the values sent through the channel.
16
+ */
17
+ export class Channel<T> {
18
+ private queue: T[] = [];
19
+ private receivers: ((value: T) => void)[] = [];
20
+ private closed = false;
21
+ private eventCb?: EventCallback<T>;
22
+
23
+ /**
24
+ * Creates a new Channel.
25
+ * @param {EventCallback<T>} [eventCb] - Optional callback triggered after each send.
26
+ */
27
+ public constructor(eventCb?: EventCallback<T>) {
28
+ this.eventCb = eventCb;
29
+ }
30
+
31
+ /**
32
+ * Sends a value to the channel.
33
+ * If there are waiting receivers, delivers immediately.
34
+ * Otherwise, buffers the value.
35
+ * Throws if channel is closed.
36
+ * @param {T} value - Value to send.
37
+ * @returns {Promise<void>} Promise resolved when the send completes.
38
+ */
39
+ public async send(value: T): Promise<void> {
40
+ if (this.closed) {
41
+ throw new ResponseError("Channel is closed", 500);
42
+ }
43
+ if (this.receivers.length > 0) {
44
+ const receiver = this.receivers.shift()!;
45
+ receiver(value);
46
+ } else {
47
+ this.queue.push(value);
48
+ }
49
+ if (this.eventCb) {
50
+ this.eventCb(value);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Receives a value from the channel.
56
+ * If no buffered value, waits until one is sent.
57
+ * Throws if channel is closed and no buffered values remain.
58
+ * @returns {Promise<T>} Promise resolved with the next value.
59
+ */
60
+ public async receive(): Promise<T> {
61
+ if (this.queue.length > 0) {
62
+ return this.queue.shift()!;
63
+ }
64
+ if (this.closed) {
65
+ throw new ResponseError("Channel is closed", 500);
66
+ }
67
+ return await new Promise<T>((resolve) => {
68
+ this.receivers.push(resolve);
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Closes the channel.
74
+ * Further sends will throw.
75
+ * Pending receivers receive undefined.
76
+ */
77
+ public close() {
78
+ this.closed = true;
79
+ while (this.receivers.length > 0) {
80
+ const receiver = this.receivers.shift()!;
81
+ receiver(undefined!); // Indicate closed channel to receivers
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Mutex class inspired by Golang sync.Mutex using Promise chaining.
88
+ * Supports lock, scoped locking with ulock, and tryLock with timeout.
89
+ */
90
+ export class Mutex {
91
+ private mutex = Promise.resolve();
92
+
93
+ /**
94
+ * Locks the mutex asynchronously.
95
+ * Returns a release function to unlock.
96
+ * @returns {Promise<() => void>} Promise resolved with the unlock function.
97
+ */
98
+ public async lock(): Promise<() => void> {
99
+ let release: () => void;
100
+ const lockPromise = new Promise<void>((res) => (release = res));
101
+ const oldMutex = this.mutex;
102
+ this.mutex = oldMutex.then(() => lockPromise);
103
+ await oldMutex;
104
+ return release!;
105
+ }
106
+
107
+ /**
108
+ * Executes a given task under mutex lock.
109
+ * Ensures the mutex is released even if task throws.
110
+ * @param {() => Promise<void> | void} task - Task to execute exclusively.
111
+ */
112
+ public async unlock(task: () => Promise<void> | void): Promise<void> {
113
+ const release = await this.lock();
114
+ try {
115
+ await task();
116
+ } finally {
117
+ release();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Attempts to acquire the lock with a timeout.
123
+ * If timeout expires, returns null.
124
+ * Otherwise returns the unlock function.
125
+ * @param {number} timeoutMs - Timeout in milliseconds.
126
+ * @returns {Promise<(() => void) | null>} Unlock function or null on timeout.
127
+ */
128
+ public async tryLock(timeoutMs: number): Promise<(() => void) | null> {
129
+ let timer: NodeJS.Timeout;
130
+ const timedOut = new Promise<null>((resolve) => {
131
+ timer = setTimeout(() => resolve(null), timeoutMs);
132
+ });
133
+ const lockPromise = this.lock();
134
+
135
+ const result = await Promise.race([lockPromise, timedOut]);
136
+ if (result === null) {
137
+ return null;
138
+ }
139
+ clearTimeout(timer!);
140
+ return result as () => void;
141
+ }
142
+ }
@@ -0,0 +1,135 @@
1
+ import { ResponseError } from "../error-handling";
2
+ import path from "path";
3
+ import { WorkerPool } from "./worker-pool";
4
+
5
+ /**
6
+ * Executes multiple asynchronous or synchronous tasks in parallel and returns their results as an array.
7
+ *
8
+ * @template T The type of the result returned by each task.
9
+ * @param {(() => T)[]} tasks - An array of functions, where each function returns a value or a promise.
10
+ * @returns {Promise<T[]>} A promise that resolves to an array containing the resolved results of all tasks, preserving order.
11
+ *
12
+ * @example
13
+ * const results = await parallel(
14
+ * () => fetchUser(1),
15
+ * () => fetchUser(2),
16
+ * () => fetchUser(3)
17
+ * );
18
+ * console.log(results); // [user1, user2, user3]
19
+ */
20
+ export async function parallel<T>(...tasks: (() => T)[]): Promise<T[]> {
21
+ return await Promise.all(tasks.map(async (task) => task()));
22
+ }
23
+
24
+ /**
25
+ * Simple parallel mapper.
26
+ * Executes the provided async mapper function on each item in parallel
27
+ * and collects the results in the same order as the input.
28
+ *
29
+ * @template T - Type of input items
30
+ * @template U - Type of mapped results
31
+ * @param items - Array of items to process
32
+ * @param mapper - Async function that maps a T to a U (or Promise<U>)
33
+ * @returns {Promise<U[]>} - Resolves to an array of mapped results in input order
34
+ */
35
+ export async function parallelMap<T, U>(
36
+ items: T[],
37
+ mapper: (item: T, index?: number, array?: T[]) => Promise<U> | U
38
+ ): Promise<U[]> {
39
+ // Map each item to its mapped Promise and await all of them in parallel
40
+ return await Promise.all(
41
+ items.map(async (item, index, array) => await mapper(item, index, array))
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Concurrency-limited parallel mapper.
47
+ * Processes items with a configurable maximum number of concurrent operations.
48
+ * If concurrency is Infinity or not finite, falls back to unbounded Promise.all.
49
+ *
50
+ * @template T - Type of input items
51
+ * @template U - Type of mapped results
52
+ * @param items - Array of items to process
53
+ * @param mapper - Async function that maps a T to a U (or Promise<U>)
54
+ * @param concurrencyLevel - number | Infinity - Max concurrent operations (default: Infinity)
55
+ * @returns {Promise<U[]>} - Resolves to an array of mapped results in input order
56
+ */
57
+ export async function parallelMapWithConcurrencyLevel<T, U>(
58
+ items: T[],
59
+ mapper: (item: T, index?: number, array?: T[]) => Promise<U> | U,
60
+ concurrencyLevel: number = Infinity
61
+ ): Promise<U[]> {
62
+ if (!Array.isArray(items)) {
63
+ throw new ResponseError("Items must be an array", 400);
64
+ }
65
+
66
+ if (concurrencyLevel <= 0) {
67
+ throw new ResponseError("Concurrency must be greater than 0", 500);
68
+ }
69
+
70
+ // If concurrency is not finite, use the simple Promise.all approach
71
+ if (!isFinite(concurrencyLevel)) {
72
+ return await Promise.all(
73
+ items.map((item) => Promise.resolve(mapper(item)))
74
+ );
75
+ }
76
+
77
+ const results: U[] = new Array(items.length);
78
+ let i = 0;
79
+
80
+ // Create a fixed number of worker promises that pull from the shared index
81
+ const workers = Array.from({ length: concurrencyLevel }, async () => {
82
+ while (i < items.length) {
83
+ const idx = i++;
84
+ const item = items[idx];
85
+ // Store the result in the corresponding position to preserve order
86
+ results[idx] = await mapper(item, idx, items);
87
+ }
88
+ });
89
+
90
+ await Promise.all(workers);
91
+ return results;
92
+ }
93
+
94
+ /**
95
+ * Parallel CPU-bound mapper using a fixed-size worker pool.
96
+ * - Distributes items to workers and collects results in input order.
97
+ * - Each worker runs a fixed performMapping function defined inside the worker.
98
+ *
99
+ * Important: The mapper logic inside the worker is fixed. If you need custom per-item logic,
100
+ * you should modify the worker to import your actual function or adapt to serialize logic.
101
+ *
102
+ * @template T - Input item type
103
+ * @template R - Result type
104
+ * @param items - Array of items to process
105
+ * @param options - Concurrency options (default uses number of CPU cores)
106
+ * @returns {Promise<R[]>} - Results in the same order as input
107
+ */
108
+ export async function parallelCpuMap<T, R>(
109
+ items: T[],
110
+ mapperWorkerFilePath: string,
111
+ concurrencyLevel: number = require("os").cpus().length || 1
112
+ ): Promise<R[]> {
113
+ if (!Array.isArray(items)) {
114
+ throw new TypeError("items must be an array");
115
+ }
116
+ if (concurrencyLevel <= 0) {
117
+ throw new Error("concurrency must be greater than 0");
118
+ }
119
+
120
+ // Worker pool setup
121
+ const workerPath = path.resolve(__dirname, mapperWorkerFilePath);
122
+
123
+ // Instantiate a concrete pool
124
+ const workerPool = new WorkerPool<T, R>(workerPath, concurrencyLevel);
125
+
126
+ try {
127
+ // Dispatch all items and collect results in order
128
+ const results = await workerPool.mapAll(items);
129
+ await workerPool.close();
130
+ return results;
131
+ } catch (err) {
132
+ await workerPool.close();
133
+ throw err;
134
+ }
135
+ }
@@ -0,0 +1,99 @@
1
+ import { Worker } from "worker_threads";
2
+ import os from "os";
3
+ import { ResponseError } from "../error-handling";
4
+
5
+ export type Task<T> = { id: number; payload: T };
6
+ export type Result<R> = { id: number; result?: R; error?: string };
7
+
8
+ export class WorkerPool<T, R> {
9
+ private workers: Worker[] = [];
10
+ private poolSize: number;
11
+ private nextId = 0;
12
+
13
+ // Map task id -> { resolve, reject }
14
+ private pending = new Map<
15
+ number,
16
+ { resolve: (r: R) => void; reject: (e: any) => void }
17
+ >();
18
+
19
+ constructor(workerPath: string, poolSize?: number) {
20
+ const cores = os.cpus().length || 1;
21
+ this.poolSize = poolSize && poolSize > 0 ? poolSize : cores;
22
+
23
+ for (let i = 0; i < this.poolSize; i++) {
24
+ const w = new Worker(workerPath);
25
+ w.on("message", (msg: Result<R>) => this.handleResult(msg));
26
+ w.on("error", (err) => this.handleError(err, i));
27
+ w.on("exit", (code) => {
28
+ if (code !== 0) {
29
+ // Notify all pending promises about the exit
30
+ for (const [, p] of this.pending) {
31
+ p.reject(
32
+ new ResponseError(`Worker ${i} exited with code ${code}`, 500)
33
+ );
34
+ }
35
+ this.pending.clear();
36
+ }
37
+ });
38
+ this.workers.push(w);
39
+ }
40
+ }
41
+
42
+ private handleResult(msg: Result<R>) {
43
+ const { id, result, error } = msg;
44
+ const entry = this.pending.get(id);
45
+ if (!entry) return;
46
+ this.pending.delete(id);
47
+ if (error) {
48
+ entry.reject(new Error(error));
49
+ } else {
50
+ entry.resolve(result as R);
51
+ }
52
+ }
53
+
54
+ private handleError(err: any, workerIndex: number) {
55
+ // Propagate error to all pending tasks assigned to this worker, if tracked
56
+ // This simple version broadcasts the error to all pending tasks for safety.
57
+ for (const [id, entry] of this.pending) {
58
+ entry.reject(
59
+ new ResponseError(
60
+ `Worker ${workerIndex} error: ${err?.message ?? err}`,
61
+ 500
62
+ )
63
+ );
64
+ }
65
+ this.pending.clear();
66
+ }
67
+
68
+ // Run a single payload through a specific worker
69
+ run(workerIndex: number, payload: T): Promise<R> {
70
+ const id = this.nextId++;
71
+ const worker = this.workers[workerIndex];
72
+ return new Promise<R>((resolve, reject) => {
73
+ this.pending.set(id, { resolve, reject });
74
+ worker.postMessage({ id, payload });
75
+ });
76
+ }
77
+
78
+ // Map all items using a round-robin distribution across workers
79
+ async mapAll(items: T[]): Promise<R[]> {
80
+ if (!Array.isArray(items))
81
+ throw new ResponseError("Items must be an array", 400);
82
+ const results: R[] = new Array(items.length);
83
+
84
+ const workerCount = this.workers.length;
85
+ const tasks: Promise<void>[] = items.map((payload, idx) => {
86
+ const workerIndex = idx % workerCount;
87
+ return this.run(workerIndex, payload).then((r) => {
88
+ results[idx] = r;
89
+ });
90
+ });
91
+
92
+ await Promise.all(tasks);
93
+ return results;
94
+ }
95
+
96
+ async close(): Promise<void> {
97
+ await Promise.all(this.workers.map((w) => w.terminate()));
98
+ }
99
+ }
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2023",
3
+ "target": "esnext",
4
4
  "module": "commonjs",
5
5
  "outDir": "./dist",
6
6
  "strict": true,