@decohere/core 0.0.1-dev.125 → 0.0.1-dev.5

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 (252) hide show
  1. package/package.json +40 -15
  2. package/src/caching/cache-manager.ts +102 -0
  3. package/src/caching/cache.decorator.ts +56 -0
  4. package/src/caching/cache.ts +11 -0
  5. package/{es6/src/caching/index.js → src/caching/index.ts} +1 -1
  6. package/src/caching/invalidate-cache.decorator.ts +81 -0
  7. package/src/caching/memoize.decorator.ts +34 -0
  8. package/src/caching/node-cache-memoize.adapter.ts +47 -0
  9. package/src/cli/action.ts +77 -0
  10. package/src/cli/command.ts +173 -0
  11. package/src/cli/index.ts +30 -0
  12. package/src/cli/module.type.ts +48 -0
  13. package/src/cli/options.ts +81 -0
  14. package/src/collections/find-duplicates.ts +16 -0
  15. package/src/collections/merge-arrays.ts +9 -0
  16. package/src/collections/sum.ts +71 -0
  17. package/src/collections/unique-objects.ts +23 -0
  18. package/src/composition/index.ts +48 -0
  19. package/src/configuration/configuration.types.ts +13 -0
  20. package/src/diagnostics/index.ts +1 -0
  21. package/src/diagnostics/trace.ts +97 -0
  22. package/{es6/src/extensibility/decorator-factory-base.js → src/extensibility/decorator-factory-base.ts} +17 -9
  23. package/src/extensibility/decorator-factory.ts +44 -0
  24. package/src/extensibility/extension.ts +34 -0
  25. package/src/extensibility/mixwith.ts +42 -0
  26. package/src/extensibility/pipeline.ts +60 -0
  27. package/{types/src/extensibility/step.d.ts → src/extensibility/step.ts} +6 -4
  28. package/src/interop/docker/docker.ts +57 -0
  29. package/src/interop/errors/interop.error.ts +1 -0
  30. package/src/interop/git/git.ts +98 -0
  31. package/src/interop/git/git.types.ts +17 -0
  32. package/src/interop/index.ts +22 -0
  33. package/src/interop/node/index.ts +1 -0
  34. package/src/interop/node/node.ts +38 -0
  35. package/src/interop/npm/npm.ts +168 -0
  36. package/src/interop/npm/npm.types.ts +28 -0
  37. package/src/interop/pnpm/pnpm.ts +22 -0
  38. package/src/interop/pnpm/pnpm.types.ts +3 -0
  39. package/src/interop/shell/shell.argv.ts +31 -0
  40. package/src/interop/shell/shell.ts +100 -0
  41. package/src/interop/shell/shell.types.ts +9 -0
  42. package/src/interop/types/detect-package-manager.ts +45 -0
  43. package/src/interop/types/package-manager.ts +7 -0
  44. package/src/interop/yarn/yarn.ts +28 -0
  45. package/src/interop/yarn/yarn.types.ts +3 -0
  46. package/src/io/copy-file.ts +13 -0
  47. package/src/io/delete-file.ts +4 -0
  48. package/src/io/directory-exists.ts +9 -0
  49. package/src/io/file-exists.ts +10 -0
  50. package/src/io/find-directories.ts +13 -0
  51. package/src/io/find-file.ts +37 -0
  52. package/{es6/src/io/io.js → src/io/io.ts} +3 -0
  53. package/src/io/read-file.ts +25 -0
  54. package/src/io/remove-dir.ts +11 -0
  55. package/src/io/types/directory.type.ts +4 -0
  56. package/src/io/types/file.ts +34 -0
  57. package/src/io/url-to-path.ts +17 -0
  58. package/src/io/watch.ts +290 -0
  59. package/src/io/write-file.ts +21 -0
  60. package/src/objects/deep-merge.ts +131 -0
  61. package/{es6/src/reflection/index.js → src/reflection/index.ts} +1 -0
  62. package/src/reflection/types/reflection.types.ts +28 -0
  63. package/src/reflection/utilities.ts +92 -0
  64. package/src/semver/index.ts +1 -0
  65. package/src/semver/semver.ts +96 -0
  66. package/src/system/camel-case.ts +3 -0
  67. package/src/system/debounce.ts +13 -0
  68. package/src/system/decorator.ts +41 -0
  69. package/src/system/environment.ts +6 -0
  70. package/{types/src/system/function.types.d.ts → src/system/function.types.ts} +10 -2
  71. package/src/system/retry.decorator.ts +43 -0
  72. package/src/system/sleep.ts +6 -0
  73. package/src/system/type.utils.ts +5 -0
  74. package/src/testing/flush-promises.ts +7 -0
  75. package/src/testing/glob-with-memfs.ts +25 -0
  76. package/src/testing/index.ts +5 -0
  77. package/src/testing/memfs.ts +28 -0
  78. package/src/testing/mock-logger.ts +42 -0
  79. package/src/testing/spy-on-constructor.ts +33 -0
  80. package/tests/caching/cache-manager.spec.ts +147 -0
  81. package/tests/caching/cache-test-class.ts +91 -0
  82. package/tests/caching/cache.decorator.spec.ts +259 -0
  83. package/tests/caching/invalidate-cache-test-class.ts +52 -0
  84. package/tests/caching/invalidate-cache.decorator.spec.ts +120 -0
  85. package/tests/caching/memoize-test-class.ts +99 -0
  86. package/tests/caching/memoize.decorator.spec.ts +203 -0
  87. package/tests/cli/command.model.spec.ts +91 -0
  88. package/tests/collections/merge-arrays.spec.ts +50 -0
  89. package/tests/collections/sum.spec.ts +86 -0
  90. package/tests/collections/unique-objects.spec.ts +233 -0
  91. package/tests/extensibility/extension.spec.ts +29 -0
  92. package/tests/extensibility/pipeline.spec.ts +379 -0
  93. package/tests/interop/detect-package-manager.spec.ts +70 -0
  94. package/tests/interop/npm.spec.ts +129 -0
  95. package/tests/interop/pnpm.spec.ts +30 -0
  96. package/tests/interop/shell.spec.ts +47 -0
  97. package/tests/interop/yarn.spec.ts +18 -0
  98. package/tests/io/find-file.spec.ts +188 -0
  99. package/tests/io/watch.real-fs.spec.ts +76 -0
  100. package/tests/io/watch.spec.ts +291 -0
  101. package/tests/mock-prototype.ts +12 -0
  102. package/tests/semver/semver.spec.ts +51 -0
  103. package/tests/system/decorator.spec.ts +250 -0
  104. package/tests/system/retry.decorator.spec.ts +57 -0
  105. package/LICENSE +0 -15
  106. package/commonjs/src/caching/cache-manager.js +0 -98
  107. package/commonjs/src/caching/cache.decorator.js +0 -69
  108. package/commonjs/src/caching/cache.js +0 -6
  109. package/commonjs/src/caching/index.js +0 -23
  110. package/commonjs/src/caching/invalidate-cache.decorator.js +0 -92
  111. package/commonjs/src/caching/map-cache.js +0 -39
  112. package/commonjs/src/caching/memoize.decorator.js +0 -36
  113. package/commonjs/src/collections/collections.js +0 -21
  114. package/commonjs/src/collections/find-duplicates.js +0 -35
  115. package/commonjs/src/collections/index.js +0 -52
  116. package/commonjs/src/collections/merge-arrays.js +0 -34
  117. package/commonjs/src/collections/sum.js +0 -63
  118. package/commonjs/src/collections/unique-objects.js +0 -39
  119. package/commonjs/src/composition/index.js +0 -4
  120. package/commonjs/src/configuration/configuration.types.js +0 -11
  121. package/commonjs/src/configuration/index.js +0 -18
  122. package/commonjs/src/extensibility/decorator-factory-base.js +0 -22
  123. package/commonjs/src/extensibility/decorator-factory.js +0 -31
  124. package/commonjs/src/extensibility/extension.js +0 -37
  125. package/commonjs/src/extensibility/index.js +0 -23
  126. package/commonjs/src/extensibility/mixwith.js +0 -36
  127. package/commonjs/src/extensibility/pipeline.js +0 -56
  128. package/commonjs/src/extensibility/step.js +0 -18
  129. package/commonjs/src/io/copy-file.js +0 -25
  130. package/commonjs/src/io/delete-file.js +0 -21
  131. package/commonjs/src/io/directory-exists.js +0 -24
  132. package/commonjs/src/io/file-exists.js +0 -19
  133. package/commonjs/src/io/index.js +0 -66
  134. package/commonjs/src/io/io.js +0 -25
  135. package/commonjs/src/io/read-file.js +0 -39
  136. package/commonjs/src/io/remove-dir.js +0 -22
  137. package/commonjs/src/io/types/directory.type.js +0 -4
  138. package/commonjs/src/io/types/file.js +0 -28
  139. package/commonjs/src/io/types/index.js +0 -19
  140. package/commonjs/src/io/url-to-path.js +0 -33
  141. package/commonjs/src/io/write-file.js +0 -30
  142. package/commonjs/src/objects/deep-merge.js +0 -67
  143. package/commonjs/src/objects/index.js +0 -11
  144. package/commonjs/src/reflection/index.js +0 -20
  145. package/commonjs/src/reflection/types/index.js +0 -18
  146. package/commonjs/src/reflection/types/reflection.types.js +0 -26
  147. package/commonjs/src/reflection/utilities.js +0 -118
  148. package/commonjs/src/system/camel-case.js +0 -13
  149. package/commonjs/src/system/debounce.js +0 -19
  150. package/commonjs/src/system/decorator.js +0 -46
  151. package/commonjs/src/system/environment.js +0 -23
  152. package/commonjs/src/system/function.types.js +0 -18
  153. package/commonjs/src/system/index.js +0 -24
  154. package/commonjs/src/system/retry.decorator.js +0 -40
  155. package/commonjs/src/system/sleep.js +0 -13
  156. package/commonjs/src/system/type.utils.js +0 -4
  157. package/es6/src/caching/cache-manager.js +0 -82
  158. package/es6/src/caching/cache.decorator.js +0 -51
  159. package/es6/src/caching/cache.js +0 -3
  160. package/es6/src/caching/invalidate-cache.decorator.js +0 -71
  161. package/es6/src/caching/map-cache.js +0 -35
  162. package/es6/src/caching/memoize.decorator.js +0 -27
  163. package/es6/src/collections/find-duplicates.js +0 -17
  164. package/es6/src/collections/merge-arrays.js +0 -16
  165. package/es6/src/collections/sum.js +0 -45
  166. package/es6/src/collections/unique-objects.js +0 -21
  167. package/es6/src/composition/index.js +0 -1
  168. package/es6/src/configuration/configuration.types.js +0 -1
  169. package/es6/src/extensibility/decorator-factory.js +0 -21
  170. package/es6/src/extensibility/extension.js +0 -27
  171. package/es6/src/extensibility/mixwith.js +0 -26
  172. package/es6/src/extensibility/pipeline.js +0 -46
  173. package/es6/src/extensibility/step.js +0 -16
  174. package/es6/src/io/copy-file.js +0 -10
  175. package/es6/src/io/delete-file.js +0 -6
  176. package/es6/src/io/directory-exists.js +0 -9
  177. package/es6/src/io/file-exists.js +0 -9
  178. package/es6/src/io/read-file.js +0 -13
  179. package/es6/src/io/remove-dir.js +0 -7
  180. package/es6/src/io/types/directory.type.js +0 -1
  181. package/es6/src/io/types/file.js +0 -18
  182. package/es6/src/io/url-to-path.js +0 -15
  183. package/es6/src/io/write-file.js +0 -15
  184. package/es6/src/objects/deep-merge.js +0 -57
  185. package/es6/src/reflection/types/reflection.types.js +0 -8
  186. package/es6/src/reflection/utilities.js +0 -74
  187. package/es6/src/system/camel-case.js +0 -3
  188. package/es6/src/system/debounce.js +0 -9
  189. package/es6/src/system/decorator.js +0 -30
  190. package/es6/src/system/environment.js +0 -5
  191. package/es6/src/system/function.types.js +0 -26
  192. package/es6/src/system/retry.decorator.js +0 -30
  193. package/es6/src/system/sleep.js +0 -3
  194. package/es6/src/system/type.utils.js +0 -1
  195. package/types/src/caching/cache-manager.d.ts +0 -59
  196. package/types/src/caching/cache.d.ts +0 -11
  197. package/types/src/caching/cache.decorator.d.ts +0 -18
  198. package/types/src/caching/index.d.ts +0 -6
  199. package/types/src/caching/invalidate-cache.decorator.d.ts +0 -17
  200. package/types/src/caching/map-cache.d.ts +0 -23
  201. package/types/src/caching/memoize.decorator.d.ts +0 -16
  202. package/types/src/collections/collections.d.ts +0 -4
  203. package/types/src/collections/find-duplicates.d.ts +0 -2
  204. package/types/src/collections/index.d.ts +0 -1
  205. package/types/src/collections/merge-arrays.d.ts +0 -2
  206. package/types/src/collections/sum.d.ts +0 -4
  207. package/types/src/collections/unique-objects.d.ts +0 -2
  208. package/types/src/composition/index.d.ts +0 -37
  209. package/types/src/configuration/configuration.types.d.ts +0 -10
  210. package/types/src/configuration/index.d.ts +0 -1
  211. package/types/src/extensibility/decorator-factory-base.d.ts +0 -39
  212. package/types/src/extensibility/decorator-factory.d.ts +0 -19
  213. package/types/src/extensibility/extension.d.ts +0 -9
  214. package/types/src/extensibility/index.d.ts +0 -6
  215. package/types/src/extensibility/mixwith.d.ts +0 -6
  216. package/types/src/extensibility/pipeline.d.ts +0 -12
  217. package/types/src/io/copy-file.d.ts +0 -4
  218. package/types/src/io/delete-file.d.ts +0 -1
  219. package/types/src/io/directory-exists.d.ts +0 -1
  220. package/types/src/io/file-exists.d.ts +0 -1
  221. package/types/src/io/index.d.ts +0 -2
  222. package/types/src/io/io.d.ts +0 -8
  223. package/types/src/io/read-file.d.ts +0 -4
  224. package/types/src/io/remove-dir.d.ts +0 -5
  225. package/types/src/io/types/directory.type.d.ts +0 -4
  226. package/types/src/io/types/file.d.ts +0 -20
  227. package/types/src/io/types/index.d.ts +0 -2
  228. package/types/src/io/url-to-path.d.ts +0 -2
  229. package/types/src/io/write-file.d.ts +0 -5
  230. package/types/src/objects/deep-merge.d.ts +0 -41
  231. package/types/src/objects/index.d.ts +0 -1
  232. package/types/src/reflection/index.d.ts +0 -3
  233. package/types/src/reflection/types/index.d.ts +0 -1
  234. package/types/src/reflection/types/reflection.types.d.ts +0 -21
  235. package/types/src/reflection/utilities.d.ts +0 -27
  236. package/types/src/system/camel-case.d.ts +0 -1
  237. package/types/src/system/debounce.d.ts +0 -1
  238. package/types/src/system/decorator.d.ts +0 -15
  239. package/types/src/system/environment.d.ts +0 -6
  240. package/types/src/system/index.d.ts +0 -7
  241. package/types/src/system/retry.decorator.d.ts +0 -6
  242. package/types/src/system/sleep.d.ts +0 -1
  243. package/types/src/system/type.utils.d.ts +0 -1
  244. /package/{es6/src/collections/collections.js → src/collections/collections.ts} +0 -0
  245. /package/{es6/src/collections/index.js → src/collections/index.ts} +0 -0
  246. /package/{es6/src/configuration/index.js → src/configuration/index.ts} +0 -0
  247. /package/{es6/src/extensibility/index.js → src/extensibility/index.ts} +0 -0
  248. /package/{es6/src/io/index.js → src/io/index.ts} +0 -0
  249. /package/{es6/src/io/types/index.js → src/io/types/index.ts} +0 -0
  250. /package/{es6/src/objects/index.js → src/objects/index.ts} +0 -0
  251. /package/{es6/src/reflection/types/index.js → src/reflection/types/index.ts} +0 -0
  252. /package/{es6/src/system/index.js → src/system/index.ts} +0 -0
