@abdokouta/ts-application 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/LICENSE +21 -0
- package/dist/index.cjs +199 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +265 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Refine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tsContainer = require('@abdokouta/ts-container');
|
|
4
|
+
|
|
5
|
+
// src/application-context.ts
|
|
6
|
+
var ApplicationContext = class _ApplicationContext {
|
|
7
|
+
container;
|
|
8
|
+
instanceLoader;
|
|
9
|
+
isInitialized = false;
|
|
10
|
+
constructor(container, instanceLoader) {
|
|
11
|
+
this.container = container;
|
|
12
|
+
this.instanceLoader = instanceLoader;
|
|
13
|
+
}
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// Static factory
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Create and bootstrap an application context.
|
|
19
|
+
*
|
|
20
|
+
* This is the main entry point. It:
|
|
21
|
+
* 1. Scans the module tree starting from the root module
|
|
22
|
+
* 2. Resolves all providers (creates instances, injects dependencies)
|
|
23
|
+
* 3. Calls `onModuleInit()` lifecycle hooks
|
|
24
|
+
*
|
|
25
|
+
* @param rootModule - The root module class (your AppModule)
|
|
26
|
+
* @returns A fully bootstrapped ApplicationContext
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { ApplicationContext } from '@pixielity/application';
|
|
31
|
+
* import { AppModule } from './app.module';
|
|
32
|
+
*
|
|
33
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
34
|
+
*
|
|
35
|
+
* const userService = app.get(UserService);
|
|
36
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
static async create(rootModule) {
|
|
40
|
+
const container = new tsContainer.NestContainer();
|
|
41
|
+
const scanner = new tsContainer.DependenciesScanner(container);
|
|
42
|
+
const instanceLoader = new tsContainer.InstanceLoader(container);
|
|
43
|
+
await scanner.scan(rootModule);
|
|
44
|
+
await instanceLoader.createInstances();
|
|
45
|
+
const app = new _ApplicationContext(container, instanceLoader);
|
|
46
|
+
app.isInitialized = true;
|
|
47
|
+
return app;
|
|
48
|
+
}
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
50
|
+
// ContainerResolver interface (used by @abdokouta/ts-container/react)
|
|
51
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a provider by its injection token.
|
|
54
|
+
*
|
|
55
|
+
* Searches all modules for the provider. For singleton providers,
|
|
56
|
+
* returns the cached instance.
|
|
57
|
+
*
|
|
58
|
+
* @param token - The injection token (class, string, or symbol)
|
|
59
|
+
* @returns The resolved provider instance
|
|
60
|
+
* @throws Error if the provider is not found
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const userService = app.get(UserService);
|
|
65
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
66
|
+
* const apiUrl = app.get<string>('API_URL');
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
get(token) {
|
|
70
|
+
this.assertInitialized();
|
|
71
|
+
for (const [, moduleRef] of this.container.getModules()) {
|
|
72
|
+
const wrapper = moduleRef.providers.get(token);
|
|
73
|
+
if (!wrapper) continue;
|
|
74
|
+
if (wrapper.isResolved && !wrapper.isTransient) {
|
|
75
|
+
return wrapper.instance;
|
|
76
|
+
}
|
|
77
|
+
if (wrapper.isTransient && wrapper.metatype) {
|
|
78
|
+
return this.instantiateTransient(wrapper, moduleRef);
|
|
79
|
+
}
|
|
80
|
+
if (wrapper.isTransient && wrapper.instance !== null) {
|
|
81
|
+
return wrapper.instance;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Provider '${this.getTokenName(token)}' not found in any module. Make sure it is provided in a module that has been imported.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Try to resolve a provider, returning `undefined` if not found.
|
|
90
|
+
*
|
|
91
|
+
* @param token - The injection token
|
|
92
|
+
* @returns The resolved instance or undefined
|
|
93
|
+
*/
|
|
94
|
+
getOptional(token) {
|
|
95
|
+
try {
|
|
96
|
+
return this.get(token);
|
|
97
|
+
} catch {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a provider is registered in any module.
|
|
103
|
+
*
|
|
104
|
+
* @param token - The injection token to check
|
|
105
|
+
*/
|
|
106
|
+
has(token) {
|
|
107
|
+
for (const [, moduleRef] of this.container.getModules()) {
|
|
108
|
+
if (moduleRef.providers.has(token)) return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
113
|
+
// Advanced API
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Select a specific module and resolve a provider from it.
|
|
117
|
+
*
|
|
118
|
+
* Useful when the same token exists in multiple modules.
|
|
119
|
+
*
|
|
120
|
+
* @param moduleClass - The module class to search in
|
|
121
|
+
* @param token - The injection token
|
|
122
|
+
*/
|
|
123
|
+
select(moduleClass, token) {
|
|
124
|
+
this.assertInitialized();
|
|
125
|
+
for (const [, moduleRef] of this.container.getModules()) {
|
|
126
|
+
if (moduleRef.metatype === moduleClass) {
|
|
127
|
+
const wrapper = moduleRef.providers.get(token);
|
|
128
|
+
if (wrapper?.isResolved) {
|
|
129
|
+
return wrapper.instance;
|
|
130
|
+
}
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Provider '${this.getTokenName(token)}' not found in module '${moduleClass.name}'.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Module '${moduleClass.name}' not found in the container.`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get the underlying NestContainer (for advanced use cases).
|
|
140
|
+
*/
|
|
141
|
+
getContainer() {
|
|
142
|
+
return this.container;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Gracefully shut down the application.
|
|
146
|
+
*
|
|
147
|
+
* Calls `onModuleDestroy()` on all providers that implement it,
|
|
148
|
+
* in reverse module order (leaf modules first).
|
|
149
|
+
*/
|
|
150
|
+
async close() {
|
|
151
|
+
await this.instanceLoader.destroy();
|
|
152
|
+
this.isInitialized = false;
|
|
153
|
+
}
|
|
154
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
155
|
+
// Private
|
|
156
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
157
|
+
assertInitialized() {
|
|
158
|
+
if (!this.isInitialized) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
"ApplicationContext is not initialized. Call ApplicationContext.create() first."
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
getTokenName(token) {
|
|
165
|
+
if (typeof token === "function") return token.name;
|
|
166
|
+
if (typeof token === "symbol") return token.toString();
|
|
167
|
+
return String(token);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Synchronously instantiate a transient provider.
|
|
171
|
+
*
|
|
172
|
+
* After bootstrap, all dependencies of a transient provider are already
|
|
173
|
+
* resolved singletons. So we can synchronously look them up and call
|
|
174
|
+
* `new Class(...deps)` without awaiting anything.
|
|
175
|
+
*/
|
|
176
|
+
instantiateTransient(wrapper, moduleRef) {
|
|
177
|
+
const injector = this.instanceLoader.getInjector();
|
|
178
|
+
const metatype = wrapper.metatype;
|
|
179
|
+
const deps = injector.getConstructorDependencies(metatype);
|
|
180
|
+
const optionalIndices = injector.getOptionalDependencies(metatype);
|
|
181
|
+
const resolvedDeps = deps.map((dep, index) => {
|
|
182
|
+
if (dep === void 0 || dep === null || dep === Object) {
|
|
183
|
+
if (optionalIndices.includes(index)) return void 0;
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
const result = injector.lookupProvider(dep, moduleRef);
|
|
187
|
+
if (!result) {
|
|
188
|
+
if (optionalIndices.includes(index)) return void 0;
|
|
189
|
+
throw new Error(`Cannot resolve transient dependency '${this.getTokenName(dep)}'`);
|
|
190
|
+
}
|
|
191
|
+
return result.wrapper.instance;
|
|
192
|
+
});
|
|
193
|
+
return new metatype(...resolvedDeps);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
exports.ApplicationContext = ApplicationContext;
|
|
198
|
+
//# sourceMappingURL=index.cjs.map
|
|
199
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/application-context.ts"],"names":["NestContainer","DependenciesScanner","InstanceLoader"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview ApplicationContext — the main entry point for bootstrapping the DI system.\n *\n * This is the equivalent of NestJS's `NestFactory.createApplicationContext()`.\n * It orchestrates the full bootstrap sequence:\n *\n * 1. **Scan** — Walk the module tree, register all modules/providers/imports/exports\n * 2. **Instantiate** — Resolve all providers (create instances, inject dependencies)\n * 3. **Lifecycle** — Call `onModuleInit()` on providers that implement it\n *\n * After bootstrap, the ApplicationContext provides `get()` to resolve any provider.\n *\n * ## Usage:\n *\n * ```typescript\n * import { ApplicationContext } from '@pixielity/application';\n * import { AppModule } from './app.module';\n *\n * // Bootstrap\n * const app = await ApplicationContext.create(AppModule);\n *\n * // Resolve providers\n * const cache = app.get(CacheManager);\n * const config = app.get<AppConfig>(APP_CONFIG);\n *\n * // Shutdown\n * await app.close();\n * ```\n *\n * ## With React:\n *\n * ```tsx\n * import { ContainerProvider } from '@abdokouta/ts-container/react';\n *\n * const app = await ApplicationContext.create(AppModule);\n *\n * ReactDOM.createRoot(root).render(\n * <ContainerProvider context={app}>\n * <App />\n * </ContainerProvider>\n * );\n * ```\n *\n * The ApplicationContext implements the `ContainerResolver` interface from\n * `@abdokouta/ts-container/react`, so it can be passed directly to `ContainerProvider`.\n *\n * @module application-context\n */\n\nimport type { Type, InjectionToken } from '@abdokouta/ts-container';\nimport {\n NestContainer,\n DependenciesScanner,\n InstanceLoader,\n ModuleRef,\n} from '@abdokouta/ts-container';\nimport type { IApplicationContext } from './interfaces/application-context.interface';\n\n/**\n * The bootstrapped application context.\n *\n * Provides access to the DI container after all modules have been\n * scanned and all providers have been instantiated.\n *\n * Implements `IApplicationContext` (which extends `ContainerResolver`)\n * so it can be used directly with `<ContainerProvider context={app}>`\n * from `@abdokouta/ts-container/react`.\n */\nexport class ApplicationContext implements IApplicationContext {\n private readonly container: NestContainer;\n private readonly instanceLoader: InstanceLoader;\n private isInitialized = false;\n\n private constructor(container: NestContainer, instanceLoader: InstanceLoader) {\n this.container = container;\n this.instanceLoader = instanceLoader;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Static factory\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Create and bootstrap an application context.\n *\n * This is the main entry point. It:\n * 1. Scans the module tree starting from the root module\n * 2. Resolves all providers (creates instances, injects dependencies)\n * 3. Calls `onModuleInit()` lifecycle hooks\n *\n * @param rootModule - The root module class (your AppModule)\n * @returns A fully bootstrapped ApplicationContext\n *\n * @example\n * ```typescript\n * import { ApplicationContext } from '@pixielity/application';\n * import { AppModule } from './app.module';\n *\n * const app = await ApplicationContext.create(AppModule);\n *\n * const userService = app.get(UserService);\n * const config = app.get<CacheConfig>(CACHE_CONFIG);\n * ```\n */\n public static async create(rootModule: Type<any>): Promise<ApplicationContext> {\n const container = new NestContainer();\n const scanner = new DependenciesScanner(container);\n const instanceLoader = new InstanceLoader(container);\n\n // Phase 1: Scan the module tree\n await scanner.scan(rootModule);\n\n // Phase 2: Create all provider instances\n await instanceLoader.createInstances();\n\n const app = new ApplicationContext(container, instanceLoader);\n app.isInitialized = true;\n\n return app;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // ContainerResolver interface (used by @abdokouta/ts-container/react)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Resolve a provider by its injection token.\n *\n * Searches all modules for the provider. For singleton providers,\n * returns the cached instance.\n *\n * @param token - The injection token (class, string, or symbol)\n * @returns The resolved provider instance\n * @throws Error if the provider is not found\n *\n * @example\n * ```typescript\n * const userService = app.get(UserService);\n * const config = app.get<CacheConfig>(CACHE_CONFIG);\n * const apiUrl = app.get<string>('API_URL');\n * ```\n */\n public get<T = any>(token: InjectionToken<T>): T {\n this.assertInitialized();\n\n for (const [, moduleRef] of this.container.getModules()) {\n const wrapper = moduleRef.providers.get(token);\n if (!wrapper) continue;\n\n // Singleton or value provider — return cached instance\n if (wrapper.isResolved && !wrapper.isTransient) {\n return wrapper.instance as T;\n }\n\n // Transient provider — create a fresh instance each time.\n // All dependencies should already be resolved after bootstrap,\n // so we can synchronously instantiate the class.\n if (wrapper.isTransient && wrapper.metatype) {\n return this.instantiateTransient<T>(wrapper, moduleRef);\n }\n\n // Transient provider that was resolved during bootstrap\n // (has a cached instance from the initial resolution)\n if (wrapper.isTransient && wrapper.instance !== null) {\n return wrapper.instance as T;\n }\n }\n\n throw new Error(\n `Provider '${this.getTokenName(token)}' not found in any module. ` +\n `Make sure it is provided in a module that has been imported.`,\n );\n }\n\n /**\n * Try to resolve a provider, returning `undefined` if not found.\n *\n * @param token - The injection token\n * @returns The resolved instance or undefined\n */\n public getOptional<T = any>(token: InjectionToken<T>): T | undefined {\n try {\n return this.get(token);\n } catch {\n return undefined;\n }\n }\n\n /**\n * Check if a provider is registered in any module.\n *\n * @param token - The injection token to check\n */\n public has(token: InjectionToken): boolean {\n for (const [, moduleRef] of this.container.getModules()) {\n if (moduleRef.providers.has(token)) return true;\n }\n return false;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Advanced API\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Select a specific module and resolve a provider from it.\n *\n * Useful when the same token exists in multiple modules.\n *\n * @param moduleClass - The module class to search in\n * @param token - The injection token\n */\n public select<T = any>(moduleClass: Type<any>, token: InjectionToken<T>): T {\n this.assertInitialized();\n\n for (const [, moduleRef] of this.container.getModules()) {\n if (moduleRef.metatype === moduleClass) {\n const wrapper = moduleRef.providers.get(token);\n if (wrapper?.isResolved) {\n return wrapper.instance as T;\n }\n throw new Error(\n `Provider '${this.getTokenName(token)}' not found in module '${moduleClass.name}'.`,\n );\n }\n }\n\n throw new Error(`Module '${moduleClass.name}' not found in the container.`);\n }\n\n /**\n * Get the underlying NestContainer (for advanced use cases).\n */\n public getContainer(): NestContainer {\n return this.container;\n }\n\n /**\n * Gracefully shut down the application.\n *\n * Calls `onModuleDestroy()` on all providers that implement it,\n * in reverse module order (leaf modules first).\n */\n public async close(): Promise<void> {\n await this.instanceLoader.destroy();\n this.isInitialized = false;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private\n // ─────────────────────────────────────────────────────────────────────────\n\n private assertInitialized(): void {\n if (!this.isInitialized) {\n throw new Error(\n 'ApplicationContext is not initialized. Call ApplicationContext.create() first.',\n );\n }\n }\n\n private getTokenName(token: InjectionToken): string {\n if (typeof token === 'function') return token.name;\n if (typeof token === 'symbol') return token.toString();\n return String(token);\n }\n\n /**\n * Synchronously instantiate a transient provider.\n *\n * After bootstrap, all dependencies of a transient provider are already\n * resolved singletons. So we can synchronously look them up and call\n * `new Class(...deps)` without awaiting anything.\n */\n private instantiateTransient<T>(wrapper: any, moduleRef: ModuleRef): T {\n const injector = this.instanceLoader.getInjector();\n\n // Look up constructor dependencies — they should all be resolved singletons\n const metatype = wrapper.metatype;\n const deps = (injector as any).getConstructorDependencies(metatype);\n const optionalIndices: number[] = (injector as any).getOptionalDependencies(metatype);\n\n const resolvedDeps = deps.map((dep: InjectionToken, index: number) => {\n if (dep === undefined || dep === null || dep === Object) {\n if (optionalIndices.includes(index)) return undefined;\n return undefined;\n }\n\n const result = injector.lookupProvider(dep, moduleRef);\n if (!result) {\n if (optionalIndices.includes(index)) return undefined;\n throw new Error(`Cannot resolve transient dependency '${this.getTokenName(dep)}'`);\n }\n\n return result.wrapper.instance;\n });\n\n return new metatype(...resolvedDeps);\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { ContainerResolver, InjectionToken, Type, NestContainer } from '@abdokouta/ts-container';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview IApplicationContext — interface for the bootstrapped application.
|
|
5
|
+
*
|
|
6
|
+
* Defines the public API of the ApplicationContext. Extends `ContainerResolver`
|
|
7
|
+
* (from `@abdokouta/ts-container`) with additional methods for module selection,
|
|
8
|
+
* container access, and graceful shutdown.
|
|
9
|
+
*
|
|
10
|
+
* ## Why an interface?
|
|
11
|
+
*
|
|
12
|
+
* - Enables mocking in tests without importing the concrete class
|
|
13
|
+
* - Documents the public API contract clearly
|
|
14
|
+
* - Allows alternative implementations (e.g., a test context, a lazy context)
|
|
15
|
+
*
|
|
16
|
+
* @module interfaces/application-context
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The public API of a bootstrapped application context.
|
|
21
|
+
*
|
|
22
|
+
* Extends `ContainerResolver` with:
|
|
23
|
+
* - `select()` — resolve from a specific module
|
|
24
|
+
* - `getContainer()` — access the raw container
|
|
25
|
+
* - `close()` — graceful shutdown with lifecycle hooks
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Type your variable as the interface for testability
|
|
30
|
+
* let app: IApplicationContext;
|
|
31
|
+
*
|
|
32
|
+
* beforeAll(async () => {
|
|
33
|
+
* app = await ApplicationContext.create(AppModule);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* afterAll(async () => {
|
|
37
|
+
* await app.close();
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* test('resolves UserService', () => {
|
|
41
|
+
* const userService = app.get(UserService);
|
|
42
|
+
* expect(userService).toBeDefined();
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
interface IApplicationContext extends ContainerResolver {
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a provider by its injection token.
|
|
49
|
+
*
|
|
50
|
+
* Searches all modules for the provider. For singleton providers,
|
|
51
|
+
* returns the cached instance.
|
|
52
|
+
*
|
|
53
|
+
* @param token - The injection token (class, string, or symbol)
|
|
54
|
+
* @returns The resolved provider instance
|
|
55
|
+
* @throws Error if the provider is not found
|
|
56
|
+
*/
|
|
57
|
+
get<T = any>(token: InjectionToken<T>): T;
|
|
58
|
+
/**
|
|
59
|
+
* Try to resolve a provider, returning `undefined` if not found.
|
|
60
|
+
*
|
|
61
|
+
* @param token - The injection token
|
|
62
|
+
* @returns The resolved instance or undefined
|
|
63
|
+
*/
|
|
64
|
+
getOptional<T = any>(token: InjectionToken<T>): T | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a provider is registered in any module.
|
|
67
|
+
*
|
|
68
|
+
* @param token - The injection token to check
|
|
69
|
+
*/
|
|
70
|
+
has(token: InjectionToken): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Select a specific module and resolve a provider from it.
|
|
73
|
+
*
|
|
74
|
+
* Useful when the same token exists in multiple modules and you
|
|
75
|
+
* need to specify which one to resolve from.
|
|
76
|
+
*
|
|
77
|
+
* @param moduleClass - The module class to search in
|
|
78
|
+
* @param token - The injection token
|
|
79
|
+
* @returns The resolved provider instance
|
|
80
|
+
* @throws Error if the module or provider is not found
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Resolve CacheManager specifically from CacheModule
|
|
85
|
+
* const cache = app.select(CacheModule, CacheManager);
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
select<T = any>(moduleClass: Type<any>, token: InjectionToken<T>): T;
|
|
89
|
+
/**
|
|
90
|
+
* Get the underlying NestContainer.
|
|
91
|
+
*
|
|
92
|
+
* For advanced use cases like inspecting the module graph,
|
|
93
|
+
* accessing raw InstanceWrappers, or building dev tools.
|
|
94
|
+
*/
|
|
95
|
+
getContainer(): NestContainer;
|
|
96
|
+
/**
|
|
97
|
+
* Gracefully shut down the application.
|
|
98
|
+
*
|
|
99
|
+
* Calls `onModuleDestroy()` on all providers that implement it,
|
|
100
|
+
* in reverse module order (leaf modules first, root module last).
|
|
101
|
+
*
|
|
102
|
+
* After calling `close()`, the context is no longer usable.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // In your app's cleanup logic
|
|
107
|
+
* window.addEventListener('beforeunload', () => {
|
|
108
|
+
* app.close();
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
close(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @fileoverview ApplicationContext — the main entry point for bootstrapping the DI system.
|
|
117
|
+
*
|
|
118
|
+
* This is the equivalent of NestJS's `NestFactory.createApplicationContext()`.
|
|
119
|
+
* It orchestrates the full bootstrap sequence:
|
|
120
|
+
*
|
|
121
|
+
* 1. **Scan** — Walk the module tree, register all modules/providers/imports/exports
|
|
122
|
+
* 2. **Instantiate** — Resolve all providers (create instances, inject dependencies)
|
|
123
|
+
* 3. **Lifecycle** — Call `onModuleInit()` on providers that implement it
|
|
124
|
+
*
|
|
125
|
+
* After bootstrap, the ApplicationContext provides `get()` to resolve any provider.
|
|
126
|
+
*
|
|
127
|
+
* ## Usage:
|
|
128
|
+
*
|
|
129
|
+
* ```typescript
|
|
130
|
+
* import { ApplicationContext } from '@pixielity/application';
|
|
131
|
+
* import { AppModule } from './app.module';
|
|
132
|
+
*
|
|
133
|
+
* // Bootstrap
|
|
134
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
135
|
+
*
|
|
136
|
+
* // Resolve providers
|
|
137
|
+
* const cache = app.get(CacheManager);
|
|
138
|
+
* const config = app.get<AppConfig>(APP_CONFIG);
|
|
139
|
+
*
|
|
140
|
+
* // Shutdown
|
|
141
|
+
* await app.close();
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* ## With React:
|
|
145
|
+
*
|
|
146
|
+
* ```tsx
|
|
147
|
+
* import { ContainerProvider } from '@abdokouta/ts-container/react';
|
|
148
|
+
*
|
|
149
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
150
|
+
*
|
|
151
|
+
* ReactDOM.createRoot(root).render(
|
|
152
|
+
* <ContainerProvider context={app}>
|
|
153
|
+
* <App />
|
|
154
|
+
* </ContainerProvider>
|
|
155
|
+
* );
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* The ApplicationContext implements the `ContainerResolver` interface from
|
|
159
|
+
* `@abdokouta/ts-container/react`, so it can be passed directly to `ContainerProvider`.
|
|
160
|
+
*
|
|
161
|
+
* @module application-context
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The bootstrapped application context.
|
|
166
|
+
*
|
|
167
|
+
* Provides access to the DI container after all modules have been
|
|
168
|
+
* scanned and all providers have been instantiated.
|
|
169
|
+
*
|
|
170
|
+
* Implements `IApplicationContext` (which extends `ContainerResolver`)
|
|
171
|
+
* so it can be used directly with `<ContainerProvider context={app}>`
|
|
172
|
+
* from `@abdokouta/ts-container/react`.
|
|
173
|
+
*/
|
|
174
|
+
declare class ApplicationContext implements IApplicationContext {
|
|
175
|
+
private readonly container;
|
|
176
|
+
private readonly instanceLoader;
|
|
177
|
+
private isInitialized;
|
|
178
|
+
private constructor();
|
|
179
|
+
/**
|
|
180
|
+
* Create and bootstrap an application context.
|
|
181
|
+
*
|
|
182
|
+
* This is the main entry point. It:
|
|
183
|
+
* 1. Scans the module tree starting from the root module
|
|
184
|
+
* 2. Resolves all providers (creates instances, injects dependencies)
|
|
185
|
+
* 3. Calls `onModuleInit()` lifecycle hooks
|
|
186
|
+
*
|
|
187
|
+
* @param rootModule - The root module class (your AppModule)
|
|
188
|
+
* @returns A fully bootstrapped ApplicationContext
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { ApplicationContext } from '@pixielity/application';
|
|
193
|
+
* import { AppModule } from './app.module';
|
|
194
|
+
*
|
|
195
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
196
|
+
*
|
|
197
|
+
* const userService = app.get(UserService);
|
|
198
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
static create(rootModule: Type<any>): Promise<ApplicationContext>;
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a provider by its injection token.
|
|
204
|
+
*
|
|
205
|
+
* Searches all modules for the provider. For singleton providers,
|
|
206
|
+
* returns the cached instance.
|
|
207
|
+
*
|
|
208
|
+
* @param token - The injection token (class, string, or symbol)
|
|
209
|
+
* @returns The resolved provider instance
|
|
210
|
+
* @throws Error if the provider is not found
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const userService = app.get(UserService);
|
|
215
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
216
|
+
* const apiUrl = app.get<string>('API_URL');
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
get<T = any>(token: InjectionToken<T>): T;
|
|
220
|
+
/**
|
|
221
|
+
* Try to resolve a provider, returning `undefined` if not found.
|
|
222
|
+
*
|
|
223
|
+
* @param token - The injection token
|
|
224
|
+
* @returns The resolved instance or undefined
|
|
225
|
+
*/
|
|
226
|
+
getOptional<T = any>(token: InjectionToken<T>): T | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* Check if a provider is registered in any module.
|
|
229
|
+
*
|
|
230
|
+
* @param token - The injection token to check
|
|
231
|
+
*/
|
|
232
|
+
has(token: InjectionToken): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Select a specific module and resolve a provider from it.
|
|
235
|
+
*
|
|
236
|
+
* Useful when the same token exists in multiple modules.
|
|
237
|
+
*
|
|
238
|
+
* @param moduleClass - The module class to search in
|
|
239
|
+
* @param token - The injection token
|
|
240
|
+
*/
|
|
241
|
+
select<T = any>(moduleClass: Type<any>, token: InjectionToken<T>): T;
|
|
242
|
+
/**
|
|
243
|
+
* Get the underlying NestContainer (for advanced use cases).
|
|
244
|
+
*/
|
|
245
|
+
getContainer(): NestContainer;
|
|
246
|
+
/**
|
|
247
|
+
* Gracefully shut down the application.
|
|
248
|
+
*
|
|
249
|
+
* Calls `onModuleDestroy()` on all providers that implement it,
|
|
250
|
+
* in reverse module order (leaf modules first).
|
|
251
|
+
*/
|
|
252
|
+
close(): Promise<void>;
|
|
253
|
+
private assertInitialized;
|
|
254
|
+
private getTokenName;
|
|
255
|
+
/**
|
|
256
|
+
* Synchronously instantiate a transient provider.
|
|
257
|
+
*
|
|
258
|
+
* After bootstrap, all dependencies of a transient provider are already
|
|
259
|
+
* resolved singletons. So we can synchronously look them up and call
|
|
260
|
+
* `new Class(...deps)` without awaiting anything.
|
|
261
|
+
*/
|
|
262
|
+
private instantiateTransient;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export { ApplicationContext, type IApplicationContext };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { ContainerResolver, InjectionToken, Type, NestContainer } from '@abdokouta/ts-container';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview IApplicationContext — interface for the bootstrapped application.
|
|
5
|
+
*
|
|
6
|
+
* Defines the public API of the ApplicationContext. Extends `ContainerResolver`
|
|
7
|
+
* (from `@abdokouta/ts-container`) with additional methods for module selection,
|
|
8
|
+
* container access, and graceful shutdown.
|
|
9
|
+
*
|
|
10
|
+
* ## Why an interface?
|
|
11
|
+
*
|
|
12
|
+
* - Enables mocking in tests without importing the concrete class
|
|
13
|
+
* - Documents the public API contract clearly
|
|
14
|
+
* - Allows alternative implementations (e.g., a test context, a lazy context)
|
|
15
|
+
*
|
|
16
|
+
* @module interfaces/application-context
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The public API of a bootstrapped application context.
|
|
21
|
+
*
|
|
22
|
+
* Extends `ContainerResolver` with:
|
|
23
|
+
* - `select()` — resolve from a specific module
|
|
24
|
+
* - `getContainer()` — access the raw container
|
|
25
|
+
* - `close()` — graceful shutdown with lifecycle hooks
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Type your variable as the interface for testability
|
|
30
|
+
* let app: IApplicationContext;
|
|
31
|
+
*
|
|
32
|
+
* beforeAll(async () => {
|
|
33
|
+
* app = await ApplicationContext.create(AppModule);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* afterAll(async () => {
|
|
37
|
+
* await app.close();
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* test('resolves UserService', () => {
|
|
41
|
+
* const userService = app.get(UserService);
|
|
42
|
+
* expect(userService).toBeDefined();
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
interface IApplicationContext extends ContainerResolver {
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a provider by its injection token.
|
|
49
|
+
*
|
|
50
|
+
* Searches all modules for the provider. For singleton providers,
|
|
51
|
+
* returns the cached instance.
|
|
52
|
+
*
|
|
53
|
+
* @param token - The injection token (class, string, or symbol)
|
|
54
|
+
* @returns The resolved provider instance
|
|
55
|
+
* @throws Error if the provider is not found
|
|
56
|
+
*/
|
|
57
|
+
get<T = any>(token: InjectionToken<T>): T;
|
|
58
|
+
/**
|
|
59
|
+
* Try to resolve a provider, returning `undefined` if not found.
|
|
60
|
+
*
|
|
61
|
+
* @param token - The injection token
|
|
62
|
+
* @returns The resolved instance or undefined
|
|
63
|
+
*/
|
|
64
|
+
getOptional<T = any>(token: InjectionToken<T>): T | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a provider is registered in any module.
|
|
67
|
+
*
|
|
68
|
+
* @param token - The injection token to check
|
|
69
|
+
*/
|
|
70
|
+
has(token: InjectionToken): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Select a specific module and resolve a provider from it.
|
|
73
|
+
*
|
|
74
|
+
* Useful when the same token exists in multiple modules and you
|
|
75
|
+
* need to specify which one to resolve from.
|
|
76
|
+
*
|
|
77
|
+
* @param moduleClass - The module class to search in
|
|
78
|
+
* @param token - The injection token
|
|
79
|
+
* @returns The resolved provider instance
|
|
80
|
+
* @throws Error if the module or provider is not found
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Resolve CacheManager specifically from CacheModule
|
|
85
|
+
* const cache = app.select(CacheModule, CacheManager);
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
select<T = any>(moduleClass: Type<any>, token: InjectionToken<T>): T;
|
|
89
|
+
/**
|
|
90
|
+
* Get the underlying NestContainer.
|
|
91
|
+
*
|
|
92
|
+
* For advanced use cases like inspecting the module graph,
|
|
93
|
+
* accessing raw InstanceWrappers, or building dev tools.
|
|
94
|
+
*/
|
|
95
|
+
getContainer(): NestContainer;
|
|
96
|
+
/**
|
|
97
|
+
* Gracefully shut down the application.
|
|
98
|
+
*
|
|
99
|
+
* Calls `onModuleDestroy()` on all providers that implement it,
|
|
100
|
+
* in reverse module order (leaf modules first, root module last).
|
|
101
|
+
*
|
|
102
|
+
* After calling `close()`, the context is no longer usable.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // In your app's cleanup logic
|
|
107
|
+
* window.addEventListener('beforeunload', () => {
|
|
108
|
+
* app.close();
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
close(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @fileoverview ApplicationContext — the main entry point for bootstrapping the DI system.
|
|
117
|
+
*
|
|
118
|
+
* This is the equivalent of NestJS's `NestFactory.createApplicationContext()`.
|
|
119
|
+
* It orchestrates the full bootstrap sequence:
|
|
120
|
+
*
|
|
121
|
+
* 1. **Scan** — Walk the module tree, register all modules/providers/imports/exports
|
|
122
|
+
* 2. **Instantiate** — Resolve all providers (create instances, inject dependencies)
|
|
123
|
+
* 3. **Lifecycle** — Call `onModuleInit()` on providers that implement it
|
|
124
|
+
*
|
|
125
|
+
* After bootstrap, the ApplicationContext provides `get()` to resolve any provider.
|
|
126
|
+
*
|
|
127
|
+
* ## Usage:
|
|
128
|
+
*
|
|
129
|
+
* ```typescript
|
|
130
|
+
* import { ApplicationContext } from '@pixielity/application';
|
|
131
|
+
* import { AppModule } from './app.module';
|
|
132
|
+
*
|
|
133
|
+
* // Bootstrap
|
|
134
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
135
|
+
*
|
|
136
|
+
* // Resolve providers
|
|
137
|
+
* const cache = app.get(CacheManager);
|
|
138
|
+
* const config = app.get<AppConfig>(APP_CONFIG);
|
|
139
|
+
*
|
|
140
|
+
* // Shutdown
|
|
141
|
+
* await app.close();
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* ## With React:
|
|
145
|
+
*
|
|
146
|
+
* ```tsx
|
|
147
|
+
* import { ContainerProvider } from '@abdokouta/ts-container/react';
|
|
148
|
+
*
|
|
149
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
150
|
+
*
|
|
151
|
+
* ReactDOM.createRoot(root).render(
|
|
152
|
+
* <ContainerProvider context={app}>
|
|
153
|
+
* <App />
|
|
154
|
+
* </ContainerProvider>
|
|
155
|
+
* );
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* The ApplicationContext implements the `ContainerResolver` interface from
|
|
159
|
+
* `@abdokouta/ts-container/react`, so it can be passed directly to `ContainerProvider`.
|
|
160
|
+
*
|
|
161
|
+
* @module application-context
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The bootstrapped application context.
|
|
166
|
+
*
|
|
167
|
+
* Provides access to the DI container after all modules have been
|
|
168
|
+
* scanned and all providers have been instantiated.
|
|
169
|
+
*
|
|
170
|
+
* Implements `IApplicationContext` (which extends `ContainerResolver`)
|
|
171
|
+
* so it can be used directly with `<ContainerProvider context={app}>`
|
|
172
|
+
* from `@abdokouta/ts-container/react`.
|
|
173
|
+
*/
|
|
174
|
+
declare class ApplicationContext implements IApplicationContext {
|
|
175
|
+
private readonly container;
|
|
176
|
+
private readonly instanceLoader;
|
|
177
|
+
private isInitialized;
|
|
178
|
+
private constructor();
|
|
179
|
+
/**
|
|
180
|
+
* Create and bootstrap an application context.
|
|
181
|
+
*
|
|
182
|
+
* This is the main entry point. It:
|
|
183
|
+
* 1. Scans the module tree starting from the root module
|
|
184
|
+
* 2. Resolves all providers (creates instances, injects dependencies)
|
|
185
|
+
* 3. Calls `onModuleInit()` lifecycle hooks
|
|
186
|
+
*
|
|
187
|
+
* @param rootModule - The root module class (your AppModule)
|
|
188
|
+
* @returns A fully bootstrapped ApplicationContext
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { ApplicationContext } from '@pixielity/application';
|
|
193
|
+
* import { AppModule } from './app.module';
|
|
194
|
+
*
|
|
195
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
196
|
+
*
|
|
197
|
+
* const userService = app.get(UserService);
|
|
198
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
static create(rootModule: Type<any>): Promise<ApplicationContext>;
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a provider by its injection token.
|
|
204
|
+
*
|
|
205
|
+
* Searches all modules for the provider. For singleton providers,
|
|
206
|
+
* returns the cached instance.
|
|
207
|
+
*
|
|
208
|
+
* @param token - The injection token (class, string, or symbol)
|
|
209
|
+
* @returns The resolved provider instance
|
|
210
|
+
* @throws Error if the provider is not found
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const userService = app.get(UserService);
|
|
215
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
216
|
+
* const apiUrl = app.get<string>('API_URL');
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
get<T = any>(token: InjectionToken<T>): T;
|
|
220
|
+
/**
|
|
221
|
+
* Try to resolve a provider, returning `undefined` if not found.
|
|
222
|
+
*
|
|
223
|
+
* @param token - The injection token
|
|
224
|
+
* @returns The resolved instance or undefined
|
|
225
|
+
*/
|
|
226
|
+
getOptional<T = any>(token: InjectionToken<T>): T | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* Check if a provider is registered in any module.
|
|
229
|
+
*
|
|
230
|
+
* @param token - The injection token to check
|
|
231
|
+
*/
|
|
232
|
+
has(token: InjectionToken): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Select a specific module and resolve a provider from it.
|
|
235
|
+
*
|
|
236
|
+
* Useful when the same token exists in multiple modules.
|
|
237
|
+
*
|
|
238
|
+
* @param moduleClass - The module class to search in
|
|
239
|
+
* @param token - The injection token
|
|
240
|
+
*/
|
|
241
|
+
select<T = any>(moduleClass: Type<any>, token: InjectionToken<T>): T;
|
|
242
|
+
/**
|
|
243
|
+
* Get the underlying NestContainer (for advanced use cases).
|
|
244
|
+
*/
|
|
245
|
+
getContainer(): NestContainer;
|
|
246
|
+
/**
|
|
247
|
+
* Gracefully shut down the application.
|
|
248
|
+
*
|
|
249
|
+
* Calls `onModuleDestroy()` on all providers that implement it,
|
|
250
|
+
* in reverse module order (leaf modules first).
|
|
251
|
+
*/
|
|
252
|
+
close(): Promise<void>;
|
|
253
|
+
private assertInitialized;
|
|
254
|
+
private getTokenName;
|
|
255
|
+
/**
|
|
256
|
+
* Synchronously instantiate a transient provider.
|
|
257
|
+
*
|
|
258
|
+
* After bootstrap, all dependencies of a transient provider are already
|
|
259
|
+
* resolved singletons. So we can synchronously look them up and call
|
|
260
|
+
* `new Class(...deps)` without awaiting anything.
|
|
261
|
+
*/
|
|
262
|
+
private instantiateTransient;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export { ApplicationContext, type IApplicationContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { NestContainer, DependenciesScanner, InstanceLoader } from '@abdokouta/ts-container';
|
|
2
|
+
|
|
3
|
+
// src/application-context.ts
|
|
4
|
+
var ApplicationContext = class _ApplicationContext {
|
|
5
|
+
container;
|
|
6
|
+
instanceLoader;
|
|
7
|
+
isInitialized = false;
|
|
8
|
+
constructor(container, instanceLoader) {
|
|
9
|
+
this.container = container;
|
|
10
|
+
this.instanceLoader = instanceLoader;
|
|
11
|
+
}
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// Static factory
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Create and bootstrap an application context.
|
|
17
|
+
*
|
|
18
|
+
* This is the main entry point. It:
|
|
19
|
+
* 1. Scans the module tree starting from the root module
|
|
20
|
+
* 2. Resolves all providers (creates instances, injects dependencies)
|
|
21
|
+
* 3. Calls `onModuleInit()` lifecycle hooks
|
|
22
|
+
*
|
|
23
|
+
* @param rootModule - The root module class (your AppModule)
|
|
24
|
+
* @returns A fully bootstrapped ApplicationContext
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { ApplicationContext } from '@pixielity/application';
|
|
29
|
+
* import { AppModule } from './app.module';
|
|
30
|
+
*
|
|
31
|
+
* const app = await ApplicationContext.create(AppModule);
|
|
32
|
+
*
|
|
33
|
+
* const userService = app.get(UserService);
|
|
34
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
static async create(rootModule) {
|
|
38
|
+
const container = new NestContainer();
|
|
39
|
+
const scanner = new DependenciesScanner(container);
|
|
40
|
+
const instanceLoader = new InstanceLoader(container);
|
|
41
|
+
await scanner.scan(rootModule);
|
|
42
|
+
await instanceLoader.createInstances();
|
|
43
|
+
const app = new _ApplicationContext(container, instanceLoader);
|
|
44
|
+
app.isInitialized = true;
|
|
45
|
+
return app;
|
|
46
|
+
}
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
48
|
+
// ContainerResolver interface (used by @abdokouta/ts-container/react)
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a provider by its injection token.
|
|
52
|
+
*
|
|
53
|
+
* Searches all modules for the provider. For singleton providers,
|
|
54
|
+
* returns the cached instance.
|
|
55
|
+
*
|
|
56
|
+
* @param token - The injection token (class, string, or symbol)
|
|
57
|
+
* @returns The resolved provider instance
|
|
58
|
+
* @throws Error if the provider is not found
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const userService = app.get(UserService);
|
|
63
|
+
* const config = app.get<CacheConfig>(CACHE_CONFIG);
|
|
64
|
+
* const apiUrl = app.get<string>('API_URL');
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
get(token) {
|
|
68
|
+
this.assertInitialized();
|
|
69
|
+
for (const [, moduleRef] of this.container.getModules()) {
|
|
70
|
+
const wrapper = moduleRef.providers.get(token);
|
|
71
|
+
if (!wrapper) continue;
|
|
72
|
+
if (wrapper.isResolved && !wrapper.isTransient) {
|
|
73
|
+
return wrapper.instance;
|
|
74
|
+
}
|
|
75
|
+
if (wrapper.isTransient && wrapper.metatype) {
|
|
76
|
+
return this.instantiateTransient(wrapper, moduleRef);
|
|
77
|
+
}
|
|
78
|
+
if (wrapper.isTransient && wrapper.instance !== null) {
|
|
79
|
+
return wrapper.instance;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Provider '${this.getTokenName(token)}' not found in any module. Make sure it is provided in a module that has been imported.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Try to resolve a provider, returning `undefined` if not found.
|
|
88
|
+
*
|
|
89
|
+
* @param token - The injection token
|
|
90
|
+
* @returns The resolved instance or undefined
|
|
91
|
+
*/
|
|
92
|
+
getOptional(token) {
|
|
93
|
+
try {
|
|
94
|
+
return this.get(token);
|
|
95
|
+
} catch {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a provider is registered in any module.
|
|
101
|
+
*
|
|
102
|
+
* @param token - The injection token to check
|
|
103
|
+
*/
|
|
104
|
+
has(token) {
|
|
105
|
+
for (const [, moduleRef] of this.container.getModules()) {
|
|
106
|
+
if (moduleRef.providers.has(token)) return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
111
|
+
// Advanced API
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
113
|
+
/**
|
|
114
|
+
* Select a specific module and resolve a provider from it.
|
|
115
|
+
*
|
|
116
|
+
* Useful when the same token exists in multiple modules.
|
|
117
|
+
*
|
|
118
|
+
* @param moduleClass - The module class to search in
|
|
119
|
+
* @param token - The injection token
|
|
120
|
+
*/
|
|
121
|
+
select(moduleClass, token) {
|
|
122
|
+
this.assertInitialized();
|
|
123
|
+
for (const [, moduleRef] of this.container.getModules()) {
|
|
124
|
+
if (moduleRef.metatype === moduleClass) {
|
|
125
|
+
const wrapper = moduleRef.providers.get(token);
|
|
126
|
+
if (wrapper?.isResolved) {
|
|
127
|
+
return wrapper.instance;
|
|
128
|
+
}
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Provider '${this.getTokenName(token)}' not found in module '${moduleClass.name}'.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
throw new Error(`Module '${moduleClass.name}' not found in the container.`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the underlying NestContainer (for advanced use cases).
|
|
138
|
+
*/
|
|
139
|
+
getContainer() {
|
|
140
|
+
return this.container;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Gracefully shut down the application.
|
|
144
|
+
*
|
|
145
|
+
* Calls `onModuleDestroy()` on all providers that implement it,
|
|
146
|
+
* in reverse module order (leaf modules first).
|
|
147
|
+
*/
|
|
148
|
+
async close() {
|
|
149
|
+
await this.instanceLoader.destroy();
|
|
150
|
+
this.isInitialized = false;
|
|
151
|
+
}
|
|
152
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
153
|
+
// Private
|
|
154
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
155
|
+
assertInitialized() {
|
|
156
|
+
if (!this.isInitialized) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"ApplicationContext is not initialized. Call ApplicationContext.create() first."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
getTokenName(token) {
|
|
163
|
+
if (typeof token === "function") return token.name;
|
|
164
|
+
if (typeof token === "symbol") return token.toString();
|
|
165
|
+
return String(token);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Synchronously instantiate a transient provider.
|
|
169
|
+
*
|
|
170
|
+
* After bootstrap, all dependencies of a transient provider are already
|
|
171
|
+
* resolved singletons. So we can synchronously look them up and call
|
|
172
|
+
* `new Class(...deps)` without awaiting anything.
|
|
173
|
+
*/
|
|
174
|
+
instantiateTransient(wrapper, moduleRef) {
|
|
175
|
+
const injector = this.instanceLoader.getInjector();
|
|
176
|
+
const metatype = wrapper.metatype;
|
|
177
|
+
const deps = injector.getConstructorDependencies(metatype);
|
|
178
|
+
const optionalIndices = injector.getOptionalDependencies(metatype);
|
|
179
|
+
const resolvedDeps = deps.map((dep, index) => {
|
|
180
|
+
if (dep === void 0 || dep === null || dep === Object) {
|
|
181
|
+
if (optionalIndices.includes(index)) return void 0;
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
const result = injector.lookupProvider(dep, moduleRef);
|
|
185
|
+
if (!result) {
|
|
186
|
+
if (optionalIndices.includes(index)) return void 0;
|
|
187
|
+
throw new Error(`Cannot resolve transient dependency '${this.getTokenName(dep)}'`);
|
|
188
|
+
}
|
|
189
|
+
return result.wrapper.instance;
|
|
190
|
+
});
|
|
191
|
+
return new metatype(...resolvedDeps);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export { ApplicationContext };
|
|
196
|
+
//# sourceMappingURL=index.js.map
|
|
197
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/application-context.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @fileoverview ApplicationContext — the main entry point for bootstrapping the DI system.\n *\n * This is the equivalent of NestJS's `NestFactory.createApplicationContext()`.\n * It orchestrates the full bootstrap sequence:\n *\n * 1. **Scan** — Walk the module tree, register all modules/providers/imports/exports\n * 2. **Instantiate** — Resolve all providers (create instances, inject dependencies)\n * 3. **Lifecycle** — Call `onModuleInit()` on providers that implement it\n *\n * After bootstrap, the ApplicationContext provides `get()` to resolve any provider.\n *\n * ## Usage:\n *\n * ```typescript\n * import { ApplicationContext } from '@pixielity/application';\n * import { AppModule } from './app.module';\n *\n * // Bootstrap\n * const app = await ApplicationContext.create(AppModule);\n *\n * // Resolve providers\n * const cache = app.get(CacheManager);\n * const config = app.get<AppConfig>(APP_CONFIG);\n *\n * // Shutdown\n * await app.close();\n * ```\n *\n * ## With React:\n *\n * ```tsx\n * import { ContainerProvider } from '@abdokouta/ts-container/react';\n *\n * const app = await ApplicationContext.create(AppModule);\n *\n * ReactDOM.createRoot(root).render(\n * <ContainerProvider context={app}>\n * <App />\n * </ContainerProvider>\n * );\n * ```\n *\n * The ApplicationContext implements the `ContainerResolver` interface from\n * `@abdokouta/ts-container/react`, so it can be passed directly to `ContainerProvider`.\n *\n * @module application-context\n */\n\nimport type { Type, InjectionToken } from '@abdokouta/ts-container';\nimport {\n NestContainer,\n DependenciesScanner,\n InstanceLoader,\n ModuleRef,\n} from '@abdokouta/ts-container';\nimport type { IApplicationContext } from './interfaces/application-context.interface';\n\n/**\n * The bootstrapped application context.\n *\n * Provides access to the DI container after all modules have been\n * scanned and all providers have been instantiated.\n *\n * Implements `IApplicationContext` (which extends `ContainerResolver`)\n * so it can be used directly with `<ContainerProvider context={app}>`\n * from `@abdokouta/ts-container/react`.\n */\nexport class ApplicationContext implements IApplicationContext {\n private readonly container: NestContainer;\n private readonly instanceLoader: InstanceLoader;\n private isInitialized = false;\n\n private constructor(container: NestContainer, instanceLoader: InstanceLoader) {\n this.container = container;\n this.instanceLoader = instanceLoader;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Static factory\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Create and bootstrap an application context.\n *\n * This is the main entry point. It:\n * 1. Scans the module tree starting from the root module\n * 2. Resolves all providers (creates instances, injects dependencies)\n * 3. Calls `onModuleInit()` lifecycle hooks\n *\n * @param rootModule - The root module class (your AppModule)\n * @returns A fully bootstrapped ApplicationContext\n *\n * @example\n * ```typescript\n * import { ApplicationContext } from '@pixielity/application';\n * import { AppModule } from './app.module';\n *\n * const app = await ApplicationContext.create(AppModule);\n *\n * const userService = app.get(UserService);\n * const config = app.get<CacheConfig>(CACHE_CONFIG);\n * ```\n */\n public static async create(rootModule: Type<any>): Promise<ApplicationContext> {\n const container = new NestContainer();\n const scanner = new DependenciesScanner(container);\n const instanceLoader = new InstanceLoader(container);\n\n // Phase 1: Scan the module tree\n await scanner.scan(rootModule);\n\n // Phase 2: Create all provider instances\n await instanceLoader.createInstances();\n\n const app = new ApplicationContext(container, instanceLoader);\n app.isInitialized = true;\n\n return app;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // ContainerResolver interface (used by @abdokouta/ts-container/react)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Resolve a provider by its injection token.\n *\n * Searches all modules for the provider. For singleton providers,\n * returns the cached instance.\n *\n * @param token - The injection token (class, string, or symbol)\n * @returns The resolved provider instance\n * @throws Error if the provider is not found\n *\n * @example\n * ```typescript\n * const userService = app.get(UserService);\n * const config = app.get<CacheConfig>(CACHE_CONFIG);\n * const apiUrl = app.get<string>('API_URL');\n * ```\n */\n public get<T = any>(token: InjectionToken<T>): T {\n this.assertInitialized();\n\n for (const [, moduleRef] of this.container.getModules()) {\n const wrapper = moduleRef.providers.get(token);\n if (!wrapper) continue;\n\n // Singleton or value provider — return cached instance\n if (wrapper.isResolved && !wrapper.isTransient) {\n return wrapper.instance as T;\n }\n\n // Transient provider — create a fresh instance each time.\n // All dependencies should already be resolved after bootstrap,\n // so we can synchronously instantiate the class.\n if (wrapper.isTransient && wrapper.metatype) {\n return this.instantiateTransient<T>(wrapper, moduleRef);\n }\n\n // Transient provider that was resolved during bootstrap\n // (has a cached instance from the initial resolution)\n if (wrapper.isTransient && wrapper.instance !== null) {\n return wrapper.instance as T;\n }\n }\n\n throw new Error(\n `Provider '${this.getTokenName(token)}' not found in any module. ` +\n `Make sure it is provided in a module that has been imported.`,\n );\n }\n\n /**\n * Try to resolve a provider, returning `undefined` if not found.\n *\n * @param token - The injection token\n * @returns The resolved instance or undefined\n */\n public getOptional<T = any>(token: InjectionToken<T>): T | undefined {\n try {\n return this.get(token);\n } catch {\n return undefined;\n }\n }\n\n /**\n * Check if a provider is registered in any module.\n *\n * @param token - The injection token to check\n */\n public has(token: InjectionToken): boolean {\n for (const [, moduleRef] of this.container.getModules()) {\n if (moduleRef.providers.has(token)) return true;\n }\n return false;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Advanced API\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Select a specific module and resolve a provider from it.\n *\n * Useful when the same token exists in multiple modules.\n *\n * @param moduleClass - The module class to search in\n * @param token - The injection token\n */\n public select<T = any>(moduleClass: Type<any>, token: InjectionToken<T>): T {\n this.assertInitialized();\n\n for (const [, moduleRef] of this.container.getModules()) {\n if (moduleRef.metatype === moduleClass) {\n const wrapper = moduleRef.providers.get(token);\n if (wrapper?.isResolved) {\n return wrapper.instance as T;\n }\n throw new Error(\n `Provider '${this.getTokenName(token)}' not found in module '${moduleClass.name}'.`,\n );\n }\n }\n\n throw new Error(`Module '${moduleClass.name}' not found in the container.`);\n }\n\n /**\n * Get the underlying NestContainer (for advanced use cases).\n */\n public getContainer(): NestContainer {\n return this.container;\n }\n\n /**\n * Gracefully shut down the application.\n *\n * Calls `onModuleDestroy()` on all providers that implement it,\n * in reverse module order (leaf modules first).\n */\n public async close(): Promise<void> {\n await this.instanceLoader.destroy();\n this.isInitialized = false;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private\n // ─────────────────────────────────────────────────────────────────────────\n\n private assertInitialized(): void {\n if (!this.isInitialized) {\n throw new Error(\n 'ApplicationContext is not initialized. Call ApplicationContext.create() first.',\n );\n }\n }\n\n private getTokenName(token: InjectionToken): string {\n if (typeof token === 'function') return token.name;\n if (typeof token === 'symbol') return token.toString();\n return String(token);\n }\n\n /**\n * Synchronously instantiate a transient provider.\n *\n * After bootstrap, all dependencies of a transient provider are already\n * resolved singletons. So we can synchronously look them up and call\n * `new Class(...deps)` without awaiting anything.\n */\n private instantiateTransient<T>(wrapper: any, moduleRef: ModuleRef): T {\n const injector = this.instanceLoader.getInjector();\n\n // Look up constructor dependencies — they should all be resolved singletons\n const metatype = wrapper.metatype;\n const deps = (injector as any).getConstructorDependencies(metatype);\n const optionalIndices: number[] = (injector as any).getOptionalDependencies(metatype);\n\n const resolvedDeps = deps.map((dep: InjectionToken, index: number) => {\n if (dep === undefined || dep === null || dep === Object) {\n if (optionalIndices.includes(index)) return undefined;\n return undefined;\n }\n\n const result = injector.lookupProvider(dep, moduleRef);\n if (!result) {\n if (optionalIndices.includes(index)) return undefined;\n throw new Error(`Cannot resolve transient dependency '${this.getTokenName(dep)}'`);\n }\n\n return result.wrapper.instance;\n });\n\n return new metatype(...resolvedDeps);\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@abdokouta/ts-application",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Application bootstrap layer for @abdokouta/ts-container. Provides ApplicationContext.create() to scan modules, instantiate providers, and run lifecycle hooks.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dependency-injection",
|
|
7
|
+
"di",
|
|
8
|
+
"application",
|
|
9
|
+
"bootstrap",
|
|
10
|
+
"nestjs",
|
|
11
|
+
"lifecycle",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Pixielity",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"main": "dist/index.cjs",
|
|
26
|
+
"module": "dist/index.mjs",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@abdokouta/ts-container": "1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^25.5.2",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^6.0.2",
|
|
40
|
+
"@nesvel/typescript-config": "^1.0.4",
|
|
41
|
+
"vitest": "^4.1.2"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"dev": "tsup --watch",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"clean": "rm -rf dist",
|
|
54
|
+
"test": "vitest run --passWithNoTests"
|
|
55
|
+
}
|
|
56
|
+
}
|