@signe/di 2.4.6 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +97 -45
- package/dist/index.js +155 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +114 -7
- package/src/inject.ts +197 -28
- package/src/provider.ts +33 -10
- package/src/types.ts +51 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context class for managing dependency injection state
|
|
3
|
+
* @module @signe/di/context
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Stores and manages the state of injected dependencies
|
|
7
|
+
* @template TState - Type for the state
|
|
8
|
+
* @template TActions - Type for the actions
|
|
9
|
+
* @template TValues - Type for additional values
|
|
10
|
+
*/
|
|
11
|
+
declare class Context<TState = any, TActions = any, TValues extends Record<string, any> = Record<string, any>> {
|
|
12
|
+
/** Internal storage for injected values */
|
|
13
|
+
private values;
|
|
14
|
+
/**
|
|
15
|
+
* Sets a value in the context
|
|
16
|
+
* @param key - Unique identifier for the value
|
|
17
|
+
* @param value - Value to store
|
|
18
|
+
*/
|
|
19
|
+
set<K extends keyof TValues>(key: K, value: TValues[K]): void;
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves a value from the context
|
|
22
|
+
* @param key - Unique identifier for the value
|
|
23
|
+
* @returns The stored value or undefined if not found
|
|
24
|
+
*/
|
|
25
|
+
get<K extends keyof TValues>(key: K): TValues[K];
|
|
26
|
+
}
|
|
27
|
+
|
|
1
28
|
/**
|
|
2
29
|
* Type definitions for the dependency injection system
|
|
3
30
|
* @module @signe/di/types
|
|
@@ -22,7 +49,51 @@ type FactoryFn = (context: ProviderContext) => any;
|
|
|
22
49
|
interface ProviderMeta {
|
|
23
50
|
[key: string]: any;
|
|
24
51
|
}
|
|
25
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Common options available to all providers
|
|
54
|
+
*/
|
|
55
|
+
interface ProviderOptions {
|
|
56
|
+
/**
|
|
57
|
+
* When true, allows multiple instances to be registered for the same token
|
|
58
|
+
*/
|
|
59
|
+
multi?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Optional name used to register and resolve a specific instance
|
|
62
|
+
*/
|
|
63
|
+
name?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Options that can be passed when calling {@link provide}
|
|
67
|
+
*/
|
|
68
|
+
interface ProvideOptions extends ProviderOptions {
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Options that can be passed when calling {@link inject}
|
|
72
|
+
*/
|
|
73
|
+
interface InjectionOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Optional name used to resolve a specific instance
|
|
76
|
+
*/
|
|
77
|
+
name?: string;
|
|
78
|
+
/**
|
|
79
|
+
* When true, allows retrieving all instances registered with {@link ProviderOptions.multi}
|
|
80
|
+
*/
|
|
81
|
+
multi?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* When true, `inject` will return `undefined` instead of throwing if the instance is missing
|
|
84
|
+
*/
|
|
85
|
+
optional?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Options used when checking if an instance exists in the context
|
|
89
|
+
*/
|
|
90
|
+
interface InstanceLookupOptions {
|
|
91
|
+
/**
|
|
92
|
+
* Optional name of the instance to check
|
|
93
|
+
*/
|
|
94
|
+
name?: string;
|
|
95
|
+
}
|
|
96
|
+
interface ValueProvider extends ProviderOptions {
|
|
26
97
|
/** Token to identify the provider */
|
|
27
98
|
provide: ProviderToken;
|
|
28
99
|
/** Value to be injected */
|
|
@@ -35,7 +106,7 @@ interface ValueProvider {
|
|
|
35
106
|
/**
|
|
36
107
|
* Provider configuration for class-based injection
|
|
37
108
|
*/
|
|
38
|
-
interface ClassProvider {
|
|
109
|
+
interface ClassProvider extends ProviderOptions {
|
|
39
110
|
/** Token to identify the provider */
|
|
40
111
|
provide: ProviderToken;
|
|
41
112
|
/** Class to be instantiated */
|
|
@@ -48,7 +119,7 @@ interface ClassProvider {
|
|
|
48
119
|
/**
|
|
49
120
|
* Provider configuration for factory-based injection
|
|
50
121
|
*/
|
|
51
|
-
interface FactoryProvider {
|
|
122
|
+
interface FactoryProvider extends ProviderOptions {
|
|
52
123
|
/** Token to identify the provider */
|
|
53
124
|
provide: ProviderToken;
|
|
54
125
|
/** Tokens that must be injected before this provider */
|
|
@@ -61,7 +132,7 @@ interface FactoryProvider {
|
|
|
61
132
|
/**
|
|
62
133
|
* Provider configuration for alias-based injection
|
|
63
134
|
*/
|
|
64
|
-
interface ExistingProvider {
|
|
135
|
+
interface ExistingProvider extends ProviderOptions {
|
|
65
136
|
/** Token to identify the provider */
|
|
66
137
|
provide: ProviderToken;
|
|
67
138
|
/** Token of the existing provider to use */
|
|
@@ -96,33 +167,6 @@ type Provider = (new (...args: any[]) => any) | (ValueProvider & {
|
|
|
96
167
|
*/
|
|
97
168
|
type Providers = (Provider | Providers)[];
|
|
98
169
|
|
|
99
|
-
/**
|
|
100
|
-
* Context class for managing dependency injection state
|
|
101
|
-
* @module @signe/di/context
|
|
102
|
-
*/
|
|
103
|
-
/**
|
|
104
|
-
* Stores and manages the state of injected dependencies
|
|
105
|
-
* @template TState - Type for the state
|
|
106
|
-
* @template TActions - Type for the actions
|
|
107
|
-
* @template TValues - Type for additional values
|
|
108
|
-
*/
|
|
109
|
-
declare class Context<TState = any, TActions = any, TValues extends Record<string, any> = Record<string, any>> {
|
|
110
|
-
/** Internal storage for injected values */
|
|
111
|
-
private values;
|
|
112
|
-
/**
|
|
113
|
-
* Sets a value in the context
|
|
114
|
-
* @param key - Unique identifier for the value
|
|
115
|
-
* @param value - Value to store
|
|
116
|
-
*/
|
|
117
|
-
set<K extends keyof TValues>(key: K, value: TValues[K]): void;
|
|
118
|
-
/**
|
|
119
|
-
* Retrieves a value from the context
|
|
120
|
-
* @param key - Unique identifier for the value
|
|
121
|
-
* @returns The stored value or undefined if not found
|
|
122
|
-
*/
|
|
123
|
-
get<K extends keyof TValues>(key: K): TValues[K];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
170
|
/**
|
|
127
171
|
* Core functionality for dependency injection
|
|
128
172
|
* @module @signe/di/inject
|
|
@@ -132,35 +176,43 @@ declare class Context<TState = any, TActions = any, TValues extends Record<strin
|
|
|
132
176
|
* Provides a value to the dependency injection context
|
|
133
177
|
* @template T - Type of the value being provided
|
|
134
178
|
* @param context - The injection context
|
|
135
|
-
* @param
|
|
179
|
+
* @param token - Identifier for the provided value
|
|
136
180
|
* @param value - The value to provide
|
|
181
|
+
* @param options - Configuration options for the provided value
|
|
137
182
|
* @returns The provided value
|
|
138
183
|
*/
|
|
139
|
-
declare function provide<T>(context: Context,
|
|
184
|
+
declare function provide<T>(context: Context, token: ProviderToken | string, value: T, options?: ProvideOptions): T;
|
|
140
185
|
/**
|
|
141
186
|
* Checks if a service has been injected into the context
|
|
142
187
|
* @param context - The injection context
|
|
143
|
-
* @param
|
|
188
|
+
* @param token - Token of the service to check
|
|
189
|
+
* @param options - Optional lookup configuration
|
|
144
190
|
* @returns True if the service has been injected, false otherwise
|
|
145
191
|
*/
|
|
146
|
-
declare function isInjected(context: Context,
|
|
192
|
+
declare function isInjected(context: Context, token: ProviderToken | string, options?: InstanceLookupOptions): boolean;
|
|
147
193
|
/**
|
|
148
194
|
* Checks if a service has been provided in the context
|
|
149
195
|
* @param context - The injection context
|
|
150
|
-
* @param
|
|
196
|
+
* @param token - Token of the service to check
|
|
197
|
+
* @param options - Optional lookup configuration
|
|
151
198
|
* @returns True if the service has been provided, false otherwise
|
|
152
199
|
*/
|
|
153
|
-
declare function isProvided(context: Context,
|
|
200
|
+
declare function isProvided(context: Context, token: ProviderToken | string, options?: InstanceLookupOptions): boolean;
|
|
154
201
|
/**
|
|
155
|
-
*
|
|
156
|
-
* @template T - Type of the service to inject
|
|
202
|
+
* Checks if an instance exists in the context
|
|
157
203
|
* @param context - The injection context
|
|
158
|
-
* @param
|
|
159
|
-
* @param
|
|
160
|
-
* @returns
|
|
161
|
-
* @throws {Error} If the requested service is not found in the context
|
|
204
|
+
* @param token - Token of the service to check
|
|
205
|
+
* @param options - Optional lookup configuration
|
|
206
|
+
* @returns True if the instance exists, false otherwise
|
|
162
207
|
*/
|
|
163
|
-
declare function
|
|
208
|
+
declare function hasInstance(context: Context, token: ProviderToken | string, options?: InstanceLookupOptions): boolean;
|
|
209
|
+
declare function inject<T>(context: Context, token: ProviderToken | string, options: InjectionOptions & {
|
|
210
|
+
multi: true;
|
|
211
|
+
}): T[];
|
|
212
|
+
declare function inject<T>(context: Context, token: ProviderToken | string, options: InjectionOptions & {
|
|
213
|
+
optional: true;
|
|
214
|
+
}): T | undefined;
|
|
215
|
+
declare function inject<T>(context: Context, token: ProviderToken | string, options?: InjectionOptions): T;
|
|
164
216
|
/**
|
|
165
217
|
* Overrides or adds a provider in the providers array
|
|
166
218
|
* @param providers - Array of existing providers
|
|
@@ -242,4 +294,4 @@ declare function mergeConfig(baseConfig: AppConfig, config: AppConfig): AppConfi
|
|
|
242
294
|
*/
|
|
243
295
|
declare function injector(context: Context, providers: Providers): Promise<void>;
|
|
244
296
|
|
|
245
|
-
export { type AppConfig, type ClassProvider, Context, type ExistingProvider, type FactoryFn, type FactoryProvider, type Provider, type ProviderContext, type ProviderMeta, type ProviderToken, type Providers, type ValueProvider, findProvider, findProviders, inject, injector, isInjected, isProvided, mergeConfig, override, provide };
|
|
297
|
+
export { type AppConfig, type ClassProvider, Context, type ExistingProvider, type FactoryFn, type FactoryProvider, type InjectionOptions, type InstanceLookupOptions, type ProvideOptions, type Provider, type ProviderContext, type ProviderMeta, type ProviderOptions, type ProviderToken, type Providers, type ValueProvider, findProvider, findProviders, hasInstance, inject, injector, isInjected, isProvided, mergeConfig, override, provide };
|
package/dist/index.js
CHANGED
|
@@ -2,29 +2,136 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/inject.ts
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
var DEFAULT_INSTANCE_KEY = "__default__";
|
|
6
|
+
function toTokenName(token) {
|
|
7
|
+
return typeof token === "function" ? token.name : token;
|
|
8
|
+
}
|
|
9
|
+
__name(toTokenName, "toTokenName");
|
|
10
|
+
function toInstanceKey(name) {
|
|
11
|
+
return name ?? DEFAULT_INSTANCE_KEY;
|
|
12
|
+
}
|
|
13
|
+
__name(toInstanceKey, "toInstanceKey");
|
|
14
|
+
function getRecord(context, token) {
|
|
15
|
+
return context.get("inject:" + toTokenName(token));
|
|
16
|
+
}
|
|
17
|
+
__name(getRecord, "getRecord");
|
|
18
|
+
function ensureRecord(context, token) {
|
|
19
|
+
const key = "inject:" + toTokenName(token);
|
|
20
|
+
let record = context.get(key);
|
|
21
|
+
if (!record) {
|
|
22
|
+
record = {
|
|
23
|
+
multi: false,
|
|
24
|
+
values: /* @__PURE__ */ new Map(),
|
|
25
|
+
injected: /* @__PURE__ */ new Set()
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
context.set(key, record);
|
|
29
|
+
return record;
|
|
30
|
+
}
|
|
31
|
+
__name(ensureRecord, "ensureRecord");
|
|
32
|
+
function provide(context, token, value, options = {}) {
|
|
33
|
+
const record = ensureRecord(context, token);
|
|
34
|
+
const instanceKey = toInstanceKey(options.name);
|
|
35
|
+
if (options.multi) {
|
|
36
|
+
record.multi = true;
|
|
37
|
+
}
|
|
38
|
+
if (!record.multi && instanceKey !== DEFAULT_INSTANCE_KEY) {
|
|
39
|
+
record.multi = true;
|
|
40
|
+
}
|
|
41
|
+
record.values.set(instanceKey, value);
|
|
7
42
|
return value;
|
|
8
43
|
}
|
|
9
44
|
__name(provide, "provide");
|
|
10
|
-
function isInjected(context,
|
|
11
|
-
|
|
45
|
+
function isInjected(context, token, options = {}) {
|
|
46
|
+
const record = getRecord(context, token);
|
|
47
|
+
if (!record) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (options.name) {
|
|
51
|
+
return record.injected.has(toInstanceKey(options.name));
|
|
52
|
+
}
|
|
53
|
+
if (record.multi) {
|
|
54
|
+
return record.injected.size > 0;
|
|
55
|
+
}
|
|
56
|
+
return record.injected.has(DEFAULT_INSTANCE_KEY);
|
|
12
57
|
}
|
|
13
58
|
__name(isInjected, "isInjected");
|
|
14
|
-
function isProvided(context,
|
|
15
|
-
|
|
59
|
+
function isProvided(context, token, options = {}) {
|
|
60
|
+
const record = getRecord(context, token);
|
|
61
|
+
if (!record) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (options.name) {
|
|
65
|
+
return record.values.has(toInstanceKey(options.name));
|
|
66
|
+
}
|
|
67
|
+
if (record.multi) {
|
|
68
|
+
return record.values.size > 0;
|
|
69
|
+
}
|
|
70
|
+
return record.values.has(DEFAULT_INSTANCE_KEY);
|
|
16
71
|
}
|
|
17
72
|
__name(isProvided, "isProvided");
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
73
|
+
function hasInstance(context, token, options = {}) {
|
|
74
|
+
return isProvided(context, token, options);
|
|
75
|
+
}
|
|
76
|
+
__name(hasInstance, "hasInstance");
|
|
77
|
+
function handleMissingInjection(token, options) {
|
|
78
|
+
const name = toTokenName(token);
|
|
79
|
+
if (options.name) {
|
|
80
|
+
throw new Error(`Injection provider ${name} with name ${options.name} not found`);
|
|
25
81
|
}
|
|
26
82
|
throw new Error(`Injection provider ${name} not found`);
|
|
27
83
|
}
|
|
84
|
+
__name(handleMissingInjection, "handleMissingInjection");
|
|
85
|
+
function markInjected(record, key) {
|
|
86
|
+
record.injected.add(key);
|
|
87
|
+
}
|
|
88
|
+
__name(markInjected, "markInjected");
|
|
89
|
+
function markAllInjected(record) {
|
|
90
|
+
for (const key of record.values.keys()) {
|
|
91
|
+
record.injected.add(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
__name(markAllInjected, "markAllInjected");
|
|
95
|
+
function inject(context, token, options = {}) {
|
|
96
|
+
const record = getRecord(context, token);
|
|
97
|
+
if (!record) {
|
|
98
|
+
if (options.optional) {
|
|
99
|
+
return options.multi ? [] : void 0;
|
|
100
|
+
}
|
|
101
|
+
return handleMissingInjection(token, options);
|
|
102
|
+
}
|
|
103
|
+
if (options.name) {
|
|
104
|
+
const instanceKey = toInstanceKey(options.name);
|
|
105
|
+
if (!record.values.has(instanceKey)) {
|
|
106
|
+
if (options.optional) {
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
return handleMissingInjection(token, options);
|
|
110
|
+
}
|
|
111
|
+
const value2 = record.values.get(instanceKey);
|
|
112
|
+
markInjected(record, instanceKey);
|
|
113
|
+
return value2;
|
|
114
|
+
}
|
|
115
|
+
if (options.multi || record.multi) {
|
|
116
|
+
if (record.values.size === 0) {
|
|
117
|
+
if (options.optional) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
return handleMissingInjection(token, options);
|
|
121
|
+
}
|
|
122
|
+
markAllInjected(record);
|
|
123
|
+
return Array.from(record.values.values());
|
|
124
|
+
}
|
|
125
|
+
const value = record.values.get(DEFAULT_INSTANCE_KEY);
|
|
126
|
+
if (value === void 0) {
|
|
127
|
+
if (options.optional) {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
return handleMissingInjection(token, options);
|
|
131
|
+
}
|
|
132
|
+
markInjected(record, DEFAULT_INSTANCE_KEY);
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
28
135
|
__name(inject, "inject");
|
|
29
136
|
function override(providers, newProvider, options) {
|
|
30
137
|
let { upsert = false, key } = options ?? {};
|
|
@@ -131,6 +238,20 @@ function mergeConfig(baseConfig, config) {
|
|
|
131
238
|
__name(mergeConfig, "mergeConfig");
|
|
132
239
|
|
|
133
240
|
// src/provider.ts
|
|
241
|
+
function extractProvideOptions(source) {
|
|
242
|
+
if (!source) {
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
const { multi, name } = source;
|
|
246
|
+
if (multi === void 0 && name === void 0) {
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
multi,
|
|
251
|
+
name
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
__name(extractProvideOptions, "extractProvideOptions");
|
|
134
255
|
function getDeps(provider) {
|
|
135
256
|
if (typeof provider === "function") {
|
|
136
257
|
return provider.deps ?? [];
|
|
@@ -143,7 +264,14 @@ function sortProviders(providers) {
|
|
|
143
264
|
const map = /* @__PURE__ */ new Map();
|
|
144
265
|
for (const p of providers) {
|
|
145
266
|
const token = tokenName(typeof p === "function" ? p : p.provide);
|
|
146
|
-
map.
|
|
267
|
+
const list = map.get(token);
|
|
268
|
+
if (list) {
|
|
269
|
+
list.push(p);
|
|
270
|
+
} else {
|
|
271
|
+
map.set(token, [
|
|
272
|
+
p
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
147
275
|
}
|
|
148
276
|
const result = [];
|
|
149
277
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -155,13 +283,15 @@ function sortProviders(providers) {
|
|
|
155
283
|
throw new Error(`Circular dependency detected for provider ${name}`);
|
|
156
284
|
}
|
|
157
285
|
stack.add(name);
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
for (const
|
|
161
|
-
|
|
286
|
+
const providersForToken = map.get(name);
|
|
287
|
+
if (providersForToken) {
|
|
288
|
+
for (const provider of providersForToken) {
|
|
289
|
+
for (const dep of getDeps(provider)) {
|
|
290
|
+
visit(dep);
|
|
291
|
+
}
|
|
292
|
+
result.push(provider);
|
|
162
293
|
}
|
|
163
294
|
visited.add(name);
|
|
164
|
-
result.push(provider);
|
|
165
295
|
}
|
|
166
296
|
stack.delete(name);
|
|
167
297
|
}, "visit");
|
|
@@ -178,11 +308,15 @@ async function injector(context, providers) {
|
|
|
178
308
|
for (const provider of providers) {
|
|
179
309
|
let token;
|
|
180
310
|
let instance;
|
|
311
|
+
let options;
|
|
181
312
|
if (typeof provider === "function") {
|
|
182
313
|
token = provider;
|
|
183
314
|
instance = new provider(context);
|
|
315
|
+
const diOptions = extractProvideOptions(provider.diOptions ?? provider.di);
|
|
316
|
+
options = diOptions;
|
|
184
317
|
} else {
|
|
185
318
|
token = provider.provide;
|
|
319
|
+
options = extractProvideOptions(provider);
|
|
186
320
|
const provideUserClass = provider.useClass;
|
|
187
321
|
const isClass = typeof provideUserClass === "function";
|
|
188
322
|
if (isClass) {
|
|
@@ -198,8 +332,7 @@ async function injector(context, providers) {
|
|
|
198
332
|
instance = inject(context, provider.useExisting);
|
|
199
333
|
}
|
|
200
334
|
}
|
|
201
|
-
|
|
202
|
-
provide(context, name, instance);
|
|
335
|
+
provide(context, token, instance, options);
|
|
203
336
|
}
|
|
204
337
|
}
|
|
205
338
|
__name(injector, "injector");
|
|
@@ -232,6 +365,7 @@ export {
|
|
|
232
365
|
Context,
|
|
233
366
|
findProvider,
|
|
234
367
|
findProviders,
|
|
368
|
+
hasInstance,
|
|
235
369
|
inject,
|
|
236
370
|
injector,
|
|
237
371
|
isInjected,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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 * Checks if a service has been provided in 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 provided, false otherwise\n */\nexport function isProvided(context: Context, name: string): boolean {\n return context.get('inject:' + name) !== undefined;\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 * Process a provider or nested provider array and add it to the merged config\n * @param mergedConfig - Configuration being built\n * @param baseConfig - Original base configuration \n * @param provider - Provider or nested provider array to process\n */\nfunction processProvider(mergedConfig: AppConfig, baseConfig: AppConfig, provider: any) {\n // Handle nested arrays of providers\n if (Array.isArray(provider)) {\n for (const nestedProvider of provider) {\n processProvider(mergedConfig, baseConfig, nestedProvider);\n }\n return;\n }\n\n // Handle individual provider\n const existingProvider = findProvider(baseConfig.providers, provider.provide);\n if (existingProvider) {\n // Replace existing provider\n mergedConfig.providers = override(mergedConfig.providers, provider);\n } else {\n // Add new provider\n mergedConfig.providers.push(provider);\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 // Create a new config object with everything except providers\n const mergedConfig: AppConfig = {\n ...baseConfig,\n ...config,\n providers: [...baseConfig.providers] // Start with a copy of base providers\n }\n\n // Process each provider from the config to merge\n for (const provider of config.providers) {\n processProvider(mergedConfig, baseConfig, provider);\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 * Retrieves dependencies declared on a provider\n * @param provider - Provider to inspect\n * @returns Array of provider tokens this provider depends on\n */\nfunction getDeps(provider: Provider): ProviderToken[] {\n if (typeof provider === 'function') {\n return (provider as any).deps ?? [];\n }\n return (provider as any).deps ?? [];\n}\n\n/**\n * Sorts providers so that dependencies are instantiated first\n * @param providers - Array of providers to sort\n * @throws When a circular dependency is detected\n */\nfunction sortProviders(providers: Provider[]): Provider[] {\n const tokenName = (t: ProviderToken) => typeof t === 'function' ? t.name : t;\n const map = new Map<string, Provider>();\n for (const p of providers) {\n const token = tokenName(typeof p === 'function' ? p : (p as any).provide);\n map.set(token, p);\n }\n\n const result: Provider[] = [];\n const visited = new Set<string>();\n const stack = new Set<string>();\n\n const visit = (token: ProviderToken) => {\n const name = tokenName(token);\n if (visited.has(name)) return;\n if (stack.has(name)) {\n throw new Error(`Circular dependency detected for provider ${name}`);\n }\n stack.add(name);\n const provider = map.get(name);\n if (provider) {\n for (const dep of getDeps(provider)) {\n visit(dep);\n }\n visited.add(name);\n result.push(provider);\n }\n stack.delete(name);\n };\n\n for (const p of providers) {\n const token = typeof p === 'function' ? p : (p as any).provide;\n visit(token);\n }\n\n return result;\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 providers = sortProviders(providers as Provider[]);\n\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 * @template TState - Type for the state\n * @template TActions - Type for the actions\n * @template TValues - Type for additional values\n */\nexport class Context<\n TState = any,\n TActions = any,\n TValues extends Record<string, any> = Record<string, any>\n> {\n /** Internal storage for injected values */\n private values: TValues = {} as TValues\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<K extends keyof TValues>(key: K, value: TValues[K]) {\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<K extends keyof TValues>(key: K): TValues[K] {\n return this.values[key]\n }\n}"],"mappings":"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","names":["provide","context","name","value","set","isInjected","get","isProvided","undefined","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","processProvider","mergedConfig","baseConfig","provider","Array","isArray","nestedProvider","existingProvider","findProvider","providers","provide","override","push","mergeConfig","config","getDeps","provider","deps","sortProviders","providers","tokenName","t","name","map","Map","p","token","provide","set","result","visited","Set","stack","visit","has","Error","add","get","dep","push","delete","injector","context","flat","instance","provideUserClass","useClass","isClass","useValue","useFactory","Promise","inject","useExisting","Context","values","set","key","value","get"]}
|
|
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 { Context } from \"./context\";\nimport {\n InjectionOptions,\n InstanceLookupOptions,\n ProvideOptions,\n Provider,\n ProviderToken,\n Providers\n} from \"./types\";\n\nconst DEFAULT_INSTANCE_KEY = \"__default__\";\n\ninterface ProviderRecord {\n multi: boolean;\n values: Map<string, any>;\n injected: Set<string>;\n}\n\nfunction toTokenName(token: ProviderToken | string): string {\n return typeof token === \"function\" ? token.name : token;\n}\n\nfunction toInstanceKey(name?: string): string {\n return name ?? DEFAULT_INSTANCE_KEY;\n}\n\nfunction getRecord(context: Context, token: ProviderToken | string): ProviderRecord | undefined {\n return context.get(\"inject:\" + toTokenName(token));\n}\n\nfunction ensureRecord(context: Context, token: ProviderToken | string): ProviderRecord {\n const key = \"inject:\" + toTokenName(token);\n let record = context.get(key) as ProviderRecord | undefined;\n if (!record) {\n record = {\n multi: false,\n values: new Map<string, any>(),\n injected: new Set<string>()\n };\n }\n context.set(key, record);\n return record;\n}\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 token - Identifier for the provided value\n * @param value - The value to provide\n * @param options - Configuration options for the provided value\n * @returns The provided value\n */\nexport function provide<T>(\n context: Context,\n token: ProviderToken | string,\n value: T,\n options: ProvideOptions = {}\n): T {\n const record = ensureRecord(context, token);\n const instanceKey = toInstanceKey(options.name);\n\n if (options.multi) {\n record.multi = true;\n }\n\n if (!record.multi && instanceKey !== DEFAULT_INSTANCE_KEY) {\n // If we are switching from single to named instance without multi, enable multi mode implicitly\n record.multi = true;\n }\n\n record.values.set(instanceKey, 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 token - Token of the service to check\n * @param options - Optional lookup configuration\n * @returns True if the service has been injected, false otherwise\n */\nexport function isInjected(\n context: Context,\n token: ProviderToken | string,\n options: InstanceLookupOptions = {}\n): boolean {\n const record = getRecord(context, token);\n if (!record) {\n return false;\n }\n\n if (options.name) {\n return record.injected.has(toInstanceKey(options.name));\n }\n\n if (record.multi) {\n return record.injected.size > 0;\n }\n\n return record.injected.has(DEFAULT_INSTANCE_KEY);\n}\n\n/**\n * Checks if a service has been provided in the context\n * @param context - The injection context\n * @param token - Token of the service to check\n * @param options - Optional lookup configuration\n * @returns True if the service has been provided, false otherwise\n */\nexport function isProvided(\n context: Context,\n token: ProviderToken | string,\n options: InstanceLookupOptions = {}\n): boolean {\n const record = getRecord(context, token);\n if (!record) {\n return false;\n }\n\n if (options.name) {\n return record.values.has(toInstanceKey(options.name));\n }\n\n if (record.multi) {\n return record.values.size > 0;\n }\n\n return record.values.has(DEFAULT_INSTANCE_KEY);\n}\n\n/**\n * Checks if an instance exists in the context\n * @param context - The injection context\n * @param token - Token of the service to check\n * @param options - Optional lookup configuration\n * @returns True if the instance exists, false otherwise\n */\nexport function hasInstance(\n context: Context,\n token: ProviderToken | string,\n options: InstanceLookupOptions = {}\n): boolean {\n return isProvided(context, token, options);\n}\n\nfunction handleMissingInjection(\n token: ProviderToken | string,\n options: InjectionOptions\n): never {\n const name = toTokenName(token);\n if (options.name) {\n throw new Error(`Injection provider ${name} with name ${options.name} not found`);\n }\n throw new Error(`Injection provider ${name} not found`);\n}\n\nfunction markInjected(record: ProviderRecord, key: string) {\n record.injected.add(key);\n}\n\nfunction markAllInjected(record: ProviderRecord) {\n for (const key of record.values.keys()) {\n record.injected.add(key);\n }\n}\n\nexport function inject<T>(context: Context, token: ProviderToken | string, options: InjectionOptions & { multi: true }): T[];\nexport function inject<T>(context: Context, token: ProviderToken | string, options: InjectionOptions & { optional: true }): T | undefined;\nexport function inject<T>(context: Context, token: ProviderToken | string, options?: InjectionOptions): T;\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 token - Class constructor or string identifier of the service\n * @param options - Optional configuration for resolving the service\n * @returns The injected service instance or `undefined` when optional\n * @throws {Error} If the requested service is not found in the context\n */\nexport function inject<T>(\n context: Context,\n token: ProviderToken | string,\n options: InjectionOptions = {}\n): T | T[] | undefined {\n const record = getRecord(context, token);\n\n if (!record) {\n if (options.optional) {\n return options.multi ? [] : undefined;\n }\n return handleMissingInjection(token, options);\n }\n\n if (options.name) {\n const instanceKey = toInstanceKey(options.name);\n if (!record.values.has(instanceKey)) {\n if (options.optional) {\n return undefined;\n }\n return handleMissingInjection(token, options);\n }\n const value = record.values.get(instanceKey);\n markInjected(record, instanceKey);\n return value as T;\n }\n\n if (options.multi || record.multi) {\n if (record.values.size === 0) {\n if (options.optional) {\n return [];\n }\n return handleMissingInjection(token, options);\n }\n markAllInjected(record);\n return Array.from(record.values.values()) as T[];\n }\n\n const value = record.values.get(DEFAULT_INSTANCE_KEY);\n if (value === undefined) {\n if (options.optional) {\n return undefined;\n }\n return handleMissingInjection(token, options);\n }\n\n markInjected(record, DEFAULT_INSTANCE_KEY);\n return value as T;\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 * Process a provider or nested provider array and add it to the merged config\n * @param mergedConfig - Configuration being built\n * @param baseConfig - Original base configuration \n * @param provider - Provider or nested provider array to process\n */\nfunction processProvider(mergedConfig: AppConfig, baseConfig: AppConfig, provider: any) {\n // Handle nested arrays of providers\n if (Array.isArray(provider)) {\n for (const nestedProvider of provider) {\n processProvider(mergedConfig, baseConfig, nestedProvider);\n }\n return;\n }\n\n // Handle individual provider\n const existingProvider = findProvider(baseConfig.providers, provider.provide);\n if (existingProvider) {\n // Replace existing provider\n mergedConfig.providers = override(mergedConfig.providers, provider);\n } else {\n // Add new provider\n mergedConfig.providers.push(provider);\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 // Create a new config object with everything except providers\n const mergedConfig: AppConfig = {\n ...baseConfig,\n ...config,\n providers: [...baseConfig.providers] // Start with a copy of base providers\n }\n\n // Process each provider from the config to merge\n for (const provider of config.providers) {\n processProvider(mergedConfig, baseConfig, provider);\n }\n\n return mergedConfig;\n}\n","/**\n * Provider system implementation for dependency injection\n * @module @signe/di/provider\n */\n\nimport { ClassProvider, ProvideOptions, Provider, Providers, ProviderToken } from \"./types\";\nimport { inject, provide } from \"./inject\";\nimport { Context } from \"./context\";\n\nfunction extractProvideOptions(source: { multi?: boolean; name?: string } | undefined): ProvideOptions | undefined {\n if (!source) {\n return undefined;\n }\n\n const { multi, name } = source;\n if (multi === undefined && name === undefined) {\n return undefined;\n }\n\n return { multi, name };\n}\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 * Retrieves dependencies declared on a provider\n * @param provider - Provider to inspect\n * @returns Array of provider tokens this provider depends on\n */\nfunction getDeps(provider: Provider): ProviderToken[] {\n if (typeof provider === 'function') {\n return (provider as any).deps ?? [];\n }\n return (provider as any).deps ?? [];\n}\n\n/**\n * Sorts providers so that dependencies are instantiated first\n * @param providers - Array of providers to sort\n * @throws When a circular dependency is detected\n */\nfunction sortProviders(providers: Provider[]): Provider[] {\n const tokenName = (t: ProviderToken) => typeof t === 'function' ? t.name : t;\n const map = new Map<string, Provider[]>();\n for (const p of providers) {\n const token = tokenName(typeof p === 'function' ? p : (p as any).provide);\n const list = map.get(token);\n if (list) {\n list.push(p);\n } else {\n map.set(token, [p]);\n }\n }\n\n const result: Provider[] = [];\n const visited = new Set<string>();\n const stack = new Set<string>();\n\n const visit = (token: ProviderToken) => {\n const name = tokenName(token);\n if (visited.has(name)) return;\n if (stack.has(name)) {\n throw new Error(`Circular dependency detected for provider ${name}`);\n }\n stack.add(name);\n const providersForToken = map.get(name);\n if (providersForToken) {\n for (const provider of providersForToken) {\n for (const dep of getDeps(provider)) {\n visit(dep);\n }\n result.push(provider);\n }\n visited.add(name);\n }\n stack.delete(name);\n };\n\n for (const p of providers) {\n const token = typeof p === 'function' ? p : (p as any).provide;\n visit(token);\n }\n\n return result;\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 providers = sortProviders(providers as Provider[]);\n\n for (const provider of providers) {\n let token: ProviderToken;\n let instance: any;\n let options: ProvideOptions | undefined;\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 const diOptions = extractProvideOptions((provider as any).diOptions ?? (provider as any).di);\n options = diOptions;\n } else {\n token = (provider as any).provide;\n options = extractProvideOptions(provider as any);\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 provide(context, token, instance, options);\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 * @template TState - Type for the state\n * @template TActions - Type for the actions\n * @template TValues - Type for additional values\n */\nexport class Context<\n TState = any,\n TActions = any,\n TValues extends Record<string, any> = Record<string, any>\n> {\n /** Internal storage for injected values */\n private values: TValues = {} as TValues\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<K extends keyof TValues>(key: K, value: TValues[K]) {\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<K extends keyof TValues>(key: K): TValues[K] {\n return this.values[key]\n }\n}"],"mappings":"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","names":["DEFAULT_INSTANCE_KEY","toTokenName","token","name","toInstanceKey","getRecord","context","get","ensureRecord","key","record","multi","values","Map","injected","Set","set","provide","value","options","instanceKey","isInjected","has","size","isProvided","hasInstance","handleMissingInjection","Error","markInjected","add","markAllInjected","keys","inject","optional","undefined","Array","from","override","providers","newProvider","upsert","flatProviders","flat","exists","some","provider","mappedProviders","map","push","findProviders","results","isArray","findProvider","providerName","RegExp","test","found","processProvider","mergedConfig","baseConfig","provider","Array","isArray","nestedProvider","existingProvider","findProvider","providers","provide","override","push","mergeConfig","config","extractProvideOptions","source","undefined","multi","name","getDeps","provider","deps","sortProviders","providers","tokenName","t","name","map","Map","p","token","provide","list","get","push","set","result","visited","Set","stack","visit","has","Error","add","providersForToken","dep","delete","injector","context","flat","instance","options","diOptions","extractProvideOptions","di","provideUserClass","useClass","isClass","useValue","useFactory","Promise","inject","useExisting","Context","values","set","key","value","get"]}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -20,13 +20,15 @@ pnpm add @signe/di
|
|
|
20
20
|
- Context-based injection
|
|
21
21
|
- Override capabilities for testing and customization
|
|
22
22
|
- Support for nested providers
|
|
23
|
+
- Optional injection resolution helpers
|
|
24
|
+
- Support for multiple named instances per token
|
|
23
25
|
|
|
24
26
|
## Usage
|
|
25
27
|
|
|
26
28
|
### Basic Usage
|
|
27
29
|
|
|
28
30
|
```typescript
|
|
29
|
-
import { provide, inject, Context, Providers } from '@signe/di';
|
|
31
|
+
import { provide, inject, Context, Providers, injector } from '@signe/di';
|
|
30
32
|
|
|
31
33
|
const context = new Context();
|
|
32
34
|
|
|
@@ -57,8 +59,8 @@ const providers: Providers = [
|
|
|
57
59
|
}
|
|
58
60
|
];
|
|
59
61
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
+
// Register the services
|
|
63
|
+
await injector(context, providers);
|
|
62
64
|
|
|
63
65
|
// Inject and use the service
|
|
64
66
|
const userService = inject(context, UserService);
|
|
@@ -83,6 +85,36 @@ const updatedProviders = override(providers, {
|
|
|
83
85
|
}, { upsert: true });
|
|
84
86
|
```
|
|
85
87
|
|
|
88
|
+
### Optional injection
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { inject } from '@signe/di';
|
|
92
|
+
|
|
93
|
+
const maybeService = inject(context, 'UNKNOWN_SERVICE', { optional: true });
|
|
94
|
+
if (!maybeService) {
|
|
95
|
+
// Handle missing service without throwing an exception
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Multiple named instances
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { provide, inject } from '@signe/di';
|
|
103
|
+
|
|
104
|
+
provide(context, UserService, new UserService('primary'), {
|
|
105
|
+
multi: true,
|
|
106
|
+
name: 'primary'
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
provide(context, UserService, new UserService('secondary'), {
|
|
110
|
+
multi: true,
|
|
111
|
+
name: 'secondary'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const allInstances = inject<UserService>(context, UserService, { multi: true });
|
|
115
|
+
const secondary = inject<UserService>(context, UserService, { name: 'secondary' });
|
|
116
|
+
```
|
|
117
|
+
|
|
86
118
|
### Check Injection Status
|
|
87
119
|
|
|
88
120
|
```typescript
|
|
@@ -93,6 +125,68 @@ if (isInjected(context, UserService)) {
|
|
|
93
125
|
}
|
|
94
126
|
```
|
|
95
127
|
|
|
128
|
+
### Dependency Declaration
|
|
129
|
+
|
|
130
|
+
You can declare dependencies using the `deps` property. The injector will automatically sort providers to ensure dependencies are instantiated before the services that need them.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { provide, inject, Context, Providers, injector } from '@signe/di';
|
|
134
|
+
|
|
135
|
+
const context = new Context();
|
|
136
|
+
|
|
137
|
+
class DatabaseService {
|
|
138
|
+
connect() {
|
|
139
|
+
return 'Connected to database';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class UserRepository {
|
|
144
|
+
constructor(context: Context) {
|
|
145
|
+
this.db = inject(context, DatabaseService);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static deps = [DatabaseService];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
class UserService {
|
|
152
|
+
constructor(context: Context) {
|
|
153
|
+
this.repository = inject(context, UserRepository);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static deps = [UserRepository];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const providers: Providers = [
|
|
160
|
+
UserService,
|
|
161
|
+
UserRepository,
|
|
162
|
+
DatabaseService
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
// The injector will automatically sort: DatabaseService -> UserRepository -> UserService
|
|
166
|
+
await injector(context, providers);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
You can also declare dependencies on provider objects:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const providers: Providers = [
|
|
173
|
+
{
|
|
174
|
+
provide: 'API_CLIENT',
|
|
175
|
+
useFactory: (context) => {
|
|
176
|
+
const config = inject(context, 'CONFIG');
|
|
177
|
+
return new ApiClient(config);
|
|
178
|
+
},
|
|
179
|
+
deps: ['CONFIG']
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
provide: 'CONFIG',
|
|
183
|
+
useValue: { apiUrl: 'https://api.example.com' }
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Note:** The injector will detect and throw an error if circular dependencies are found.
|
|
189
|
+
|
|
96
190
|
### Find Providers
|
|
97
191
|
|
|
98
192
|
```typescript
|
|
@@ -107,14 +201,27 @@ const allServices = findProviders(providers, /Service$/);
|
|
|
107
201
|
|
|
108
202
|
## API Reference
|
|
109
203
|
|
|
110
|
-
### `provide(context,
|
|
204
|
+
### `provide(context, token, value, options?)`
|
|
111
205
|
Stores a value in the context for dependency injection.
|
|
112
206
|
|
|
113
|
-
|
|
207
|
+
- `options.multi` — When `true`, allows multiple instances for the same token
|
|
208
|
+
- `options.name` — Registers the instance under a specific name
|
|
209
|
+
|
|
210
|
+
### `inject(context, token, options?)`
|
|
114
211
|
Retrieves an injected value from the context.
|
|
115
212
|
|
|
116
|
-
|
|
117
|
-
|
|
213
|
+
- `options.optional` — Returns `undefined`/`[]` instead of throwing when missing
|
|
214
|
+
- `options.multi` — Returns all registered instances as an array
|
|
215
|
+
- `options.name` — Retrieves a specific named instance
|
|
216
|
+
|
|
217
|
+
### `isInjected(context, token, options?)`
|
|
218
|
+
Checks if a value has been injected. Supports named lookups via the `name` option.
|
|
219
|
+
|
|
220
|
+
### `isProvided(context, token, options?)`
|
|
221
|
+
Checks if a value has been registered in the context.
|
|
222
|
+
|
|
223
|
+
### `hasInstance(context, token, options?)`
|
|
224
|
+
Alias of `isProvided`, kept for readability when checking for instance existence.
|
|
118
225
|
|
|
119
226
|
### `override(providers, newProvider, options?)`
|
|
120
227
|
Overrides or adds new providers to the existing provider array.
|
package/src/inject.ts
CHANGED
|
@@ -3,64 +3,233 @@
|
|
|
3
3
|
* @module @signe/di/inject
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Provider, Providers } from "./types";
|
|
7
6
|
import { Context } from "./context";
|
|
7
|
+
import {
|
|
8
|
+
InjectionOptions,
|
|
9
|
+
InstanceLookupOptions,
|
|
10
|
+
ProvideOptions,
|
|
11
|
+
Provider,
|
|
12
|
+
ProviderToken,
|
|
13
|
+
Providers
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_INSTANCE_KEY = "__default__";
|
|
17
|
+
|
|
18
|
+
interface ProviderRecord {
|
|
19
|
+
multi: boolean;
|
|
20
|
+
values: Map<string, any>;
|
|
21
|
+
injected: Set<string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toTokenName(token: ProviderToken | string): string {
|
|
25
|
+
return typeof token === "function" ? token.name : token;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toInstanceKey(name?: string): string {
|
|
29
|
+
return name ?? DEFAULT_INSTANCE_KEY;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getRecord(context: Context, token: ProviderToken | string): ProviderRecord | undefined {
|
|
33
|
+
return context.get("inject:" + toTokenName(token));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ensureRecord(context: Context, token: ProviderToken | string): ProviderRecord {
|
|
37
|
+
const key = "inject:" + toTokenName(token);
|
|
38
|
+
let record = context.get(key) as ProviderRecord | undefined;
|
|
39
|
+
if (!record) {
|
|
40
|
+
record = {
|
|
41
|
+
multi: false,
|
|
42
|
+
values: new Map<string, any>(),
|
|
43
|
+
injected: new Set<string>()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
context.set(key, record);
|
|
47
|
+
return record;
|
|
48
|
+
}
|
|
8
49
|
|
|
9
50
|
/**
|
|
10
51
|
* Provides a value to the dependency injection context
|
|
11
52
|
* @template T - Type of the value being provided
|
|
12
53
|
* @param context - The injection context
|
|
13
|
-
* @param
|
|
54
|
+
* @param token - Identifier for the provided value
|
|
14
55
|
* @param value - The value to provide
|
|
56
|
+
* @param options - Configuration options for the provided value
|
|
15
57
|
* @returns The provided value
|
|
16
58
|
*/
|
|
17
|
-
export function provide<T>(
|
|
18
|
-
context
|
|
59
|
+
export function provide<T>(
|
|
60
|
+
context: Context,
|
|
61
|
+
token: ProviderToken | string,
|
|
62
|
+
value: T,
|
|
63
|
+
options: ProvideOptions = {}
|
|
64
|
+
): T {
|
|
65
|
+
const record = ensureRecord(context, token);
|
|
66
|
+
const instanceKey = toInstanceKey(options.name);
|
|
67
|
+
|
|
68
|
+
if (options.multi) {
|
|
69
|
+
record.multi = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!record.multi && instanceKey !== DEFAULT_INSTANCE_KEY) {
|
|
73
|
+
// If we are switching from single to named instance without multi, enable multi mode implicitly
|
|
74
|
+
record.multi = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
record.values.set(instanceKey, value);
|
|
19
78
|
return value;
|
|
20
79
|
}
|
|
21
80
|
|
|
22
81
|
/**
|
|
23
82
|
* Checks if a service has been injected into the context
|
|
24
83
|
* @param context - The injection context
|
|
25
|
-
* @param
|
|
84
|
+
* @param token - Token of the service to check
|
|
85
|
+
* @param options - Optional lookup configuration
|
|
26
86
|
* @returns True if the service has been injected, false otherwise
|
|
27
87
|
*/
|
|
28
|
-
export function isInjected(
|
|
29
|
-
|
|
88
|
+
export function isInjected(
|
|
89
|
+
context: Context,
|
|
90
|
+
token: ProviderToken | string,
|
|
91
|
+
options: InstanceLookupOptions = {}
|
|
92
|
+
): boolean {
|
|
93
|
+
const record = getRecord(context, token);
|
|
94
|
+
if (!record) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.name) {
|
|
99
|
+
return record.injected.has(toInstanceKey(options.name));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (record.multi) {
|
|
103
|
+
return record.injected.size > 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return record.injected.has(DEFAULT_INSTANCE_KEY);
|
|
30
107
|
}
|
|
31
108
|
|
|
32
109
|
/**
|
|
33
110
|
* Checks if a service has been provided in the context
|
|
34
111
|
* @param context - The injection context
|
|
35
|
-
* @param
|
|
112
|
+
* @param token - Token of the service to check
|
|
113
|
+
* @param options - Optional lookup configuration
|
|
36
114
|
* @returns True if the service has been provided, false otherwise
|
|
37
115
|
*/
|
|
38
|
-
export function isProvided(
|
|
39
|
-
|
|
116
|
+
export function isProvided(
|
|
117
|
+
context: Context,
|
|
118
|
+
token: ProviderToken | string,
|
|
119
|
+
options: InstanceLookupOptions = {}
|
|
120
|
+
): boolean {
|
|
121
|
+
const record = getRecord(context, token);
|
|
122
|
+
if (!record) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (options.name) {
|
|
127
|
+
return record.values.has(toInstanceKey(options.name));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (record.multi) {
|
|
131
|
+
return record.values.size > 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return record.values.has(DEFAULT_INSTANCE_KEY);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Checks if an instance exists in the context
|
|
139
|
+
* @param context - The injection context
|
|
140
|
+
* @param token - Token of the service to check
|
|
141
|
+
* @param options - Optional lookup configuration
|
|
142
|
+
* @returns True if the instance exists, false otherwise
|
|
143
|
+
*/
|
|
144
|
+
export function hasInstance(
|
|
145
|
+
context: Context,
|
|
146
|
+
token: ProviderToken | string,
|
|
147
|
+
options: InstanceLookupOptions = {}
|
|
148
|
+
): boolean {
|
|
149
|
+
return isProvided(context, token, options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function handleMissingInjection(
|
|
153
|
+
token: ProviderToken | string,
|
|
154
|
+
options: InjectionOptions
|
|
155
|
+
): never {
|
|
156
|
+
const name = toTokenName(token);
|
|
157
|
+
if (options.name) {
|
|
158
|
+
throw new Error(`Injection provider ${name} with name ${options.name} not found`);
|
|
159
|
+
}
|
|
160
|
+
throw new Error(`Injection provider ${name} not found`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function markInjected(record: ProviderRecord, key: string) {
|
|
164
|
+
record.injected.add(key);
|
|
40
165
|
}
|
|
41
166
|
|
|
167
|
+
function markAllInjected(record: ProviderRecord) {
|
|
168
|
+
for (const key of record.values.keys()) {
|
|
169
|
+
record.injected.add(key);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function inject<T>(context: Context, token: ProviderToken | string, options: InjectionOptions & { multi: true }): T[];
|
|
174
|
+
export function inject<T>(context: Context, token: ProviderToken | string, options: InjectionOptions & { optional: true }): T | undefined;
|
|
175
|
+
export function inject<T>(context: Context, token: ProviderToken | string, options?: InjectionOptions): T;
|
|
42
176
|
/**
|
|
43
177
|
* Retrieves a service from the dependency injection context
|
|
44
178
|
* @template T - Type of the service to inject
|
|
45
179
|
* @param context - The injection context
|
|
46
|
-
* @param
|
|
47
|
-
* @param
|
|
48
|
-
* @returns The injected service instance
|
|
180
|
+
* @param token - Class constructor or string identifier of the service
|
|
181
|
+
* @param options - Optional configuration for resolving the service
|
|
182
|
+
* @returns The injected service instance or `undefined` when optional
|
|
49
183
|
* @throws {Error} If the requested service is not found in the context
|
|
50
184
|
*/
|
|
51
185
|
export function inject<T>(
|
|
52
186
|
context: Context,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
): T {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
187
|
+
token: ProviderToken | string,
|
|
188
|
+
options: InjectionOptions = {}
|
|
189
|
+
): T | T[] | undefined {
|
|
190
|
+
const record = getRecord(context, token);
|
|
191
|
+
|
|
192
|
+
if (!record) {
|
|
193
|
+
if (options.optional) {
|
|
194
|
+
return options.multi ? [] : undefined;
|
|
195
|
+
}
|
|
196
|
+
return handleMissingInjection(token, options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (options.name) {
|
|
200
|
+
const instanceKey = toInstanceKey(options.name);
|
|
201
|
+
if (!record.values.has(instanceKey)) {
|
|
202
|
+
if (options.optional) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
return handleMissingInjection(token, options);
|
|
206
|
+
}
|
|
207
|
+
const value = record.values.get(instanceKey);
|
|
208
|
+
markInjected(record, instanceKey);
|
|
61
209
|
return value as T;
|
|
62
210
|
}
|
|
63
|
-
|
|
211
|
+
|
|
212
|
+
if (options.multi || record.multi) {
|
|
213
|
+
if (record.values.size === 0) {
|
|
214
|
+
if (options.optional) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
return handleMissingInjection(token, options);
|
|
218
|
+
}
|
|
219
|
+
markAllInjected(record);
|
|
220
|
+
return Array.from(record.values.values()) as T[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const value = record.values.get(DEFAULT_INSTANCE_KEY);
|
|
224
|
+
if (value === undefined) {
|
|
225
|
+
if (options.optional) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
return handleMissingInjection(token, options);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
markInjected(record, DEFAULT_INSTANCE_KEY);
|
|
232
|
+
return value as T;
|
|
64
233
|
}
|
|
65
234
|
|
|
66
235
|
/**
|
|
@@ -89,7 +258,7 @@ export function override(
|
|
|
89
258
|
|
|
90
259
|
// Flatten the providers array
|
|
91
260
|
const flatProviders = providers.flat();
|
|
92
|
-
|
|
261
|
+
|
|
93
262
|
// Check if provider exists
|
|
94
263
|
const exists = flatProviders.some(provider => {
|
|
95
264
|
if (typeof provider === "function") {
|
|
@@ -127,7 +296,7 @@ export function override(
|
|
|
127
296
|
*/
|
|
128
297
|
export function findProviders<T extends Provider = Provider>(providers: Providers, name: string | RegExp): T[] {
|
|
129
298
|
const results: any[] = [];
|
|
130
|
-
|
|
299
|
+
|
|
131
300
|
for (const provider of providers) {
|
|
132
301
|
if (Array.isArray(provider)) {
|
|
133
302
|
// Recursively search in nested arrays and concat results
|
|
@@ -137,7 +306,7 @@ export function findProviders<T extends Provider = Provider>(providers: Provider
|
|
|
137
306
|
results.push(provider as T);
|
|
138
307
|
}
|
|
139
308
|
}
|
|
140
|
-
|
|
309
|
+
|
|
141
310
|
return results;
|
|
142
311
|
}
|
|
143
312
|
|
|
@@ -154,7 +323,7 @@ export function findProvider<T extends Provider = Provider>(providers: Providers
|
|
|
154
323
|
if (typeof providers === "object" && 'provide' in providers) {
|
|
155
324
|
const provider = providers as any
|
|
156
325
|
const providerName = typeof provider.provide === "function"
|
|
157
|
-
? provider.provide.name
|
|
326
|
+
? provider.provide.name
|
|
158
327
|
: provider.provide;
|
|
159
328
|
|
|
160
329
|
if (name instanceof RegExp) {
|
|
@@ -177,8 +346,8 @@ export function findProvider<T extends Provider = Provider>(providers: Providers
|
|
|
177
346
|
|
|
178
347
|
// Check object provider
|
|
179
348
|
if (typeof provider === "object" && 'provide' in provider) {
|
|
180
|
-
const providerName = typeof provider.provide === "function"
|
|
181
|
-
? provider.provide.name
|
|
349
|
+
const providerName = typeof provider.provide === "function"
|
|
350
|
+
? provider.provide.name
|
|
182
351
|
: provider.provide;
|
|
183
352
|
|
|
184
353
|
// Handle RegExp matching
|
package/src/provider.ts
CHANGED
|
@@ -3,10 +3,23 @@
|
|
|
3
3
|
* @module @signe/di/provider
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ClassProvider, Provider, Providers, ProviderToken } from "./types";
|
|
6
|
+
import { ClassProvider, ProvideOptions, Provider, Providers, ProviderToken } from "./types";
|
|
7
7
|
import { inject, provide } from "./inject";
|
|
8
8
|
import { Context } from "./context";
|
|
9
9
|
|
|
10
|
+
function extractProvideOptions(source: { multi?: boolean; name?: string } | undefined): ProvideOptions | undefined {
|
|
11
|
+
if (!source) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { multi, name } = source;
|
|
16
|
+
if (multi === undefined && name === undefined) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { multi, name };
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
/**
|
|
11
24
|
* Type guard to check if a provider is a ClassProvider
|
|
12
25
|
* @param provider - Provider to check
|
|
@@ -38,10 +51,15 @@ function getDeps(provider: Provider): ProviderToken[] {
|
|
|
38
51
|
*/
|
|
39
52
|
function sortProviders(providers: Provider[]): Provider[] {
|
|
40
53
|
const tokenName = (t: ProviderToken) => typeof t === 'function' ? t.name : t;
|
|
41
|
-
const map = new Map<string, Provider>();
|
|
54
|
+
const map = new Map<string, Provider[]>();
|
|
42
55
|
for (const p of providers) {
|
|
43
56
|
const token = tokenName(typeof p === 'function' ? p : (p as any).provide);
|
|
44
|
-
map.
|
|
57
|
+
const list = map.get(token);
|
|
58
|
+
if (list) {
|
|
59
|
+
list.push(p);
|
|
60
|
+
} else {
|
|
61
|
+
map.set(token, [p]);
|
|
62
|
+
}
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
const result: Provider[] = [];
|
|
@@ -55,13 +73,15 @@ function sortProviders(providers: Provider[]): Provider[] {
|
|
|
55
73
|
throw new Error(`Circular dependency detected for provider ${name}`);
|
|
56
74
|
}
|
|
57
75
|
stack.add(name);
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
for (const
|
|
61
|
-
|
|
76
|
+
const providersForToken = map.get(name);
|
|
77
|
+
if (providersForToken) {
|
|
78
|
+
for (const provider of providersForToken) {
|
|
79
|
+
for (const dep of getDeps(provider)) {
|
|
80
|
+
visit(dep);
|
|
81
|
+
}
|
|
82
|
+
result.push(provider);
|
|
62
83
|
}
|
|
63
84
|
visited.add(name);
|
|
64
|
-
result.push(provider);
|
|
65
85
|
}
|
|
66
86
|
stack.delete(name);
|
|
67
87
|
};
|
|
@@ -99,13 +119,17 @@ export async function injector(context: Context, providers: Providers) {
|
|
|
99
119
|
for (const provider of providers) {
|
|
100
120
|
let token: ProviderToken;
|
|
101
121
|
let instance: any;
|
|
122
|
+
let options: ProvideOptions | undefined;
|
|
102
123
|
|
|
103
124
|
if (typeof provider === 'function') {
|
|
104
125
|
// If provider is a class, treat it as a ClassProvider
|
|
105
126
|
token = provider;
|
|
106
127
|
instance = new provider(context);
|
|
128
|
+
const diOptions = extractProvideOptions((provider as any).diOptions ?? (provider as any).di);
|
|
129
|
+
options = diOptions;
|
|
107
130
|
} else {
|
|
108
131
|
token = (provider as any).provide;
|
|
132
|
+
options = extractProvideOptions(provider as any);
|
|
109
133
|
const provideUserClass = (provider as any).useClass;
|
|
110
134
|
const isClass = typeof provideUserClass === 'function';
|
|
111
135
|
if (isClass) {
|
|
@@ -122,7 +146,6 @@ export async function injector(context: Context, providers: Providers) {
|
|
|
122
146
|
}
|
|
123
147
|
}
|
|
124
148
|
|
|
125
|
-
|
|
126
|
-
provide(context, name, instance);
|
|
149
|
+
provide(context, token, instance, options);
|
|
127
150
|
}
|
|
128
151
|
}
|
package/src/types.ts
CHANGED
|
@@ -27,7 +27,54 @@ export interface ProviderMeta {
|
|
|
27
27
|
[key: string]: any;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Common options available to all providers
|
|
32
|
+
*/
|
|
33
|
+
export interface ProviderOptions {
|
|
34
|
+
/**
|
|
35
|
+
* When true, allows multiple instances to be registered for the same token
|
|
36
|
+
*/
|
|
37
|
+
multi?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Optional name used to register and resolve a specific instance
|
|
40
|
+
*/
|
|
41
|
+
name?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Options that can be passed when calling {@link provide}
|
|
46
|
+
*/
|
|
47
|
+
export interface ProvideOptions extends ProviderOptions {}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options that can be passed when calling {@link inject}
|
|
51
|
+
*/
|
|
52
|
+
export interface InjectionOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Optional name used to resolve a specific instance
|
|
55
|
+
*/
|
|
56
|
+
name?: string;
|
|
57
|
+
/**
|
|
58
|
+
* When true, allows retrieving all instances registered with {@link ProviderOptions.multi}
|
|
59
|
+
*/
|
|
60
|
+
multi?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* When true, `inject` will return `undefined` instead of throwing if the instance is missing
|
|
63
|
+
*/
|
|
64
|
+
optional?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Options used when checking if an instance exists in the context
|
|
69
|
+
*/
|
|
70
|
+
export interface InstanceLookupOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Optional name of the instance to check
|
|
73
|
+
*/
|
|
74
|
+
name?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ValueProvider extends ProviderOptions {
|
|
31
78
|
/** Token to identify the provider */
|
|
32
79
|
provide: ProviderToken;
|
|
33
80
|
/** Value to be injected */
|
|
@@ -41,7 +88,7 @@ export interface ValueProvider {
|
|
|
41
88
|
/**
|
|
42
89
|
* Provider configuration for class-based injection
|
|
43
90
|
*/
|
|
44
|
-
export interface ClassProvider {
|
|
91
|
+
export interface ClassProvider extends ProviderOptions {
|
|
45
92
|
/** Token to identify the provider */
|
|
46
93
|
provide: ProviderToken;
|
|
47
94
|
/** Class to be instantiated */
|
|
@@ -55,7 +102,7 @@ export interface ClassProvider {
|
|
|
55
102
|
/**
|
|
56
103
|
* Provider configuration for factory-based injection
|
|
57
104
|
*/
|
|
58
|
-
export interface FactoryProvider {
|
|
105
|
+
export interface FactoryProvider extends ProviderOptions {
|
|
59
106
|
/** Token to identify the provider */
|
|
60
107
|
provide: ProviderToken;
|
|
61
108
|
/** Tokens that must be injected before this provider */
|
|
@@ -69,7 +116,7 @@ export interface FactoryProvider {
|
|
|
69
116
|
/**
|
|
70
117
|
* Provider configuration for alias-based injection
|
|
71
118
|
*/
|
|
72
|
-
export interface ExistingProvider {
|
|
119
|
+
export interface ExistingProvider extends ProviderOptions {
|
|
73
120
|
/** Token to identify the provider */
|
|
74
121
|
provide: ProviderToken;
|
|
75
122
|
/** Token of the existing provider to use */
|