sandly 0.1.0 → 0.2.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.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
- * Internal symbol used to identify tagged types within the dependency injection system.
7
- * This symbol is used as a property key to attach metadata to both value tags and class tags.
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
+ *
25
+ * @internal
26
+ */
27
+ const ValueTagIdKey = "__sandly/ValueTagIdKey__";
28
+ const ServiceTagIdKey = "__sandly/ServiceTagIdKey__";
29
+ /**
30
+ * Internal symbol used to identify the type of a tagged type within the dependency injection system.
31
+ * This symbol 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.
8
33
  * @internal
9
34
  */
10
- const TagId = "__tag_id__";
35
+ const TagTypeKey = Symbol.for("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 class tags
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
- [TagId]: id,
22
- __type: void 0
46
+ [ValueTagIdKey]: id,
47
+ [TagTypeKey]: void 0
23
48
  });
24
49
  },
25
50
  for: () => {
26
51
  return {
27
- [TagId]: Symbol(),
28
- __type: void 0
52
+ [ValueTagIdKey]: Symbol(),
53
+ [TagTypeKey]: void 0
29
54
  };
30
55
  },
31
- Class: (id) => {
56
+ Service: (id) => {
32
57
  class Tagged {
33
- static [TagId] = id;
34
- [TagId] = id;
58
+ static [ServiceTagIdKey] = id;
59
+ [ServiceTagIdKey] = id;
35
60
  }
36
61
  return Tagged;
37
62
  },
38
63
  id: (tag) => {
39
- if (typeof tag === "function") {
40
- const id$1 = tag[TagId];
41
- return typeof id$1 === "symbol" ? id$1.toString() : String(id$1);
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
+ * Unique symbol 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 = Symbol("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.get(SomeService);
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.get()`, `container.register()`, or `container.destroy()`
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.get(Tag)` for a tag that was never
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 c = container(); // Empty container
152
+ * const c = Container.empty(); // Empty container
125
153
  *
126
154
  * try {
127
- * await c.get(UnregisteredService); // This will throw
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.Class('ServiceA') {}
156
- * class ServiceB extends Tag.Class('ServiceB') {}
183
+ * class ServiceA extends Tag.Service('ServiceA') {}
184
+ * class ServiceB extends Tag.Service('ServiceB') {}
157
185
  *
158
- * const c = container()
186
+ * const c = Container.empty()
159
187
  * .register(ServiceA, async (ctx) =>
160
- * new ServiceA(await ctx.get(ServiceB)) // Depends on B
188
+ * new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
161
189
  * )
162
190
  * .register(ServiceB, async (ctx) =>
163
- * new ServiceB(await ctx.get(ServiceA)) // Depends on A - CIRCULAR!
191
+ * new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
164
192
  * );
165
193
  *
166
194
  * try {
167
- * await c.get(ServiceA);
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.Class('DatabaseService') {}
228
+ * class DatabaseService extends Tag.Service('DatabaseService') {}
201
229
  *
202
- * const c = container().register(DatabaseService, () => {
230
+ * const c = Container.empty().register(DatabaseService, () => {
203
231
  * throw new Error('Database connection failed');
204
232
  * });
205
233
  *
206
234
  * try {
207
- * await c.get(DatabaseService);
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 class tags
314
+ * @example Basic usage with service tags
286
315
  * ```typescript
287
316
  * import { container, Tag } from 'sandly';
288
317
  *
289
- * class DatabaseService extends Tag.Class('DatabaseService') {
318
+ * class DatabaseService extends Tag.Service('DatabaseService') {
290
319
  * query() { return 'data'; }
291
320
  * }
292
321
  *
293
- * class UserService extends Tag.Class('UserService') {
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 c = container()
327
+ * const c = Container.empty()
299
328
  * .register(DatabaseService, () => new DatabaseService())
300
329
  * .register(UserService, async (ctx) =>
301
- * new UserService(await ctx.get(DatabaseService))
330
+ * new UserService(await ctx.resolve(DatabaseService))
302
331
  * );
303
332
  *
304
- * const userService = await c.get(UserService);
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 c = container()
341
+ * const c = 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.get(ApiKeyTag);
317
- * const config = await c.get(ConfigTag);
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.Class('DatabaseConnection') {
351
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
323
352
  * async connect() { return; }
324
353
  * async disconnect() { return; }
325
354
  * }
326
355
  *
327
- * const c = container().register(
356
+ * const c = Container.empty().register(
328
357
  * DatabaseConnection,
329
358
  * async () => {
330
359
  * const conn = new DatabaseConnection();
@@ -339,6 +368,7 @@ const resolutionChain = new AsyncLocalStorage();
339
368
  * ```
340
369
  */
341
370
  var Container = class Container {
371
+ [ContainerTypeId];
342
372
  /**
343
373
  * Cache of instantiated dependencies as promises.
344
374
  * Ensures singleton behavior and supports concurrent access.
@@ -360,12 +390,15 @@ var Container = class Container {
360
390
  * @internal
361
391
  */
362
392
  isDestroyed = false;
393
+ static empty() {
394
+ return new Container();
395
+ }
363
396
  /**
364
397
  * Registers a dependency in the container with a factory function and optional finalizer.
365
398
  *
366
399
  * The factory function receives the current container instance and must return the
367
400
  * service instance (or a Promise of it). The container tracks the registration at
368
- * the type level, ensuring type safety for subsequent `.get()` calls.
401
+ * the type level, ensuring type safety for subsequent `.resolve()` calls.
369
402
  *
370
403
  * If a dependency is already registered, this method will override it unless the
371
404
  * dependency has already been instantiated, in which case it will throw an error.
@@ -380,11 +413,11 @@ var Container = class Container {
380
413
  *
381
414
  * @example Registering a simple service
382
415
  * ```typescript
383
- * class LoggerService extends Tag.Class('LoggerService') {
416
+ * class LoggerService extends Tag.Service('LoggerService') {
384
417
  * log(message: string) { console.log(message); }
385
418
  * }
386
419
  *
387
- * const c = container().register(
420
+ * const c = Container.empty().register(
388
421
  * LoggerService,
389
422
  * () => new LoggerService()
390
423
  * );
@@ -392,24 +425,24 @@ var Container = class Container {
392
425
  *
393
426
  * @example Registering with dependencies
394
427
  * ```typescript
395
- * class UserService extends Tag.Class('UserService') {
428
+ * class UserService extends Tag.Service('UserService') {
396
429
  * constructor(private db: DatabaseService, private logger: LoggerService) {}
397
430
  * }
398
431
  *
399
- * const c = container()
432
+ * const c = Container.empty()
400
433
  * .register(DatabaseService, () => new DatabaseService())
401
434
  * .register(LoggerService, () => new LoggerService())
402
435
  * .register(UserService, async (ctx) =>
403
436
  * new UserService(
404
- * await ctx.get(DatabaseService),
405
- * await ctx.get(LoggerService)
437
+ * await ctx.resolve(DatabaseService),
438
+ * await ctx.resolve(LoggerService)
406
439
  * )
407
440
  * );
408
441
  * ```
409
442
  *
410
443
  * @example Overriding a dependency
411
444
  * ```typescript
412
- * const c = container()
445
+ * const c = Container.empty()
413
446
  * .register(DatabaseService, () => new DatabaseService())
414
447
  * .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
415
448
  * ```
@@ -418,7 +451,7 @@ var Container = class Container {
418
451
  * ```typescript
419
452
  * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
420
453
  *
421
- * const c = container().register(
454
+ * const c = Container.empty().register(
422
455
  * ConfigTag,
423
456
  * () => ({ apiUrl: 'https://api.example.com' })
424
457
  * );
@@ -426,12 +459,12 @@ var Container = class Container {
426
459
  *
427
460
  * @example With finalizer for cleanup
428
461
  * ```typescript
429
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
462
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
430
463
  * async connect() { return; }
431
464
  * async close() { return; }
432
465
  * }
433
466
  *
434
- * const c = container().register(
467
+ * const c = Container.empty().register(
435
468
  * DatabaseConnection,
436
469
  * async () => {
437
470
  * const conn = new DatabaseConnection();
@@ -444,7 +477,7 @@ var Container = class Container {
444
477
  */
445
478
  register(tag, spec) {
446
479
  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.`);
480
+ 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
481
  if (typeof spec === "function") {
449
482
  this.factories.set(tag, spec);
450
483
  this.finalizers.delete(tag);
@@ -465,7 +498,7 @@ var Container = class Container {
465
498
  *
466
499
  * @example
467
500
  * ```typescript
468
- * const c = container().register(DatabaseService, () => new DatabaseService());
501
+ * const c = Container.empty().register(DatabaseService, () => new DatabaseService());
469
502
  * console.log(c.has(DatabaseService)); // true
470
503
  * ```
471
504
  */
@@ -500,10 +533,10 @@ var Container = class Container {
500
533
  *
501
534
  * @example Basic usage
502
535
  * ```typescript
503
- * const c = container()
536
+ * const c = Container.empty()
504
537
  * .register(DatabaseService, () => new DatabaseService());
505
538
  *
506
- * const db = await c.get(DatabaseService);
539
+ * const db = await c.resolve(DatabaseService);
507
540
  * db.query('SELECT * FROM users');
508
541
  * ```
509
542
  *
@@ -511,9 +544,9 @@ var Container = class Container {
511
544
  * ```typescript
512
545
  * // All three calls will receive the same instance
513
546
  * const [db1, db2, db3] = await Promise.all([
514
- * c.get(DatabaseService),
515
- * c.get(DatabaseService),
516
- * c.get(DatabaseService)
547
+ * c.resolve(DatabaseService),
548
+ * c.resolve(DatabaseService),
549
+ * c.resolve(DatabaseService)
517
550
  * ]);
518
551
  *
519
552
  * console.log(db1 === db2 === db3); // true
@@ -521,17 +554,17 @@ var Container = class Container {
521
554
  *
522
555
  * @example Dependency injection in factories
523
556
  * ```typescript
524
- * const c = container()
557
+ * const c = Container.empty()
525
558
  * .register(DatabaseService, () => new DatabaseService())
526
559
  * .register(UserService, async (ctx) => {
527
- * const db = await ctx.get(DatabaseService);
560
+ * const db = await ctx.resolve(DatabaseService);
528
561
  * return new UserService(db);
529
562
  * });
530
563
  *
531
- * const userService = await c.get(UserService);
564
+ * const userService = await c.resolve(UserService);
532
565
  * ```
533
566
  */
534
- async get(tag) {
567
+ async resolve(tag) {
535
568
  if (this.isDestroyed) throw new ContainerDestroyedError("Cannot resolve dependencies from a destroyed container");
536
569
  const cached = this.cache.get(tag);
537
570
  if (cached !== void 0) return cached;
@@ -554,6 +587,52 @@ var Container = class Container {
554
587
  return instancePromise;
555
588
  }
556
589
  /**
590
+ * Resolves multiple dependencies concurrently using Promise.all.
591
+ *
592
+ * This method takes a variable number of dependency tags and resolves all of them concurrently,
593
+ * returning a tuple with the resolved instances in the same order as the input tags.
594
+ * The method maintains all the same guarantees as the individual resolve method:
595
+ * singleton behavior, circular dependency detection, and proper error handling.
596
+ *
597
+ * @template T - The tuple type of dependency tags to resolve
598
+ * @param tags - Variable number of dependency tags to resolve
599
+ * @returns Promise resolving to a tuple of service instances in the same order
600
+ * @throws {ContainerDestroyedError} If the container has been destroyed
601
+ * @throws {UnknownDependencyError} If any dependency is not registered
602
+ * @throws {CircularDependencyError} If a circular dependency is detected
603
+ * @throws {DependencyCreationError} If any factory function throws an error
604
+ *
605
+ * @example Basic usage
606
+ * ```typescript
607
+ * const c = Container.empty()
608
+ * .register(DatabaseService, () => new DatabaseService())
609
+ * .register(LoggerService, () => new LoggerService());
610
+ *
611
+ * const [db, logger] = await c.resolveAll(DatabaseService, LoggerService);
612
+ * ```
613
+ *
614
+ * @example Mixed tag types
615
+ * ```typescript
616
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
617
+ * const c = Container.empty()
618
+ * .register(ApiKeyTag, () => 'secret-key')
619
+ * .register(UserService, () => new UserService());
620
+ *
621
+ * const [apiKey, userService] = await c.resolveAll(ApiKeyTag, UserService);
622
+ * ```
623
+ *
624
+ * @example Empty array
625
+ * ```typescript
626
+ * const results = await c.resolveAll(); // Returns empty array
627
+ * ```
628
+ */
629
+ async resolveAll(...tags) {
630
+ if (this.isDestroyed) throw new ContainerDestroyedError("Cannot resolve dependencies from a destroyed container");
631
+ const promises = tags.map((tag) => this.resolve(tag));
632
+ const results = await Promise.all(promises);
633
+ return results;
634
+ }
635
+ /**
557
636
  * Copies all registrations from this container to a target container.
558
637
  *
559
638
  * @internal
@@ -587,10 +666,10 @@ var Container = class Container {
587
666
  *
588
667
  * @example Merging containers
589
668
  * ```typescript
590
- * const container1 = container()
669
+ * const container1 = Container.empty()
591
670
  * .register(DatabaseService, () => new DatabaseService());
592
671
  *
593
- * const container2 = container()
672
+ * const container2 = Container.empty()
594
673
  * .register(UserService, () => new UserService());
595
674
  *
596
675
  * const merged = container1.merge(container2);
@@ -625,7 +704,7 @@ var Container = class Container {
625
704
  *
626
705
  * @example Basic cleanup
627
706
  * ```typescript
628
- * const c = container()
707
+ * const c = Container.empty()
629
708
  * .register(DatabaseConnection,
630
709
  * async () => {
631
710
  * const conn = new DatabaseConnection();
@@ -635,12 +714,12 @@ var Container = class Container {
635
714
  * (conn) => conn.disconnect() // Finalizer
636
715
  * );
637
716
  *
638
- * const db = await c.get(DatabaseConnection);
717
+ * const db = await c.resolve(DatabaseConnection);
639
718
  * await c.destroy(); // Calls conn.disconnect(), container becomes unusable
640
719
  *
641
720
  * // This will throw an error
642
721
  * try {
643
- * await c.get(DatabaseConnection);
722
+ * await c.resolve(DatabaseConnection);
644
723
  * } catch (error) {
645
724
  * console.log(error.message); // "Cannot resolve dependencies from a destroyed container"
646
725
  * }
@@ -648,9 +727,9 @@ var Container = class Container {
648
727
  *
649
728
  * @example Application shutdown
650
729
  * ```typescript
651
- * const appContainer = container()
730
+ * const appContainer Container.empty
652
731
  * .register(DatabaseService, () => new DatabaseService())
653
- * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.get(DatabaseService)));
732
+ * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.resolve(DatabaseService)));
654
733
  *
655
734
  * // During application shutdown
656
735
  * process.on('SIGTERM', async () => {
@@ -691,38 +770,14 @@ var Container = class Container {
691
770
  }
692
771
  }
693
772
  };
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
773
 
723
774
  //#endregion
724
775
  //#region src/layer.ts
725
776
  /**
777
+ * The type ID for the Layer interface.
778
+ */
779
+ const LayerTypeId = Symbol.for("sandly/Layer");
780
+ /**
726
781
  * Creates a new dependency layer that encapsulates a set of dependency registrations.
727
782
  * Layers are the primary building blocks for organizing and composing dependency injection setups.
728
783
  *
@@ -736,7 +791,7 @@ function container() {
736
791
  * ```typescript
737
792
  * import { layer, Tag } from 'sandly';
738
793
  *
739
- * class DatabaseService extends Tag.Class('DatabaseService') {
794
+ * class DatabaseService extends Tag.Service('DatabaseService') {
740
795
  * constructor(private url: string = 'sqlite://memory') {}
741
796
  * query() { return 'data'; }
742
797
  * }
@@ -761,15 +816,15 @@ function container() {
761
816
  * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
762
817
  * (container) =>
763
818
  * container
764
- * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.get(ConfigTag)))
765
- * .register(CacheService, async (ctx) => new CacheService(await ctx.get(ConfigTag)))
819
+ * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
820
+ * .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
766
821
  * );
767
822
  *
768
823
  * // Service layer (requires infrastructure)
769
824
  * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
770
825
  * (container) =>
771
826
  * container.register(UserService, async (ctx) =>
772
- * new UserService(await ctx.get(DatabaseService), await ctx.get(CacheService))
827
+ * new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
773
828
  * )
774
829
  * );
775
830
  *
@@ -779,7 +834,7 @@ function container() {
779
834
  */
780
835
  function layer(register) {
781
836
  const layerImpl = {
782
- register: (container$1) => register(container$1),
837
+ register: (container) => register(container),
783
838
  provide(dependency) {
784
839
  return createProvidedLayer(dependency, layerImpl);
785
840
  },
@@ -808,8 +863,8 @@ function createProvidedLayer(dependency, target) {
808
863
  * @internal
809
864
  */
810
865
  function createComposedLayer(dependency, target) {
811
- return layer((container$1) => {
812
- const containerWithDependency = dependency.register(container$1);
866
+ return layer((container) => {
867
+ const containerWithDependency = dependency.register(container);
813
868
  return target.register(containerWithDependency);
814
869
  });
815
870
  }
@@ -820,8 +875,8 @@ function createComposedLayer(dependency, target) {
820
875
  * @internal
821
876
  */
822
877
  function createMergedLayer(layer1, layer2) {
823
- return layer((container$1) => {
824
- const container1 = layer1.register(container$1);
878
+ return layer((container) => {
879
+ const container1 = layer1.register(container);
825
880
  const container2 = layer2.register(container1);
826
881
  return container2;
827
882
  });
@@ -831,7 +886,7 @@ function createMergedLayer(layer1, layer2) {
831
886
  */
832
887
  const Layer = {
833
888
  empty() {
834
- return layer((container$1) => container$1);
889
+ return layer((container) => container);
835
890
  },
836
891
  mergeAll(...layers) {
837
892
  return layers.reduce((acc, layer$1) => acc.merge(layer$1));
@@ -852,6 +907,9 @@ var ScopedContainer = class ScopedContainer extends Container {
852
907
  this.parent = parent;
853
908
  this.scope = scope;
854
909
  }
910
+ static empty(scope) {
911
+ return new ScopedContainer(null, scope);
912
+ }
855
913
  /**
856
914
  * Registers a dependency in the scoped container.
857
915
  *
@@ -891,9 +949,9 @@ var ScopedContainer = class ScopedContainer extends Container {
891
949
  * 3. Otherwise, delegate to parent scope
892
950
  * 4. If no parent or parent doesn't have it, throw UnknownDependencyError
893
951
  */
894
- async get(tag) {
895
- if (this.factories.has(tag)) return super.get(tag);
896
- if (this.parent !== null) return this.parent.get(tag);
952
+ async resolve(tag) {
953
+ if (this.factories.has(tag)) return super.resolve(tag);
954
+ if (this.parent !== null) return this.parent.resolve(tag);
897
955
  throw new UnknownDependencyError(tag);
898
956
  }
899
957
  /**
@@ -973,7 +1031,7 @@ var ScopedContainer = class ScopedContainer extends Container {
973
1031
  * ```typescript
974
1032
  * import { container, scoped } from 'sandly';
975
1033
  *
976
- * const appContainer = container()
1034
+ * const appContainer = Container.empty()
977
1035
  * .register(DatabaseService, () => new DatabaseService())
978
1036
  * .register(ConfigService, () => new ConfigService());
979
1037
  *
@@ -985,10 +1043,10 @@ var ScopedContainer = class ScopedContainer extends Container {
985
1043
  *
986
1044
  * @example Copying complex registrations
987
1045
  * ```typescript
988
- * const baseContainer = container()
1046
+ * const baseContainer = Container.empty()
989
1047
  * .register(DatabaseService, () => new DatabaseService())
990
1048
  * .register(UserService, {
991
- * factory: async (ctx) => new UserService(await ctx.get(DatabaseService)),
1049
+ * factory: async (ctx) => new UserService(await ctx.resolve(DatabaseService)),
992
1050
  * finalizer: (service) => service.cleanup()
993
1051
  * });
994
1052
  *
@@ -996,32 +1054,31 @@ var ScopedContainer = class ScopedContainer extends Container {
996
1054
  * // scopedContainer now has all the same registrations with finalizers preserved
997
1055
  * ```
998
1056
  */
999
- function scoped(container$1, scope) {
1000
- const emptyScoped = new ScopedContainer(null, scope);
1001
- const result = emptyScoped.merge(container$1);
1002
- return result;
1057
+ function scoped(container, scope) {
1058
+ const emptyScoped = ScopedContainer.empty(scope);
1059
+ return emptyScoped.merge(container);
1003
1060
  }
1004
1061
 
1005
1062
  //#endregion
1006
1063
  //#region src/service.ts
1007
1064
  /**
1008
- * Creates a service layer from any tag type (ClassTag or ValueTag) with optional parameters.
1065
+ * Creates a service layer from any tag type (ServiceTag or ValueTag) with optional parameters.
1009
1066
  *
1010
- * For ClassTag services:
1067
+ * For ServiceTag services:
1011
1068
  * - Dependencies are automatically inferred from constructor parameters
1012
1069
  * - The factory function must handle dependency injection by resolving dependencies from the container
1013
1070
  *
1014
1071
  * For ValueTag services:
1015
1072
  * - No constructor dependencies are needed since they don't have constructors
1016
1073
  *
1017
- * @template T - The tag representing the service (ClassTag or ValueTag)
1018
- * @param serviceClass - The tag (ClassTag or ValueTag)
1074
+ * @template T - The tag representing the service (ServiceTag or ValueTag)
1075
+ * @param tag - The tag (ServiceTag or ValueTag)
1019
1076
  * @param factory - Factory function for service instantiation with container
1020
1077
  * @returns The service layer
1021
1078
  *
1022
1079
  * @example Simple service without dependencies
1023
1080
  * ```typescript
1024
- * class LoggerService extends Tag.Class('LoggerService') {
1081
+ * class LoggerService extends Tag.Service('LoggerService') {
1025
1082
  * log(message: string) { console.log(message); }
1026
1083
  * }
1027
1084
  *
@@ -1030,11 +1087,11 @@ function scoped(container$1, scope) {
1030
1087
  *
1031
1088
  * @example Service with dependencies
1032
1089
  * ```typescript
1033
- * class DatabaseService extends Tag.Class('DatabaseService') {
1090
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1034
1091
  * query() { return []; }
1035
1092
  * }
1036
1093
  *
1037
- * class UserService extends Tag.Class('UserService') {
1094
+ * class UserService extends Tag.Service('UserService') {
1038
1095
  * constructor(private db: DatabaseService) {
1039
1096
  * super();
1040
1097
  * }
@@ -1043,13 +1100,13 @@ function scoped(container$1, scope) {
1043
1100
  * }
1044
1101
  *
1045
1102
  * const userService = service(UserService, async (ctx) =>
1046
- * new UserService(await ctx.get(DatabaseService))
1103
+ * new UserService(await ctx.resolve(DatabaseService))
1047
1104
  * );
1048
1105
  * ```
1049
1106
  */
1050
- function service(serviceClass, spec) {
1051
- return layer((container$1) => {
1052
- return container$1.register(serviceClass, spec);
1107
+ function service(tag, spec) {
1108
+ return layer((container) => {
1109
+ return container.register(tag, spec);
1053
1110
  });
1054
1111
  }
1055
1112
 
@@ -1074,8 +1131,8 @@ function service(serviceClass, spec) {
1074
1131
  * ```
1075
1132
  */
1076
1133
  function value(tag, constantValue) {
1077
- return layer((container$1) => container$1.register(tag, () => constantValue));
1134
+ return layer((container) => container.register(tag, () => constantValue));
1078
1135
  }
1079
1136
 
1080
1137
  //#endregion
1081
- export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, Layer, ScopedContainer, Tag, UnknownDependencyError, container, layer, scoped, service, value };
1138
+ export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, ScopedContainer, Tag, UnknownDependencyError, layer, scoped, service, value };