ts-ioc-container 71.0.0 → 72.0.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.
Files changed (57) hide show
  1. package/README.md +109 -65
  2. package/cjm/container/Container.js +5 -5
  3. package/cjm/hooks/HookContext.js +3 -2
  4. package/cjm/hooks/injectProp.js +2 -4
  5. package/cjm/index.js +3 -3
  6. package/cjm/injector/IInjector.js +2 -2
  7. package/cjm/injector/MetadataInjector.js +14 -9
  8. package/cjm/injector/ProxyInjector.js +1 -1
  9. package/cjm/injector/SimpleInjector.js +2 -2
  10. package/cjm/provider/Provider.js +9 -8
  11. package/cjm/registration/IRegistration.js +2 -2
  12. package/cjm/select.js +2 -2
  13. package/cjm/token/ClassToken.js +3 -3
  14. package/cjm/token/FunctionToken.js +5 -4
  15. package/cjm/token/GroupAliasToken.js +3 -3
  16. package/cjm/token/InjectionToken.js +1 -1
  17. package/cjm/token/SingleAliasToken.js +3 -3
  18. package/cjm/token/SingleToken.js +3 -3
  19. package/cjm/token/toToken.js +1 -8
  20. package/esm/container/Container.js +5 -5
  21. package/esm/hooks/HookContext.js +3 -2
  22. package/esm/hooks/injectProp.js +1 -4
  23. package/esm/index.js +2 -2
  24. package/esm/injector/IInjector.js +2 -2
  25. package/esm/injector/MetadataInjector.js +13 -9
  26. package/esm/injector/ProxyInjector.js +1 -1
  27. package/esm/injector/SimpleInjector.js +2 -2
  28. package/esm/provider/Provider.js +9 -8
  29. package/esm/registration/IRegistration.js +2 -2
  30. package/esm/select.js +2 -2
  31. package/esm/token/ClassToken.js +3 -3
  32. package/esm/token/FunctionToken.js +5 -4
  33. package/esm/token/GroupAliasToken.js +3 -3
  34. package/esm/token/InjectionToken.js +1 -1
  35. package/esm/token/SingleAliasToken.js +3 -3
  36. package/esm/token/SingleToken.js +3 -3
  37. package/esm/token/toToken.js +0 -6
  38. package/package.json +1 -1
  39. package/typings/container/IContainer.d.ts +2 -2
  40. package/typings/hooks/HookContext.d.ts +3 -3
  41. package/typings/hooks/hook.d.ts +1 -2
  42. package/typings/hooks/injectProp.d.ts +2 -14
  43. package/typings/index.d.ts +4 -4
  44. package/typings/injector/IInjector.d.ts +7 -4
  45. package/typings/injector/MetadataInjector.d.ts +6 -16
  46. package/typings/injector/ProxyInjector.d.ts +1 -2
  47. package/typings/injector/SimpleInjector.d.ts +1 -2
  48. package/typings/provider/IProvider.d.ts +5 -4
  49. package/typings/provider/Provider.d.ts +2 -2
  50. package/typings/select.d.ts +3 -3
  51. package/typings/token/ClassToken.d.ts +2 -2
  52. package/typings/token/FunctionToken.d.ts +2 -2
  53. package/typings/token/GroupAliasToken.d.ts +2 -2
  54. package/typings/token/InjectionToken.d.ts +2 -2
  55. package/typings/token/SingleAliasToken.d.ts +3 -3
  56. package/typings/token/SingleToken.d.ts +2 -2
  57. package/typings/token/toToken.d.ts +0 -2
package/README.md CHANGED
@@ -114,7 +114,7 @@ bundlers tree-shake unused exports.
114
114
  ## Quickstart
115
115
 
116
116
  ```typescript
117
- import { bindTo, Container, inject, register, Registration as R, singleton, SingleToken } from 'ts-ioc-container';
117
+ import { bindTo, Container, inject, register, Registration as R, singleton, SingleToken, by } from 'ts-ioc-container';
118
118
 
119
119
  interface ILogger {
120
120
  log(message: string): void;
@@ -130,7 +130,7 @@ class Logger implements ILogger {
130
130
  }
131
131
 
132
132
  class App {
133
- constructor(@inject(ILoggerToken) private logger: ILogger) {}
133
+ constructor(@inject(by(ILoggerToken)) private logger: ILogger) {}
134
134
  start() {
135
135
  this.logger.log('hello');
136
136
  }
@@ -153,16 +153,17 @@ describe('Quickstart', function () {
153
153
 
154
154
  - Register class with key (preferred): `@register(bindTo('Key')) class Service {}` then `container.addRegistration(R.fromClass(Service))`
155
155
  - Register value: `R.fromValue(config).bindTo('Config')`
156
- - Register factory: `R.fromFn((c) => createX(c)).bindTo('X')`
156
+ - Register factory: `R.fromFn(({ scope }) => createX(scope)).bindTo('X')`
157
157
  - Singleton: `@register(singleton())`
158
158
  - Eager service: `@register(autoResolve())` + `container.useModule(new AutoResolveModule())`
159
159
  - Scoped registration: `@register(scope((s) => s.hasTag('request')))`
160
160
  - Resolve by alias: `container.resolveByAlias('Alias')`
161
161
  - Current scope token: `select.scope.current`
162
162
  - Lazy token: `select.token('Service').lazy()`
163
- - Inject decorator: `@inject('Key')`
164
- - Map an injected value: `@inject('Key', sanitize(), validate())`
165
- - Property inject: `@hook('onInit', injectProp('Key'))`
163
+ - Inject decorator: `@inject(by(Token))` / `@inject(by('Key'))` / `@inject(by(Logger))` — the argument is always one `InjectFn`, `by(...)` builds the usual one
164
+ - Custom inject function: `@inject(({ scope, args }) => ...)`
165
+ - Map an injected value: `@inject(pipe(by('Key'), sanitize(), validate()))`
166
+ - Property inject: `@hook('onInit', injectProp(by('Key')))`
166
167
 
167
168
  > [!TIP]
168
169
  > For classes, prefer the `@register(bindTo('Key'))` decorator over the fluent
@@ -184,7 +185,7 @@ describe('Quickstart', function () {
184
185
 
185
186
  ```typescript