@@ -0,0 +1,28 @@
1
+ export type Constructable<T extends object = object> = {
2
+ new (...args: any[]): T;
3
+ } & Type;
4
+
5
+ export type Field = {
6
+ key: string;
7
+ propertyDescriptor: PropertyDescriptor;
8
+ };
9
+
10
+ export type FunctionType = (
11
+ ...args: unknown[]
12
+ ) => unknown | unknown[] | undefined | Promise<unknown>;
13
+ export type ReflectMeta = "paramtypes" | "type";
14
+
15
+ export const MetaDataOwners = {
16
+ Hearth: "hearth",
17
+ Reflect: "design",
18
+ } satisfies Record<string, string>;
19
+
20
+ export const ReflectionMeta = {
21
+ paramtypes: `${MetaDataOwners.Reflect}:paramtypes`,
22
+ type: `${MetaDataOwners.Reflect}:type`,
23
+ } satisfies Record<ReflectMeta, string>;
24
+
25
+ export type Type = {
26
+ prototype: object;
27
+ name?: string;
28
+ };
@@ -0,0 +1,92 @@
1
+ import {
2
+ type Constructable,
3
+ type Field,
4
+ type FunctionType,
5
+ ReflectionMeta,
6
+ type Type,
7
+ } from "./types/reflection.types.js";
8
+
9
+ const Primitives = ["string", "number", "bigint", "boolean", "undefined", "symbol", "null"];
10
+
11
+ export const isPrimitiveOrDateType = (type: Type) => {
12
+ return Primitives.includes(type.name.toLocaleLowerCase()) || isDateType(type);
13
+ };
14
+
15
+ export const isDateType = (type: Type) => {
16
+ return type?.prototype === Date.prototype;
17
+ };
18
+
19
+ export function isConstructable(
20
+ type: Constructable | object | FunctionType,
21
+ ): type is Constructable {
22
+ const proto = (type as Constructable)?.prototype;
23
+ const isConstructable = typeof type === "function" && typeof proto === "object";
24
+ return isConstructable;
25
+ }
26
+ export const isArrayType = (type: Type) => {
27
+ return type?.prototype === Array.prototype;
28
+ };
29
+ export function getMeta<T>(key: string, target: object, propertyKey?: string) {
30
+ const meta = Reflect.getMetadata(key, target, propertyKey) as T;
31
+ return meta;
32
+ }
33
+ export function getFunctions(object: Constructable) {
34
+ const functions = Object.entries(Object.getOwnPropertyDescriptors(object.prototype))
35
+ .filter(([key]) => key !== "constructor")
36
+ .map(([, value]) => value);
37
+
38
+ return functions;
39
+ }
40
+ export function getFunction(object: Constructable, functionName: string) {
41
+ const [, value] = Object.entries(Object.getOwnPropertyDescriptors(object.prototype)).find(
42
+ ([k]) => k !== "constructor" && k === functionName,
43
+ );
44
+
45
+ return value;
46
+ }
47
+ export function getFunctionMetadata<T>(key: string, target: object, func: FunctionType) {
48
+ const meta = Reflect.getMetadata(key, target, func.name) as T;
49
+ return meta;
50
+ }
51
+ export function getFunctionArgs(target: object, func: FunctionType): Type[] {
52
+ const meta = getFunctionMetadata<Type[]>(ReflectionMeta.paramtypes, target, func);
53
+ return meta;
54
+ }
55
+
56
+ /**
57
+ * @description Gets the fields from an object
58
+ * @param object
59
+ * @returns
60
+ */
61
+ export function getFields<T>(object: T) {
62
+ const fields = Object.entries(Object.getOwnPropertyDescriptors(object))
63
+ .filter(([, val]) => typeof val.value !== "function")
64
+ .map<Field>(([key, propertyDescriptor]) => ({ key, propertyDescriptor }));
65
+ return fields;
66
+ }
67
+ export function getFieldType(target: object, fieldName: string) {
68
+ return Reflect.getMetadata(ReflectionMeta.type, target, fieldName) as Type;
69
+ }
70
+ export function getArgumentMeta<T>(key: string, target: FunctionType, argIndex: number) {
71
+ const meta = Reflect.getMetadata(key, target, argIndex.toString()) as T;
72
+ return meta;
73
+ }
74
+ export function defineMeta<T>(key: string, value: T, target: object) {
75
+ Reflect.defineMetadata(key, value, target);
76
+ }
77
+ /**
78
+ * @description Facade function to define metadata for a function argument
79
+ * @param key
80
+ * @param value
81
+ * @param func
82
+ * @param argIndex
83
+ */
84
+
85
+ export function defineArgumentMeta<T>(
86
+ key: string,
87
+ value: T,
88
+ func: FunctionType,
89
+ argIndex: number,
90
+ ) {
91
+ Reflect.defineMetadata(key, value, func, argIndex.toString());
92
+ }
@@ -0,0 +1 @@
1
+ export * from "./semver.js";
@@ -0,0 +1,96 @@
1
+ export type VersionType = "major" | "minor" | "patch" | "prerelease";
2
+
3
+ export const VersionTypes = {
4
+ major: "major",
5
+ minor: "minor",
6
+ patch: "patch",
7
+ prerelease: "prerelease",
8
+ } satisfies Record<string, VersionType>;
9
+
10
+ export class SemverError extends Error {
11
+ public constructor(message: string) {
12
+ super(message);
13
+ this.name = SemverError.name;
14
+ }
15
+ }
16
+
17
+ const SEMVER_REGEX = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
18
+
19
+ type SemverParts = {
20
+ major: number;
21
+ minor: number;
22
+ patch: number;
23
+ prerelease?: string;
24
+ };
25
+
26
+ export class Semver {
27
+ private version: string;
28
+
29
+ public constructor(version: string) {
30
+ this.ensureValid(version);
31
+ this.version = version;
32
+ }
33
+
34
+ public increment(versionType: VersionType, preid = "pre"): Semver {
35
+ const { major, minor, patch, prerelease } = this.parse(this.version);
36
+
37
+ if (versionType === "major") {
38
+ this.version = `${major + 1}.0.0`;
39
+ return this;
40
+ }
41
+
42
+ if (versionType === "minor") {
43
+ this.version = `${major}.${minor + 1}.0`;
44
+ return this;
45
+ }
46
+
47
+ if (versionType === "patch") {
48
+ this.version = `${major}.${minor}.${patch + 1}`;
49
+ return this;
50
+ }
51
+
52
+ if (prerelease == null) {
53
+ this.version = `${major}.${minor}.${patch + 1}-${preid}.0`;
54
+ return this;
55
+ }
56
+
57
+ const [identifier, ...rest] = prerelease.split(".");
58
+ const maybeCounter = rest.at(-1);
59
+ const counter = maybeCounter != null ? Number.parseInt(maybeCounter, 10) : Number.NaN;
60
+ if (identifier === preid && Number.isFinite(counter)) {
61
+ const nextCounter = counter + 1;
62
+ const prefix = rest.length > 1 ? `${rest.slice(0, -1).join(".")}.` : "";
63
+ this.version = `${major}.${minor}.${patch}-${preid}.${prefix}${nextCounter}`;
64
+ return this;
65
+ }
66
+
67
+ this.version = `${major}.${minor}.${patch}-${preid}.0`;
68
+ return this;
69
+ }
70
+
71
+ public toString(): string {
72
+ return this.version;
73
+ }
74
+
75
+ private ensureValid(version: string): void {
76
+ if (SEMVER_REGEX.test(version)) {
77
+ return;
78
+ }
79
+
80
+ throw new SemverError(`Invalid semantic version: ${version}`);
81
+ }
82
+
83
+ private parse(version: string): SemverParts {
84
+ const match = version.match(SEMVER_REGEX);
85
+ if (match == null) {
86
+ throw new SemverError(`Invalid semantic version: ${version}`);
87
+ }
88
+
89
+ return {
90
+ major: Number.parseInt(match[1], 10),
91
+ minor: Number.parseInt(match[2], 10),
92
+ patch: Number.parseInt(match[3], 10),
93
+ prerelease: match[4],
94
+ };
95
+ }
96
+ }
@@ -0,0 +1,3 @@
1
+ export function lowerCamelCase(val) {
2
+ return String(val).charAt(0).toLowerCase() + String(val).slice(1);
3
+ }
@@ -0,0 +1,13 @@
1
+ export function debounce<T extends unknown[]>(
2
+ callback: (...args: T) => Promise<void>,
3
+ delay: number,
4
+ ) {
5
+ let timeoutTimer: ReturnType<typeof setTimeout>;
6
+
7
+ return (...args: T) => {
8
+ clearTimeout(timeoutTimer);
9
+ timeoutTimer = setTimeout(async () => {
10
+ await callback(...args);
11
+ }, delay);
12
+ };
13
+ }
@@ -0,0 +1,41 @@
1
+ export abstract class FunctionDecorator<TOptions = undefined> {
2
+ protected options: TOptions;
3
+ public constructor() {
4
+ this.decoratorFactory = this.decoratorFactory.bind(this);
5
+ }
6
+
7
+ public abstract decorator(
8
+ target: object,
9
+ propertyKey: string,
10
+ descriptor: PropertyDescriptor,
11
+ );
12
+
13
+ public decoratorFactory(options?: TOptions) {
14
+ this.options = options;
15
+ return this.decorator.bind(this);
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Skips the decorated method decorator if any of the named environment variables equal "true".
21
+ * Useful for disabling decorators like @cache() during unit tests or via feature flags.
22
+ *
23
+ * @example @ignoreIf("TEST", "DISABLE_CACHE")
24
+ */
25
+ export function ignoreIf(...envVars: string[]) {
26
+ return (_: object, __: string, descriptor: PropertyDescriptor) => {
27
+ const originalFunction = descriptor.value;
28
+ descriptor.value = function (target: object, key: string, descriptor: PropertyDescriptor) {
29
+ if (envVars.some((v) => process.env[v] === "true")) {
30
+ return descriptor;
31
+ }
32
+ return originalFunction.bind(this)(target, key, descriptor);
33
+ };
34
+ return descriptor;
35
+ };
36
+ }
37
+
38
+ /** @deprecated Use ignoreIf("TEST") instead */
39
+ export function ignoreDuringUnitTest() {
40
+ return ignoreIf("TEST");
41
+ }
@@ -0,0 +1,6 @@
1
+ export type Environment = "production" | "development";
2
+ export const Environments = {
3
+ production: "production",
4
+ development: "development",
5
+ } satisfies Record<string, Environment>;
6
+ export const DefaultEnvironment = Environments.development;
@@ -13,7 +13,10 @@
13
13
  * type MyAsyncFunc = Func<[], Promise<string>>;
14
14
  * ```
15
15
  */
16
- export type Func<TArgs extends readonly unknown[] = [], TReturn = void> = (...args: TArgs) => TReturn;
16
+ export type Func<TArgs extends readonly unknown[] = [], TReturn = void> = (
17
+ ...args: TArgs
18
+ ) => TReturn;
19
+
17
20
  /**
18
21
  * Async function type that returns a Promise.
19
22
  *
@@ -26,7 +29,11 @@ export type Func<TArgs extends readonly unknown[] = [], TReturn = void> = (...ar
26
29
  * type InitFunc = AsyncFunc<[], void>;
27
30
  * ```
28
31
  */
29
- export type AsyncFunc<TArgs extends readonly unknown[] = [], TReturn = void> = Func<TArgs, Promise<TReturn>>;
32
+ export type AsyncFunc<TArgs extends readonly unknown[] = [], TReturn = void> = Func<
33
+ TArgs,
34
+ Promise<TReturn>
35
+ >;
36
+
30
37
  /**
31
38
  * Predicate function type that returns a boolean.
32
39
  *
@@ -40,6 +47,7 @@ export type AsyncFunc<TArgs extends readonly unknown[] = [], TReturn = void> = F
40
47
  * ```
41
48
  */
42
49
  export type Predicate<TArgs extends readonly unknown[] = []> = Func<TArgs, boolean>;
50
+
43
51
  /**
44
52
  * Action function type that returns void.
45
53
  *
@@ -0,0 +1,43 @@
1
+ import { FunctionDecorator } from "./decorator.js";
2
+ import { sleep } from "./sleep.js";
3
+
4
+ export type RetryOptions = {
5
+ attempts?: number;
6
+ delayMs?: number;
7
+ shouldRetry?: (error: unknown) => boolean;
8
+ };
9
+
10
+ class RetryDecorator extends FunctionDecorator<RetryOptions> {
11
+ public decorator(_target: object, _propertyKey: string, descriptor: PropertyDescriptor) {
12
+ const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
13
+ const attempts = Math.max(1, this.options?.attempts ?? 3);
14
+ const delayMs = Math.max(0, this.options?.delayMs ?? 0);
15
+ const shouldRetry = this.options?.shouldRetry ?? (() => true);
16
+
17
+ descriptor.value = async function (...args: unknown[]) {
18
+ let lastError: unknown;
19
+
20
+ for (let attempt = 1; attempt <= attempts; attempt++) {
21
+ try {
22
+ return await Promise.resolve(originalMethod.apply(this, args));
23
+ } catch (error) {
24
+ lastError = error;
25
+ const shouldAttemptAgain = attempt < attempts && shouldRetry(error);
26
+ if (!shouldAttemptAgain) {
27
+ throw error;
28
+ }
29
+
30
+ if (delayMs > 0) {
31
+ await sleep(delayMs);
32
+ }
33
+ }
34
+ }
35
+
36
+ throw lastError;
37
+ };
38
+
39
+ return descriptor;
40
+ }
41
+ }
42
+
43
+ export const retry = new RetryDecorator().decoratorFactory;
@@ -0,0 +1,6 @@
1
+ export const sleep = (ms: number) =>
2
+ new Promise((resolve) =>
3
+ setTimeout(() => {
4
+ resolve(null);
5
+ }, ms),
6
+ );
@@ -0,0 +1,5 @@
1
+ export type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (
2
+ x: infer I,
3
+ ) => void
4
+ ? I
5
+ : never;
@@ -0,0 +1,7 @@
1
+ var scheduler = typeof setImmediate === "function" ? setImmediate : setTimeout;
2
+
3
+ export function flushPromises() {
4
+ return new Promise((resolve) => {
5
+ scheduler(resolve);
6
+ });
7
+ }
@@ -0,0 +1,25 @@
1
+ import { fs } from "memfs";
2
+
3
+ // Wraps a real `glob` module so all file-system access is routed through memfs.
4
+ //
5
+ // Why this exists:
6
+ // - Integration tests often need real glob matching semantics.
7
+ // - Vitest `vi.mock` factories are hoisted, so directly importing and patching
8
+ // `glob` inside helper modules can trigger initialization-order issues.
9
+ // - Passing the already-loaded `actual` module keeps the helper pure and
10
+ // hoist-safe while still allowing us to inject memfs.
11
+ export function withMemfsFsForGlob<
12
+ TGlobModule extends { glob: (...args: unknown[]) => unknown },
13
+ >(actual: TGlobModule): TGlobModule {
14
+ // Preserve all module exports, overriding only `glob` to force `{ fs: memfs }`.
15
+ const glob = ((pattern: string | string[], options?: unknown) =>
16
+ (actual.glob as (...args: unknown[]) => unknown)(pattern, {
17
+ ...(options as Record<string, unknown> | undefined),
18
+ fs,
19
+ })) as TGlobModule["glob"];
20
+
21
+ return {
22
+ ...actual,
23
+ glob,
24
+ };
25
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./flush-promises.js";
2
+ export * from "./glob-with-memfs.js";
3
+ export * from "./memfs.js";
4
+ export * from "./mock-logger.js";
5
+ export * from "./spy-on-constructor.js";
@@ -0,0 +1,28 @@
1
+ import { fs, vol } from "memfs";
2
+
3
+ export function resetMemfs(): void {
4
+ vol.reset();
5
+ }
6
+
7
+ export function seedMemfs(files: Record<string, string>): void {
8
+ vol.fromJSON(files);
9
+ }
10
+
11
+ export function createTestFile(filePath: string, content: string | Buffer): void {
12
+ fs.mkdirSync(filePath.substring(0, filePath.lastIndexOf("/")), { recursive: true });
13
+ fs.writeFileSync(filePath, content);
14
+ }
15
+
16
+ export async function readFile(filePath: string): Promise<string> {
17
+ const response = (await fs.promises.readFile(filePath, "utf8")) as string;
18
+ return response;
19
+ }
20
+
21
+ export function fileExists(filePath: string): boolean {
22
+ try {
23
+ vol.statSync(filePath);
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
@@ -0,0 +1,42 @@
1
+ import { vi } from "vitest";
2
+
3
+ export type LoggerMethod = (msg: string | number | object, ...args: unknown[]) => void;
4
+
5
+ export type TestLogger = {
6
+ fatal: LoggerMethod;
7
+ error: LoggerMethod;
8
+ warn: LoggerMethod;
9
+ info: LoggerMethod;
10
+ debug: LoggerMethod;
11
+ trace: LoggerMethod;
12
+ isLevelEnabled(level: string): boolean;
13
+ readonly currentLevel: string;
14
+ readonly logLevels: Record<string, number>;
15
+ };
16
+
17
+ const DefaultLogLevels: Record<string, number> = {
18
+ fatal: 60,
19
+ error: 50,
20
+ warn: 40,
21
+ info: 30,
22
+ debug: 20,
23
+ trace: 10,
24
+ };
25
+
26
+ export function createMockLogger(): TestLogger {
27
+ return {
28
+ error: vi.fn(),
29
+ info: vi.fn(),
30
+ warn: vi.fn(),
31
+ debug: vi.fn(),
32
+ trace: vi.fn(),
33
+ fatal: vi.fn(),
34
+ isLevelEnabled: vi.fn().mockReturnValue(true),
35
+ get currentLevel() {
36
+ return "info";
37
+ },
38
+ get logLevels() {
39
+ return DefaultLogLevels;
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,33 @@
1
+ import { vi } from "vitest";
2
+ import type { Constructable } from "../reflection/index.js";
3
+
4
+ /**
5
+ * Creates a Proxy around a class constructor that intercepts `new` calls,
6
+ * records arguments via a spy, and delegates to the real constructor.
7
+ *
8
+ * Use this to assert what was injected into a class by a DI container
9
+ * (e.g. Container) without changing actual behavior.
10
+ *
11
+ * The proxy copies all `reflect-metadata` entries from the original class
12
+ * so that DI metadata (`design:paramtypes`) remains intact.
13
+ *
14
+ * @example
15
+ * const { spy, Proxy: SpiedService } = spyOnConstructor(MyService);
16
+ * // use SpiedService in place of MyService when wiring the container
17
+ * expect(spy).toHaveBeenCalledWith(expect.any(ExpectedDependency));
18
+ */
19
+ export function spyOnConstructor<T extends Constructable>(
20
+ Target: T,
21
+ ): { spy: ReturnType<typeof vi.fn>; Proxy: T } {
22
+ const spy = vi.fn();
23
+ const ProxiedTarget = new Proxy(Target, {
24
+ construct(Ctor, args) {
25
+ spy(...args);
26
+ return Reflect.construct(Ctor, args);
27
+ },
28
+ });
29
+ for (const key of Reflect.getMetadataKeys(Target)) {
30
+ Reflect.defineMetadata(key, Reflect.getMetadata(key, Target), ProxiedTarget);
31
+ }
32
+ return { spy, Proxy: ProxiedTarget as T };
33
+ }
@@ -0,0 +1,147 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import type { ICache } from "../../src/caching/cache.js";
3
+ import { Caching } from "../../src/caching/cache-manager.js";
4
+
5
+ // Simple cache implementation for testing
6
+ class TestCache implements ICache {
7
+ public get isMemoize(): boolean {
8
+ throw new Error("Method not implemented.");
9
+ }
10
+ private data = new Map<string, unknown>();
11
+
12
+ public get(key: string): unknown {
13
+ return this.data.get(key);
14
+ }
15
+
16
+ public set(key: string, value: unknown): void {
17
+ this.data.set(key, value);
18
+ }
19
+
20
+ public has(key: string): boolean {
21
+ return this.data.has(key);
22
+ }
23
+
24
+ public del(key: string): void {
25
+ this.data.delete(key);
26
+ }
27
+
28
+ public flushAll(): void {
29
+ this.data.clear();
30
+ }
31
+ }
32
+
33
+ describe("Caching manager", () => {
34
+ afterEach(() => {
35
+ Caching.flushAllCaches();
36
+ });
37
+
38
+ describe("setCache and getCache", () => {
39
+ it("should set and get a named cache instance", () => {
40
+ const testCache = new TestCache();
41
+ Caching.setCache("TEST_CACHE", testCache);
42
+
43
+ const retrieved = Caching.getCache("TEST_CACHE");
44
+ expect(retrieved).toBe(testCache);
45
+ });
46
+
47
+ it("should create default cache if not set", () => {
48
+ const defaultCache = Caching.getCache();
49
+ expect(defaultCache).toBeDefined();
50
+
51
+ // Should return the same instance on subsequent calls
52
+ const defaultCache2 = Caching.getCache();
53
+ expect(defaultCache2).toBe(defaultCache);
54
+ });
55
+
56
+ it("should create default cache when getting by 'DEFAULT' name", () => {
57
+ const defaultCache = Caching.getCache("DEFAULT");
58
+ expect(defaultCache).toBeDefined();
59
+
60
+ const defaultCache2 = Caching.getCache();
61
+ expect(defaultCache2).toBe(defaultCache);
62
+ });
63
+
64
+ it("should maintain separate cache instances for different names", () => {
65
+ const cache1 = new TestCache();
66
+ const cache2 = new TestCache();
67
+
68
+ Caching.setCache("CACHE_1", cache1);
69
+ Caching.setCache("CACHE_2", cache2);
70
+
71
+ expect(Caching.getCache("CACHE_1")).toBe(cache1);
72
+ expect(Caching.getCache("CACHE_2")).toBe(cache2);
73
+ expect(Caching.getCache("CACHE_1")).not.toBe(cache2);
74
+ });
75
+ });
76
+
77
+ describe("hasCache", () => {
78
+ it("should return true for existing cache", () => {
79
+ const testCache = new TestCache();
80
+ Caching.setCache("TEST_CACHE", testCache);
81
+
82
+ expect(Caching.hasCache("TEST_CACHE")).toBe(true);
83
+ });
84
+
85
+ it("should return false for non-existing cache", () => {
86
+ expect(Caching.hasCache("NON_EXISTENT")).toBe(false);
87
+ });
88
+
89
+ it("should return true for default cache after first access", () => {
90
+ Caching.getCache(); // This creates the default cache
91
+ expect(Caching.hasCache("DEFAULT")).toBe(true);
92
+ });
93
+ });
94
+
95
+ describe("clear", () => {
96
+ it("should clear a named cache if it has a clear method", () => {
97
+ const testCache = new TestCache();
98
+ Caching.setCache("TEST_CACHE", testCache);
99
+
100
+ const cache = Caching.getCache("TEST_CACHE");
101
+ cache.set("key1", "value1");
102
+ expect(cache.get("key1")).toBe("value1");
103
+
104
+ Caching.flushAllCaches();
105
+ expect(cache.get("key1")).toBeUndefined();
106
+ });
107
+
108
+ it("should clear default cache when no name is provided", () => {
109
+ const cache = Caching.getCache();
110
+ cache.set("key1", "value1");
111
+ expect(cache.get("key1")).toBe("value1");
112
+
113
+ Caching.flushAllCaches();
114
+ expect(cache.get("key1")).toBeUndefined();
115
+ });
116
+ });
117
+
118
+ describe("removeCache", () => {
119
+ it("should remove a cache instance", () => {
120
+ const testCache = new TestCache();
121
+ Caching.setCache("TEST_CACHE", testCache);
122
+
123
+ expect(Caching.hasCache("TEST_CACHE")).toBe(true);
124
+
125
+ Caching.removeCache("TEST_CACHE");
126
+ expect(Caching.hasCache("TEST_CACHE")).toBe(false);
127
+ });
128
+
129
+ it("should not throw error when removing non-existent cache", () => {
130
+ expect(() => Caching.removeCache("NON_EXISTENT")).not.toThrow();
131
+ });
132
+
133
+ it("should allow re-adding cache after removal", () => {
134
+ const testCache1 = new TestCache();
135
+ const testCache2 = new TestCache();
136
+
137
+ Caching.setCache("TEST_CACHE", testCache1);
138
+ expect(Caching.getCache("TEST_CACHE")).toBe(testCache1);
139
+
140
+ Caching.removeCache("TEST_CACHE");
141
+ expect(Caching.hasCache("TEST_CACHE")).toBe(false);
142
+
143
+ Caching.setCache("TEST_CACHE", testCache2);
144
+ expect(Caching.getCache("TEST_CACHE")).toBe(testCache2);
145
+ });
146
+ });
147
+ });