@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,151 @@
|
|
|
1
|
+
import { Scope } from '../context/scope.js';
|
|
2
|
+
import { ModuleRef } from '../module/module.ref.js';
|
|
3
|
+
import { hasOnReady } from '../core/core.lifecycle.js';
|
|
4
|
+
/**
|
|
5
|
+
* A compiled test module.
|
|
6
|
+
* Provides resolution and lifecycle management for test scenarios.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* await using module = await Test.createModule({
|
|
11
|
+
* imports: [UserModule],
|
|
12
|
+
* providers: [UserService],
|
|
13
|
+
* }).compile();
|
|
14
|
+
*
|
|
15
|
+
* const userService = await module.resolve(UserService);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class TestModule {
|
|
19
|
+
container;
|
|
20
|
+
injector;
|
|
21
|
+
rootModule;
|
|
22
|
+
sortedIds;
|
|
23
|
+
disposed = false;
|
|
24
|
+
/** @internal */
|
|
25
|
+
constructor(container, injector, rootModule, sortedIds) {
|
|
26
|
+
this.container = container;
|
|
27
|
+
this.injector = injector;
|
|
28
|
+
this.rootModule = rootModule;
|
|
29
|
+
this.sortedIds = sortedIds;
|
|
30
|
+
}
|
|
31
|
+
/** Resolves a provider by token from the root module. */
|
|
32
|
+
async resolve(token, scope = Scope.STATIC) {
|
|
33
|
+
this.ensureNotDisposed();
|
|
34
|
+
const { wrapper, module } = this.container.getProviderByToken(token, this.rootModule);
|
|
35
|
+
return this.injector.loadInstance(wrapper, module, scope);
|
|
36
|
+
}
|
|
37
|
+
/** Synchronously resolves a provider by token. */
|
|
38
|
+
resolveSync(token, scope = Scope.STATIC) {
|
|
39
|
+
this.ensureNotDisposed();
|
|
40
|
+
const { wrapper, module } = this.container.getProviderByToken(token, this.rootModule);
|
|
41
|
+
return this.injector.loadInstanceSync(wrapper, module, scope);
|
|
42
|
+
}
|
|
43
|
+
/** Navigates to a specific module for resolution. */
|
|
44
|
+
select(moduleClass) {
|
|
45
|
+
this.ensureNotDisposed();
|
|
46
|
+
const module = this.container.getModuleByClass(moduleClass);
|
|
47
|
+
return new ModuleRef(this.container, this.injector, module);
|
|
48
|
+
}
|
|
49
|
+
/** Returns all component instances for a given metadata key. */
|
|
50
|
+
getComponents(key) {
|
|
51
|
+
this.ensureNotDisposed();
|
|
52
|
+
const refs = [];
|
|
53
|
+
for (const id of this.sortedIds) {
|
|
54
|
+
const module = this.container.getModule(id);
|
|
55
|
+
const moduleRef = module
|
|
56
|
+
.getProvider(ModuleRef)
|
|
57
|
+
?.getInstance(Scope.STATIC);
|
|
58
|
+
for (const entry of module.getComponents(key)) {
|
|
59
|
+
refs.push({
|
|
60
|
+
instance: entry.instance,
|
|
61
|
+
classRef: entry.classRef,
|
|
62
|
+
moduleRef,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return refs;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolves an injectable class with global dependency resolution.
|
|
70
|
+
*
|
|
71
|
+
* Dependencies are always resolved globally and the instance is cached
|
|
72
|
+
* on the root module. This makes TestModule satisfy the InjectableHost
|
|
73
|
+
* structural interface used by extension resolvers.
|
|
74
|
+
*
|
|
75
|
+
* @param ctor - The injectable class to resolve.
|
|
76
|
+
* @param _options - Accepted for InjectableHost compatibility; ignored
|
|
77
|
+
* (TestModule always resolves globally, like Context).
|
|
78
|
+
*/
|
|
79
|
+
async resolveInjectable(ctor, _options) {
|
|
80
|
+
this.ensureNotDisposed();
|
|
81
|
+
const cached = this.rootModule.getInjectable(ctor);
|
|
82
|
+
if (cached)
|
|
83
|
+
return cached;
|
|
84
|
+
const scope = Scope.current() ?? Scope.STATIC;
|
|
85
|
+
const instance = await this.injector.instantiateClassGlobal(ctor, scope);
|
|
86
|
+
if (hasOnReady(instance)) {
|
|
87
|
+
await instance.onReady();
|
|
88
|
+
}
|
|
89
|
+
this.rootModule.setInjectable(ctor, instance);
|
|
90
|
+
return instance;
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Extension internals (for extension authors building test wrappers)
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
/** The DI container. For extension authors only. */
|
|
96
|
+
get $container() {
|
|
97
|
+
return this.container;
|
|
98
|
+
}
|
|
99
|
+
/** The dependency injector. For extension authors only. */
|
|
100
|
+
get $injector() {
|
|
101
|
+
return this.injector;
|
|
102
|
+
}
|
|
103
|
+
/** The root module of the test module graph. For extension authors only. */
|
|
104
|
+
get $rootModule() {
|
|
105
|
+
return this.rootModule;
|
|
106
|
+
}
|
|
107
|
+
/** Module IDs in topological order. For extension authors only. */
|
|
108
|
+
get $sortedIds() {
|
|
109
|
+
return this.sortedIds;
|
|
110
|
+
}
|
|
111
|
+
/** Creates a new disposable scope for scoped resolution. */
|
|
112
|
+
createScope() {
|
|
113
|
+
this.ensureNotDisposed();
|
|
114
|
+
return Scope.create((scope) => this.close(scope));
|
|
115
|
+
}
|
|
116
|
+
async [Symbol.asyncDispose]() {
|
|
117
|
+
await this.close();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Disposes the test module.
|
|
121
|
+
* Call in afterEach/afterAll to clean up.
|
|
122
|
+
*/
|
|
123
|
+
async close(scope) {
|
|
124
|
+
if (this.disposed && !scope)
|
|
125
|
+
return;
|
|
126
|
+
if (!scope) {
|
|
127
|
+
// Dispose injectables → components → providers.
|
|
128
|
+
const moduleIds = [...this.sortedIds].reverse();
|
|
129
|
+
for (const id of moduleIds) {
|
|
130
|
+
const module = this.container.getModule(id);
|
|
131
|
+
if (module)
|
|
132
|
+
await module.disposeInjectables();
|
|
133
|
+
}
|
|
134
|
+
for (const id of moduleIds) {
|
|
135
|
+
const module = this.container.getModule(id);
|
|
136
|
+
if (module)
|
|
137
|
+
await module.disposeComponents();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await this.container.dispose(scope);
|
|
141
|
+
if (!scope) {
|
|
142
|
+
this.disposed = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
ensureNotDisposed() {
|
|
146
|
+
if (this.disposed) {
|
|
147
|
+
throw new Error('TestModule has been closed.');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=test.module.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Token } from './token.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Metadata key for lazy tokens.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LAZY_BRAND: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* A lazy token that defers resolution until first property access.
|
|
8
|
+
* Used to break circular dependencies between providers.
|
|
9
|
+
*/
|
|
10
|
+
export interface LazyToken<T = unknown> {
|
|
11
|
+
readonly [LAZY_BRAND]: true;
|
|
12
|
+
readonly factory: () => Token<T>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Wraps a token factory to enable lazy resolution, breaking circular dependencies.
|
|
16
|
+
*
|
|
17
|
+
* When a provider depends on another provider that creates a circular dependency,
|
|
18
|
+
* wrap the token with `lazy()` to defer its resolution until first access.
|
|
19
|
+
* Only one side of the cycle needs to use `lazy()`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* @injectable()
|
|
24
|
+
* class ServiceA {
|
|
25
|
+
* static inject = [lazy(() => ServiceB)];
|
|
26
|
+
* constructor(private b: ServiceB) {}
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* @injectable()
|
|
30
|
+
* class ServiceB {
|
|
31
|
+
* static inject = [ServiceA];
|
|
32
|
+
* constructor(private a: ServiceA) {}
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @param factory - A function that returns the injection token (deferred to handle forward references)
|
|
37
|
+
* @returns A lazy token that the injector resolves on first property access
|
|
38
|
+
*/
|
|
39
|
+
export declare function lazy<T>(factory: () => Token<T>): LazyToken<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Type guard that checks if a value is a lazy token.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isLazyToken(value: unknown): value is LazyToken;
|
|
44
|
+
//# sourceMappingURL=lazy.token.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata key for lazy tokens.
|
|
3
|
+
*/
|
|
4
|
+
export const LAZY_BRAND = Symbol.for('stitchem:lazy');
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a token factory to enable lazy resolution, breaking circular dependencies.
|
|
7
|
+
*
|
|
8
|
+
* When a provider depends on another provider that creates a circular dependency,
|
|
9
|
+
* wrap the token with `lazy()` to defer its resolution until first access.
|
|
10
|
+
* Only one side of the cycle needs to use `lazy()`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* @injectable()
|
|
15
|
+
* class ServiceA {
|
|
16
|
+
* static inject = [lazy(() => ServiceB)];
|
|
17
|
+
* constructor(private b: ServiceB) {}
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* @injectable()
|
|
21
|
+
* class ServiceB {
|
|
22
|
+
* static inject = [ServiceA];
|
|
23
|
+
* constructor(private a: ServiceA) {}
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @param factory - A function that returns the injection token (deferred to handle forward references)
|
|
28
|
+
* @returns A lazy token that the injector resolves on first property access
|
|
29
|
+
*/
|
|
30
|
+
export function lazy(factory) {
|
|
31
|
+
return { [LAZY_BRAND]: true, factory };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Type guard that checks if a value is a lazy token.
|
|
35
|
+
*/
|
|
36
|
+
export function isLazyToken(value) {
|
|
37
|
+
return (value !== null &&
|
|
38
|
+
typeof value === 'object' &&
|
|
39
|
+
LAZY_BRAND in value &&
|
|
40
|
+
value[LAZY_BRAND]);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=lazy.token.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { constructor } from '../core/core.types.js';
|
|
2
|
+
import type { LazyToken } from './lazy.token.js';
|
|
3
|
+
/**
|
|
4
|
+
* A token that can be used to identify a dependency.
|
|
5
|
+
* Can be a class constructor, abstract class, string, symbol, or lazy token.
|
|
6
|
+
*/
|
|
7
|
+
export type Token<T = unknown> = constructor<T> | LazyToken<T> | string | symbol;
|
|
8
|
+
//# sourceMappingURL=token.types.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Token } from './token.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Formats a token for display in error messages and debugging.
|
|
4
|
+
*
|
|
5
|
+
* @param token - The injection token to format
|
|
6
|
+
* @returns A human-readable string representation of the token
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatToken(token: Token): string;
|
|
9
|
+
//# sourceMappingURL=token.utils.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isLazyToken } from './lazy.token.js';
|
|
2
|
+
/**
|
|
3
|
+
* Formats a token for display in error messages and debugging.
|
|
4
|
+
*
|
|
5
|
+
* @param token - The injection token to format
|
|
6
|
+
* @returns A human-readable string representation of the token
|
|
7
|
+
*/
|
|
8
|
+
export function formatToken(token) {
|
|
9
|
+
if (typeof token === 'function')
|
|
10
|
+
return token.name;
|
|
11
|
+
if (typeof token === 'symbol')
|
|
12
|
+
return token.description ?? token.toString();
|
|
13
|
+
if (isLazyToken(token)) {
|
|
14
|
+
const inner = token.factory();
|
|
15
|
+
return `lazy(${formatToken(inner)})`;
|
|
16
|
+
}
|
|
17
|
+
return String(token);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=token.utils.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stitchem/core",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Extensible modular dependency injection for TypeScript",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "hexac",
|
|
7
|
+
"url": "https://existin.space",
|
|
8
|
+
"email": "hexac@existin.space"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/eishexac/stitchem.git",
|
|
17
|
+
"directory": "core"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/eishexac/stitchem/tree/release/#readme",
|
|
20
|
+
"bugs": "https://github.com/eishexac/stitchem/issues",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"!dist/**/*.map",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "dist/index.js",
|
|
30
|
+
"types": "dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"@source": "./src/index.ts",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"log-symbols": "^7.0.1",
|
|
40
|
+
"picocolors": "^1.1.1",
|
|
41
|
+
"toposort": "^2.0.2",
|
|
42
|
+
"uuid": "^13.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/toposort": "^2.0.7"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"typescript": ">=5.2"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
52
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
53
|
+
"type:check": "tsc --noEmit",
|
|
54
|
+
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
55
|
+
"lint:fix": "eslint \"src/**/*.ts\" \"test/**/*.ts\" --fix",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:unit": "vitest run --project unit",
|
|
58
|
+
"test:int": "vitest run --project int",
|
|
59
|
+
"test:e2e": "vitest run --project e2e",
|
|
60
|
+
"test:coverage": "vitest run --coverage"
|
|
61
|
+
}
|
|
62
|
+
}
|