sandly 0.1.0 → 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/README.md +20 -17
- package/dist/index.d.ts +394 -226
- package/dist/index.js +319 -136
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,49 +1,77 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
|
|
3
|
+
//#region src/utils/object.ts
|
|
4
|
+
function hasKey(obj, key) {
|
|
5
|
+
return obj !== void 0 && obj !== null && (typeof obj === "object" || typeof obj === "function") && key in obj;
|
|
6
|
+
}
|
|
7
|
+
function getKey(obj, ...keys) {
|
|
8
|
+
let current = obj;
|
|
9
|
+
for (const key of keys) {
|
|
10
|
+
if (!hasKey(current, key)) return void 0;
|
|
11
|
+
current = current[key];
|
|
12
|
+
}
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
3
17
|
//#region src/tag.ts
|
|
4
|
-
Symbol("InjectSource");
|
|
5
18
|
/**
|
|
6
|
-
*
|
|
7
|
-
* This symbol is used as a property key to attach metadata to both value tags and
|
|
19
|
+
* Symbol used to identify tagged types within the dependency injection system.
|
|
20
|
+
* This symbol is used as a property key to attach metadata to both value tags and service tags.
|
|
21
|
+
*
|
|
22
|
+
* Note: We can't use a symbol here becuase it produced the following TS error:
|
|
23
|
+
* error TS4020: 'extends' clause of exported class 'NotificationService' has or is using private name 'TagIdKey'.
|
|
24
|
+
*
|
|
8
25
|
* @internal
|
|
9
26
|
*/
|
|
10
|
-
const
|
|
27
|
+
const ValueTagIdKey = "sandly/ValueTagIdKey";
|
|
28
|
+
const ServiceTagIdKey = "sandly/ServiceTagIdKey";
|
|
29
|
+
/**
|
|
30
|
+
* Internal string used to identify the type of a tagged type within the dependency injection system.
|
|
31
|
+
* This string is used as a property key to attach metadata to both value tags and service tags.
|
|
32
|
+
* It is used to carry the type of the tagged type and should not be used directly.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
const TagTypeKey = "sandly/TagTypeKey";
|
|
11
36
|
/**
|
|
12
37
|
* Utility object containing factory functions for creating dependency tags.
|
|
13
38
|
*
|
|
14
|
-
* The Tag object provides the primary API for creating both value tags and
|
|
39
|
+
* The Tag object provides the primary API for creating both value tags and service tags
|
|
15
40
|
* used throughout the dependency injection system. It's the main entry point for
|
|
16
41
|
* defining dependencies in a type-safe way.
|
|
17
42
|
*/
|
|
18
43
|
const Tag = {
|
|
19
44
|
of: (id) => {
|
|
20
45
|
return () => ({
|
|
21
|
-
[
|
|
22
|
-
|
|
46
|
+
[ValueTagIdKey]: id,
|
|
47
|
+
[TagTypeKey]: void 0
|
|
23
48
|
});
|
|
24
49
|
},
|
|
25
50
|
for: () => {
|
|
26
51
|
return {
|
|
27
|
-
[
|
|
28
|
-
|
|
52
|
+
[ValueTagIdKey]: Symbol(),
|
|
53
|
+
[TagTypeKey]: void 0
|
|
29
54
|
};
|
|
30
55
|
},
|
|
31
|
-
|
|
56
|
+
Service: (id) => {
|
|
32
57
|
class Tagged {
|
|
33
|
-
static [
|
|
34
|
-
[
|
|
58
|
+
static [ServiceTagIdKey] = id;
|
|
59
|
+
[ServiceTagIdKey] = id;
|
|
35
60
|
}
|
|
36
61
|
return Tagged;
|
|
37
62
|
},
|
|
38
63
|
id: (tag) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const id = tag[TagId];
|
|
44
|
-
return typeof id === "symbol" ? id.toString() : String(id);
|
|
64
|
+
return typeof tag === "function" ? tag[ServiceTagIdKey] : tag[ValueTagIdKey];
|
|
65
|
+
},
|
|
66
|
+
isTag: (tag) => {
|
|
67
|
+
return typeof tag === "function" ? getKey(tag, ServiceTagIdKey) !== void 0 : getKey(tag, ValueTagIdKey) !== void 0;
|
|
45
68
|
}
|
|
46
69
|
};
|
|
70
|
+
/**
|
|
71
|
+
* String used to store the original ValueTag in Inject<T> types.
|
|
72
|
+
* This prevents property name collisions while allowing type-level extraction.
|
|
73
|
+
*/
|
|
74
|
+
const InjectSource = "sandly/InjectSource";
|
|
47
75
|
|
|
48
76
|
//#endregion
|
|
49
77
|
//#region src/errors.ts
|
|
@@ -86,7 +114,7 @@ var BaseError = class BaseError extends Error {
|
|
|
86
114
|
* @example Catching DI errors
|
|
87
115
|
* ```typescript
|
|
88
116
|
* try {
|
|
89
|
-
* await container.
|
|
117
|
+
* await container.resolve(SomeService);
|
|
90
118
|
* } catch (error) {
|
|
91
119
|
* if (error instanceof ContainerError) {
|
|
92
120
|
* console.error('DI Error:', error.message);
|
|
@@ -107,7 +135,7 @@ var DependencyAlreadyInstantiatedError = class extends ContainerError {};
|
|
|
107
135
|
/**
|
|
108
136
|
* Error thrown when attempting to use a container that has been destroyed.
|
|
109
137
|
*
|
|
110
|
-
* This error occurs when calling `container.
|
|
138
|
+
* This error occurs when calling `container.resolve()`, `container.register()`, or `container.destroy()`
|
|
111
139
|
* on a container that has already been destroyed. It indicates a programming error where the container
|
|
112
140
|
* is being used after it has been destroyed.
|
|
113
141
|
*/
|
|
@@ -115,16 +143,16 @@ var ContainerDestroyedError = class extends ContainerError {};
|
|
|
115
143
|
/**
|
|
116
144
|
* Error thrown when attempting to retrieve a dependency that hasn't been registered.
|
|
117
145
|
*
|
|
118
|
-
* This error occurs when calling `container.
|
|
146
|
+
* This error occurs when calling `container.resolve(Tag)` for a tag that was never
|
|
119
147
|
* registered via `container.register()`. It indicates a programming error where
|
|
120
148
|
* the dependency setup is incomplete.
|
|
121
149
|
*
|
|
122
150
|
* @example
|
|
123
151
|
* ```typescript
|
|
124
|
-
* const
|
|
152
|
+
* const container = Container.empty(); // Empty container
|
|
125
153
|
*
|
|
126
154
|
* try {
|
|
127
|
-
* await c.
|
|
155
|
+
* await c.resolve(UnregisteredService); // This will throw
|
|
128
156
|
* } catch (error) {
|
|
129
157
|
* if (error instanceof UnknownDependencyError) {
|
|
130
158
|
* console.error('Missing dependency:', error.message);
|
|
@@ -140,7 +168,7 @@ var UnknownDependencyError = class extends ContainerError {
|
|
|
140
168
|
* @param tag - The dependency tag that wasn't found
|
|
141
169
|
*/
|
|
142
170
|
constructor(tag) {
|
|
143
|
-
super(`No factory registered for dependency ${Tag.id(tag)}`);
|
|
171
|
+
super(`No factory registered for dependency ${String(Tag.id(tag))}`);
|
|
144
172
|
}
|
|
145
173
|
};
|
|
146
174
|
/**
|
|
@@ -152,19 +180,19 @@ var UnknownDependencyError = class extends ContainerError {
|
|
|
152
180
|
*
|
|
153
181
|
* @example Circular dependency scenario
|
|
154
182
|
* ```typescript
|
|
155
|
-
* class ServiceA extends Tag.
|
|
156
|
-
* class ServiceB extends Tag.
|
|
183
|
+
* class ServiceA extends Tag.Service('ServiceA') {}
|
|
184
|
+
* class ServiceB extends Tag.Service('ServiceB') {}
|
|
157
185
|
*
|
|
158
|
-
* const
|
|
186
|
+
* const container = Container.empty()
|
|
159
187
|
* .register(ServiceA, async (ctx) =>
|
|
160
|
-
* new ServiceA(await ctx.
|
|
188
|
+
* new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
|
|
161
189
|
* )
|
|
162
190
|
* .register(ServiceB, async (ctx) =>
|
|
163
|
-
* new ServiceB(await ctx.
|
|
191
|
+
* new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
|
|
164
192
|
* );
|
|
165
193
|
*
|
|
166
194
|
* try {
|
|
167
|
-
* await c.
|
|
195
|
+
* await c.resolve(ServiceA);
|
|
168
196
|
* } catch (error) {
|
|
169
197
|
* if (error instanceof CircularDependencyError) {
|
|
170
198
|
* console.error('Circular dependency:', error.message);
|
|
@@ -183,7 +211,7 @@ var CircularDependencyError = class extends ContainerError {
|
|
|
183
211
|
*/
|
|
184
212
|
constructor(tag, dependencyChain) {
|
|
185
213
|
const chain = dependencyChain.map((t) => Tag.id(t)).join(" -> ");
|
|
186
|
-
super(`Circular dependency detected for ${Tag.id(tag)}: ${chain} -> ${Tag.id(tag)}`, { detail: {
|
|
214
|
+
super(`Circular dependency detected for ${String(Tag.id(tag))}: ${chain} -> ${String(Tag.id(tag))}`, { detail: {
|
|
187
215
|
tag: Tag.id(tag),
|
|
188
216
|
dependencyChain: dependencyChain.map((t) => Tag.id(t))
|
|
189
217
|
} });
|
|
@@ -197,14 +225,14 @@ var CircularDependencyError = class extends ContainerError {
|
|
|
197
225
|
*
|
|
198
226
|
* @example Factory throwing error
|
|
199
227
|
* ```typescript
|
|
200
|
-
* class DatabaseService extends Tag.
|
|
228
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {}
|
|
201
229
|
*
|
|
202
|
-
* const
|
|
230
|
+
* const container = Container.empty().register(DatabaseService, () => {
|
|
203
231
|
* throw new Error('Database connection failed');
|
|
204
232
|
* });
|
|
205
233
|
*
|
|
206
234
|
* try {
|
|
207
|
-
* await c.
|
|
235
|
+
* await c.resolve(DatabaseService);
|
|
208
236
|
* } catch (error) {
|
|
209
237
|
* if (error instanceof DependencyCreationError) {
|
|
210
238
|
* console.error('Failed to create:', error.message);
|
|
@@ -222,7 +250,7 @@ var DependencyCreationError = class extends ContainerError {
|
|
|
222
250
|
* @param error - The original error thrown by the factory function
|
|
223
251
|
*/
|
|
224
252
|
constructor(tag, error) {
|
|
225
|
-
super(`Error creating instance of ${Tag.id(tag)}`, {
|
|
253
|
+
super(`Error creating instance of ${String(Tag.id(tag))}`, {
|
|
226
254
|
cause: error,
|
|
227
255
|
detail: { tag: Tag.id(tag) }
|
|
228
256
|
});
|
|
@@ -271,6 +299,7 @@ var DependencyFinalizationError = class extends ContainerError {
|
|
|
271
299
|
* @internal
|
|
272
300
|
*/
|
|
273
301
|
const resolutionChain = new AsyncLocalStorage();
|
|
302
|
+
const ContainerTypeId = Symbol.for("sandly/Container");
|
|
274
303
|
/**
|
|
275
304
|
* A type-safe dependency injection container that manages service instantiation,
|
|
276
305
|
* caching, and lifecycle management with support for async dependencies and
|
|
@@ -282,26 +311,26 @@ const resolutionChain = new AsyncLocalStorage();
|
|
|
282
311
|
*
|
|
283
312
|
* @template TReg - Union type of all registered dependency tags in this container
|
|
284
313
|
*
|
|
285
|
-
* @example Basic usage with
|
|
314
|
+
* @example Basic usage with service tags
|
|
286
315
|
* ```typescript
|
|
287
316
|
* import { container, Tag } from 'sandly';
|
|
288
317
|
*
|
|
289
|
-
* class DatabaseService extends Tag.
|
|
318
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
290
319
|
* query() { return 'data'; }
|
|
291
320
|
* }
|
|
292
321
|
*
|
|
293
|
-
* class UserService extends Tag.
|
|
322
|
+
* class UserService extends Tag.Service('UserService') {
|
|
294
323
|
* constructor(private db: DatabaseService) {}
|
|
295
324
|
* getUser() { return this.db.query(); }
|
|
296
325
|
* }
|
|
297
326
|
*
|
|
298
|
-
* const
|
|
327
|
+
* const container = Container.empty()
|
|
299
328
|
* .register(DatabaseService, () => new DatabaseService())
|
|
300
329
|
* .register(UserService, async (ctx) =>
|
|
301
|
-
* new UserService(await ctx.
|
|
330
|
+
* new UserService(await ctx.resolve(DatabaseService))
|
|
302
331
|
* );
|
|
303
332
|
*
|
|
304
|
-
* const userService = await c.
|
|
333
|
+
* const userService = await c.resolve(UserService);
|
|
305
334
|
* ```
|
|
306
335
|
*
|
|
307
336
|
* @example Usage with value tags
|
|
@@ -309,22 +338,22 @@ const resolutionChain = new AsyncLocalStorage();
|
|
|
309
338
|
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
310
339
|
* const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
|
|
311
340
|
*
|
|
312
|
-
* const
|
|
341
|
+
* const container = Container.empty()
|
|
313
342
|
* .register(ApiKeyTag, () => process.env.API_KEY!)
|
|
314
343
|
* .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
|
|
315
344
|
*
|
|
316
|
-
* const apiKey = await c.
|
|
317
|
-
* const config = await c.
|
|
345
|
+
* const apiKey = await c.resolve(ApiKeyTag);
|
|
346
|
+
* const config = await c.resolve(ConfigTag);
|
|
318
347
|
* ```
|
|
319
348
|
*
|
|
320
349
|
* @example With finalizers for cleanup
|
|
321
350
|
* ```typescript
|
|
322
|
-
* class DatabaseConnection extends Tag.
|
|
351
|
+
* class DatabaseConnection extends Tag.Service('DatabaseConnection') {
|
|
323
352
|
* async connect() { return; }
|
|
324
353
|
* async disconnect() { return; }
|
|
325
354
|
* }
|
|
326
355
|
*
|
|
327
|
-
* const
|
|
356
|
+
* const container = Container.empty().register(
|
|
328
357
|
* DatabaseConnection,
|
|
329
358
|
* async () => {
|
|
330
359
|
* const conn = new DatabaseConnection();
|
|
@@ -339,6 +368,8 @@ const resolutionChain = new AsyncLocalStorage();
|
|
|
339
368
|
* ```
|
|
340
369
|
*/
|
|
341
370
|
var Container = class Container {
|
|
371
|
+
[ContainerTypeId];
|
|
372
|
+
constructor() {}
|
|
342
373
|
/**
|
|
343
374
|
* Cache of instantiated dependencies as promises.
|
|
344
375
|
* Ensures singleton behavior and supports concurrent access.
|
|
@@ -361,11 +392,18 @@ var Container = class Container {
|
|
|
361
392
|
*/
|
|
362
393
|
isDestroyed = false;
|
|
363
394
|
/**
|
|
395
|
+
* Creates a new empty container instance.
|
|
396
|
+
* @returns A new empty Container instance with no registered dependencies.
|
|
397
|
+
*/
|
|
398
|
+
static empty() {
|
|
399
|
+
return new Container();
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
364
402
|
* Registers a dependency in the container with a factory function and optional finalizer.
|
|
365
403
|
*
|
|
366
404
|
* The factory function receives the current container instance and must return the
|
|
367
405
|
* service instance (or a Promise of it). The container tracks the registration at
|
|
368
|
-
* the type level, ensuring type safety for subsequent `.
|
|
406
|
+
* the type level, ensuring type safety for subsequent `.resolve()` calls.
|
|
369
407
|
*
|
|
370
408
|
* If a dependency is already registered, this method will override it unless the
|
|
371
409
|
* dependency has already been instantiated, in which case it will throw an error.
|
|
@@ -380,11 +418,11 @@ var Container = class Container {
|
|
|
380
418
|
*
|
|
381
419
|
* @example Registering a simple service
|
|
382
420
|
* ```typescript
|
|
383
|
-
* class LoggerService extends Tag.
|
|
421
|
+
* class LoggerService extends Tag.Service('LoggerService') {
|
|
384
422
|
* log(message: string) { console.log(message); }
|
|
385
423
|
* }
|
|
386
424
|
*
|
|
387
|
-
* const
|
|
425
|
+
* const container = Container.empty().register(
|
|
388
426
|
* LoggerService,
|
|
389
427
|
* () => new LoggerService()
|
|
390
428
|
* );
|
|
@@ -392,24 +430,24 @@ var Container = class Container {
|
|
|
392
430
|
*
|
|
393
431
|
* @example Registering with dependencies
|
|
394
432
|
* ```typescript
|
|
395
|
-
* class UserService extends Tag.
|
|
433
|
+
* class UserService extends Tag.Service('UserService') {
|
|
396
434
|
* constructor(private db: DatabaseService, private logger: LoggerService) {}
|
|
397
435
|
* }
|
|
398
436
|
*
|
|
399
|
-
* const
|
|
437
|
+
* const container = Container.empty()
|
|
400
438
|
* .register(DatabaseService, () => new DatabaseService())
|
|
401
439
|
* .register(LoggerService, () => new LoggerService())
|
|
402
440
|
* .register(UserService, async (ctx) =>
|
|
403
441
|
* new UserService(
|
|
404
|
-
* await ctx.
|
|
405
|
-
* await ctx.
|
|
442
|
+
* await ctx.resolve(DatabaseService),
|
|
443
|
+
* await ctx.resolve(LoggerService)
|
|
406
444
|
* )
|
|
407
445
|
* );
|
|
408
446
|
* ```
|
|
409
447
|
*
|
|
410
448
|
* @example Overriding a dependency
|
|
411
449
|
* ```typescript
|
|
412
|
-
* const
|
|
450
|
+
* const container = Container.empty()
|
|
413
451
|
* .register(DatabaseService, () => new DatabaseService())
|
|
414
452
|
* .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
|
|
415
453
|
* ```
|
|
@@ -418,7 +456,7 @@ var Container = class Container {
|
|
|
418
456
|
* ```typescript
|
|
419
457
|
* const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
|
|
420
458
|
*
|
|
421
|
-
* const
|
|
459
|
+
* const container = Container.empty().register(
|
|
422
460
|
* ConfigTag,
|
|
423
461
|
* () => ({ apiUrl: 'https://api.example.com' })
|
|
424
462
|
* );
|
|
@@ -426,12 +464,12 @@ var Container = class Container {
|
|
|
426
464
|
*
|
|
427
465
|
* @example With finalizer for cleanup
|
|
428
466
|
* ```typescript
|
|
429
|
-
* class DatabaseConnection extends Tag.
|
|
467
|
+
* class DatabaseConnection extends Tag.Service('DatabaseConnection') {
|
|
430
468
|
* async connect() { return; }
|
|
431
469
|
* async close() { return; }
|
|
432
470
|
* }
|
|
433
471
|
*
|
|
434
|
-
* const
|
|
472
|
+
* const container = Container.empty().register(
|
|
435
473
|
* DatabaseConnection,
|
|
436
474
|
* async () => {
|
|
437
475
|
* const conn = new DatabaseConnection();
|
|
@@ -444,7 +482,7 @@ var Container = class Container {
|
|
|
444
482
|
*/
|
|
445
483
|
register(tag, spec) {
|
|
446
484
|
if (this.isDestroyed) throw new ContainerDestroyedError("Cannot register dependencies on a destroyed container");
|
|
447
|
-
if (this.has(tag) && this.exists(tag)) throw new DependencyAlreadyInstantiatedError(`Cannot register dependency ${Tag.id(tag)} - it has already been instantiated. Registration must happen before any instantiation occurs, as cached instances would still be used by existing dependencies.`);
|
|
485
|
+
if (this.has(tag) && this.exists(tag)) throw new DependencyAlreadyInstantiatedError(`Cannot register dependency ${String(Tag.id(tag))} - it has already been instantiated. Registration must happen before any instantiation occurs, as cached instances would still be used by existing dependencies.`);
|
|
448
486
|
if (typeof spec === "function") {
|
|
449
487
|
this.factories.set(tag, spec);
|
|
450
488
|
this.finalizers.delete(tag);
|
|
@@ -465,7 +503,7 @@ var Container = class Container {
|
|
|
465
503
|
*
|
|
466
504
|
* @example
|
|
467
505
|
* ```typescript
|
|
468
|
-
* const
|
|
506
|
+
* const container = Container.empty().register(DatabaseService, () => new DatabaseService());
|
|
469
507
|
* console.log(c.has(DatabaseService)); // true
|
|
470
508
|
* ```
|
|
471
509
|
*/
|
|
@@ -500,10 +538,10 @@ var Container = class Container {
|
|
|
500
538
|
*
|
|
501
539
|
* @example Basic usage
|
|
502
540
|
* ```typescript
|
|
503
|
-
* const
|
|
541
|
+
* const container = Container.empty()
|
|
504
542
|
* .register(DatabaseService, () => new DatabaseService());
|
|
505
543
|
*
|
|
506
|
-
* const db = await c.
|
|
544
|
+
* const db = await c.resolve(DatabaseService);
|
|
507
545
|
* db.query('SELECT * FROM users');
|
|
508
546
|
* ```
|
|
509
547
|
*
|
|
@@ -511,9 +549,9 @@ var Container = class Container {
|
|
|
511
549
|
* ```typescript
|
|
512
550
|
* // All three calls will receive the same instance
|
|
513
551
|
* const [db1, db2, db3] = await Promise.all([
|
|
514
|
-
* c.
|
|
515
|
-
* c.
|
|
516
|
-
* c.
|
|
552
|
+
* c.resolve(DatabaseService),
|
|
553
|
+
* c.resolve(DatabaseService),
|
|
554
|
+
* c.resolve(DatabaseService)
|
|
517
555
|
* ]);
|
|
518
556
|
*
|
|
519
557
|
* console.log(db1 === db2 === db3); // true
|
|
@@ -521,17 +559,17 @@ var Container = class Container {
|
|
|
521
559
|
*
|
|
522
560
|
* @example Dependency injection in factories
|
|
523
561
|
* ```typescript
|
|
524
|
-
* const
|
|
562
|
+
* const container = Container.empty()
|
|
525
563
|
* .register(DatabaseService, () => new DatabaseService())
|
|
526
564
|
* .register(UserService, async (ctx) => {
|
|
527
|
-
* const db = await ctx.
|
|
565
|
+
* const db = await ctx.resolve(DatabaseService);
|
|
528
566
|
* return new UserService(db);
|
|
529
567
|
* });
|
|
530
568
|
*
|
|
531
|
-
* const userService = await c.
|
|
569
|
+
* const userService = await c.resolve(UserService);
|
|
532
570
|
* ```
|
|
533
571
|
*/
|
|
534
|
-
async
|
|
572
|
+
async resolve(tag) {
|
|
535
573
|
if (this.isDestroyed) throw new ContainerDestroyedError("Cannot resolve dependencies from a destroyed container");
|
|
536
574
|
const cached = this.cache.get(tag);
|
|
537
575
|
if (cached !== void 0) return cached;
|
|
@@ -554,6 +592,52 @@ var Container = class Container {
|
|
|
554
592
|
return instancePromise;
|
|
555
593
|
}
|
|
556
594
|
/**
|
|
595
|
+
* Resolves multiple dependencies concurrently using Promise.all.
|
|
596
|
+
*
|
|
597
|
+
* This method takes a variable number of dependency tags and resolves all of them concurrently,
|
|
598
|
+
* returning a tuple with the resolved instances in the same order as the input tags.
|
|
599
|
+
* The method maintains all the same guarantees as the individual resolve method:
|
|
600
|
+
* singleton behavior, circular dependency detection, and proper error handling.
|
|
601
|
+
*
|
|
602
|
+
* @template T - The tuple type of dependency tags to resolve
|
|
603
|
+
* @param tags - Variable number of dependency tags to resolve
|
|
604
|
+
* @returns Promise resolving to a tuple of service instances in the same order
|
|
605
|
+
* @throws {ContainerDestroyedError} If the container has been destroyed
|
|
606
|
+
* @throws {UnknownDependencyError} If any dependency is not registered
|
|
607
|
+
* @throws {CircularDependencyError} If a circular dependency is detected
|
|
608
|
+
* @throws {DependencyCreationError} If any factory function throws an error
|
|
609
|
+
*
|
|
610
|
+
* @example Basic usage
|
|
611
|
+
* ```typescript
|
|
612
|
+
* const container = Container.empty()
|
|
613
|
+
* .register(DatabaseService, () => new DatabaseService())
|
|
614
|
+
* .register(LoggerService, () => new LoggerService());
|
|
615
|
+
*
|
|
616
|
+
* const [db, logger] = await c.resolveAll(DatabaseService, LoggerService);
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* @example Mixed tag types
|
|
620
|
+
* ```typescript
|
|
621
|
+
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
622
|
+
* const container = Container.empty()
|
|
623
|
+
* .register(ApiKeyTag, () => 'secret-key')
|
|
624
|
+
* .register(UserService, () => new UserService());
|
|
625
|
+
*
|
|
626
|
+
* const [apiKey, userService] = await c.resolveAll(ApiKeyTag, UserService);
|
|
627
|
+
* ```
|
|
628
|
+
*
|
|
629
|
+
* @example Empty array
|
|
630
|
+
* ```typescript
|
|
631
|
+
* const results = await c.resolveAll(); // Returns empty array
|
|
632
|
+
* ```
|
|
633
|
+
*/
|
|
634
|
+
async resolveAll(...tags) {
|
|
635
|
+
if (this.isDestroyed) throw new ContainerDestroyedError("Cannot resolve dependencies from a destroyed container");
|
|
636
|
+
const promises = tags.map((tag) => this.resolve(tag));
|
|
637
|
+
const results = await Promise.all(promises);
|
|
638
|
+
return results;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
557
641
|
* Copies all registrations from this container to a target container.
|
|
558
642
|
*
|
|
559
643
|
* @internal
|
|
@@ -587,10 +671,10 @@ var Container = class Container {
|
|
|
587
671
|
*
|
|
588
672
|
* @example Merging containers
|
|
589
673
|
* ```typescript
|
|
590
|
-
* const container1 =
|
|
674
|
+
* const container1 = Container.empty()
|
|
591
675
|
* .register(DatabaseService, () => new DatabaseService());
|
|
592
676
|
*
|
|
593
|
-
* const container2 =
|
|
677
|
+
* const container2 = Container.empty()
|
|
594
678
|
* .register(UserService, () => new UserService());
|
|
595
679
|
*
|
|
596
680
|
* const merged = container1.merge(container2);
|
|
@@ -625,7 +709,7 @@ var Container = class Container {
|
|
|
625
709
|
*
|
|
626
710
|
* @example Basic cleanup
|
|
627
711
|
* ```typescript
|
|
628
|
-
* const
|
|
712
|
+
* const container = Container.empty()
|
|
629
713
|
* .register(DatabaseConnection,
|
|
630
714
|
* async () => {
|
|
631
715
|
* const conn = new DatabaseConnection();
|
|
@@ -635,12 +719,12 @@ var Container = class Container {
|
|
|
635
719
|
* (conn) => conn.disconnect() // Finalizer
|
|
636
720
|
* );
|
|
637
721
|
*
|
|
638
|
-
* const db = await c.
|
|
722
|
+
* const db = await c.resolve(DatabaseConnection);
|
|
639
723
|
* await c.destroy(); // Calls conn.disconnect(), container becomes unusable
|
|
640
724
|
*
|
|
641
725
|
* // This will throw an error
|
|
642
726
|
* try {
|
|
643
|
-
* await c.
|
|
727
|
+
* await c.resolve(DatabaseConnection);
|
|
644
728
|
* } catch (error) {
|
|
645
729
|
* console.log(error.message); // "Cannot resolve dependencies from a destroyed container"
|
|
646
730
|
* }
|
|
@@ -648,9 +732,9 @@ var Container = class Container {
|
|
|
648
732
|
*
|
|
649
733
|
* @example Application shutdown
|
|
650
734
|
* ```typescript
|
|
651
|
-
* const appContainer
|
|
735
|
+
* const appContainer Container.empty
|
|
652
736
|
* .register(DatabaseService, () => new DatabaseService())
|
|
653
|
-
* .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.
|
|
737
|
+
* .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.resolve(DatabaseService)));
|
|
654
738
|
*
|
|
655
739
|
* // During application shutdown
|
|
656
740
|
* process.on('SIGTERM', async () => {
|
|
@@ -691,38 +775,14 @@ var Container = class Container {
|
|
|
691
775
|
}
|
|
692
776
|
}
|
|
693
777
|
};
|
|
694
|
-
/**
|
|
695
|
-
* Creates a new empty dependency injection container.
|
|
696
|
-
*
|
|
697
|
-
* This is a convenience factory function that creates a new DependencyContainer instance.
|
|
698
|
-
* The returned container starts with no registered dependencies and the type parameter
|
|
699
|
-
* defaults to `never`, indicating no dependencies are available for retrieval yet.
|
|
700
|
-
*
|
|
701
|
-
* @returns A new empty DependencyContainer instance
|
|
702
|
-
*
|
|
703
|
-
* @example
|
|
704
|
-
* ```typescript
|
|
705
|
-
* import { container, Tag } from 'sandly';
|
|
706
|
-
*
|
|
707
|
-
* class DatabaseService extends Tag.Class('DatabaseService') {}
|
|
708
|
-
* class UserService extends Tag.Class('UserService') {}
|
|
709
|
-
*
|
|
710
|
-
* const c = container()
|
|
711
|
-
* .register(DatabaseService, () => new DatabaseService())
|
|
712
|
-
* .register(UserService, async (ctx) =>
|
|
713
|
-
* new UserService(await ctx.get(DatabaseService))
|
|
714
|
-
* );
|
|
715
|
-
*
|
|
716
|
-
* const userService = await c.get(UserService);
|
|
717
|
-
* ```
|
|
718
|
-
*/
|
|
719
|
-
function container() {
|
|
720
|
-
return new Container();
|
|
721
|
-
}
|
|
722
778
|
|
|
723
779
|
//#endregion
|
|
724
780
|
//#region src/layer.ts
|
|
725
781
|
/**
|
|
782
|
+
* The type ID for the Layer interface.
|
|
783
|
+
*/
|
|
784
|
+
const LayerTypeId = Symbol.for("sandly/Layer");
|
|
785
|
+
/**
|
|
726
786
|
* Creates a new dependency layer that encapsulates a set of dependency registrations.
|
|
727
787
|
* Layers are the primary building blocks for organizing and composing dependency injection setups.
|
|
728
788
|
*
|
|
@@ -736,7 +796,7 @@ function container() {
|
|
|
736
796
|
* ```typescript
|
|
737
797
|
* import { layer, Tag } from 'sandly';
|
|
738
798
|
*
|
|
739
|
-
* class DatabaseService extends Tag.
|
|
799
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
740
800
|
* constructor(private url: string = 'sqlite://memory') {}
|
|
741
801
|
* query() { return 'data'; }
|
|
742
802
|
* }
|
|
@@ -761,15 +821,15 @@ function container() {
|
|
|
761
821
|
* const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
|
|
762
822
|
* (container) =>
|
|
763
823
|
* container
|
|
764
|
-
* .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.
|
|
765
|
-
* .register(CacheService, async (ctx) => new CacheService(await ctx.
|
|
824
|
+
* .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
|
|
825
|
+
* .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
|
|
766
826
|
* );
|
|
767
827
|
*
|
|
768
828
|
* // Service layer (requires infrastructure)
|
|
769
829
|
* const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
|
|
770
830
|
* (container) =>
|
|
771
831
|
* container.register(UserService, async (ctx) =>
|
|
772
|
-
* new UserService(await ctx.
|
|
832
|
+
* new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
|
|
773
833
|
* )
|
|
774
834
|
* );
|
|
775
835
|
*
|
|
@@ -779,7 +839,7 @@ function container() {
|
|
|
779
839
|
*/
|
|
780
840
|
function layer(register) {
|
|
781
841
|
const layerImpl = {
|
|
782
|
-
register: (container
|
|
842
|
+
register: (container) => register(container),
|
|
783
843
|
provide(dependency) {
|
|
784
844
|
return createProvidedLayer(dependency, layerImpl);
|
|
785
845
|
},
|
|
@@ -808,8 +868,8 @@ function createProvidedLayer(dependency, target) {
|
|
|
808
868
|
* @internal
|
|
809
869
|
*/
|
|
810
870
|
function createComposedLayer(dependency, target) {
|
|
811
|
-
return layer((container
|
|
812
|
-
const containerWithDependency = dependency.register(container
|
|
871
|
+
return layer((container) => {
|
|
872
|
+
const containerWithDependency = dependency.register(container);
|
|
813
873
|
return target.register(containerWithDependency);
|
|
814
874
|
});
|
|
815
875
|
}
|
|
@@ -820,8 +880,8 @@ function createComposedLayer(dependency, target) {
|
|
|
820
880
|
* @internal
|
|
821
881
|
*/
|
|
822
882
|
function createMergedLayer(layer1, layer2) {
|
|
823
|
-
return layer((container
|
|
824
|
-
const container1 = layer1.register(container
|
|
883
|
+
return layer((container) => {
|
|
884
|
+
const container1 = layer1.register(container);
|
|
825
885
|
const container2 = layer2.register(container1);
|
|
826
886
|
return container2;
|
|
827
887
|
});
|
|
@@ -831,7 +891,7 @@ function createMergedLayer(layer1, layer2) {
|
|
|
831
891
|
*/
|
|
832
892
|
const Layer = {
|
|
833
893
|
empty() {
|
|
834
|
-
return layer((container
|
|
894
|
+
return layer((container) => container);
|
|
835
895
|
},
|
|
836
896
|
mergeAll(...layers) {
|
|
837
897
|
return layers.reduce((acc, layer$1) => acc.merge(layer$1));
|
|
@@ -853,6 +913,14 @@ var ScopedContainer = class ScopedContainer extends Container {
|
|
|
853
913
|
this.scope = scope;
|
|
854
914
|
}
|
|
855
915
|
/**
|
|
916
|
+
* Creates a new empty scoped container instance.
|
|
917
|
+
* @param scope - The scope identifier for this container
|
|
918
|
+
* @returns A new empty ScopedContainer instance with no registered dependencies
|
|
919
|
+
*/
|
|
920
|
+
static empty(scope) {
|
|
921
|
+
return new ScopedContainer(null, scope);
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
856
924
|
* Registers a dependency in the scoped container.
|
|
857
925
|
*
|
|
858
926
|
* Overrides the base implementation to return ScopedContainer type
|
|
@@ -891,9 +959,9 @@ var ScopedContainer = class ScopedContainer extends Container {
|
|
|
891
959
|
* 3. Otherwise, delegate to parent scope
|
|
892
960
|
* 4. If no parent or parent doesn't have it, throw UnknownDependencyError
|
|
893
961
|
*/
|
|
894
|
-
async
|
|
895
|
-
if (this.factories.has(tag)) return super.
|
|
896
|
-
if (this.parent !== null) return this.parent.
|
|
962
|
+
async resolve(tag) {
|
|
963
|
+
if (this.factories.has(tag)) return super.resolve(tag);
|
|
964
|
+
if (this.parent !== null) return this.parent.resolve(tag);
|
|
897
965
|
throw new UnknownDependencyError(tag);
|
|
898
966
|
}
|
|
899
967
|
/**
|
|
@@ -973,7 +1041,7 @@ var ScopedContainer = class ScopedContainer extends Container {
|
|
|
973
1041
|
* ```typescript
|
|
974
1042
|
* import { container, scoped } from 'sandly';
|
|
975
1043
|
*
|
|
976
|
-
* const appContainer =
|
|
1044
|
+
* const appContainer = Container.empty()
|
|
977
1045
|
* .register(DatabaseService, () => new DatabaseService())
|
|
978
1046
|
* .register(ConfigService, () => new ConfigService());
|
|
979
1047
|
*
|
|
@@ -985,10 +1053,10 @@ var ScopedContainer = class ScopedContainer extends Container {
|
|
|
985
1053
|
*
|
|
986
1054
|
* @example Copying complex registrations
|
|
987
1055
|
* ```typescript
|
|
988
|
-
* const baseContainer =
|
|
1056
|
+
* const baseContainer = Container.empty()
|
|
989
1057
|
* .register(DatabaseService, () => new DatabaseService())
|
|
990
1058
|
* .register(UserService, {
|
|
991
|
-
* factory: async (ctx) => new UserService(await ctx.
|
|
1059
|
+
* factory: async (ctx) => new UserService(await ctx.resolve(DatabaseService)),
|
|
992
1060
|
* finalizer: (service) => service.cleanup()
|
|
993
1061
|
* });
|
|
994
1062
|
*
|
|
@@ -996,32 +1064,31 @@ var ScopedContainer = class ScopedContainer extends Container {
|
|
|
996
1064
|
* // scopedContainer now has all the same registrations with finalizers preserved
|
|
997
1065
|
* ```
|
|
998
1066
|
*/
|
|
999
|
-
function scoped(container
|
|
1000
|
-
const emptyScoped =
|
|
1001
|
-
|
|
1002
|
-
return result;
|
|
1067
|
+
function scoped(container, scope) {
|
|
1068
|
+
const emptyScoped = ScopedContainer.empty(scope);
|
|
1069
|
+
return emptyScoped.merge(container);
|
|
1003
1070
|
}
|
|
1004
1071
|
|
|
1005
1072
|
//#endregion
|
|
1006
1073
|
//#region src/service.ts
|
|
1007
1074
|
/**
|
|
1008
|
-
* Creates a service layer from any tag type (
|
|
1075
|
+
* Creates a service layer from any tag type (ServiceTag or ValueTag) with optional parameters.
|
|
1009
1076
|
*
|
|
1010
|
-
* For
|
|
1077
|
+
* For ServiceTag services:
|
|
1011
1078
|
* - Dependencies are automatically inferred from constructor parameters
|
|
1012
1079
|
* - The factory function must handle dependency injection by resolving dependencies from the container
|
|
1013
1080
|
*
|
|
1014
1081
|
* For ValueTag services:
|
|
1015
1082
|
* - No constructor dependencies are needed since they don't have constructors
|
|
1016
1083
|
*
|
|
1017
|
-
* @template T - The tag representing the service (
|
|
1018
|
-
* @param
|
|
1084
|
+
* @template T - The tag representing the service (ServiceTag or ValueTag)
|
|
1085
|
+
* @param tag - The tag (ServiceTag or ValueTag)
|
|
1019
1086
|
* @param factory - Factory function for service instantiation with container
|
|
1020
1087
|
* @returns The service layer
|
|
1021
1088
|
*
|
|
1022
1089
|
* @example Simple service without dependencies
|
|
1023
1090
|
* ```typescript
|
|
1024
|
-
* class LoggerService extends Tag.
|
|
1091
|
+
* class LoggerService extends Tag.Service('LoggerService') {
|
|
1025
1092
|
* log(message: string) { console.log(message); }
|
|
1026
1093
|
* }
|
|
1027
1094
|
*
|
|
@@ -1030,11 +1097,11 @@ function scoped(container$1, scope) {
|
|
|
1030
1097
|
*
|
|
1031
1098
|
* @example Service with dependencies
|
|
1032
1099
|
* ```typescript
|
|
1033
|
-
* class DatabaseService extends Tag.
|
|
1100
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1034
1101
|
* query() { return []; }
|
|
1035
1102
|
* }
|
|
1036
1103
|
*
|
|
1037
|
-
* class UserService extends Tag.
|
|
1104
|
+
* class UserService extends Tag.Service('UserService') {
|
|
1038
1105
|
* constructor(private db: DatabaseService) {
|
|
1039
1106
|
* super();
|
|
1040
1107
|
* }
|
|
@@ -1043,15 +1110,131 @@ function scoped(container$1, scope) {
|
|
|
1043
1110
|
* }
|
|
1044
1111
|
*
|
|
1045
1112
|
* const userService = service(UserService, async (ctx) =>
|
|
1046
|
-
* new UserService(await ctx.
|
|
1113
|
+
* new UserService(await ctx.resolve(DatabaseService))
|
|
1047
1114
|
* );
|
|
1048
1115
|
* ```
|
|
1049
1116
|
*/
|
|
1050
|
-
function service(
|
|
1051
|
-
return layer((container
|
|
1052
|
-
return container
|
|
1117
|
+
function service(tag, spec) {
|
|
1118
|
+
return layer((container) => {
|
|
1119
|
+
return container.register(tag, spec);
|
|
1053
1120
|
});
|
|
1054
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates a service layer with automatic dependency injection by inferring constructor parameters.
|
|
1124
|
+
*
|
|
1125
|
+
* This is a convenience function that automatically resolves constructor dependencies and passes
|
|
1126
|
+
* both DI-managed dependencies and static values to the service constructor in the correct order.
|
|
1127
|
+
* It eliminates the need to manually write factory functions for services with constructor dependencies.
|
|
1128
|
+
*
|
|
1129
|
+
* @template T - The ServiceTag representing the service class
|
|
1130
|
+
* @param tag - The service tag (must be a ServiceTag, not a ValueTag)
|
|
1131
|
+
* @param deps - Tuple of constructor parameters in order - mix of dependency tags and static values
|
|
1132
|
+
* @param finalizer - Optional cleanup function called when the container is destroyed
|
|
1133
|
+
* @returns A service layer that automatically handles dependency injection
|
|
1134
|
+
*
|
|
1135
|
+
* @example Simple service with dependencies
|
|
1136
|
+
* ```typescript
|
|
1137
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1138
|
+
* constructor(private url: string) {
|
|
1139
|
+
* super();
|
|
1140
|
+
* }
|
|
1141
|
+
* connect() { return `Connected to ${this.url}`; }
|
|
1142
|
+
* }
|
|
1143
|
+
*
|
|
1144
|
+
* class UserService extends Tag.Service('UserService') {
|
|
1145
|
+
* constructor(private db: DatabaseService, private timeout: number) {
|
|
1146
|
+
* super();
|
|
1147
|
+
* }
|
|
1148
|
+
* getUsers() { return this.db.query('SELECT * FROM users'); }
|
|
1149
|
+
* }
|
|
1150
|
+
*
|
|
1151
|
+
* // Automatically inject DatabaseService and pass static timeout value
|
|
1152
|
+
* const userService = autoService(UserService, [DatabaseService, 5000]);
|
|
1153
|
+
* ```
|
|
1154
|
+
*
|
|
1155
|
+
* @example Mixed dependencies and static values
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* class NotificationService extends Tag.Service('NotificationService') {
|
|
1158
|
+
* constructor(
|
|
1159
|
+
* private logger: LoggerService,
|
|
1160
|
+
* private apiKey: string,
|
|
1161
|
+
* private retries: number,
|
|
1162
|
+
* private cache: CacheService
|
|
1163
|
+
* ) {
|
|
1164
|
+
* super();
|
|
1165
|
+
* }
|
|
1166
|
+
* }
|
|
1167
|
+
*
|
|
1168
|
+
* // Mix of DI tags and static values in constructor order
|
|
1169
|
+
* const notificationService = autoService(NotificationService, [
|
|
1170
|
+
* LoggerService, // Will be resolved from container
|
|
1171
|
+
* 'secret-api-key', // Static string value
|
|
1172
|
+
* 3, // Static number value
|
|
1173
|
+
* CacheService // Will be resolved from container
|
|
1174
|
+
* ]);
|
|
1175
|
+
* ```
|
|
1176
|
+
*
|
|
1177
|
+
* @example Compared to manual service creation
|
|
1178
|
+
* ```typescript
|
|
1179
|
+
* // Manual approach (more verbose)
|
|
1180
|
+
* const userServiceManual = service(UserService, async (ctx) => {
|
|
1181
|
+
* const db = await ctx.resolve(DatabaseService);
|
|
1182
|
+
* return new UserService(db, 5000);
|
|
1183
|
+
* });
|
|
1184
|
+
*
|
|
1185
|
+
* // Auto approach (concise)
|
|
1186
|
+
* const userServiceAuto = autoService(UserService, [DatabaseService, 5000]);
|
|
1187
|
+
* ```
|
|
1188
|
+
*
|
|
1189
|
+
* @example With finalizer for cleanup
|
|
1190
|
+
* ```typescript
|
|
1191
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1192
|
+
* constructor(private connectionString: string) {
|
|
1193
|
+
* super();
|
|
1194
|
+
* }
|
|
1195
|
+
*
|
|
1196
|
+
* private connection: Connection | null = null;
|
|
1197
|
+
*
|
|
1198
|
+
* async connect() {
|
|
1199
|
+
* this.connection = await createConnection(this.connectionString);
|
|
1200
|
+
* }
|
|
1201
|
+
*
|
|
1202
|
+
* async disconnect() {
|
|
1203
|
+
* if (this.connection) {
|
|
1204
|
+
* await this.connection.close();
|
|
1205
|
+
* this.connection = null;
|
|
1206
|
+
* }
|
|
1207
|
+
* }
|
|
1208
|
+
* }
|
|
1209
|
+
*
|
|
1210
|
+
* // Service with automatic cleanup
|
|
1211
|
+
* const dbService = autoService(
|
|
1212
|
+
* DatabaseService,
|
|
1213
|
+
* {
|
|
1214
|
+
* dependencies: ['postgresql://localhost:5432/mydb'],
|
|
1215
|
+
* finalizer: (service) => service.disconnect() // Finalizer for cleanup
|
|
1216
|
+
* }
|
|
1217
|
+
* );
|
|
1218
|
+
* ```
|
|
1219
|
+
*/
|
|
1220
|
+
function autoService(tag, spec) {
|
|
1221
|
+
if (Array.isArray(spec)) spec = { dependencies: spec };
|
|
1222
|
+
const factory = async (ctx) => {
|
|
1223
|
+
const diDeps = [];
|
|
1224
|
+
for (const dep of spec.dependencies) if (Tag.isTag(dep)) diDeps.push(dep);
|
|
1225
|
+
const resolved = await ctx.resolveAll(...diDeps);
|
|
1226
|
+
const args = [];
|
|
1227
|
+
let resolvedIndex = 0;
|
|
1228
|
+
for (const dep of spec.dependencies) if (Tag.isTag(dep)) args.push(resolved[resolvedIndex++]);
|
|
1229
|
+
else args.push(dep);
|
|
1230
|
+
return new tag(...args);
|
|
1231
|
+
};
|
|
1232
|
+
const finalSpec = spec.finalizer ? {
|
|
1233
|
+
factory,
|
|
1234
|
+
finalizer: spec.finalizer
|
|
1235
|
+
} : factory;
|
|
1236
|
+
return service(tag, finalSpec);
|
|
1237
|
+
}
|
|
1055
1238
|
|
|
1056
1239
|
//#endregion
|
|
1057
1240
|
//#region src/value.ts
|
|
@@ -1074,8 +1257,8 @@ function service(serviceClass, spec) {
|
|
|
1074
1257
|
* ```
|
|
1075
1258
|
*/
|
|
1076
1259
|
function value(tag, constantValue) {
|
|
1077
|
-
return layer((container
|
|
1260
|
+
return layer((container) => container.register(tag, () => constantValue));
|
|
1078
1261
|
}
|
|
1079
1262
|
|
|
1080
1263
|
//#endregion
|
|
1081
|
-
export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, Layer, ScopedContainer, Tag, UnknownDependencyError,
|
|
1264
|
+
export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, ScopedContainer, Tag, UnknownDependencyError, autoService, layer, scoped, service, value };
|