@half0wl/container 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +266 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +325 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.js +234 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BaseService: () => BaseService,
|
|
24
|
+
Container: () => Container,
|
|
25
|
+
Inject: () => Inject,
|
|
26
|
+
Service: () => Service,
|
|
27
|
+
getPropertyDescriptorFromChain: () => getPropertyDescriptorFromChain,
|
|
28
|
+
wrapWithTracing: () => wrapWithTracing
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/base.ts
|
|
33
|
+
var BaseService = class {
|
|
34
|
+
/** The user-defined dependencies (everything except `registry`). */
|
|
35
|
+
deps;
|
|
36
|
+
/** The container instance, used internally by {@link Inject} to resolve other services. */
|
|
37
|
+
registry;
|
|
38
|
+
constructor(dependencies) {
|
|
39
|
+
const { registry, ...rest } = dependencies;
|
|
40
|
+
this.deps = rest;
|
|
41
|
+
this.registry = registry;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/decorators.ts
|
|
46
|
+
var TRACED_MARKER = /* @__PURE__ */ Symbol("__service_traced__");
|
|
47
|
+
function Service(options) {
|
|
48
|
+
return (target) => {
|
|
49
|
+
if (options?.trace) {
|
|
50
|
+
target[TRACED_MARKER] = true;
|
|
51
|
+
}
|
|
52
|
+
return target;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function Inject(factory) {
|
|
56
|
+
return (_target, propertyKey) => {
|
|
57
|
+
const cacheKey = /* @__PURE__ */ Symbol(`__inject_${String(propertyKey)}`);
|
|
58
|
+
Object.defineProperty(_target, propertyKey, {
|
|
59
|
+
get() {
|
|
60
|
+
const cached = this[cacheKey];
|
|
61
|
+
if (cached !== void 0) return cached;
|
|
62
|
+
const resolved = this.registry.get(factory());
|
|
63
|
+
this[cacheKey] = resolved;
|
|
64
|
+
return resolved;
|
|
65
|
+
},
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/helpers.ts
|
|
73
|
+
function getPropertyDescriptorFromChain(proto, key, stopAt) {
|
|
74
|
+
let current = proto;
|
|
75
|
+
while (current && current !== stopAt) {
|
|
76
|
+
const desc = Object.getOwnPropertyDescriptor(current, key);
|
|
77
|
+
if (desc) return desc;
|
|
78
|
+
current = Object.getPrototypeOf(current);
|
|
79
|
+
}
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
function wrapWithTracing(instance, trace, stopAt) {
|
|
83
|
+
const className = instance.constructor.name;
|
|
84
|
+
const seen = /* @__PURE__ */ new Set();
|
|
85
|
+
let proto = Object.getPrototypeOf(instance);
|
|
86
|
+
while (proto && proto !== stopAt) {
|
|
87
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
88
|
+
if (key === "constructor" || seen.has(key)) continue;
|
|
89
|
+
seen.add(key);
|
|
90
|
+
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
|
91
|
+
if (!desc || !desc.value || typeof desc.value !== "function") continue;
|
|
92
|
+
const original = desc.value;
|
|
93
|
+
const spanName = `${className}.${key}`;
|
|
94
|
+
instance[key] = function(...args) {
|
|
95
|
+
return trace(spanName, () => original.apply(this, args));
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
proto = Object.getPrototypeOf(proto);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/container.ts
|
|
103
|
+
var Container = class _Container {
|
|
104
|
+
static globalInstance;
|
|
105
|
+
instances = /* @__PURE__ */ new Map();
|
|
106
|
+
deps;
|
|
107
|
+
config;
|
|
108
|
+
constructor(config = {}) {
|
|
109
|
+
this.config = config;
|
|
110
|
+
this.deps = config.deps;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create a new container instance.
|
|
114
|
+
*
|
|
115
|
+
* Accepts either a plain deps object or a {@link ContainerConfig} with
|
|
116
|
+
* additional options like `trace`.
|
|
117
|
+
* When called with no arguments, the container defers resolution
|
|
118
|
+
* until {@link setDeps} is called.
|
|
119
|
+
*
|
|
120
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
121
|
+
* @param depsOrConfig - Dependencies or full configuration object
|
|
122
|
+
* @returns A new isolated container instance
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* // Simple — pass deps directly
|
|
127
|
+
* const container = Container.create<ContainerDeps>({ db, logger });
|
|
128
|
+
*
|
|
129
|
+
* // With config — pass options alongside deps
|
|
130
|
+
* const container = Container.create<ContainerDeps>({
|
|
131
|
+
* deps: { db, logger },
|
|
132
|
+
* trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* // Lazy — set deps later
|
|
136
|
+
* const container = Container.create<ContainerDeps>();
|
|
137
|
+
* container.setDeps({ db, logger });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
static create(depsOrConfig) {
|
|
141
|
+
if (depsOrConfig && typeof depsOrConfig === "object" && ("deps" in depsOrConfig || "trace" in depsOrConfig)) {
|
|
142
|
+
return new _Container(depsOrConfig);
|
|
143
|
+
}
|
|
144
|
+
return new _Container({
|
|
145
|
+
deps: depsOrConfig
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get the global singleton container instance.
|
|
150
|
+
*
|
|
151
|
+
* Creates one on first call. Useful for module-level exports that
|
|
152
|
+
* need a container reference before deps are available.
|
|
153
|
+
*
|
|
154
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
155
|
+
* @returns The global container instance
|
|
156
|
+
*/
|
|
157
|
+
static getGlobalInstance() {
|
|
158
|
+
if (!_Container.globalInstance) {
|
|
159
|
+
_Container.globalInstance = new _Container();
|
|
160
|
+
}
|
|
161
|
+
return _Container.globalInstance;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Reset the global singleton container.
|
|
165
|
+
*
|
|
166
|
+
* Clears all cached instances and removes the global reference.
|
|
167
|
+
* Primarily used for test cleanup.
|
|
168
|
+
*/
|
|
169
|
+
static resetGlobal() {
|
|
170
|
+
_Container.globalInstance?.clear();
|
|
171
|
+
_Container.globalInstance = void 0;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Set or replace the dependencies after container creation.
|
|
175
|
+
*
|
|
176
|
+
* Useful for lazy initialization patterns where the container
|
|
177
|
+
* is created before deps are available.
|
|
178
|
+
*
|
|
179
|
+
* @param deps - The user-defined dependencies
|
|
180
|
+
*/
|
|
181
|
+
setDeps(deps) {
|
|
182
|
+
this.deps = deps;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolve a service by its class constructor.
|
|
186
|
+
*
|
|
187
|
+
* On first call, lazily constructs the service with `{ ...deps, registry: this }`
|
|
188
|
+
* and caches the singleton. Subsequent calls return the cached instance.
|
|
189
|
+
*
|
|
190
|
+
* If the service was decorated with `@Service({ trace: true })`, all its methods
|
|
191
|
+
* are automatically wrapped with the configured `trace` function.
|
|
192
|
+
*
|
|
193
|
+
* @typeParam T - The service type
|
|
194
|
+
* @param serviceClass - The class constructor to resolve
|
|
195
|
+
* @returns The singleton instance
|
|
196
|
+
* @throws If deps have not been set
|
|
197
|
+
* @throws If the service has tracing enabled but no `trace` function was configured
|
|
198
|
+
*/
|
|
199
|
+
get(serviceClass) {
|
|
200
|
+
const existing = this.instances.get(serviceClass);
|
|
201
|
+
if (existing) return existing;
|
|
202
|
+
if (!this.deps) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"Container deps not set. Call setDeps() or pass deps to Container.create()."
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const serviceDeps = {
|
|
208
|
+
...this.deps,
|
|
209
|
+
registry: this
|
|
210
|
+
};
|
|
211
|
+
const instance = new serviceClass(serviceDeps);
|
|
212
|
+
if (serviceClass[TRACED_MARKER]) {
|
|
213
|
+
if (!this.config.trace) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Service "${serviceClass.name}" has trace enabled but no trace function was provided. Pass { trace } to Container.create().`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
wrapWithTracing(
|
|
219
|
+
instance,
|
|
220
|
+
this.config.trace,
|
|
221
|
+
BaseService.prototype
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
this.instances.set(serviceClass, instance);
|
|
225
|
+
return instance;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Register a pre-built instance for a service class.
|
|
229
|
+
*
|
|
230
|
+
* The registered instance is returned by {@link get} without constructing.
|
|
231
|
+
* Primarily used for injecting test mocks.
|
|
232
|
+
*
|
|
233
|
+
* @typeParam T - The service type
|
|
234
|
+
* @param serviceClass - The class constructor to associate with the instance
|
|
235
|
+
* @param instance - The pre-built instance to register
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });
|
|
240
|
+
* testContainer.register(UserService, mockUserService);
|
|
241
|
+
* const auth = testContainer.get(AuthService); // uses mockUserService via @Inject
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
register(serviceClass, instance) {
|
|
245
|
+
this.instances.set(serviceClass, instance);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Clear all cached service instances.
|
|
249
|
+
*
|
|
250
|
+
* After calling this, the next {@link get} call for any service
|
|
251
|
+
* will construct a fresh instance. Does not clear deps or config.
|
|
252
|
+
*/
|
|
253
|
+
clear() {
|
|
254
|
+
this.instances.clear();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
258
|
+
0 && (module.exports = {
|
|
259
|
+
BaseService,
|
|
260
|
+
Container,
|
|
261
|
+
Inject,
|
|
262
|
+
Service,
|
|
263
|
+
getPropertyDescriptorFromChain,
|
|
264
|
+
wrapWithTracing
|
|
265
|
+
});
|
|
266
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/base.ts","../src/decorators.ts","../src/helpers.ts","../src/container.ts"],"sourcesContent":["export {\n BaseService,\n type Constructor,\n type ContainerConfig,\n type IContainer,\n type ServiceDependencies,\n type TraceFn,\n} from \"./base.js\";\nexport { Container } from \"./container.js\";\nexport { Inject, Service, type ServiceOptions } from \"./decorators.js\";\nexport { getPropertyDescriptorFromChain, wrapWithTracing } from \"./helpers.js\";\n","/** A class constructor type. */\n// biome-ignore lint/suspicious/noExplicitAny: Constructor must accept any args to be generic over all classes\nexport type Constructor<T = unknown> = new (...args: any[]) => T;\n\n/** A pluggable tracing function signature. */\nexport type TraceFn = (spanName: string, fn: () => unknown) => unknown;\n\n/**\n * Minimal interface representing a DI container.\n *\n * Services receive this as `this.registry` to resolve other services\n * without coupling to the full {@link Container} implementation.\n */\nexport interface IContainer {\n /**\n * Resolve a service by its class constructor.\n *\n * @typeParam T - The service type\n * @param serviceClass - The class constructor to resolve\n * @returns The singleton instance of the service\n */\n get<T>(serviceClass: Constructor<T>): T;\n}\n\n/**\n * The full set of dependencies passed to a service constructor.\n *\n * Combines the user-defined `TDeps` with the container's `registry`,\n * which is injected automatically by the container.\n *\n * @typeParam TDeps - User-defined dependency interface\n */\nexport type ServiceDependencies<TDeps> = TDeps & { registry: IContainer };\n\n/**\n * Configuration object for {@link Container.create}.\n *\n * @typeParam TDeps - User-defined dependency interface\n */\nexport interface ContainerConfig<TDeps> {\n /** The user-defined dependencies to inject into services. */\n deps?: TDeps;\n\n /**\n * Optional tracing function for services decorated with `@Service({ trace: true })`.\n * When a traced service is resolved, all its methods are wrapped with this function.\n *\n * @param spanName - The span name in `ClassName.methodName` format\n * @param fn - The original method to execute within the span\n * @returns The return value of `fn`\n *\n * @example\n * ```ts\n * const container = Container.create<ContainerDeps>({\n * deps: { db, logger },\n * trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),\n * });\n * ```\n */\n trace?: TraceFn;\n}\n\n/**\n * Abstract base class that all container-managed services extend.\n *\n * Receives injected dependencies via its constructor and exposes them\n * as `this.deps` (user-defined) and `this.registry` (the container).\n *\n * @typeParam TDeps - User-defined dependency interface\n *\n * @example\n * ```ts\n * interface ContainerDeps {\n * db: DatabaseClient;\n * logger: Logger;\n * }\n *\n * @Service()\n * class UserService extends BaseService<ContainerDeps> {\n * findById(id: string) {\n * return this.deps.db.users.findUnique({ where: { id } });\n * }\n * }\n * ```\n */\nexport abstract class BaseService<TDeps = unknown> {\n /** The user-defined dependencies (everything except `registry`). */\n protected readonly deps: TDeps;\n\n /** The container instance, used internally by {@link Inject} to resolve other services. */\n protected readonly registry: IContainer;\n\n constructor(dependencies: ServiceDependencies<TDeps>) {\n const { registry, ...rest } = dependencies as ServiceDependencies<TDeps>;\n this.deps = rest as unknown as TDeps;\n this.registry = registry;\n }\n}\n","import type { Constructor, IContainer } from \"./base.js\";\n\n/** @internal Symbol used to mark classes decorated with `@Service({ trace: true })`. */\nexport const TRACED_MARKER = Symbol(\"__service_traced__\");\n\n/**\n * Options for the {@link Service} decorator.\n */\nexport interface ServiceOptions {\n /**\n * When `true`, all methods on this service are automatically wrapped\n * with the container's `trace` function after construction.\n *\n * Requires a `trace` function in the {@link ContainerConfig}.\n *\n * @defaultValue `false`\n */\n trace?: boolean;\n}\n\n/**\n * Class decorator that marks a class as container-managed.\n *\n * When `trace` is enabled, the container will wrap all methods\n * with tracing spans using the configured trace function.\n *\n * Requires `experimentalDecorators: true` in tsconfig.json.\n *\n * @param options - Optional configuration for the service\n * @returns A class decorator\n *\n * @example\n * ```ts\n * @Service()\n * class UserService extends BaseService<ContainerDeps> {\n * findById(id: string) {\n * return this.deps.db.users.findUnique({ where: { id } });\n * }\n * }\n *\n * @Service({ trace: true })\n * class TracedService extends BaseService<ContainerDeps> {\n * // All methods auto-traced as \"TracedService.methodName\"\n * process() { ... }\n * }\n * ```\n */\nexport function Service(options?: ServiceOptions): ClassDecorator {\n return (target) => {\n if (options?.trace) {\n (target as unknown as Record<symbol, boolean>)[TRACED_MARKER] = true;\n }\n return target;\n };\n}\n\ntype InjectTarget<T> = { registry: IContainer } & Record<symbol, T>;\n\n/**\n * Property decorator for declarative inter-service dependency injection.\n *\n * Defines a lazy getter that resolves the dependency from the same container\n * on first access and caches it per-instance. Uses a factory function\n * (`() => X` instead of `X` directly) to avoid circular import issues\n * between service files.\n *\n * Requires `experimentalDecorators: true` in tsconfig.json.\n *\n * @typeParam T - The injected service type\n * @param factory - A factory function returning the service class constructor.\n * Called lazily on first property access.\n * @returns A property decorator\n *\n * @example\n * ```ts\n * @Service()\n * class AuthService extends BaseService<ContainerDeps> {\n * @Inject(() => UserService)\n * declare readonly userService: UserService;\n *\n * @Inject(() => TokenService)\n * declare readonly tokenService: TokenService;\n *\n * authenticate(token: string) {\n * const payload = this.tokenService.verify(token);\n * return this.userService.findById(payload.userId);\n * }\n * }\n * ```\n */\nexport function Inject<T>(factory: () => Constructor<T>) {\n return (_target: object, propertyKey: string | symbol): void => {\n const cacheKey = Symbol(`__inject_${String(propertyKey)}`);\n\n Object.defineProperty(_target, propertyKey, {\n get(this: InjectTarget<T>) {\n const cached = this[cacheKey];\n if (cached !== undefined) return cached;\n const resolved = this.registry.get(factory());\n this[cacheKey] = resolved;\n return resolved;\n },\n enumerable: true,\n configurable: true,\n });\n };\n}\n","import type { TraceFn } from \"./base.js\";\n\n/**\n * Walk the prototype chain from `proto` up to (but not including) `stopAt`,\n * looking for a property descriptor with the given key.\n *\n * @param proto - The prototype to start searching from\n * @param key - The property name to look for\n * @param stopAt - The prototype to stop at (exclusive)\n * @returns The property descriptor if found, or `undefined`\n */\nexport function getPropertyDescriptorFromChain(\n proto: object,\n key: string,\n stopAt: object,\n): PropertyDescriptor | undefined {\n let current: object | null = proto;\n while (current && current !== stopAt) {\n const desc = Object.getOwnPropertyDescriptor(current, key);\n if (desc) return desc;\n current = Object.getPrototypeOf(current);\n }\n return undefined;\n}\n\n/**\n * Wrap all methods on an instance with a tracing function.\n *\n * Walks the prototype chain from the instance up to `stopAt`, wrapping\n * every method (functions with a `value` descriptor) with the `trace` callback.\n * Getters, setters, and the constructor are skipped. When a method exists on\n * multiple prototypes in the chain, the closest (most derived) version wins.\n *\n * Span names follow the `ClassName.methodName` format.\n *\n * @param instance - The object instance whose methods will be wrapped\n * @param trace - The tracing function that wraps each method call\n * @param stopAt - The prototype to stop walking at (typically `BaseService.prototype`)\n *\n * @example\n * ```ts\n * wrapWithTracing(instance, (spanName, fn) => {\n * console.log(`>> ${spanName}`);\n * const result = fn();\n * console.log(`<< ${spanName}`);\n * return result;\n * }, BaseService.prototype);\n * ```\n */\nexport function wrapWithTracing(\n instance: object,\n trace: TraceFn,\n stopAt: object,\n): void {\n const className = instance.constructor.name;\n const seen = new Set<string>();\n let proto: object | null = Object.getPrototypeOf(instance);\n\n while (proto && proto !== stopAt) {\n for (const key of Object.getOwnPropertyNames(proto)) {\n if (key === \"constructor\" || seen.has(key)) continue;\n seen.add(key);\n\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (!desc || !desc.value || typeof desc.value !== \"function\") continue;\n\n const original = desc.value as (...args: unknown[]) => unknown;\n const spanName = `${className}.${key}`;\n\n (instance as Record<string, unknown>)[key] = function (\n this: unknown,\n ...args: unknown[]\n ) {\n return trace(spanName, () => original.apply(this, args));\n };\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n","import {\n BaseService,\n type Constructor,\n type ContainerConfig,\n type IContainer,\n type ServiceDependencies,\n} from \"./base.js\";\nimport { TRACED_MARKER } from \"./decorators.js\";\nimport { wrapWithTracing } from \"./helpers.js\";\n\n/**\n * A dependency injection container that lazily creates and caches singleton service instances.\n *\n * Supports both a global singleton (via {@link Container.getGlobalInstance}) and\n * isolated instances (via {@link Container.create}) for test environments.\n *\n * @typeParam TDeps - User-defined dependency interface\n *\n * @example\n * ```ts\n * // Production usage\n * const container = Container.create<ContainerDeps>({ db, logger });\n * const auth = container.get(AuthService);\n *\n * // Test usage (isolated)\n * const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });\n * testContainer.register(UserService, mockUserService);\n * const auth = testContainer.get(AuthService);\n * ```\n */\nexport class Container<TDeps = unknown> implements IContainer {\n private static globalInstance: Container<unknown> | undefined;\n\n private instances = new Map<Constructor, unknown>();\n private deps: TDeps | undefined;\n private config: ContainerConfig<TDeps>;\n\n private constructor(config: ContainerConfig<TDeps> = {}) {\n this.config = config;\n this.deps = config.deps;\n }\n\n /**\n * Create a new container instance.\n *\n * Accepts either a plain deps object or a {@link ContainerConfig} with\n * additional options like `trace`.\n * When called with no arguments, the container defers resolution\n * until {@link setDeps} is called.\n *\n * @typeParam TDeps - User-defined dependency interface\n * @param depsOrConfig - Dependencies or full configuration object\n * @returns A new isolated container instance\n *\n * @example\n * ```ts\n * // Simple — pass deps directly\n * const container = Container.create<ContainerDeps>({ db, logger });\n *\n * // With config — pass options alongside deps\n * const container = Container.create<ContainerDeps>({\n * deps: { db, logger },\n * trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),\n * });\n *\n * // Lazy — set deps later\n * const container = Container.create<ContainerDeps>();\n * container.setDeps({ db, logger });\n * ```\n */\n static create<TDeps>(\n depsOrConfig?: TDeps | ContainerConfig<TDeps>,\n ): Container<TDeps> {\n if (\n depsOrConfig &&\n typeof depsOrConfig === \"object\" &&\n (\"deps\" in depsOrConfig || \"trace\" in depsOrConfig)\n ) {\n return new Container<TDeps>(depsOrConfig as ContainerConfig<TDeps>);\n }\n return new Container<TDeps>({\n deps: depsOrConfig as TDeps | undefined,\n });\n }\n\n /**\n * Get the global singleton container instance.\n *\n * Creates one on first call. Useful for module-level exports that\n * need a container reference before deps are available.\n *\n * @typeParam TDeps - User-defined dependency interface\n * @returns The global container instance\n */\n static getGlobalInstance<TDeps>(): Container<TDeps> {\n if (!Container.globalInstance) {\n Container.globalInstance = new Container<TDeps>();\n }\n return Container.globalInstance as Container<TDeps>;\n }\n\n /**\n * Reset the global singleton container.\n *\n * Clears all cached instances and removes the global reference.\n * Primarily used for test cleanup.\n */\n static resetGlobal(): void {\n Container.globalInstance?.clear();\n Container.globalInstance = undefined;\n }\n\n /**\n * Set or replace the dependencies after container creation.\n *\n * Useful for lazy initialization patterns where the container\n * is created before deps are available.\n *\n * @param deps - The user-defined dependencies\n */\n setDeps(deps: TDeps): void {\n this.deps = deps;\n }\n\n /**\n * Resolve a service by its class constructor.\n *\n * On first call, lazily constructs the service with `{ ...deps, registry: this }`\n * and caches the singleton. Subsequent calls return the cached instance.\n *\n * If the service was decorated with `@Service({ trace: true })`, all its methods\n * are automatically wrapped with the configured `trace` function.\n *\n * @typeParam T - The service type\n * @param serviceClass - The class constructor to resolve\n * @returns The singleton instance\n * @throws If deps have not been set\n * @throws If the service has tracing enabled but no `trace` function was configured\n */\n get<T>(serviceClass: Constructor<T>): T {\n const existing = this.instances.get(serviceClass);\n if (existing) return existing as T;\n\n if (!this.deps) {\n throw new Error(\n \"Container deps not set. Call setDeps() or pass deps to Container.create().\",\n );\n }\n\n const serviceDeps: ServiceDependencies<TDeps> = {\n ...this.deps,\n registry: this,\n };\n\n const instance = new serviceClass(serviceDeps);\n\n // Apply tracing if @Service({ trace: true }) was used\n if ((serviceClass as unknown as Record<symbol, unknown>)[TRACED_MARKER]) {\n if (!this.config.trace) {\n throw new Error(\n `Service \"${serviceClass.name}\" has trace enabled but no trace function was provided. Pass { trace } to Container.create().`,\n );\n }\n wrapWithTracing(\n instance as object,\n this.config.trace,\n BaseService.prototype,\n );\n }\n\n this.instances.set(serviceClass, instance);\n return instance;\n }\n\n /**\n * Register a pre-built instance for a service class.\n *\n * The registered instance is returned by {@link get} without constructing.\n * Primarily used for injecting test mocks.\n *\n * @typeParam T - The service type\n * @param serviceClass - The class constructor to associate with the instance\n * @param instance - The pre-built instance to register\n *\n * @example\n * ```ts\n * const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });\n * testContainer.register(UserService, mockUserService);\n * const auth = testContainer.get(AuthService); // uses mockUserService via @Inject\n * ```\n */\n register<T>(serviceClass: Constructor<T>, instance: T): void {\n this.instances.set(serviceClass, instance);\n }\n\n /**\n * Clear all cached service instances.\n *\n * After calling this, the next {@link get} call for any service\n * will construct a fresh instance. Does not clear deps or config.\n */\n clear(): void {\n this.instances.clear();\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/** A class constructor type. */
|
|
2
|
+
type Constructor<T = unknown> = new (...args: any[]) => T;
|
|
3
|
+
/** A pluggable tracing function signature. */
|
|
4
|
+
type TraceFn = (spanName: string, fn: () => unknown) => unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Minimal interface representing a DI container.
|
|
7
|
+
*
|
|
8
|
+
* Services receive this as `this.registry` to resolve other services
|
|
9
|
+
* without coupling to the full {@link Container} implementation.
|
|
10
|
+
*/
|
|
11
|
+
interface IContainer {
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a service by its class constructor.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam T - The service type
|
|
16
|
+
* @param serviceClass - The class constructor to resolve
|
|
17
|
+
* @returns The singleton instance of the service
|
|
18
|
+
*/
|
|
19
|
+
get<T>(serviceClass: Constructor<T>): T;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The full set of dependencies passed to a service constructor.
|
|
23
|
+
*
|
|
24
|
+
* Combines the user-defined `TDeps` with the container's `registry`,
|
|
25
|
+
* which is injected automatically by the container.
|
|
26
|
+
*
|
|
27
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
28
|
+
*/
|
|
29
|
+
type ServiceDependencies<TDeps> = TDeps & {
|
|
30
|
+
registry: IContainer;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Configuration object for {@link Container.create}.
|
|
34
|
+
*
|
|
35
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
36
|
+
*/
|
|
37
|
+
interface ContainerConfig<TDeps> {
|
|
38
|
+
/** The user-defined dependencies to inject into services. */
|
|
39
|
+
deps?: TDeps;
|
|
40
|
+
/**
|
|
41
|
+
* Optional tracing function for services decorated with `@Service({ trace: true })`.
|
|
42
|
+
* When a traced service is resolved, all its methods are wrapped with this function.
|
|
43
|
+
*
|
|
44
|
+
* @param spanName - The span name in `ClassName.methodName` format
|
|
45
|
+
* @param fn - The original method to execute within the span
|
|
46
|
+
* @returns The return value of `fn`
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const container = Container.create<ContainerDeps>({
|
|
51
|
+
* deps: { db, logger },
|
|
52
|
+
* trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
trace?: TraceFn;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Abstract base class that all container-managed services extend.
|
|
60
|
+
*
|
|
61
|
+
* Receives injected dependencies via its constructor and exposes them
|
|
62
|
+
* as `this.deps` (user-defined) and `this.registry` (the container).
|
|
63
|
+
*
|
|
64
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* interface ContainerDeps {
|
|
69
|
+
* db: DatabaseClient;
|
|
70
|
+
* logger: Logger;
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* @Service()
|
|
74
|
+
* class UserService extends BaseService<ContainerDeps> {
|
|
75
|
+
* findById(id: string) {
|
|
76
|
+
* return this.deps.db.users.findUnique({ where: { id } });
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare abstract class BaseService<TDeps = unknown> {
|
|
82
|
+
/** The user-defined dependencies (everything except `registry`). */
|
|
83
|
+
protected readonly deps: TDeps;
|
|
84
|
+
/** The container instance, used internally by {@link Inject} to resolve other services. */
|
|
85
|
+
protected readonly registry: IContainer;
|
|
86
|
+
constructor(dependencies: ServiceDependencies<TDeps>);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A dependency injection container that lazily creates and caches singleton service instances.
|
|
91
|
+
*
|
|
92
|
+
* Supports both a global singleton (via {@link Container.getGlobalInstance}) and
|
|
93
|
+
* isolated instances (via {@link Container.create}) for test environments.
|
|
94
|
+
*
|
|
95
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* // Production usage
|
|
100
|
+
* const container = Container.create<ContainerDeps>({ db, logger });
|
|
101
|
+
* const auth = container.get(AuthService);
|
|
102
|
+
*
|
|
103
|
+
* // Test usage (isolated)
|
|
104
|
+
* const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });
|
|
105
|
+
* testContainer.register(UserService, mockUserService);
|
|
106
|
+
* const auth = testContainer.get(AuthService);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare class Container<TDeps = unknown> implements IContainer {
|
|
110
|
+
private static globalInstance;
|
|
111
|
+
private instances;
|
|
112
|
+
private deps;
|
|
113
|
+
private config;
|
|
114
|
+
private constructor();
|
|
115
|
+
/**
|
|
116
|
+
* Create a new container instance.
|
|
117
|
+
*
|
|
118
|
+
* Accepts either a plain deps object or a {@link ContainerConfig} with
|
|
119
|
+
* additional options like `trace`.
|
|
120
|
+
* When called with no arguments, the container defers resolution
|
|
121
|
+
* until {@link setDeps} is called.
|
|
122
|
+
*
|
|
123
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
124
|
+
* @param depsOrConfig - Dependencies or full configuration object
|
|
125
|
+
* @returns A new isolated container instance
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* // Simple — pass deps directly
|
|
130
|
+
* const container = Container.create<ContainerDeps>({ db, logger });
|
|
131
|
+
*
|
|
132
|
+
* // With config — pass options alongside deps
|
|
133
|
+
* const container = Container.create<ContainerDeps>({
|
|
134
|
+
* deps: { db, logger },
|
|
135
|
+
* trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* // Lazy — set deps later
|
|
139
|
+
* const container = Container.create<ContainerDeps>();
|
|
140
|
+
* container.setDeps({ db, logger });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
static create<TDeps>(depsOrConfig?: TDeps | ContainerConfig<TDeps>): Container<TDeps>;
|
|
144
|
+
/**
|
|
145
|
+
* Get the global singleton container instance.
|
|
146
|
+
*
|
|
147
|
+
* Creates one on first call. Useful for module-level exports that
|
|
148
|
+
* need a container reference before deps are available.
|
|
149
|
+
*
|
|
150
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
151
|
+
* @returns The global container instance
|
|
152
|
+
*/
|
|
153
|
+
static getGlobalInstance<TDeps>(): Container<TDeps>;
|
|
154
|
+
/**
|
|
155
|
+
* Reset the global singleton container.
|
|
156
|
+
*
|
|
157
|
+
* Clears all cached instances and removes the global reference.
|
|
158
|
+
* Primarily used for test cleanup.
|
|
159
|
+
*/
|
|
160
|
+
static resetGlobal(): void;
|
|
161
|
+
/**
|
|
162
|
+
* Set or replace the dependencies after container creation.
|
|
163
|
+
*
|
|
164
|
+
* Useful for lazy initialization patterns where the container
|
|
165
|
+
* is created before deps are available.
|
|
166
|
+
*
|
|
167
|
+
* @param deps - The user-defined dependencies
|
|
168
|
+
*/
|
|
169
|
+
setDeps(deps: TDeps): void;
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a service by its class constructor.
|
|
172
|
+
*
|
|
173
|
+
* On first call, lazily constructs the service with `{ ...deps, registry: this }`
|
|
174
|
+
* and caches the singleton. Subsequent calls return the cached instance.
|
|
175
|
+
*
|
|
176
|
+
* If the service was decorated with `@Service({ trace: true })`, all its methods
|
|
177
|
+
* are automatically wrapped with the configured `trace` function.
|
|
178
|
+
*
|
|
179
|
+
* @typeParam T - The service type
|
|
180
|
+
* @param serviceClass - The class constructor to resolve
|
|
181
|
+
* @returns The singleton instance
|
|
182
|
+
* @throws If deps have not been set
|
|
183
|
+
* @throws If the service has tracing enabled but no `trace` function was configured
|
|
184
|
+
*/
|
|
185
|
+
get<T>(serviceClass: Constructor<T>): T;
|
|
186
|
+
/**
|
|
187
|
+
* Register a pre-built instance for a service class.
|
|
188
|
+
*
|
|
189
|
+
* The registered instance is returned by {@link get} without constructing.
|
|
190
|
+
* Primarily used for injecting test mocks.
|
|
191
|
+
*
|
|
192
|
+
* @typeParam T - The service type
|
|
193
|
+
* @param serviceClass - The class constructor to associate with the instance
|
|
194
|
+
* @param instance - The pre-built instance to register
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });
|
|
199
|
+
* testContainer.register(UserService, mockUserService);
|
|
200
|
+
* const auth = testContainer.get(AuthService); // uses mockUserService via @Inject
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
register<T>(serviceClass: Constructor<T>, instance: T): void;
|
|
204
|
+
/**
|
|
205
|
+
* Clear all cached service instances.
|
|
206
|
+
*
|
|
207
|
+
* After calling this, the next {@link get} call for any service
|
|
208
|
+
* will construct a fresh instance. Does not clear deps or config.
|
|
209
|
+
*/
|
|
210
|
+
clear(): void;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Options for the {@link Service} decorator.
|
|
215
|
+
*/
|
|
216
|
+
interface ServiceOptions {
|
|
217
|
+
/**
|
|
218
|
+
* When `true`, all methods on this service are automatically wrapped
|
|
219
|
+
* with the container's `trace` function after construction.
|
|
220
|
+
*
|
|
221
|
+
* Requires a `trace` function in the {@link ContainerConfig}.
|
|
222
|
+
*
|
|
223
|
+
* @defaultValue `false`
|
|
224
|
+
*/
|
|
225
|
+
trace?: boolean;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Class decorator that marks a class as container-managed.
|
|
229
|
+
*
|
|
230
|
+
* When `trace` is enabled, the container will wrap all methods
|
|
231
|
+
* with tracing spans using the configured trace function.
|
|
232
|
+
*
|
|
233
|
+
* Requires `experimentalDecorators: true` in tsconfig.json.
|
|
234
|
+
*
|
|
235
|
+
* @param options - Optional configuration for the service
|
|
236
|
+
* @returns A class decorator
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* @Service()
|
|
241
|
+
* class UserService extends BaseService<ContainerDeps> {
|
|
242
|
+
* findById(id: string) {
|
|
243
|
+
* return this.deps.db.users.findUnique({ where: { id } });
|
|
244
|
+
* }
|
|
245
|
+
* }
|
|
246
|
+
*
|
|
247
|
+
* @Service({ trace: true })
|
|
248
|
+
* class TracedService extends BaseService<ContainerDeps> {
|
|
249
|
+
* // All methods auto-traced as "TracedService.methodName"
|
|
250
|
+
* process() { ... }
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function Service(options?: ServiceOptions): ClassDecorator;
|
|
255
|
+
/**
|
|
256
|
+
* Property decorator for declarative inter-service dependency injection.
|
|
257
|
+
*
|
|
258
|
+
* Defines a lazy getter that resolves the dependency from the same container
|
|
259
|
+
* on first access and caches it per-instance. Uses a factory function
|
|
260
|
+
* (`() => X` instead of `X` directly) to avoid circular import issues
|
|
261
|
+
* between service files.
|
|
262
|
+
*
|
|
263
|
+
* Requires `experimentalDecorators: true` in tsconfig.json.
|
|
264
|
+
*
|
|
265
|
+
* @typeParam T - The injected service type
|
|
266
|
+
* @param factory - A factory function returning the service class constructor.
|
|
267
|
+
* Called lazily on first property access.
|
|
268
|
+
* @returns A property decorator
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* @Service()
|
|
273
|
+
* class AuthService extends BaseService<ContainerDeps> {
|
|
274
|
+
* @Inject(() => UserService)
|
|
275
|
+
* declare readonly userService: UserService;
|
|
276
|
+
*
|
|
277
|
+
* @Inject(() => TokenService)
|
|
278
|
+
* declare readonly tokenService: TokenService;
|
|
279
|
+
*
|
|
280
|
+
* authenticate(token: string) {
|
|
281
|
+
* const payload = this.tokenService.verify(token);
|
|
282
|
+
* return this.userService.findById(payload.userId);
|
|
283
|
+
* }
|
|
284
|
+
* }
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
declare function Inject<T>(factory: () => Constructor<T>): (_target: object, propertyKey: string | symbol) => void;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Walk the prototype chain from `proto` up to (but not including) `stopAt`,
|
|
291
|
+
* looking for a property descriptor with the given key.
|
|
292
|
+
*
|
|
293
|
+
* @param proto - The prototype to start searching from
|
|
294
|
+
* @param key - The property name to look for
|
|
295
|
+
* @param stopAt - The prototype to stop at (exclusive)
|
|
296
|
+
* @returns The property descriptor if found, or `undefined`
|
|
297
|
+
*/
|
|
298
|
+
declare function getPropertyDescriptorFromChain(proto: object, key: string, stopAt: object): PropertyDescriptor | undefined;
|
|
299
|
+
/**
|
|
300
|
+
* Wrap all methods on an instance with a tracing function.
|
|
301
|
+
*
|
|
302
|
+
* Walks the prototype chain from the instance up to `stopAt`, wrapping
|
|
303
|
+
* every method (functions with a `value` descriptor) with the `trace` callback.
|
|
304
|
+
* Getters, setters, and the constructor are skipped. When a method exists on
|
|
305
|
+
* multiple prototypes in the chain, the closest (most derived) version wins.
|
|
306
|
+
*
|
|
307
|
+
* Span names follow the `ClassName.methodName` format.
|
|
308
|
+
*
|
|
309
|
+
* @param instance - The object instance whose methods will be wrapped
|
|
310
|
+
* @param trace - The tracing function that wraps each method call
|
|
311
|
+
* @param stopAt - The prototype to stop walking at (typically `BaseService.prototype`)
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* wrapWithTracing(instance, (spanName, fn) => {
|
|
316
|
+
* console.log(`>> ${spanName}`);
|
|
317
|
+
* const result = fn();
|
|
318
|
+
* console.log(`<< ${spanName}`);
|
|
319
|
+
* return result;
|
|
320
|
+
* }, BaseService.prototype);
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
declare function wrapWithTracing(instance: object, trace: TraceFn, stopAt: object): void;
|
|
324
|
+
|
|
325
|
+
export { BaseService, type Constructor, Container, type ContainerConfig, type IContainer, Inject, Service, type ServiceDependencies, type ServiceOptions, type TraceFn, getPropertyDescriptorFromChain, wrapWithTracing };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/** A class constructor type. */
|
|
2
|
+
type Constructor<T = unknown> = new (...args: any[]) => T;
|
|
3
|
+
/** A pluggable tracing function signature. */
|
|
4
|
+
type TraceFn = (spanName: string, fn: () => unknown) => unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Minimal interface representing a DI container.
|
|
7
|
+
*
|
|
8
|
+
* Services receive this as `this.registry` to resolve other services
|
|
9
|
+
* without coupling to the full {@link Container} implementation.
|
|
10
|
+
*/
|
|
11
|
+
interface IContainer {
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a service by its class constructor.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam T - The service type
|
|
16
|
+
* @param serviceClass - The class constructor to resolve
|
|
17
|
+
* @returns The singleton instance of the service
|
|
18
|
+
*/
|
|
19
|
+
get<T>(serviceClass: Constructor<T>): T;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The full set of dependencies passed to a service constructor.
|
|
23
|
+
*
|
|
24
|
+
* Combines the user-defined `TDeps` with the container's `registry`,
|
|
25
|
+
* which is injected automatically by the container.
|
|
26
|
+
*
|
|
27
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
28
|
+
*/
|
|
29
|
+
type ServiceDependencies<TDeps> = TDeps & {
|
|
30
|
+
registry: IContainer;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Configuration object for {@link Container.create}.
|
|
34
|
+
*
|
|
35
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
36
|
+
*/
|
|
37
|
+
interface ContainerConfig<TDeps> {
|
|
38
|
+
/** The user-defined dependencies to inject into services. */
|
|
39
|
+
deps?: TDeps;
|
|
40
|
+
/**
|
|
41
|
+
* Optional tracing function for services decorated with `@Service({ trace: true })`.
|
|
42
|
+
* When a traced service is resolved, all its methods are wrapped with this function.
|
|
43
|
+
*
|
|
44
|
+
* @param spanName - The span name in `ClassName.methodName` format
|
|
45
|
+
* @param fn - The original method to execute within the span
|
|
46
|
+
* @returns The return value of `fn`
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const container = Container.create<ContainerDeps>({
|
|
51
|
+
* deps: { db, logger },
|
|
52
|
+
* trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
trace?: TraceFn;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Abstract base class that all container-managed services extend.
|
|
60
|
+
*
|
|
61
|
+
* Receives injected dependencies via its constructor and exposes them
|
|
62
|
+
* as `this.deps` (user-defined) and `this.registry` (the container).
|
|
63
|
+
*
|
|
64
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* interface ContainerDeps {
|
|
69
|
+
* db: DatabaseClient;
|
|
70
|
+
* logger: Logger;
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* @Service()
|
|
74
|
+
* class UserService extends BaseService<ContainerDeps> {
|
|
75
|
+
* findById(id: string) {
|
|
76
|
+
* return this.deps.db.users.findUnique({ where: { id } });
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare abstract class BaseService<TDeps = unknown> {
|
|
82
|
+
/** The user-defined dependencies (everything except `registry`). */
|
|
83
|
+
protected readonly deps: TDeps;
|
|
84
|
+
/** The container instance, used internally by {@link Inject} to resolve other services. */
|
|
85
|
+
protected readonly registry: IContainer;
|
|
86
|
+
constructor(dependencies: ServiceDependencies<TDeps>);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A dependency injection container that lazily creates and caches singleton service instances.
|
|
91
|
+
*
|
|
92
|
+
* Supports both a global singleton (via {@link Container.getGlobalInstance}) and
|
|
93
|
+
* isolated instances (via {@link Container.create}) for test environments.
|
|
94
|
+
*
|
|
95
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* // Production usage
|
|
100
|
+
* const container = Container.create<ContainerDeps>({ db, logger });
|
|
101
|
+
* const auth = container.get(AuthService);
|
|
102
|
+
*
|
|
103
|
+
* // Test usage (isolated)
|
|
104
|
+
* const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });
|
|
105
|
+
* testContainer.register(UserService, mockUserService);
|
|
106
|
+
* const auth = testContainer.get(AuthService);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare class Container<TDeps = unknown> implements IContainer {
|
|
110
|
+
private static globalInstance;
|
|
111
|
+
private instances;
|
|
112
|
+
private deps;
|
|
113
|
+
private config;
|
|
114
|
+
private constructor();
|
|
115
|
+
/**
|
|
116
|
+
* Create a new container instance.
|
|
117
|
+
*
|
|
118
|
+
* Accepts either a plain deps object or a {@link ContainerConfig} with
|
|
119
|
+
* additional options like `trace`.
|
|
120
|
+
* When called with no arguments, the container defers resolution
|
|
121
|
+
* until {@link setDeps} is called.
|
|
122
|
+
*
|
|
123
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
124
|
+
* @param depsOrConfig - Dependencies or full configuration object
|
|
125
|
+
* @returns A new isolated container instance
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* // Simple — pass deps directly
|
|
130
|
+
* const container = Container.create<ContainerDeps>({ db, logger });
|
|
131
|
+
*
|
|
132
|
+
* // With config — pass options alongside deps
|
|
133
|
+
* const container = Container.create<ContainerDeps>({
|
|
134
|
+
* deps: { db, logger },
|
|
135
|
+
* trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* // Lazy — set deps later
|
|
139
|
+
* const container = Container.create<ContainerDeps>();
|
|
140
|
+
* container.setDeps({ db, logger });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
static create<TDeps>(depsOrConfig?: TDeps | ContainerConfig<TDeps>): Container<TDeps>;
|
|
144
|
+
/**
|
|
145
|
+
* Get the global singleton container instance.
|
|
146
|
+
*
|
|
147
|
+
* Creates one on first call. Useful for module-level exports that
|
|
148
|
+
* need a container reference before deps are available.
|
|
149
|
+
*
|
|
150
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
151
|
+
* @returns The global container instance
|
|
152
|
+
*/
|
|
153
|
+
static getGlobalInstance<TDeps>(): Container<TDeps>;
|
|
154
|
+
/**
|
|
155
|
+
* Reset the global singleton container.
|
|
156
|
+
*
|
|
157
|
+
* Clears all cached instances and removes the global reference.
|
|
158
|
+
* Primarily used for test cleanup.
|
|
159
|
+
*/
|
|
160
|
+
static resetGlobal(): void;
|
|
161
|
+
/**
|
|
162
|
+
* Set or replace the dependencies after container creation.
|
|
163
|
+
*
|
|
164
|
+
* Useful for lazy initialization patterns where the container
|
|
165
|
+
* is created before deps are available.
|
|
166
|
+
*
|
|
167
|
+
* @param deps - The user-defined dependencies
|
|
168
|
+
*/
|
|
169
|
+
setDeps(deps: TDeps): void;
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a service by its class constructor.
|
|
172
|
+
*
|
|
173
|
+
* On first call, lazily constructs the service with `{ ...deps, registry: this }`
|
|
174
|
+
* and caches the singleton. Subsequent calls return the cached instance.
|
|
175
|
+
*
|
|
176
|
+
* If the service was decorated with `@Service({ trace: true })`, all its methods
|
|
177
|
+
* are automatically wrapped with the configured `trace` function.
|
|
178
|
+
*
|
|
179
|
+
* @typeParam T - The service type
|
|
180
|
+
* @param serviceClass - The class constructor to resolve
|
|
181
|
+
* @returns The singleton instance
|
|
182
|
+
* @throws If deps have not been set
|
|
183
|
+
* @throws If the service has tracing enabled but no `trace` function was configured
|
|
184
|
+
*/
|
|
185
|
+
get<T>(serviceClass: Constructor<T>): T;
|
|
186
|
+
/**
|
|
187
|
+
* Register a pre-built instance for a service class.
|
|
188
|
+
*
|
|
189
|
+
* The registered instance is returned by {@link get} without constructing.
|
|
190
|
+
* Primarily used for injecting test mocks.
|
|
191
|
+
*
|
|
192
|
+
* @typeParam T - The service type
|
|
193
|
+
* @param serviceClass - The class constructor to associate with the instance
|
|
194
|
+
* @param instance - The pre-built instance to register
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });
|
|
199
|
+
* testContainer.register(UserService, mockUserService);
|
|
200
|
+
* const auth = testContainer.get(AuthService); // uses mockUserService via @Inject
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
register<T>(serviceClass: Constructor<T>, instance: T): void;
|
|
204
|
+
/**
|
|
205
|
+
* Clear all cached service instances.
|
|
206
|
+
*
|
|
207
|
+
* After calling this, the next {@link get} call for any service
|
|
208
|
+
* will construct a fresh instance. Does not clear deps or config.
|
|
209
|
+
*/
|
|
210
|
+
clear(): void;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Options for the {@link Service} decorator.
|
|
215
|
+
*/
|
|
216
|
+
interface ServiceOptions {
|
|
217
|
+
/**
|
|
218
|
+
* When `true`, all methods on this service are automatically wrapped
|
|
219
|
+
* with the container's `trace` function after construction.
|
|
220
|
+
*
|
|
221
|
+
* Requires a `trace` function in the {@link ContainerConfig}.
|
|
222
|
+
*
|
|
223
|
+
* @defaultValue `false`
|
|
224
|
+
*/
|
|
225
|
+
trace?: boolean;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Class decorator that marks a class as container-managed.
|
|
229
|
+
*
|
|
230
|
+
* When `trace` is enabled, the container will wrap all methods
|
|
231
|
+
* with tracing spans using the configured trace function.
|
|
232
|
+
*
|
|
233
|
+
* Requires `experimentalDecorators: true` in tsconfig.json.
|
|
234
|
+
*
|
|
235
|
+
* @param options - Optional configuration for the service
|
|
236
|
+
* @returns A class decorator
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* @Service()
|
|
241
|
+
* class UserService extends BaseService<ContainerDeps> {
|
|
242
|
+
* findById(id: string) {
|
|
243
|
+
* return this.deps.db.users.findUnique({ where: { id } });
|
|
244
|
+
* }
|
|
245
|
+
* }
|
|
246
|
+
*
|
|
247
|
+
* @Service({ trace: true })
|
|
248
|
+
* class TracedService extends BaseService<ContainerDeps> {
|
|
249
|
+
* // All methods auto-traced as "TracedService.methodName"
|
|
250
|
+
* process() { ... }
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function Service(options?: ServiceOptions): ClassDecorator;
|
|
255
|
+
/**
|
|
256
|
+
* Property decorator for declarative inter-service dependency injection.
|
|
257
|
+
*
|
|
258
|
+
* Defines a lazy getter that resolves the dependency from the same container
|
|
259
|
+
* on first access and caches it per-instance. Uses a factory function
|
|
260
|
+
* (`() => X` instead of `X` directly) to avoid circular import issues
|
|
261
|
+
* between service files.
|
|
262
|
+
*
|
|
263
|
+
* Requires `experimentalDecorators: true` in tsconfig.json.
|
|
264
|
+
*
|
|
265
|
+
* @typeParam T - The injected service type
|
|
266
|
+
* @param factory - A factory function returning the service class constructor.
|
|
267
|
+
* Called lazily on first property access.
|
|
268
|
+
* @returns A property decorator
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* @Service()
|
|
273
|
+
* class AuthService extends BaseService<ContainerDeps> {
|
|
274
|
+
* @Inject(() => UserService)
|
|
275
|
+
* declare readonly userService: UserService;
|
|
276
|
+
*
|
|
277
|
+
* @Inject(() => TokenService)
|
|
278
|
+
* declare readonly tokenService: TokenService;
|
|
279
|
+
*
|
|
280
|
+
* authenticate(token: string) {
|
|
281
|
+
* const payload = this.tokenService.verify(token);
|
|
282
|
+
* return this.userService.findById(payload.userId);
|
|
283
|
+
* }
|
|
284
|
+
* }
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
declare function Inject<T>(factory: () => Constructor<T>): (_target: object, propertyKey: string | symbol) => void;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Walk the prototype chain from `proto` up to (but not including) `stopAt`,
|
|
291
|
+
* looking for a property descriptor with the given key.
|
|
292
|
+
*
|
|
293
|
+
* @param proto - The prototype to start searching from
|
|
294
|
+
* @param key - The property name to look for
|
|
295
|
+
* @param stopAt - The prototype to stop at (exclusive)
|
|
296
|
+
* @returns The property descriptor if found, or `undefined`
|
|
297
|
+
*/
|
|
298
|
+
declare function getPropertyDescriptorFromChain(proto: object, key: string, stopAt: object): PropertyDescriptor | undefined;
|
|
299
|
+
/**
|
|
300
|
+
* Wrap all methods on an instance with a tracing function.
|
|
301
|
+
*
|
|
302
|
+
* Walks the prototype chain from the instance up to `stopAt`, wrapping
|
|
303
|
+
* every method (functions with a `value` descriptor) with the `trace` callback.
|
|
304
|
+
* Getters, setters, and the constructor are skipped. When a method exists on
|
|
305
|
+
* multiple prototypes in the chain, the closest (most derived) version wins.
|
|
306
|
+
*
|
|
307
|
+
* Span names follow the `ClassName.methodName` format.
|
|
308
|
+
*
|
|
309
|
+
* @param instance - The object instance whose methods will be wrapped
|
|
310
|
+
* @param trace - The tracing function that wraps each method call
|
|
311
|
+
* @param stopAt - The prototype to stop walking at (typically `BaseService.prototype`)
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* wrapWithTracing(instance, (spanName, fn) => {
|
|
316
|
+
* console.log(`>> ${spanName}`);
|
|
317
|
+
* const result = fn();
|
|
318
|
+
* console.log(`<< ${spanName}`);
|
|
319
|
+
* return result;
|
|
320
|
+
* }, BaseService.prototype);
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
declare function wrapWithTracing(instance: object, trace: TraceFn, stopAt: object): void;
|
|
324
|
+
|
|
325
|
+
export { BaseService, type Constructor, Container, type ContainerConfig, type IContainer, Inject, Service, type ServiceDependencies, type ServiceOptions, type TraceFn, getPropertyDescriptorFromChain, wrapWithTracing };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// src/base.ts
|
|
2
|
+
var BaseService = class {
|
|
3
|
+
/** The user-defined dependencies (everything except `registry`). */
|
|
4
|
+
deps;
|
|
5
|
+
/** The container instance, used internally by {@link Inject} to resolve other services. */
|
|
6
|
+
registry;
|
|
7
|
+
constructor(dependencies) {
|
|
8
|
+
const { registry, ...rest } = dependencies;
|
|
9
|
+
this.deps = rest;
|
|
10
|
+
this.registry = registry;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/decorators.ts
|
|
15
|
+
var TRACED_MARKER = /* @__PURE__ */ Symbol("__service_traced__");
|
|
16
|
+
function Service(options) {
|
|
17
|
+
return (target) => {
|
|
18
|
+
if (options?.trace) {
|
|
19
|
+
target[TRACED_MARKER] = true;
|
|
20
|
+
}
|
|
21
|
+
return target;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function Inject(factory) {
|
|
25
|
+
return (_target, propertyKey) => {
|
|
26
|
+
const cacheKey = /* @__PURE__ */ Symbol(`__inject_${String(propertyKey)}`);
|
|
27
|
+
Object.defineProperty(_target, propertyKey, {
|
|
28
|
+
get() {
|
|
29
|
+
const cached = this[cacheKey];
|
|
30
|
+
if (cached !== void 0) return cached;
|
|
31
|
+
const resolved = this.registry.get(factory());
|
|
32
|
+
this[cacheKey] = resolved;
|
|
33
|
+
return resolved;
|
|
34
|
+
},
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/helpers.ts
|
|
42
|
+
function getPropertyDescriptorFromChain(proto, key, stopAt) {
|
|
43
|
+
let current = proto;
|
|
44
|
+
while (current && current !== stopAt) {
|
|
45
|
+
const desc = Object.getOwnPropertyDescriptor(current, key);
|
|
46
|
+
if (desc) return desc;
|
|
47
|
+
current = Object.getPrototypeOf(current);
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
function wrapWithTracing(instance, trace, stopAt) {
|
|
52
|
+
const className = instance.constructor.name;
|
|
53
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54
|
+
let proto = Object.getPrototypeOf(instance);
|
|
55
|
+
while (proto && proto !== stopAt) {
|
|
56
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
57
|
+
if (key === "constructor" || seen.has(key)) continue;
|
|
58
|
+
seen.add(key);
|
|
59
|
+
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
|
60
|
+
if (!desc || !desc.value || typeof desc.value !== "function") continue;
|
|
61
|
+
const original = desc.value;
|
|
62
|
+
const spanName = `${className}.${key}`;
|
|
63
|
+
instance[key] = function(...args) {
|
|
64
|
+
return trace(spanName, () => original.apply(this, args));
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
proto = Object.getPrototypeOf(proto);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/container.ts
|
|
72
|
+
var Container = class _Container {
|
|
73
|
+
static globalInstance;
|
|
74
|
+
instances = /* @__PURE__ */ new Map();
|
|
75
|
+
deps;
|
|
76
|
+
config;
|
|
77
|
+
constructor(config = {}) {
|
|
78
|
+
this.config = config;
|
|
79
|
+
this.deps = config.deps;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a new container instance.
|
|
83
|
+
*
|
|
84
|
+
* Accepts either a plain deps object or a {@link ContainerConfig} with
|
|
85
|
+
* additional options like `trace`.
|
|
86
|
+
* When called with no arguments, the container defers resolution
|
|
87
|
+
* until {@link setDeps} is called.
|
|
88
|
+
*
|
|
89
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
90
|
+
* @param depsOrConfig - Dependencies or full configuration object
|
|
91
|
+
* @returns A new isolated container instance
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* // Simple — pass deps directly
|
|
96
|
+
* const container = Container.create<ContainerDeps>({ db, logger });
|
|
97
|
+
*
|
|
98
|
+
* // With config — pass options alongside deps
|
|
99
|
+
* const container = Container.create<ContainerDeps>({
|
|
100
|
+
* deps: { db, logger },
|
|
101
|
+
* trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
104
|
+
* // Lazy — set deps later
|
|
105
|
+
* const container = Container.create<ContainerDeps>();
|
|
106
|
+
* container.setDeps({ db, logger });
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
static create(depsOrConfig) {
|
|
110
|
+
if (depsOrConfig && typeof depsOrConfig === "object" && ("deps" in depsOrConfig || "trace" in depsOrConfig)) {
|
|
111
|
+
return new _Container(depsOrConfig);
|
|
112
|
+
}
|
|
113
|
+
return new _Container({
|
|
114
|
+
deps: depsOrConfig
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get the global singleton container instance.
|
|
119
|
+
*
|
|
120
|
+
* Creates one on first call. Useful for module-level exports that
|
|
121
|
+
* need a container reference before deps are available.
|
|
122
|
+
*
|
|
123
|
+
* @typeParam TDeps - User-defined dependency interface
|
|
124
|
+
* @returns The global container instance
|
|
125
|
+
*/
|
|
126
|
+
static getGlobalInstance() {
|
|
127
|
+
if (!_Container.globalInstance) {
|
|
128
|
+
_Container.globalInstance = new _Container();
|
|
129
|
+
}
|
|
130
|
+
return _Container.globalInstance;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Reset the global singleton container.
|
|
134
|
+
*
|
|
135
|
+
* Clears all cached instances and removes the global reference.
|
|
136
|
+
* Primarily used for test cleanup.
|
|
137
|
+
*/
|
|
138
|
+
static resetGlobal() {
|
|
139
|
+
_Container.globalInstance?.clear();
|
|
140
|
+
_Container.globalInstance = void 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Set or replace the dependencies after container creation.
|
|
144
|
+
*
|
|
145
|
+
* Useful for lazy initialization patterns where the container
|
|
146
|
+
* is created before deps are available.
|
|
147
|
+
*
|
|
148
|
+
* @param deps - The user-defined dependencies
|
|
149
|
+
*/
|
|
150
|
+
setDeps(deps) {
|
|
151
|
+
this.deps = deps;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve a service by its class constructor.
|
|
155
|
+
*
|
|
156
|
+
* On first call, lazily constructs the service with `{ ...deps, registry: this }`
|
|
157
|
+
* and caches the singleton. Subsequent calls return the cached instance.
|
|
158
|
+
*
|
|
159
|
+
* If the service was decorated with `@Service({ trace: true })`, all its methods
|
|
160
|
+
* are automatically wrapped with the configured `trace` function.
|
|
161
|
+
*
|
|
162
|
+
* @typeParam T - The service type
|
|
163
|
+
* @param serviceClass - The class constructor to resolve
|
|
164
|
+
* @returns The singleton instance
|
|
165
|
+
* @throws If deps have not been set
|
|
166
|
+
* @throws If the service has tracing enabled but no `trace` function was configured
|
|
167
|
+
*/
|
|
168
|
+
get(serviceClass) {
|
|
169
|
+
const existing = this.instances.get(serviceClass);
|
|
170
|
+
if (existing) return existing;
|
|
171
|
+
if (!this.deps) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Container deps not set. Call setDeps() or pass deps to Container.create()."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const serviceDeps = {
|
|
177
|
+
...this.deps,
|
|
178
|
+
registry: this
|
|
179
|
+
};
|
|
180
|
+
const instance = new serviceClass(serviceDeps);
|
|
181
|
+
if (serviceClass[TRACED_MARKER]) {
|
|
182
|
+
if (!this.config.trace) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Service "${serviceClass.name}" has trace enabled but no trace function was provided. Pass { trace } to Container.create().`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
wrapWithTracing(
|
|
188
|
+
instance,
|
|
189
|
+
this.config.trace,
|
|
190
|
+
BaseService.prototype
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
this.instances.set(serviceClass, instance);
|
|
194
|
+
return instance;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Register a pre-built instance for a service class.
|
|
198
|
+
*
|
|
199
|
+
* The registered instance is returned by {@link get} without constructing.
|
|
200
|
+
* Primarily used for injecting test mocks.
|
|
201
|
+
*
|
|
202
|
+
* @typeParam T - The service type
|
|
203
|
+
* @param serviceClass - The class constructor to associate with the instance
|
|
204
|
+
* @param instance - The pre-built instance to register
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });
|
|
209
|
+
* testContainer.register(UserService, mockUserService);
|
|
210
|
+
* const auth = testContainer.get(AuthService); // uses mockUserService via @Inject
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
register(serviceClass, instance) {
|
|
214
|
+
this.instances.set(serviceClass, instance);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clear all cached service instances.
|
|
218
|
+
*
|
|
219
|
+
* After calling this, the next {@link get} call for any service
|
|
220
|
+
* will construct a fresh instance. Does not clear deps or config.
|
|
221
|
+
*/
|
|
222
|
+
clear() {
|
|
223
|
+
this.instances.clear();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
export {
|
|
227
|
+
BaseService,
|
|
228
|
+
Container,
|
|
229
|
+
Inject,
|
|
230
|
+
Service,
|
|
231
|
+
getPropertyDescriptorFromChain,
|
|
232
|
+
wrapWithTracing
|
|
233
|
+
};
|
|
234
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/base.ts","../src/decorators.ts","../src/helpers.ts","../src/container.ts"],"sourcesContent":["/** A class constructor type. */\n// biome-ignore lint/suspicious/noExplicitAny: Constructor must accept any args to be generic over all classes\nexport type Constructor<T = unknown> = new (...args: any[]) => T;\n\n/** A pluggable tracing function signature. */\nexport type TraceFn = (spanName: string, fn: () => unknown) => unknown;\n\n/**\n * Minimal interface representing a DI container.\n *\n * Services receive this as `this.registry` to resolve other services\n * without coupling to the full {@link Container} implementation.\n */\nexport interface IContainer {\n /**\n * Resolve a service by its class constructor.\n *\n * @typeParam T - The service type\n * @param serviceClass - The class constructor to resolve\n * @returns The singleton instance of the service\n */\n get<T>(serviceClass: Constructor<T>): T;\n}\n\n/**\n * The full set of dependencies passed to a service constructor.\n *\n * Combines the user-defined `TDeps` with the container's `registry`,\n * which is injected automatically by the container.\n *\n * @typeParam TDeps - User-defined dependency interface\n */\nexport type ServiceDependencies<TDeps> = TDeps & { registry: IContainer };\n\n/**\n * Configuration object for {@link Container.create}.\n *\n * @typeParam TDeps - User-defined dependency interface\n */\nexport interface ContainerConfig<TDeps> {\n /** The user-defined dependencies to inject into services. */\n deps?: TDeps;\n\n /**\n * Optional tracing function for services decorated with `@Service({ trace: true })`.\n * When a traced service is resolved, all its methods are wrapped with this function.\n *\n * @param spanName - The span name in `ClassName.methodName` format\n * @param fn - The original method to execute within the span\n * @returns The return value of `fn`\n *\n * @example\n * ```ts\n * const container = Container.create<ContainerDeps>({\n * deps: { db, logger },\n * trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),\n * });\n * ```\n */\n trace?: TraceFn;\n}\n\n/**\n * Abstract base class that all container-managed services extend.\n *\n * Receives injected dependencies via its constructor and exposes them\n * as `this.deps` (user-defined) and `this.registry` (the container).\n *\n * @typeParam TDeps - User-defined dependency interface\n *\n * @example\n * ```ts\n * interface ContainerDeps {\n * db: DatabaseClient;\n * logger: Logger;\n * }\n *\n * @Service()\n * class UserService extends BaseService<ContainerDeps> {\n * findById(id: string) {\n * return this.deps.db.users.findUnique({ where: { id } });\n * }\n * }\n * ```\n */\nexport abstract class BaseService<TDeps = unknown> {\n /** The user-defined dependencies (everything except `registry`). */\n protected readonly deps: TDeps;\n\n /** The container instance, used internally by {@link Inject} to resolve other services. */\n protected readonly registry: IContainer;\n\n constructor(dependencies: ServiceDependencies<TDeps>) {\n const { registry, ...rest } = dependencies as ServiceDependencies<TDeps>;\n this.deps = rest as unknown as TDeps;\n this.registry = registry;\n }\n}\n","import type { Constructor, IContainer } from \"./base.js\";\n\n/** @internal Symbol used to mark classes decorated with `@Service({ trace: true })`. */\nexport const TRACED_MARKER = Symbol(\"__service_traced__\");\n\n/**\n * Options for the {@link Service} decorator.\n */\nexport interface ServiceOptions {\n /**\n * When `true`, all methods on this service are automatically wrapped\n * with the container's `trace` function after construction.\n *\n * Requires a `trace` function in the {@link ContainerConfig}.\n *\n * @defaultValue `false`\n */\n trace?: boolean;\n}\n\n/**\n * Class decorator that marks a class as container-managed.\n *\n * When `trace` is enabled, the container will wrap all methods\n * with tracing spans using the configured trace function.\n *\n * Requires `experimentalDecorators: true` in tsconfig.json.\n *\n * @param options - Optional configuration for the service\n * @returns A class decorator\n *\n * @example\n * ```ts\n * @Service()\n * class UserService extends BaseService<ContainerDeps> {\n * findById(id: string) {\n * return this.deps.db.users.findUnique({ where: { id } });\n * }\n * }\n *\n * @Service({ trace: true })\n * class TracedService extends BaseService<ContainerDeps> {\n * // All methods auto-traced as \"TracedService.methodName\"\n * process() { ... }\n * }\n * ```\n */\nexport function Service(options?: ServiceOptions): ClassDecorator {\n return (target) => {\n if (options?.trace) {\n (target as unknown as Record<symbol, boolean>)[TRACED_MARKER] = true;\n }\n return target;\n };\n}\n\ntype InjectTarget<T> = { registry: IContainer } & Record<symbol, T>;\n\n/**\n * Property decorator for declarative inter-service dependency injection.\n *\n * Defines a lazy getter that resolves the dependency from the same container\n * on first access and caches it per-instance. Uses a factory function\n * (`() => X` instead of `X` directly) to avoid circular import issues\n * between service files.\n *\n * Requires `experimentalDecorators: true` in tsconfig.json.\n *\n * @typeParam T - The injected service type\n * @param factory - A factory function returning the service class constructor.\n * Called lazily on first property access.\n * @returns A property decorator\n *\n * @example\n * ```ts\n * @Service()\n * class AuthService extends BaseService<ContainerDeps> {\n * @Inject(() => UserService)\n * declare readonly userService: UserService;\n *\n * @Inject(() => TokenService)\n * declare readonly tokenService: TokenService;\n *\n * authenticate(token: string) {\n * const payload = this.tokenService.verify(token);\n * return this.userService.findById(payload.userId);\n * }\n * }\n * ```\n */\nexport function Inject<T>(factory: () => Constructor<T>) {\n return (_target: object, propertyKey: string | symbol): void => {\n const cacheKey = Symbol(`__inject_${String(propertyKey)}`);\n\n Object.defineProperty(_target, propertyKey, {\n get(this: InjectTarget<T>) {\n const cached = this[cacheKey];\n if (cached !== undefined) return cached;\n const resolved = this.registry.get(factory());\n this[cacheKey] = resolved;\n return resolved;\n },\n enumerable: true,\n configurable: true,\n });\n };\n}\n","import type { TraceFn } from \"./base.js\";\n\n/**\n * Walk the prototype chain from `proto` up to (but not including) `stopAt`,\n * looking for a property descriptor with the given key.\n *\n * @param proto - The prototype to start searching from\n * @param key - The property name to look for\n * @param stopAt - The prototype to stop at (exclusive)\n * @returns The property descriptor if found, or `undefined`\n */\nexport function getPropertyDescriptorFromChain(\n proto: object,\n key: string,\n stopAt: object,\n): PropertyDescriptor | undefined {\n let current: object | null = proto;\n while (current && current !== stopAt) {\n const desc = Object.getOwnPropertyDescriptor(current, key);\n if (desc) return desc;\n current = Object.getPrototypeOf(current);\n }\n return undefined;\n}\n\n/**\n * Wrap all methods on an instance with a tracing function.\n *\n * Walks the prototype chain from the instance up to `stopAt`, wrapping\n * every method (functions with a `value` descriptor) with the `trace` callback.\n * Getters, setters, and the constructor are skipped. When a method exists on\n * multiple prototypes in the chain, the closest (most derived) version wins.\n *\n * Span names follow the `ClassName.methodName` format.\n *\n * @param instance - The object instance whose methods will be wrapped\n * @param trace - The tracing function that wraps each method call\n * @param stopAt - The prototype to stop walking at (typically `BaseService.prototype`)\n *\n * @example\n * ```ts\n * wrapWithTracing(instance, (spanName, fn) => {\n * console.log(`>> ${spanName}`);\n * const result = fn();\n * console.log(`<< ${spanName}`);\n * return result;\n * }, BaseService.prototype);\n * ```\n */\nexport function wrapWithTracing(\n instance: object,\n trace: TraceFn,\n stopAt: object,\n): void {\n const className = instance.constructor.name;\n const seen = new Set<string>();\n let proto: object | null = Object.getPrototypeOf(instance);\n\n while (proto && proto !== stopAt) {\n for (const key of Object.getOwnPropertyNames(proto)) {\n if (key === \"constructor\" || seen.has(key)) continue;\n seen.add(key);\n\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (!desc || !desc.value || typeof desc.value !== \"function\") continue;\n\n const original = desc.value as (...args: unknown[]) => unknown;\n const spanName = `${className}.${key}`;\n\n (instance as Record<string, unknown>)[key] = function (\n this: unknown,\n ...args: unknown[]\n ) {\n return trace(spanName, () => original.apply(this, args));\n };\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n","import {\n BaseService,\n type Constructor,\n type ContainerConfig,\n type IContainer,\n type ServiceDependencies,\n} from \"./base.js\";\nimport { TRACED_MARKER } from \"./decorators.js\";\nimport { wrapWithTracing } from \"./helpers.js\";\n\n/**\n * A dependency injection container that lazily creates and caches singleton service instances.\n *\n * Supports both a global singleton (via {@link Container.getGlobalInstance}) and\n * isolated instances (via {@link Container.create}) for test environments.\n *\n * @typeParam TDeps - User-defined dependency interface\n *\n * @example\n * ```ts\n * // Production usage\n * const container = Container.create<ContainerDeps>({ db, logger });\n * const auth = container.get(AuthService);\n *\n * // Test usage (isolated)\n * const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });\n * testContainer.register(UserService, mockUserService);\n * const auth = testContainer.get(AuthService);\n * ```\n */\nexport class Container<TDeps = unknown> implements IContainer {\n private static globalInstance: Container<unknown> | undefined;\n\n private instances = new Map<Constructor, unknown>();\n private deps: TDeps | undefined;\n private config: ContainerConfig<TDeps>;\n\n private constructor(config: ContainerConfig<TDeps> = {}) {\n this.config = config;\n this.deps = config.deps;\n }\n\n /**\n * Create a new container instance.\n *\n * Accepts either a plain deps object or a {@link ContainerConfig} with\n * additional options like `trace`.\n * When called with no arguments, the container defers resolution\n * until {@link setDeps} is called.\n *\n * @typeParam TDeps - User-defined dependency interface\n * @param depsOrConfig - Dependencies or full configuration object\n * @returns A new isolated container instance\n *\n * @example\n * ```ts\n * // Simple — pass deps directly\n * const container = Container.create<ContainerDeps>({ db, logger });\n *\n * // With config — pass options alongside deps\n * const container = Container.create<ContainerDeps>({\n * deps: { db, logger },\n * trace: (spanName, fn) => tracer.startActiveSpan(spanName, () => fn()),\n * });\n *\n * // Lazy — set deps later\n * const container = Container.create<ContainerDeps>();\n * container.setDeps({ db, logger });\n * ```\n */\n static create<TDeps>(\n depsOrConfig?: TDeps | ContainerConfig<TDeps>,\n ): Container<TDeps> {\n if (\n depsOrConfig &&\n typeof depsOrConfig === \"object\" &&\n (\"deps\" in depsOrConfig || \"trace\" in depsOrConfig)\n ) {\n return new Container<TDeps>(depsOrConfig as ContainerConfig<TDeps>);\n }\n return new Container<TDeps>({\n deps: depsOrConfig as TDeps | undefined,\n });\n }\n\n /**\n * Get the global singleton container instance.\n *\n * Creates one on first call. Useful for module-level exports that\n * need a container reference before deps are available.\n *\n * @typeParam TDeps - User-defined dependency interface\n * @returns The global container instance\n */\n static getGlobalInstance<TDeps>(): Container<TDeps> {\n if (!Container.globalInstance) {\n Container.globalInstance = new Container<TDeps>();\n }\n return Container.globalInstance as Container<TDeps>;\n }\n\n /**\n * Reset the global singleton container.\n *\n * Clears all cached instances and removes the global reference.\n * Primarily used for test cleanup.\n */\n static resetGlobal(): void {\n Container.globalInstance?.clear();\n Container.globalInstance = undefined;\n }\n\n /**\n * Set or replace the dependencies after container creation.\n *\n * Useful for lazy initialization patterns where the container\n * is created before deps are available.\n *\n * @param deps - The user-defined dependencies\n */\n setDeps(deps: TDeps): void {\n this.deps = deps;\n }\n\n /**\n * Resolve a service by its class constructor.\n *\n * On first call, lazily constructs the service with `{ ...deps, registry: this }`\n * and caches the singleton. Subsequent calls return the cached instance.\n *\n * If the service was decorated with `@Service({ trace: true })`, all its methods\n * are automatically wrapped with the configured `trace` function.\n *\n * @typeParam T - The service type\n * @param serviceClass - The class constructor to resolve\n * @returns The singleton instance\n * @throws If deps have not been set\n * @throws If the service has tracing enabled but no `trace` function was configured\n */\n get<T>(serviceClass: Constructor<T>): T {\n const existing = this.instances.get(serviceClass);\n if (existing) return existing as T;\n\n if (!this.deps) {\n throw new Error(\n \"Container deps not set. Call setDeps() or pass deps to Container.create().\",\n );\n }\n\n const serviceDeps: ServiceDependencies<TDeps> = {\n ...this.deps,\n registry: this,\n };\n\n const instance = new serviceClass(serviceDeps);\n\n // Apply tracing if @Service({ trace: true }) was used\n if ((serviceClass as unknown as Record<symbol, unknown>)[TRACED_MARKER]) {\n if (!this.config.trace) {\n throw new Error(\n `Service \"${serviceClass.name}\" has trace enabled but no trace function was provided. Pass { trace } to Container.create().`,\n );\n }\n wrapWithTracing(\n instance as object,\n this.config.trace,\n BaseService.prototype,\n );\n }\n\n this.instances.set(serviceClass, instance);\n return instance;\n }\n\n /**\n * Register a pre-built instance for a service class.\n *\n * The registered instance is returned by {@link get} without constructing.\n * Primarily used for injecting test mocks.\n *\n * @typeParam T - The service type\n * @param serviceClass - The class constructor to associate with the instance\n * @param instance - The pre-built instance to register\n *\n * @example\n * ```ts\n * const testContainer = Container.create<ContainerDeps>({ db: mockDb, logger: mockLogger });\n * testContainer.register(UserService, mockUserService);\n * const auth = testContainer.get(AuthService); // uses mockUserService via @Inject\n * ```\n */\n register<T>(serviceClass: Constructor<T>, instance: T): void {\n this.instances.set(serviceClass, instance);\n }\n\n /**\n * Clear all cached service instances.\n *\n * After calling this, the next {@link get} call for any service\n * will construct a fresh instance. Does not clear deps or config.\n */\n clear(): void {\n this.instances.clear();\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@half0wl/container",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight decorator-based dependency injection container for TypeScript services",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"typecheck": "tsc -p tsconfig.check.json",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"dependency-injection",
|
|
33
|
+
"di",
|
|
34
|
+
"container",
|
|
35
|
+
"typescript",
|
|
36
|
+
"decorators",
|
|
37
|
+
"singleton"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.4.0",
|
|
43
|
+
"vitest": "^3.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|