186
187
  import 'reflect-metadata';
187
- import { bindTo, Container, type IContainer, inject, register, Registration as R, select } from 'ts-ioc-container';
188
+ import { bindTo, Container, type IContainer, inject, register, Registration as R, select, by } from 'ts-ioc-container';
188
189
 
189
190
  /**
190
191
  * User Management Domain - Basic Dependency Injection
@@ -219,7 +220,7 @@ describe('Basic usage', function () {
219
220
  it('should inject dependencies', function () {
220
221
  // AuthService depends on IUserRepository
221
222
  class AuthService {
222
- constructor(@inject('IUserRepository') private userRepo: IUserRepository) {}
223
+ constructor(@inject(by('IUserRepository')) private userRepo: IUserRepository) {}
223
224
 
224
225
  authenticate(email: string): boolean {
225
226
  const user = this.userRepo.findByEmail(email);
@@ -243,7 +244,7 @@ describe('Basic usage', function () {
243
244
  const appContainer = new Container({ tags: ['application'] });
244
245
 
245
246
  class RequestHandler {
246
- constructor(@inject(select.scope.current) public requestScope: IContainer) {}
247
+ constructor(@inject(by(select.scope.current)) public requestScope: IContainer) {}
247
248
 
248
249
  handleRequest(): string {
249
250
  // Access request-scoped dependencies
@@ -286,6 +287,7 @@ import {
286
287
  scope,
287
288
  select,
288
289
  singleton,
290
+ by,
289
291
  } from 'ts-ioc-container';
290
292
 
291
293
  /**
@@ -345,7 +347,10 @@ describe('Scopes', function () {
345
347
 
346
348
  // RequestHandler can create a transaction scope for database operations
347
349
  class RequestHandler {
348
- constructor(@inject(select.scope.create({ tags: ['transaction'] })) public transactionScope: IContainer) {}
350
+ constructor(
351
+ @inject(by(select.scope.create({ tags: ['transaction'] })))
352
+ public transactionScope: IContainer,
353
+ ) {}
349
354
 
350
355
  executeInTransaction(): boolean {
351
356
  // Transaction scope inherits from request scope
@@ -371,7 +376,7 @@ Sometimes you want to get all instances from container and its scopes. For examp
371
376
  - you can get instances from container and scope which were created by injector
372
377
 
373
378
  ```typescript
374
- import { bindTo, Container, inject, register, Registration as R, select } from 'ts-ioc-container';
379
+ import { bindTo, Container, inject, register, Registration as R, select, by } from 'ts-ioc-container';
375
380
 
376
381
  /**
377
382
  * User Management Domain - Instance Collection
@@ -391,7 +396,7 @@ describe('Instances', function () {
391
396
  it('should collect instances across scope hierarchy', () => {
392
397
  // App that needs access to all logger instances (e.g., for flushing)
393
398
  class App {
394
- constructor(@inject(select.instances()) public loggers: Logger[]) {}
399
+ constructor(@inject(by(select.instances())) public loggers: Logger[]) {}
395
400
  }
396
401
 
397
402
  const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
@@ -413,7 +418,7 @@ describe('Instances', function () {
413
418
  it('should return only current scope instances when cascade is disabled', () => {
414
419
  // Only get instances from current scope, not parent scopes
415
420
  class App {
416
- constructor(@inject(select.instances().cascade(false)) public loggers: Logger[]) {}
421
+ constructor(@inject(by(select.instances().cascade(false))) public loggers: Logger[]) {}
417
422
  }
418
423
 
419
424
  const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
@@ -432,7 +437,7 @@ describe('Instances', function () {
432
437
  const isLogger = (instance: unknown) => instance instanceof Logger;
433
438
 
434
439
  class App {
435
- constructor(@inject(select.instances(isLogger)) public loggers: Logger[]) {}
440
+ constructor(@inject(by(select.instances(isLogger))) public loggers: Logger[]) {}
436
441
  }
437
442
 
438
443
  const container = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
@@ -569,7 +574,7 @@ Sometimes you want to create dependency only when somebody want to invoke it's m
569
574
 
570
575
  ```typescript
571
576
  import 'reflect-metadata';
572
- import { Container, inject, register, Registration as R, select as s, singleton } from 'ts-ioc-container';
577
+ import { Container, inject, register, Registration as R, select as s, singleton, by } from 'ts-ioc-container';
573
578
 
574
579
  /**
575
580
  * User Management Domain - Lazy Loading
@@ -600,7 +605,7 @@ describe('lazy provider', () => {
600
605
 
601
606
  // EmailNotifier is expensive - establishes SMTP connection on construction
602
607
  class EmailNotifier {
603
- constructor(@inject('SmtpConnectionStatus') private smtp: SmtpConnectionStatus) {
608
+ constructor(@inject(by('SmtpConnectionStatus')) private smtp: SmtpConnectionStatus) {
604
609
  // Simulate expensive SMTP connection
605
610
  this.smtp.connect();
606
611
  }
@@ -613,7 +618,10 @@ describe('lazy provider', () => {
613
618
  // AuthService might need to send password reset emails
614
619
  // But most login requests don't need email (only password reset does)
615
620
  class AuthService {
616
- constructor(@inject(s.token('EmailNotifier').lazy()) public emailNotifier: EmailNotifier) {}
621
+ constructor(
622
+ @inject(by(s.token('EmailNotifier').lazy()))
623
+ public emailNotifier: EmailNotifier,
624
+ ) {}
617
625
 
618
626
  login(email: string, password: string): boolean {
619
627
  // Most requests just validate credentials - no email needed
@@ -720,6 +728,7 @@ import {
720
728
  register,
721
729
  Registration as R,
722
730
  singleton,
731
+ by,
723
732
  } from 'ts-ioc-container';
724
733
 
725
734
  /**
@@ -762,7 +771,7 @@ describe('lazy registerPipe', () => {
762
771
  // Analytics service - expensive, but only used occasionally
763
772
  @register(bindTo('AnalyticsService'), lazy(), singleton())
764
773
  class AnalyticsService {
765
- constructor(@inject('DatabasePool') private db: DatabasePool) {
774
+ constructor(@inject(by('DatabasePool')) private db: DatabasePool) {
766
775
  initLog.push('AnalyticsService initialized');
767
776
  }
768
777
 
@@ -777,7 +786,7 @@ describe('lazy registerPipe', () => {
777
786
 
778
787
  // Application service - always used
779
788
  class AppService {
780
- constructor(@inject('AnalyticsService') public analytics: AnalyticsService) {
789
+ constructor(@inject(by('AnalyticsService')) public analytics: AnalyticsService) {
781
790
  initLog.push('AppService initialized');
782
791
  }
783
792
 
@@ -874,8 +883,8 @@ describe('lazy registerPipe', () => {
874
883
  // Notification service - uses email and SMS, but maybe not both
875
884
  class NotificationService {
876
885
  constructor(
877
- @inject('EmailService') public email: EmailService,
878
- @inject('SmsService') public sms: SmsService,
886
+ @inject(by('EmailService')) public email: EmailService,
887
+ @inject(by('SmsService')) public sms: SmsService,
879
888
  ) {
880
889
  initLog.push('NotificationService initialized');
881
890
  }
@@ -967,7 +976,7 @@ describe('lazy registerPipe', () => {
967
976
  }
968
977
 
969
978
  class ApiService {
970
- constructor(@inject('CacheService') private cache: CacheService) {
979
+ constructor(@inject(by('CacheService')) private cache: CacheService) {
971
980
  initLog.push('ApiService initialized');
972
981
  }
973
982
 
@@ -1085,8 +1094,8 @@ describe('lazy registerPipe', () => {
1085
1094
 
1086
1095
  class Application {
1087
1096
  constructor(
1088
- @inject('FeatureFlagService') private flags: FeatureFlagService,
1089
- @inject('PremiumFeature') private premium: PremiumFeature,
1097
+ @inject(by('FeatureFlagService')) private flags: FeatureFlagService,
1098
+ @inject(by('PremiumFeature')) private premium: PremiumFeature,
1090
1099
  ) {
1091
1100
  initLog.push('Application initialized');
1092
1101
  }
@@ -1144,8 +1153,23 @@ describe('lazy registerPipe', () => {
1144
1153
  This type of injector uses `@inject` decorator to mark where dependencies should be injected. It's bases on `reflect-metadata` package. That's why I call it `MetadataInjector`.
1145
1154
  Also you can [inject property.](#inject-property)
1146
1155
 
1156
+ `@inject` takes exactly one argument, an `InjectFn`: `(options: ProviderOptions) => T`.
1157
+ It is called with the resolution context of the class being constructed — the
1158
+ `scope` it is built in and the runtime `args` it is built with — and whatever it
1159
+ returns is injected. There is no other form, only functions which build one:
1160
+
1161
+ - `by(target)` — resolves a token, a `DependencyKey` or a class from the scope,
1162
+ forwarding the runtime args (see
1163
+ [runtime args flow through tokens](#runtime-args-flow-through-tokens)):
1164
+ `@inject(by(LoggerToken))`, `@inject(by('Logger'))`, `@inject(by(Logger))`.
1165
+ - `arg(index)` / `args` / `argsFn(predicate)` — pick from the runtime args.
1166
+ - `pipe(fn, ...mappers)` — map what another `InjectFn` returns.
1167
+ - Your own: `@inject(({ scope }) => scope)` injects the current scope,
1168
+ `@inject(({ scope }) => Token.resolve(scope))` resolves a token *without*
1169
+ the args.
1170
+
1147
1171
  ```typescript
1148
- import { bindTo, Container, inject, register, Registration as R } from 'ts-ioc-container';
1172
+ import { bindTo, Container, inject, register, Registration as R, by } from 'ts-ioc-container';
1149
1173
 
1150
1174
  /**
1151
1175
  * User Management Domain - Metadata Injection
@@ -1154,7 +1178,7 @@ import { bindTo, Container, inject, register, Registration as R } from 'ts-ioc-c
1154
1178
  * to automatically inject dependencies into constructor parameters.
1155
1179
  *
1156
1180
  * How it works:
1157
- * 1. @inject('key') decorator marks a parameter for injection
1181
+ * 1. @inject(by('key')) decorator marks a parameter for injection
1158
1182
  * 2. Container reads metadata at resolution time
1159
1183
  * 3. Dependencies are resolved and passed to constructor
1160
1184
  *
@@ -1169,10 +1193,10 @@ class Logger {
1169
1193
 
1170
1194
  class App {
1171
1195
  // @inject tells the container which dependency to resolve for this parameter
1172
- constructor(@inject('ILogger') private logger: Logger) {}
1196
+ constructor(@inject(by('ILogger')) private logger: Logger) {}
1173
1197
 
1174
1198
  // Alternative: inject via function for dynamic resolution
1175
- // constructor(@inject((container, ...args) => container.resolve('ILogger', ...args)) private logger: ILogger) {}
1199
+ // constructor(@inject(by('ILogger')) private logger: ILogger) {}
1176
1200
 
1177
1201
  getLoggerName(): string {
1178
1202
  return this.logger.name;
@@ -1194,31 +1218,32 @@ describe('Metadata Injector', function () {
1194
1218
 
1195
1219
  ### Mapping injected values
1196
1220
 
1197
- Every argument after the first one is a **mapper** applied to the resolved
1198
- instance, left to right — the value the last mapper returns is what reaches the
1199
- constructor parameter:
1221
+ An `InjectFn` is a function, so mapping the value it resolves is function
1222
+ composition — `pipe(fn, ...mappers)` (exported) runs each **mapper** on the
1223
+ previous result, left to right, and the value the last one returns is what
1224
+ reaches the constructor parameter:
1200
1225
 
1201
1226
  ```typescript
1202
- @inject('Config', takeApiUrl(), stripTrailingSlash(), requireHttps())
1227
+ @inject(pipe(by<Config>('Config'), takeApiUrl(), stripTrailingSlash(), requireHttps()))
1203
1228
  ```
1204
1229
 
1205
1230
  A mapper is a plain `(value) => value` function, so mappers compose into named,
1206
1231
  reusable steps (selecting a member, sanitizing, validating) and each step's
1207
- parameter type is inferred from the previous one. With no mapper the resolved
1208
- instance is injected untouched.
1209
-
1210
- `injectProp` takes the same rest parameters: `injectProp('Config', takeApiUrl())`.
1232
+ parameter type is inferred from the previous one. `pipe(fn, ...mappers)` is
1233
+ itself an `InjectFn`, so it goes wherever one does — `@inject(...)`, a
1234
+ `FunctionToken`, or `injectProp(pipe(by('Config'), takeApiUrl()))`.
1211
1235
 
1212
1236
  ```typescript
1213
1237
  import 'reflect-metadata';
1214
- import { Container, inject, Registration as R } from 'ts-ioc-container';
1238
+ import { Container, inject, Registration as R, pipe, by } from 'ts-ioc-container';
1215
1239
 
1216
1240
  /**
1217
1241
  * Mapping injected values
1218
1242
  *
1219
- * Every argument after the first one passed to `@inject` (or `injectProp`) is a
1220
- * mapper applied to the resolved instance, left to right. Mappers are plain
1221
- * functions, so they compose into reusable, named steps.
1243
+ * `@inject` takes one `InjectFn`, so mapping what it resolves is composition:
1244
+ * `pipe(fn, ...mappers)` applies each mapper to the previous result, left to
1245
+ * right, and is itself an `InjectFn`. Mappers are plain functions, so they
1246
+ * compose into reusable, named steps.
1222
1247
  */
