@decaf-ts/injectable-decorators 1.6.8 → 1.6.10

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
@@ -81,232 +81,216 @@ Unlike more complex DI frameworks, this library doesn't require extensive config
81
81
 
82
82
  ## Basic Usage Examples
83
83
 
84
- ### Creating an Injectable Service
84
+ ### 1) Mark a class as injectable and get it from the registry
85
85
 
86
- **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.
87
87
 
88
88
  ```typescript
89
- import { injectable } from 'injectable-decorators';
89
+ import 'reflect-metadata';
90
+ import { injectable, Injectables } from 'injectable-decorators';
90
91
 
91
92
  @injectable()
92
- class LoggerService {
93
- log(message: string): void {
94
- console.log(`[LOG]: ${message}`);
95
- }
96
-
97
- error(message: string): void {
98
- console.error(`[ERROR]: ${message}`);
99
- }
93
+ class InitialObject {
94
+ doSomething() { return 5; }
100
95
  }
101
96
 
102
- // The service is automatically registered in the Injectables registry
103
- // 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)
104
100
  ```
105
101
 
106
- ### Injecting a Service into a Component
102
+ ### 2) Inject a dependency into a property
107
103
 
108
- **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.
109
105
 
110
106
  ```typescript
111
- import { inject } from 'injectable-decorators';
112
- import { LoggerService } from './logger.service';
107
+ import 'reflect-metadata';
108
+ import { injectable, inject, Injectables } from 'injectable-decorators';
113
109
 
114
- class UserComponent {
115
- @inject()
116
- private logger!: LoggerService;
110
+ @injectable()
111
+ class SomeService { value = 5; }
117
112
 
118
- createUser(username: string): void {
119
- this.logger.log(`Creating user: ${username}`);
120
- // User creation logic...
121
- this.logger.log(`User ${username} created successfully`);
122
- }
113
+ class Controller {
114
+ @inject()
115
+ service!: SomeService; // non-null assertion because it's set outside the constructor
123
116
  }
124
117
 
125
- // When the logger property is accessed, the LoggerService instance
126
- // 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
127
121
  ```
128
122
 
129
- ### Using a Custom Category Name
123
+ ### 3) Use a custom category (string) for minification or upcasting
130
124
 
131
- **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.
132
126
 
133
127
  ```typescript
134
- import { injectable } from 'injectable-decorators';
135
-
136
- @injectable('AuthService')
137
- class AuthenticationService {
138
- authenticate(username: string, password: string): boolean {
139
- // Authentication logic...
140
- return true;
141
- }
142
- }
128
+ import 'reflect-metadata';
129
+ import { injectable, inject, singleton } from 'injectable-decorators';
130
+
131
+ @singleton()
132
+ class AAA { a = 'aaa'; }
133
+
134
+ @injectable('AAA')
135
+ class BBB extends AAA { b = 'bbb'; }
143
136
 
144
- class LoginComponent {
145
- @inject('AuthService')
146
- private auth!: AuthenticationService;
147
-
148
- login(username: string, password: string): void {
149
- if (this.auth.authenticate(username, password)) {
150
- console.log('Login successful');
151
- } else {
152
- console.log('Login failed');
153
- }
154
- }
137
+ const b = new BBB();
138
+
139
+ class Host {
140
+ @inject()
141
+ repo!: AAA; // resolves to the instance registered under category 'AAA'
155
142
  }
143
+
144
+ const h = new Host();
145
+ console.log(h.repo === b); // true
156
146
  ```
157
147
 
158
- ### Using a Transformer with Inject
148
+ ### 4) Inject by explicit category (string)
159
149
 
160
- **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.
161
151
 
162
152
  ```typescript
163
- import { inject, InstanceTransformer } from 'injectable-decorators';
153
+ import 'reflect-metadata';
154
+ import { inject, singleton } from 'injectable-decorators';
164
155
 
165
- @injectable()
166
- class ConfigService {
167
- private config: Record<string, any> = {
168
- apiUrl: 'https://api.example.com',
169
- timeout: 5000
170
- };
171
-
172
- get(key: string): any {
173
- return this.config[key];
174
- }
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;
175
166
  }
176
167
 
177
- // Transformer function that adds environment-specific configuration
178
- const configTransformer: InstanceTransformer = (config: ConfigService, target: any) => {
179
- // You could customize the config based on the target or environment
180
- return config;
181
- };
168
+ const h = new Holder();
169
+ console.log(h.repo === instance); // true
170
+ ```
171
+
172
+ ### 5) Map one constructor to another and inject by constructor
173
+
174
+ Description: You can register an injectable using another constructor as the category, then inject it by that constructor.
182
175
 
