sandly 0.2.0 → 0.3.1

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 CHANGED
@@ -23,13 +23,13 @@ class DatabaseService extends Tag.Service('DatabaseService') {
23
23
  }
24
24
  }
25
25
 
26
- const c = container().register(DatabaseService, () => new DatabaseService());
26
+ const container = Container.empty().register(DatabaseService, () => new DatabaseService());
27
27
 
28
28
  // ✅ TypeScript knows DatabaseService is available
29
- const db = await c.resolve(DatabaseService);
29
+ const db = await container.resolve(DatabaseService);
30
30
 
31
31
  // ❌ Compile error - UserService not registered
32
- const user = await c.resolve(UserService); // Type error
32
+ const user = await container.resolve(UserService); // Type error
33
33
  ```
34
34
 
35
35
  ### Modular Architecture with Layers
package/dist/index.d.ts CHANGED
@@ -13,15 +13,15 @@ type TagId = string | symbol;
13
13
  *
14
14
  * @internal
15
15
  */
16
- declare const ValueTagIdKey = "__sandly/ValueTagIdKey__";
17
- declare const ServiceTagIdKey = "__sandly/ServiceTagIdKey__";
16
+ declare const ValueTagIdKey = "sandly/ValueTagIdKey";
17
+ declare const ServiceTagIdKey = "sandly/ServiceTagIdKey";
18
18
  /**
19
- * Internal symbol used to identify the type of a tagged type within the dependency injection system.
20
- * This symbol is used as a property key to attach metadata to both value tags and service tags.
19
+ * Internal string used to identify the type of a tagged type within the dependency injection system.
20
+ * This string is used as a property key to attach metadata to both value tags and service tags.
21
21
  * It is used to carry the type of the tagged type and should not be used directly.
22
22
  * @internal
23
23
  */
24
- declare const TagTypeKey: unique symbol;
24
+ declare const TagTypeKey = "sandly/TagTypeKey";
25
25
  /**
26
26
  * Type representing a value-based dependency tag.
27
27
  *
@@ -70,9 +70,9 @@ interface ValueTag<Id extends TagId, T> {
70
70
  */
71
71
  interface ServiceTag<Id extends TagId, T> {
72
72
  new (...args: any[]): T & {
73
- readonly [ServiceTagIdKey]: Id;
73
+ readonly [ServiceTagIdKey]?: Id;
74
74
  };
75
- readonly [ServiceTagIdKey]: Id;
75
+ readonly [ServiceTagIdKey]?: Id;
76
76
  }
77
77
  /**
78
78
  * Utility type that extracts the service type from any dependency tag.
@@ -286,7 +286,7 @@ declare const Tag: {
286
286
  * ```
287
287
  */
288
288
  Service: <Id extends TagId>(id: Id) => ServiceTag<Id, {
289
- readonly "__sandly/ServiceTagIdKey__": Id;
289
+ readonly "sandly/ServiceTagIdKey"?: Id;
290
290
  }>;
291
291
  /**
292
292
  * Extracts the string representation of a tag's identifier.
@@ -311,14 +311,14 @@ declare const Tag: {
311
311
  *
312
312
  * @internal - Primarily for internal use in error messages and debugging
313
313
  */
314
- id: (tag: AnyTag) => TagId;
314
+ id: (tag: AnyTag) => TagId | undefined;
315
315
  isTag: (tag: unknown) => tag is AnyTag;
316
316
  };
317
317
  /**
318
- * Unique symbol used to store the original ValueTag in Inject<T> types.
318
+ * String used to store the original ValueTag in Inject<T> types.
319
319
  * This prevents property name collisions while allowing type-level extraction.
320
320
  */
