@signe/di 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +220 -0
- package/dist/index.js +182 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -0
- package/readme.md +130 -0
- package/src/context.ts +30 -0
- package/src/index.ts +27 -0
- package/src/inject.ts +184 -0
- package/src/merge-config.ts +41 -0
- package/src/provider.ts +71 -0
- package/src/types.ts +80 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the dependency injection system
|
|
3
|
+
* @module @signe/di/types
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Context object used for dependency injection
|
|
7
|
+
*/
|
|
8
|
+
type ProviderContext = any;
|
|
9
|
+
/**
|
|
10
|
+
* Token used to identify a provider. Can be either a class constructor or a string
|
|
11
|
+
*/
|
|
12
|
+
type ProviderToken = (new (...args: any[]) => any) | string;
|
|
13
|
+
/**
|
|
14
|
+
* Factory function type that creates instances using the provided context
|
|
15
|
+
* @param context - The injection context
|
|
16
|
+
* @returns The created instance
|
|
17
|
+
*/
|
|
18
|
+
type FactoryFn = (context: ProviderContext) => any;
|
|
19
|
+
/**
|
|
20
|
+
* Provider configuration for value-based injection
|
|
21
|
+
*/
|
|
22
|
+
interface ValueProvider {
|
|
23
|
+
/** Token to identify the provider */
|
|
24
|
+
provide: ProviderToken;
|
|
25
|
+
/** Value to be injected */
|
|
26
|
+
useValue: any;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Provider configuration for class-based injection
|
|
30
|
+
*/
|
|
31
|
+
interface ClassProvider {
|
|
32
|
+
/** Token to identify the provider */
|
|
33
|
+
provide: ProviderToken;
|
|
34
|
+
/** Class to be instantiated */
|
|
35
|
+
useClass: new (context: ProviderContext) => any;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Provider configuration for factory-based injection
|
|
39
|
+
*/
|
|
40
|
+
interface FactoryProvider {
|
|
41
|
+
/** Token to identify the provider */
|
|
42
|
+
provide: ProviderToken;
|
|
43
|
+
/** Optional metadata for the provider */
|
|
44
|
+
meta?: {
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
};
|
|
47
|
+
/** Factory function to create the instance */
|
|
48
|
+
useFactory: FactoryFn;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Provider configuration for alias-based injection
|
|
52
|
+
*/
|
|
53
|
+
interface ExistingProvider {
|
|
54
|
+
/** Token to identify the provider */
|
|
55
|
+
provide: ProviderToken;
|
|
56
|
+
/** Token of the existing provider to use */
|
|
57
|
+
useExisting: ProviderToken;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Union type for all possible provider configurations
|
|
61
|
+
*/
|
|
62
|
+
type Provider = (new (...args: any[]) => any) | (ValueProvider & {
|
|
63
|
+
useClass?: never;
|
|
64
|
+
useFactory?: never;
|
|
65
|
+
useExisting?: never;
|
|
66
|
+
}) | (ClassProvider & {
|
|
67
|
+
useValue?: never;
|
|
68
|
+
useFactory?: never;
|
|
69
|
+
useExisting?: never;
|
|
70
|
+
}) | (FactoryProvider & {
|
|
71
|
+
useValue?: never;
|
|
72
|
+
useClass?: never;
|
|
73
|
+
useExisting?: never;
|
|
74
|
+
}) | (ExistingProvider & {
|
|
75
|
+
useValue?: never;
|
|
76
|
+
useClass?: never;
|
|
77
|
+
useFactory?: never;
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Array of providers that can be nested one level deep
|
|
81
|
+
*/
|
|
82
|
+
type Providers = (Provider | Providers)[];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Context class for managing dependency injection state
|
|
86
|
+
* @module @signe/di/context
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Stores and manages the state of injected dependencies
|
|
90
|
+
*/
|
|
91
|
+
declare class Context {
|
|
92
|
+
/** Internal storage for injected values */
|
|
93
|
+
private values;
|
|
94
|
+
/**
|
|
95
|
+
* Sets a value in the context
|
|
96
|
+
* @param key - Unique identifier for the value
|
|
97
|
+
* @param value - Value to store
|
|
98
|
+
*/
|
|
99
|
+
set(key: string, value: any): void;
|
|
100
|
+
/**
|
|
101
|
+
* Retrieves a value from the context
|
|
102
|
+
* @param key - Unique identifier for the value
|
|
103
|
+
* @returns The stored value or undefined if not found
|
|
104
|
+
*/
|
|
105
|
+
get(key: string): any;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Core functionality for dependency injection
|
|
110
|
+
* @module @signe/di/inject
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Provides a value to the dependency injection context
|
|
115
|
+
* @template T - Type of the value being provided
|
|
116
|
+
* @param context - The injection context
|
|
117
|
+
* @param name - Identifier for the provided value
|
|
118
|
+
* @param value - The value to provide
|
|
119
|
+
* @returns The provided value
|
|
120
|
+
*/
|
|
121
|
+
declare function provide<T>(context: Context, name: string, value: T): T;
|
|
122
|
+
/**
|
|
123
|
+
* Checks if a service has been injected into the context
|
|
124
|
+
* @param context - The injection context
|
|
125
|
+
* @param name - Name of the service to check
|
|
126
|
+
* @returns True if the service has been injected, false otherwise
|
|
127
|
+
*/
|
|
128
|
+
declare function isInjected(context: Context, name: string): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Retrieves a service from the dependency injection context
|
|
131
|
+
* @template T - Type of the service to inject
|
|
132
|
+
* @param context - The injection context
|
|
133
|
+
* @param service - Class constructor or string identifier of the service
|
|
134
|
+
* @param args - Optional arguments for service instantiation
|
|
135
|
+
* @returns The injected service instance
|
|
136
|
+
* @throws {Error} If the requested service is not found in the context
|
|
137
|
+
*/
|
|
138
|
+
declare function inject<T>(context: Context, service: (new (...args: any[]) => T) | string, args?: any[]): T;
|
|
139
|
+
/**
|
|
140
|
+
* Overrides or adds a provider in the providers array
|
|
141
|
+
* @param providers - Array of existing providers
|
|
142
|
+
* @param newProvider - Provider to add or replace with
|
|
143
|
+
* @param options - Configuration options
|
|
144
|
+
* @param options.upsert - If true, adds the provider when not found
|
|
145
|
+
* @param options.key - Custom key to identify the provider
|
|
146
|
+
* @returns Updated array of providers
|
|
147
|
+
*/
|
|
148
|
+
declare function override(providers: Providers, newProvider: Provider, options?: {
|
|
149
|
+
upsert?: boolean;
|
|
150
|
+
key?: string;
|
|
151
|
+
}): (Provider | Providers)[];
|
|
152
|
+
/**
|
|
153
|
+
* Finds all providers matching the given name or pattern
|
|
154
|
+
* @template T - Type of provider to return
|
|
155
|
+
* @param providers - Array of providers to search
|
|
156
|
+
* @param name - String or RegExp to match against provider names
|
|
157
|
+
* @returns Array of matching providers
|
|
158
|
+
*/
|
|
159
|
+
declare function findProviders<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T[];
|
|
160
|
+
/**
|
|
161
|
+
* Finds the first provider matching the given name or pattern
|
|
162
|
+
* @template T - Type of provider to return
|
|
163
|
+
* @param providers - Array of providers to search
|
|
164
|
+
* @param name - String or RegExp to match against provider names
|
|
165
|
+
* @returns Matching provider or null if not found
|
|
166
|
+
*/
|
|
167
|
+
declare function findProvider<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T | null;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Configuration merging functionality for dependency injection
|
|
171
|
+
* @module @signe/di/merge-config
|
|
172
|
+
*/
|
|
173
|
+
/**
|
|
174
|
+
* Application configuration interface
|
|
175
|
+
*/
|
|
176
|
+
interface AppConfig {
|
|
177
|
+
/** Array of dependency providers */
|
|
178
|
+
providers: any[];
|
|
179
|
+
/** Optional static files configuration */
|
|
180
|
+
staticFiles?: {
|
|
181
|
+
/** Path to static files */
|
|
182
|
+
path: string;
|
|
183
|
+
/** Static file serving configuration */
|
|
184
|
+
serve: any;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Merges two application configurations
|
|
189
|
+
* @param baseConfig - Base configuration to merge into
|
|
190
|
+
* @param config - Configuration to merge with base
|
|
191
|
+
* @returns Merged configuration with providers properly combined
|
|
192
|
+
*/
|
|
193
|
+
declare function mergeConfig(baseConfig: AppConfig, config: AppConfig): AppConfig;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Provider system implementation for dependency injection
|
|
197
|
+
* @module @signe/di/provider
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Processes and instantiates all providers in the given context
|
|
202
|
+
* @param context - The injection context
|
|
203
|
+
* @param providers - Array of providers to process
|
|
204
|
+
* @returns Promise that resolves when all providers are processed
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* const context = new Context();
|
|
209
|
+
* const providers = [
|
|
210
|
+
* UserService,
|
|
211
|
+
* { provide: 'CONFIG', useValue: { apiUrl: 'http://api.example.com' } },
|
|
212
|
+
* { provide: AuthService, useFactory: (ctx) => new AuthService(ctx) }
|
|
213
|
+
* ];
|
|
214
|
+
*
|
|
215
|
+
* await injector(context, providers);
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
declare function injector(context: Context, providers: Providers): Promise<void>;
|
|
219
|
+
|
|
220
|
+
export { type AppConfig, type ClassProvider, Context, type ExistingProvider, type FactoryFn, type FactoryProvider, type Provider, type ProviderContext, type ProviderToken, type Providers, type ValueProvider, findProvider, findProviders, inject, injector, isInjected, mergeConfig, override, provide };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/inject.ts
|
|
5
|
+
function provide(context, name, value) {
|
|
6
|
+
context.set("inject:" + name, value);
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
__name(provide, "provide");
|
|
10
|
+
function isInjected(context, name) {
|
|
11
|
+
return context.get("injected:" + name) === true;
|
|
12
|
+
}
|
|
13
|
+
__name(isInjected, "isInjected");
|
|
14
|
+
function inject(context, service, args = []) {
|
|
15
|
+
const isClass = typeof service === "function";
|
|
16
|
+
const name = isClass ? service.name : service;
|
|
17
|
+
const value = context.get("inject:" + name);
|
|
18
|
+
if (value) {
|
|
19
|
+
context.set("injected:" + name, true);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Injection provider ${name} not found`);
|
|
23
|
+
}
|
|
24
|
+
__name(inject, "inject");
|
|
25
|
+
function override(providers, newProvider, options) {
|
|
26
|
+
let { upsert = false, key } = options ?? {};
|
|
27
|
+
if (!key) {
|
|
28
|
+
key = typeof newProvider === "function" ? newProvider.name : newProvider.provide;
|
|
29
|
+
}
|
|
30
|
+
const flatProviders = providers.flat();
|
|
31
|
+
const exists = flatProviders.some((provider) => {
|
|
32
|
+
if (typeof provider === "function") {
|
|
33
|
+
return provider.name === key;
|
|
34
|
+
} else if (typeof provider === "object") {
|
|
35
|
+
return provider.provide === key;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
});
|
|
39
|
+
const mappedProviders = flatProviders.map((provider) => {
|
|
40
|
+
if (typeof provider === "function" && provider.name === key) {
|
|
41
|
+
return newProvider;
|
|
42
|
+
} else if (typeof provider === "object" && provider.provide === key) {
|
|
43
|
+
return newProvider;
|
|
44
|
+
}
|
|
45
|
+
return provider;
|
|
46
|
+
});
|
|
47
|
+
if (upsert && !exists) {
|
|
48
|
+
mappedProviders.push(newProvider);
|
|
49
|
+
}
|
|
50
|
+
return mappedProviders;
|
|
51
|
+
}
|
|
52
|
+
__name(override, "override");
|
|
53
|
+
function findProviders(providers, name) {
|
|
54
|
+
const results = [];
|
|
55
|
+
for (const provider of providers) {
|
|
56
|
+
if (Array.isArray(provider)) {
|
|
57
|
+
results.push(...findProviders(provider, name));
|
|
58
|
+
} else if (findProvider(provider, name)) {
|
|
59
|
+
results.push(provider);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
__name(findProviders, "findProviders");
|
|
65
|
+
function findProvider(providers, name) {
|
|
66
|
+
if (!Array.isArray(providers)) {
|
|
67
|
+
if (typeof providers === "object" && "provide" in providers) {
|
|
68
|
+
const provider = providers;
|
|
69
|
+
const providerName = typeof provider.provide === "function" ? provider.provide.name : provider.provide;
|
|
70
|
+
if (name instanceof RegExp) {
|
|
71
|
+
if (name.test(providerName)) return providers;
|
|
72
|
+
} else {
|
|
73
|
+
if (providerName === name) return providers;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
for (const provider of providers) {
|
|
79
|
+
if (Array.isArray(provider)) {
|
|
80
|
+
const found = findProvider(provider, name);
|
|
81
|
+
if (found) return found;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (typeof provider === "object" && "provide" in provider) {
|
|
85
|
+
const providerName = typeof provider.provide === "function" ? provider.provide.name : provider.provide;
|
|
86
|
+
if (name instanceof RegExp) {
|
|
87
|
+
if (name.test(providerName)) return provider;
|
|
88
|
+
} else {
|
|
89
|
+
if (providerName === name) return provider;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
__name(findProvider, "findProvider");
|
|
96
|
+
|
|
97
|
+
// src/merge-config.ts
|
|
98
|
+
function mergeConfig(baseConfig, config) {
|
|
99
|
+
const mergedConfig = {
|
|
100
|
+
...baseConfig,
|
|
101
|
+
...config
|
|
102
|
+
};
|
|
103
|
+
for (let provider of config.providers) {
|
|
104
|
+
const isFound = findProvider(baseConfig.providers, provider.provide);
|
|
105
|
+
if (!isFound) {
|
|
106
|
+
mergedConfig.providers = override(baseConfig.providers, provider, {
|
|
107
|
+
upsert: true
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return mergedConfig;
|
|
112
|
+
}
|
|
113
|
+
__name(mergeConfig, "mergeConfig");
|
|
114
|
+
|
|
115
|
+
// src/provider.ts
|
|
116
|
+
async function injector(context, providers) {
|
|
117
|
+
providers = providers.flat();
|
|
118
|
+
for (const provider of providers) {
|
|
119
|
+
let token;
|
|
120
|
+
let instance;
|
|
121
|
+
if (typeof provider === "function") {
|
|
122
|
+
token = provider;
|
|
123
|
+
instance = new provider(context);
|
|
124
|
+
} else {
|
|
125
|
+
token = provider.provide;
|
|
126
|
+
const provideUserClass = provider.useClass;
|
|
127
|
+
const isClass = typeof provideUserClass === "function";
|
|
128
|
+
if (isClass) {
|
|
129
|
+
instance = new provideUserClass(context);
|
|
130
|
+
} else if ("useValue" in provider) {
|
|
131
|
+
instance = provider.useValue;
|
|
132
|
+
} else if ("useFactory" in provider) {
|
|
133
|
+
instance = provider.useFactory?.(context);
|
|
134
|
+
if (instance instanceof Promise) {
|
|
135
|
+
instance = await instance;
|
|
136
|
+
}
|
|
137
|
+
} else if ("useExisting" in provider) {
|
|
138
|
+
instance = inject(context, provider.useExisting);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const name = typeof token === "function" ? token.name : token;
|
|
142
|
+
provide(context, name, instance);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
__name(injector, "injector");
|
|
146
|
+
|
|
147
|
+
// src/context.ts
|
|
148
|
+
var Context = class {
|
|
149
|
+
static {
|
|
150
|
+
__name(this, "Context");
|
|
151
|
+
}
|
|
152
|
+
/** Internal storage for injected values */
|
|
153
|
+
values = {};
|
|
154
|
+
/**
|
|
155
|
+
* Sets a value in the context
|
|
156
|
+
* @param key - Unique identifier for the value
|
|
157
|
+
* @param value - Value to store
|
|
158
|
+
*/
|
|
159
|
+
set(key, value) {
|
|
160
|
+
this.values[key] = value;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Retrieves a value from the context
|
|
164
|
+
* @param key - Unique identifier for the value
|
|
165
|
+
* @returns The stored value or undefined if not found
|
|
166
|
+
*/
|
|
167
|
+
get(key) {
|
|
168
|
+
return this.values[key];
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export {
|
|
172
|
+
Context,
|
|
173
|
+
findProvider,
|
|
174
|
+
findProviders,
|
|
175
|
+
inject,
|
|
176
|
+
injector,
|
|
177
|
+
isInjected,
|
|
178
|
+
mergeConfig,
|
|
179
|
+
override,
|
|
180
|
+
provide
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/inject.ts","../src/merge-config.ts","../src/provider.ts","../src/context.ts"],"sourcesContent":["/**\n * Core functionality for dependency injection\n * @module @signe/di/inject\n */\n\nimport { Provider, Providers } from \"./types\";\nimport { Context } from \"./context\";\n\n/**\n * Provides a value to the dependency injection context\n * @template T - Type of the value being provided\n * @param context - The injection context\n * @param name - Identifier for the provided value\n * @param value - The value to provide\n * @returns The provided value\n */\nexport function provide<T>(context: Context, name: string, value: T): T {\n context.set(\"inject:\" + name, value);\n return value;\n}\n\n/**\n * Checks if a service has been injected into the context\n * @param context - The injection context\n * @param name - Name of the service to check\n * @returns True if the service has been injected, false otherwise\n */\nexport function isInjected(context: Context, name: string): boolean {\n return context.get('injected:' + name) === true;\n}\n\n/**\n * Retrieves a service from the dependency injection context\n * @template T - Type of the service to inject\n * @param context - The injection context\n * @param service - Class constructor or string identifier of the service\n * @param args - Optional arguments for service instantiation\n * @returns The injected service instance\n * @throws {Error} If the requested service is not found in the context\n */\nexport function inject<T>(\n context: Context,\n service: (new (...args: any[]) => T) | string,\n args: any[] = []\n): T {\n const isClass = typeof service === \"function\";\n const name = isClass ? service.name : service;\n const value = context.get(\"inject:\" + name);\n if (value) {\n context.set('injected:' + name, true)\n return value as T;\n }\n throw new Error(`Injection provider ${name} not found`);\n}\n\n/**\n * Overrides or adds a provider in the providers array\n * @param providers - Array of existing providers\n * @param newProvider - Provider to add or replace with\n * @param options - Configuration options\n * @param options.upsert - If true, adds the provider when not found\n * @param options.key - Custom key to identify the provider\n * @returns Updated array of providers\n */\nexport function override(\n providers: Providers,\n newProvider: Provider,\n options?: {\n upsert?: boolean,\n key?: string\n }\n) {\n let { upsert = false, key } = options ?? {};\n if (!key) {\n key = (\n typeof newProvider === \"function\" ? newProvider.name : newProvider.provide\n ) as string;\n }\n\n // Flatten the providers array\n const flatProviders = providers.flat();\n \n // Check if provider exists\n const exists = flatProviders.some(provider => {\n if (typeof provider === \"function\") {\n return provider.name === key;\n } else if (typeof provider === \"object\") {\n return (provider as any).provide === key;\n }\n return false;\n });\n\n // Map and replace if found\n const mappedProviders = flatProviders.map((provider) => {\n if (typeof provider === \"function\" && provider.name === key) {\n return newProvider;\n } else if (typeof provider === \"object\" && (provider as any).provide === key) {\n return newProvider;\n }\n return provider;\n });\n\n // If upsert is true and provider wasn't found, add it to the end\n if (upsert && !exists) {\n mappedProviders.push(newProvider);\n }\n\n return mappedProviders;\n}\n\n/**\n * Finds all providers matching the given name or pattern\n * @template T - Type of provider to return\n * @param providers - Array of providers to search\n * @param name - String or RegExp to match against provider names\n * @returns Array of matching providers\n */\nexport function findProviders<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T[] {\n const results: any[] = [];\n \n for (const provider of providers) {\n if (Array.isArray(provider)) {\n // Recursively search in nested arrays and concat results\n results.push(...findProviders(provider, name));\n } else if (findProvider(provider as any, name)) {\n // Add matching provider to results\n results.push(provider as T);\n }\n }\n \n return results;\n}\n\n/**\n * Finds the first provider matching the given name or pattern\n * @template T - Type of provider to return\n * @param providers - Array of providers to search\n * @param name - String or RegExp to match against provider names\n * @returns Matching provider or null if not found\n */\nexport function findProvider<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T | null {\n // Handle single provider case\n if (!Array.isArray(providers)) {\n if (typeof providers === \"object\" && 'provide' in providers) {\n const provider = providers as any\n const providerName = typeof provider.provide === \"function\"\n ? provider.provide.name \n : provider.provide;\n\n if (name instanceof RegExp) {\n if (name.test(providerName)) return providers as T;\n } else {\n if (providerName === name) return providers as T;\n }\n }\n return null;\n }\n\n // Original array handling logic\n for (const provider of providers) {\n // If provider is an array, recursively search in it\n if (Array.isArray(provider)) {\n const found = findProvider(provider, name);\n if (found) return found as T;\n continue;\n }\n\n // Check object provider\n if (typeof provider === \"object\" && 'provide' in provider) {\n const providerName = typeof provider.provide === \"function\" \n ? provider.provide.name \n : provider.provide;\n\n // Handle RegExp matching\n if (name instanceof RegExp) {\n if (name.test(providerName)) return provider as T;\n } else {\n // Handle exact string matching\n if (providerName === name) return provider as T;\n }\n }\n }\n return null;\n}\n","/**\n * Configuration merging functionality for dependency injection\n * @module @signe/di/merge-config\n */\n\nimport { findProvider, override } from \"./inject\";\n\n/**\n * Application configuration interface\n */\nexport interface AppConfig {\n /** Array of dependency providers */\n providers: any[];\n /** Optional static files configuration */\n staticFiles?: {\n /** Path to static files */\n path: string;\n /** Static file serving configuration */\n serve: any;\n }\n}\n\n/**\n * Merges two application configurations\n * @param baseConfig - Base configuration to merge into\n * @param config - Configuration to merge with base\n * @returns Merged configuration with providers properly combined\n */\nexport function mergeConfig(baseConfig: AppConfig, config: AppConfig): AppConfig {\n const mergedConfig: AppConfig = {\n ...baseConfig,\n ...config\n }\n for (let provider of config.providers) {\n const isFound = findProvider(baseConfig.providers, provider.provide)\n if (!isFound) {\n mergedConfig.providers = override(baseConfig.providers, provider, { upsert: true })\n }\n }\n return mergedConfig;\n}\n","/**\n * Provider system implementation for dependency injection\n * @module @signe/di/provider\n */\n\nimport { ClassProvider, Provider, Providers, ProviderToken } from \"./types\";\nimport { inject, provide } from \"./inject\";\nimport { Context } from \"./context\";\n\n/**\n * Type guard to check if a provider is a ClassProvider\n * @param provider - Provider to check\n * @returns True if the provider is a ClassProvider\n */\nfunction isClassProvider(provider: Provider): provider is ClassProvider {\n if (typeof provider === 'string') {\n return false;\n }\n return 'useClass' in provider && typeof provider.useClass === 'function';\n}\n\n/**\n * Processes and instantiates all providers in the given context\n * @param context - The injection context\n * @param providers - Array of providers to process\n * @returns Promise that resolves when all providers are processed\n * \n * @example\n * ```typescript\n * const context = new Context();\n * const providers = [\n * UserService,\n * { provide: 'CONFIG', useValue: { apiUrl: 'http://api.example.com' } },\n * { provide: AuthService, useFactory: (ctx) => new AuthService(ctx) }\n * ];\n * \n * await injector(context, providers);\n * ```\n */\nexport async function injector(context: Context, providers: Providers) {\n providers = providers.flat();\n for (const provider of providers) {\n let token: ProviderToken;\n let instance: any;\n\n if (typeof provider === 'function') {\n // If provider is a class, treat it as a ClassProvider\n token = provider;\n instance = new provider(context);\n } else {\n token = (provider as any).provide;\n const provideUserClass = (provider as any).useClass;\n const isClass = typeof provideUserClass === 'function';\n if (isClass) {\n instance = new provideUserClass(context);\n } else if ('useValue' in provider) {\n instance = provider.useValue;\n } else if ('useFactory' in provider) {\n instance = provider.useFactory?.(context);\n if (instance instanceof Promise) {\n instance = await instance;\n }\n } else if ('useExisting' in provider) {\n instance = inject(context, provider.useExisting);\n }\n }\n\n const name = typeof token === 'function' ? token.name : token;\n provide(context, name, instance);\n }\n}","/**\n * Context class for managing dependency injection state\n * @module @signe/di/context\n */\n\n/**\n * Stores and manages the state of injected dependencies\n */\nexport class Context {\n /** Internal storage for injected values */\n private values: { [key: string]: any } = {}\n\n /**\n * Sets a value in the context\n * @param key - Unique identifier for the value\n * @param value - Value to store\n */\n set(key: string, value: any) {\n this.values[key] = value\n }\n\n /**\n * Retrieves a value from the context\n * @param key - Unique identifier for the value\n * @returns The stored value or undefined if not found\n */\n get(key: string) {\n return this.values[key]\n }\n}"],"mappings":"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","names":["provide","context","name","value","set","isInjected","get","inject","service","args","isClass","Error","override","providers","newProvider","options","upsert","key","flatProviders","flat","exists","some","provider","mappedProviders","map","push","findProviders","results","Array","isArray","findProvider","providerName","RegExp","test","found","mergeConfig","baseConfig","config","mergedConfig","provider","providers","isFound","findProvider","provide","override","upsert","injector","context","providers","flat","provider","token","instance","provide","provideUserClass","useClass","isClass","useValue","useFactory","Promise","inject","useExisting","name","Context","values","set","key","value","get"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@signe/di",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
},
|
|
11
|
+
"./*": "./*"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "Samuel Ronce",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts",
|
|
22
|
+
"dev": "tsup src/index.ts --watch"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @signe/di
|
|
2
|
+
|
|
3
|
+
A lightweight and flexible dependency injection system for JavaScript/TypeScript applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @signe/di
|
|
9
|
+
# or
|
|
10
|
+
yarn add @signe/di
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @signe/di
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Simple and intuitive API
|
|
18
|
+
- Type-safe dependency injection
|
|
19
|
+
- Provider system with multiple configuration options
|
|
20
|
+
- Context-based injection
|
|
21
|
+
- Override capabilities for testing and customization
|
|
22
|
+
- Support for nested providers
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Basic Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { provide, inject, Context, Providers } from '@signe/di';
|
|
30
|
+
|
|
31
|
+
const context = new Context();
|
|
32
|
+
|
|
33
|
+
class UserService {
|
|
34
|
+
getUser(id: string) {
|
|
35
|
+
return { id, name: 'John Doe' };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class AuthService {
|
|
40
|
+
constructor(private config: any) {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const providers: Providers = [
|
|
44
|
+
UserService,
|
|
45
|
+
{
|
|
46
|
+
provide: 'CONFIG',
|
|
47
|
+
useValue: {
|
|
48
|
+
apiUrl: 'https://api.example.com'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
provide: AuthService,
|
|
53
|
+
useFactory: (context) => {
|
|
54
|
+
const config = inject(context, 'CONFIG');
|
|
55
|
+
return new AuthService(config);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// Provide the service
|
|
61
|
+
provide(context, providers);
|
|
62
|
+
|
|
63
|
+
// Inject and use the service
|
|
64
|
+
const userService = inject(context, UserService);
|
|
65
|
+
const user = userService.getUser('123');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Override Providers
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { override } from '@signe/di';
|
|
72
|
+
|
|
73
|
+
// Override existing provider
|
|
74
|
+
const newProviders = override(providers, {
|
|
75
|
+
provide: UserService,
|
|
76
|
+
useValue: new MockUserService()
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Add new provider with upsert option
|
|
80
|
+
const updatedProviders = override(providers, {
|
|
81
|
+
provide: 'NEW_SERVICE',
|
|
82
|
+
useValue: service
|
|
83
|
+
}, { upsert: true });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Check Injection Status
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { isInjected } from '@signe/di';
|
|
90
|
+
|
|
91
|
+
if (isInjected(context, UserService)) {
|
|
92
|
+
// Service is already injected
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Find Providers
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { findProvider, findProviders } from '@signe/di';
|
|
100
|
+
|
|
101
|
+
// Find single provider
|
|
102
|
+
const userProvider = findProvider(providers, UserService);
|
|
103
|
+
|
|
104
|
+
// Find multiple providers by regex
|
|
105
|
+
const allServices = findProviders(providers, /Service$/);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API Reference
|
|
109
|
+
|
|
110
|
+
### `provide(context, key, value)`
|
|
111
|
+
Stores a value in the context for dependency injection.
|
|
112
|
+
|
|
113
|
+
### `inject(context, key)`
|
|
114
|
+
Retrieves an injected value from the context.
|
|
115
|
+
|
|
116
|
+
### `isInjected(context, key)`
|
|
117
|
+
Checks if a value has been injected.
|
|
118
|
+
|
|
119
|
+
### `override(providers, newProvider, options?)`
|
|
120
|
+
Overrides or adds new providers to the existing provider array.
|
|
121
|
+
|
|
122
|
+
### `findProvider(providers, query)`
|
|
123
|
+
Finds a single provider by name or regex.
|
|
124
|
+
|
|
125
|
+
### `findProviders(providers, query)`
|
|
126
|
+
Finds all providers matching the query.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT © Samuel Ronce
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context class for managing dependency injection state
|
|
3
|
+
* @module @signe/di/context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Stores and manages the state of injected dependencies
|
|
8
|
+
*/
|
|
9
|
+
export class Context {
|
|
10
|
+
/** Internal storage for injected values */
|
|
11
|
+
private values: { [key: string]: any } = {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sets a value in the context
|
|
15
|
+
* @param key - Unique identifier for the value
|
|
16
|
+
* @param value - Value to store
|
|
17
|
+
*/
|
|
18
|
+
set(key: string, value: any) {
|
|
19
|
+
this.values[key] = value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves a value from the context
|
|
24
|
+
* @param key - Unique identifier for the value
|
|
25
|
+
* @returns The stored value or undefined if not found
|
|
26
|
+
*/
|
|
27
|
+
get(key: string) {
|
|
28
|
+
return this.values[key]
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @signe/di - A lightweight dependency injection system
|
|
3
|
+
* @module @signe/di
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* This package provides a complete dependency injection system with the following features:
|
|
7
|
+
* - Type-safe dependency injection
|
|
8
|
+
* - Provider system with multiple configuration options
|
|
9
|
+
* - Context-based injection
|
|
10
|
+
* - Override capabilities for testing
|
|
11
|
+
* - Support for nested providers
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { Context, provide, inject } from '@signe/di';
|
|
16
|
+
*
|
|
17
|
+
* const context = new Context();
|
|
18
|
+
* provide(context, 'config', { apiUrl: 'http://api.example.com' });
|
|
19
|
+
* const config = inject(context, 'config');
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export * from "./inject";
|
|
24
|
+
export * from "./types";
|
|
25
|
+
export * from "./merge-config";
|
|
26
|
+
export * from "./provider";
|
|
27
|
+
export * from "./context";
|
package/src/inject.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core functionality for dependency injection
|
|
3
|
+
* @module @signe/di/inject
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Provider, Providers } from "./types";
|
|
7
|
+
import { Context } from "./context";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Provides a value to the dependency injection context
|
|
11
|
+
* @template T - Type of the value being provided
|
|
12
|
+
* @param context - The injection context
|
|
13
|
+
* @param name - Identifier for the provided value
|
|
14
|
+
* @param value - The value to provide
|
|
15
|
+
* @returns The provided value
|
|
16
|
+
*/
|
|
17
|
+
export function provide<T>(context: Context, name: string, value: T): T {
|
|
18
|
+
context.set("inject:" + name, value);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a service has been injected into the context
|
|
24
|
+
* @param context - The injection context
|
|
25
|
+
* @param name - Name of the service to check
|
|
26
|
+
* @returns True if the service has been injected, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
export function isInjected(context: Context, name: string): boolean {
|
|
29
|
+
return context.get('injected:' + name) === true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves a service from the dependency injection context
|
|
34
|
+
* @template T - Type of the service to inject
|
|
35
|
+
* @param context - The injection context
|
|
36
|
+
* @param service - Class constructor or string identifier of the service
|
|
37
|
+
* @param args - Optional arguments for service instantiation
|
|
38
|
+
* @returns The injected service instance
|
|
39
|
+
* @throws {Error} If the requested service is not found in the context
|
|
40
|
+
*/
|
|
41
|
+
export function inject<T>(
|
|
42
|
+
context: Context,
|
|
43
|
+
service: (new (...args: any[]) => T) | string,
|
|
44
|
+
args: any[] = []
|
|
45
|
+
): T {
|
|
46
|
+
const isClass = typeof service === "function";
|
|
47
|
+
const name = isClass ? service.name : service;
|
|
48
|
+
const value = context.get("inject:" + name);
|
|
49
|
+
if (value) {
|
|
50
|
+
context.set('injected:' + name, true)
|
|
51
|
+
return value as T;
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`Injection provider ${name} not found`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Overrides or adds a provider in the providers array
|
|
58
|
+
* @param providers - Array of existing providers
|
|
59
|
+
* @param newProvider - Provider to add or replace with
|
|
60
|
+
* @param options - Configuration options
|
|
61
|
+
* @param options.upsert - If true, adds the provider when not found
|
|
62
|
+
* @param options.key - Custom key to identify the provider
|
|
63
|
+
* @returns Updated array of providers
|
|
64
|
+
*/
|
|
65
|
+
export function override(
|
|
66
|
+
providers: Providers,
|
|
67
|
+
newProvider: Provider,
|
|
68
|
+
options?: {
|
|
69
|
+
upsert?: boolean,
|
|
70
|
+
key?: string
|
|
71
|
+
}
|
|
72
|
+
) {
|
|
73
|
+
let { upsert = false, key } = options ?? {};
|
|
74
|
+
if (!key) {
|
|
75
|
+
key = (
|
|
76
|
+
typeof newProvider === "function" ? newProvider.name : newProvider.provide
|
|
77
|
+
) as string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Flatten the providers array
|
|
81
|
+
const flatProviders = providers.flat();
|
|
82
|
+
|
|
83
|
+
// Check if provider exists
|
|
84
|
+
const exists = flatProviders.some(provider => {
|
|
85
|
+
if (typeof provider === "function") {
|
|
86
|
+
return provider.name === key;
|
|
87
|
+
} else if (typeof provider === "object") {
|
|
88
|
+
return (provider as any).provide === key;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Map and replace if found
|
|
94
|
+
const mappedProviders = flatProviders.map((provider) => {
|
|
95
|
+
if (typeof provider === "function" && provider.name === key) {
|
|
96
|
+
return newProvider;
|
|
97
|
+
} else if (typeof provider === "object" && (provider as any).provide === key) {
|
|
98
|
+
return newProvider;
|
|
99
|
+
}
|
|
100
|
+
return provider;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// If upsert is true and provider wasn't found, add it to the end
|
|
104
|
+
if (upsert && !exists) {
|
|
105
|
+
mappedProviders.push(newProvider);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return mappedProviders;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Finds all providers matching the given name or pattern
|
|
113
|
+
* @template T - Type of provider to return
|
|
114
|
+
* @param providers - Array of providers to search
|
|
115
|
+
* @param name - String or RegExp to match against provider names
|
|
116
|
+
* @returns Array of matching providers
|
|
117
|
+
*/
|
|
118
|
+
export function findProviders<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T[] {
|
|
119
|
+
const results: any[] = [];
|
|
120
|
+
|
|
121
|
+
for (const provider of providers) {
|
|
122
|
+
if (Array.isArray(provider)) {
|
|
123
|
+
// Recursively search in nested arrays and concat results
|
|
124
|
+
results.push(...findProviders(provider, name));
|
|
125
|
+
} else if (findProvider(provider as any, name)) {
|
|
126
|
+
// Add matching provider to results
|
|
127
|
+
results.push(provider as T);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return results;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Finds the first provider matching the given name or pattern
|
|
136
|
+
* @template T - Type of provider to return
|
|
137
|
+
* @param providers - Array of providers to search
|
|
138
|
+
* @param name - String or RegExp to match against provider names
|
|
139
|
+
* @returns Matching provider or null if not found
|
|
140
|
+
*/
|
|
141
|
+
export function findProvider<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T | null {
|
|
142
|
+
// Handle single provider case
|
|
143
|
+
if (!Array.isArray(providers)) {
|
|
144
|
+
if (typeof providers === "object" && 'provide' in providers) {
|
|
145
|
+
const provider = providers as any
|
|
146
|
+
const providerName = typeof provider.provide === "function"
|
|
147
|
+
? provider.provide.name
|
|
148
|
+
: provider.provide;
|
|
149
|
+
|
|
150
|
+
if (name instanceof RegExp) {
|
|
151
|
+
if (name.test(providerName)) return providers as T;
|
|
152
|
+
} else {
|
|
153
|
+
if (providerName === name) return providers as T;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Original array handling logic
|
|
160
|
+
for (const provider of providers) {
|
|
161
|
+
// If provider is an array, recursively search in it
|
|
162
|
+
if (Array.isArray(provider)) {
|
|
163
|
+
const found = findProvider(provider, name);
|
|
164
|
+
if (found) return found as T;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check object provider
|
|
169
|
+
if (typeof provider === "object" && 'provide' in provider) {
|
|
170
|
+
const providerName = typeof provider.provide === "function"
|
|
171
|
+
? provider.provide.name
|
|
172
|
+
: provider.provide;
|
|
173
|
+
|
|
174
|
+
// Handle RegExp matching
|
|
175
|
+
if (name instanceof RegExp) {
|
|
176
|
+
if (name.test(providerName)) return provider as T;
|
|
177
|
+
} else {
|
|
178
|
+
// Handle exact string matching
|
|
179
|
+
if (providerName === name) return provider as T;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration merging functionality for dependency injection
|
|
3
|
+
* @module @signe/di/merge-config
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { findProvider, override } from "./inject";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Application configuration interface
|
|
10
|
+
*/
|
|
11
|
+
export interface AppConfig {
|
|
12
|
+
/** Array of dependency providers */
|
|
13
|
+
providers: any[];
|
|
14
|
+
/** Optional static files configuration */
|
|
15
|
+
staticFiles?: {
|
|
16
|
+
/** Path to static files */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Static file serving configuration */
|
|
19
|
+
serve: any;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Merges two application configurations
|
|
25
|
+
* @param baseConfig - Base configuration to merge into
|
|
26
|
+
* @param config - Configuration to merge with base
|
|
27
|
+
* @returns Merged configuration with providers properly combined
|
|
28
|
+
*/
|
|
29
|
+
export function mergeConfig(baseConfig: AppConfig, config: AppConfig): AppConfig {
|
|
30
|
+
const mergedConfig: AppConfig = {
|
|
31
|
+
...baseConfig,
|
|
32
|
+
...config
|
|
33
|
+
}
|
|
34
|
+
for (let provider of config.providers) {
|
|
35
|
+
const isFound = findProvider(baseConfig.providers, provider.provide)
|
|
36
|
+
if (!isFound) {
|
|
37
|
+
mergedConfig.providers = override(baseConfig.providers, provider, { upsert: true })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return mergedConfig;
|
|
41
|
+
}
|
package/src/provider.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider system implementation for dependency injection
|
|
3
|
+
* @module @signe/di/provider
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ClassProvider, Provider, Providers, ProviderToken } from "./types";
|
|
7
|
+
import { inject, provide } from "./inject";
|
|
8
|
+
import { Context } from "./context";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if a provider is a ClassProvider
|
|
12
|
+
* @param provider - Provider to check
|
|
13
|
+
* @returns True if the provider is a ClassProvider
|
|
14
|
+
*/
|
|
15
|
+
function isClassProvider(provider: Provider): provider is ClassProvider {
|
|
16
|
+
if (typeof provider === 'string') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return 'useClass' in provider && typeof provider.useClass === 'function';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Processes and instantiates all providers in the given context
|
|
24
|
+
* @param context - The injection context
|
|
25
|
+
* @param providers - Array of providers to process
|
|
26
|
+
* @returns Promise that resolves when all providers are processed
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const context = new Context();
|
|
31
|
+
* const providers = [
|
|
32
|
+
* UserService,
|
|
33
|
+
* { provide: 'CONFIG', useValue: { apiUrl: 'http://api.example.com' } },
|
|
34
|
+
* { provide: AuthService, useFactory: (ctx) => new AuthService(ctx) }
|
|
35
|
+
* ];
|
|
36
|
+
*
|
|
37
|
+
* await injector(context, providers);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function injector(context: Context, providers: Providers) {
|
|
41
|
+
providers = providers.flat();
|
|
42
|
+
for (const provider of providers) {
|
|
43
|
+
let token: ProviderToken;
|
|
44
|
+
let instance: any;
|
|
45
|
+
|
|
46
|
+
if (typeof provider === 'function') {
|
|
47
|
+
// If provider is a class, treat it as a ClassProvider
|
|
48
|
+
token = provider;
|
|
49
|
+
instance = new provider(context);
|
|
50
|
+
} else {
|
|
51
|
+
token = (provider as any).provide;
|
|
52
|
+
const provideUserClass = (provider as any).useClass;
|
|
53
|
+
const isClass = typeof provideUserClass === 'function';
|
|
54
|
+
if (isClass) {
|
|
55
|
+
instance = new provideUserClass(context);
|
|
56
|
+
} else if ('useValue' in provider) {
|
|
57
|
+
instance = provider.useValue;
|
|
58
|
+
} else if ('useFactory' in provider) {
|
|
59
|
+
instance = provider.useFactory?.(context);
|
|
60
|
+
if (instance instanceof Promise) {
|
|
61
|
+
instance = await instance;
|
|
62
|
+
}
|
|
63
|
+
} else if ('useExisting' in provider) {
|
|
64
|
+
instance = inject(context, provider.useExisting);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const name = typeof token === 'function' ? token.name : token;
|
|
69
|
+
provide(context, name, instance);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the dependency injection system
|
|
3
|
+
* @module @signe/di/types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Context object used for dependency injection
|
|
8
|
+
*/
|
|
9
|
+
export type ProviderContext = any;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Token used to identify a provider. Can be either a class constructor or a string
|
|
13
|
+
*/
|
|
14
|
+
export type ProviderToken = (new (...args: any[]) => any) | string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Factory function type that creates instances using the provided context
|
|
18
|
+
* @param context - The injection context
|
|
19
|
+
* @returns The created instance
|
|
20
|
+
*/
|
|
21
|
+
export type FactoryFn = (context: ProviderContext) => any;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Provider configuration for value-based injection
|
|
25
|
+
*/
|
|
26
|
+
export interface ValueProvider {
|
|
27
|
+
/** Token to identify the provider */
|
|
28
|
+
provide: ProviderToken;
|
|
29
|
+
/** Value to be injected */
|
|
30
|
+
useValue: any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Provider configuration for class-based injection
|
|
35
|
+
*/
|
|
36
|
+
export interface ClassProvider {
|
|
37
|
+
/** Token to identify the provider */
|
|
38
|
+
provide: ProviderToken;
|
|
39
|
+
/** Class to be instantiated */
|
|
40
|
+
useClass: new (context: ProviderContext) => any;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Provider configuration for factory-based injection
|
|
45
|
+
*/
|
|
46
|
+
export interface FactoryProvider {
|
|
47
|
+
/** Token to identify the provider */
|
|
48
|
+
provide: ProviderToken;
|
|
49
|
+
/** Optional metadata for the provider */
|
|
50
|
+
meta?: {
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
};
|
|
53
|
+
/** Factory function to create the instance */
|
|
54
|
+
useFactory: FactoryFn;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Provider configuration for alias-based injection
|
|
59
|
+
*/
|
|
60
|
+
export interface ExistingProvider {
|
|
61
|
+
/** Token to identify the provider */
|
|
62
|
+
provide: ProviderToken;
|
|
63
|
+
/** Token of the existing provider to use */
|
|
64
|
+
useExisting: ProviderToken;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Union type for all possible provider configurations
|
|
69
|
+
*/
|
|
70
|
+
export type Provider =
|
|
71
|
+
| (new (...args: any[]) => any) // Allow direct class usage
|
|
72
|
+
| (ValueProvider & { useClass?: never, useFactory?: never, useExisting?: never })
|
|
73
|
+
| (ClassProvider & { useValue?: never, useFactory?: never, useExisting?: never })
|
|
74
|
+
| (FactoryProvider & { useValue?: never, useClass?: never, useExisting?: never })
|
|
75
|
+
| (ExistingProvider & { useValue?: never, useClass?: never, useFactory?: never });
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Array of providers that can be nested one level deep
|
|
79
|
+
*/
|
|
80
|
+
export type Providers = (Provider | Providers)[];
|