@decohere/core 0.0.1-dev.126 → 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,30 @@
1
+ export type {
2
+ Action,
3
+ ActionClassMap,
4
+ ActionOptions,
5
+ Actions,
6
+ MergeOptions,
7
+ Result,
8
+ ResultStatus,
9
+ } from "./action.js";
10
+ export { ActionMap, ActionResponse } from "./action.js";
11
+ export type { CommandHandler, CommandType } from "./command.js";
12
+ export { CommandError, CommandResponse, DecohereCommand } from "./command.js";
13
+ export type {
14
+ ApplicationModule,
15
+ CommandMap as CommandClassMap,
16
+ ModuleConfig,
17
+ WorkspaceFactoryOptions,
18
+ } from "./module.type.js";
19
+ export { ModuleManifest } from "./module.type.js";
20
+ export {
21
+ ConfigFileOption,
22
+ ConfigurationOption,
23
+ DryRunOption,
24
+ EnvFileIfExistsOption,
25
+ EnvFileOption,
26
+ LogLevelOption,
27
+ NoBuildOptions,
28
+ NoCleanOptions,
29
+ ProjectOption,
30
+ } from "./options.js";
@@ -0,0 +1,48 @@
1
+ import type { ContainerBuilder } from "../composition/index.js";
2
+ import type { ActionClassMap } from "./action.js";
3
+ import type { CommandType, DecohereCommand } from "./command.js";
4
+
5
+ export type CommandMap = Record<string, CommandType>;
6
+
7
+ export type WorkspaceFactoryOptions = {
8
+ configFile?: string;
9
+ rootDir?: string;
10
+ };
11
+
12
+ export interface ApplicationModule {
13
+ id: string;
14
+ dispose?(): void;
15
+ asyncDispose?(): Promise<void>;
16
+ command: DecohereCommand;
17
+ }
18
+
19
+ export type ModuleConfig = {
20
+ actions?: ActionClassMap;
21
+ commands?: CommandMap;
22
+ };
23
+
24
+ /**
25
+ * Simple declaration interface for a CLI module.
26
+ * Extend this and implement the four abstract members.
27
+ */
28
+ export abstract class ModuleManifest {
29
+ /** Unique module identifier, used as the root command name. */
30
+ public abstract readonly id: string;
31
+
32
+ /** Sub-command classes this module contributes. */
33
+ public readonly commands: CommandMap = {};
34
+
35
+ /** Action classes this module contributes. */
36
+ public readonly actions: ActionClassMap = {};
37
+
38
+ /** The root command class for this module. */
39
+ public abstract readonly command: CommandType;
40
+
41
+ /** Register infrastructure services into the DI container. */
42
+ public registerServices(_builder: ContainerBuilder): void {}
43
+
44
+ /** Lazily create a workspace instance. Return null if this module does not use a workspace. */
45
+ public createWorkspace(_options: WorkspaceFactoryOptions): Promise<object | null> {
46
+ return Promise.resolve(null);
47
+ }
48
+ }
@@ -0,0 +1,81 @@
1
+ import { Option } from "commander";
2
+
3
+ function parseOptionalBoolean(input: string | boolean): boolean {
4
+ if (typeof input === "boolean") {
5
+ return input;
6
+ }
7
+
8
+ const value = input.trim().toLowerCase();
9
+ if (["true", "1", "yes", "on"].includes(value)) {
10
+ return true;
11
+ }
12
+ if (["false", "0", "no", "off"].includes(value)) {
13
+ return false;
14
+ }
15
+
16
+ throw new Error(`Invalid boolean value: "${input}". Use true/false.`);
17
+ }
18
+
19
+ export class ConfigurationOption extends Option {
20
+ public constructor(defaultValue: string) {
21
+ super("--configuration <configuration>", "Configuration name");
22
+ this.default(defaultValue);
23
+ }
24
+ }
25
+
26
+ export class ConfigFileOption extends Option {
27
+ public constructor(defaultValue = ".decohere.js") {
28
+ super("--config-file <file>", "Path to Decohere config file");
29
+ this.default(defaultValue);
30
+ }
31
+ }
32
+
33
+ export class LogLevelOption extends Option {
34
+ public constructor() {
35
+ super("--log-level <level>", "Log level (trace, debug, info, warn, error)");
36
+ }
37
+ }
38
+
39
+ export class EnvFileOption extends Option {
40
+ public constructor() {
41
+ super("--env-file <file>", "Load environment variables from this file before running");
42
+ }
43
+ }
44
+
45
+ export class EnvFileIfExistsOption extends Option {
46
+ public constructor() {
47
+ super(
48
+ "--env-file-if-exists <file>",
49
+ "Like --env-file, but skip it when the file is missing",
50
+ );
51
+ }
52
+ }
53
+
54
+ export class NoCleanOptions extends Option {
55
+ public constructor(defaultValue: boolean) {
56
+ super("--no-clean", "Will not clean the project directories");
57
+ this.default(defaultValue);
58
+ }
59
+ }
60
+
61
+ export class NoBuildOptions extends Option {
62
+ public constructor(defaultValue: boolean) {
63
+ super("--no-build", "Will not run the build step");
64
+ this.default(defaultValue);
65
+ }
66
+ }
67
+
68
+ export class DryRunOption extends Option {
69
+ public constructor(description: string, defaultValue = false) {
70
+ super("--dry-run [dryRun]", description);
71
+ this.default(defaultValue);
72
+ this.preset(true);
73
+ this.argParser(parseOptionalBoolean);
74
+ }
75
+ }
76
+
77
+ export class ProjectOption extends Option {
78
+ public constructor() {
79
+ super("--project <project>", "Project name to target");
80
+ }
81
+ }
@@ -0,0 +1,16 @@
1
+ export function findDuplicatesByKey<T>(items: T[], key: keyof T): [string, T[]][] {
2
+ return findDuplicatesBy(items, (item) => String(item[key]));
3
+ }
4
+
5
+ export function findDuplicatesBy<T>(
6
+ items: T[],
7
+ selector: (item: T) => string,
8
+ ): [string, T[]][] {
9
+ const groups = new Map<string, T[]>();
10
+ for (const item of items) {
11
+ const k = selector(item);
12
+ const existing = groups.get(k) ?? [];
13
+ groups.set(k, [...existing, item]);
14
+ }
15
+ return [...groups.entries()].filter(([, group]) => group.length > 1);
16
+ }
@@ -0,0 +1,9 @@
1
+ export function mergeByKey<T>(first: T[], second: T[], key: keyof T): T[] {
2
+ return mergeBy(first, second, (item) => String(item[key]));
3
+ }
4
+
5
+ export function mergeBy<T>(first: T[], second: T[], selector: (item: T) => string): T[] {
6
+ // Second collection values win when keys collide.
7
+ const merged = [...first, ...second];
8
+ return [...new Map(merged.map((item) => [selector(item), item])).values()];
9
+ }
@@ -0,0 +1,71 @@
1
+ export function sumByKey<T>(objects: T[], key: keyof T): number;
2
+ export function sumByKey<T>(objects: T[], key: keyof T): bigint;
3
+ export function sumByKey<T>(objects: T[], key: keyof T): number | bigint {
4
+ return sum(objects, key);
5
+ }
6
+
7
+ export function sumBy<T>(objects: T[], key: (obj: T) => number): number;
8
+ export function sumBy<T>(objects: T[], key: (obj: T) => bigint): bigint;
9
+ export function sumBy<T>(objects: T[], key: (obj: T) => number | bigint): number | bigint {
10
+ return sum(objects, key);
11
+ }
12
+
13
+ function extractValueFromItem<T>(
14
+ item: T,
15
+ key: keyof T | ((obj: T) => number | bigint),
16
+ ): number | bigint {
17
+ if (typeof key === "function") {
18
+ const result = key(item);
19
+ return isNumericValue(result) ? result : 0;
20
+ }
21
+
22
+ const value = item?.[key];
23
+ return isNumericValue(value) ? value : 0;
24
+ }
25
+
26
+ function isNumericValue(value: unknown): value is number | bigint {
27
+ return typeof value === "number" || typeof value === "bigint";
28
+ }
29
+
30
+ function determineSumType(firstValue: number | bigint): "bigint" | "number" {
31
+ return typeof firstValue === "bigint" ? "bigint" : "number";
32
+ }
33
+
34
+ function getInitialSumValue(sumType: "bigint" | "number"): number | bigint {
35
+ return sumType === "bigint" ? 0n : 0;
36
+ }
37
+
38
+ function addValues(
39
+ total: number | bigint,
40
+ value: number | bigint,
41
+ sumType: "bigint" | "number",
42
+ ): number | bigint {
43
+ if (sumType === "bigint") {
44
+ const totalBigInt = total as bigint;
45
+ const valueBigInt = typeof value === "bigint" ? value : BigInt(value);
46
+ return totalBigInt + valueBigInt;
47
+ }
48
+
49
+ const totalNumber = total as number;
50
+ const valueNumber = typeof value === "number" ? value : Number(value);
51
+ return totalNumber + valueNumber;
52
+ }
53
+
54
+ function sum<T>(objects: T[], key: (obj: T) => number): number;
55
+ function sum<T>(objects: T[], key: (obj: T) => bigint): bigint;
56
+ function sum<T>(objects: T[], key: (obj: T) => number | bigint): number | bigint;
57
+ function sum<T>(objects: T[], key: keyof T): number | bigint;
58
+ function sum<T>(objects: T[], key: keyof T | ((obj: T) => number | bigint)): number | bigint {
59
+ if (objects.length === 0) {
60
+ return 0;
61
+ }
62
+
63
+ const firstValue = extractValueFromItem(objects[0], key);
64
+ const sumType = determineSumType(firstValue);
65
+ const initialValue = getInitialSumValue(sumType);
66
+
67
+ return objects.reduce((total, item) => {
68
+ const value = extractValueFromItem(item, key);
69
+ return addValues(total, value, sumType);
70
+ }, initialValue);
71
+ }
@@ -0,0 +1,23 @@
1
+ export function uniqueObjectsByKey<T>(objects: T[], key: keyof T): T[] {
2
+ return uniqueObjects(objects, key);
3
+ }
4
+
5
+ export function uniqueObjectsBy<T>(objects: T[], key: (obj: T) => string): T[] {
6
+ return uniqueObjects(objects, key);
7
+ }
8
+
9
+ function uniqueObjects<T>(objects: T[], key: (obj: T) => string);
10
+ function uniqueObjects<T>(objects: T[], key: keyof T);
11
+ function uniqueObjects<T>(objects: T[], key: keyof T | ((obj: T) => string)) {
12
+ const getKey = (key: keyof T | ((obj: T) => string), item: T) => {
13
+ if (typeof key === "function") {
14
+ return key(item);
15
+ }
16
+
17
+ return item != null ? item[key] : null;
18
+ };
19
+ const arrayUniqueByKey = [
20
+ ...new Map(objects.map((item) => [getKey(key, item), item])).values(),
21
+ ];
22
+ return arrayUniqueByKey;
23
+ }
@@ -0,0 +1,48 @@
1
+ import type { Constructable } from "../reflection/index.js";
2
+
3
+ export type ResolveRequest<T extends object = object> = string | symbol | Constructable<T>;
4
+
5
+ export type Scope = "single-instance" | "request" | "transient";
6
+
7
+ export interface ServiceProvider {
8
+ /**
9
+ * @param key
10
+ * @throws {ServiceNotFoundException}
11
+ */
12
+ resolve<T extends object>(key: ResolveRequest<T>): T;
13
+
14
+ resolveMany<T extends object = object>(resolveRequest: ResolveRequest<T>): T[];
15
+ }
16
+
17
+ export interface LifetimeScope extends ServiceProvider {
18
+ /** A name must be unique among open scopes; omit it for per-request scopes. */
19
+ beginScope(name?: string): LifetimeScope;
20
+ [Symbol.dispose](): void;
21
+ [Symbol.asyncDispose](): Promise<void>;
22
+ }
23
+
24
+ export interface IContainer extends LifetimeScope {}
25
+
26
+ export interface ContainerRegistrar {
27
+ registerTransient(key: string | symbol, type: Constructable): unknown;
28
+ registerTransient(type: Constructable): unknown;
29
+
30
+ registerRequestScoped<T>(type: () => T): unknown;
31
+ registerRequestScoped<T>(key: string | symbol, type: () => T): unknown;
32
+ registerRequestScoped(key: string | symbol, type: Constructable): unknown;
33
+ registerRequestScoped(type: Constructable): unknown;
34
+
35
+ registerSingleton(key: string | symbol | Constructable, instance: object): unknown;
36
+ registerSingleton(key: string | symbol, type: Constructable): unknown;
37
+ registerSingleton(type: Constructable): unknown;
38
+
39
+ registerType(type: Constructable, scope?: Scope): unknown;
40
+
41
+ registerTypes(...types: Constructable[]): unknown[];
42
+ registerTypes(scope: Scope, ...types: Constructable[]): unknown[];
43
+ }
44
+
45
+ export interface ContainerBuilder extends ContainerRegistrar {
46
+ readonly override: ContainerRegistrar;
47
+ build(): IContainer;
48
+ }
@@ -0,0 +1,13 @@
1
+ import type { Constructable } from "../reflection/index.js";
2
+
3
+ export const ConfigurationServiceToken = Symbol("IConfigurationService");
4
+
5
+ export interface IConfigurationService {
6
+ readonly dataSources: readonly IConfigurationDataSource[];
7
+ get<T extends object>(typeOrString: Constructable<T> | string): Promise<T>;
8
+ }
9
+
10
+ export interface IConfigurationDataSource {
11
+ readonly name: string;
12
+ getData(): Promise<Record<string, unknown>>;
13
+ }
@@ -0,0 +1 @@
1
+ export { Trace, timed, trace } from "./trace.js";
@@ -0,0 +1,97 @@
1
+ import { isPromise } from "node:util/types";
2
+ import { ConsoleAdapter, scribe } from "@decohere/scribe";
3
+
4
+ // Keep existing behavior for now: trace mode is toggled via FORGE_TRACE.
5
+ const isTraceEnabled = process.env.FORGE_TRACE != null;
6
+
7
+ if (isTraceEnabled) {
8
+ const hasExplicitLevel =
9
+ process.env.LOG_LEVEL != null ||
10
+ process.env.SCRIBE_LOG_LEVEL != null ||
11
+ process.env.LOGGING_LEVEL != null;
12
+ if (!hasExplicitLevel) {
13
+ scribe.configure({ adapter: new ConsoleAdapter(), options: { logLevel: "trace" } });
14
+ }
15
+ }
16
+
17
+ type AnyFn = (...args: unknown[]) => unknown;
18
+ type TimingLevel = "info" | "trace";
19
+
20
+ function serializeArgs(args: unknown[]): string {
21
+ if (args.length === 0) return "";
22
+ return args
23
+ .map((arg) => {
24
+ if (Array.isArray(arg)) return `[${arg.map(String).join(", ")}]`;
25
+ const s = String(arg);
26
+ return s.length > 60 ? `${s.slice(0, 57)}...` : s;
27
+ })
28
+ .join(", ");
29
+ }
30
+
31
+ function wrapWithTiming(
32
+ fn: AnyFn,
33
+ className: string,
34
+ methodName: string,
35
+ logLevel: TimingLevel,
36
+ ): AnyFn {
37
+ let logger: ReturnType<typeof scribe.logger> | undefined;
38
+ return function (this: unknown, ...args: unknown[]) {
39
+ if (logger == null) {
40
+ logger = scribe.logger(className);
41
+ }
42
+ const log = logger;
43
+ const label = `${methodName}(${serializeArgs(args)})`;
44
+ const start = performance.now();
45
+ const logDuration = () => {
46
+ log[logLevel](`${label}: ${(performance.now() - start).toFixed(2)}ms`);
47
+ };
48
+ const result = fn.apply(this, args);
49
+ if (isPromise(result)) {
50
+ return result.finally(logDuration);
51
+ }
52
+ logDuration();
53
+ return result;
54
+ };
55
+ }
56
+
57
+ export function trace(
58
+ target: object,
59
+ propertyKey: string,
60
+ descriptor: PropertyDescriptor,
61
+ ): PropertyDescriptor {
62
+ if (!isTraceEnabled) return descriptor;
63
+ const className = (target as { constructor: { name: string } }).constructor.name;
64
+ descriptor.value = wrapWithTiming(
65
+ descriptor.value as AnyFn,
66
+ className,
67
+ propertyKey,
68
+ "trace",
69
+ );
70
+ return descriptor;
71
+ }
72
+
73
+ /**
74
+ * Always-on method timing. Logs duration at `info` regardless of `FORGE_TRACE`.
75
+ * Use this to time a specific method without raising the process log level.
76
+ */
77
+ export function timed(
78
+ target: object,
79
+ propertyKey: string,
80
+ descriptor: PropertyDescriptor,
81
+ ): PropertyDescriptor {
82
+ const className = (target as { constructor: { name: string } }).constructor.name;
83
+ descriptor.value = wrapWithTiming(descriptor.value as AnyFn, className, propertyKey, "info");
84
+ return descriptor;
85
+ }
86
+
87
+ export function Trace(constructor: { name: string; prototype: object }): void {
88
+ if (!isTraceEnabled) return;
89
+ const className = constructor.name;
90
+ for (const key of Object.getOwnPropertyNames(constructor.prototype)) {
91
+ if (key === "constructor") continue;
92
+ const descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, key);
93
+ if (descriptor == null || typeof descriptor.value !== "function") continue;
94
+ descriptor.value = wrapWithTiming(descriptor.value as AnyFn, className, key, "trace");
95
+ Object.defineProperty(constructor.prototype, key, descriptor);
96
+ }
97
+ }
@@ -1,3 +1,5 @@
1
+ import type { FunctionDecorator } from "./decorator-factory.js";
2
+
1
3
  /**
2
4
  * Base class for creating decorator factory instances with methods that can be
3
5
  * conditionally disabled during unit testing.
@@ -25,15 +27,21 @@
25
27
  * }
26
28
  * }
27
29
  * ```
28
- */ export class DecoratorFactoryBase {
29
- /**
30
+ */
31
+
32
+ export abstract class DecoratorFactoryBase {
33
+ /**
30
34
  * Utility method to check if we're currently in a test environment
31
- */ isTestEnvironment() {
32
- return process.env.TEST === "true";
33
- }
34
- /**
35
+ */
36
+ protected isTestEnvironment(): boolean {
37
+ return process.env.TEST === "true";
38
+ }
39
+
40
+ /**
35
41
  * Creates a no-op decorator that returns the original function unchanged
36
- */ createNoOpDecorator() {
37
- return (_target, _propertyKey, descriptor)=>descriptor;
38
- }
42
+ */
43
+ protected createNoOpDecorator(): FunctionDecorator {
44
+ return (_target: object, _propertyKey: string, descriptor: PropertyDescriptor) =>
45
+ descriptor;
46
+ }
39
47
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Decorator factory class that provides decorator methods which can be conditionally
3
+ * disabled during unit testing using the @ignoreInUnitTest decorator.
4
+ *
5
+ * Inspired by: https://jasonharper.blog/2018/03/13/decorate-the-decorator-to-exclude-in-unit-testing/
6
+ */
7
+
8
+ /**
9
+ * Decorator that disables the decorated decorator method when TEST environment variable is true.
10
+ * If TEST=true, it returns a no-op decorator that simply returns the original function unchanged.
11
+ */
12
+ export function ignoreInUnitTest(
13
+ _target: object,
14
+ _propertyKey: string,
15
+ descriptor: PropertyDescriptor,
16
+ ) {
17
+ const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
18
+
19
+ descriptor.value = function (this: unknown, ...args: unknown[]) {
20
+ // Check if we're in a test environment
21
+ if (process.env.TEST === "true") {
22
+ // Return a no-op decorator that just returns the original function
23
+ return (originalFunction: unknown) => originalFunction;
24
+ }
25
+ // Otherwise, apply the original decorator method
26
+ return originalMethod.apply(this, args);
27
+ };
28
+
29
+ return descriptor;
30
+ }
31
+
32
+ /**
33
+ * Type definition for a function decorator
34
+ */
35
+ export type FunctionDecorator = (
36
+ target: object,
37
+ propertyKey: string,
38
+ descriptor: PropertyDescriptor,
39
+ ) => PropertyDescriptor | undefined;
40
+
41
+ /**
42
+ * Type definition for a decorator factory function (returns a decorator)
43
+ */
44
+ export type DecoratorFactory<TOptions = unknown> = (options?: TOptions) => FunctionDecorator;
@@ -0,0 +1,34 @@
1
+ import type { Type } from "../reflection/types/reflection.types.js";
2
+
3
+ export type ExtensionOptions = {
4
+ /**
5
+ * Also install the method on the constructor. Use for singleton facades
6
+ * (`scribe.withPinoPretty()`) whose instance lives at `ctor.instance`.
7
+ */
8
+ static?: boolean;
9
+ };
10
+
11
+ export function extension(ctor: Type, options?: ExtensionOptions) {
12
+ return (_target: object, propertyKey: string, descriptor: PropertyDescriptor) => {
13
+ const originalFunction = descriptor.value as (...args: unknown[]) => unknown;
14
+
15
+ ctor.prototype[propertyKey] = function (...args: unknown[]) {
16
+ return originalFunction.apply(this, [this, ...args]);
17
+ };
18
+
19
+ if (options?.static) {
20
+ Object.defineProperty(ctor, propertyKey, {
21
+ configurable: true,
22
+ writable: true,
23
+ value(...args: unknown[]) {
24
+ const receiver = singletonOf(ctor) ?? this;
25
+ return originalFunction.apply(receiver, [receiver, ...args]);
26
+ },
27
+ });
28
+ }
29
+ };
30
+ }
31
+
32
+ function singletonOf(ctor: Type): unknown {
33
+ return (ctor as { instance?: unknown }).instance;
34
+ }
@@ -0,0 +1,42 @@
1
+ type Constructor<T = object> = abstract new (...args: unknown[]) => T;
2
+
3
+ type UnionToIntersection<T> = (T extends unknown ? (arg: T) => void : never) extends (
4
+ arg: infer TResult,
5
+ ) => void
6
+ ? TResult
7
+ : never;
8
+
9
+ type MixedInstance<TMixins extends readonly Constructor[]> = UnionToIntersection<
10
+ InstanceType<TMixins[number]>
11
+ >;
12
+
13
+ type MixedClass<TMixins extends readonly Constructor[]> = abstract new (
14
+ ...args: unknown[]
15
+ ) => MixedInstance<TMixins>;
16
+
17
+ const BLOCKED_PROTOTYPE_KEYS = new Set(["constructor", "prototype", "name", "length"]);
18
+
19
+ function copyPrototypeProperties(target: object, source: object) {
20
+ for (const key of Reflect.ownKeys(source)) {
21
+ if (typeof key === "string" && BLOCKED_PROTOTYPE_KEYS.has(key)) {
22
+ continue;
23
+ }
24
+
25
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
26
+ if (descriptor == null) {
27
+ continue;
28
+ }
29
+
30
+ Object.defineProperty(target, key, descriptor);
31
+ }
32
+ }
33
+
34
+ export function mixwith<TMixins extends readonly Constructor[]>(...mixins: TMixins) {
35
+ abstract class Mixed {}
36
+
37
+ for (const mixin of mixins) {
38
+ copyPrototypeProperties(Mixed.prototype, mixin.prototype);
39
+ }
40
+
41
+ return Mixed as MixedClass<TMixins>;
42
+ }
@@ -0,0 +1,60 @@
1
+ import { scribe } from "@decohere/scribe";
2
+
3
+ export interface PipelineStep<
4
+ TContext extends Record<string, unknown> = Record<string, unknown>,
5
+ > {
6
+ run(ctx: TContext, next: Next): Promise<void> | void;
7
+ }
8
+
9
+ export type Next = () => Promise<void> | void;
10
+ export class Pipeline<T extends Record<string, unknown>> {
11
+ private readonly stack: [string, PipelineStep<T>, ((ctx: T) => boolean)?][];
12
+ private readonly logger = scribe.logger(Pipeline.name);
13
+ private currentStep: string | undefined;
14
+ public constructor(...steps: [string, PipelineStep<T>, ((ctx: T) => boolean)?][]) {
15
+ this.stack = steps;
16
+ }
17
+
18
+ public [Symbol.dispose]() {
19
+ for (const [, step] of this.stack) {
20
+ if (Symbol.dispose in step) {
21
+ (step as Disposable)[Symbol.dispose]();
22
+ }
23
+ }
24
+ }
25
+
26
+ public async start(context: T) {
27
+ const runStep = async (index: number): Promise<void> => {
28
+ const [key, middleware, when] = this.stack[index] ?? [];
29
+ if (middleware == null) {
30
+ return;
31
+ }
32
+
33
+ if (when != null && !when(context)) {
34
+ this.logger.info(`Skipping step ${key}`);
35
+ return await runStep(index + 1);
36
+ }
37
+
38
+ this.currentStep = key;
39
+ this.logger.info(`Running step ${key}`);
40
+ await middleware.run(context, async () => {
41
+ await runStep(index + 1);
42
+ });
43
+ };
44
+
45
+ try {
46
+ await runStep(0);
47
+ } catch (error) {
48
+ const step = this.currentStep ?? "unknown";
49
+ if (error instanceof Error) {
50
+ throw new Error(`Pipeline step '${step}' failed: ${error.message}`, {
51
+ cause: error,
52
+ });
53
+ }
54
+
55
+ throw new Error(`Pipeline step '${step}' failed with non-Error value`, {
56
+ cause: error,
57
+ });
58
+ }
59
+ }
60
+ }