@squiz/optimization-utils 7.1.2 → 7.2.0-rc1

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 (183) hide show
  1. package/dist/cjs/array/__tests__/chunkify.test.d.ts +2 -0
  2. package/dist/cjs/array/__tests__/chunkify.test.d.ts.map +1 -0
  3. package/dist/{array → cjs/array}/chunkify.d.ts +1 -0
  4. package/dist/cjs/array/chunkify.d.ts.map +1 -0
  5. package/dist/cjs/array/chunkify.js.map +1 -0
  6. package/dist/cjs/array/index.d.ts +3 -0
  7. package/dist/cjs/array/index.d.ts.map +1 -0
  8. package/dist/cjs/array/index.js.map +1 -0
  9. package/dist/{array → cjs/array}/unique.d.ts +1 -0
  10. package/dist/cjs/array/unique.d.ts.map +1 -0
  11. package/dist/cjs/array/unique.js.map +1 -0
  12. package/dist/{change-tracker → cjs/change-tracker}/ChangeTracker.d.ts +1 -0
  13. package/dist/cjs/change-tracker/ChangeTracker.d.ts.map +1 -0
  14. package/dist/cjs/change-tracker/ChangeTracker.js.map +1 -0
  15. package/dist/{data-structures → cjs/data-structures}/Result.d.ts +1 -0
  16. package/dist/cjs/data-structures/Result.d.ts.map +1 -0
  17. package/dist/cjs/data-structures/Result.js.map +1 -0
  18. package/dist/cjs/data-structures/index.d.ts +2 -0
  19. package/dist/cjs/data-structures/index.d.ts.map +1 -0
  20. package/dist/cjs/data-structures/index.js.map +1 -0
  21. package/dist/cjs/index.d.ts +14 -0
  22. package/dist/cjs/index.d.ts.map +1 -0
  23. package/dist/cjs/index.js.map +1 -0
  24. package/dist/{logging → cjs/logging}/Log.d.ts +1 -0
  25. package/dist/cjs/logging/Log.d.ts.map +1 -0
  26. package/dist/cjs/logging/Log.js.map +1 -0
  27. package/dist/{measurement → cjs/measurement}/performance.d.ts +1 -0
  28. package/dist/cjs/measurement/performance.d.ts.map +1 -0
  29. package/dist/cjs/measurement/performance.js.map +1 -0
  30. package/dist/{metadata → cjs/metadata}/CopyReflection.d.ts +1 -0
  31. package/dist/cjs/metadata/CopyReflection.d.ts.map +1 -0
  32. package/dist/cjs/metadata/CopyReflection.js.map +1 -0
  33. package/dist/{object → cjs/object}/getProperty.d.ts +1 -0
  34. package/dist/cjs/object/getProperty.d.ts.map +1 -0
  35. package/dist/cjs/object/getProperty.js.map +1 -0
  36. package/dist/cjs/object/index.d.ts +2 -0
  37. package/dist/cjs/object/index.d.ts.map +1 -0
  38. package/dist/cjs/object/index.js.map +1 -0
  39. package/dist/cjs/package.json +3 -0
  40. package/dist/{promises → cjs/promises}/PromiseMaxConcurrency.d.ts +1 -0
  41. package/dist/cjs/promises/PromiseMaxConcurrency.d.ts.map +1 -0
  42. package/dist/cjs/promises/PromiseMaxConcurrency.js.map +1 -0
  43. package/dist/{queue → cjs/queue}/LRUQueue.d.ts +1 -0
  44. package/dist/cjs/queue/LRUQueue.d.ts.map +1 -0
  45. package/dist/cjs/queue/LRUQueue.js.map +1 -0
  46. package/dist/cjs/queue/LRUQueueFactory.d.ts +5 -0
  47. package/dist/cjs/queue/LRUQueueFactory.d.ts.map +1 -0
  48. package/dist/cjs/queue/LRUQueueFactory.js.map +1 -0
  49. package/dist/{retries → cjs/retries}/Retries.d.ts +1 -0
  50. package/dist/cjs/retries/Retries.d.ts.map +1 -0
  51. package/dist/cjs/retries/Retries.js.map +1 -0
  52. package/dist/{types → cjs/types}/class-utilities-types.d.ts +1 -0
  53. package/dist/cjs/types/class-utilities-types.d.ts.map +1 -0
  54. package/dist/cjs/types/class-utilities-types.js.map +1 -0
  55. package/dist/cjs/types/index.d.ts +2 -0
  56. package/dist/cjs/types/index.d.ts.map +1 -0
  57. package/dist/cjs/types/index.js.map +1 -0
  58. package/dist/mjs/array/__tests__/chunkify.test.d.ts +2 -0
  59. package/dist/mjs/array/__tests__/chunkify.test.d.ts.map +1 -0
  60. package/dist/mjs/array/__tests__/chunkify.test.js +50 -0
  61. package/dist/mjs/array/__tests__/chunkify.test.js.map +7 -0
  62. package/dist/mjs/array/chunkify.d.ts +3 -0
  63. package/dist/mjs/array/chunkify.d.ts.map +1 -0
  64. package/dist/mjs/array/chunkify.js +16 -0
  65. package/dist/mjs/array/chunkify.js.map +7 -0
  66. package/dist/mjs/array/index.d.ts +3 -0
  67. package/dist/mjs/array/index.d.ts.map +1 -0
  68. package/dist/mjs/array/index.js +3 -0
  69. package/dist/mjs/array/index.js.map +7 -0
  70. package/dist/mjs/array/unique.d.ts +3 -0
  71. package/dist/mjs/array/unique.d.ts.map +1 -0
  72. package/dist/mjs/array/unique.js +9 -0
  73. package/dist/mjs/array/unique.js.map +7 -0
  74. package/dist/mjs/change-tracker/ChangeTracker.d.ts +13 -0
  75. package/dist/mjs/change-tracker/ChangeTracker.d.ts.map +1 -0
  76. package/dist/mjs/change-tracker/ChangeTracker.js +44 -0
  77. package/dist/mjs/change-tracker/ChangeTracker.js.map +7 -0
  78. package/dist/mjs/data-structures/Result.d.ts +50 -0
  79. package/dist/mjs/data-structures/Result.d.ts.map +1 -0
  80. package/dist/mjs/data-structures/Result.js +84 -0
  81. package/dist/mjs/data-structures/Result.js.map +7 -0
  82. package/dist/mjs/data-structures/index.d.ts +2 -0
  83. package/dist/mjs/data-structures/index.d.ts.map +1 -0
  84. package/dist/mjs/data-structures/index.js +2 -0
  85. package/dist/mjs/data-structures/index.js.map +7 -0
  86. package/dist/mjs/index.d.ts +14 -0
  87. package/dist/mjs/index.d.ts.map +1 -0
  88. package/dist/mjs/index.js +14 -0
  89. package/dist/mjs/index.js.map +7 -0
  90. package/dist/mjs/logging/Log.d.ts +19 -0
  91. package/dist/mjs/logging/Log.d.ts.map +1 -0
  92. package/dist/mjs/logging/Log.js +103 -0
  93. package/dist/mjs/logging/Log.js.map +7 -0
  94. package/dist/mjs/measurement/performance.d.ts +6 -0
  95. package/dist/mjs/measurement/performance.d.ts.map +1 -0
  96. package/dist/mjs/measurement/performance.js +50 -0
  97. package/dist/mjs/measurement/performance.js.map +7 -0
  98. package/dist/mjs/metadata/CopyReflection.d.ts +6 -0
  99. package/dist/mjs/metadata/CopyReflection.d.ts.map +1 -0
  100. package/dist/mjs/metadata/CopyReflection.js +21 -0
  101. package/dist/mjs/metadata/CopyReflection.js.map +7 -0
  102. package/dist/mjs/object/getProperty.d.ts +2 -0
  103. package/dist/mjs/object/getProperty.d.ts.map +1 -0
  104. package/dist/mjs/object/getProperty.js +12 -0
  105. package/dist/mjs/object/getProperty.js.map +7 -0
  106. package/dist/mjs/object/index.d.ts +2 -0
  107. package/dist/mjs/object/index.d.ts.map +1 -0
  108. package/dist/mjs/object/index.js +2 -0
  109. package/dist/mjs/object/index.js.map +7 -0
  110. package/dist/mjs/promises/PromiseMaxConcurrency.d.ts +25 -0
  111. package/dist/mjs/promises/PromiseMaxConcurrency.d.ts.map +1 -0
  112. package/dist/mjs/promises/PromiseMaxConcurrency.js +63 -0
  113. package/dist/mjs/promises/PromiseMaxConcurrency.js.map +7 -0
  114. package/dist/mjs/queue/LRUQueue.d.ts +32 -0
  115. package/dist/mjs/queue/LRUQueue.d.ts.map +1 -0
  116. package/dist/mjs/queue/LRUQueue.js +154 -0
  117. package/dist/mjs/queue/LRUQueue.js.map +7 -0
  118. package/dist/mjs/queue/LRUQueueFactory.d.ts +5 -0
  119. package/dist/mjs/queue/LRUQueueFactory.d.ts.map +1 -0
  120. package/dist/mjs/queue/LRUQueueFactory.js +20 -0
  121. package/dist/mjs/queue/LRUQueueFactory.js.map +7 -0
  122. package/dist/mjs/retries/Retries.d.ts +6 -0
  123. package/dist/mjs/retries/Retries.d.ts.map +1 -0
  124. package/dist/mjs/retries/Retries.js +61 -0
  125. package/dist/mjs/retries/Retries.js.map +7 -0
  126. package/dist/mjs/types/class-utilities-types.d.ts +10 -0
  127. package/dist/mjs/types/class-utilities-types.d.ts.map +1 -0
  128. package/dist/mjs/types/class-utilities-types.js +1 -0
  129. package/dist/mjs/types/class-utilities-types.js.map +7 -0
  130. package/dist/mjs/types/index.d.ts +2 -0
  131. package/dist/mjs/types/index.d.ts.map +1 -0
  132. package/dist/mjs/types/index.js +2 -0
  133. package/dist/mjs/types/index.js.map +7 -0
  134. package/package.json +55 -20
  135. package/scripts/build.mjs +96 -0
  136. package/tsconfig-cjs.json +15 -0
  137. package/tsconfig-mjs.json +24 -0
  138. package/dist/array/__tests__/chunkify.test.d.ts +0 -1
  139. package/dist/array/__tests__/chunkify.test.js +0 -52
  140. package/dist/array/__tests__/chunkify.test.js.map +0 -1
  141. package/dist/array/chunkify.js.map +0 -1
  142. package/dist/array/index.d.ts +0 -2
  143. package/dist/array/index.js.map +0 -1
  144. package/dist/array/unique.js.map +0 -1
  145. package/dist/change-tracker/ChangeTracker.js.map +0 -1
  146. package/dist/data-structures/Result.js.map +0 -1
  147. package/dist/data-structures/index.d.ts +0 -1
  148. package/dist/data-structures/index.js.map +0 -1
  149. package/dist/index.d.ts +0 -13
  150. package/dist/index.js.map +0 -1
  151. package/dist/logging/Log.js.map +0 -1
  152. package/dist/measurement/performance.js.map +0 -1
  153. package/dist/metadata/CopyReflection.js.map +0 -1
  154. package/dist/object/getProperty.js.map +0 -1
  155. package/dist/object/index.d.ts +0 -1
  156. package/dist/object/index.js.map +0 -1
  157. package/dist/promises/PromiseMaxConcurrency.js.map +0 -1
  158. package/dist/queue/LRUQueue.js.map +0 -1
  159. package/dist/queue/LRUQueueFactory.d.ts +0 -4
  160. package/dist/queue/LRUQueueFactory.js.map +0 -1
  161. package/dist/retries/Retries.js.map +0 -1
  162. package/dist/types/class-utilities-types.js.map +0 -1
  163. package/dist/types/index.d.ts +0 -1
  164. package/dist/types/index.js.map +0 -1
  165. package/tsconfig-package.json +0 -8
  166. /package/dist/{array → cjs/array}/chunkify.js +0 -0
  167. /package/dist/{array → cjs/array}/index.js +0 -0
  168. /package/dist/{array → cjs/array}/unique.js +0 -0
  169. /package/dist/{change-tracker → cjs/change-tracker}/ChangeTracker.js +0 -0
  170. /package/dist/{data-structures → cjs/data-structures}/Result.js +0 -0
  171. /package/dist/{data-structures → cjs/data-structures}/index.js +0 -0
  172. /package/dist/{index.js → cjs/index.js} +0 -0
  173. /package/dist/{logging → cjs/logging}/Log.js +0 -0
  174. /package/dist/{measurement → cjs/measurement}/performance.js +0 -0
  175. /package/dist/{metadata → cjs/metadata}/CopyReflection.js +0 -0
  176. /package/dist/{object → cjs/object}/getProperty.js +0 -0
  177. /package/dist/{object → cjs/object}/index.js +0 -0
  178. /package/dist/{promises → cjs/promises}/PromiseMaxConcurrency.js +0 -0
  179. /package/dist/{queue → cjs/queue}/LRUQueue.js +0 -0
  180. /package/dist/{queue → cjs/queue}/LRUQueueFactory.js +0 -0
  181. /package/dist/{retries → cjs/retries}/Retries.js +0 -0
  182. /package/dist/{types → cjs/types}/class-utilities-types.js +0 -0
  183. /package/dist/{types → cjs/types}/index.js +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/promises/PromiseMaxConcurrency.ts"],
