async-injection 2.3.0 → 3.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 (45) hide show
  1. package/ReadMe.md +133 -194
  2. package/lib/async-factory-provider.d.ts +1 -1
  3. package/lib/bindable-provider.d.ts +2 -2
  4. package/lib/binding.d.ts +80 -0
  5. package/lib/cjs/async-factory-provider.js.map +1 -1
  6. package/lib/cjs/bindable-provider.js +5 -18
  7. package/lib/cjs/bindable-provider.js.map +1 -1
  8. package/lib/cjs/{binder.js → binding.js} +1 -1
  9. package/lib/cjs/binding.js.map +1 -0
  10. package/lib/cjs/class-provider.js +142 -39
  11. package/lib/cjs/class-provider.js.map +1 -1
  12. package/lib/cjs/container.js +62 -22
  13. package/lib/cjs/container.js.map +1 -1
  14. package/lib/cjs/decorators.js +61 -17
  15. package/lib/cjs/decorators.js.map +1 -1
  16. package/lib/cjs/index.js.map +1 -1
  17. package/lib/cjs/injector.js.map +1 -1
  18. package/lib/cjs/provider.js.map +1 -1
  19. package/lib/cjs/sync-factory-provider.js.map +1 -1
  20. package/lib/class-provider.d.ts +16 -2
  21. package/lib/container.d.ts +40 -14
  22. package/lib/decorators.d.ts +15 -2
  23. package/lib/esm/async-factory-provider.js.map +1 -1
  24. package/lib/esm/bindable-provider.js +5 -18
  25. package/lib/esm/bindable-provider.js.map +1 -1
  26. package/lib/esm/binding.js +2 -0
  27. package/lib/esm/binding.js.map +1 -0
  28. package/lib/esm/class-provider.js +143 -40
  29. package/lib/esm/class-provider.js.map +1 -1
  30. package/lib/esm/container.js +62 -22
  31. package/lib/esm/container.js.map +1 -1
  32. package/lib/esm/decorators.js +60 -18
  33. package/lib/esm/decorators.js.map +1 -1
  34. package/lib/esm/index.js.map +1 -1
  35. package/lib/esm/injector.js.map +1 -1
  36. package/lib/esm/provider.js.map +1 -1
  37. package/lib/esm/sync-factory-provider.js.map +1 -1
  38. package/lib/index.d.ts +1 -0
  39. package/lib/injector.d.ts +1 -1
  40. package/lib/sync-factory-provider.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/lib/binder.d.ts +0 -110
  43. package/lib/cjs/binder.js.map +0 -1
  44. package/lib/esm/binder.js +0 -2
  45. package/lib/esm/binder.js.map +0 -1
package/ReadMe.md CHANGED
@@ -1,43 +1,26 @@
1
1
  # Async-Injection
