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.
- package/README.md +109 -65
- package/cjm/container/Container.js +5 -5
- package/cjm/hooks/HookContext.js +3 -2
- package/cjm/hooks/injectProp.js +2 -4
- package/cjm/index.js +3 -3
- package/cjm/injector/IInjector.js +2 -2
- package/cjm/injector/MetadataInjector.js +14 -9
- package/cjm/injector/ProxyInjector.js +1 -1
- package/cjm/injector/SimpleInjector.js +2 -2
- package/cjm/provider/Provider.js +9 -8
- package/cjm/registration/IRegistration.js +2 -2
- package/cjm/select.js +2 -2
- package/cjm/token/ClassToken.js +3 -3
- package/cjm/token/FunctionToken.js +5 -4
- package/cjm/token/GroupAliasToken.js +3 -3
- package/cjm/token/InjectionToken.js +1 -1
- package/cjm/token/SingleAliasToken.js +3 -3
- package/cjm/token/SingleToken.js +3 -3
- package/cjm/token/toToken.js +1 -8
- package/esm/container/Container.js +5 -5
- package/esm/hooks/HookContext.js +3 -2
- package/esm/hooks/injectProp.js +1 -4
- package/esm/index.js +2 -2
- package/esm/injector/IInjector.js +2 -2
- package/esm/injector/MetadataInjector.js +13 -9
- package/esm/injector/ProxyInjector.js +1 -1
- package/esm/injector/SimpleInjector.js +2 -2
- package/esm/provider/Provider.js +9 -8
- package/esm/registration/IRegistration.js +2 -2
- package/esm/select.js +2 -2
- package/esm/token/ClassToken.js +3 -3
- package/esm/token/FunctionToken.js +5 -4
- package/esm/token/GroupAliasToken.js +3 -3
- package/esm/token/InjectionToken.js +1 -1
- package/esm/token/SingleAliasToken.js +3 -3
- package/esm/token/SingleToken.js +3 -3
- package/esm/token/toToken.js +0 -6
- package/package.json +1 -1
- package/typings/container/IContainer.d.ts +2 -2
- package/typings/hooks/HookContext.d.ts +3 -3
- package/typings/hooks/hook.d.ts +1 -2
- package/typings/hooks/injectProp.d.ts +2 -14
- package/typings/index.d.ts +4 -4
- package/typings/injector/IInjector.d.ts +7 -4
- package/typings/injector/MetadataInjector.d.ts +6 -16
- package/typings/injector/ProxyInjector.d.ts +1 -2
- package/typings/injector/SimpleInjector.d.ts +1 -2
- package/typings/provider/IProvider.d.ts +5 -4
- package/typings/provider/Provider.d.ts +2 -2
- package/typings/select.d.ts +3 -3
- package/typings/token/ClassToken.d.ts +2 -2
- package/typings/token/FunctionToken.d.ts +2 -2
- package/typings/token/GroupAliasToken.d.ts +2 -2
- package/typings/token/InjectionToken.d.ts +2 -2
- package/typings/token/SingleAliasToken.d.ts +3 -3
- package/typings/token/SingleToken.d.ts +2 -2
- 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((
|
|
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
|
-
-
|
|
165
|
-
-
|
|
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(
|
|
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(
|
|
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((
|
|
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
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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.
|
|
1208
|
-
|
|
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
|
-
*
|
|
1220
|
-
* mapper
|
|
1221
|
-
*
|
|
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(
|
|
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(
|
|
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((
|
|
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((
|
|
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((
|
|
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,
|
|
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 `(
|
|
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
|
-
|
|
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(
|
|
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((
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
96
|
+
provider.resolve({ scope: this, args });
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
return this;
|
package/cjm/hooks/HookContext.js
CHANGED
|
@@ -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)(
|
|
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
|
|
25
|
+
this.instance[this.methodName] = fn({ scope: this.scope });
|
|
25
26
|
}
|
|
26
27
|
getProperty() {
|
|
27
28
|
return this.instance[this.methodName];
|
package/cjm/hooks/injectProp.js
CHANGED
|
@@ -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
|
-
}
|