4
+ "sourcesContent": ["import { injectable } from 'inversify';\n\nexport type MaxConcurrencyOptions = {\n maxConcurrency: number;\n};\n\n/**\n * A utility class that executes promises with controlled concurrency.\n * This class helps to limit the number of promises running simultaneously,\n * preventing potential memory or performance issues when dealing with large arrays.\n */\n@injectable()\nexport class PromiseMaxConcurrency<T> {\n private options: MaxConcurrencyOptions;\n\n /**\n * Creates a new instance of PromiseMaxConcurrency.\n * @param items - Array of items to process\n * @param options - Configuration options for controlling concurrency\n * @param options.maxConcurrency - Maximum number of promises to run simultaneously (defaults to 3)\n */\n constructor(\n private readonly items: Array<T>,\n options?: MaxConcurrencyOptions,\n ) {\n this.options = options ?? {\n maxConcurrency: 3,\n };\n }\n\n async handle<R>(\n handler: (value: T, index: number, array: Array<T>) => Promise<R>,\n ): Promise<Array<R>> {\n let promises: Array<Promise<R>> = [];\n const results: Array<R> = [];\n const maxConcurrency = this.options.maxConcurrency;\n\n for (let i = 0; i < this.items.length; i++) {\n promises.push(handler(this.items[i], i, this.items));\n\n if (promises.length === maxConcurrency) {\n const result = await Promise.all(promises);\n\n results.push(...result);\n promises = [];\n }\n }\n\n const result = await Promise.all(promises);\n\n results.push(...result);\n\n return results;\n }\n}\n\n@injectable()\nexport class PromiseMaxConcurrencyFactory {\n constructor() {}\n\n create<T>(\n items: Array<T>,\n options?: MaxConcurrencyOptions,\n ): PromiseMaxConcurrency<T> {\n return new PromiseMaxConcurrency(items, options);\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAAA,SAAS,kBAAkB;AAYpB,IAAM,wBAAN,MAAMA,uBAAqB;EAUb;EATX;;;;;;;EAQR,YACmB,OACjB,SAA+B;AADd,SAAA,QAAA;AAGjB,SAAK,UAAU,WAAW;MACxB,gBAAgB;;EAEpB;EAEA,MAAM,OACJ,SAAiE;AAEjE,QAAI,WAA8B,CAAA;AAClC,UAAM,UAAoB,CAAA;AAC1B,UAAM,iBAAiB,KAAK,QAAQ;AAEpC,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,eAAS,KAAK,QAAQ,KAAK,MAAM,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC;AAEnD,UAAI,SAAS,WAAW,gBAAgB;AACtC,cAAMC,UAAS,MAAM,QAAQ,IAAI,QAAQ;AAEzC,gBAAQ,KAAK,GAAGA,OAAM;AACtB,mBAAW,CAAA;MACb;IACF;AAEA,UAAM,SAAS,MAAM,QAAQ,IAAI,QAAQ;AAEzC,YAAQ,KAAK,GAAG,MAAM;AAEtB,WAAO;EACT;;AAzCW,wBAAqB,WAAA;EADjC,WAAU;uDAWiB,UAAK,eAAL,WAAK,aAAA,KAAA,QAAA,MAAA,CAAA;GAVpB,qBAAqB;AA6C3B,IAAM,+BAAN,MAAMC,8BAA4B;EACvC,cAAA;EAAe;EAEf,OACE,OACA,SAA+B;AAE/B,WAAO,IAAI,sBAAsB,OAAO,OAAO;EACjD;;AARW,+BAA4B,WAAA;EADxC,WAAU;;GACE,4BAA4B;",
6
+ "names": ["PromiseMaxConcurrency", "result", "PromiseMaxConcurrencyFactory"]
7
+ }
@@ -0,0 +1,32 @@
1
+ export type LRUQueueSetOptions<T> = {
2
+ keySelector: (value: T) => string;
3
+ /** Defaults to JSON.stringify when omitted. */
4
+ serialize?: (value: T) => string;
5
+ /** Defaults to JSON.parse when omitted. */
6
+ deserialize?: (payload: string) => T;
7
+ };
8
+ /**
9
+ * Least Recently Used (LRU) queue implementation using SQLite.
10
+ */
11
+ export declare class LRUQueue<T> implements Disposable {
12
+ private readonly db;
13
+ private readonly keySelector;
14
+ private readonly serialize;
15
+ private readonly deserialize;
16
+ private readonly insertQueue;
17
+ private readonly selectOldestN;
18
+ private readonly markFetchedByKey;
19
+ private readonly selectPeekOldest;
20
+ private readonly selectPeekNewest;
21
+ private readonly selectCount;
22
+ get size(): number;
23
+ constructor({ keySelector, serialize, deserialize }: LRUQueueSetOptions<T>);
24
+ enqueue(...values: Array<T>): number;
25
+ dequeue(count?: number): Array<T>;
26
+ peekOldest(): T | undefined;
27
+ peekNewest(): T | undefined;
28
+ clone(): LRUQueue<T>;
29
+ toFile(filePath?: string): string;
30
+ [Symbol.dispose](): void;
31
+ }
32
+ //# sourceMappingURL=LRUQueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LRUQueue.d.ts","sourceRoot":"","sources":["../../../src/queue/LRUQueue.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI;IAClC,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IAClC,+CAA+C;IAC/C,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,2CAA2C;IAC3C,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,CAAC,CAAC;CACtC,CAAC;AAEF;;GAEG;AACH,qBAAa,QAAQ,CAAC,CAAC,CAAE,YAAW,UAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuB;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IAErD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAE5C,IAAI,IAAI,IAAI,MAAM,CAIjB;gBAEW,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IA4C1E,OAAO,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM;IA0BpC,OAAO,CAAC,KAAK,SAAI,GAAG,KAAK,CAAC,CAAC,CAAC;IAmC5B,UAAU,IAAI,CAAC,GAAG,SAAS;IAM3B,UAAU,IAAI,CAAC,GAAG,SAAS;IAM3B,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC;IAoCpB,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAWjC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB"}
@@ -0,0 +1,154 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as path from "node:path";
3
+ import { resolve } from "node:path";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ class LRUQueue {
6
+ db;
7
+ keySelector;
8
+ serialize;
9
+ deserialize;
10
+ insertQueue;
11
+ selectOldestN;
12
+ markFetchedByKey;
13
+ selectPeekOldest;
14
+ selectPeekNewest;
15
+ selectCount;
16
+ get size() {
17
+ const row = this.selectCount.get();
18
+ return row?.c ?? 0;
19
+ }
20
+ constructor({ keySelector, serialize, deserialize }) {
21
+ this.db = new DatabaseSync(":memory:");
22
+ this.db.exec(`
23
+ PRAGMA journal_mode = MEMORY;
24
+ PRAGMA synchronous = OFF;
25
+
26
+ CREATE TABLE queue (
27
+ item_key TEXT PRIMARY KEY,
28
+ payload TEXT NOT NULL,
29
+ fetched INTEGER NOT NULL DEFAULT 0
30
+ CHECK (fetched IN (0, 1))
31
+ );
32
+
33
+ CREATE INDEX idx_queue_pending ON queue (fetched);
34
+ `);
35
+ this.keySelector = keySelector;
36
+ this.serialize = serialize ?? ((value) => JSON.stringify(value));
37
+ this.deserialize = deserialize ?? ((payload) => JSON.parse(payload));
38
+ this.insertQueue = this.db.prepare(
39
+ "INSERT OR IGNORE INTO queue (item_key, payload) VALUES (?, ?)"
40
+ );
41
+ this.selectOldestN = this.db.prepare(
42
+ `SELECT item_key, payload FROM queue
43
+ WHERE fetched = 0 ORDER BY rowid ASC LIMIT ?`
44
+ );
45
+ this.markFetchedByKey = this.db.prepare(
46
+ "UPDATE queue SET fetched = 1 WHERE item_key = ? AND fetched = 0"
47
+ );
48
+ this.selectPeekOldest = this.db.prepare(
49
+ `SELECT payload FROM queue
50
+ WHERE fetched = 0 ORDER BY rowid ASC LIMIT 1`
51
+ );
52
+ this.selectPeekNewest = this.db.prepare(
53
+ `SELECT payload FROM queue
54
+ WHERE fetched = 0 ORDER BY rowid DESC LIMIT 1`
55
+ );
56
+ this.selectCount = this.db.prepare(
57
+ "SELECT COUNT(*) AS c FROM queue WHERE fetched = 0"
58
+ );
59
+ }
60
+ enqueue(...values) {
61
+ let added = 0;
62
+ this.db.exec("BEGIN IMMEDIATE");
63
+ try {
64
+ for (const value of values) {
65
+ const key = this.keySelector(value);
66
+ const payload = this.serialize(value);
67
+ const insertResult = this.insertQueue.run(key, payload);
68
+ if (insertResult.changes === 0) {
69
+ continue;
70
+ }
71
+ added++;
72
+ }
73
+ this.db.exec("COMMIT");
74
+ } catch (error) {
75
+ this.db.exec("ROLLBACK");
76
+ throw error;
77
+ }
78
+ return added;
79
+ }
80
+ dequeue(count = 1) {
81
+ if (!Number.isInteger(count) || count < 0) {
82
+ throw new Error("count must be a non-negative integer");
83
+ }
84
+ if (count === 0) {
85
+ return [];
86
+ }
87
+ this.db.exec("BEGIN IMMEDIATE");
88
+ try {
89
+ const rows = this.selectOldestN.all(count);
90
+ if (rows.length === 0) {
91
+ this.db.exec("COMMIT");
92
+ return [];
93
+ }
94
+ for (const row of rows) {
95
+ this.markFetchedByKey.run(row.item_key);
96
+ }
97
+ this.db.exec("COMMIT");
98
+ return rows.map((row) => this.deserialize(row.payload));
99
+ } catch (error) {
100
+ this.db.exec("ROLLBACK");
101
+ throw error;
102
+ }
103
+ }
104
+ peekOldest() {
105
+ const row = this.selectPeekOldest.get();
106
+ return row ? this.deserialize(row.payload) : void 0;
107
+ }
108
+ peekNewest() {
109
+ const row = this.selectPeekNewest.get();
110
+ return row ? this.deserialize(row.payload) : void 0;
111
+ }
112
+ clone() {
113
+ const cloned = new LRUQueue({
114
+ keySelector: this.keySelector,
115
+ serialize: this.serialize,
116
+ deserialize: this.deserialize
117
+ });
118
+ const rows = this.db.prepare(
119
+ "SELECT item_key, payload, fetched FROM queue ORDER BY rowid ASC"
120
+ ).all();
121
+ if (rows.length === 0) {
122
+ return cloned;
123
+ }
124
+ const insert = cloned.db.prepare(
125
+ "INSERT INTO queue (item_key, payload, fetched) VALUES (?, ?, ?)"
126
+ );
127
+ cloned.db.exec("BEGIN IMMEDIATE");
128
+ try {
129
+ for (const row of rows) {
130
+ insert.run(row.item_key, row.payload, row.fetched);
131
+ }
132
+ cloned.db.exec("COMMIT");
133
+ } catch (error) {
134
+ cloned.db.exec("ROLLBACK");
135
+ throw error;
136
+ }
137
+ return cloned;
138
+ }
139
+ toFile(filePath) {
140
+ const absolutePath = resolve(
141
+ filePath ?? path.join(process.cwd(), `${randomUUID()}.sqlite`)
142
+ );
143
+ const escaped = absolutePath.replace(/'/g, `''`);
144
+ this.db.exec(`VACUUM INTO '${escaped}'`);
145
+ return absolutePath;
146
+ }
147
+ [Symbol.dispose]() {
148
+ this.db[Symbol.dispose]();
149
+ }
150
+ }
151
+ export {
152
+ LRUQueue
153
+ };
154
+ //# sourceMappingURL=LRUQueue.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/queue/LRUQueue.ts"],
4
+ "sourcesContent": ["import { randomUUID } from 'node:crypto';\nimport * as path from 'node:path';\nimport { resolve } from 'node:path';\nimport { DatabaseSync, StatementSync } from 'node:sqlite';\n\nexport type LRUQueueSetOptions<T> = {\n keySelector: (value: T) => string;\n /** Defaults to JSON.stringify when omitted. */\n serialize?: (value: T) => string;\n /** Defaults to JSON.parse when omitted. */\n deserialize?: (payload: string) => T;\n};\n\n/**\n * Least Recently Used (LRU) queue implementation using SQLite.\n */\nexport class LRUQueue<T> implements Disposable {\n private readonly db: DatabaseSync;\n private readonly keySelector: (value: T) => string;\n private readonly serialize: (value: T) => string;\n private readonly deserialize: (payload: string) => T;\n\n private readonly insertQueue: StatementSync;\n private readonly selectOldestN: StatementSync;\n private readonly markFetchedByKey: StatementSync;\n private readonly selectPeekOldest: StatementSync;\n private readonly selectPeekNewest: StatementSync;\n private readonly selectCount: StatementSync;\n\n get size(): number {\n const row = this.selectCount.get() as { c: number } | undefined;\n\n return row?.c ?? 0;\n }\n\n constructor({ keySelector, serialize, deserialize }: LRUQueueSetOptions<T>) {\n this.db = new DatabaseSync(':memory:');\n this.db.exec(`\n PRAGMA journal_mode = MEMORY;\n PRAGMA synchronous = OFF;\n\n CREATE TABLE queue (\n item_key TEXT PRIMARY KEY,\n payload TEXT NOT NULL,\n fetched INTEGER NOT NULL DEFAULT 0\n CHECK (fetched IN (0, 1))\n );\n\n CREATE INDEX idx_queue_pending ON queue (fetched);\n `);\n\n this.keySelector = keySelector;\n this.serialize = serialize ?? ((value: T): string => JSON.stringify(value));\n this.deserialize =\n deserialize ?? ((payload: string): T => JSON.parse(payload) as T);\n\n this.insertQueue = this.db.prepare(\n 'INSERT OR IGNORE INTO queue (item_key, payload) VALUES (?, ?)',\n );\n this.selectOldestN = this.db.prepare(\n `SELECT item_key, payload FROM queue\n WHERE fetched = 0 ORDER BY rowid ASC LIMIT ?`,\n );\n this.markFetchedByKey = this.db.prepare(\n 'UPDATE queue SET fetched = 1 WHERE item_key = ? AND fetched = 0',\n );\n this.selectPeekOldest = this.db.prepare(\n `SELECT payload FROM queue\n WHERE fetched = 0 ORDER BY rowid ASC LIMIT 1`,\n );\n this.selectPeekNewest = this.db.prepare(\n `SELECT payload FROM queue\n WHERE fetched = 0 ORDER BY rowid DESC LIMIT 1`,\n );\n this.selectCount = this.db.prepare(\n 'SELECT COUNT(*) AS c FROM queue WHERE fetched = 0',\n );\n }\n\n enqueue(...values: Array<T>): number {\n let added = 0;\n\n this.db.exec('BEGIN IMMEDIATE');\n try {\n for (const value of values) {\n const key = this.keySelector(value);\n const payload = this.serialize(value);\n const insertResult = this.insertQueue.run(key, payload);\n\n if (insertResult.changes === 0) {\n continue;\n }\n\n added++;\n }\n\n this.db.exec('COMMIT');\n } catch (error) {\n this.db.exec('ROLLBACK');\n throw error;\n }\n\n return added;\n }\n\n dequeue(count = 1): Array<T> {\n if (!Number.isInteger(count) || count < 0) {\n throw new Error('count must be a non-negative integer');\n }\n\n if (count === 0) {\n return [];\n }\n\n this.db.exec('BEGIN IMMEDIATE');\n try {\n const rows = this.selectOldestN.all(count) as Array<{\n item_key: string;\n payload: string;\n }>;\n\n if (rows.length === 0) {\n this.db.exec('COMMIT');\n\n return [];\n }\n\n for (const row of rows) {\n this.markFetchedByKey.run(row.item_key);\n }\n\n this.db.exec('COMMIT');\n\n return rows.map((row) => this.deserialize(row.payload));\n } catch (error) {\n this.db.exec('ROLLBACK');\n throw error;\n }\n }\n\n peekOldest(): T | undefined {\n const row = this.selectPeekOldest.get() as { payload: string } | undefined;\n\n return row ? this.deserialize(row.payload) : undefined;\n }\n\n peekNewest(): T | undefined {\n const row = this.selectPeekNewest.get() as { payload: string } | undefined;\n\n return row ? this.deserialize(row.payload) : undefined;\n }\n\n clone(): LRUQueue<T> {\n const cloned = new LRUQueue<T>({\n keySelector: this.keySelector,\n serialize: this.serialize,\n deserialize: this.deserialize,\n });\n\n const rows = this.db\n .prepare(\n 'SELECT item_key, payload, fetched FROM queue ORDER BY rowid ASC',\n )\n .all() as Array<{ item_key: string; payload: string; fetched: number }>;\n\n if (rows.length === 0) {\n return cloned;\n }\n\n const insert = cloned.db.prepare(\n 'INSERT INTO queue (item_key, payload, fetched) VALUES (?, ?, ?)',\n );\n\n cloned.db.exec('BEGIN IMMEDIATE');\n try {\n for (const row of rows) {\n insert.run(row.item_key, row.payload, row.fetched);\n }\n\n cloned.db.exec('COMMIT');\n } catch (error) {\n cloned.db.exec('ROLLBACK');\n throw error;\n }\n\n return cloned;\n }\n\n toFile(filePath?: string): string {\n const absolutePath = resolve(\n filePath ?? path.join(process.cwd(), `${randomUUID()}.sqlite`),\n );\n const escaped = absolutePath.replace(/'/g, `''`);\n\n this.db.exec(`VACUUM INTO '${escaped}'`);\n\n return absolutePath;\n }\n\n [Symbol.dispose](): void {\n this.db[Symbol.dispose]();\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,5 @@
1
+ import { LRUQueue, LRUQueueSetOptions } from './LRUQueue.js';
2
+ export declare class LRUQueueFactory {
3
+ create<T>(options: LRUQueueSetOptions<T>): LRUQueue<T>;
4
+ }
5
+ //# sourceMappingURL=LRUQueueFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LRUQueueFactory.d.ts","sourceRoot":"","sources":["../../../src/queue/LRUQueueFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAG1D,qBACa,eAAe;IAC1B,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;CAGvD"}
@@ -0,0 +1,20 @@
1
+ var __decorate = function(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LRUQueue } from "./LRUQueue.js";
8
+ import { injectable } from "inversify";
9
+ let LRUQueueFactory = class LRUQueueFactory2 {
10
+ create(options) {
11
+ return new LRUQueue(options);
12
+ }
13
+ };
14
+ LRUQueueFactory = __decorate([
15
+ injectable()
16
+ ], LRUQueueFactory);
17
+ export {
18
+ LRUQueueFactory
19
+ };
20
+ //# sourceMappingURL=LRUQueueFactory.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/queue/LRUQueueFactory.ts"],
4
+ "sourcesContent": ["import { LRUQueue, LRUQueueSetOptions } from './LRUQueue';\nimport { injectable } from 'inversify';\n\n@injectable()\nexport class LRUQueueFactory {\n create<T>(options: LRUQueueSetOptions<T>): LRUQueue<T> {\n return new LRUQueue(options);\n }\n}\n"],
5
+ "mappings": ";;;;;;AAAA,SAAS,gBAAoC;AAC7C,SAAS,kBAAkB;AAGpB,IAAM,kBAAN,MAAMA,iBAAe;EAC1B,OAAU,SAA8B;AACtC,WAAO,IAAI,SAAS,OAAO;EAC7B;;AAHW,kBAAe,WAAA;EAD3B,WAAU;GACE,eAAe;",
6
+ "names": ["LRUQueueFactory"]
7
+ }
@@ -0,0 +1,6 @@
1
+ export declare function Retry({ delay, retries, retryableErrors, }: {
2
+ retries: number;
3
+ delay: number;
4
+ retryableErrors: Array<new (...args: Array<any>) => Error>;
5
+ }): MethodDecorator;
6
+ //# sourceMappingURL=Retries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Retries.d.ts","sourceRoot":"","sources":["../../../src/retries/Retries.ts"],"names":[],"mappings":"AAKA,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IAEd,eAAe,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC;CAC5D,GAAG,eAAe,CAgElB"}
@@ -0,0 +1,61 @@
1
+ import { setTimeout } from "timers/promises";
2
+ import { inject } from "inversify";
3
+ import { createLogger, Logger } from "@squiz/optimization-logger";
4
+ import { CopyReflection } from "../metadata/CopyReflection.js";
5
+ function Retry({
6
+ delay,
7
+ retries,
8
+ retryableErrors
9
+ }) {
10
+ return (target, propertyKey, descriptor) => {
11
+ const injection = inject(Logger);
12
+ const loggerSymbol = /* @__PURE__ */ Symbol("logger");
13
+ injection(target, loggerSymbol);
14
+ const originalMethod = descriptor.value;
15
+ descriptor.value = async function(...args) {
16
+ const logger = this[loggerSymbol] || createLogger();
17
+ let attempt = 0;
18
+ while (attempt <= retries) {
19
+ try {
20
+ return await originalMethod.apply(this, args);
21
+ } catch (error) {
22
+ logger.debug({
23
+ message: `Attempt ${attempt} of ${retries}`
24
+ });
25
+ attempt++;
26
+ if (!retryableErrors.some((errType) => error instanceof errType)) {
27
+ logger.error({
28
+ message: `Non-retryable error encountered`,
29
+ error
30
+ });
31
+ throw error;
32
+ }
33
+ const isMaxRetriesReached = attempt > retries;
34
+ if (isMaxRetriesReached) {
35
+ logger.error({
36
+ message: `Max retries (${retries}) reached`,
37
+ error
38
+ });
39
+ throw error;
40
+ }
41
+ const _delay = delay * Math.pow(2, attempt - 1);
42
+ logger.debug({
43
+ message: `Retrying in ${_delay}ms`,
44
+ attempt,
45
+ delay: _delay
46
+ });
47
+ await setTimeout(_delay);
48
+ }
49
+ }
50
+ };
51
+ CopyReflection.copyPropertyMetadata(
52
+ originalMethod,
53
+ descriptor.value,
54
+ propertyKey
55
+ );
56
+ };
57
+ }
58
+ export {
59
+ Retry
60
+ };
61
+ //# sourceMappingURL=Retries.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/retries/Retries.ts"],
4
+ "sourcesContent": ["import { setTimeout } from 'timers/promises';\nimport { inject } from 'inversify';\nimport { createLogger, Logger } from '@squiz/optimization-logger';\nimport { CopyReflection } from '../metadata/CopyReflection';\n\nexport function Retry({\n delay,\n retries,\n retryableErrors,\n}: {\n retries: number;\n delay: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n retryableErrors: Array<new (...args: Array<any>) => Error>;\n}): MethodDecorator {\n return (target, propertyKey, descriptor: PropertyDescriptor): void => {\n const injection = inject(Logger);\n const loggerSymbol = Symbol('logger');\n\n injection(target, loggerSymbol);\n\n const originalMethod = descriptor.value;\n\n descriptor.value = async function (\n ...args: Array<unknown>\n ): Promise<unknown> {\n const logger =\n (this as { [loggerSymbol]: Logger | undefined })[loggerSymbol] ||\n createLogger();\n\n let attempt = 0;\n\n while (attempt <= retries) {\n try {\n return await originalMethod.apply(this, args);\n } catch (error) {\n logger.debug({\n message: `Attempt ${attempt} of ${retries}`,\n });\n attempt++;\n\n if (!retryableErrors.some((errType) => error instanceof errType)) {\n logger.error({\n message: `Non-retryable error encountered`,\n error,\n });\n throw error;\n }\n\n const isMaxRetriesReached = attempt > retries;\n\n if (isMaxRetriesReached) {\n logger.error({\n message: `Max retries (${retries}) reached`,\n error,\n });\n throw error;\n }\n\n const _delay = delay * Math.pow(2, attempt - 1);\n\n logger.debug({\n message: `Retrying in ${_delay}ms`,\n attempt,\n delay: _delay,\n });\n\n await setTimeout(_delay);\n }\n }\n };\n\n CopyReflection.copyPropertyMetadata(\n originalMethod,\n descriptor.value,\n propertyKey,\n );\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,cAAc,cAAc;AACrC,SAAS,sBAAsB;AAExB,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAKoB;AAClB,SAAO,CAAC,QAAQ,aAAa,eAAyC;AACpE,UAAM,YAAY,OAAO,MAAM;AAC/B,UAAM,eAAe,uBAAO,QAAQ;AAEpC,cAAU,QAAQ,YAAY;AAE9B,UAAM,iBAAiB,WAAW;AAElC,eAAW,QAAQ,kBACd,MACe;AAClB,YAAM,SACH,KAAgD,YAAY,KAC7D,aAAa;AAEf,UAAI,UAAU;AAEd,aAAO,WAAW,SAAS;AACzB,YAAI;AACF,iBAAO,MAAM,eAAe,MAAM,MAAM,IAAI;AAAA,QAC9C,SAAS,OAAO;AACd,iBAAO,MAAM;AAAA,YACX,SAAS,WAAW,OAAO,OAAO,OAAO;AAAA,UAC3C,CAAC;AACD;AAEA,cAAI,CAAC,gBAAgB,KAAK,CAAC,YAAY,iBAAiB,OAAO,GAAG;AAChE,mBAAO,MAAM;AAAA,cACX,SAAS;AAAA,cACT;AAAA,YACF,CAAC;AACD,kBAAM;AAAA,UACR;AAEA,gBAAM,sBAAsB,UAAU;AAEtC,cAAI,qBAAqB;AACvB,mBAAO,MAAM;AAAA,cACX,SAAS,gBAAgB,OAAO;AAAA,cAChC;AAAA,YACF,CAAC;AACD,kBAAM;AAAA,UACR;AAEA,gBAAM,SAAS,QAAQ,KAAK,IAAI,GAAG,UAAU,CAAC;AAE9C,iBAAO,MAAM;AAAA,YACX,SAAS,eAAe,MAAM;AAAA,YAC9B;AAAA,YACA,OAAO;AAAA,UACT,CAAC;AAED,gBAAM,WAAW,MAAM;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,mBAAe;AAAA,MACb;AAAA,MACA,WAAW;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ type MethodKeysOrNever<T> = {
2
+ [K in keyof T]: T[K] extends (...args: Array<unknown>) => unknown ? K : never;
3
+ };
4
+ export type MethodKeys<T> = MethodKeysOrNever<T>[keyof T];
5
+ export type ClassFields<T> = Omit<T, Exclude<MethodKeys<T>, undefined>>;
6
+ export type OverwriteValueOf<TInstance, DValueOfReturn> = Omit<TInstance, 'valueOf'> & {
7
+ valueOf(): DValueOfReturn;
8
+ };
9
+ export {};
10
+ //# sourceMappingURL=class-utilities-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class-utilities-types.d.ts","sourceRoot":"","sources":["../../../src/types/class-utilities-types.ts"],"names":[],"mappings":"AAAA,KAAK,iBAAiB,CAAC,CAAC,IAAI;KACzB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,CAAC,GAAG,KAAK;CAC9E,CAAC;AACF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAC1D,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;AACxE,MAAM,MAAM,gBAAgB,CAAC,SAAS,EAAE,cAAc,IAAI,IAAI,CAC5D,SAAS,EACT,SAAS,CACV,GAAG;IACF,OAAO,IAAI,cAAc,CAAC;CAC3B,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=class-utilities-types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from './class-utilities-types.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./class-utilities-types.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/types/index.ts"],
4
+ "sourcesContent": ["export * from './class-utilities-types';\n"],
5
+ "mappings": "AAAA,cAAc;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,42 +1,77 @@
1
1
  {
2
2
  "name": "@squiz/optimization-utils",
3
- "version": "7.1.2",
3
+ "version": "7.2.0-rc1",
4
4
  "description": "",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "repository": "https://gitlab.squiz.net/optimization/optimization-utils",
8
- "scripts": {
9
- "build": "tsc -p ./tsconfig-package.json",
10
- "build:check": "tsc --noEmit",
11
- "exports:generate": "opti-build-utils exports:generate --config ../../generate-barrels.json && prettier -w ./src/index.ts"
12
- },
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "module": "./dist/mjs/index.js",
8
+ "types": "./dist/mjs/index.d.ts",
13
9
  "exports": {
14
10
  ".": {
15
- "default": "./dist/index.js"
11
+ "import": {
12
+ "types": "./dist/mjs/index.d.ts",
13
+ "default": "./dist/mjs/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/cjs/index.d.ts",
17
+ "default": "./dist/cjs/index.js"
18
+ }
16
19
  },
17
20
  "./array": {
18
- "default": "./dist/array/index.js",
19
- "types": "./dist/array/index.d.ts"
21
+ "import": {
22
+ "types": "./dist/mjs/array/index.d.ts",
23
+ "default": "./dist/mjs/array/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/cjs/array/index.d.ts",
27
+ "default": "./dist/cjs/array/index.js"
28
+ }
20
29
  },
21
30
  "./object": {
22
- "default": "./dist/object/index.js",
23
- "types": "./dist/object/index.d.ts"
31
+ "import": {
32
+ "types": "./dist/mjs/object/index.d.ts",
33
+ "default": "./dist/mjs/object/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/cjs/object/index.d.ts",
37
+ "default": "./dist/cjs/object/index.js"
38
+ }
24
39
  },
25
40
  "./data-structures": {
26
- "default": "./dist/data-structures/index.js",
27
- "types": "./dist/data-structures/index.d.ts"
41
+ "import": {
42
+ "types": "./dist/mjs/data-structures/index.d.ts",
43
+ "default": "./dist/mjs/data-structures/index.js"
44
+ },
45
+ "require": {
46
+ "types": "./dist/cjs/data-structures/index.d.ts",
47
+ "default": "./dist/cjs/data-structures/index.js"
48
+ }
28
49
  },
29
50
  "./types": {
30
- "default": "./dist/types/index.js",
31
- "types": "./dist/types/index.d.ts"
51
+ "import": {
52
+ "types": "./dist/mjs/types/index.d.ts",
53
+ "default": "./dist/mjs/types/index.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/cjs/types/index.d.ts",
57
+ "default": "./dist/cjs/types/index.js"
58
+ }
32
59
  }
33
60
  },
61
+ "repository": "https://gitlab.squiz.net/optimization/optimization-utils",
62
+ "scripts": {
63
+ "build": "npm run build:cjs && npm run build:mjs",
64
+ "build:check": "tsc --noEmit",
65
+ "exports:generate": "opti-build-utils exports:generate --config ../../generate-barrels.json && prettier -w ./src/index.ts",
66
+ "build:cjs": "tsc -p ./tsconfig-cjs.json",
67
+ "build:mjs": "tsc -p ./tsconfig-mjs.json && node ./scripts/build.mjs"
68
+ },
34
69
  "dependencies": {},
35
- "devDependencies": {},
36
70
  "peerDependencies": {
37
- "inversify": "^7.11.0",
71
+ "inversify": "^8.1.0",
38
72
  "@squiz/optimization-logger": "^1.11.4"
39
73
  },
74
+ "devDependencies": {},
40
75
  "author": "",
41
76
  "license": "ISC"
42
77
  }
@@ -0,0 +1,96 @@
1
+ import { build } from 'esbuild';
2
+ import esbuildPluginTsc from 'esbuild-plugin-tsc';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import * as fsPromises from 'node:fs/promises';
6
+
7
+ const packageRoot = path.resolve(import.meta.dirname, '..');
8
+ const tsconfigMjsPath = path.join(packageRoot, 'tsconfig-mjs.json');
9
+ const mjsOutDir = path.join(packageRoot, 'dist/mjs');
10
+ const cjsOutDir = path.join(packageRoot, 'dist/cjs');
11
+
12
+ const entryPoints = fs
13
+ .globSync('src/**/*.ts', {
14
+ cwd: packageRoot,
15
+ exclude: (file) => file.endsWith('.spec.ts') || file.endsWith('.d.ts'),
16
+ })
17
+ .map((file) => path.join(packageRoot, file));
18
+
19
+ await build({
20
+ entryPoints,
21
+ outdir: mjsOutDir,
22
+ outbase: path.join(packageRoot, 'src'),
23
+ platform: 'node',
24
+ target: 'node22',
25
+ format: 'esm',
26
+ sourcemap: true,
27
+ plugins: [esbuildPluginTsc({ tsconfigPath: tsconfigMjsPath })],
28
+ tsconfig: tsconfigMjsPath,
29
+ packages: 'external',
30
+ });
31
+
32
+ // Add .js extension to relative imports for native ESM and Node16/NodeNext type resolution.
33
+ // Resolve directory imports ("./foo") to "./foo/index.js" when applicable.
34
+ const relativeImportRegex =
35
+ /(\bfrom\s+["']|\bimport\s*\(\s*["'])(\.{1,2}\/[^"']*?)(["'])/g;
36
+ const filesToRewrite = fs.globSync('**/*.{js,d.ts}', { cwd: mjsOutDir });
37
+
38
+ function resolveImport(fromFile, importPath) {
39
+ if (/\.(?:m?js|json|d\.ts)$/.test(importPath)) {
40
+ return importPath;
41
+ }
42
+ const absoluteFrom = path.dirname(fromFile);
43
+ const candidateFile = path.resolve(absoluteFrom, `${importPath}.js`);
44
+ if (fs.existsSync(candidateFile)) {
45
+ return `${importPath}.js`;
46
+ }
47
+ const candidateDir = path.resolve(absoluteFrom, importPath);
48
+ if (
49
+ fs.existsSync(candidateDir) &&
50
+ fs.statSync(candidateDir).isDirectory() &&
51
+ fs.existsSync(path.join(candidateDir, 'index.js'))
52
+ ) {
53
+ const sep = importPath.endsWith('/') ? '' : '/';
54
+ return `${importPath}${sep}index.js`;
55
+ }
56
+ return `${importPath}.js`;
57
+ }
58
+
59
+ await Promise.all(
60
+ filesToRewrite.map(async (file) => {
61
+ const filePath = path.join(mjsOutDir, file);
62
+ const content = await fsPromises.readFile(filePath, 'utf8');
63
+ const updated = content.replace(
64
+ relativeImportRegex,
65
+ (match, prefix, importPath, suffix) => {
66
+ const resolved = resolveImport(filePath, importPath);
67
+ if (resolved === importPath) {
68
+ return match;
69
+ }
70
+ return `${prefix}${resolved}${suffix}`;
71
+ },
72
+ );
73
+ if (updated !== content) {
74
+ await fsPromises.writeFile(filePath, updated, 'utf8');
75
+ }
76
+ }),
77
+ );
78
+
79
+ // Mark dist/cjs as commonjs (root package.json declares "type": "module")
80
+ if (fs.existsSync(cjsOutDir)) {
81
+ await fsPromises.writeFile(
82
+ path.join(cjsOutDir, 'package.json'),
83
+ `${JSON.stringify({ type: 'commonjs' }, null, 2)}\n`,
84
+ );
85
+
86
+ // Copy .d.ts (and source maps) from dist/mjs so CJS consumers also get types
87
+ const typeFiles = fs.globSync('**/*.{d.ts,d.ts.map}', { cwd: mjsOutDir });
88
+ await Promise.all(
89
+ typeFiles.map(async (file) => {
90
+ const src = path.join(mjsOutDir, file);
91
+ const dst = path.join(cjsOutDir, file);
92
+ await fsPromises.mkdir(path.dirname(dst), { recursive: true });
93
+ await fsPromises.copyFile(src, dst);
94
+ }),
95
+ );
96
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "CommonJS",
5
+ "moduleResolution": "Bundler",
6
+ "verbatimModuleSyntax": false,
7
+ "ignoreDeprecations": "6.0",
8
+ "outDir": "dist/cjs",
9
+ "rootDir": "./src",
10
+ "declaration": false,
11
+ "types": ["*"]
12
+ },
13
+ "include": ["src"],
14
+ "exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"]
15
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "verbatimModuleSyntax": false,
7
+ "outDir": "dist/mjs",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "emitDeclarationOnly": true,
12
+ "types": [
13
+ "*"
14
+ ]
15
+ },
16
+ "include": [
17
+ "src"
18
+ ],
19
+ "exclude": [
20
+ "dist",
21
+ "**/*.spec.ts",
22
+ "**/*.test.ts"
23
+ ]
24
+ }
@@ -1 +0,0 @@
1
- export {};