321
- declare const InjectSource: unique symbol;
321
+ declare const InjectSource = "sandly/InjectSource";
322
322
  /**
323
323
  * Helper type for injecting ValueTag dependencies in constructor parameters.
324
324
  * This allows clean specification of ValueTag dependencies while preserving
@@ -549,7 +549,7 @@ interface IContainer<TReg extends AnyTag = never> {
549
549
  * getUser() { return this.db.query(); }
550
550
  * }
551
551
  *
552
- * const c = Container.empty()
552
+ * const container = Container.empty()
553
553
  * .register(DatabaseService, () => new DatabaseService())
554
554
  * .register(UserService, async (ctx) =>
555
555
  * new UserService(await ctx.resolve(DatabaseService))
@@ -563,7 +563,7 @@ interface IContainer<TReg extends AnyTag = never> {
563
563
  * const ApiKeyTag = Tag.of('apiKey')<string>();
564
564
  * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
565
565
  *
566
- * const c = Container.empty()
566
+ * const container = Container.empty()
567
567
  * .register(ApiKeyTag, () => process.env.API_KEY!)
568
568
  * .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
569
569
  *
@@ -578,7 +578,7 @@ interface IContainer<TReg extends AnyTag = never> {
578
578
  * async disconnect() { return; }
579
579
  * }
580
580
  *
581
- * const c = Container.empty().register(
581
+ * const container = Container.empty().register(
582
582
  * DatabaseConnection,
583
583
  * async () => {
584
584
  * const conn = new DatabaseConnection();
@@ -596,6 +596,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
596
596
  readonly [ContainerTypeId]: {
597
597
  readonly _TReg: Contravariant<TReg>;
598
598
  };
599
+ protected constructor();
599
600
  /**
600
601
  * Cache of instantiated dependencies as promises.
601
602
  * Ensures singleton behavior and supports concurrent access.
@@ -617,6 +618,10 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
617
618
  * @internal
618
619
  */
619
620
  protected isDestroyed: boolean;
621
+ /**
622
+ * Creates a new empty container instance.
623
+ * @returns A new empty Container instance with no registered dependencies.
624
+ */
620
625
  static empty(): Container<never>;
621
626
  /**
622
627
  * Registers a dependency in the container with a factory function and optional finalizer.
@@ -642,7 +647,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
642
647
  * log(message: string) { console.log(message); }
643
648
  * }
644
649
  *
645
- * const c = Container.empty().register(
650
+ * const container = Container.empty().register(
646
651
  * LoggerService,
647
652
  * () => new LoggerService()
648
653
  * );
@@ -654,7 +659,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
654
659
  * constructor(private db: DatabaseService, private logger: LoggerService) {}
655
660
  * }
656
661
  *
657
- * const c = Container.empty()
662
+ * const container = Container.empty()
658
663
  * .register(DatabaseService, () => new DatabaseService())
659
664
  * .register(LoggerService, () => new LoggerService())
660
665
  * .register(UserService, async (ctx) =>
@@ -667,7 +672,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
667
672
  *
668
673
  * @example Overriding a dependency
669
674
  * ```typescript
670
- * const c = Container.empty()
675
+ * const container = Container.empty()
671
676
  * .register(DatabaseService, () => new DatabaseService())
672
677
  * .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
673
678
  * ```
@@ -676,7 +681,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
676
681
  * ```typescript
677
682
  * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
678
683
  *
679
- * const c = Container.empty().register(
684
+ * const container = Container.empty().register(
680
685
  * ConfigTag,
681
686
  * () => ({ apiUrl: 'https://api.example.com' })
682
687
  * );
@@ -689,7 +694,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
689
694
  * async close() { return; }
690
695
  * }
691
696
  *
692
- * const c = Container.empty().register(
697
+ * const container = Container.empty().register(
693
698
  * DatabaseConnection,
694
699
  * async () => {
695
700
  * const conn = new DatabaseConnection();
@@ -712,7 +717,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
712
717
  *
713
718
  * @example
714
719
  * ```typescript
715
- * const c = Container.empty().register(DatabaseService, () => new DatabaseService());
720
+ * const container = Container.empty().register(DatabaseService, () => new DatabaseService());
716
721
  * console.log(c.has(DatabaseService)); // true
717
722
  * ```
718
723
  */
@@ -743,7 +748,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
743
748
  *
744
749
  * @example Basic usage
745
750
  * ```typescript
746
- * const c = Container.empty()
751
+ * const container = Container.empty()
747
752
  * .register(DatabaseService, () => new DatabaseService());
748
753
  *
749
754
  * const db = await c.resolve(DatabaseService);
@@ -764,7 +769,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
764
769
  *
765
770
  * @example Dependency injection in factories
766
771
  * ```typescript
767
- * const c = Container.empty()
772
+ * const container = Container.empty()
768
773
  * .register(DatabaseService, () => new DatabaseService())
