@sentzunhat/zacatl 0.0.24 → 0.0.26

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.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "main": "build/index.js",
5
5
  "module": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
- "version": "0.0.24",
7
+ "version": "0.0.26",
8
8
  "packageManager": "npm@10.9.0",
9
9
  "exports": {
10
10
  ".": {
@@ -1,6 +1,17 @@
1
1
  import "reflect-metadata";
2
2
  import { container as tsyringeContainer } from "tsyringe";
3
3
  import type { DependencyContainer, InjectionToken } from "tsyringe";
4
+ import type { Constructor } from "../service/architecture/architecture";
5
+
6
+ /**
7
+ * Track registered classes to avoid requiring decorator metadata
8
+ */
9
+ const registeredClasses = new Set<Constructor>();
10
+
11
+ /**
12
+ * Cache for singleton instances
13
+ */
14
+ const singletonInstances = new Map<Constructor, object>();
4
15
 
5
16
  /**
6
17
  * Standalone DI container decoupled from microservice architecture.
@@ -83,10 +94,104 @@ export const resolveDependency = <T>(token: InjectionToken<T>): T => {
83
94
  return tsyringeContainer.resolve(token);
84
95
  };
85
96
 
97
+ /**
98
+ * Register a dependency with explicit dependencies (no decorator metadata required)
99
+ * Useful for tsx/ts-node/ESM environments where decorator metadata is not available
100
+ *
101
+ * @param serviceClass - Class to register
102
+ * @param dependencies - Array of dependency classes to inject (in constructor order)
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * import { registerWithDependencies } from '@sentzunhat/zacatl';
107
+ *
108
+ * // Register with no deps
109
+ * registerWithDependencies(MachineRepository, []);
110
+ *
111
+ * // Register with explicit deps
112
+ * registerWithDependencies(MachineService, [MachineRepository]);
113
+ * ```
114
+ */
115
+ export const registerWithDependencies = <T extends object>(
116
+ serviceClass: Constructor<T>,
117
+ dependencies: Constructor[] = [],
118
+ ): void => {
119
+ // Track this class as registered
120
+ registeredClasses.add(serviceClass);
121
+
122
+ tsyringeContainer.register(serviceClass, {
123
+ useFactory: () => {
124
+ const resolvedDeps = dependencies.map((dep) => {
125
+ // Check if dependency was registered
126
+ if (!registeredClasses.has(dep)) {
127
+ throw new Error(
128
+ `Dependency ${dep.name} not registered. Register it before ${serviceClass.name}.`,
129
+ );
130
+ }
131
+ // Resolve from container (works because we registered it)
132
+ return tsyringeContainer.resolve(dep);
133
+ });
134
+ return new serviceClass(...resolvedDeps);
135
+ },
136
+ } as never);
137
+ };
138
+
139
+ /**
140
+ * Register a singleton with explicit dependencies (no decorator metadata required)
141
+ * Useful for tsx/ts-node/ESM environments where decorator metadata is not available
142
+ *
143
+ * @param serviceClass - Class to register as singleton
144
+ * @param dependencies - Array of dependency classes to inject (in constructor order)
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * import { registerSingletonWithDependencies } from '@sentzunhat/zacatl';
149
+ *
150
+ * // Register singleton with deps
151
+ * registerSingletonWithDependencies(MachineService, [MachineRepository]);
152
+ * ```
153
+ */
154
+ export const registerSingletonWithDependencies = <T extends object>(
155
+ serviceClass: Constructor<T>,
156
+ dependencies: Constructor[] = [],
157
+ ): void => {
158
+ // Track this class as registered
159
+ registeredClasses.add(serviceClass);
160
+
161
+ // Use register() with manual singleton pattern (lifecycle can't be used with factories)
162
+ tsyringeContainer.register(serviceClass, {
163
+ useFactory: () => {
164
+ // Check if we already have a singleton instance
165
+ if (singletonInstances.has(serviceClass)) {
166
+ return singletonInstances.get(serviceClass) as T;
167
+ }
168
+
169
+ // Create new instance
170
+ const resolvedDeps = dependencies.map((dep) => {
171
+ // Check if dependency was registered
172
+ if (!registeredClasses.has(dep)) {
173
+ throw new Error(
174
+ `Dependency ${dep.name} not registered. Register it before ${serviceClass.name}.`,
175
+ );
176
+ }
177
+ // Resolve from container (works because we registered it)
178
+ return tsyringeContainer.resolve(dep);
179
+ });
180
+ const instance = new serviceClass(...resolvedDeps);
181
+
182
+ // Cache the singleton instance
183
+ singletonInstances.set(serviceClass, instance);
184
+ return instance;
185
+ },
186
+ } as never);
187
+ };
188
+
86
189
  /**
87
190
  * Clear all registrations (useful for testing)
88
191
  */
89
192
  export const clearContainer = (): void => {
90
193
  tsyringeContainer.clearInstances();
91
194
  tsyringeContainer.reset();
195
+ singletonInstances.clear();
196
+ registeredClasses.clear();
92
197
  };
@@ -1,6 +1,11 @@
1
1
  import { container } from "tsyringe";
2
2
 
3
- export type Constructor<T = unknown> = new (...args: unknown[]) => T;
3
+ /**
4
+ * Constructor type that accepts classes with any parameter signature
5
+ * Uses contravariant any[] to allow flexibility in DI container
6
+ */
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ export type Constructor<T = object> = new (...args: any[]) => T;
4
9
 
5
10
  type Architecture = {
6
11
  start: () => void;
@@ -13,9 +18,8 @@ export abstract class AbstractArchitecture implements Architecture {
13
18
  */
14
19
  protected registerDependencies<T>(dependencies: Array<Constructor<T>>): void {
15
20
  for (const dependency of dependencies) {
16
- const instance = new dependency();
17
-
18
- container.register<typeof instance>(dependency.name, {
21
+ // Register class directly - tsyringe handles instantiation with DI
22
+ container.register(dependency.name, {
19
23
  useClass: dependency,
20
24
  });
21
25
  }
@@ -28,7 +32,7 @@ export abstract class AbstractArchitecture implements Architecture {
28
32
  */
29
33
  protected registerAndStoreDependencies<T>(
30
34
  dependencies: Array<new () => T>,
31
- storage: Array<T>
35
+ storage: Array<T>,
32
36
  ): void {
33
37
  for (const dependency of dependencies) {
34
38
  container.register(dependency.name, { useClass: dependency });