@lppedd/di-wise-neo 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +488 -0
- package/dist/cjs/index.d.ts +772 -0
- package/dist/cjs/index.js +1055 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/es/index.d.mts +772 -0
- package/dist/es/index.mjs +1037 -0
- package/dist/es/index.mjs.map +1 -0
- package/package.json +77 -0
- package/src/container.ts +292 -0
- package/src/decorators/autoRegister.ts +24 -0
- package/src/decorators/decorators.ts +47 -0
- package/src/decorators/index.ts +7 -0
- package/src/decorators/inject.ts +42 -0
- package/src/decorators/injectAll.ts +45 -0
- package/src/decorators/injectable.ts +61 -0
- package/src/decorators/optional.ts +39 -0
- package/src/decorators/optionalAll.ts +42 -0
- package/src/decorators/scoped.ts +32 -0
- package/src/defaultContainer.ts +519 -0
- package/src/errors.ts +47 -0
- package/src/index.ts +16 -0
- package/src/inject.ts +88 -0
- package/src/injectAll.ts +21 -0
- package/src/injectionContext.ts +46 -0
- package/src/injector.ts +117 -0
- package/src/metadata.ts +41 -0
- package/src/middleware.ts +85 -0
- package/src/optional.ts +65 -0
- package/src/optionalAll.ts +19 -0
- package/src/provider.ts +61 -0
- package/src/scope.ts +8 -0
- package/src/token.ts +84 -0
- package/src/tokenRegistry.ts +165 -0
- package/src/tokensRef.ts +46 -0
- package/src/utils/context.ts +19 -0
- package/src/utils/disposable.ts +10 -0
- package/src/utils/invariant.ts +6 -0
- package/src/utils/keyedStack.ts +26 -0
- package/src/utils/typeName.ts +28 -0
- package/src/utils/weakRefMap.ts +28 -0
- package/src/valueRef.ts +3 -0
@@ -0,0 +1,1037 @@
|
|
1
|
+
// @internal
|
2
|
+
function assert(condition, message) {
|
3
|
+
if (!condition) {
|
4
|
+
const resolvedMessage = typeof message === "string" ? message : message();
|
5
|
+
throw new Error(tag(resolvedMessage));
|
6
|
+
}
|
7
|
+
}
|
8
|
+
// @internal
|
9
|
+
function expectNever(value) {
|
10
|
+
throw new TypeError(tag(`unexpected value ${String(value)}`));
|
11
|
+
}
|
12
|
+
// @internal
|
13
|
+
function throwUnregisteredError(token) {
|
14
|
+
throw new Error(tag(`unregistered token ${token.name}`));
|
15
|
+
}
|
16
|
+
// @internal
|
17
|
+
function throwExistingUnregisteredError(sourceToken, targetTokenOrError) {
|
18
|
+
let message = tag(`token resolution error encountered while resolving ${sourceToken.name}`);
|
19
|
+
if (isError(targetTokenOrError)) {
|
20
|
+
message += `\n [cause] ${untag(targetTokenOrError.message)}`;
|
21
|
+
} else {
|
22
|
+
message += `\n [cause] the aliased token ${targetTokenOrError.name} is not registered`;
|
23
|
+
}
|
24
|
+
throw new Error(message);
|
25
|
+
}
|
26
|
+
function isError(value) {
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
28
|
+
return value && value.stack && value.message && typeof value.message === "string";
|
29
|
+
}
|
30
|
+
function tag(message) {
|
31
|
+
return `[di-wise-neo] ${message}`;
|
32
|
+
}
|
33
|
+
function untag(message) {
|
34
|
+
return message.startsWith("[di-wise-neo]") //
|
35
|
+
? message.substring(13).trimStart() : message;
|
36
|
+
}
|
37
|
+
|
38
|
+
// @internal
|
39
|
+
function createInjectionContext() {
|
40
|
+
let current = null;
|
41
|
+
function provide(next) {
|
42
|
+
const prev = current;
|
43
|
+
current = next;
|
44
|
+
return ()=>current = prev;
|
45
|
+
}
|
46
|
+
function use() {
|
47
|
+
return current;
|
48
|
+
}
|
49
|
+
return [
|
50
|
+
provide,
|
51
|
+
use
|
52
|
+
];
|
53
|
+
}
|
54
|
+
|
55
|
+
// @internal
|
56
|
+
function invariant(condition) {
|
57
|
+
if (!condition) {
|
58
|
+
throw new Error("invariant violation");
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
// @internal
|
63
|
+
class KeyedStack {
|
64
|
+
has(key) {
|
65
|
+
return this.myKeys.has(key);
|
66
|
+
}
|
67
|
+
peek() {
|
68
|
+
const entry = this.myEntries.at(-1);
|
69
|
+
return entry?.value;
|
70
|
+
}
|
71
|
+
push(key, value) {
|
72
|
+
invariant(!this.has(key));
|
73
|
+
this.myKeys.add(key);
|
74
|
+
this.myEntries.push({
|
75
|
+
key,
|
76
|
+
value
|
77
|
+
});
|
78
|
+
return ()=>{
|
79
|
+
this.myEntries.pop();
|
80
|
+
this.myKeys.delete(key);
|
81
|
+
};
|
82
|
+
}
|
83
|
+
constructor(){
|
84
|
+
this.myEntries = new Array();
|
85
|
+
this.myKeys = new WeakSet();
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
// @internal
|
90
|
+
class WeakRefMap {
|
91
|
+
get(key) {
|
92
|
+
const ref = this.myMap.get(key);
|
93
|
+
if (ref) {
|
94
|
+
const value = ref.deref();
|
95
|
+
if (value) {
|
96
|
+
return value;
|
97
|
+
}
|
98
|
+
this.myMap.delete(key);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
set(key, value) {
|
102
|
+
invariant(!this.get(key));
|
103
|
+
this.myMap.set(key, new WeakRef(value));
|
104
|
+
return ()=>{
|
105
|
+
this.myMap.delete(key);
|
106
|
+
};
|
107
|
+
}
|
108
|
+
constructor(){
|
109
|
+
this.myMap = new WeakMap();
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
// @internal
|
114
|
+
function createResolution() {
|
115
|
+
return {
|
116
|
+
stack: new KeyedStack(),
|
117
|
+
values: new WeakRefMap(),
|
118
|
+
dependents: new WeakRefMap()
|
119
|
+
};
|
120
|
+
}
|
121
|
+
// @internal
|
122
|
+
const [provideInjectionContext, useInjectionContext] = createInjectionContext();
|
123
|
+
// @internal
|
124
|
+
function ensureInjectionContext(fn) {
|
125
|
+
const context = useInjectionContext();
|
126
|
+
assert(context, `${fn.name}() can only be invoked within an injection context`);
|
127
|
+
return context;
|
128
|
+
}
|
129
|
+
|
130
|
+
function inject(token) {
|
131
|
+
const context = ensureInjectionContext(inject);
|
132
|
+
return context.container.resolve(token);
|
133
|
+
}
|
134
|
+
function injectBy(thisArg, token) {
|
135
|
+
const context = ensureInjectionContext(injectBy);
|
136
|
+
const resolution = context.resolution;
|
137
|
+
const currentFrame = resolution.stack.peek();
|
138
|
+
if (!currentFrame) {
|
139
|
+
return inject(token);
|
140
|
+
}
|
141
|
+
const currentRef = {
|
142
|
+
current: thisArg
|
143
|
+
};
|
144
|
+
const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
|
145
|
+
try {
|
146
|
+
return inject(token);
|
147
|
+
} finally{
|
148
|
+
cleanup();
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
function injectAll(token) {
|
153
|
+
const context = ensureInjectionContext(injectAll);
|
154
|
+
return context.container.resolveAll(token);
|
155
|
+
}
|
156
|
+
|
157
|
+
// @internal
|
158
|
+
function getMetadata(Class) {
|
159
|
+
let metadata = metadataMap.get(Class);
|
160
|
+
if (!metadata) {
|
161
|
+
metadataMap.set(Class, metadata = {
|
162
|
+
tokensRef: {
|
163
|
+
getRefTokens: ()=>new Set()
|
164
|
+
},
|
165
|
+
provider: {
|
166
|
+
useClass: Class
|
167
|
+
},
|
168
|
+
dependencies: {
|
169
|
+
constructor: [],
|
170
|
+
methods: new Map()
|
171
|
+
}
|
172
|
+
});
|
173
|
+
}
|
174
|
+
return metadata;
|
175
|
+
}
|
176
|
+
const metadataMap = new WeakMap();
|
177
|
+
|
178
|
+
function optional(token) {
|
179
|
+
const context = ensureInjectionContext(optional);
|
180
|
+
return context.container.resolve(token, true);
|
181
|
+
}
|
182
|
+
function optionalBy(thisArg, token) {
|
183
|
+
const context = ensureInjectionContext(optionalBy);
|
184
|
+
const resolution = context.resolution;
|
185
|
+
const currentFrame = resolution.stack.peek();
|
186
|
+
if (!currentFrame) {
|
187
|
+
return optional(token);
|
188
|
+
}
|
189
|
+
const currentRef = {
|
190
|
+
current: thisArg
|
191
|
+
};
|
192
|
+
const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
|
193
|
+
try {
|
194
|
+
return optional(token);
|
195
|
+
} finally{
|
196
|
+
cleanup();
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
function optionalAll(token) {
|
201
|
+
const context = ensureInjectionContext(optionalAll);
|
202
|
+
return context.container.resolveAll(token, true);
|
203
|
+
}
|
204
|
+
|
205
|
+
// @internal
|
206
|
+
function isClassProvider(provider) {
|
207
|
+
return "useClass" in provider;
|
208
|
+
}
|
209
|
+
// @internal
|
210
|
+
function isExistingProvider(provider) {
|
211
|
+
return "useExisting" in provider;
|
212
|
+
}
|
213
|
+
// @internal
|
214
|
+
function isFactoryProvider(provider) {
|
215
|
+
return "useFactory" in provider;
|
216
|
+
}
|
217
|
+
// @internal
|
218
|
+
function isValueProvider(provider) {
|
219
|
+
return "useValue" in provider;
|
220
|
+
}
|
221
|
+
|
222
|
+
const Scope = {
|
223
|
+
Inherited: "Inherited",
|
224
|
+
Transient: "Transient",
|
225
|
+
Resolution: "Resolution",
|
226
|
+
Container: "Container"
|
227
|
+
};
|
228
|
+
|
229
|
+
/**
|
230
|
+
* Type API.
|
231
|
+
*/ /**
|
232
|
+
* Create a type token.
|
233
|
+
*
|
234
|
+
* @example
|
235
|
+
* ```ts
|
236
|
+
* const Spell = Type<Spell>("Spell");
|
237
|
+
* ```
|
238
|
+
*
|
239
|
+
* @__NO_SIDE_EFFECTS__
|
240
|
+
*/ function Type(typeName) {
|
241
|
+
const type = {
|
242
|
+
name: `Type<${typeName}>`,
|
243
|
+
inter: Type,
|
244
|
+
union: Type,
|
245
|
+
toString () {
|
246
|
+
return type.name;
|
247
|
+
}
|
248
|
+
};
|
249
|
+
return type;
|
250
|
+
}
|
251
|
+
// @internal
|
252
|
+
function isConstructor(token) {
|
253
|
+
return typeof token == "function";
|
254
|
+
}
|
255
|
+
|
256
|
+
// @internal
|
257
|
+
function getTypeName(value) {
|
258
|
+
if (typeof value == "string") {
|
259
|
+
return `"${value}"`;
|
260
|
+
}
|
261
|
+
if (typeof value == "function") {
|
262
|
+
return value.name && `typeof ${value.name}` || "Function";
|
263
|
+
}
|
264
|
+
if (typeof value == "object") {
|
265
|
+
if (value === null) {
|
266
|
+
return "null";
|
267
|
+
}
|
268
|
+
const proto = Object.getPrototypeOf(value);
|
269
|
+
if (proto && proto !== Object.prototype) {
|
270
|
+
const constructor = proto.constructor;
|
271
|
+
if (typeof constructor == "function" && constructor.name) {
|
272
|
+
return constructor.name;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
return typeof value;
|
277
|
+
}
|
278
|
+
|
279
|
+
// @internal
|
280
|
+
class TokenRegistry {
|
281
|
+
constructor(parent){
|
282
|
+
this.parent = parent;
|
283
|
+
this.myMap = new Map();
|
284
|
+
}
|
285
|
+
get(token) {
|
286
|
+
// To clarify, at(-1) means we take the last added registration for this token
|
287
|
+
return this.getAll(token)?.at(-1);
|
288
|
+
}
|
289
|
+
getAll(token) {
|
290
|
+
const internal = internals.get(token);
|
291
|
+
return internal && [
|
292
|
+
internal
|
293
|
+
] || this.getAllFromParent(token);
|
294
|
+
}
|
295
|
+
set(token, registration) {
|
296
|
+
assert(!internals.has(token), `cannot register reserved token ${token.name}`);
|
297
|
+
let registrations = this.myMap.get(token);
|
298
|
+
if (!registrations) {
|
299
|
+
this.myMap.set(token, registrations = []);
|
300
|
+
}
|
301
|
+
registrations.push(registration);
|
302
|
+
}
|
303
|
+
delete(token) {
|
304
|
+
const registrations = this.myMap.get(token);
|
305
|
+
this.myMap.delete(token);
|
306
|
+
return registrations;
|
307
|
+
}
|
308
|
+
deleteAll() {
|
309
|
+
const tokens = Array.from(this.myMap.keys());
|
310
|
+
const registrations = Array.from(this.myMap.values()).flat();
|
311
|
+
this.myMap.clear();
|
312
|
+
return [
|
313
|
+
tokens,
|
314
|
+
registrations
|
315
|
+
];
|
316
|
+
}
|
317
|
+
clearRegistrations() {
|
318
|
+
const values = new Set();
|
319
|
+
for (const registrations of this.myMap.values()){
|
320
|
+
for(let i = 0; i < registrations.length; i++){
|
321
|
+
const registration = registrations[i];
|
322
|
+
const value = registration.value;
|
323
|
+
if (value) {
|
324
|
+
values.add(value.current);
|
325
|
+
}
|
326
|
+
registrations[i] = {
|
327
|
+
...registration,
|
328
|
+
value: undefined
|
329
|
+
};
|
330
|
+
}
|
331
|
+
}
|
332
|
+
return Array.from(values);
|
333
|
+
}
|
334
|
+
getAllFromParent(token) {
|
335
|
+
const registrations = this.myMap.get(token);
|
336
|
+
return registrations || this.parent?.getAllFromParent(token);
|
337
|
+
}
|
338
|
+
}
|
339
|
+
// @internal
|
340
|
+
function isBuilder(provider) {
|
341
|
+
return builders.has(provider);
|
342
|
+
}
|
343
|
+
/**
|
344
|
+
* Create a one-off type token from a factory function.
|
345
|
+
*
|
346
|
+
* @example
|
347
|
+
* ```ts
|
348
|
+
* class Wizard {
|
349
|
+
* wand = inject(
|
350
|
+
* Build(() => {
|
351
|
+
* const wand = inject(Wand);
|
352
|
+
* wand.owner = this;
|
353
|
+
* // ...
|
354
|
+
* return wand;
|
355
|
+
* }),
|
356
|
+
* );
|
357
|
+
* }
|
358
|
+
* ```
|
359
|
+
*
|
360
|
+
* @__NO_SIDE_EFFECTS__
|
361
|
+
*/ function Build(factory) {
|
362
|
+
const token = Type(`Build<${getTypeName(factory)}>`);
|
363
|
+
const provider = {
|
364
|
+
useFactory: factory
|
365
|
+
};
|
366
|
+
internals.set(token, {
|
367
|
+
provider: provider,
|
368
|
+
options: {
|
369
|
+
scope: Scope.Transient
|
370
|
+
}
|
371
|
+
});
|
372
|
+
builders.add(provider);
|
373
|
+
return token;
|
374
|
+
}
|
375
|
+
const internals = new WeakMap();
|
376
|
+
const builders = new WeakSet();
|
377
|
+
|
378
|
+
// @internal
|
379
|
+
// @internal
|
380
|
+
function isDisposable(value) {
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
382
|
+
return value && typeof value === "object" && typeof value.dispose === "function";
|
383
|
+
}
|
384
|
+
|
385
|
+
/**
|
386
|
+
* The default implementation of a di-wise-neo {@link Container}.
|
387
|
+
*/ class DefaultContainer {
|
388
|
+
constructor(myParent, options){
|
389
|
+
this.myParent = myParent;
|
390
|
+
// eslint-disable-next-line no-use-before-define
|
391
|
+
this.myChildren = new Set();
|
392
|
+
this.myDisposed = false;
|
393
|
+
this.myOptions = {
|
394
|
+
autoRegister: false,
|
395
|
+
defaultScope: Scope.Inherited,
|
396
|
+
...options
|
397
|
+
};
|
398
|
+
this.myTokenRegistry = new TokenRegistry(this.myParent?.myTokenRegistry);
|
399
|
+
}
|
400
|
+
get registry() {
|
401
|
+
return this.myTokenRegistry;
|
402
|
+
}
|
403
|
+
get options() {
|
404
|
+
return {
|
405
|
+
...this.myOptions
|
406
|
+
};
|
407
|
+
}
|
408
|
+
get parent() {
|
409
|
+
return this.myParent;
|
410
|
+
}
|
411
|
+
get isDisposed() {
|
412
|
+
return this.myDisposed;
|
413
|
+
}
|
414
|
+
createChild(options) {
|
415
|
+
this.checkDisposed();
|
416
|
+
const container = new DefaultContainer(this, {
|
417
|
+
...this.myOptions,
|
418
|
+
...options
|
419
|
+
});
|
420
|
+
this.myChildren.add(container);
|
421
|
+
return container;
|
422
|
+
}
|
423
|
+
clearCache() {
|
424
|
+
this.checkDisposed();
|
425
|
+
return this.myTokenRegistry.clearRegistrations();
|
426
|
+
}
|
427
|
+
getCached(token) {
|
428
|
+
this.checkDisposed();
|
429
|
+
const registration = this.myTokenRegistry.get(token);
|
430
|
+
return registration?.value?.current;
|
431
|
+
}
|
432
|
+
getAllCached(token) {
|
433
|
+
this.checkDisposed();
|
434
|
+
const registrations = this.myTokenRegistry.getAll(token);
|
435
|
+
if (!registrations) {
|
436
|
+
return [];
|
437
|
+
}
|
438
|
+
const values = new Set();
|
439
|
+
for (const registration of registrations){
|
440
|
+
const value = registration.value;
|
441
|
+
if (value) {
|
442
|
+
values.add(value.current);
|
443
|
+
}
|
444
|
+
}
|
445
|
+
return Array.from(values);
|
446
|
+
}
|
447
|
+
resetRegistry() {
|
448
|
+
this.checkDisposed();
|
449
|
+
const [, registrations] = this.myTokenRegistry.deleteAll();
|
450
|
+
const values = new Set();
|
451
|
+
for (const registration of registrations){
|
452
|
+
const value = registration.value;
|
453
|
+
if (value) {
|
454
|
+
values.add(value.current);
|
455
|
+
}
|
456
|
+
}
|
457
|
+
return Array.from(values);
|
458
|
+
}
|
459
|
+
isRegistered(token) {
|
460
|
+
this.checkDisposed();
|
461
|
+
return this.myTokenRegistry.get(token) !== undefined;
|
462
|
+
}
|
463
|
+
register(...args) {
|
464
|
+
this.checkDisposed();
|
465
|
+
if (args.length == 1) {
|
466
|
+
const Class = args[0];
|
467
|
+
const metadata = getMetadata(Class);
|
468
|
+
// Register the class itself
|
469
|
+
this.myTokenRegistry.set(Class, {
|
470
|
+
// The provider is of type ClassProvider, initialized by getMetadata
|
471
|
+
provider: metadata.provider,
|
472
|
+
options: {
|
473
|
+
scope: metadata.scope
|
474
|
+
},
|
475
|
+
dependencies: metadata.dependencies
|
476
|
+
});
|
477
|
+
// Register the additional tokens specified via class decorators.
|
478
|
+
// These tokens will point to the original Class token and will have the same scope.
|
479
|
+
for (const token of metadata.tokensRef.getRefTokens()){
|
480
|
+
this.myTokenRegistry.set(token, {
|
481
|
+
provider: {
|
482
|
+
useExisting: Class
|
483
|
+
}
|
484
|
+
});
|
485
|
+
}
|
486
|
+
} else {
|
487
|
+
const [token, provider, options] = args;
|
488
|
+
if (isClassProvider(provider)) {
|
489
|
+
const Class = provider.useClass;
|
490
|
+
const metadata = getMetadata(Class);
|
491
|
+
this.myTokenRegistry.set(token, {
|
492
|
+
provider: metadata.provider,
|
493
|
+
options: {
|
494
|
+
// The explicit registration options override what is specified
|
495
|
+
// via class decorators (e.g., @Scoped)
|
496
|
+
scope: metadata.scope,
|
497
|
+
...options
|
498
|
+
},
|
499
|
+
dependencies: metadata.dependencies
|
500
|
+
});
|
501
|
+
} else {
|
502
|
+
if (isExistingProvider(provider)) {
|
503
|
+
assert(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
|
504
|
+
}
|
505
|
+
this.myTokenRegistry.set(token, {
|
506
|
+
provider: provider,
|
507
|
+
options: options
|
508
|
+
});
|
509
|
+
}
|
510
|
+
}
|
511
|
+
return this;
|
512
|
+
}
|
513
|
+
unregister(token) {
|
514
|
+
this.checkDisposed();
|
515
|
+
const registrations = this.myTokenRegistry.delete(token);
|
516
|
+
if (!registrations) {
|
517
|
+
return [];
|
518
|
+
}
|
519
|
+
const values = new Set();
|
520
|
+
for (const registration of registrations){
|
521
|
+
const value = registration.value;
|
522
|
+
if (value) {
|
523
|
+
values.add(value.current);
|
524
|
+
}
|
525
|
+
}
|
526
|
+
return Array.from(values);
|
527
|
+
}
|
528
|
+
resolve(token, optional) {
|
529
|
+
this.checkDisposed();
|
530
|
+
const registration = this.myTokenRegistry.get(token);
|
531
|
+
if (registration) {
|
532
|
+
return this.resolveRegistration(token, registration);
|
533
|
+
}
|
534
|
+
if (isConstructor(token)) {
|
535
|
+
return this.instantiateClass(token, optional);
|
536
|
+
}
|
537
|
+
return optional ? undefined : throwUnregisteredError(token);
|
538
|
+
}
|
539
|
+
resolveAll(token, optional) {
|
540
|
+
this.checkDisposed();
|
541
|
+
const registrations = this.myTokenRegistry.getAll(token);
|
542
|
+
if (registrations) {
|
543
|
+
return registrations.map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
|
544
|
+
}
|
545
|
+
if (isConstructor(token)) {
|
546
|
+
const instance = this.instantiateClass(token, optional);
|
547
|
+
return instance === undefined // = could not resolve, but since it is optional
|
548
|
+
? [] : [
|
549
|
+
instance
|
550
|
+
];
|
551
|
+
}
|
552
|
+
return optional ? [] : throwUnregisteredError(token);
|
553
|
+
}
|
554
|
+
dispose() {
|
555
|
+
if (this.myDisposed) {
|
556
|
+
return;
|
557
|
+
}
|
558
|
+
// Dispose children containers first
|
559
|
+
for (const child of this.myChildren){
|
560
|
+
child.dispose();
|
561
|
+
}
|
562
|
+
this.myChildren.clear();
|
563
|
+
// Remove ourselves from our parent container
|
564
|
+
this.myParent?.myChildren?.delete(this);
|
565
|
+
this.myDisposed = true;
|
566
|
+
const [, registrations] = this.myTokenRegistry.deleteAll();
|
567
|
+
const disposedRefs = new Set();
|
568
|
+
// Dispose all resolved (aka instantiated) tokens that implement the Disposable interface
|
569
|
+
for (const registration of registrations){
|
570
|
+
const value = registration.value?.current;
|
571
|
+
if (isDisposable(value) && !disposedRefs.has(value)) {
|
572
|
+
disposedRefs.add(value);
|
573
|
+
value.dispose();
|
574
|
+
}
|
575
|
+
}
|
576
|
+
// Allow values to be GCed
|
577
|
+
disposedRefs.clear();
|
578
|
+
}
|
579
|
+
resolveRegistration(token, registration) {
|
580
|
+
let currRegistration = registration;
|
581
|
+
let currProvider = currRegistration.provider;
|
582
|
+
while(isExistingProvider(currProvider)){
|
583
|
+
const targetToken = currProvider.useExisting;
|
584
|
+
currRegistration = this.myTokenRegistry.get(targetToken);
|
585
|
+
if (!currRegistration) {
|
586
|
+
throwExistingUnregisteredError(token, targetToken);
|
587
|
+
}
|
588
|
+
currProvider = currRegistration.provider;
|
589
|
+
}
|
590
|
+
try {
|
591
|
+
return this.resolveProviderValue(currRegistration, currProvider);
|
592
|
+
} catch (e) {
|
593
|
+
// If we were trying to resolve a token registered via ExistingProvider,
|
594
|
+
// we must add the cause of the error to the message
|
595
|
+
if (isExistingProvider(registration.provider)) {
|
596
|
+
throwExistingUnregisteredError(token, e);
|
597
|
+
}
|
598
|
+
throw e;
|
599
|
+
}
|
600
|
+
}
|
601
|
+
instantiateClass(Class, optional) {
|
602
|
+
const metadata = getMetadata(Class);
|
603
|
+
if (metadata.autoRegister ?? this.myOptions.autoRegister) {
|
604
|
+
this.register(Class);
|
605
|
+
return this.resolve(Class);
|
606
|
+
}
|
607
|
+
const scope = this.resolveScope(metadata.scope);
|
608
|
+
if (optional && scope === Scope.Container) {
|
609
|
+
// It would not be possible to resolve the class in container scope,
|
610
|
+
// as that would require prior registration.
|
611
|
+
// However, since resolution is marked optional, we simply return undefined.
|
612
|
+
return undefined;
|
613
|
+
}
|
614
|
+
assert(scope !== Scope.Container, `unregistered class ${Class.name} cannot be resolved in container scope`);
|
615
|
+
const registration = {
|
616
|
+
provider: metadata.provider,
|
617
|
+
options: {
|
618
|
+
scope: scope
|
619
|
+
},
|
620
|
+
dependencies: metadata.dependencies
|
621
|
+
};
|
622
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
623
|
+
return this.resolveScopedValue(registration, (args)=>new Class(...args));
|
624
|
+
}
|
625
|
+
resolveProviderValue(registration, provider) {
|
626
|
+
assert(registration.provider === provider, "internal error: mismatching provider");
|
627
|
+
if (isClassProvider(provider)) {
|
628
|
+
const Class = provider.useClass;
|
629
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
630
|
+
return this.resolveScopedValue(registration, (args)=>new Class(...args));
|
631
|
+
}
|
632
|
+
if (isFactoryProvider(provider)) {
|
633
|
+
const factory = provider.useFactory;
|
634
|
+
return this.resolveScopedValue(registration, factory);
|
635
|
+
}
|
636
|
+
if (isValueProvider(provider)) {
|
637
|
+
return provider.useValue;
|
638
|
+
}
|
639
|
+
if (isExistingProvider(provider)) {
|
640
|
+
assert(false, "internal error: unexpected ExistingProvider");
|
641
|
+
}
|
642
|
+
expectNever(provider);
|
643
|
+
}
|
644
|
+
resolveScopedValue(registration, factory) {
|
645
|
+
let context = useInjectionContext();
|
646
|
+
if (!context || context.container !== this) {
|
647
|
+
context = {
|
648
|
+
container: this,
|
649
|
+
resolution: createResolution()
|
650
|
+
};
|
651
|
+
}
|
652
|
+
const resolution = context.resolution;
|
653
|
+
const provider = registration.provider;
|
654
|
+
const options = registration.options;
|
655
|
+
if (resolution.stack.has(provider)) {
|
656
|
+
const dependentRef = resolution.dependents.get(provider);
|
657
|
+
assert(dependentRef, "circular dependency detected");
|
658
|
+
return dependentRef.current;
|
659
|
+
}
|
660
|
+
const scope = this.resolveScope(options?.scope, context);
|
661
|
+
const cleanups = [
|
662
|
+
provideInjectionContext(context),
|
663
|
+
!isBuilder(provider) && resolution.stack.push(provider, {
|
664
|
+
provider,
|
665
|
+
scope
|
666
|
+
})
|
667
|
+
];
|
668
|
+
try {
|
669
|
+
switch(scope){
|
670
|
+
case Scope.Container:
|
671
|
+
{
|
672
|
+
const valueRef = registration.value;
|
673
|
+
if (valueRef) {
|
674
|
+
return valueRef.current;
|
675
|
+
}
|
676
|
+
const args = this.resolveConstructorDependencies(registration);
|
677
|
+
const value = this.injectDependencies(registration, factory(args));
|
678
|
+
registration.value = {
|
679
|
+
current: value
|
680
|
+
};
|
681
|
+
return value;
|
682
|
+
}
|
683
|
+
case Scope.Resolution:
|
684
|
+
{
|
685
|
+
const valueRef = resolution.values.get(provider);
|
686
|
+
if (valueRef) {
|
687
|
+
return valueRef.current;
|
688
|
+
}
|
689
|
+
const args = this.resolveConstructorDependencies(registration);
|
690
|
+
const value = this.injectDependencies(registration, factory(args));
|
691
|
+
resolution.values.set(provider, {
|
692
|
+
current: value
|
693
|
+
});
|
694
|
+
return value;
|
695
|
+
}
|
696
|
+
case Scope.Transient:
|
697
|
+
{
|
698
|
+
const args = this.resolveConstructorDependencies(registration);
|
699
|
+
return this.injectDependencies(registration, factory(args));
|
700
|
+
}
|
701
|
+
}
|
702
|
+
} finally{
|
703
|
+
cleanups.forEach((cleanup)=>cleanup && cleanup());
|
704
|
+
}
|
705
|
+
}
|
706
|
+
resolveScope(scope = this.myOptions.defaultScope, context = useInjectionContext()) {
|
707
|
+
if (scope == Scope.Inherited) {
|
708
|
+
const dependentFrame = context?.resolution.stack.peek();
|
709
|
+
return dependentFrame?.scope || Scope.Transient;
|
710
|
+
}
|
711
|
+
return scope;
|
712
|
+
}
|
713
|
+
resolveConstructorDependencies(registration) {
|
714
|
+
const dependencies = registration.dependencies;
|
715
|
+
if (dependencies) {
|
716
|
+
assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
717
|
+
const ctorDeps = dependencies.constructor;
|
718
|
+
if (ctorDeps.length > 0) {
|
719
|
+
// Let's check if all necessary constructor parameters are decorated.
|
720
|
+
// If not, we cannot safely create an instance.
|
721
|
+
const ctor = registration.provider.useClass;
|
722
|
+
assert(ctor.length === ctorDeps.length, ()=>{
|
723
|
+
const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
|
724
|
+
return msg + `, but found ${ctorDeps.length}`;
|
725
|
+
});
|
726
|
+
return ctorDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
|
727
|
+
const token = dep.tokenRef.getRefToken();
|
728
|
+
switch(dep.decorator){
|
729
|
+
case "Inject":
|
730
|
+
return this.resolve(token);
|
731
|
+
case "InjectAll":
|
732
|
+
return this.resolveAll(token);
|
733
|
+
case "Optional":
|
734
|
+
return this.resolve(token, true);
|
735
|
+
case "OptionalAll":
|
736
|
+
return this.resolveAll(token, true);
|
737
|
+
}
|
738
|
+
});
|
739
|
+
}
|
740
|
+
}
|
741
|
+
return [];
|
742
|
+
}
|
743
|
+
injectDependencies(registration, instance) {
|
744
|
+
const dependencies = registration.dependencies;
|
745
|
+
if (dependencies) {
|
746
|
+
assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
747
|
+
const ctor = registration.provider.useClass;
|
748
|
+
// Perform method injection
|
749
|
+
for (const [key, methodDeps] of dependencies.methods){
|
750
|
+
// Let's check if all necessary method parameters are decorated.
|
751
|
+
// If not, we cannot safely invoke the method.
|
752
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
753
|
+
const method = instance[key];
|
754
|
+
assert(methodDeps.length === method.length, ()=>{
|
755
|
+
const msg = `expected ${method.length} decorated method parameters`;
|
756
|
+
return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
|
757
|
+
});
|
758
|
+
const args = methodDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
|
759
|
+
const token = dep.tokenRef.getRefToken();
|
760
|
+
switch(dep.decorator){
|
761
|
+
case "Inject":
|
762
|
+
return injectBy(instance, token);
|
763
|
+
case "InjectAll":
|
764
|
+
return injectAll(token);
|
765
|
+
case "Optional":
|
766
|
+
return optionalBy(instance, token);
|
767
|
+
case "OptionalAll":
|
768
|
+
return optionalAll(token);
|
769
|
+
}
|
770
|
+
});
|
771
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
772
|
+
method.bind(instance)(...args);
|
773
|
+
}
|
774
|
+
}
|
775
|
+
return instance;
|
776
|
+
}
|
777
|
+
checkDisposed() {
|
778
|
+
assert(!this.myDisposed, "the container is disposed");
|
779
|
+
}
|
780
|
+
}
|
781
|
+
|
782
|
+
/**
|
783
|
+
* Creates a new container.
|
784
|
+
*/ function createContainer(options = {
|
785
|
+
autoRegister: false,
|
786
|
+
defaultScope: Scope.Inherited
|
787
|
+
}) {
|
788
|
+
return new DefaultContainer(undefined, options);
|
789
|
+
}
|
790
|
+
|
791
|
+
/**
|
792
|
+
* Class decorator for enabling auto-registration of an unregistered class
|
793
|
+
* when first resolving it from the container.
|
794
|
+
*
|
795
|
+
* @example
|
796
|
+
* ```ts
|
797
|
+
* @AutoRegister()
|
798
|
+
* class Wizard {}
|
799
|
+
*
|
800
|
+
* const wizard = container.resolve(Wizard);
|
801
|
+
* container.isRegistered(Wizard); // => true
|
802
|
+
* ```
|
803
|
+
*
|
804
|
+
* @__NO_SIDE_EFFECTS__
|
805
|
+
*/ function AutoRegister(enable = true) {
|
806
|
+
return function(Class) {
|
807
|
+
const metadata = getMetadata(Class);
|
808
|
+
metadata.autoRegister = enable;
|
809
|
+
};
|
810
|
+
}
|
811
|
+
|
812
|
+
function forwardRef(token) {
|
813
|
+
return {
|
814
|
+
getRefTokens: ()=>{
|
815
|
+
// Normalize the single token here, so that we don't have
|
816
|
+
// to do it at every getRefTokens call site
|
817
|
+
const tokenOrTokens = token();
|
818
|
+
const tokensArray = Array.isArray(tokenOrTokens) ? tokenOrTokens : [
|
819
|
+
tokenOrTokens
|
820
|
+
];
|
821
|
+
return new Set(tokensArray);
|
822
|
+
},
|
823
|
+
getRefToken: ()=>{
|
824
|
+
const tokenOrTokens = token();
|
825
|
+
assert(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
|
826
|
+
return tokenOrTokens;
|
827
|
+
}
|
828
|
+
};
|
829
|
+
}
|
830
|
+
// @internal
|
831
|
+
function isTokensRef(value) {
|
832
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
833
|
+
return value && typeof value === "object" && typeof value.getRefTokens === "function";
|
834
|
+
}
|
835
|
+
// @internal
|
836
|
+
function isTokenRef(value) {
|
837
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
838
|
+
return value && typeof value === "object" && typeof value.getRefToken === "function";
|
839
|
+
}
|
840
|
+
|
841
|
+
function processDecoratedParameter(decorator, token, target, propertyKey, parameterIndex) {
|
842
|
+
// Error out immediately if the decorator has been applied
|
843
|
+
// to a static property or a static method
|
844
|
+
if (propertyKey !== undefined && typeof target === "function") {
|
845
|
+
assert(false, `@${decorator} cannot be used on static member ${target.name}.${String(propertyKey)}`);
|
846
|
+
}
|
847
|
+
const tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
848
|
+
if (propertyKey === undefined) {
|
849
|
+
// Constructor
|
850
|
+
const metadata = getMetadata(target);
|
851
|
+
metadata.dependencies.constructor.push({
|
852
|
+
decorator: decorator,
|
853
|
+
tokenRef: tokenRef,
|
854
|
+
index: parameterIndex
|
855
|
+
});
|
856
|
+
} else {
|
857
|
+
// Normal instance method
|
858
|
+
const metadata = getMetadata(target.constructor);
|
859
|
+
const methods = metadata.dependencies.methods;
|
860
|
+
let dep = methods.get(propertyKey);
|
861
|
+
if (dep === undefined) {
|
862
|
+
dep = [];
|
863
|
+
methods.set(propertyKey, dep);
|
864
|
+
}
|
865
|
+
dep.push({
|
866
|
+
decorator: decorator,
|
867
|
+
tokenRef: tokenRef,
|
868
|
+
index: parameterIndex
|
869
|
+
});
|
870
|
+
}
|
871
|
+
}
|
872
|
+
|
873
|
+
function Inject(token) {
|
874
|
+
return function(target, propertyKey, parameterIndex) {
|
875
|
+
processDecoratedParameter("Inject", token, target, propertyKey, parameterIndex);
|
876
|
+
};
|
877
|
+
}
|
878
|
+
|
879
|
+
/**
|
880
|
+
* @__NO_SIDE_EFFECTS__
|
881
|
+
*/ function Injectable(...args) {
|
882
|
+
return function(Class) {
|
883
|
+
const metadata = getMetadata(Class);
|
884
|
+
const arg0 = args[0];
|
885
|
+
const tokensRef = isTokensRef(arg0) ? arg0 : forwardRef(()=>args);
|
886
|
+
const existingTokensRef = metadata.tokensRef;
|
887
|
+
metadata.tokensRef = {
|
888
|
+
getRefTokens: ()=>{
|
889
|
+
const existingTokens = existingTokensRef.getRefTokens();
|
890
|
+
for (const token of tokensRef.getRefTokens()){
|
891
|
+
existingTokens.add(token);
|
892
|
+
}
|
893
|
+
return existingTokens;
|
894
|
+
}
|
895
|
+
};
|
896
|
+
};
|
897
|
+
}
|
898
|
+
|
899
|
+
function InjectAll(token) {
|
900
|
+
return function(target, propertyKey, parameterIndex) {
|
901
|
+
processDecoratedParameter("InjectAll", token, target, propertyKey, parameterIndex);
|
902
|
+
};
|
903
|
+
}
|
904
|
+
|
905
|
+
function Optional(token) {
|
906
|
+
return function(target, propertyKey, parameterIndex) {
|
907
|
+
processDecoratedParameter("Optional", token, target, propertyKey, parameterIndex);
|
908
|
+
};
|
909
|
+
}
|
910
|
+
|
911
|
+
function OptionalAll(token) {
|
912
|
+
return function(target, propertyKey, parameterIndex) {
|
913
|
+
processDecoratedParameter("OptionalAll", token, target, propertyKey, parameterIndex);
|
914
|
+
};
|
915
|
+
}
|
916
|
+
|
917
|
+
/**
|
918
|
+
* Class decorator for setting the scope of the decorated type when registering it.
|
919
|
+
*
|
920
|
+
* The scope set by this decorator can be overridden by explicit registration options, if provided.
|
921
|
+
*
|
922
|
+
* @example
|
923
|
+
* ```ts
|
924
|
+
* @Scoped(Scope.Container)
|
925
|
+
* class Wizard {}
|
926
|
+
*
|
927
|
+
* container.register(Wizard);
|
928
|
+
*
|
929
|
+
* // Under the hood
|
930
|
+
* container.register(
|
931
|
+
* Wizard,
|
932
|
+
* { useClass: Wizard },
|
933
|
+
* { scope: Scope.Container },
|
934
|
+
* );
|
935
|
+
* ```
|
936
|
+
*
|
937
|
+
* @__NO_SIDE_EFFECTS__
|
938
|
+
*/ function Scoped(scope) {
|
939
|
+
return function(Class) {
|
940
|
+
const metadata = getMetadata(Class);
|
941
|
+
metadata.scope = scope;
|
942
|
+
};
|
943
|
+
}
|
944
|
+
|
945
|
+
/**
|
946
|
+
* Injector token for dynamic injections.
|
947
|
+
*
|
948
|
+
* @example
|
949
|
+
* ```ts
|
950
|
+
* class Wizard {
|
951
|
+
* private injector = inject(Injector);
|
952
|
+
* private wand?: Wand;
|
953
|
+
*
|
954
|
+
* getWand(): Wand {
|
955
|
+
* return (this.wand ??= this.injector.inject(Wand));
|
956
|
+
* }
|
957
|
+
* }
|
958
|
+
*
|
959
|
+
* const wizard = container.resolve(Wizard);
|
960
|
+
* wizard.getWand(); // => Wand
|
961
|
+
* ```
|
962
|
+
*/ const Injector = /*@__PURE__*/ Build(function Injector() {
|
963
|
+
const context = ensureInjectionContext(Injector);
|
964
|
+
const resolution = context.resolution;
|
965
|
+
const dependentFrame = resolution.stack.peek();
|
966
|
+
const dependentRef = dependentFrame && resolution.dependents.get(dependentFrame.provider);
|
967
|
+
function withCurrentContext(fn) {
|
968
|
+
if (useInjectionContext()) {
|
969
|
+
return fn();
|
970
|
+
}
|
971
|
+
const cleanups = [
|
972
|
+
provideInjectionContext(context),
|
973
|
+
dependentFrame && resolution.stack.push(dependentFrame.provider, dependentFrame),
|
974
|
+
dependentRef && resolution.dependents.set(dependentFrame.provider, dependentRef)
|
975
|
+
];
|
976
|
+
try {
|
977
|
+
return fn();
|
978
|
+
} finally{
|
979
|
+
for (const cleanup of cleanups){
|
980
|
+
cleanup?.();
|
981
|
+
}
|
982
|
+
}
|
983
|
+
}
|
984
|
+
return {
|
985
|
+
inject: (token)=>withCurrentContext(()=>inject(token)),
|
986
|
+
injectAll: (token)=>withCurrentContext(()=>injectAll(token)),
|
987
|
+
optional: (token)=>withCurrentContext(()=>optional(token)),
|
988
|
+
optionalAll: (token)=>withCurrentContext(()=>optionalAll(token))
|
989
|
+
};
|
990
|
+
});
|
991
|
+
|
992
|
+
/**
|
993
|
+
* Apply middleware functions to a container.
|
994
|
+
*
|
995
|
+
* Middlewares are applied in array order, but execute in reverse order.
|
996
|
+
*
|
997
|
+
* @example
|
998
|
+
* ```ts
|
999
|
+
* const container = applyMiddleware(
|
1000
|
+
* createContainer(),
|
1001
|
+
* [A, B],
|
1002
|
+
* );
|
1003
|
+
* ```
|
1004
|
+
*
|
1005
|
+
* The execution order will be:
|
1006
|
+
*
|
1007
|
+
* 1. B before
|
1008
|
+
* 2. A before
|
1009
|
+
* 3. original function
|
1010
|
+
* 4. A after
|
1011
|
+
* 5. B after
|
1012
|
+
*
|
1013
|
+
* This allows outer middlewares to wrap and control the behavior of inner middlewares.
|
1014
|
+
*/ function applyMiddleware(container, middlewares) {
|
1015
|
+
const composer = {
|
1016
|
+
use (key, wrap) {
|
1017
|
+
// We need to bind the 'this' context of the function to the container
|
1018
|
+
// before passing it to the middleware wrapper.
|
1019
|
+
//
|
1020
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
|
1021
|
+
const fn = container[key].bind(container);
|
1022
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1023
|
+
container[key] = wrap(fn);
|
1024
|
+
return composer;
|
1025
|
+
}
|
1026
|
+
};
|
1027
|
+
const api = container.api ||= {
|
1028
|
+
...container
|
1029
|
+
};
|
1030
|
+
for (const middleware of middlewares){
|
1031
|
+
middleware(composer, api);
|
1032
|
+
}
|
1033
|
+
return container;
|
1034
|
+
}
|
1035
|
+
|
1036
|
+
export { AutoRegister, Build, Inject, InjectAll, Injectable, Injector, Optional, OptionalAll, Scope, Scoped, Type, applyMiddleware, createContainer, forwardRef, inject, injectAll, injectBy };
|
1037
|
+
//# sourceMappingURL=index.mjs.map
|