codeweaver 3.1.2 → 4.0.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 (79) hide show
  1. package/README.md +56 -73
  2. package/package.json +23 -1
  3. package/src/config.ts +17 -15
  4. package/src/constants.ts +1 -0
  5. package/src/core/aws/api-gateway.ts +187 -0
  6. package/src/core/aws/basic-types.ts +147 -0
  7. package/src/core/aws/dynamodb.ts +187 -0
  8. package/src/core/aws/index.ts +9 -0
  9. package/src/core/aws/lambda.ts +199 -0
  10. package/src/core/aws/message-broker.ts +167 -0
  11. package/src/core/aws/message.ts +259 -0
  12. package/src/core/aws/s3.ts +136 -0
  13. package/src/core/aws/utilities.ts +44 -0
  14. package/src/core/cache/basic-types.ts +17 -0
  15. package/src/core/cache/decorator.ts +72 -0
  16. package/src/core/cache/index.ts +4 -0
  17. package/src/core/cache/memory-cache.class.ts +119 -0
  18. package/src/{utilities/cache/redis-cache.ts → core/cache/redis-cache.class.ts} +58 -10
  19. package/src/core/container/basic-types.ts +10 -0
  20. package/src/{utilities → core/container}/container.ts +7 -17
  21. package/src/core/container/index.ts +2 -0
  22. package/src/{utilities → core/error}/error-handling.ts +1 -65
  23. package/src/core/error/index.ts +3 -0
  24. package/src/core/error/response-error.ts +45 -0
  25. package/src/core/error/send-http-error.ts +15 -0
  26. package/src/core/file/file-helpers.ts +166 -0
  27. package/src/core/file/index.ts +1 -0
  28. package/src/{utilities → core/helpers}/assignment.ts +2 -2
  29. package/src/core/helpers/comparison.ts +86 -0
  30. package/src/{utilities → core/helpers}/conversion.ts +2 -2
  31. package/src/core/helpers/decorators.ts +316 -0
  32. package/src/core/helpers/format.ts +9 -0
  33. package/src/core/helpers/index.ts +7 -0
  34. package/src/core/helpers/range.ts +67 -0
  35. package/src/core/helpers/types.ts +3 -0
  36. package/src/core/logger/index.ts +4 -0
  37. package/src/{utilities/logger/logger.config.ts → core/logger/winston-logger.config.ts} +1 -1
  38. package/src/{utilities → core}/logger/winston-logger.service.ts +3 -3
  39. package/src/core/message-broker/bullmq/basic-types.ts +67 -0
  40. package/src/core/message-broker/bullmq/broker.ts +141 -0
  41. package/src/core/message-broker/bullmq/index.ts +3 -0
  42. package/src/core/message-broker/bullmq/queue.ts +58 -0
  43. package/src/core/message-broker/bullmq/worker.ts +68 -0
  44. package/src/core/message-broker/kafka/basic-types.ts +45 -0
  45. package/src/core/message-broker/kafka/consumer.ts +95 -0
  46. package/src/core/message-broker/kafka/index.ts +3 -0
  47. package/src/core/message-broker/kafka/producer.ts +113 -0
  48. package/src/core/message-broker/rabitmq/basic-types.ts +44 -0
  49. package/src/core/message-broker/rabitmq/channel.ts +95 -0
  50. package/src/core/message-broker/rabitmq/consumer.ts +94 -0
  51. package/src/core/message-broker/rabitmq/index.ts +4 -0
  52. package/src/core/message-broker/rabitmq/producer.ts +100 -0
  53. package/src/core/message-broker/utilities.ts +50 -0
  54. package/src/core/middlewares/basic-types.ts +39 -0
  55. package/src/core/middlewares/decorators.ts +244 -0
  56. package/src/core/middlewares/index.ts +3 -0
  57. package/src/core/middlewares/middlewares.ts +246 -0
  58. package/src/core/parallel/index.ts +3 -0
  59. package/src/{utilities → core}/parallel/parallel.ts +11 -1
  60. package/src/core/rate-limit/basic-types.ts +43 -0
  61. package/src/core/rate-limit/index.ts +4 -0
  62. package/src/core/rate-limit/memory-store.ts +65 -0
  63. package/src/core/rate-limit/rate-limit.ts +134 -0
  64. package/src/core/rate-limit/redis-store.ts +141 -0
  65. package/src/core/retry/basic-types.ts +21 -0
  66. package/src/core/retry/decorator.ts +139 -0
  67. package/src/core/retry/index.ts +2 -0
  68. package/src/main.ts +6 -8
  69. package/src/routers/orders/index.router.ts +5 -1
  70. package/src/routers/orders/order.controller.ts +46 -59
  71. package/src/routers/products/index.router.ts +2 -1
  72. package/src/routers/products/product.controller.ts +25 -63
  73. package/src/routers/users/index.router.ts +1 -1
  74. package/src/routers/users/user.controller.ts +23 -51
  75. package/src/utilities/cache/memory-cache.ts +0 -74
  76. /package/src/{utilities → core}/logger/base-logger.interface.ts +0 -0
  77. /package/src/{utilities → core}/logger/logger.service.ts +0 -0
  78. /package/src/{utilities → core}/parallel/chanel.ts +0 -0
  79. /package/src/{utilities → core}/parallel/worker-pool.ts +0 -0