769
774
  * .register(UserService, async (ctx) => {
770
775
  * const db = await ctx.resolve(DatabaseService);
@@ -793,7 +798,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
793
798
  *
794
799
  * @example Basic usage
795
800
  * ```typescript
796
- * const c = Container.empty()
801
+ * const container = Container.empty()
797
802
  * .register(DatabaseService, () => new DatabaseService())
798
803
  * .register(LoggerService, () => new LoggerService());
799
804
  *
@@ -803,7 +808,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
803
808
  * @example Mixed tag types
804
809
  * ```typescript
805
810
  * const ApiKeyTag = Tag.of('apiKey')<string>();
806
- * const c = Container.empty()
811
+ * const container = Container.empty()
807
812
  * .register(ApiKeyTag, () => 'secret-key')
808
813
  * .register(UserService, () => new UserService());
809
814
  *
@@ -872,7 +877,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
872
877
  *
873
878
  * @example Basic cleanup
874
879
  * ```typescript
875
- * const c = Container.empty()
880
+ * const container = Container.empty()
876
881
  * .register(DatabaseConnection,
877
882
  * async () => {
878
883
  * const conn = new DatabaseConnection();
@@ -995,7 +1000,7 @@ declare class ContainerDestroyedError extends ContainerError {}
995
1000
  *
996
1001
  * @example
997
1002
  * ```typescript
998
- * const c = Container.empty(); // Empty container
1003
+ * const container = Container.empty(); // Empty container
999
1004
  *
1000
1005
  * try {
1001
1006
  * await c.resolve(UnregisteredService); // This will throw
@@ -1027,7 +1032,7 @@ declare class UnknownDependencyError extends ContainerError {
1027
1032
  * class ServiceA extends Tag.Service('ServiceA') {}
1028
1033
  * class ServiceB extends Tag.Service('ServiceB') {}
1029
1034
  *
1030
- * const c = Container.empty()
1035
+ * const container = Container.empty()
1031
1036
  * .register(ServiceA, async (ctx) =>
1032
1037
  * new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
1033
1038
  * )
@@ -1065,7 +1070,7 @@ declare class CircularDependencyError extends ContainerError {
1065
1070
  * ```typescript
1066
1071
  * class DatabaseService extends Tag.Service('DatabaseService') {}
1067
1072
  *
1068
- * const c = Container.empty().register(DatabaseService, () => {
1073
+ * const container = Container.empty().register(DatabaseService, () => {
1069
1074
  * throw new Error('Database connection failed');
1070
1075
  * });
1071
1076
  *
@@ -1163,7 +1168,7 @@ declare const LayerTypeId: unique symbol;
1163
1168
  * );
1164
1169
  *
1165
1170
  * // Apply the layer to a container
1166
- * const c = Container.empty();
1171
+ * const container = Container.empty();
1167
1172
  * const finalContainer = databaseLayer.register(c);
1168
1173
  *
1169
1174
  * const db = await finalContainer.resolve(DatabaseService);
@@ -1204,7 +1209,7 @@ interface Layer<TRequires extends AnyTag, TProvides extends AnyTag> {
1204
1209
  *
1205
1210
  * @example Basic usage
1206
1211
  * ```typescript
1207
- * const c = Container.empty();
1212
+ * const container = Container.empty();
1208
1213
  * const updatedContainer = myLayer.register(c);
1209
1214
  * ```
1210
1215
  *
@@ -1505,6 +1510,11 @@ declare class ScopedContainer<TReg extends AnyTag> extends Container<TReg> {
1505
1510
  private parent;
1506
1511
  private readonly children;
1507
1512
  protected constructor(parent: IContainer<TReg> | null, scope: Scope);
1513
+ /**
1514
+ * Creates a new empty scoped container instance.
1515
+ * @param scope - The scope identifier for this container
1516
+ * @returns A new empty ScopedContainer instance with no registered dependencies
1517
+ */
1508
1518
  static empty(scope: Scope): ScopedContainer<never>;
1509
1519
  /**
1510
1520
  * Registers a dependency in the scoped container.
@@ -1630,7 +1640,7 @@ type ConstructorParams<T extends ServiceTag<TagId, unknown>> = T extends (new (.
1630
1640
  * @internal
1631
1641
  */
1632
1642
  type ExtractConstructorDeps<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1633
- readonly [ServiceTagIdKey]: infer Id;
1643
+ readonly [ServiceTagIdKey]?: infer Id;
1634
1644
  } ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
1635
1645
  /**
1636
1646
  * Produces an ordered tuple of constructor parameters
@@ -1638,7 +1648,9 @@ type ExtractConstructorDeps<T extends readonly unknown[]> = T extends readonly [
1638
1648
  * while non‑DI parameters are preserved as‑is.
1639
1649
  * @internal
1640
1650
  */
1641
-
1651
+ type InferConstructorDepsTuple<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1652
+ readonly [ServiceTagIdKey]?: infer Id;
1653
+ } ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? T[K] : ExtractInjectTag<T[K]> };
1642
1654
  /**
1643
1655
  * Union of all dependency tags a ServiceTag constructor requires.
1644
1656
  * Filters out non‑DI parameters.
@@ -1648,7 +1660,7 @@ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstruc
1648
1660
  * Ordered tuple of dependency tags (and other constructor params)
1649
1661
  * inferred from a ServiceTag’s constructor.
1650
1662
  */
1651
-
1663
+ type ServiceDepsTuple<T extends ServiceTag<TagId, unknown>> = InferConstructorDepsTuple<ConstructorParams<T>>;
1652
1664
  /**
1653
1665
  * Creates a service layer from any tag type (ServiceTag or ValueTag) with optional parameters.
1654
1666
  *
@@ -1693,6 +1705,14 @@ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstruc
1693
1705
  * ```
1694
1706
  */
1695
1707
  declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: DependencySpec<T, ServiceDependencies<T>>): Layer<ServiceDependencies<T>, T>;
1708
+ /**
1709
+ * Specification for autoService.
1710
+ * Can be either a tuple of constructor parameters or an object with dependencies and finalizer.
1711
+ */
1712
+ type AutoServiceSpec<T extends ServiceTag<TagId, unknown>> = ServiceDepsTuple<T> | {
1713
+ dependencies: ServiceDepsTuple<T>;
1714
+ finalizer?: Finalizer<TagType<T>>;
1715
+ };
1696
1716
  /**
1697
1717
  * Creates a service layer with automatic dependency injection by inferring constructor parameters.
1698
1718
  *
@@ -1784,11 +1804,14 @@ declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: Dep
1784
1804
  * // Service with automatic cleanup
1785
1805
  * const dbService = autoService(
1786
1806
  * DatabaseService,
1787
- * ['postgresql://localhost:5432/mydb'],
1788
- * (service) => service.disconnect() // Finalizer for cleanup
1807
+ * {
1808
+ * dependencies: ['postgresql://localhost:5432/mydb'],
1809
+ * finalizer: (service) => service.disconnect() // Finalizer for cleanup
1810
+ * }
1789
1811
  * );
1790
1812
  * ```
1791
1813
  */
1814
+ declare function autoService<T extends ServiceTag<TagId, unknown>>(tag: T, spec: AutoServiceSpec<T>): Layer<ServiceDependencies<T>, T>;
1792
1815
  //#endregion
1793
1816
  //#region src/value.d.ts
1794
1817
  /**
@@ -1811,4 +1834,4 @@ declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: Dep
1811
1834
  */
1812
1835
  declare function value<Id extends TagId, T>(tag: ValueTag<Id, T>, constantValue: T): Layer<never, ValueTag<Id, T>>;
1813
1836
  //#endregion
1814
- export { type AnyLayer, type AnyTag, CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, type DependencyLifecycle, type DependencySpec, type Factory, type Finalizer, type IContainer, type Inject, InjectSource, Layer, type PromiseOrValue, type ResolutionContext, type Scope, ScopedContainer, type ServiceTag, Tag, type TagType, UnknownDependencyError, type ValueTag, layer, scoped, service, value };
1837
+ export { type AnyLayer, type AnyTag, CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, type DependencyLifecycle, type DependencySpec, type Factory, type Finalizer, type IContainer, type Inject, InjectSource, Layer, type PromiseOrValue, type ResolutionContext, type Scope, ScopedContainer, type ServiceDependencies, type ServiceDepsTuple, type ServiceTag, Tag, type TagType, UnknownDependencyError, type ValueTag, autoService, layer, scoped, service, value };
package/dist/index.js CHANGED
@@ -24,15 +24,15 @@ function getKey(obj, ...keys) {
24
24
  *
25
25
  * @internal
26
26
  */
27
- const ValueTagIdKey = "__sandly/ValueTagIdKey__";
28
- const ServiceTagIdKey = "__sandly/ServiceTagIdKey__";
27
+ const ValueTagIdKey = "sandly/ValueTagIdKey";
28
+ const ServiceTagIdKey = "sandly/ServiceTagIdKey";
29
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.
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
32
  * It is used to carry the type of the tagged type and should not be used directly.
33
33
  * @internal
34
34
  */
35
- const TagTypeKey = Symbol.for("sandly/TagTypeKey");
35
+ const TagTypeKey = "sandly/TagTypeKey";
36
36
  /**
37
37
  * Utility object containing factory functions for creating dependency tags.
38
38
  *
@@ -68,10 +68,10 @@ const Tag = {
68
68
  }
69
69
  };
70
70
  /**
71
- * Unique symbol used to store the original ValueTag in Inject<T> types.
71
+ * String used to store the original ValueTag in Inject<T> types.
72
72
  * This prevents property name collisions while allowing type-level extraction.
73
73
  */
74
- const InjectSource = Symbol("InjectSource");
74
+ const InjectSource = "sandly/InjectSource";
75
75
 
76
76
  //#endregion
77
77
  //#region src/errors.ts
@@ -149,7 +149,7 @@ var ContainerDestroyedError = class extends ContainerError {};
149
149
  *
150
150
  * @example
151
151
  * ```typescript
152
- * const c = Container.empty(); // Empty container
152
+ * const container = Container.empty(); // Empty container
153
153
  *
154
154
  * try {
155
155
  * await c.resolve(UnregisteredService); // This will throw
@@ -183,7 +183,7 @@ var UnknownDependencyError = class extends ContainerError {
183
183
  * class ServiceA extends Tag.Service('ServiceA') {}
184
184
  * class ServiceB extends Tag.Service('ServiceB') {}
185
185
  *
186
- * const c = Container.empty()
186
+ * const container = Container.empty()
187
187
  * .register(ServiceA, async (ctx) =>
188
188
  * new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
189
189
  * )
@@ -227,7 +227,7 @@ var CircularDependencyError = class extends ContainerError {
227
227
  * ```typescript
228
228
  * class DatabaseService extends Tag.Service('DatabaseService') {}
229
229
  *
230
- * const c = Container.empty().register(DatabaseService, () => {
230
+ * const container = Container.empty().register(DatabaseService, () => {
231
231
  * throw new Error('Database connection failed');
232
232
  * });
233
233
  *
@@ -324,7 +324,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
324
324
  * getUser() { return this.db.query(); }
325
325
  * }
326
326
  *
327
- * const c = Container.empty()
327
+ * const container = Container.empty()
328
328
  * .register(DatabaseService, () => new DatabaseService())
329
329
  * .register(UserService, async (ctx) =>
330
330
  * new UserService(await ctx.resolve(DatabaseService))
@@ -338,7 +338,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
338
338
  * const ApiKeyTag = Tag.of('apiKey')<string>();
339
339
  * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
340
340
  *
341
- * const c = Container.empty()
341
+ * const container = Container.empty()
342
342
  * .register(ApiKeyTag, () => process.env.API_KEY!)
343
343
  * .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
344
344
  *
@@ -353,7 +353,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
353
353
  * async disconnect() { return; }
354
354
  * }
355
355
  *
356
- * const c = Container.empty().register(
356
+ * const container = Container.empty().register(
357
357
  * DatabaseConnection,
358
358
  * async () => {
359
359
  * const conn = new DatabaseConnection();
@@ -369,6 +369,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
369
369
  */
370
370
  var Container = class Container {
371
371
  [ContainerTypeId];
372
+ constructor() {}
372
373
  /**
373
374
  * Cache of instantiated dependencies as promises.
374
375
  * Ensures singleton behavior and supports concurrent access.
@@ -390,6 +391,10 @@ var Container = class Container {
390
391
  * @internal
391
392
  */
392
393
  isDestroyed = false;
394
+ /**
395
+ * Creates a new empty container instance.
396
+ * @returns A new empty Container instance with no registered dependencies.
397
+ */
393
398
  static empty() {
394
399
  return new Container();
395
400
  }
@@ -417,7 +422,7 @@ var Container = class Container {
417
422
  * log(message: string) { console.log(message); }
418
423
  * }
419
424
  *
420
- * const c = Container.empty().register(
425
+ * const container = Container.empty().register(
421
426
  * LoggerService,
422
427
  * () => new LoggerService()
423
428
  * );
@@ -429,7 +434,7 @@ var Container = class Container {
429
434
  * constructor(private db: DatabaseService, private logger: LoggerService) {}
430
435
  * }
431
436
  *
432
- * const c = Container.empty()
437
+ * const container = Container.empty()
433
438
  * .register(DatabaseService, () => new DatabaseService())
434
439
  * .register(LoggerService, () => new LoggerService())
435
440
  * .register(UserService, async (ctx) =>
@@ -442,7 +447,7 @@ var Container = class Container {
442
447
  *
443
448
  * @example Overriding a dependency
444
449
  * ```typescript
445
- * const c = Container.empty()
450
+ * const container = Container.empty()
446
451
  * .register(DatabaseService, () => new DatabaseService())
447
452
  * .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
448
453
  * ```
@@ -451,7 +456,7 @@ var Container = class Container {
451
456
  * ```typescript
452
457
  * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
453
458
  *
454
- * const c = Container.empty().register(
459
+ * const container = Container.empty().register(
455
460
  * ConfigTag,
456
461
  * () => ({ apiUrl: 'https://api.example.com' })
457
462
  * );
@@ -464,7 +469,7 @@ var Container = class Container {
464
469
  * async close() { return; }
465
470
  * }
466
471
  *
467
- * const c = Container.empty().register(
472
+ * const container = Container.empty().register(
468
473
  * DatabaseConnection,
469
474
  * async () => {
470
475
  * const conn = new DatabaseConnection();
@@ -498,7 +503,7 @@ var Container = class Container {
498
503
  *
499
504
  * @example
500
505
  * ```typescript
501
- * const c = Container.empty().register(DatabaseService, () => new DatabaseService());
506
+ * const container = Container.empty().register(DatabaseService, () => new DatabaseService());
502
507
  * console.log(c.has(DatabaseService)); // true
503
508
  * ```
504
509
  */
@@ -533,7 +538,7 @@ var Container = class Container {
533
538
  *
534
539
  * @example Basic usage
535
540
  * ```typescript
536
- * const c = Container.empty()
541
+ * const container = Container.empty()
537
542
  * .register(DatabaseService, () => new DatabaseService());
538
543
  *
539
544
  * const db = await c.resolve(DatabaseService);
@@ -554,7 +559,7 @@ var Container = class Container {
554
559
  *
555
560
  * @example Dependency injection in factories
556
561
  * ```typescript
557
- * const c = Container.empty()
562
+ * const container = Container.empty()
558
563
  * .register(DatabaseService, () => new DatabaseService())
559
564
  * .register(UserService, async (ctx) => {
560
565
  * const db = await ctx.resolve(DatabaseService);
@@ -604,7 +609,7 @@ var Container = class Container {
604
609
  *
605
610
  * @example Basic usage
606
611
  * ```typescript
607
- * const c = Container.empty()
612
+ * const container = Container.empty()
608
613
  * .register(DatabaseService, () => new DatabaseService())
609
614
  * .register(LoggerService, () => new LoggerService());
610
615
  *
@@ -614,7 +619,7 @@ var Container = class Container {
614
619
  * @example Mixed tag types
615
620
  * ```typescript
616
621
  * const ApiKeyTag = Tag.of('apiKey')<string>();
617
- * const c = Container.empty()
622
+ * const container = Container.empty()
618
623
  * .register(ApiKeyTag, () => 'secret-key')
619
624
  * .register(UserService, () => new UserService());
620
625
  *
@@ -704,7 +709,7 @@ var Container = class Container {
704
709
  *
705
710
  * @example Basic cleanup
706
711
  * ```typescript
707
- * const c = Container.empty()
712
+ * const container = Container.empty()
708
713
  * .register(DatabaseConnection,
709
714
  * async () => {
710
715
  * const conn = new DatabaseConnection();
@@ -907,6 +912,11 @@ var ScopedContainer = class ScopedContainer extends Container {
907
912
  this.parent = parent;
908
913
  this.scope = scope;
909
914
  }
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
+ */
910
920
  static empty(scope) {
911
921
  return new ScopedContainer(null, scope);
912
922
  }
@@ -1109,6 +1119,122 @@ function service(tag, spec) {
1109
1119
  return container.register(tag, spec);
1110
1120
  });
1111
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
+ }
1112
1238
 
1113
1239
  //#endregion
1114
1240
  //#region src/value.ts
@@ -1135,4 +1261,4 @@ function value(tag, constantValue) {
1135
1261
  }
1136
1262
 
1137
1263
  //#endregion
1138
- export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, ScopedContainer, Tag, UnknownDependencyError, layer, scoped, service, value };
1264
+ export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, ScopedContainer, Tag, UnknownDependencyError, autoService, layer, scoped, service, value };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandly",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "keywords": [
5
5
  "typescript",
6
6
  "sandly",