@decaf-ts/injectable-decorators 1.6.7 → 1.6.9

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
@@ -18,12 +18,6 @@ A lightweight TypeScript dependency injection library that provides decorators f
18
18
  ![Pull Requests](https://img.shields.io/github/issues-pr-closed/decaf-ts/injectable-decorators.svg)
19
19
  ![Maintained](https://img.shields.io/badge/Maintained%3F-yes-green.svg)
20
20
 
21
- ![Line Coverage](workdocs/reports/coverage/badge-lines.svg)
22
- ![Function Coverage](workdocs/reports/coverage/badge-functions.svg)
23
- ![Statement Coverage](workdocs/reports/coverage/badge-statements.svg)
24
- ![Branch Coverage](workdocs/reports/coverage/badge-branches.svg)
25
-
26
-
27
21
  ![Forks](https://img.shields.io/github/forks/decaf-ts/injectable-decorators.svg)
28
22
  ![Stars](https://img.shields.io/github/stars/decaf-ts/injectable-decorators.svg)
29
23
  ![Watchers](https://img.shields.io/github/watchers/decaf-ts/injectable-decorators.svg)
@@ -82,237 +76,221 @@ Unlike more complex DI frameworks, this library doesn't require extensive config
82
76
 
83
77
  ### How to Use
84
78
 
85
- - [Initial Setup](./tutorials/For%20Developers.md#_initial-setup_)
86
- - [Installation](./tutorials/For%20Developers.md#installation)
79
+ - [Initial Setup](./workdocs/tutorials/For%20Developers.md#_initial-setup_)
80
+ - [Installation](./workdocs/tutorials/For%20Developers.md#installation)
87
81
 
88
82
  ## Basic Usage Examples
89
83
 
90
- ### Creating an Injectable Service
84
+ ### 1) Mark a class as injectable and get it from the registry
91
85
 
92
- **Use Case**: You want to create a service that can be injected into other components of your application.
86
+ Description: Define a class with @injectable() so it becomes available through the central registry. Creating with new returns the instance managed by the registry.
93
87
 
94
88
  ```typescript
95
- import { injectable } from 'injectable-decorators';
89
+ import 'reflect-metadata';
90
+ import { injectable, Injectables } from 'injectable-decorators';
96
91
 
97
92
  @injectable()
98
- class LoggerService {
99
- log(message: string): void {
100
- console.log(`[LOG]: ${message}`);
101
- }
102
-
103
- error(message: string): void {
104
- console.error(`[ERROR]: ${message}`);
105
- }
93
+ class InitialObject {
94
+ doSomething() { return 5; }
106
95
  }
107
96
 
108
- // The service is automatically registered in the Injectables registry
109
- // and will be available for injection
97
+ const obj = new InitialObject();
98
+ const same = Injectables.get(InitialObject);
99
+ // obj and same refer to the same instance (singleton by default)
110
100
  ```
111
101
 
112
- ### Injecting a Service into a Component
102
+ ### 2) Inject a dependency into a property
113
103
 
114
- **Use Case**: You want to use a service in a component without manually creating an instance.
104
+ Description: Use @inject() on a typed property. The instance is created lazily when the property is first accessed and cached thereafter.
115
105
 
116
106
  ```typescript
117
- import { inject } from 'injectable-decorators';
118
- import { LoggerService } from './logger.service';
107
+ import 'reflect-metadata';
108
+ import { injectable, inject, Injectables } from 'injectable-decorators';
119
109
 
120
- class UserComponent {
121
- @inject()
122
- private logger!: LoggerService;
110
+ @injectable()
111
+ class SomeService { value = 5; }
123
112
 
124
- createUser(username: string): void {
125
- this.logger.log(`Creating user: ${username}`);
126
- // User creation logic...
127
- this.logger.log(`User ${username} created successfully`);
128
- }
113
+ class Controller {
114
+ @inject()
115
+ service!: SomeService; // non-null assertion because it's set outside the constructor
129
116
  }
130
117
 
131
- // When the logger property is accessed, the LoggerService instance
132
- // will be automatically injected
118
+ const c = new Controller();
119
+ console.log(c.service.value); // 5
120
+ console.log(c.service === Injectables.get(SomeService)); // true
133
121
  ```
134
122
 
135
- ### Using a Custom Category Name
123
+ ### 3) Use a custom category (string) for minification or upcasting
136
124
 
137
- **Use Case**: You want to ensure your injectables work correctly even after code minification, or you want to use a different name for the injectable.
125
+ Description: Provide a stable name when class names may change (e.g., minification) or to upcast through a base type.
138
126
 
139
127
  ```typescript
140
- import { injectable } from 'injectable-decorators';
141
-
142
- @injectable('AuthService')
143
- class AuthenticationService {
144
- authenticate(username: string, password: string): boolean {
145
- // Authentication logic...
146
- return true;
147
- }
148
- }
128
+ import 'reflect-metadata';
129
+ import { injectable, inject, singleton } from 'injectable-decorators';
149
130
 
150
- class LoginComponent {
151
- @inject('AuthService')
152
- private auth!: AuthenticationService;
153
-
154
- login(username: string, password: string): void {
155
- if (this.auth.authenticate(username, password)) {
156
- console.log('Login successful');
157
- } else {
158
- console.log('Login failed');
159
- }
160
- }
131
+ @singleton()
132
+ class AAA { a = 'aaa'; }
133
+
134
+ @injectable('AAA')
135
+ class BBB extends AAA { b = 'bbb'; }
136
+
137
+ const b = new BBB();
138
+
139
+ class Host {
140
+ @inject()
141
+ repo!: AAA; // resolves to the instance registered under category 'AAA'
161
142
  }
143
+
144
+ const h = new Host();
145
+ console.log(h.repo === b); // true
162
146
  ```
163
147
 
164
- ### Using a Transformer with Inject
148
+ ### 4) Inject by explicit category (string)
165
149
 
166
- **Use Case**: You want to transform or configure an injectable instance before it's used.
150
+ Description: When a different string category was used at registration, pass that string to @inject.
167
151
 
168
152
  ```typescript
169
- import { inject, InstanceTransformer } from 'injectable-decorators';
153
+ import 'reflect-metadata';
154
+ import { inject, singleton } from 'injectable-decorators';
170
155
 
171
- @injectable()
172
- class ConfigService {
173
- private config: Record<string, any> = {
174
- apiUrl: 'https://api.example.com',
175
- timeout: 5000
176
- };
177
-
178
- get(key: string): any {
179
- return this.config[key];
180
- }
156
+ class DDD { a = 'aaa'; }
157
+
158
+ @singleton('EEE')
159
+ class CCC extends DDD { b = 'bbb'; }
160
+
161
+ const instance = new CCC();
162
+
163
+ class Holder {
164
+ @inject('EEE')
165
+ repo!: CCC;
181
166
  }
182
167
 
183
- // Transformer function that adds environment-specific configuration
184
- const configTransformer: InstanceTransformer = (config: ConfigService, target: any) => {
185
- // You could customize the config based on the target or environment
186
- return config;
187
- };
168
+ const h = new Holder();
169
+ console.log(h.repo === instance); // true
170
+ ```
188
171
 
189
- class ApiClient {
190
- @inject(undefined, configTransformer)
191
- private config!: ConfigService;
172
+ ### 5) Map one constructor to another and inject by constructor
192
173
 
193
- fetchData(): Promise<any> {
194
- const apiUrl = this.config.get('apiUrl');
195
- const timeout = this.config.get('timeout');
174
+ Description: You can register an injectable using another constructor as the category, then inject it by that constructor.
196
175
 
197
- // Use the configured values...
198
- return Promise.resolve({ data: 'example' });
199
- }
176
+ ```typescript
177
+ import 'reflect-metadata';
178
+ import { injectable, inject } from 'injectable-decorators';
179
+
180
+ class Token {}
181
+
182
+ @injectable(Token, { callback: (original) => original })
183
+ class Impl {
184
+ id = 1;
200
185
  }
186
+
187
+ class UsesImpl {
188
+ @inject(Token)
189
+ object!: Impl; // injects the instance registered under Token (Impl instance)
190
+ }
191
+
192
+ const u = new UsesImpl();
193
+ console.log(u.object instanceof Impl); // true
201
194
  ```
202
195
 
203
- ### Manually Registering and Retrieving Injectables
196
+ ### 6) Non-singleton injectables with @onDemand and passing constructor args
204
197
 
205
- **Use Case**: You want to manually register an existing instance or retrieve an injectable instance directly.
198
+ Description: Use @onDemand() so each injection produces a fresh instance. You can pass args for construction via @inject({ args }).
206
199
 
207
200
  ```typescript
208
- import { Injectables } from 'injectable-decorators';
209
-
210
- // Register an existing instance
211
- const databaseConnection = {
212
- query: (sql: string) => Promise.resolve([]),
213
- close: () => Promise.resolve()
214
- };
215
-
216
- Injectables.register(databaseConnection, 'DatabaseConnection');
217
-
218
- // Retrieve the instance elsewhere in your code
219
- class QueryService {
220
- private db = Injectables.get<typeof databaseConnection>('DatabaseConnection');
221
-
222
- async executeQuery(sql: string): Promise<any[]> {
223
- if (!this.db) {
224
- throw new Error('Database connection not available');
225
- }
226
- return this.db.query(sql);
227
- }
201
+ import 'reflect-metadata';
202
+ import { onDemand, inject } from 'injectable-decorators';
203
+
204
+ @onDemand()
205
+ class FreshObject {
206
+ constructor(public a?: string, public b?: string) {}
207
+ }
208
+
209
+ class ParentA {
210
+ @inject()
211
+ fresh!: FreshObject; // new instance per parent
212
+ }
213
+
214
+ class ParentB {
215
+ @inject({ args: ['x', 'y'] })
216
+ fresh!: FreshObject; // passes constructor args to on-demand instance
228
217
  }
218
+
219
+ const p1 = new ParentA();
220
+ const p2 = new ParentA();
221
+ console.log(p1.fresh !== p2.fresh); // true
222
+
223
+ const p3 = new ParentB();
224
+ console.log([p3.fresh.a, p3.fresh.b]); // ['x','y']
229
225
  ```
230
226
 
231
- ### Creating a Custom Injectable Registry
227
+ ### 7) Transform an injected value
232
228
 
233
- **Use Case**: You want to customize how injectables are stored and retrieved, perhaps for testing or to add additional functionality.
229
+ Description: Modify the resolved instance before assignment using a transformer.
234
230
 
235
231
  ```typescript
236
- import { Injectables, InjectablesRegistry } from 'injectable-decorators';
237
-
238
- // Create a custom registry implementation
239
- class LoggingRegistry implements InjectablesRegistry {
240
- private defaultRegistry: InjectablesRegistry;
241
-
242
- constructor(defaultRegistry: InjectablesRegistry) {
243
- this.defaultRegistry = defaultRegistry;
244
- }
245
-
246
- get<T>(name: string, ...args: any[]): T | undefined {
247
- console.log(`Getting injectable: ${name}`);
248
- return this.defaultRegistry.get<T>(name, ...args);
249
- }
250
-
251
- register<T>(constructor: any, ...args: any[]): void {
252
- console.log(`Registering injectable: ${args[0] || constructor.name}`);
253
- return this.defaultRegistry.register(constructor, ...args);
254
- }
255
-
256
- build<T>(obj: Record<string, any>, ...args: any[]): T {
257
- console.log(`Building injectable: ${obj.name}`);
258
- return this.defaultRegistry.build<T>(obj, ...args);
259
- }
232
+ import 'reflect-metadata';
233
+ import { injectable, inject } from 'injectable-decorators';
234
+
235
+ @injectable('SomeOtherObject')
236
+ class SomeOtherObject { value() { return 10; } }
237
+
238
+ class Controller {
239
+ @inject({ transformer: (obj: SomeOtherObject, c: Controller) => '1' })
240
+ repo!: SomeOtherObject | string;
260
241
  }
261
242
 
262
- // Use the custom registry
263
- import { InjectableRegistryImp } from 'injectable-decorators';
264
- const customRegistry = new LoggingRegistry(new InjectableRegistryImp());
265
- Injectables.setRegistry(customRegistry);
243
+ const c = new Controller();
244
+ console.log(c.repo); // '1'
266
245
  ```
267
246
 
268
- ### Resetting the Registry
247
+ ### 8) Registry operations: reset and swapping registry
269
248
 
270
- **Use Case**: You want to clear all registered injectables, perhaps for testing or when switching application contexts.
249
+ Description: Reset clears all registrations. Swapping the registry replaces the storage, losing previous entries.
271
250
 
272
251
  ```typescript
273
- import { Injectables } from 'injectable-decorators';
252
+ import { Injectables, InjectableRegistryImp } from 'injectable-decorators';
253
+
254
+ // ensure something is registered
255
+ Injectables.get('SomeOtherObject');
274
256
 
275
- // Reset all injectables
257
+ // swap to a fresh registry
258
+ Injectables.setRegistry(new InjectableRegistryImp());
259
+ console.log(Injectables.get('SomeOtherObject')); // undefined
260
+
261
+ // reset to a new empty default registry
276
262
  Injectables.reset();
263
+ ```
264
+
265
+ ### 9) Singleton vs onDemand convenience decorators
266
+
267
+ Description: Prefer @singleton() to force single instance, or @onDemand() for new instance per retrieval.
268
+
269
+ ```typescript
270
+ import { singleton, onDemand } from 'injectable-decorators';
271
+
272
+ @singleton()
273
+ class OneOnly {}
277
274
 
278
- // Selectively reset injectables matching a pattern
279
- Injectables.selectiveReset(/^Auth/); // Resets all injectables whose names start with "Auth"
275
+ @onDemand()
276
+ class Many {}
280
277
  ```
281
278
 
282
- ### Using Callback with Injectable
279
+ ### 10) Utility helpers and constants
283
280
 
284
- **Use Case**: You want to perform additional setup on an injectable instance after it's created.
281
+ Description: Generate reflection keys and understand default config.
285
282
 
286
283
  ```typescript
287
- import { injectable } from 'injectable-decorators';
288
-
289
- const setupLogger = (instance: LoggerService) => {
290
- // Configure the logger after instantiation
291
- instance.setLogLevel('debug');
292
- instance.enableConsoleOutput(true);
293
- };
294
-
295
- @injectable(undefined, false, setupLogger)
296
- class LoggerService {
297
- private logLevel: string = 'info';
298
- private consoleOutput: boolean = false;
299
-
300
- setLogLevel(level: string): void {
301
- this.logLevel = level;
302
- }
303
-
304
- enableConsoleOutput(enabled: boolean): void {
305
- this.consoleOutput = enabled;
306
- }
307
-
308
- log(message: string): void {
309
- if (this.consoleOutput) {
310
- console.log(`[${this.logLevel.toUpperCase()}]: ${message}`);
311
- }
312
- }
313
- }
284
+ import { getInjectKey } from 'injectable-decorators';
285
+
286
+ console.log(getInjectKey('injectable')); // "inject.db.injectable"
287
+ console.log(getInjectKey('inject')); // "inject.db.inject"
314
288
  ```
315
289
 
290
+ Notes:
291
+ - Always include `import 'reflect-metadata'` once in your app before using decorators.
292
+ - VERSION is exported as a string placeholder defined at build time.
293
+
316
294
 
317
295
  ### Related
318
296