@@ -1,16 +1,21 @@
1
- import { AsyncCache } from "utils-decorators";
2
- import { Redis } from "ioredis";
1
+ import { Redis, RedisKey } from "ioredis";
2
+ import { AsyncCache } from "./basic-types";
3
3
 
4
4
  /**
5
5
  * A Redis-backed cache adapter implementing AsyncCache<T | null>.
6
6
  * This wraps a Redis instance and provides a similar API to MapAsyncCache.
7
7
  */
8
8
  export class RedisCache<T> implements AsyncCache<T> {
9
+ private startTimeMs?: number;
10
+
9
11
  public constructor(
10
- private cache?: Redis,
11
12
  private capacity?: number,
13
+ private durationMs?: number,
14
+ private cache?: Redis,
12
15
  private namespace?: string
13
16
  ) {
17
+ this.startTimeMs = this.startTimeMs ?? Date.now();
18
+ this.durationMs = this.durationMs ?? Number.POSITIVE_INFINITY;
14
19
  this.cache = this.cache ?? new Redis();
15
20
  this.capacity = this.capacity ?? Number.POSITIVE_INFINITY;
16
21
  this.namespace = this.namespace ?? "redis";
@@ -20,13 +25,55 @@ export class RedisCache<T> implements AsyncCache<T> {
20
25
  return `${this.namespace}:${key}`;
21
26
  }
22
27
 
28
+ public async size(): Promise<number> {
29
+ return await this.cache!.dbsize();
30
+ }
31
+
32
+ public async keys(pattern: string): Promise<string[]> {
33
+ return await Array.fromAsync((await this.cache!.keys(pattern)) ?? []);
34
+ }
35
+
36
+ /**
37
+ * Returns all the field names (keys) in a Redis hash.
38
+ * @param key The Redis key (hash) to query.
39
+ * @returns An array of field names (keys) in the hash.
40
+ */
41
+ public async hkeys(key: RedisKey): Promise<string[]> {
42
+ return await Array.fromAsync((await this.cache!.hkeys(key)) ?? []);
43
+ }
44
+
45
+ /**
46
+ * Whether the cache is still valid.
47
+ * This is a computed property that returns true if the cache
48
+ * has not yet expired, and false otherwise.
49
+ * @returns true if the cache is still valid, false otherwise
50
+ */
51
+ public get isValid(): boolean {
52
+ return this.startTimeMs! + this.durationMs! > Date.now();
53
+ }
54
+
55
+ /**
56
+ * Resets the cache if it has expired.
57
+ * If the cache has expired, resets the cache by clearing it and updating the start time.
58
+ * @returns true if the cache was reset, false otherwise.
59
+ */
60
+ public async resetIfExpired(): Promise<boolean> {
61
+ if (this.isValid == false) {
62
+ await this.clear();
63
+ this.startTimeMs = Date.now();
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+
23
69
  /**
24
70
  * Asynchronously set a value by key.
25
71
  * If value is null, delete the key to represent "not present".
26
72
  * Capacity is not strictly enforced in Redis on a per-key basis here; you
27
73
  * could leverage Redis max memory and eviction policies for that.
28
74
  */
29
- async set(key: string, value: T): Promise<void> {
75
+ public async set(key: string, value: T): Promise<void> {
76
+ if ((await this.resetIfExpired()) == true) return;
30
77
  const k = this.keyFor(key);
31
78
 
32
79
  if (value != null) {
@@ -50,7 +97,7 @@ export class RedisCache<T> implements AsyncCache<T> {
50
97
  * - If the key exists, returns the parsed value.
51
98
  * - If the key does not exist, returns null to satisfy Promise<T | null>.
52
99
  */
53
- async get(key: string): Promise<T> {
100
+ public async get(key: string): Promise<T> {
54
101
  const k = this.keyFor(key);
55
102
  const raw = (await this.cache?.get(k)) ?? null;
56
103
  if (raw == null) {
@@ -66,14 +113,15 @@ export class RedisCache<T> implements AsyncCache<T> {
66
113
  /**
67
114
  * Asynchronously delete a value by key.
68
115
  */
69
- async delete(key: string): Promise<void> {
116
+ public async delete(key: string): Promise<void> {
70
117
  await this.cache?.del(this.keyFor(key));
71
118
  }
72
119
 
73
120
  /**
74
121
  * Asynchronously check if a key exists in the cache.
75
122
  */
76
- async has(key: string): Promise<boolean> {
123
+ public async has(key: string): Promise<boolean> {
124
+ if ((await this.resetIfExpired()) == true) return false;
77
125
  const exists = await this.cache?.exists(this.keyFor(key));
78
126
  return exists === 1;
79
127
  }
@@ -82,10 +130,10 @@ export class RedisCache<T> implements AsyncCache<T> {
82
130
  * Asynchronously clear the cache (namespace-scoped).
83
131
  * Use with caution in a shared Redis instance.
84
132
  */
85
- async clear(key: string): Promise<void> {
133
+ public async clear(key: string = ""): Promise<void> {
86
134
  // Optional: implement namespace-wide clear if needed
87
135
  // This simple example uses a pattern-based approach for a full clear.
88
- const pattern = `${this.namespace}:*${key ? "" : ""}`;
136
+ const pattern = `${this.namespace}:*`;
89
137
  const stream = this.cache?.scanStream({ match: pattern });
90
138
  const pipeline = this.cache?.pipeline();
91
139
  stream?.on("data", (keys: string[]) => {
@@ -105,7 +153,7 @@ export class RedisCache<T> implements AsyncCache<T> {
105
153
  /**
106
154
  * Optional: gracefully close the Redis connection.
107
155
  */
108
- async close(): Promise<void> {
156
+ public async close(): Promise<void> {
109
157
  await this.cache?.quit();
110
158
  }
111
159
  }
@@ -0,0 +1,10 @@
1
+ export type Constructor<T = any> = new (...args: any[]) => T;
2
+
3
+ export interface Provider<T = any> {
4
+ /** The concrete class to instantiate (may be the same as token or a different implementation). */
5
+ useClass: Constructor<T>;
6
+ /** Cached singleton instance, if one has been created. */
7
+ instance?: T;
8
+ /** Whether to treat the provider as a singleton. Defaults to true. */
9
+ singleton?: boolean;
10
+ }
@@ -1,17 +1,7 @@
1
1
  import "reflect-metadata";
2
+ import { Constructor, Provider } from "./basic-types";
2
3
 
3
- export type Constructor<T = any> = new (...args: any[]) => T;
4
-
5
- interface Provider<T = any> {
6
- /** The concrete class to instantiate (may be the same as token or a different implementation). */
7
- useClass: Constructor<T>;
8
- /** Cached singleton instance, if one has been created. */
9
- instance?: T;
10
- /** Whether to treat the provider as a singleton. Defaults to true. */
11
- singleton?: boolean;
12
- }
13
-
14
- const InjectableRegistry: Array<Constructor<any>> = [];
4
+ const injectableRegistry: Array<Constructor<any>> = [];
15
5
 
16
6
  /**
17
7
  * A tiny dependency injection container.
@@ -39,7 +29,7 @@ class Container {
39
29
  * - useClass?: The concrete class to instantiate for this token.
40
30
  * - singleton?: Whether to reuse a single instance (default: true).
41
31
  */
42
- register<T>(
32
+ public register<T>(
43
33
  token: Constructor<T>,
44
34
  options?: { useClass?: Constructor<T>; singleton?: boolean }
45
35
  ): void {
@@ -61,7 +51,7 @@ class Container {
61
51
  * @param token - The token (class constructor) to resolve.
62
52
  * @returns An instance of the requested type T.
63
53
  */
64
- resolve<T>(token: Constructor<T>): T {
54
+ public resolve<T>(token: Constructor<T>): T {
65
55
  const provider = this.registrations.get(token);
66
56
 
67
57
  if (!provider) {
@@ -113,7 +103,7 @@ class Container {
113
103
  * metadata is actually readable.
114
104
  */
115
105
  function bootstrapContainer(container: Container) {
116
- for (const constructorFunction of InjectableRegistry) {
106
+ for (const constructorFunction of injectableRegistry) {
117
107
  // Read per-class options if you added metadata
118
108
  const meta = Reflect.getMetadata("di:injectable", constructorFunction) as
119
109
  | { singleton?: boolean }
@@ -135,14 +125,14 @@ export function Injectable(options?: {
135
125
  }): ClassDecorator {
136
126
  return (target: any) => {
137
127
  // Push into registry for later registration
138
- InjectableRegistry.push(target);
128
+ injectableRegistry.push(target);
139
129
  // You could also attach metadata if you want to customize per-class
140
130
  Reflect.defineMetadata("di:injectable", options ?? {}, target);
141
131
  };
142
132
  }
143
133
 
144
134
  /** A single, shared DI container for the app. */
145
- const container = new Container();
135
+ export const container = new Container();
146
136
  bootstrapContainer(container);
147
137
 
148
138
  /**
@@ -0,0 +1,2 @@
1
+ export * from "./basic-types";
2
+ export * from "./container";
@@ -1,68 +1,4 @@
1
- import { Response } from "express";
2
-
3
- /**
4
- * Represents a standardized error response structure for API endpoints.
5
- *
6
- * This class models an API-friendly error, carrying a human message plus
7
- * optional metadata (status, details, input, code, stack). Extends the built-in Error
8
- * so it works naturally with try/catch blocks.
9
- */
10
- export class ResponseError extends Error {
11
- public constructor(
12
- /**
13
- * User-facing error message describing what went wrong.
14
- */
15
- public message: string,
16
-
17
- /**
18
- * Optional HTTP status code related to the error (e.g., 400, 404, 500).
19
- */
20
- public status?: number,
21
-
22
- /**
23
- * Optional human-readable details or context about the error.
24
- */
25
- public details?: string,
26
-
27
- /**
28
- * Optional input value that caused the error (useful for logging/diagnostics).
29
- */
30
- public input?: string,
31
-
32
- /**
33
- * Optional application-specific error code (e.g., "INVALID_INPUT").
34
- */
35
- public code?: string,
36
-
37
- /**
38
- * Optional stack trace string (usually provided by runtime).
39
- * Note: In many environments, stack is inherited from Error; you may
40
- * not need to redefine it here unless you have a specific reason.
41
- */
42
- public stack?: string
43
- ) {
44
- // Ensure the base Error class gets the message for standard properties like name, stack, etc.
45
- super(message);
46
-
47
- // If a custom stack is provided, you might assign it; otherwise, the runtime stack will be used.
48
- if (stack) {
49
- this.stack = stack;
50
- }
51
- }
52
- }
53
-
54
- /**
55
- * Sends a standardized HTTP error response.
56
- *
57
- * This function sets the response status from the provided error (defaulting to 500)
58
- * and serializes the error object as JSON.
59
- *
60
- * @param res - Express Response object to send the error on
61
- * @param error - Error details to return to the client (must include status or default will be 500)
62
- */
63
- export function sendHttpError(res: Response, error: ResponseError): void {
64
- res.status(error.status ?? 500).json(error);
65
- }
1
+ import { ResponseError } from "./response-error";
66
2
 
67
3
  /**
68
4
  * A generic alias representing a tuple of [result, error].
@@ -0,0 +1,3 @@
1
+ export * from "./response-error";
2
+ export * from "./send-http-error";
3
+ export * from "./error-handling";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Represents a standardized error response structure for API endpoints.
3
+ *
4
+ * This class models an API-friendly error, carrying a human message plus
5
+ * optional metadata (status, details, input, code, stack). Extends the built-in Error
6
+ * so it works naturally with try/catch blocks.
7
+ */
8
+ export class ResponseError extends Error {
9
+ public constructor(
10
+ /**
11
+ * User-facing error message describing what went wrong.
12
+ */
13
+ public override message: string,
14
+
15
+ /**
16
+ * Optional HTTP status code related to the error (e.g., 400, 404, 500).
17
+ */
18
+ public status?: number,
19
+
20
+ /**
21
+ * Optional human-readable details or context about the error.
22
+ */
23
+ public details?: string,
24
+
25
+ /**
26
+ * Optional input value that caused the error (useful for logging/diagnostics).
27
+ */
28
+ public input?: string,
29
+
30
+ /**
31
+ * Optional application-specific error code (e.g., "INVALID_INPUT").
32
+ */
33
+ public code?: string,
34
+
35
+ /**
36
+ * Optional stack trace string (usually provided by runtime).
37
+ * Note: In many environments, stack is inherited from Error; you may
38
+ * not need to redefine it here unless you have a specific reason.
39
+ */
40
+ public override stack?: string
41
+ ) {
42
+ // Ensure the base Error class gets the message for standard properties like name, stack, etc.
43
+ super(message);
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ import { Response } from "express";
2
+ import { ResponseError } from "./response-error";
3
+
4
+ /**
5
+ * Sends a standardized HTTP error response.
6
+ *
7
+ * This function sets the response status from the provided error (defaulting to 500)
8
+ * and serializes the error object as JSON.
9
+ *
10
+ * @param res - Express Response object to send the error on
11
+ * @param error - Error details to return to the client (must include status or default will be 500)
12
+ */
13
+ export function sendHttpError(res: Response, error: ResponseError): void {
14
+ res.status(error.status ?? 500).json(error);
15
+ }
@@ -0,0 +1,166 @@
1
+ import { promises as fs } from "fs";
2
+ import * as path from "path";
3
+ import * as yaml from "js-yaml";
4
+
5
+ /**
6
+ * A union type representing valid JSON-like values.
7
+ * - Primitives: string, number, boolean, null
8
+ * - Objects: { [key: string]: JsonValue }
9
+ * - Arrays: JsonValue[]
10
+ */
11
+ export type JsonValue =
12
+ | string
13
+ | number
14
+ | boolean
15
+ | null
16
+ | { [key: string]: JsonValue }
17
+ | JsonValue[];
18
+
19
+ /**
20
+ * Reads a file and parses its contents as JSON or YAML depending on the file extension.
21
+ *
22
+ * - If the extension is ".json", parses with JSON.parse.
23
+ * - If the extension is ".yaml" or ".yml", parses with js-yaml's YAML loader.
24
+ * - For any other extension, throws an error indicating unsupported extension.
25
+ *
26
+ * @template T - The expected return type (defaults to JsonValue).
27
+ * @param {string} filePath - The path to the file to read.
28
+ * @returns {Promise<T>} The parsed value typed as T.
29
+ * @throws {Error} If the file cannot be read, the extension is unsupported, or parsing fails.
30
+ *
31
+ * Edge cases:
32
+ * - If the YAML/JSON content does not match the requested T, a runtime cast is performed.
33
+ * - If the file does not exist, an error is thrown by the underlying readFile call.
34
+ */
35
+ export async function readJsonOrYaml<T = JsonValue>(
36
+ filePath: string
37
+ ): Promise<T> {
38
+ const ext = path.extname(filePath).toLowerCase();
39
+
40
+ try {
41
+ const data = await fs.readFile(filePath, "utf-8");
42
+ if (ext === ".json") {
43
+ return JSON.parse(data) as T;
44
+ } else if (ext === ".yaml" || ext === ".yml") {
45
+ return yaml.load(data) as T;
46
+ } else {
47
+ throw new Error(
48
+ `Unsupported file extension: ${ext}. Only .json, .yaml, .yml are supported.`
49
+ );
50
+ }
51
+ } catch (err) {
52
+ throw new Error(`Failed to read ${filePath}: ${(err as Error).message}`);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Serializes data to JSON or YAML and writes it to disk, based on the file extension.
58
+ *
59
+ * - If the extension is ".json", the data is serialized with JSON.stringify (pretty-printed with 2 spaces).
60
+ * - If the extension is ".yaml" or ".yml", the data is serialized with js-yaml's dump.
61
+ * - For any other extension, throws an error indicating unsupported extension.
62
+ *
63
+ * @template T - Type of the data to write (defaults to JsonValue).
64
+ * @param {string} filePath - The path to write the file to.
65
+ * @param {T} data - The data to serialize and write.
66
+ * @returns {Promise<void>} Resolves when the write completes.
67
+ * @throws {Error} If the extension is unsupported or the write fails.
68
+ */
69
+ export async function writeJsonOrYaml<T = JsonValue>(
70
+ filePath: string,
71
+ data: T
72
+ ): Promise<void> {
73
+ const ext = path.extname(filePath).toLowerCase();
74
+
75
+ try {
76
+ const serialized =
77
+ ext === ".json"
78
+ ? JSON.stringify(data, null, 2)
79
+ : ext === ".yaml" || ext === ".yml"
80
+ ? yaml.dump(data)
81
+ : null;
82
+
83
+ if (serialized === null) {
84
+ throw new Error(
85
+ `Unsupported file extension: ${ext}. Only .json, .yaml, .yml are supported.`
86
+ );
87
+ }
88
+
89
+ await fs.writeFile(filePath, serialized, "utf-8");
90
+ } catch (err) {
91
+ throw new Error(`Failed to write ${filePath}: ${(err as Error).message}`);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Updates a top-level key in a JSON/YAML file. If the file does not exist, starts with an empty object.
97
+ *
98
+ * - Loads existing data from the file (JSON or YAML) if present and valid.
99
+ * - If the file does not exist, initializes data as an empty object.
100
+ * - Sets data[key] = value and saves the updated object back to disk.
101
+ * - If the existing content is not an object, it will be replaced with a new object.
102
+ *
103
+ * @template K extends string - The key type (string literal type or string).
104
+ * @template V extends JsonValue - The value to assign to the key.
105
+ * @param {string} filePath - The path to the file to update.
106
+ * @param {K} key - The key to set on the root object.
107
+ * @param {V} value - The value to assign to the key.
108
+ * @returns {Promise<void>} Resolves when the update/write completes.
109
+ * @throws {Error} If reading the file fails with an unsupported extension, or writing fails.
110
+ *
111
+ * Notes:
112
+ * - This function operates at the top level only (not nested paths).
113
+ */
114
+ export async function updateJsonOrYaml<K extends string, V extends JsonValue>(
115
+ filePath: string,
116
+ key: K,
117
+ value: V
118
+ ): Promise<void> {
119
+ const ext = path.extname(filePath).toLowerCase();
120
+
121
+ // Load existing data
122
+ let data: any;
123
+ try {
124
+ if (ext === ".json" || ext === ".yaml" || ext === ".yml") {
125
+ data = await readJsonOrYaml<any>(filePath);
126
+ } else {
127
+ throw new Error(`Unsupported file extension: ${ext}.`);
128
+ }
129
+ } catch (err) {
130
+ // If file doesn't exist, start with an empty object
131
+ if (
132
+ (err as Error).message.includes("Failed to read") &&
133
+ (await fileExists(filePath)) === false
134
+ ) {
135
+ data = {};
136
+ } else {
137
+ throw err;
138
+ }
139
+ }
140
+
141
+ // Ensure we have an object to update
142
+ if (typeof data !== "object" || data === null) {
143
+ data = {} as Record<string, any>;
144
+ }
145
+
146
+ // Update the key
147
+ data[key] = value;
148
+
149
+ // Save back
150
+ await writeJsonOrYaml(filePath, data);
151
+ }
152
+
153
+ /**
154
+ * Helper to check if a file exists.
155
+ *
156
+ * @param {string} filePath - The path to check.
157
+ * @returns {Promise<boolean>} True if the file exists, false otherwise.
158
+ */
159
+ async function fileExists(filePath: string): Promise<boolean> {
160
+ try {
161
+ await fs.access(filePath);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
@@ -0,0 +1 @@
1
+ export * from "./file-helpers";
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { parallelMap } from "./parallel/parallel";
2
+ import { parallelMap } from "../parallel/parallel";
3
3
 
4
4
  /**
5
5
  * Strictly assign obj (type T1) to T2 using a Zod schema.
@@ -13,7 +13,7 @@ import { parallelMap } from "./parallel/parallel";
13
13
  * @param schema - Zod schema describing the target type T2
14
14
  * @returns T2 representing the destination after assignment
15
15
  */
16
- export default async function assign<T1 extends object, T2 extends object>(
16
+ export async function assign<T1 extends object, T2 extends object>(
17
17
  destination: T2,
18
18
  source: T1,
19
19
  destinationSchema?: z.ZodObject<any>
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Compare two values for deep equality using parallel, recursive checks.
3
+ *
4
+ * This function performs a structural equality check between two values.
5
+ * It handles primitives, null, Date, RegExp, Arrays, and plain objects.
6
+ * Object properties are checked in parallel via Promise.all for potential
7
+ * performance benefits on large objects.
8
+ *
9
+ * Notes:
10
+ * - For objects, keys must match exactly; values are compared recursively.
11
+ * - For Arrays, element order and values must match.
12
+ * - For Dates, times (getTime) must be identical.
13
+ * - For RegExp, string representations (via toString) must match.
14
+ * - Functions are compared by reference (i.e., a === b) since they cannot be
15
+ * meaningfully “deep-equal” compared here.
16
+ * - If you enable a cycle-detection mechanism, circular references are handled
17
+ * by tracking seen pairs to avoid infinite recursion.
18
+ *
19
+ * @template T - Type of the first value (and, by structural typing, the second).
20
+ * @param a - The first value to compare.
21
+ * @param b - The second value to compare.
22
+ * @returns A Promise that resolves to true if `a` and `b` are deeply equal, otherwise false.
23
+ *
24
+ * @example
25
+ * true: simple primitives
26
+ * await equal(1, 1); // true
27
+ *
28
+ * @example
29
+ * true: identical objects
30
+ * await equal({ x: 1, y: [2, 3] }, { x: 1, y: [2, 3] }); // true
31
+ *
32
+ * @example
33
+ * false: different structure
34
+ * await equal({ a: 1 }, { a: 1, b: 2 }); // false
35
+ *
36
+ * @example
37
+ * false: different array contents
38
+ * await equal([1, 2], [1, 3]); // false
39
+ */
40
+ export async function equal<T>(
41
+ a: T,
42
+ b: T,
43
+ _seen = new WeakMap<object, WeakMap<object, boolean>>()
44
+ ): Promise<boolean> {
45
+ if (a === b) return true;
46
+ if (typeof a !== typeof b) return false;
47
+ if (a === null || b === null) return a === b;
48
+
49
+ const ta = typeof a;
50
+ if (ta !== "object") return a === b;
51
+
52
+ // Cycle detection for objects
53
+ const A = a as object;
54
+ const B = b as object;
55
+ if (_seen.has(A)) {
56
+ const inner = _seen.get(A)!;
57
+ if (inner.has(B as object)) return inner.get(B as object)!;
58
+ } else {
59
+ _seen.set(A, new WeakMap<object, boolean>());
60
+ }
61
+
62
+ // Update trace
63
+ _seen.get(A)!.set(B as object, true);
64
+
65
+ if (a instanceof Date && b instanceof Date)
66
+ return a.getTime() === b.getTime();
67
+ if (a instanceof RegExp && b instanceof RegExp)
68
+ return a.toString() === b.toString();
69
+
70
+ if (Array.isArray(a) && Array.isArray(b)) {
71
+ if (a.length !== (b as any).length) return false;
72
+ const checks = a.map((_, i) => equal((a as any)[i], (b as any)[i], _seen));
73
+ const results = await Promise.all(checks);
74
+ return results.every(Boolean);
75
+ }
76
+
77
+ const aKeys = Object.keys(a as Object);
78
+ const bKeys = Object.keys(b as Object);
79
+ if (aKeys.length !== bKeys.length) return false;
80
+ const keySet = new Set<string>(aKeys);
81
+ for (const k of bKeys) if (!keySet.has(k)) return false;
82
+
83
+ const checks = aKeys.map((k) => equal((a as any)[k], (b as any)[k], _seen));
84
+ const results = await Promise.all(checks);
85
+ return results.every(Boolean);
86
+ }
@@ -1,5 +1,5 @@
1
1
  import { z, ZodRawShape } from "zod";
2
- import { parallelMap } from "./parallel/parallel";
2
+ import { parallelMap } from "../parallel/parallel";
3
3
 
4
4
  /**
5
5
  * Helper: normalize and validate a numeric string for integer parsing.
@@ -11,7 +11,7 @@ function parseIntegerStrict(input: string): number {
11
11
 
12
12
  // Empty or just sign is invalid
13
13
  if (s.length === 0 || s === "+" || s === "-") {
14
- throw new TypeError("Invalid integer");
14
+ throw new TypeError("Invalid sign");
15
15
  }
16
16
 
17
17
  // Use a regex to ensure the entire string is an optional sign followed by digits