1223
1248
 
1224
1249
  interface Config {
@@ -1239,7 +1264,10 @@ const requireHttps = () => (url: string) => {
1239
1264
  describe('inject mappers', () => {
1240
1265
  it('should pipe the resolved dependency through every mapper', () => {
1241
1266
  class ApiClient {
1242
- constructor(@inject('Config', takeApiUrl(), stripTrailingSlash(), requireHttps()) readonly apiUrl: string) {}
1267
+ constructor(
1268
+ @inject(pipe(by<Config>('Config'), takeApiUrl(), stripTrailingSlash(), requireHttps()))
1269
+ readonly apiUrl: string,
1270
+ ) {}
1243
1271
  }
1244
1272
 
1245
1273
  const container = new Container().addRegistration(
@@ -1251,7 +1279,10 @@ describe('inject mappers', () => {
1251
1279
 
1252
1280
  it('should throw from a mapper when the resolved value is not acceptable', () => {
1253
1281
  class ApiClient {
1254
- constructor(@inject('Config', takeApiUrl(), requireHttps()) readonly apiUrl: string) {}
1282
+ constructor(
1283
+ @inject(pipe(by<Config>('Config'), takeApiUrl(), requireHttps()))
1284
+ readonly apiUrl: string,
1285
+ ) {}
1255
1286
  }
1256
1287
 
1257
1288
  const container = new Container().addRegistration(
@@ -1391,7 +1422,7 @@ Provider is dependency factory which creates dependency.
1391
1422
 
1392
1423
  - `Provider.fromClass(Logger)`
1393
1424
  - `Provider.fromValue(logger)`
1394
- - `new Provider((container, options) => container.resolve(Logger, options))`
1425
+ - `new Provider(({ scope, ...options }) => scope.resolve(Logger, options))`
1395
1426
 
1396
1427
  ```typescript
1397
1428
  import { arg, bindTo, Container, inject, lazy, Provider, register, Registration as R } from 'ts-ioc-container';
@@ -1466,7 +1497,7 @@ describe('Provider', () => {
1466
1497
 
1467
1498
  const container = new Container().register(
1468
1499
  'FileService',
1469
- Provider.fromClass(FileService).addArgsFn((_, { args = [] } = {}) => [...args, '/var/data']),
1500
+ Provider.fromClass(FileService).addArgsFn(({ args = [] }) => [...args, '/var/data']),
1470
1501
  );
1471
1502
 
1472
1503
  const service = container.resolve<FileService>('FileService');
@@ -1481,7 +1512,7 @@ describe('Provider', () => {
1481
1512
  const container = new Container().register('DbPath', Provider.fromValue('localhost:5432')).register(
1482
1513
  'Database',
1483
1514
  // Dynamically resolve connection string at creation time
1484
- Provider.fromClass(Database).addArgsFn((scope) => [`postgres://${scope.resolve('DbPath')}`]),
1515
+ Provider.fromClass(Database).addArgsFn(({ scope }) => [`postgres://${scope.resolve('DbPath')}`]),
1485
1516
  );
1486
1517
 
1487
1518
  const db = container.resolve<Database>('Database');
@@ -1771,7 +1802,7 @@ describe('Auto resolve', function () {
1771
1802
  Sometimes you want to bind some arguments to provider.
1772
1803
 
1773
1804
  - `provider(appendArgs('someArgument'))`
1774
- - `provider(appendArgsFn((container) => [container.resolve(Logger), 'someValue']))`
1805
+ - `provider(appendArgsFn(({ scope }) => [scope.resolve(Logger), 'someValue']))`
1775
1806
  - `Provider.fromClass(Logger).pipe(appendArgs('someArgument'))`
1776
1807
 
1777
1808
  ### Dependencies as arguments
@@ -1783,7 +1814,7 @@ Args are passed to the constructor **as-is** — the library never resolves an `
1783
1814
  - `ServiceToken.args('literal')` — literal value passed directly
1784
1815
  - `ServiceToken.args(ValueToken)` — the token object itself is passed as arg
1785
1816
 
1786
- `argToToken(value)` is the helper for a call site that wants "resolve tokens, pass literals through": it returns an `InjectionToken` as-is and wraps anything else in a `ConstantToken`, so `@inject((scope, { args = [] }) => argToToken(args[0]).resolve(scope))` accepts either.
1817
+ `argToToken(value)` is the helper for a call site that wants "resolve tokens, pass literals through": it returns an `InjectionToken` as-is and wraps anything else in a `ConstantToken`, so `@inject(({ scope, args = [] }) => argToToken(args[0]).resolve(scope))` accepts either.
1787
1818
 
1788
1819
  ### Positional arg injection with `arg(index)`, `args`, and `argsFn`
1789
1820
 
@@ -1793,7 +1824,7 @@ Constructor parameters that should pick up positional args from `ProviderOptions
1793
1824
  - `@inject(args)` — resolves the whole runtime `args` array
1794
1825
  - Works together with `token.args(...)` / `token.argsFn(...)` to pass typed dependencies through the args context
1795
1826
 
1796
- `argsFn(predicate)` is the general form: it iterates the runtime `args` array and returns the **first argument matching** `predicate(value, index)` — think `args.find(predicate)`. `arg(index)` is just a shortcut for matching by position: `arg(0)` is `argsFn((value, index) => index === 0)`. `args` is `(scope, options) => options.args`, i.e. it returns the runtime args array as-is. Every `InjectFn` receives `(scope, options)`, where `options.args` is the runtime args array.
1827
+ `argsFn(predicate)` is the general form: it iterates the runtime `args` array and returns the **first argument matching** `predicate(value, index)` — think `args.find(predicate)`. `arg(index)` is just a shortcut for matching by position: `arg(0)` is `argsFn((value, index) => index === 0)`. `args` is `({ args }) => args`, i.e. it returns the runtime args array as-is. Every `InjectFn` receives one `ProviderOptions` object — `{ scope, args, lazy }` — where `args` is the runtime args array.
1797
1828
 
1798
1829
  `findOrFail(predicate)` is the strict counterpart for `singleton()` cache keys — `singleton(findOrFail(isUserId))` keys a per-argument singleton, for example. It receives the full runtime `args` array, returns the first argument matching `predicate(value)`, and throws `ArgumentNotFoundError` when none does, instead of silently handing out `undefined`.
1799
1830
 
@@ -1801,7 +1832,7 @@ Constructor parameters that should pick up positional args from `ProviderOptions
1801
1832
 
1802
1833
  Every container-backed token (`SingleToken`, `ClassToken`, `SingleAliasToken`, `GroupAliasToken`, `FunctionToken`) forwards the `args` of its own `resolve` call to the provider, exactly like `container.resolve(key, { args })` does. `token.args(...)` and `token.argsFn(...)` **append after** the runtime args, so `token.args('x').resolve(container, { args: ['r'] })` hands the provider `['r', 'x']`.
1803
1834
 
1804
- Because `@inject(token)` parameters are resolved with the args of the class being constructed, those args **cascade** into every injected dependency: they reach the dependency's provider, its `@inject(arg(index))` parameters, its `scopeAccess` rule and its `singleton()` cache key. That is what lets a per-user `UserService` share a per-user `UserRepository` without passing the id along by hand:
1835
+ Every `@inject` function is called with the args of the class being constructed, so an `InjectFn` which hands them on — `by(Token)` does, being `({ scope, ...options }) => Token.resolve(scope, options)` — **cascades** them into the injected dependency: they reach the dependency's provider, its `@inject(arg(index))` parameters, its `scopeAccess` rule and its `singleton()` cache key. One that does not — `({ scope }) => Token.resolve(scope)` — resolves the dependency with no args at all; which one a parameter wants is written at the parameter. The cascade is what lets a per-user `UserService` share a per-user `UserRepository` without passing the id along by hand:
1805
1836
 
1806
1837
  ```typescript
1807
1838
  import {
@@ -1814,6 +1845,7 @@ import {
1814
1845
  Registration as R,
1815
1846
  singleton,
1816
1847
  SingleToken,
1848
+ by,
1817
1849
  } from 'ts-ioc-container';
1818
1850
 
1819
1851
  interface IUserRepository {
@@ -1830,7 +1862,7 @@ class UserRepository implements IUserRepository {
1830
1862
  }
1831
1863
 
1832
1864
  class UserService {
1833
- constructor(@inject(IUserRepositoryKey) public repository: IUserRepository) {}
1865
+ constructor(@inject(by(IUserRepositoryKey)) public repository: IUserRepository) {}
1834
1866
  }
1835
1867
 
1836
1868
  describe('Token Runtime Arguments', function () {
@@ -1881,6 +1913,7 @@ import {
1881
1913
  Registration as R,
1882
1914
  SingleToken,
1883
1915
  singleton,
1916
+ by,
1884
1917
  } from 'ts-ioc-container';
1885
1918
 
1886
1919
  /**
@@ -1938,7 +1971,7 @@ describe('IProvider', function () {
1938
1971
  }
1939
1972
 
1940
1973
  // Extract 'env' from Config service dynamically
1941
- @register(appendArgsFn((scope) => [scope.resolve<Config>('Config').env]))
1974
+ @register(appendArgsFn(({ scope }) => [scope.resolve<Config>('Config').env]))
1942
1975
  class Service {
1943
1976
  constructor(@inject(arg(0)) public env: string) {}
1944
1977
  }
@@ -1974,7 +2007,7 @@ describe('IProvider', function () {
1974
2007
  tenant = 'tenant-a';
1975
2008
  }
1976
2009
 
1977
- @register(appendArgs('fixed'), appendArgsFn((scope) => [scope.resolve<Config>('Config').tenant]))
2010
+ @register(appendArgs('fixed'), appendArgsFn(({ scope }) => [scope.resolve<Config>('Config').tenant]))
1978
2011
  class Service {
1979
2012
  constructor(
1980
2013
  @inject(arg(0)) public runtime: string,
@@ -2033,11 +2066,11 @@ describe('IProvider', function () {
2033
2066
  class App {
2034
2067
  constructor(
2035
2068
  // Inject EntityManager configured for Users
2036
- @inject(withRepository(UserRepositoryToken))
2069
+ @inject(by(withRepository(UserRepositoryToken)))
2037
2070
  public userManager: EntityManager,
2038
2071
 
2039
2072
  // Inject EntityManager configured for Todos
2040
- @inject(withRepository(TodoRepositoryToken))
2073
+ @inject(by(withRepository(TodoRepositoryToken)))
2041
2074
  public todoManager: EntityManager,
2042
2075
  ) {}
2043
2076
  }
@@ -2200,6 +2233,7 @@ import {
2200
2233
  Registration as R,
2201
2234
  scope,
2202
2235
  select as s,
2236
+ by,
2203
2237
  } from 'ts-ioc-container';
2204
2238
 
2205
2239
  /**
@@ -2254,7 +2288,10 @@ describe('alias', () => {
2254
2288
  it('should notify through all channels', () => {
2255
2289
  // NotificationManager broadcasts to ALL registered channels
2256
2290
  class NotificationManager {
2257
- constructor(@inject(s.alias(INotificationChannel)) private channels: INotificationChannel[]) {}
2291
+ constructor(
2292
+ @inject(by(s.alias(INotificationChannel)))
2293
+ private channels: INotificationChannel[],
2294
+ ) {}
2258
2295
 
2259
2296
  notifyUser(userId: string, message: string): void {
2260
2297
  for (const channel of this.channels) {
@@ -2346,6 +2383,7 @@ import {
2346
2383
  Registration as R,
2347
2384
  select as s,
2348
2385
  singleton,
2386
+ by,
2349
2387
  } from 'ts-ioc-container';
2350
2388
 
2351
2389
  /**
@@ -2391,7 +2429,7 @@ describe('Decorator Pattern', () => {
2391
2429
  class LoggingRepository implements IRepository {
2392
2430
  constructor(
2393
2431
  @inject(arg(0)) private repository: IRepository,
2394
- @inject(s.token('Logger').lazy()) private logger: Logger,
2432
+ @inject(by(s.token('Logger').lazy())) private logger: Logger,
2395
2433
  ) {}
2396
2434
 
2397
2435
  async save(item: Todo): Promise<void> {
@@ -2415,7 +2453,7 @@ describe('Decorator Pattern', () => {
2415
2453
  }
2416
2454
 
2417
2455
  class App {
2418
- constructor(@inject('IRepository') public repository: IRepository) {}
2456
+ constructor(@inject(by('IRepository')) public repository: IRepository) {}
2419
2457
 
2420
2458
  async run() {
2421
2459
  await this.repository.save({ id: '1', text: 'Buy groceries' });
@@ -2533,7 +2571,7 @@ Registration is provider factory which registers provider in container.
2533
2571
  - `Registration.fromClass(Logger).bindTo('logger')`
2534
2572
  - `Registration.fromClass(Logger)`
2535
2573
  - `Registration.fromValue(Logger)`
2536
- - `Registration.fromFn((container, options) => container.resolve(Logger, options))`
2574
+ - `Registration.fromFn(({ scope, ...options }) => scope.resolve(Logger, options))`
2537
2575
 
2538
2576
  ### Token
2539
2577
 
@@ -2673,6 +2711,7 @@ import {
2673
2711
  scope,
2674
2712
  select,
2675
2713
  singleton,
2714
+ by,
2676
2715
  } from 'ts-ioc-container';
2677
2716
 
2678
2717
  /**
@@ -2732,7 +2771,10 @@ describe('Scopes', function () {
2732
2771
 
2733
2772
  // RequestHandler can create a transaction scope for database operations
2734
2773
  class RequestHandler {
2735
- constructor(@inject(select.scope.create({ tags: ['transaction'] })) public transactionScope: IContainer) {}
2774
+ constructor(
2775
+ @inject(by(select.scope.create({ tags: ['transaction'] })))
2776
+ public transactionScope: IContainer,
2777
+ ) {}
2736
2778
 
2737
2779
  executeInTransaction(): boolean {
2738
2780
  // Transaction scope inherits from request scope
@@ -3103,6 +3145,7 @@ import {
3103
3145
  toTask,
3104
3146
  type ExecutionContext,
3105
3147
  type HookAction,
3148
+ by,
3106
3149
  } from 'ts-ioc-container';
3107
3150
 
3108
3151
  const execute: HookFn = (ctx) => {
@@ -3151,7 +3194,7 @@ describe('onConstruct', function () {
3151
3194
  connectionString = '';
3152
3195
 
3153
3196
  @onConstruct(execute)
3154
- connect(@inject('ConnectionString') connectionString: string) {
3197
+ connect(@inject(by('ConnectionString')) connectionString: string) {
3155
3198
  this.connectionString = connectionString;
3156
3199
  this.isConnected = true;
3157
3200
  }
@@ -3229,7 +3272,7 @@ describe('onConstruct', function () {
3229
3272
  }
3230
3273
 
3231
3274
  @onConstruct(executeAsync)
3232
- async connect(@inject('ConnectionString') connectionString: string) {
3275
+ async connect(@inject(by('ConnectionString')) connectionString: string) {
3233
3276
  await Promise.resolve();
3234
3277
  this.connectionString = connectionString;
3235
3278
  this.isConnected = true;
@@ -3300,6 +3343,7 @@ import {
3300
3343
  type ExecutionContext,
3301
3344
  type HookAction,
3302
3345
  type IContainerModule,
3346
+ by,
3303
3347
  } from 'ts-ioc-container';
3304
3348
 
3305
3349
  const execute: HookFn = (ctx) => {
@@ -3349,7 +3393,7 @@ class LogsRepo {
3349
3393
  class Logger {
3350
3394
  private messages: string[] = [];
3351
3395
 
3352
- constructor(@inject('logsRepo') private logsRepo: LogsRepo) {}
3396
+ constructor(@inject(by('logsRepo')) private logsRepo: LogsRepo) {}
3353
3397
 
3354
3398
  log(message: string): void {
3355
3399
  this.messages.push(message);
@@ -3384,7 +3428,7 @@ describe('onScopeDisposed', function () {
3384
3428
 
3385
3429
  ```typescript
3386
3430
  import 'reflect-metadata';
3387
- import { Container, hook, HookCollector, injectProp, Registration, sequential, toTask } from 'ts-ioc-container';
3431
+ import { Container, hook, HookCollector, injectProp, Registration, sequential, toTask, by } from 'ts-ioc-container';
3388
3432
 
3389
3433
  /**
3390
3434
  * UI Components - Property Injection
@@ -3404,7 +3448,7 @@ describe('inject property', () => {
3404
3448
 
3405
3449
  class UserViewModel {
3406
3450
  // Inject 'GreetingService' into 'greeting' property during 'onInit'
3407
- @hook('onInit', injectProp('GreetingService'))
3451
+ @hook('onInit', injectProp(by('GreetingService')))
3408
3452
  greetingService!: string;
3409
3453
 
3410
3454
  display(): string {
@@ -3435,7 +3479,7 @@ describe('inject property', () => {
3435
3479
  class UserViewModel {
3436
3480
  @hook(
3437
3481
  'onInit',
3438
- sequential(injectProp('GreetingService'), (context) => {
3482
+ sequential(injectProp(by('GreetingService')), (context) => {
3439
3483
  injectedValue = context.getProperty();
3440
3484
  }),
3441
3485
  )
@@ -41,11 +41,11 @@ class Container {
41
41
  resolve(target, { args = [], child = this, lazy } = {}) {
42
42
  this.validateContainer();
43
43
  if (basic_1.Is.constructor(target)) {
44
- return this.injector.resolve(this, target, { args, lazy });
44
+ return this.injector.resolve(target, { scope: this, args, lazy });
45
45
  }
46
46
  const provider = this.providers.get(target);
47
47
  return provider?.hasAccess({ invocationScope: child, providerScope: this, args })
48
- ? provider.resolve(this, { args, lazy })
48
+ ? provider.resolve({ scope: this, args, lazy })
49
49
  : this.parent.resolve(target, { args, child, lazy });
50
50
  }
51
51
  resolveByAlias(alias, { args = [], child = this, lazy, excludedKeys = [] } = {}) {
@@ -58,7 +58,7 @@ class Container {
58
58
  continue;
59
59
  }
60
60
  keys.push(key);
61
- deps.push(provider.resolve(this, { args, lazy }));
61
+ deps.push(provider.resolve({ scope: this, args, lazy }));
62
62
  }
63
63
  const parentDeps = this.parent.resolveByAlias(alias, {
64
64
  args,
@@ -73,7 +73,7 @@ class Container {
73
73
  const [key] = this.aliases.getKeysByAlias(alias);
74
74
  const provider = key ? this.findProviderByKeyOrFail(key) : undefined;
75
75
  return provider?.hasAccess({ invocationScope: child, providerScope: this, args })
76
- ? provider.resolve(this, { args, lazy })
76
+ ? provider.resolve({ scope: this, args, lazy })
77
77
  : this.parent.resolveOneByAlias(alias, { args, child, lazy });
78
78
  }
79
79
  createScope({ tags } = {}) {
@@ -93,7 +93,7 @@ class Container {
93
93
  this.validateContainer();
94
94
  for (const provider of this.providers.values()) {
95
95
  if (provider.isAutoResolvable() && provider.hasAccess({ invocationScope: this, providerScope: this, args })) {
96
- provider.resolve(this, { args });
96
+ provider.resolve({ scope: this, args });
97
97
  }
98
98
  }
99
99
  return this;
@@ -13,7 +13,8 @@ class HookContext {
13
13
  this.methodName = methodName;
14
14
  }
15
15
  resolveArgs(...args) {
16
- return (0, MetadataInjector_1.resolveArgs)(this.instance, this.methodName)(this.scope, {
16
+ return (0, MetadataInjector_1.resolveArgs)(this.instance, this.methodName)({
17
+ scope: this.scope,
17
18
  args: [...this.initialArgs, ...args],
18
19
  });
19
20
  }
@@ -21,7 +22,7 @@ class HookContext {
21
22
  return this.instance[this.methodName](...args);
22
23
  }
23
24
  setProperty(fn) {
24
- this.instance[this.methodName] = fn.resolve(this.scope);
25
+ this.instance[this.methodName] = fn({ scope: this.scope });
25
26
  }
26
27
  getProperty() {
27
28
  return this.instance[this.methodName];
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.injectProp = void 0;
4
+ const injectProp = (fn) => (context) => context.setProperty(fn);
3
5
  exports.injectProp = injectProp;
4
- const toToken_1 = require("../token/toToken");
5
- function injectProp(fn, ...mappers) {
6
- return (context) => context.setProperty((0, toToken_1.toMappedToken)(fn, mappers));
7
- }