@stitchem/core 0.0.3
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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +815 -0
- package/dist/container/container.d.ts +79 -0
- package/dist/container/container.js +156 -0
- package/dist/container/module.map.d.ts +22 -0
- package/dist/container/module.map.js +40 -0
- package/dist/context/context.d.ts +181 -0
- package/dist/context/context.js +395 -0
- package/dist/context/scope.d.ts +30 -0
- package/dist/context/scope.js +42 -0
- package/dist/core/core.lifecycle.d.ts +41 -0
- package/dist/core/core.lifecycle.js +37 -0
- package/dist/core/core.lifetime.d.ts +21 -0
- package/dist/core/core.lifetime.js +22 -0
- package/dist/core/core.types.d.ts +2 -0
- package/dist/core/core.types.js +2 -0
- package/dist/core/core.utils.d.ts +8 -0
- package/dist/core/core.utils.js +13 -0
- package/dist/decorator/inject.decorator.d.ts +50 -0
- package/dist/decorator/inject.decorator.js +78 -0
- package/dist/decorator/injectable.decorator.d.ts +45 -0
- package/dist/decorator/injectable.decorator.js +46 -0
- package/dist/errors/core.error.d.ts +24 -0
- package/dist/errors/core.error.js +59 -0
- package/dist/errors/error.codes.d.ts +17 -0
- package/dist/errors/error.codes.js +21 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +23 -0
- package/dist/injector/injector.d.ts +78 -0
- package/dist/injector/injector.js +295 -0
- package/dist/instance-wrapper/instance-wrapper.d.ts +61 -0
- package/dist/instance-wrapper/instance-wrapper.js +142 -0
- package/dist/instance-wrapper/instance-wrapper.types.d.ts +18 -0
- package/dist/instance-wrapper/instance-wrapper.types.js +2 -0
- package/dist/logger/console.logger.d.ts +52 -0
- package/dist/logger/console.logger.js +90 -0
- package/dist/logger/logger.token.d.ts +23 -0
- package/dist/logger/logger.token.js +23 -0
- package/dist/logger/logger.types.d.ts +38 -0
- package/dist/logger/logger.types.js +12 -0
- package/dist/module/module.d.ts +104 -0
- package/dist/module/module.decorator.d.ts +28 -0
- package/dist/module/module.decorator.js +42 -0
- package/dist/module/module.graph.d.ts +52 -0
- package/dist/module/module.graph.js +263 -0
- package/dist/module/module.js +181 -0
- package/dist/module/module.ref.d.ts +81 -0
- package/dist/module/module.ref.js +123 -0
- package/dist/module/module.types.d.ts +80 -0
- package/dist/module/module.types.js +10 -0
- package/dist/provider/provider.guards.d.ts +46 -0
- package/dist/provider/provider.guards.js +62 -0
- package/dist/provider/provider.interface.d.ts +39 -0
- package/dist/provider/provider.interface.js +2 -0
- package/dist/test/test.d.ts +22 -0
- package/dist/test/test.js +23 -0
- package/dist/test/test.module-builder.d.ts +136 -0
- package/dist/test/test.module-builder.js +377 -0
- package/dist/test/test.module.d.ts +71 -0
- package/dist/test/test.module.js +151 -0
- package/dist/token/lazy.token.d.ts +44 -0
- package/dist/token/lazy.token.js +42 -0
- package/dist/token/token.types.d.ts +8 -0
- package/dist/token/token.types.js +2 -0
- package/dist/token/token.utils.d.ts +9 -0
- package/dist/token/token.utils.js +19 -0
- package/package.json +62 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { Scope } from '../context/scope.js';
|
|
2
|
+
import { hasOnInit } from '../core/core.lifecycle.js';
|
|
3
|
+
import { CoreError } from '../errors/core.error.js';
|
|
4
|
+
import { formatToken } from '../token/token.utils.js';
|
|
5
|
+
import { getConstructorTokens, getInjectTokens, } from '../decorator/inject.decorator.js';
|
|
6
|
+
import { isLazyToken } from '../token/lazy.token.js';
|
|
7
|
+
import { isValueProvider, isFactoryProvider, isExistingProvider, isClassProvider, } from '../provider/provider.guards.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles dependency resolution and instance creation.
|
|
10
|
+
* Supports constructor injection (via `static inject`), accessor injection
|
|
11
|
+
* (via `@inject()` accessor decorator), and lazy proxies for circular deps.
|
|
12
|
+
*/
|
|
13
|
+
export class Injector {
|
|
14
|
+
container;
|
|
15
|
+
/** Tracks tokens currently being resolved (circular dependency detection). */
|
|
16
|
+
resolutionStack = new Set();
|
|
17
|
+
constructor(container) {
|
|
18
|
+
this.container = container;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves an instance from a wrapper (async).
|
|
22
|
+
* Handles caching, circular dependency detection, and lifecycle hooks.
|
|
23
|
+
*
|
|
24
|
+
* @throws CoreError (SCOPED_RESOLUTION) if resolving scoped without a scope
|
|
25
|
+
* @throws CoreError (CIRCULAR_DEPENDENCY) if a circular dependency is detected
|
|
26
|
+
*/
|
|
27
|
+
async loadInstance(wrapper, module, scope = Scope.STATIC) {
|
|
28
|
+
const cached = this.guardResolution(wrapper, scope);
|
|
29
|
+
if (cached !== null)
|
|
30
|
+
return cached;
|
|
31
|
+
this.resolutionStack.add(wrapper.token);
|
|
32
|
+
try {
|
|
33
|
+
const resolver = this.moduleResolver(module);
|
|
34
|
+
const instance = await this.createInstance(wrapper, resolver, scope);
|
|
35
|
+
if (hasOnInit(instance)) {
|
|
36
|
+
await instance.onInit();
|
|
37
|
+
}
|
|
38
|
+
wrapper.setInstance(instance, scope);
|
|
39
|
+
return instance;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
this.resolutionStack.delete(wrapper.token);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves an instance from a wrapper (sync).
|
|
47
|
+
*
|
|
48
|
+
* @throws CoreError (SCOPED_RESOLUTION) if resolving scoped without a scope
|
|
49
|
+
* @throws CoreError (CIRCULAR_DEPENDENCY) if a circular dependency is detected
|
|
50
|
+
* @throws Error if async factory or async onInit is encountered
|
|
51
|
+
*/
|
|
52
|
+
loadInstanceSync(wrapper, module, scope = Scope.STATIC) {
|
|
53
|
+
const cached = this.guardResolution(wrapper, scope);
|
|
54
|
+
if (cached !== null)
|
|
55
|
+
return cached;
|
|
56
|
+
this.resolutionStack.add(wrapper.token);
|
|
57
|
+
try {
|
|
58
|
+
const resolver = this.moduleResolver(module);
|
|
59
|
+
const instance = this.createInstanceSync(wrapper, resolver, scope);
|
|
60
|
+
if (hasOnInit(instance)) {
|
|
61
|
+
const result = instance.onInit();
|
|
62
|
+
if (result instanceof Promise) {
|
|
63
|
+
throw new Error(`Cannot call async onInit synchronously for ${formatToken(wrapper.token)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
wrapper.setInstance(instance, scope);
|
|
67
|
+
return instance;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
this.resolutionStack.delete(wrapper.token);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new instance without caching.
|
|
75
|
+
* Useful for transient-like creation on demand.
|
|
76
|
+
*
|
|
77
|
+
* @throws CoreError (CIRCULAR_DEPENDENCY) if a circular dependency is detected
|
|
78
|
+
*/
|
|
79
|
+
async createUncached(wrapper, module, scope = Scope.STATIC) {
|
|
80
|
+
if (this.resolutionStack.has(wrapper.token)) {
|
|
81
|
+
throw CoreError.circularDependency([
|
|
82
|
+
...this.resolutionStack,
|
|
83
|
+
wrapper.token,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
this.resolutionStack.add(wrapper.token);
|
|
87
|
+
try {
|
|
88
|
+
const resolver = this.moduleResolver(module);
|
|
89
|
+
const instance = await this.createInstance(wrapper, resolver, scope);
|
|
90
|
+
if (hasOnInit(instance)) {
|
|
91
|
+
await instance.onInit();
|
|
92
|
+
}
|
|
93
|
+
return instance;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
this.resolutionStack.delete(wrapper.token);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Instantiates any class by resolving its constructor and accessor dependencies.
|
|
101
|
+
* The class does not need to be registered as a provider or marked with @injectable().
|
|
102
|
+
*/
|
|
103
|
+
async instantiateClass(ctor, module, scope = Scope.STATIC) {
|
|
104
|
+
const resolver = this.moduleResolver(module);
|
|
105
|
+
const tokens = getConstructorTokens(ctor);
|
|
106
|
+
const deps = await this.resolveDependencies(tokens, resolver, scope);
|
|
107
|
+
const instance = new ctor(...deps);
|
|
108
|
+
await this.injectAccessorProperties(instance, ctor, resolver, scope);
|
|
109
|
+
if (hasOnInit(instance)) {
|
|
110
|
+
await instance.onInit();
|
|
111
|
+
}
|
|
112
|
+
return instance;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Instantiates any class using global resolution (bypasses module visibility).
|
|
116
|
+
*/
|
|
117
|
+
async instantiateClassGlobal(ctor, scope = Scope.STATIC) {
|
|
118
|
+
const resolver = this.globalResolver();
|
|
119
|
+
const tokens = getConstructorTokens(ctor);
|
|
120
|
+
const deps = await this.resolveDependencies(tokens, resolver, scope);
|
|
121
|
+
const instance = new ctor(...deps);
|
|
122
|
+
await this.injectAccessorProperties(instance, ctor, resolver, scope);
|
|
123
|
+
if (hasOnInit(instance)) {
|
|
124
|
+
await instance.onInit();
|
|
125
|
+
}
|
|
126
|
+
return instance;
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Private — Resolution guards
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Validates resolution preconditions and returns the cached instance if available.
|
|
133
|
+
* Returns null if the instance needs to be created.
|
|
134
|
+
*
|
|
135
|
+
* @throws CoreError (SCOPED_RESOLUTION) if resolving scoped without a scope
|
|
136
|
+
* @throws CoreError (CIRCULAR_DEPENDENCY) if a circular dependency is detected
|
|
137
|
+
*/
|
|
138
|
+
guardResolution(wrapper, scope) {
|
|
139
|
+
if (wrapper.isScoped && scope === Scope.STATIC) {
|
|
140
|
+
throw CoreError.scopedResolution(wrapper.token);
|
|
141
|
+
}
|
|
142
|
+
if (wrapper.isResolved(scope)) {
|
|
143
|
+
return wrapper.getInstance(scope);
|
|
144
|
+
}
|
|
145
|
+
if (this.resolutionStack.has(wrapper.token)) {
|
|
146
|
+
throw CoreError.circularDependency([
|
|
147
|
+
...this.resolutionStack,
|
|
148
|
+
wrapper.token,
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Private — Token resolvers
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
/** Creates a resolver scoped to a specific module. */
|
|
157
|
+
moduleResolver(module) {
|
|
158
|
+
return (token) => this.container.getProviderByToken(token, module);
|
|
159
|
+
}
|
|
160
|
+
/** Creates a resolver that searches all modules (bypasses visibility). */
|
|
161
|
+
globalResolver() {
|
|
162
|
+
return (token) => this.container.getProviderByTokenGlobal(token);
|
|
163
|
+
}
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Private — Instance creation
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
/** Creates an instance from a wrapper's provider (async). */
|
|
168
|
+
async createInstance(wrapper, resolver, scope) {
|
|
169
|
+
const provider = wrapper.provider;
|
|
170
|
+
if (isValueProvider(provider)) {
|
|
171
|
+
return provider.useValue;
|
|
172
|
+
}
|
|
173
|
+
if (isFactoryProvider(provider)) {
|
|
174
|
+
const deps = await this.resolveDependencies(provider.inject ?? [], resolver, scope);
|
|
175
|
+
const result = provider.useFactory(...deps);
|
|
176
|
+
return result instanceof Promise ? await result : result;
|
|
177
|
+
}
|
|
178
|
+
if (isExistingProvider(provider)) {
|
|
179
|
+
const { wrapper: aliased, module: aliasedModule } = resolver(provider.useExisting);
|
|
180
|
+
return this.loadInstance(aliased, aliasedModule, scope);
|
|
181
|
+
}
|
|
182
|
+
// Class or constructor provider.
|
|
183
|
+
const ctor = isClassProvider(provider) ? provider.useClass : provider;
|
|
184
|
+
const tokens = getConstructorTokens(ctor);
|
|
185
|
+
const deps = await this.resolveDependencies(tokens, resolver, scope);
|
|
186
|
+
const instance = new ctor(...deps);
|
|
187
|
+
await this.injectAccessorProperties(instance, ctor, resolver, scope);
|
|
188
|
+
return instance;
|
|
189
|
+
}
|
|
190
|
+
/** Creates an instance from a wrapper's provider (sync). */
|
|
191
|
+
createInstanceSync(wrapper, resolver, scope) {
|
|
192
|
+
const provider = wrapper.provider;
|
|
193
|
+
if (isValueProvider(provider)) {
|
|
194
|
+
return provider.useValue;
|
|
195
|
+
}
|
|
196
|
+
if (isFactoryProvider(provider)) {
|
|
197
|
+
const deps = this.resolveDependenciesSync(provider.inject ?? [], resolver, scope);
|
|
198
|
+
const result = provider.useFactory(...deps);
|
|
199
|
+
if (result instanceof Promise) {
|
|
200
|
+
throw new Error(`Cannot resolve async factory synchronously for ${formatToken(wrapper.token)}`);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
if (isExistingProvider(provider)) {
|
|
205
|
+
const { wrapper: aliased, module: aliasedModule } = resolver(provider.useExisting);
|
|
206
|
+
return this.loadInstanceSync(aliased, aliasedModule, scope);
|
|
207
|
+
}
|
|
208
|
+
// Class or constructor provider.
|
|
209
|
+
const ctor = isClassProvider(provider) ? provider.useClass : provider;
|
|
210
|
+
const tokens = getConstructorTokens(ctor);
|
|
211
|
+
const deps = this.resolveDependenciesSync(tokens, resolver, scope);
|
|
212
|
+
const instance = new ctor(...deps);
|
|
213
|
+
this.injectAccessorPropertiesSync(instance, ctor, resolver, scope);
|
|
214
|
+
return instance;
|
|
215
|
+
}
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Private — Dependency resolution
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
/** Resolves an array of dependency tokens (async). */
|
|
220
|
+
async resolveDependencies(tokens, resolver, scope) {
|
|
221
|
+
return Promise.all(tokens.map((token) => {
|
|
222
|
+
if (isLazyToken(token)) {
|
|
223
|
+
return this.createLazyProxy(token, resolver, scope);
|
|
224
|
+
}
|
|
225
|
+
const { wrapper, module } = resolver(token);
|
|
226
|
+
return this.loadInstance(wrapper, module, scope);
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
/** Resolves an array of dependency tokens (sync). */
|
|
230
|
+
resolveDependenciesSync(tokens, resolver, scope) {
|
|
231
|
+
return tokens.map((token) => {
|
|
232
|
+
if (isLazyToken(token)) {
|
|
233
|
+
return this.createLazyProxy(token, resolver, scope);
|
|
234
|
+
}
|
|
235
|
+
const { wrapper, module } = resolver(token);
|
|
236
|
+
return this.loadInstanceSync(wrapper, module, scope);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Private — Accessor injection
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
/** Injects accessor-decorated properties on an instance (async). */
|
|
243
|
+
async injectAccessorProperties(instance, ctor, resolver, scope) {
|
|
244
|
+
const injectTokens = getInjectTokens(ctor);
|
|
245
|
+
if (!injectTokens)
|
|
246
|
+
return;
|
|
247
|
+
for (const [propName, token] of injectTokens) {
|
|
248
|
+
const { wrapper, module } = resolver(token);
|
|
249
|
+
instance[propName] =
|
|
250
|
+
await this.loadInstance(wrapper, module, scope);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Injects accessor-decorated properties on an instance (sync). */
|
|
254
|
+
injectAccessorPropertiesSync(instance, ctor, resolver, scope) {
|
|
255
|
+
const injectTokens = getInjectTokens(ctor);
|
|
256
|
+
if (!injectTokens)
|
|
257
|
+
return;
|
|
258
|
+
for (const [propName, token] of injectTokens) {
|
|
259
|
+
const { wrapper, module } = resolver(token);
|
|
260
|
+
instance[propName] =
|
|
261
|
+
this.loadInstanceSync(wrapper, module, scope);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Private — Lazy proxies
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
/**
|
|
268
|
+
* Creates a lazy proxy that defers resolution until first property access.
|
|
269
|
+
* Used to break circular dependencies.
|
|
270
|
+
*/
|
|
271
|
+
createLazyProxy(lazyToken, resolver, scope) {
|
|
272
|
+
let instance;
|
|
273
|
+
const resolve = () => {
|
|
274
|
+
if (instance === undefined) {
|
|
275
|
+
const realToken = lazyToken.factory();
|
|
276
|
+
const { wrapper, module } = resolver(realToken);
|
|
277
|
+
instance = this.loadInstanceSync(wrapper, module, scope);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
return new Proxy(Object.create(null), {
|
|
281
|
+
get: (_, prop, receiver) => {
|
|
282
|
+
// Prevent Promise.all from triggering resolution via thenable check.
|
|
283
|
+
if (prop === 'then')
|
|
284
|
+
return undefined;
|
|
285
|
+
resolve();
|
|
286
|
+
return Reflect.get(instance, prop, receiver);
|
|
287
|
+
},
|
|
288
|
+
set: (_, prop, value) => {
|
|
289
|
+
resolve();
|
|
290
|
+
return Reflect.set(instance, prop, value);
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=injector.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Token } from '../token/token.types.js';
|
|
2
|
+
import type { constructor } from '../core/core.types.js';
|
|
3
|
+
import type { Provider } from '../provider/provider.interface.js';
|
|
4
|
+
import { Lifetime } from '../core/core.lifetime.js';
|
|
5
|
+
import { Scope } from '../context/scope.js';
|
|
6
|
+
/**
|
|
7
|
+
* Wraps a provider with instance caching and metadata.
|
|
8
|
+
* Handles context-aware instance storage for different lifetimes.
|
|
9
|
+
*
|
|
10
|
+
* - SINGLETON: Single instance stored directly
|
|
11
|
+
* - SCOPED: Instances stored per Scope in WeakMap (allows GC)
|
|
12
|
+
* - TRANSIENT: Never cached, always creates new instance
|
|
13
|
+
*/
|
|
14
|
+
export declare class InstanceWrapper<T = unknown> {
|
|
15
|
+
/** The injection token for this provider. */
|
|
16
|
+
readonly token: Token<T>;
|
|
17
|
+
/** The class constructor (if class-based provider). */
|
|
18
|
+
readonly classRef: constructor<T> | undefined;
|
|
19
|
+
/** The original provider configuration. */
|
|
20
|
+
readonly provider: Provider<T>;
|
|
21
|
+
/** The lifetime of this provider. */
|
|
22
|
+
readonly lifetime: Lifetime;
|
|
23
|
+
/** Singleton instance storage. */
|
|
24
|
+
private singletonInstance?;
|
|
25
|
+
private singletonResolved;
|
|
26
|
+
/** WeakMap for scoped instances (allows GC when Scope is no longer referenced). */
|
|
27
|
+
private readonly scopedInstances;
|
|
28
|
+
private readonly scopedResolved;
|
|
29
|
+
private constructor();
|
|
30
|
+
/**
|
|
31
|
+
* Creates an InstanceWrapper from a Provider.
|
|
32
|
+
* Extracts token, classRef, and lifetime from the provider configuration.
|
|
33
|
+
*/
|
|
34
|
+
static fromProvider<T>(provider: Provider<T>): InstanceWrapper<T>;
|
|
35
|
+
/** Whether this is a singleton provider. */
|
|
36
|
+
get isSingleton(): boolean;
|
|
37
|
+
/** Whether this is a transient provider (never cached). */
|
|
38
|
+
get isTransient(): boolean;
|
|
39
|
+
/** Whether this is a scoped provider. */
|
|
40
|
+
get isScoped(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Gets the cached instance for a scope.
|
|
43
|
+
* Returns undefined if not yet resolved or if transient.
|
|
44
|
+
*/
|
|
45
|
+
getInstance(scope?: Scope): T | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Stores an instance for a scope. No-op for transient providers.
|
|
48
|
+
*/
|
|
49
|
+
setInstance(instance: T, scope?: Scope): void;
|
|
50
|
+
/**
|
|
51
|
+
* Checks if an instance is already resolved for a scope.
|
|
52
|
+
* Unlike getInstance, this correctly handles cases where the resolved value is undefined.
|
|
53
|
+
*/
|
|
54
|
+
isResolved(scope?: Scope): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Clears the cached instance for a scope.
|
|
57
|
+
* For singletons, scope is ignored. For transient, this is a no-op.
|
|
58
|
+
*/
|
|
59
|
+
clear(scope?: Scope): void;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=instance-wrapper.d.ts.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Lifetime } from '../core/core.lifetime.js';
|
|
2
|
+
import { Scope } from '../context/scope.js';
|
|
3
|
+
import { isConstructorProvider, isClassProvider, isValueProvider, isFactoryProvider, getProviderToken, } from '../provider/provider.guards.js';
|
|
4
|
+
import { getInjectableMetadata } from '../decorator/injectable.decorator.js';
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the class constructor from a provider, if applicable.
|
|
7
|
+
*/
|
|
8
|
+
function extractClassRef(provider) {
|
|
9
|
+
if (isConstructorProvider(provider))
|
|
10
|
+
return provider;
|
|
11
|
+
if (isClassProvider(provider))
|
|
12
|
+
return provider.useClass;
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extracts the lifetime from a provider configuration.
|
|
17
|
+
*/
|
|
18
|
+
function extractLifetime(provider) {
|
|
19
|
+
// Value providers are always singletons.
|
|
20
|
+
if (isValueProvider(provider))
|
|
21
|
+
return Lifetime.SINGLETON;
|
|
22
|
+
// Class and factory providers can specify lifetime.
|
|
23
|
+
if (isClassProvider(provider) || isFactoryProvider(provider)) {
|
|
24
|
+
return provider.lifetime ?? Lifetime.SINGLETON;
|
|
25
|
+
}
|
|
26
|
+
// Constructor providers get lifetime from @injectable() metadata.
|
|
27
|
+
if (isConstructorProvider(provider)) {
|
|
28
|
+
return getInjectableMetadata(provider)?.lifetime ?? Lifetime.SINGLETON;
|
|
29
|
+
}
|
|
30
|
+
// Existing providers (aliases) are effectively singletons.
|
|
31
|
+
return Lifetime.SINGLETON;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wraps a provider with instance caching and metadata.
|
|
35
|
+
* Handles context-aware instance storage for different lifetimes.
|
|
36
|
+
*
|
|
37
|
+
* - SINGLETON: Single instance stored directly
|
|
38
|
+
* - SCOPED: Instances stored per Scope in WeakMap (allows GC)
|
|
39
|
+
* - TRANSIENT: Never cached, always creates new instance
|
|
40
|
+
*/
|
|
41
|
+
export class InstanceWrapper {
|
|
42
|
+
/** The injection token for this provider. */
|
|
43
|
+
token;
|
|
44
|
+
/** The class constructor (if class-based provider). */
|
|
45
|
+
classRef;
|
|
46
|
+
/** The original provider configuration. */
|
|
47
|
+
provider;
|
|
48
|
+
/** The lifetime of this provider. */
|
|
49
|
+
lifetime;
|
|
50
|
+
/** Singleton instance storage. */
|
|
51
|
+
singletonInstance;
|
|
52
|
+
singletonResolved = false;
|
|
53
|
+
/** WeakMap for scoped instances (allows GC when Scope is no longer referenced). */
|
|
54
|
+
scopedInstances = new WeakMap();
|
|
55
|
+
scopedResolved = new WeakSet();
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.token = options.token;
|
|
58
|
+
this.provider = options.provider;
|
|
59
|
+
this.classRef = options.classRef;
|
|
60
|
+
this.lifetime = options.lifetime;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates an InstanceWrapper from a Provider.
|
|
64
|
+
* Extracts token, classRef, and lifetime from the provider configuration.
|
|
65
|
+
*/
|
|
66
|
+
static fromProvider(provider) {
|
|
67
|
+
return new InstanceWrapper({
|
|
68
|
+
token: getProviderToken(provider),
|
|
69
|
+
provider,
|
|
70
|
+
classRef: extractClassRef(provider),
|
|
71
|
+
lifetime: extractLifetime(provider),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Whether this is a singleton provider. */
|
|
75
|
+
get isSingleton() {
|
|
76
|
+
return this.lifetime === Lifetime.SINGLETON;
|
|
77
|
+
}
|
|
78
|
+
/** Whether this is a transient provider (never cached). */
|
|
79
|
+
get isTransient() {
|
|
80
|
+
return this.lifetime === Lifetime.TRANSIENT;
|
|
81
|
+
}
|
|
82
|
+
/** Whether this is a scoped provider. */
|
|
83
|
+
get isScoped() {
|
|
84
|
+
return this.lifetime === Lifetime.SCOPED;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Gets the cached instance for a scope.
|
|
88
|
+
* Returns undefined if not yet resolved or if transient.
|
|
89
|
+
*/
|
|
90
|
+
getInstance(scope = Scope.STATIC) {
|
|
91
|
+
if (this.lifetime === Lifetime.SINGLETON)
|
|
92
|
+
return this.singletonInstance;
|
|
93
|
+
if (this.lifetime === Lifetime.TRANSIENT)
|
|
94
|
+
return undefined;
|
|
95
|
+
return this.scopedInstances.get(scope);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Stores an instance for a scope. No-op for transient providers.
|
|
99
|
+
*/
|
|
100
|
+
setInstance(instance, scope = Scope.STATIC) {
|
|
101
|
+
if (this.lifetime === Lifetime.SINGLETON) {
|
|
102
|
+
this.singletonInstance = instance;
|
|
103
|
+
this.singletonResolved = true;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.lifetime === Lifetime.SCOPED) {
|
|
107
|
+
this.scopedInstances.set(scope, instance);
|
|
108
|
+
this.scopedResolved.add(scope);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// TRANSIENT: don't cache.
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Checks if an instance is already resolved for a scope.
|
|
115
|
+
* Unlike getInstance, this correctly handles cases where the resolved value is undefined.
|
|
116
|
+
*/
|
|
117
|
+
isResolved(scope = Scope.STATIC) {
|
|
118
|
+
if (this.lifetime === Lifetime.SINGLETON)
|
|
119
|
+
return this.singletonResolved;
|
|
120
|
+
if (this.lifetime === Lifetime.TRANSIENT)
|
|
121
|
+
return false;
|
|
122
|
+
return this.scopedResolved.has(scope);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Clears the cached instance for a scope.
|
|
126
|
+
* For singletons, scope is ignored. For transient, this is a no-op.
|
|
127
|
+
*/
|
|
128
|
+
clear(scope = Scope.STATIC) {
|
|
129
|
+
if (this.lifetime === Lifetime.SINGLETON) {
|
|
130
|
+
this.singletonInstance = undefined;
|
|
131
|
+
this.singletonResolved = false;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (this.lifetime === Lifetime.SCOPED) {
|
|
135
|
+
this.scopedInstances.delete(scope);
|
|
136
|
+
this.scopedResolved.delete(scope);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// TRANSIENT: nothing to clear.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=instance-wrapper.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Token } from '../token/token.types.js';
|
|
2
|
+
import type { constructor } from '../core/core.types.js';
|
|
3
|
+
import type { Provider } from '../provider/provider.interface.js';
|
|
4
|
+
import type { Lifetime } from '../core/core.lifetime.js';
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating an InstanceWrapper.
|
|
7
|
+
*/
|
|
8
|
+
export interface InstanceWrapperOptions<T = unknown> {
|
|
9
|
+
/** The injection token. */
|
|
10
|
+
token: Token<T>;
|
|
11
|
+
/** The provider configuration. */
|
|
12
|
+
provider: Provider<T>;
|
|
13
|
+
/** The class constructor (if class-based provider). */
|
|
14
|
+
classRef?: constructor<T>;
|
|
15
|
+
/** The lifetime of this provider. */
|
|
16
|
+
lifetime: Lifetime;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=instance-wrapper.types.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LogLevel } from './logger.types.js';
|
|
2
|
+
import type { Logger } from './logger.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default logger implementation with colored symbols and optional context prefix.
|
|
5
|
+
*
|
|
6
|
+
* Output format:
|
|
7
|
+
* ```
|
|
8
|
+
* 12:30:15 [http:server] ℹ Starting server...
|
|
9
|
+
* 12:30:15 [http:server] ⚠ Deprecation warning
|
|
10
|
+
* 12:30:17 [http:server] ✗ Connection failed
|
|
11
|
+
* 12:30:18 [http:server] ◆ Query executed
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @example Standalone
|
|
15
|
+
* ```ts
|
|
16
|
+
* const logger = new ConsoleLogger('http:server');
|
|
17
|
+
* logger.info('Starting server...');
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example Set global level
|
|
21
|
+
* ```ts
|
|
22
|
+
* ConsoleLogger.setLevel(LogLevel.WARN);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class ConsoleLogger implements Logger {
|
|
26
|
+
private static level;
|
|
27
|
+
private readonly context?;
|
|
28
|
+
constructor(context?: string);
|
|
29
|
+
/**
|
|
30
|
+
* Sets the global log level for all ConsoleLogger instances.
|
|
31
|
+
*/
|
|
32
|
+
static setLevel(level: LogLevel): void;
|
|
33
|
+
/**
|
|
34
|
+
* Gets the current global log level.
|
|
35
|
+
*/
|
|
36
|
+
static getLevel(): LogLevel;
|
|
37
|
+
info(message: string, ...args: unknown[]): void;
|
|
38
|
+
warn(message: string, ...args: unknown[]): void;
|
|
39
|
+
error(message: string, ...args: unknown[]): void;
|
|
40
|
+
debug(message: string, ...args: unknown[]): void;
|
|
41
|
+
private prefix;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Silent logger that discards all messages.
|
|
45
|
+
*/
|
|
46
|
+
export declare class NoopLogger implements Logger {
|
|
47
|
+
info(_message: string, ..._args: unknown[]): void;
|
|
48
|
+
warn(_message: string, ..._args: unknown[]): void;
|
|
49
|
+
error(_message: string, ..._args: unknown[]): void;
|
|
50
|
+
debug(_message: string, ..._args: unknown[]): void;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=console.logger.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import logSymbols from 'log-symbols';
|
|
3
|
+
import { LogLevel } from './logger.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Default logger implementation with colored symbols and optional context prefix.
|
|
6
|
+
*
|
|
7
|
+
* Output format:
|
|
8
|
+
* ```
|
|
9
|
+
* 12:30:15 [http:server] ℹ Starting server...
|
|
10
|
+
* 12:30:15 [http:server] ⚠ Deprecation warning
|
|
11
|
+
* 12:30:17 [http:server] ✗ Connection failed
|
|
12
|
+
* 12:30:18 [http:server] ◆ Query executed
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example Standalone
|
|
16
|
+
* ```ts
|
|
17
|
+
* const logger = new ConsoleLogger('http:server');
|
|
18
|
+
* logger.info('Starting server...');
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example Set global level
|
|
22
|
+
* ```ts
|
|
23
|
+
* ConsoleLogger.setLevel(LogLevel.WARN);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class ConsoleLogger {
|
|
27
|
+
static level = LogLevel.DEBUG;
|
|
28
|
+
context;
|
|
29
|
+
constructor(context) {
|
|
30
|
+
this.context = context;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sets the global log level for all ConsoleLogger instances.
|
|
34
|
+
*/
|
|
35
|
+
static setLevel(level) {
|
|
36
|
+
ConsoleLogger.level = level;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Gets the current global log level.
|
|
40
|
+
*/
|
|
41
|
+
static getLevel() {
|
|
42
|
+
return ConsoleLogger.level;
|
|
43
|
+
}
|
|
44
|
+
info(message, ...args) {
|
|
45
|
+
if (ConsoleLogger.level > LogLevel.INFO)
|
|
46
|
+
return;
|
|
47
|
+
const prefix = this.prefix(logSymbols.info, pc.blue);
|
|
48
|
+
console.log(prefix, message, ...args);
|
|
49
|
+
}
|
|
50
|
+
warn(message, ...args) {
|
|
51
|
+
if (ConsoleLogger.level > LogLevel.WARN)
|
|
52
|
+
return;
|
|
53
|
+
const prefix = this.prefix(logSymbols.warning, pc.yellow);
|
|
54
|
+
console.warn(prefix, message, ...args);
|
|
55
|
+
}
|
|
56
|
+
error(message, ...args) {
|
|
57
|
+
if (ConsoleLogger.level > LogLevel.ERROR)
|
|
58
|
+
return;
|
|
59
|
+
const prefix = this.prefix(logSymbols.error, pc.red);
|
|
60
|
+
console.error(prefix, message, ...args);
|
|
61
|
+
}
|
|
62
|
+
debug(message, ...args) {
|
|
63
|
+
if (ConsoleLogger.level > LogLevel.DEBUG)
|
|
64
|
+
return;
|
|
65
|
+
const prefix = this.prefix('\u25c6', pc.magenta);
|
|
66
|
+
console.debug(prefix, message, ...args);
|
|
67
|
+
}
|
|
68
|
+
prefix(symbol, colorFn) {
|
|
69
|
+
const timestamp = pc.dim(compactTimestamp());
|
|
70
|
+
const ctx = this.context ? ` ${pc.dim(`[${this.context}]`)}` : '';
|
|
71
|
+
return `${timestamp}${ctx} ${colorFn(symbol)}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Silent logger that discards all messages.
|
|
76
|
+
*/
|
|
77
|
+
export class NoopLogger {
|
|
78
|
+
info(_message, ..._args) { }
|
|
79
|
+
warn(_message, ..._args) { }
|
|
80
|
+
error(_message, ..._args) { }
|
|
81
|
+
debug(_message, ..._args) { }
|
|
82
|
+
}
|
|
83
|
+
function compactTimestamp() {
|
|
84
|
+
const now = new Date();
|
|
85
|
+
const h = now.getHours().toString().padStart(2, '0');
|
|
86
|
+
const m = now.getMinutes().toString().padStart(2, '0');
|
|
87
|
+
const s = now.getSeconds().toString().padStart(2, '0');
|
|
88
|
+
return `${h}:${m}:${s}`;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=console.logger.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection token for the logger.
|
|
3
|
+
*
|
|
4
|
+
* Use this token to inject or replace the logger via DI.
|
|
5
|
+
*
|
|
6
|
+
* @example Inject
|
|
7
|
+
* ```ts
|
|
8
|
+
* @injectable()
|
|
9
|
+
* class UserService {
|
|
10
|
+
* static inject = [LOGGER] as const;
|
|
11
|
+
* constructor(readonly logger: Logger) {}
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example Replace globally
|
|
16
|
+
* ```ts
|
|
17
|
+
* await using ctx = await Context.create(AppModule, {
|
|
18
|
+
* logging: { logger: pino() },
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const LOGGER: unique symbol;
|
|
23
|
+
//# sourceMappingURL=logger.token.d.ts.map
|