183
- class ApiClient {
184
- @inject(undefined, configTransformer)
185
- private config!: ConfigService;
176
+ ```typescript
177
+ import 'reflect-metadata';
178
+ import { injectable, inject } from 'injectable-decorators';
179
+
180
+ class Token {}
186
181
 
187
- fetchData(): Promise<any> {
188
- const apiUrl = this.config.get('apiUrl');
189
- const timeout = this.config.get('timeout');
182
+ @injectable(Token, { callback: (original) => original })
183
+ class Impl {
184
+ id = 1;
185
+ }
190
186
 
191
- // Use the configured values...
192
- return Promise.resolve({ data: 'example' });
193
- }
187
+ class UsesImpl {
188
+ @inject(Token)
189
+ object!: Impl; // injects the instance registered under Token (Impl instance)
194
190
  }
191
+
192
+ const u = new UsesImpl();
193
+ console.log(u.object instanceof Impl); // true
195
194
  ```
196
195
 
197
- ### Manually Registering and Retrieving Injectables
196
+ ### 6) Non-singleton injectables with @onDemand and passing constructor args
198
197
 
199
- **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 }).
200
199
 
201
200
  ```typescript
202
- import { Injectables } from 'injectable-decorators';
203
-
204
- // Register an existing instance
205
- const databaseConnection = {
206
- query: (sql: string) => Promise.resolve([]),
207
- close: () => Promise.resolve()
208
- };
209
-
210
- Injectables.register(databaseConnection, 'DatabaseConnection');
211
-
212
- // Retrieve the instance elsewhere in your code
213
- class QueryService {
214
- private db = Injectables.get<typeof databaseConnection>('DatabaseConnection');
215
-
216
- async executeQuery(sql: string): Promise<any[]> {
217
- if (!this.db) {
218
- throw new Error('Database connection not available');
219
- }
220
- return this.db.query(sql);
221
- }
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) {}
222
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
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']
223
225
  ```
224
226
 
225
- ### Creating a Custom Injectable Registry
227
+ ### 7) Transform an injected value
226
228
 
227
- **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.
228
230
 
229
231
  ```typescript
230
- import { Injectables, InjectablesRegistry } from 'injectable-decorators';
231
-
232
- // Create a custom registry implementation
233
- class LoggingRegistry implements InjectablesRegistry {
234
- private defaultRegistry: InjectablesRegistry;
235
-
236
- constructor(defaultRegistry: InjectablesRegistry) {
237
- this.defaultRegistry = defaultRegistry;
238
- }
239
-
240
- get<T>(name: string, ...args: any[]): T | undefined {
241
- console.log(`Getting injectable: ${name}`);
242
- return this.defaultRegistry.get<T>(name, ...args);
243
- }
244
-
245
- register<T>(constructor: any, ...args: any[]): void {
246
- console.log(`Registering injectable: ${args[0] || constructor.name}`);
247
- return this.defaultRegistry.register(constructor, ...args);
248
- }
249
-
250
- build<T>(obj: Record<string, any>, ...args: any[]): T {
251
- console.log(`Building injectable: ${obj.name}`);
252
- return this.defaultRegistry.build<T>(obj, ...args);
253
- }
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;
254
241
  }
255
242
 
256
- // Use the custom registry
257
- import { InjectableRegistryImp } from 'injectable-decorators';
258
- const customRegistry = new LoggingRegistry(new InjectableRegistryImp());
259
- Injectables.setRegistry(customRegistry);
243
+ const c = new Controller();
244
+ console.log(c.repo); // '1'
260
245
  ```
261
246
 
262
- ### Resetting the Registry
247
+ ### 8) Registry operations: reset and swapping registry
263
248
 
264
- **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.
265
250
 
266
251
  ```typescript
267
- import { Injectables } from 'injectable-decorators';
252
+ import { Injectables, InjectableRegistryImp } from 'injectable-decorators';
253
+
254
+ // ensure something is registered
255
+ Injectables.get('SomeOtherObject');
268
256
 
269
- // 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
270
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 {}
271
274
 
272
- // Selectively reset injectables matching a pattern
273
- Injectables.selectiveReset(/^Auth/); // Resets all injectables whose names start with "Auth"
275
+ @onDemand()
276
+ class Many {}
274
277
  ```
275
278
 
276
- ### Using Callback with Injectable
279
+ ### 10) Utility helpers and constants
277
280
 
278
- **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.
279
282
 
280
283
  ```typescript
281
- import { injectable } from 'injectable-decorators';
282
-
283
- const setupLogger = (instance: LoggerService) => {
284
- // Configure the logger after instantiation
285
- instance.setLogLevel('debug');
286
- instance.enableConsoleOutput(true);
287
- };
288
-
289
- @injectable(undefined, false, setupLogger)
290
- class LoggerService {
291
- private logLevel: string = 'info';
292
- private consoleOutput: boolean = false;
293
-
294
- setLogLevel(level: string): void {
295
- this.logLevel = level;
296
- }
297
-
298
- enableConsoleOutput(enabled: boolean): void {
299
- this.consoleOutput = enabled;
300
- }
301
-
302
- log(message: string): void {
303
- if (this.consoleOutput) {
304
- console.log(`[${this.logLevel.toUpperCase()}]: ${message}`);
305
- }
306
- }
307
- }
284
+ import { getInjectKey } from 'injectable-decorators';
285
+
286
+ console.log(getInjectKey('injectable')); // "inject.db.injectable"
287
+ console.log(getInjectKey('inject')); // "inject.db.inject"
308
288
  ```
309
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
+
310
294
 
311
295
  ### Related
312
296