2
- [![CI Actions](https://github.com/pcafstockf/async-injection/workflows/CI/badge.svg)](https://github.com/pcafstockf/async-injection/actions)
3
- [![Publish Actions](https://github.com/pcafstockf/async-injection/workflows/NPM%20Publish/badge.svg)](https://github.com/pcafstockf/async-injection/actions)
2
+
3
+ [![CI](https://github.com/pcafstockf/async-injection/workflows/CI/badge.svg)](https://github.com/pcafstockf/async-injection/actions)
4
+ [![npm version](https://img.shields.io/npm/v/async-injection)](https://www.npmjs.com/package/async-injection)
4
5
  [![codecov](https://codecov.io/gh/pcafstockf/async-injection/graph/badge.svg)](https://codecov.io/gh/pcafstockf/async-injection)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
6
7
  ![OSS Lifecycle](https://img.shields.io/osslifecycle/pcafstockf/async-injection.svg)
7
- [![npm version](https://img.shields.io/npm/v/async-injection)](https://www.npmjs.com/package/async-injection)
8
-
9
- A robust lightweight dependency injection library for TypeScript.
10
-
11
- ## About
12
- Async-Injection is a small IoC container with support for both synchronous and asynchronous dependency injection, as well as isolated and/or hierarchical scopes.
13
-
14
- ## Installation
15
-
16
- You can get the latest release from [npm](https://www.npmjs.com/package/async-injection):
17
8
 
18
- ```
19
- $ npm install async-injection --save
20
- ```
9
+ **Lightweight TypeScript dependency injection — with first-class async support.**
21
10
 
22
- To enhance flexibility, Async-Injection does not dictate which Reflect API implementation you use. However, you will need to explicitly choose and load one.
23
- You can use:
11
+ Most DI containers assume your dependencies are ready the moment they are constructed. `async-injection` doesn't.
12
+ Synchronous and asynchronous dependencies can coexist naturally in the same container, and the library resolves each correctly — whether you get them immediately or need to await them.
24
13
 
25
- - [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
26
- - [core-js (core-js/es7/reflect)](https://www.npmjs.com/package/core-js)
27
- - [reflection](https://www.npmjs.com/package/@abraham/reflection)
14
+ ## Install
28
15
 
29
- The Reflect polyfill import should only be added once, and before Async-Injection is used:
30
-
31
- ```typescript
32
- // entry.ts
33
- import "reflect-metadata";
34
- // Your code here...
16
+ ```bash
17
+ npm install async-injection
35
18
  ```
36
19
 
37
- Please note that this library supports a wide variety of runtimes and is distributed as both esm and cjs modules, side by side.
20
+ Works in Node, browsers, Electron, and other runtimes.
21
+ Ships as both ESM and CJS side by side.
38
22
 
39
- ## Basic Usage (synchronous)
40
- Here we 'get' a new transaction handling object, that itself, relies on a shared service:
23
+ ## Quick start
41
24
 
42
25
  ```typescript
43
26
  @Injectable()
@@ -50,209 +33,165 @@ class TransactionHandler {
50
33
  constructor(svc: SharedService) { }
51
34
  }
52
35
 
53
- // Create a simple container (we will bind providers into it).
54
36
  const container = new Container();
37
+ container.bindClass(SharedService).asSingleton(); // one shared instance
38
+ container.bindClass(TransactionHandler); // new instance on each get
39
+ container.bindConstant('LogLevel', 'info'); // override defaulted 'warn' level
55
40
 
56
- // A single instance will be created and shared by everyone.
57
- container.bindClass(SharedService).asSingleton();
58
-
59
- // A new instance will be created each time one is requested.
60
- container.bindClass(TransactionHandler);
61
-
62
- // If we omit this line, the logLevel of SharedService will be initialized to 'warn'
63
- container.bindConstant('LogLevel', 'info');
64
-
65
- // In our request processing code (which would be an anti-pattern)...
66
- // Instantiate a new transaction handler (it will be injected with the shared service).
67
41
  const tx = container.get(TransactionHandler);
68
42
  ```
69
- **NOTE:**
70
- The examples in this ReadMe are contrived to quickly communicate concepts and usage.
71
- Your real world project should of course follow best practices like [separation of concerns](https://medium.com/machine-words/separation-of-concerns-1d735b703a60), having a [composition root](https://medium.com/@cfryerdev/dependency-injection-composition-root-418a1bb19130), and should avoid anti-patterns like [service locator](http://scotthannen.org/blog/2018/11/27/stop-worrying-love-service-locator.html).
72
43
 
73
- ## Scopes
74
- Scopes can be created using multiple Containers, and/or a hierarchy of Containers.
44
+ > **Tip:**
45
+ > Real-world projects should follow best practices like [separation of concerns](https://medium.com/machine-words/separation-of-concerns-1d735b703a60), having a [composition root](https://medium.com/@cfryerdev/dependency-injection-composition-root-418a1bb19130), and should avoid anti-patterns like [service locator](http://scotthannen.org/blog/2018/11/27/stop-worrying-love-service-locator.html).
75
46
 
76
- ## IoC Modules
77
- Why reinvent the wheel? TypeScript is great!
78
- Implement the "module" you want and just import it:
47
+ ## Setup
79
48
 
80
- `my-http-ioc-module.ts`
81
- ```typescript
82
- import {myContainer} from './app';
83
- import {Logger, HttpClient} from './services';
84
- import {HttpClientGotWrapper} from './impl';
49
+ Two `tsconfig.json` settings are required:
85
50
 
86
- myContainer.bind(Logger).asSingleton();
87
- myContainer.bind(HttpClient, HttpClientGotWrapper);
51
+ ```json
52
+ {
53
+ "experimentalDecorators": true,
54
+ "emitDecoratorMetadata": true
55
+ }
88
56
  ```
89
57
 
90
- ## Asynchronous Support
91
- For simplicity, it is recommended that you use traditional synchronous injection for any class where that is possible.
92
- But when that's just to much work, you can "blend" synchronous and asynchronous injection.
93
- To support "blending", we introduce three new methods on the `Container` which will be explained below.
58
+ Reflection metadata is also required. Rather than mandate a specific library, you have the freedom to bring your own — choose whichever fits your project:
59
+ * [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
60
+ * [core-js/es7/reflect](https://www.npmjs.com/package/core-js)
61
+ * [@abraham/reflection](https://www.npmjs.com/package/@abraham/reflection)
94
62
 
95
- ## Asynchronous Usage
96
- Perhaps in the example above, our `SharedService` is useless until it has established a database connection.
97
- Of course such a simple scenario could easily be handled in user-land code, but as application complexity grows, this becomes more tedious and difficult to maintain.
98
- Let's modify the example as follows:
63
+ Import it once at your entry point, before anything else:
99
64
  ```typescript
100
- @Injectable()
101
- class SharedService {
102
- constructor() { }
103
- connect(): Promise<void> { ... }
104
- }
65
+ import 'reflect-metadata';
66
+ ```
105
67
 
106
- const container = new Container();
68
+ ## Async dependencies
69
+
70
+ Synchronous injection is straightforward and well understood.
71
+ Asynchronous injection is also well established.
72
+ But when you are **blending** the two in the same container, it requires a little care.
73
+
74
+ ### `get` vs `resolve`
75
+
76
+ Think of `get(X)` / `resolve(X)` as a request not just for `X`, but for the entire tree of objects `X` depends on.
77
+ `get` is only safe when every node in that tree is already settled.
78
+
79
+ | Condition | When to use |
80
+ |---|---|
81
+ | All dependencies are synchronous, **or** async singletons are already resolved | `container.get(X)` |
82
+ | Any dependency in the tree may still be pending | `await container.resolve(X)` |
83
+
84
+ > **Tip:**
85
+ > Call `resolveSingletons(true)` after your last `bindXXX` call and before any `get` call to avoid hard-to-debug timing issues.
86
+
87
+ **When a dependency must do async work before it is usable** — open a database connection, load remote config, etc. — there are two ways to handle it:
107
88
 
108
- // Bind a factory function that awaits until it can fully create a SharedService.
89
+ #### **Async factory** — bind an async factory that performs the initialization and returns the ready instance:
90
+
91
+ ```typescript
109
92
  container.bindAsyncFactory(SharedService, async () => {
110
- let svc = new SharedService();
111
- return await svc.connect();
93
+ const svc = new SharedService();
94
+ return svc.connect(); // returns Promise<SharedService>
112
95
  }).asSingleton();
113
96
 
114
- // A new transient instance will be created each time one is requested.
115
- container.bindClass(TransactionHandler);
116
-
117
- // Wait for all bound asynchronous factory functions to complete.
118
- // This step is optional. You could omit and use Container.resolve instead (see alternative below).
97
+ // Option A resolve everything up front, then use get() as normal
119
98
  await container.resolveSingletons(true);
120
- // We are now connected to the database
121
-
122
- // In our request processing code...
123
99
  const tx = container.get(TransactionHandler);
124
- ```
125
- As an alternative, we could **remove** the call to `Container.resolveSingletons`, and in our request processing code, simply call `Container.resolve`.
126
- ```typescript
100
+
101
+ // Option B resolve on demand
127
102
  const tx = await container.resolve(TransactionHandler);
128
103
  ```
129
104
 
130
- ## Important - Container.resolve vs Container.get
131
- Blending synchronous and asynchronous injection adds complexity to your application.
132
- The key to successful blending is to think of the object you are requesting, not as an object, but as a tree of objects with your object at the top.
133
- Keep in mind that you may have **transient** objects which need to be created each time, as well as existing **singleton** objects in your dependency tree.
134
- If you know ahead of time that every object which you depend on is immediately (synchronously) available, **or** if they are asynchronous **singletons** which have already been resolved (via `Container.resolveSingletons`, or a previous call to `Container.resolve`), then no need to wait, you can just `Container.get` the tree.
135
- Otherwise you need to await the full resolution of the tree with `await Container.resolve`.
105
+ > **Note:**
106
+ > A factory takes full responsibility for constructing and initializing its object `@PostConstruct` is not called on factory-returned instances.
107
+ > `bindFactory` and `bindAsyncFactory` are therefore the right choice when you need complete control over how an object is built, or when you cannot annotate the class.
108
+
109
+ #### **`@PostConstruct`** mark an initialization method to run on the fully constructed object after the constructor returns.
110
+ The method can be synchronous or asynchronous, which is especially useful since a class constructor can never be async.
111
+ It is also useful because a base class constructor cannot call methods overridden by a subclass.
112
+ The method can have parameters which can be annotated with `@Inject` and `@Optional` — the container resolves and injects them before calling the method.
113
+ This lets you avoid storing dependencies from the constructor solely for post-construction use:
136
114
 
137
- ## @PostConstruct Support
138
- It is not always possible to fully initialize your object in the class constructor.
139
- This (albeit contrived) demo shows that the `Employee` class is not yet initialized when the `Person` subclass tries to call the overridden `state` method.
140
115
  ```typescript
141
- class Person {
142
- public constructor() { this.describe(); }
143
- protected state() { return "relaxing"; }
144
- public describe() { console.log("Hi I'm '" + this.state() + "'"); }
145
- }
146
- class Employee extends Person {
147
- constructor(private manager: boolean) { super(); }
148
- protected state() { return this.manager ? "busy" : "producing"; }
116
+ @Injectable()
117
+ class DatabasePool {
118
+ @PostConstruct()
119
+ async init(@Inject(DbConfig) config: DbConfig): Promise<void> {
120
+ this.pool = await createPool(config); // config is injected, not stored
121
+ }
149
122
  }
150
- // This will print:
151
- // "Hi I'm 'producing", even though the author probably expected
152
- // "Hi I'm busy", because they passed true for the 'manager' parameter.
153
- new Employee(true);
154
123
  ```
155
- Can we refactor code to work around this? Sure. You may have to submit a couple of PR's, re-write legacy code that has no unit tests, trash encapsulation, skip a few nights sleep, etc. But why?
156
- A PostConstruct annotation ensure's your initialization method is working on a fully constructed version of your object.
157
- Even better, since constructors cannot be asynchronous, PostConstruct gives you an easy way to asynchronously prepare an object before it's put into service.
158
124
 
159
- ## @PostConstruct Usage
160
- Post construction methods can be either synchronous or asynchronous.
125
+ > **Important:**
126
+ > Always explicitly declare the return type (`void` or `Promise<void>`, never leave it to be inferred).
127
+ > `container.get()` will throw if the return type is missing and the method actually does return a Promise.
128
+ > Constructor and `@PostConstruct` parameters follow the same rules: class-typed params are auto-resolved by reflected type; use `@Inject` for interface or primitive types. Use `@Optional()` with no argument to pass `undefined` if you want to allow a JS parameter default.
161
129
 
162
- ```typescript
163
- class A {
164
- public constructor() { }
130
+ ## Scopes
165
131
 
166
- // Called before the object is placed into the container (or is returned from get/resolve)
167
- @PostConstruct()
168
- public init(): void { ... }
169
- }
170
- class D {
171
- public constructor() { }
132
+ Create isolated or hierarchical scopes using multiple containers.
133
+ A child container searches its own bindings first, then walks up the parent hierarchy:
172
134
 
173
- // Will not be placed into the container (or returned) until the Promise has been resolved.
174
- @PostConstruct()
175
- public init(): Promise<void> { ... }
176
- }
135
+ ```typescript
136
+ const child = new Container(parent);
177
137
  ```
178
138
 
179
- ### @PostConstruct Guidelines:
180
- - Ensure your post construction method signature properly **declares** it's return type.
181
- **WARNING!** An unspecified return type signature where the type is implied by `return new Promise(...)` is not sufficient! You must explicitly declare the return type.
182
- - `Container.get` will throw an exception if you try to retrieve a class with `@PostConstruct` on a method that returns a `Promise`, but which does not **declare** it's return type to be `Promise`.
183
- - The library will not invoke @PostConstruct on an object returned from a factory. It is the factory's responsibility to construct and initialize before returning.
184
- - You will likely want a `Container.resolveSingletons(true)` call between your last `Container.bindXXX()` call and any `Container.get` call.
185
-
186
- ## API Overview
187
- Async-Injection tries to follow the common API patterns found in most other DI implementations. Please refer to the examples above or the linked elements below for specific syntax.
188
- - The
189
- [Container](https://pcafstockf.github.io/async-injection/api-docs/container.html) class implements a
190
- [Binder](https://pcafstockf.github.io/async-injection/api-docs/binder.html) interface, which allows you to bind a
191
- [Constant](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindconstant),
192
- [Factory](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindfactory),
193
- [AsyncFactory](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindasyncfactory), or
194
- [Class](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindclass) value to an
195
- [InjectableId](https://pcafstockf.github.io/async-injection/api-docs/globals.html#injectableid) (aka key) within a
196
- [Container](https://pcafstockf.github.io/async-injection/api-docs/container.html).
197
- - The
198
- [Container](https://pcafstockf.github.io/async-injection/api-docs/container.html) also implements an
199
- [Injector](https://pcafstockf.github.io/async-injection/api-docs/injector.html) interface which allows you to synchronously
200
- [get](https://pcafstockf.github.io/async-injection/api-docs/container.html#get) or asynchronously
201
- [resolve](https://pcafstockf.github.io/async-injection/api-docs/container.html#resolve) anything that has been bound.
202
- - When binding a
203
- [Factory](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindfactory),
204
- [AsyncFactory](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindasyncfactory) or
205
- [Class](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindclass) to an
206
- [InjectableId](https://pcafstockf.github.io/async-injection/api-docs/globals.html#injectableid), you can chain the result of the call to specify the binding as a
207
- [Singleton](https://pcafstockf.github.io/async-injection/api-docs/bindas.html#assingleton), and/or configure an
208
- [Error Handler](https://pcafstockf.github.io/async-injection/api-docs/bindas.html#onerror).
209
- - Containers may be nested by passing a parent Container to the
210
- [constructor](https://pcafstockf.github.io/async-injection/api-docs/container.html#constructor) of a child Container.
211
- - To bind a
212
- [Class](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindclass) into the
213
- [Container](https://pcafstockf.github.io/async-injection/api-docs/container.html), you must add the
214
- [@Injectable](https://pcafstockf.github.io/async-injection/api-docs/globals.html#injectable) decorator (aka annotation) to your class (see examples above).
215
- - You may optionally add a
216
- [@PostConstruct](https://pcafstockf.github.io/async-injection/api-docs/globals.html#postconstruct) decorator to a method of your class to perform synchronous or asynchronous initialization of a new instance.
217
- - By default, Async-Injection will examine the parameters of a class constructor and do it's best to match those to a bound
218
- [InjectableId](https://pcafstockf.github.io/async-injection/api-docs/globals.html#injectableid).
219
- - You may use the
220
- [@Inject](https://pcafstockf.github.io/async-injection/api-docs/globals.html#inject) decorator to explicitly declare which
221
- [InjectableId](https://pcafstockf.github.io/async-injection/api-docs/globals.html#injectableid) should be used (generally required for a
222
- [Constant](https://pcafstockf.github.io/async-injection/api-docs/container.html#bindconstant) binding as in the logLevel example above).
223
- - The
224
- [@Optional](https://pcafstockf.github.io/async-injection/api-docs/globals.html#optional) decorator allows you to specify a default value for a class constructor parameter in the event that no matching
225
- [InjectableId](https://pcafstockf.github.io/async-injection/api-docs/globals.html#injectableid) can be found.
226
- - The Container's
227
- [resolveSingletons](https://pcafstockf.github.io/async-injection/api-docs/container.html#resolvesingletons) method may be used to wait for any bound (a)synchronous [Singletons](https://en.wikipedia.org/wiki/Singleton_pattern) to finish initialization before continuing execution of your application.
139
+ ## IoC modules
228
140
 
229
- ## Acknowledgements
230
- Thanks to all the contributors at [InversifyJS](https://github.com/inversify/InversifyJS). It is a powerful, clean, flexible, inspiring design.
141
+ No special module system needed — TypeScript's own `import` is your module system. Create a file, import your container, and register your bindings.
142
+
143
+ ## API
144
+
145
+ A Container's life follows a simple arc: *configure* it by registering bindings, *activate* it so async singletons can initialize, then *use* it to retrieve objects.
231
146
 
232
- Thanks to everyone at [NestJS](https://docs.nestjs.com/fundamentals/async-providers) for giving us Asynchronous providers.
147
+ #### Configure
233
148
 
234
- Thanks to Darcy Rayner for describing a [DI implementation](https://dev.to/darcyrayner/typescript-dependency-injection-in-200-loc-12j7) so simply and clearly.
149
+ | | |
150
+ |---|----------------------------------------------------------------|
151
+ | `new Container(parent?)` | Create a container; optionally inherit bound ids from a parent |
152
+ | `bindConstant(id, value)` | Bind a fixed value |
153
+ | `bindClass(id, class?)` | Bind a class (requires `@Injectable`) |
154
+ | `bindFactory(id, fn)` | Bind a synchronous factory function |
155
+ | `bindAsyncFactory(id, fn)` | Bind an asynchronous factory function |
156
+ | `.asSingleton()` | Chain: share one instance across the Container |
157
+ | `.onError(cb)` | Chain: handle construction errors |
235
158
 
236
- Thanks to Carlos Delgado for the idea of a ["QuerablePromise"](https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved) which allowed us to blend asynchronous DI with the simplicity of synchronous DI.
159
+ #### Activate
237
160
 
238
- ## MIT License
161
+ | | |
162
+ |---|---|
163
+ | `resolveSingletons(true)` | Await all async singleton initializations |
164
+
165
+ #### Use
166
+
167
+ | | |
168
+ |---|---|
169
+ | `get(id)` | Synchronously retrieve a bound value |
170
+ | `resolve(id)` | Asynchronously retrieve a bound value (see [`get` vs `resolve`](#get-vs-resolve)) |
171
+
172
+ #### Annotate your classes
173
+
174
+ | | |
175
+ |---|---|
176
+ | `@Injectable()` | Required on any class bound with `bindClass` |
177
+ | `@Inject(id)` | Explicitly declare which id to inject into a constructor parameter |
178
+ | `@Optional(default?)` | Provide a fallback if the id is not bound; omit the argument to let a JS parameter default apply |
179
+ | `@PostConstruct()` | Mark a method to run after full construction (sync or async); parameters annotated with `@Inject`/`@Optional` are injected by the container |
180
+ | `@Release()` | Mark a method to call when a singleton is released |
181
+ | `InjectionToken<T>` | Create a typed token for binding interfaces or primitives |
182
+
183
+ ## Acknowledgements
239
184
 
240
- Copyright (c) 2020-2023 Frank Stock
185
+ Inspired by [InversifyJS](https://github.com/inversify/InversifyJS), [NestJS async providers](https://docs.nestjs.com/fundamentals/async-providers), [Darcy Rayner's DI walkthrough](https://dev.to/darcyrayner/typescript-dependency-injection-in-200-loc-12j7), and Carlos Delgado's [QueryablePromise](https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved) idea.
241
186
 
242
- Permission is hereby granted, free of charge, to any person obtaining a copy
243
- of this software and associated documentation files (the "Software"), to deal
244
- in the Software without restriction, including without limitation the rights
245
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
246
- copies of the Software, and to permit persons to whom the Software is
247
- furnished to do so, subject to the following conditions:
187
+ ## Support Resources
188
+ The [`support/`](./support) directory contains supplementary guides that are **not** part of the library itself:
189
+ - [`lazy-loading/`](./support/lazy-loading.md) patterns for on-demand, split-bundle DI module loading
190
+ - [`react-integration/`](./support/react-integration.md) using with React applications, including scoped child containers and testing patterns
191
+ - [`migrate-from-inversify/`](./support/migrate-from-inversify/ReadMe.md) shim files and a two-phase migration guide for InversifyJS users
192
+ - [`migrate-from-tsyringe/`](./support/migrate-from-tsyringe.md) migration guide for TSyringe users
193
+ - [`migrate-from-typedi/`](./support/migrate-from-typedi.md) — migration guide for TypeDI users
248
194
 
249
- The above copyright notice and this permission notice shall be included in all
250
- copies or substantial portions of the Software.
195
+ ## License
251
196
 
252
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
253
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
254
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
255
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
256
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
257
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
258
- SOFTWARE.
197
+ [MIT](./License.txt) © 2020–2024 Frank Stock
@@ -1,5 +1,5 @@
1
1
  import { BindableProvider } from './bindable-provider';
2
- import { AsyncFactory } from './binder';
2
+ import { AsyncFactory } from './binding';
3
3
  import { InjectableId, Injector } from './injector';
4
4
  import { State } from './state';
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { AsyncFactory, BindAs, OnErrorCallback, OnSuccessCallback, SyncFactory } from './binder';
1
+ import { AsyncFactory, BindAs, OnErrorCallback, OnSuccessCallback, SyncFactory } from './binding';
2
2
  import { ClassConstructor, InjectableId, Injector } from './injector';
3
3
  import { Provider } from './provider';
4
4
  /**
@@ -21,7 +21,7 @@ export declare abstract class BindableProvider<T, M = ClassConstructor<T> | Sync
21
21
  */
22
22
  protected errorHandler?: OnErrorCallback<T, any>;
23
23
  /**
24
- * Invoked by the Binder to create chain-able configuration
24
+ * Invoked by the Container to create chain-able configuration
25
25
  *
26
26
  * @see BindAs
27
27
  */
@@ -0,0 +1,80 @@
1
+ import { ClassConstructor, InjectableId, Injector } from './injector';
2
+ /**
3
+ * Type definition for functions that return a value.
4
+ * The function should return a valid value, but may throw an exception if it cannot.
5
+ */
6
+ export type SyncFactory<T> = (injector: Injector) => T;
7
+ /**
8
+ * Type definition for functions that return a Promise for a value.
9
+ * The function *must* not throw and must return a valid Promise (e.g. pending, resolved, rejected).
10
+ */
11
+ export type AsyncFactory<T> = (injector: Injector) => Promise<T>;
12
+ /**
13
+ * You may bind an error handler which will be invoked if the bound InjectableId could not be put into service.
14
+ * An error handler *must* not throw, but may return an Error that will be propagated back up the call chain.
15
+ *
16
+ * @param injector The Injector that experienced the error.
17
+ * @param id The identifier for what was trying to be made.
18
+ * @param maker The thing that made (or tried to make) the value. Will be one of type ClassConstructor, SyncFactory, or AsyncFactory, depending on how you registered the binding.
19
+ * @param error Identifies the problem that occurred.
20
+ * @param value If the 'maker' was able to create the thing, but it had an error during post construction, the made thing will be passed here.
21
+ * @returns one of 3 results...
22
+ * A substitute thing (kind of like a 'maker' do-over) which must be fully operational (e.g. any `@PostConstruct` will be ignored).
23
+ * An alternate Error which will be propagated back up the call chain.
24
+ * Undefined, which means the 'error' parameter will be propagated back up the call chain.
25
+ */
26
+ export type OnErrorCallback<T, M = unknown> = (injector: Injector, id: InjectableId<T>, maker: M, error: unknown, value?: T) => T | Error | void;
27
+ /**
28
+ * You may bind a success handler which will be invoked just before the bound InjectableId is put into service.
29
+ * This is an alternative to the more preferred `@PostConstruct` decorator for scenarios when usage of that decorator is not feasible.
30
+ * WARNING:
31
+ * By registering a success handler, you override and nullify any `@PostConstruct` decorator on the class.
32
+ * In such a scenario, the success handler should perform whatever care and feeding the class expected from the `@PostConstruct` decorator.
33
+ * A success handler *must* not throw, but may return an Error that will be propagated back up the call chain.
34
+ *
35
+ * @param value The thing that was made.
36
+ * @param injector The Injector that performed the construction.
37
+ * @param id The identifier for what was made.
38
+ * @param maker The thing that made. Will be one of type ClassConstructor, SyncFactory, or AsyncFactory, depending on how you registered the binding.
39
+ * @returns one of 3 results...
40
+ * An Error which will be propagated back up the call chain.
41
+ * Undefined, which means the object is ready to be placed into service.
42
+ * A Promise that resolves to one of the above two values (undefined or Error).
43
+ */
44
+ export type OnSuccessCallback<T, M = unknown> = (value: T, injector: Injector, id: InjectableId<T>, maker: M) => Promise<Error | void> | Error | void;
45
+ /**
46
+ * Descriptor object used with {@link Container.register} to specify how an id should be bound.
47
+ * Mirrors the TSyringe registration API.
48
+ */
49
+ export type RegisterDescriptor<T> = {
50
+ useClass: ClassConstructor<T>;
51
+ } | {
52
+ useValue: T;
53
+ } | {
54
+ useFactory: SyncFactory<T>;
55
+ } | {
56
+ useAsyncFactory: AsyncFactory<T>;
57
+ };
58
+ /**
59
+ * An interface allowing binding of an error handler.
60
+ *
61
+ * @see OnErrorCallback
62
+ */
63
+ export interface BindErrHandler<T, M = unknown> {
64
+ onError(cb: OnErrorCallback<T, M>): void;
65
+ }
66
+ /**
67
+ * An interface allowing binding of a post construction handler.
68
+ *
69
+ * @see OnSuccessCallback
70
+ */
71
+ export interface BindHandler<T, M = unknown> extends BindErrHandler<T, M> {
72
+ onSuccess(cb: OnSuccessCallback<T, M>): BindErrHandler<T, M>;
73
+ }
74
+ /**
75
+ * @inheritDoc
76
+ * This specialization also allows you to specify that the binding is 'Singleton' (e.g. only one in the system).
77
+ */
78
+ export interface BindAs<T, M = unknown> extends BindHandler<T, M> {
79
+ asSingleton(): BindHandler<T, M>;
80
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"async-factory-provider.js","sourceRoot":"","sources":["../../src/async-factory-provider.ts"],"names":[],"mappings":";;;AAAA,2DAAqD;AAGrD,mCAA8B;AAE9B;;;GAGG;AACH,MAAa,yBAA6B,SAAQ,oCAAoC;IACrF,YAAY,QAAkB,EAAE,EAAmB,EAAE,KAAsB;QAC1E,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,cAAc;QACb,IAAI,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,qEAAqE;YACrE,+HAA+H;YAC/H,MAAM,GAAG,aAAK,CAAC,SAAS,CAAI,IAAI,CAAC,iBAAiB,CAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI;YAC1B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACzB,OAAO,MAAM,CAAC;IACf,CAAC;CACD;AApBD,8DAoBC","sourcesContent":["import {BindableProvider} from './bindable-provider';\nimport {AsyncFactory} from './binder';\nimport {InjectableId, Injector} from './injector';\nimport {State} from './state';\n\n/**\n * @inheritDoc\n * This specialization invokes it's configured Factory asynchronously and waits until it can provide the result.\n */\nexport class AsyncFactoryBasedProvider<T> extends BindableProvider<T, AsyncFactory<T>> {\n\tconstructor(injector: Injector, id: InjectableId<T>, maker: AsyncFactory<T>) {\n\t\tsuper(injector, id, maker);\n\t}\n\n\t/**\n\t * @inheritDoc\n\t * This specialization invokes it's configured Factory and provides the result (or invokes the error handler if necessary).\n\t */\n\tprovideAsState(): State<T> {\n\t\tlet retVal = this.singleton;\n\t\tif (!retVal) {\n\t\t\t// Wrap the async factory's Promise in an errorHandler aware Promise.\n\t\t\t// Our contract is that an AsyncFactory may not throw and must return a valid Promise (e.g., pending, resolved, rejected, etc).\n\t\t\tretVal = State.MakeState<T>(this.makePromiseForObj<T>(this.maker(this.injector), obj => obj));\n\t\t}\n\t\tif (this.singleton === null)\n\t\t\tthis.singleton = retVal;\n\t\treturn retVal;\n\t}\n}\n"]}
1
+ {"version":3,"file":"async-factory-provider.js","sourceRoot":"","sources":["../../src/async-factory-provider.ts"],"names":[],"mappings":";;;AAAA,2DAAqD;AAGrD,mCAA8B;AAE9B;;;GAGG;AACH,MAAa,yBAA6B,SAAQ,oCAAoC;IACrF,YAAY,QAAkB,EAAE,EAAmB,EAAE,KAAsB;QAC1E,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,cAAc;QACb,IAAI,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,qEAAqE;YACrE,+HAA+H;YAC/H,MAAM,GAAG,aAAK,CAAC,SAAS,CAAI,IAAI,CAAC,iBAAiB,CAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI;YAC1B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACzB,OAAO,MAAM,CAAC;IACf,CAAC;CACD;AApBD,8DAoBC","sourcesContent":["import {BindableProvider} from './bindable-provider';\nimport {AsyncFactory} from './binding';\nimport {InjectableId, Injector} from './injector';\nimport {State} from './state';\n\n/**\n * @inheritDoc\n * This specialization invokes it's configured Factory asynchronously and waits until it can provide the result.\n */\nexport class AsyncFactoryBasedProvider<T> extends BindableProvider<T, AsyncFactory<T>> {\n\tconstructor(injector: Injector, id: InjectableId<T>, maker: AsyncFactory<T>) {\n\t\tsuper(injector, id, maker);\n\t}\n\n\t/**\n\t * @inheritDoc\n\t * This specialization invokes it's configured Factory and provides the result (or invokes the error handler if necessary).\n\t */\n\tprovideAsState(): State<T> {\n\t\tlet retVal = this.singleton;\n\t\tif (!retVal) {\n\t\t\t// Wrap the async factory's Promise in an errorHandler aware Promise.\n\t\t\t// Our contract is that an AsyncFactory may not throw and must return a valid Promise (e.g., pending, resolved, rejected, etc).\n\t\t\tretVal = State.MakeState<T>(this.makePromiseForObj<T>(this.maker(this.injector), obj => obj));\n\t\t}\n\t\tif (this.singleton === null)\n\t\t\tthis.singleton = retVal;\n\t\treturn retVal;\n\t}\n}\n"]}
@@ -15,7 +15,7 @@ class BindableProvider extends provider_1.Provider {
15
15
  this.maker = maker;
16
16
  }
17
17
  /**
18
- * Invoked by the Binder to create chain-able configuration
18
+ * Invoked by the Container to create chain-able configuration
19
19
  *
20
20
  * @see BindAs
21
21
  */
@@ -49,13 +49,13 @@ class BindableProvider extends provider_1.Provider {
49
49
  // Error handler wants us to propagate an error.
50
50
  if ((0, utils_1.isErrorObj)(handlerResult))
51
51
  throw handlerResult;
52
- // Error handler has no opinion, so provideAsState a state that reflects the error we just caught.
52
+ // Error handler has no opinion, so propagate the error we just caught.
53
53
  if (typeof handlerResult === 'undefined')
54
54
  throw err;
55
55
  // Error handler provided a valid (fully resolved) replacement.
56
56
  return handlerResult;
57
57
  }
58
- // No error handler, provideAsState a state that reflects the error we just caught.
58
+ // No error handler, propagate the error we just caught.
59
59
  throw err;
60
60
  }
61
61
  /**
@@ -67,33 +67,20 @@ class BindableProvider extends provider_1.Provider {
67
67
  * @param cb Callback to be invoked if the supplied Promise resolves.
68
68
  */
69
69
  async makePromiseForObj(waitFor, cb) {
70
- // Local helper: consults the errorHandler (if any) for recovery; returns a substitute or re-throws.
71
- const handleError = (err, objValue) => {
72
- if (this.errorHandler) {
73
- const handlerResult = this.errorHandler(this.injector, this.id, this.maker, err, objValue);
74
- // Error handler wants us to propagate an alternative error.
75
- if ((0, utils_1.isErrorObj)(handlerResult))
76
- throw handlerResult;
77
- // Error handler provided a valid (fully resolved) replacement.
78
- else if (typeof handlerResult !== 'undefined')
79
- return handlerResult;
80
- }
81
- throw err;
82
- };
83
70
  let result;
84
71
  try {
85
72
  result = await waitFor;
86
73
  }
87
74
  catch (err) {
88
75
  // waitFor rejected — ask the error handler for recovery, passing cb(undefined) as the partial object value.
89
- return handleError(err, cb(undefined));
76
+ return this.queryErrorHandler(err, cb(undefined));
90
77
  }
91
78
  try {
92
79
  return cb(result);
93
80
  }
94
81
  catch (err) {
95
82
  // cb threw after a successful resolution — ask the error handler for recovery.
96
- return handleError(err, cb(result));
83
+ return this.queryErrorHandler(err, cb(result));
97
84
  }
98
85
  }
99
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bindable-provider.js","sourceRoot":"","sources":["../../src/bindable-provider.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,mCAAmC;AAEnC;;;GAGG;AACH,MAAsB,gBAAgF,SAAQ,mBAAW;IACxH,YAAgC,QAAkB,EAAY,EAAmB,EAAY,KAAQ;QACpG,KAAK,EAAE,CAAC;QADuB,aAAQ,GAAR,QAAQ,CAAU;QAAY,OAAE,GAAF,EAAE,CAAiB;QAAY,UAAK,GAAL,KAAK,CAAG;IAErG,CAAC;IAcD;;;;OAIG;IACH,UAAU;QACT,MAAM,MAAM,GAAiB;YAC5B,OAAO,EAAE,CAAC,EAAyB,EAAE,EAAE;gBACtC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;YACxB,CAAC;YACD,SAAS,EAAE,CAAC,EAA2B,EAAE,EAAE;gBAC1C,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;gBACzB,OAAO,MAAM,CAAC;YACf,CAAC;YACD,WAAW,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,qCAAqC;gBAC5D,OAAO,MAAM,CAAC;YACf,CAAC;SACD,CAAC;QACF,OAAO,MAAM,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACO,iBAAiB,CAAC,GAAY,EAAE,GAAO;QAChD,oHAAoH;QACpH,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACtF,gDAAgD;YAChD,IAAI,IAAA,kBAAU,EAAC,aAAa,CAAC;gBAC5B,MAAM,aAAa,CAAC;YACrB,kGAAkG;YAClG,IAAI,OAAO,aAAa,KAAK,WAAW;gBACvC,MAAM,GAAG,CAAC;YACX,+DAA+D;YAC/D,OAAO,aAAa,CAAC;QACtB,CAAC;QACD,mFAAmF;QACnF,MAAM,GAAG,CAAC;IACX,CAAC;IAED;;;;;;;OAOG;IACO,KAAK,CAAC,iBAAiB,CAAI,OAAmB,EAAE,EAAoB;QAC7E,oGAAoG;QACpG,MAAM,WAAW,GAAG,CAAC,GAAY,EAAE,QAAY,EAAK,EAAE;YACrD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;gBAC3F,4DAA4D;gBAC5D,IAAI,IAAA,kBAAU,EAAC,aAAa,CAAC;oBAC5B,MAAM,aAAa,CAAC;gBACrB,+DAA+D;qBAC1D,IAAI,OAAO,aAAa,KAAK,WAAW;oBAC5C,OAAO,aAAa,CAAC;YACvB,CAAC;YACD,MAAM,GAAG,CAAC;QACX,CAAC,CAAC;QACF,IAAI,MAAS,CAAC;QACd,IAAI,CAAC;YACJ,MAAM,GAAG,MAAM,OAAO,CAAC;QACxB,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACZ,4GAA4G;YAC5G,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,CAAC,SAAyB,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,CAAC;YACJ,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACZ,+EAA+E;YAC/E,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,CAAC,MAAW,CAAC,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;CACD;AArGD,4CAqGC","sourcesContent":["import {AsyncFactory, BindAs, OnErrorCallback, OnSuccessCallback, SyncFactory} from './binder';\nimport {ClassConstructor, InjectableId, Injector} from './injector';\nimport {Provider} from './provider';\nimport {isErrorObj} from './utils';\n\n/**\n * @inheritDoc\n * This abstraction is for Providers that can be additionally configured as Singletons and/or configured with error and/or success handling callback(s).\n */\nexport abstract class BindableProvider<T, M = ClassConstructor<T> | SyncFactory<T> | AsyncFactory<T>> extends Provider<T> {\n\tprotected constructor(protected injector: Injector, protected id: InjectableId<T>, protected maker: M) {\n\t\tsuper();\n\t}\n\n\t/**\n\t * A user supplied success handling function.\n\t * Default value is undefined.\n\t */\n\tprotected successHandler?: OnSuccessCallback<T, any>;\n\n\t/**\n\t * A user supplied error handling function.\n\t * Default value is undefined.\n\t */\n\tprotected errorHandler?: OnErrorCallback<T, any>;\n\n\t/**\n\t * Invoked by the Binder to create chain-able configuration\n\t *\n\t * @see BindAs\n\t */\n\tmakeBindAs(): BindAs<T, M> {\n\t\tconst retVal: BindAs<T, M> = {\n\t\t\tonError: (cb: OnErrorCallback<T, M>) => {\n\t\t\t\tthis.errorHandler = cb;\n\t\t\t},\n\t\t\tonSuccess: (cb: OnSuccessCallback<T, M>) => {\n\t\t\t\tthis.successHandler = cb;\n\t\t\t\treturn retVal;\n\t\t\t},\n\t\t\tasSingleton: () => {\n\t\t\t\tthis.singleton = null; // Flag state as no longer undefined.\n\t\t\t\treturn retVal;\n\t\t\t}\n\t\t};\n\t\treturn retVal;\n\t}\n\n\t/**\n\t * Encapsulate the logic of invoking any configured error handler, and processing it's result.\n\t *\n\t * @see OnErrorCallback\n\t *\n\t * @returns The object substituted by the callback (otherwise this method throws the appropriate error).\n\t */\n\tprotected queryErrorHandler(err: unknown, obj?: T): T {\n\t\t// There was an error during construction, see if an error handler was provided, and if so, see what it wants to do.\n\t\tif (this.errorHandler) {\n\t\t\tconst handlerResult = this.errorHandler(this.injector, this.id, this.maker, err, obj);\n\t\t\t// Error handler wants us to propagate an error.\n\t\t\tif (isErrorObj(handlerResult))\n\t\t\t\tthrow handlerResult;\n\t\t\t// Error handler has no opinion, so provideAsState a state that reflects the error we just caught.\n\t\t\tif (typeof handlerResult === 'undefined')\n\t\t\t\tthrow err;\n\t\t\t// Error handler provided a valid (fully resolved) replacement.\n\t\t\treturn handlerResult;\n\t\t}\n\t\t// No error handler, provideAsState a state that reflects the error we just caught.\n\t\tthrow err;\n\t}\n\n\t/**\n\t * This is like a retry mechanism that uses the Provider's errorHandler (if any) to attempt recovery whenever the supplied Promise rejects.\n\t * This method returns a Promise that rejects if recovery was not possible.\n\t * If the supplied Promise resolves, then this method passes the result to the callback, and then resolve as whatever that callback returns.\n\t *\n\t * @param waitFor The supplied Promise.\n\t * @param cb Callback to be invoked if the supplied Promise resolves.\n\t */\n\tprotected async makePromiseForObj<R>(waitFor: Promise<R>, cb: (result: R) => T): Promise<T> {\n\t\t// Local helper: consults the errorHandler (if any) for recovery; returns a substitute or re-throws.\n\t\tconst handleError = (err: unknown, objValue?: T): T => {\n\t\t\tif (this.errorHandler) {\n\t\t\t\tconst handlerResult = this.errorHandler(this.injector, this.id, this.maker, err, objValue);\n\t\t\t\t// Error handler wants us to propagate an alternative error.\n\t\t\t\tif (isErrorObj(handlerResult))\n\t\t\t\t\tthrow handlerResult;\n\t\t\t\t// Error handler provided a valid (fully resolved) replacement.\n\t\t\t\telse if (typeof handlerResult !== 'undefined')\n\t\t\t\t\treturn handlerResult;\n\t\t\t}\n\t\t\tthrow err;\n\t\t};\n\t\tlet result: R;\n\t\ttry {\n\t\t\tresult = await waitFor;\n\t\t}\n\t\tcatch (err) {\n\t\t\t// waitFor rejected — ask the error handler for recovery, passing cb(undefined) as the partial object value.\n\t\t\treturn handleError(err, cb(undefined as unknown as R));\n\t\t}\n\t\ttry {\n\t\t\treturn cb(result);\n\t\t}\n\t\tcatch (err) {\n\t\t\t// cb threw after a successful resolution — ask the error handler for recovery.\n\t\t\treturn handleError(err, cb(result as R));\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"bindable-provider.js","sourceRoot":"","sources":["../../src/bindable-provider.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,mCAAmC;AAEnC;;;GAGG;AACH,MAAsB,gBAAgF,SAAQ,mBAAW;IACxH,YAAgC,QAAkB,EAAY,EAAmB,EAAY,KAAQ;QACpG,KAAK,EAAE,CAAC;QADuB,aAAQ,GAAR,QAAQ,CAAU;QAAY,OAAE,GAAF,EAAE,CAAiB;QAAY,UAAK,GAAL,KAAK,CAAG;IAErG,CAAC;IAcD;;;;OAIG;IACH,UAAU;QACT,MAAM,MAAM,GAAiB;YAC5B,OAAO,EAAE,CAAC,EAAyB,EAAE,EAAE;gBACtC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;YACxB,CAAC;YACD,SAAS,EAAE,CAAC,EAA2B,EAAE,EAAE;gBAC1C,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;gBACzB,OAAO,MAAM,CAAC;YACf,CAAC;YACD,WAAW,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,qCAAqC;gBAC5D,OAAO,MAAM,CAAC;YACf,CAAC;SACD,CAAC;QACF,OAAO,MAAM,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACO,iBAAiB,CAAC,GAAY,EAAE,GAAO;QAChD,oHAAoH;QACpH,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACtF,gDAAgD;YAChD,IAAI,IAAA,kBAAU,EAAC,aAAa,CAAC;gBAC5B,MAAM,aAAa,CAAC;YACrB,uEAAuE;YACvE,IAAI,OAAO,aAAa,KAAK,WAAW;gBACvC,MAAM,GAAG,CAAC;YACX,+DAA+D;YAC/D,OAAO,aAAa,CAAC;QACtB,CAAC;QACD,wDAAwD;QACxD,MAAM,GAAG,CAAC;IACX,CAAC;IAED;;;;;;;OAOG;IACO,KAAK,CAAC,iBAAiB,CAAI,OAAmB,EAAE,EAAoB;QAC7E,IAAI,MAAS,CAAC;QACd,IAAI,CAAC;YACJ,MAAM,GAAG,MAAM,OAAO,CAAC;QACxB,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACZ,4GAA4G;YAC5G,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAyB,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC;YACJ,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACZ,+EAA+E;YAC/E,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,MAAW,CAAC,CAAC,CAAC;QACrD,CAAC;IACF,CAAC;CACD;AAxFD,4CAwFC","sourcesContent":["import {AsyncFactory, BindAs, OnErrorCallback, OnSuccessCallback, SyncFactory} from './binding';\nimport {ClassConstructor, InjectableId, Injector} from './injector';\nimport {Provider} from './provider';\nimport {isErrorObj} from './utils';\n\n/**\n * @inheritDoc\n * This abstraction is for Providers that can be additionally configured as Singletons and/or configured with error and/or success handling callback(s).\n */\nexport abstract class BindableProvider<T, M = ClassConstructor<T> | SyncFactory<T> | AsyncFactory<T>> extends Provider<T> {\n\tprotected constructor(protected injector: Injector, protected id: InjectableId<T>, protected maker: M) {\n\t\tsuper();\n\t}\n\n\t/**\n\t * A user supplied success handling function.\n\t * Default value is undefined.\n\t */\n\tprotected successHandler?: OnSuccessCallback<T, any>;\n\n\t/**\n\t * A user supplied error handling function.\n\t * Default value is undefined.\n\t */\n\tprotected errorHandler?: OnErrorCallback<T, any>;\n\n\t/**\n\t * Invoked by the Container to create chain-able configuration\n\t *\n\t * @see BindAs\n\t */\n\tmakeBindAs(): BindAs<T, M> {\n\t\tconst retVal: BindAs<T, M> = {\n\t\t\tonError: (cb: OnErrorCallback<T, M>) => {\n\t\t\t\tthis.errorHandler = cb;\n\t\t\t},\n\t\t\tonSuccess: (cb: OnSuccessCallback<T, M>) => {\n\t\t\t\tthis.successHandler = cb;\n\t\t\t\treturn retVal;\n\t\t\t},\n\t\t\tasSingleton: () => {\n\t\t\t\tthis.singleton = null; // Flag state as no longer undefined.\n\t\t\t\treturn retVal;\n\t\t\t}\n\t\t};\n\t\treturn retVal;\n\t}\n\n\t/**\n\t * Encapsulate the logic of invoking any configured error handler, and processing it's result.\n\t *\n\t * @see OnErrorCallback\n\t *\n\t * @returns The object substituted by the callback (otherwise this method throws the appropriate error).\n\t */\n\tprotected queryErrorHandler(err: unknown, obj?: T): T {\n\t\t// There was an error during construction, see if an error handler was provided, and if so, see what it wants to do.\n\t\tif (this.errorHandler) {\n\t\t\tconst handlerResult = this.errorHandler(this.injector, this.id, this.maker, err, obj);\n\t\t\t// Error handler wants us to propagate an error.\n\t\t\tif (isErrorObj(handlerResult))\n\t\t\t\tthrow handlerResult;\n\t\t\t// Error handler has no opinion, so propagate the error we just caught.\n\t\t\tif (typeof handlerResult === 'undefined')\n\t\t\t\tthrow err;\n\t\t\t// Error handler provided a valid (fully resolved) replacement.\n\t\t\treturn handlerResult;\n\t\t}\n\t\t// No error handler, propagate the error we just caught.\n\t\tthrow err;\n\t}\n\n\t/**\n\t * This is like a retry mechanism that uses the Provider's errorHandler (if any) to attempt recovery whenever the supplied Promise rejects.\n\t * This method returns a Promise that rejects if recovery was not possible.\n\t * If the supplied Promise resolves, then this method passes the result to the callback, and then resolve as whatever that callback returns.\n\t *\n\t * @param waitFor The supplied Promise.\n\t * @param cb Callback to be invoked if the supplied Promise resolves.\n\t */\n\tprotected async makePromiseForObj<R>(waitFor: Promise<R>, cb: (result: R) => T): Promise<T> {\n\t\tlet result: R;\n\t\ttry {\n\t\t\tresult = await waitFor;\n\t\t}\n\t\tcatch (err) {\n\t\t\t// waitFor rejected — ask the error handler for recovery, passing cb(undefined) as the partial object value.\n\t\t\treturn this.queryErrorHandler(err, cb(undefined as unknown as R));\n\t\t}\n\t\ttry {\n\t\t\treturn cb(result);\n\t\t}\n\t\tcatch (err) {\n\t\t\t// cb threw after a successful resolution — ask the error handler for recovery.\n\t\t\treturn this.queryErrorHandler(err, cb(result as R));\n\t\t}\n\t}\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=binder.js.map
3
+ //# sourceMappingURL=binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/binding.ts"],"names":[],"mappings":"","sourcesContent":["import {ClassConstructor, InjectableId, Injector} from './injector';\n\n/**\n * Type definition for functions that return a value.\n * The function should return a valid value, but may throw an exception if it cannot.\n */\nexport type SyncFactory<T> = (injector: Injector) => T;\n\n/**\n * Type definition for functions that return a Promise for a value.\n * The function *must* not throw and must return a valid Promise (e.g. pending, resolved, rejected).\n */\nexport type AsyncFactory<T> = (injector: Injector) => Promise<T>;\n\n/**\n * You may bind an error handler which will be invoked if the bound InjectableId could not be put into service.\n * An error handler *must* not throw, but may return an Error that will be propagated back up the call chain.\n *\n * @param injector The Injector that experienced the error.\n * @param id The identifier for what was trying to be made.\n * @param maker The thing that made (or tried to make) the value. Will be one of type ClassConstructor, SyncFactory, or AsyncFactory, depending on how you registered the binding.\n * @param error Identifies the problem that occurred.\n * @param value If the 'maker' was able to create the thing, but it had an error during post construction, the made thing will be passed here.\n * @returns one of 3 results...\n * A substitute thing (kind of like a 'maker' do-over) which must be fully operational (e.g. any `@PostConstruct` will be ignored).\n * An alternate Error which will be propagated back up the call chain.\n * Undefined, which means the 'error' parameter will be propagated back up the call chain.\n */\nexport type OnErrorCallback<T, M = unknown> = (injector: Injector, id: InjectableId<T>, maker: M, error: unknown, value?: T) => T | Error | void;\n\n/**\n * You may bind a success handler which will be invoked just before the bound InjectableId is put into service.\n * This is an alternative to the more preferred `@PostConstruct` decorator for scenarios when usage of that decorator is not feasible.\n * WARNING:\n * By registering a success handler, you override and nullify any `@PostConstruct` decorator on the class.\n * In such a scenario, the success handler should perform whatever care and feeding the class expected from the `@PostConstruct` decorator.\n * A success handler *must* not throw, but may return an Error that will be propagated back up the call chain.\n *\n * @param value The thing that was made.\n * @param injector The Injector that performed the construction.\n * @param id The identifier for what was made.\n * @param maker The thing that made. Will be one of type ClassConstructor, SyncFactory, or AsyncFactory, depending on how you registered the binding.\n * @returns one of 3 results...\n * An Error which will be propagated back up the call chain.\n * Undefined, which means the object is ready to be placed into service.\n * A Promise that resolves to one of the above two values (undefined or Error).\n */\nexport type OnSuccessCallback<T, M = unknown> = (value: T, injector: Injector, id: InjectableId<T>, maker: M) => Promise<Error | void> | Error | void;\n\n/**\n * Descriptor object used with {@link Container.register} to specify how an id should be bound.\n * Mirrors the TSyringe registration API.\n */\nexport type RegisterDescriptor<T> =\n\t| { useClass: ClassConstructor<T> }\n\t| { useValue: T }\n\t| { useFactory: SyncFactory<T> }\n\t| { useAsyncFactory: AsyncFactory<T> };\n\n/**\n * An interface allowing binding of an error handler.\n *\n * @see OnErrorCallback\n */\nexport interface BindErrHandler<T, M = unknown> {\n\tonError(cb: OnErrorCallback<T, M>): void;\n}\n\n/**\n * An interface allowing binding of a post construction handler.\n *\n * @see OnSuccessCallback\n */\nexport interface BindHandler<T, M = unknown> extends BindErrHandler<T, M> {\n\tonSuccess(cb: OnSuccessCallback<T, M>): BindErrHandler<T, M>;\n}\n\n/**\n * @inheritDoc\n * This specialization also allows you to specify that the binding is 'Singleton' (e.g. only one in the system).\n */\nexport interface BindAs<T, M = unknown> extends BindHandler<T, M> {\n\tasSingleton(): BindHandler<T, M>;\n}\n\n"]}