getbox 1.0.0 → 1.2.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/CONTEXT.md +123 -0
- package/README.md +86 -52
- package/dist/context.cjs +42 -0
- package/dist/context.d.cts +33 -0
- package/dist/context.d.mts +33 -0
- package/dist/context.mjs +38 -0
- package/dist/index.cjs +164 -3
- package/dist/index.d.cts +155 -1
- package/dist/index.d.mts +155 -1
- package/dist/index.mjs +162 -3
- package/package.json +20 -9
package/CONTEXT.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# getbox/context
|
|
2
|
+
|
|
3
|
+
`getbox/context` provides an alternative pattern where the box is available implicitly via `AsyncLocalStorage`. Classes can resolve dependencies directly in their constructors without needing `static init` methods or passing the box around.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Wrap your application entry point with `withBox` to create a scoped box.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { withBox } from "getbox/context";
|
|
11
|
+
|
|
12
|
+
withBox(() => {
|
|
13
|
+
// All code in this scope can resolve dependencies
|
|
14
|
+
const app = resolve(App);
|
|
15
|
+
app.start();
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You can also pass an existing box to the scope.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Box } from "getbox";
|
|
23
|
+
import { withBox } from "getbox/context";
|
|
24
|
+
|
|
25
|
+
const box = new Box();
|
|
26
|
+
Box.mock(box, LoggerFactory, new TestLogger());
|
|
27
|
+
|
|
28
|
+
withBox(box, () => {
|
|
29
|
+
const app = resolve(App);
|
|
30
|
+
app.start();
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Resolving dependencies
|
|
35
|
+
|
|
36
|
+
With the context pattern, classes can use `resolve` directly as field initializers. No `static init` method is needed.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { resolve } from "getbox/context";
|
|
40
|
+
|
|
41
|
+
class UserService {
|
|
42
|
+
public db = resolve(Database);
|
|
43
|
+
public logger = resolve(LoggerFactory);
|
|
44
|
+
|
|
45
|
+
createUser(name: string) {
|
|
46
|
+
this.logger.log(`Creating user: ${name}`);
|
|
47
|
+
return this.db.query("INSERT INTO users ...");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Compare this with the base pattern which requires `static init`:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { Box } from "getbox";
|
|
56
|
+
|
|
57
|
+
class UserService {
|
|
58
|
+
constructor(private db: Database, private logger: Logger) {}
|
|
59
|
+
|
|
60
|
+
static init(box: Box) {
|
|
61
|
+
return box.for(UserService).get(Database, LoggerFactory);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Resolving multiple dependencies
|
|
67
|
+
|
|
68
|
+
Use `resolveAll` to resolve multiple constructors at once. Accepts an object or array of constructors.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
withBox(() => {
|
|
72
|
+
// Object form
|
|
73
|
+
const { db, logger } = resolveAll({ db: Database, logger: LoggerFactory });
|
|
74
|
+
|
|
75
|
+
// Array form
|
|
76
|
+
const [db2, logger2] = resolveAll([Database, LoggerFactory]);
|
|
77
|
+
|
|
78
|
+
console.log(db === db2); // true (cached)
|
|
79
|
+
console.log(logger === logger2); // true (cached)
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Resolving constructor parameters
|
|
84
|
+
|
|
85
|
+
Use `construct` to create a class instance with resolved constructor parameters.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
class UserService {
|
|
89
|
+
constructor(private db: Database, private logger: Logger) {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
withBox(() => {
|
|
93
|
+
const service = construct(UserService).get(Database, LoggerFactory);
|
|
94
|
+
service.createUser("Alice");
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Accessing the box
|
|
99
|
+
|
|
100
|
+
Use `useBox()` to get the current box from the scope. This is useful when you need full access to the box API.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
withBox(() => {
|
|
104
|
+
const box = useBox();
|
|
105
|
+
const db = box.new(Database);
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Nested and concurrent scopes
|
|
110
|
+
|
|
111
|
+
Each `withBox` call creates an independent scope. Nested scopes do not share the parent box, and concurrent async scopes are isolated from each other.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
withBox(() => {
|
|
115
|
+
const db = resolve(Database);
|
|
116
|
+
|
|
117
|
+
// Inner scope gets a fresh box
|
|
118
|
+
withBox(() => {
|
|
119
|
+
const db2 = resolve(Database);
|
|
120
|
+
console.log(db === db2); // false (different scope)
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
```
|
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
### Lightweight dependency injection for TypeScript.
|
|
4
4
|
|
|
5
|
-
`getbox` provides a simple way of managing dependencies in TypeScript applications.
|
|
5
|
+
`getbox` provides a simple way of managing dependencies in TypeScript applications. Dependencies are defined as constructors that act as interfaces for the values they resolve.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Callers know the type of the value they need, but not how it will be derived. The box resolves constructors lazily and caches instances automatically.
|
|
8
|
+
|
|
9
|
+
For an alternative pattern using AsyncLocalStorage where classes can resolve dependencies directly in their constructors, see [getbox/context](./CONTEXT.md).
|
|
8
10
|
|
|
9
11
|
## Installation
|
|
10
12
|
|
|
@@ -22,8 +24,6 @@ Classes are instantiated once and cached. Subsequent calls return the cached ins
|
|
|
22
24
|
|
|
23
25
|
```ts
|
|
24
26
|
// printer.ts
|
|
25
|
-
import { Box } from "getbox";
|
|
26
|
-
|
|
27
27
|
export class Printer {
|
|
28
28
|
print(text: string): string {
|
|
29
29
|
return text.toUpperCase();
|
|
@@ -77,29 +77,21 @@ console.log(office.printer === printer); // true
|
|
|
77
77
|
Use `box.new()` to create a new instance each time without caching. This is useful for instances that should not be shared.
|
|
78
78
|
|
|
79
79
|
```ts
|
|
80
|
-
//
|
|
80
|
+
// main.ts
|
|
81
81
|
import { Box } from "getbox";
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
print(text: string): string {
|
|
87
|
-
return text.toUpperCase();
|
|
83
|
+
class Database {
|
|
84
|
+
connect() {
|
|
85
|
+
/* ... */
|
|
88
86
|
}
|
|
89
87
|
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
// main.ts
|
|
94
|
-
import { Box } from "getbox";
|
|
95
|
-
import { Printer } from "./printer";
|
|
96
88
|
|
|
97
89
|
const box = new Box();
|
|
98
90
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
91
|
+
const db1 = box.new(Database);
|
|
92
|
+
const db2 = box.new(Database);
|
|
101
93
|
|
|
102
|
-
console.log(
|
|
94
|
+
console.log(db1 === db2); // false
|
|
103
95
|
```
|
|
104
96
|
|
|
105
97
|
## Factory functions
|
|
@@ -146,7 +138,7 @@ export class UserService {
|
|
|
146
138
|
|
|
147
139
|
## Constants
|
|
148
140
|
|
|
149
|
-
Use the `constant` helper to register constant values without needing a factory or class.
|
|
141
|
+
Use the `constant` helper to register constant values without needing a factory or class. Constant values are never cached since they are already fixed.
|
|
150
142
|
|
|
151
143
|
```ts
|
|
152
144
|
import { Box, constant } from "getbox";
|
|
@@ -154,7 +146,7 @@ import { Box, constant } from "getbox";
|
|
|
154
146
|
const ApiUrl = constant("https://api.example.com");
|
|
155
147
|
const Port = constant(3000);
|
|
156
148
|
const Config = constant({
|
|
157
|
-
|
|
149
|
+
baseUrl: "https://example.com",
|
|
158
150
|
timeout: 5000,
|
|
159
151
|
});
|
|
160
152
|
|
|
@@ -169,39 +161,71 @@ console.log(port); // 3000
|
|
|
169
161
|
console.log(config.timeout); // 5000
|
|
170
162
|
```
|
|
171
163
|
|
|
172
|
-
|
|
164
|
+
Since constructors act as interfaces, a `constant` can later be replaced with a `factory` without changing any callers.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const ApiUrl = factory((box: Box) => {
|
|
168
|
+
const config = box.get(Config);
|
|
169
|
+
return `${config.baseUrl}/api`;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const apiUrl = box.get(ApiUrl); // "https://example.com/api"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Transient factories
|
|
173
176
|
|
|
174
|
-
Use `
|
|
177
|
+
Use the `transient` helper to create a factory whose result is never cached. The factory is called on every resolution, even when retrieved via `box.get()`.
|
|
175
178
|
|
|
176
179
|
```ts
|
|
177
|
-
|
|
178
|
-
export class Database {
|
|
179
|
-
connect() { /* ... */ }
|
|
180
|
-
}
|
|
180
|
+
import { Box, transient } from "getbox";
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
import { Box, factory } from "getbox";
|
|
182
|
+
const RequestId = transient(() => crypto.randomUUID());
|
|
184
183
|
|
|
185
|
-
|
|
186
|
-
log(message: string): void;
|
|
187
|
-
}
|
|
184
|
+
const box = new Box();
|
|
188
185
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
const id1 = box.get(RequestId);
|
|
187
|
+
const id2 = box.get(RequestId);
|
|
188
|
+
|
|
189
|
+
console.log(id1 === id2); // false
|
|
192
190
|
```
|
|
193
191
|
|
|
192
|
+
## Resolving multiple constructors
|
|
193
|
+
|
|
194
|
+
Use `box.all.get()` to resolve multiple constructors at once. Pass an object to get an object of instances, or an array to get an array of instances.
|
|
195
|
+
|
|
194
196
|
```ts
|
|
195
|
-
// service.ts
|
|
196
197
|
import { Box } from "getbox";
|
|
197
|
-
import { Database } from "./database";
|
|
198
|
-
import { Logger, LoggerFactory } from "./logger";
|
|
199
198
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
const box = new Box();
|
|
200
|
+
|
|
201
|
+
// Object form
|
|
202
|
+
const { db, logger } = box.all.get({ db: Database, logger: LoggerFactory });
|
|
203
|
+
|
|
204
|
+
// Array form
|
|
205
|
+
const [db2, logger2] = box.all.get([Database, LoggerFactory]);
|
|
206
|
+
|
|
207
|
+
console.log(db === db2); // true (cached)
|
|
208
|
+
console.log(logger === logger2); // true (cached)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Use `box.all.new()` to resolve multiple constructors as transient instances.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const { db } = box.all.new({ db: Database });
|
|
215
|
+
const [db2] = box.all.new([Database]);
|
|
216
|
+
|
|
217
|
+
console.log(db === db2); // false (transient)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Class constructors
|
|
221
|
+
|
|
222
|
+
Use `box.for()` inside a class's `static init` method to resolve constructor dependencies automatically. The instance returned by the builder is cached or transient depending on whether the class is retrieved via `box.get()` or `box.new()`.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { Box, factory } from "getbox";
|
|
226
|
+
|
|
227
|
+
class UserService {
|
|
228
|
+
constructor(private db: Database, private logger: Logger) {}
|
|
205
229
|
|
|
206
230
|
static init(box: Box) {
|
|
207
231
|
// Create new instance with cached dependencies
|
|
@@ -210,18 +234,10 @@ export class UserService {
|
|
|
210
234
|
|
|
211
235
|
createUser(name: string) {
|
|
212
236
|
this.logger.log(`Creating user: ${name}`);
|
|
213
|
-
// Use db to save user
|
|
214
237
|
}
|
|
215
238
|
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
// main.ts
|
|
220
|
-
import { Box } from "getbox";
|
|
221
|
-
import { UserService } from "./service";
|
|
222
239
|
|
|
223
240
|
const box = new Box();
|
|
224
|
-
|
|
225
241
|
const service = box.get(UserService);
|
|
226
242
|
service.createUser("Alice");
|
|
227
243
|
```
|
|
@@ -245,7 +261,8 @@ class MockLogger implements Logger {
|
|
|
245
261
|
}
|
|
246
262
|
|
|
247
263
|
const box = new Box();
|
|
248
|
-
|
|
264
|
+
const mockLogger = new MockLogger();
|
|
265
|
+
Box.mock(box, LoggerFactory, mockLogger);
|
|
249
266
|
|
|
250
267
|
const service = box.get(UserService);
|
|
251
268
|
service.createUser("Alice");
|
|
@@ -253,6 +270,23 @@ service.createUser("Alice");
|
|
|
253
270
|
console.log(mockLogger.messages); // ["Creating user: Alice"]
|
|
254
271
|
```
|
|
255
272
|
|
|
273
|
+
## Clearing the cache
|
|
274
|
+
|
|
275
|
+
Use `Box.clear` to remove cached instances. Pass a specific constructor to clear a single entry, or omit it to clear all cached instances.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
const box = new Box();
|
|
279
|
+
|
|
280
|
+
const db = box.get(Database);
|
|
281
|
+
|
|
282
|
+
// Clear a specific constructor
|
|
283
|
+
Box.clear(box, Database);
|
|
284
|
+
console.log(box.get(Database) === db); // false (new instance)
|
|
285
|
+
|
|
286
|
+
// Clear all cached instances
|
|
287
|
+
Box.clear(box);
|
|
288
|
+
```
|
|
289
|
+
|
|
256
290
|
## Circular dependencies
|
|
257
291
|
|
|
258
292
|
`getbox` does not prevent circular dependencies. You should structure your code to avoid circular imports between modules.
|
package/dist/context.cjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const require_index = require('./index.cjs');
|
|
2
|
+
let node_async_hooks = require("node:async_hooks");
|
|
3
|
+
|
|
4
|
+
//#region src/context.ts
|
|
5
|
+
const storage = new node_async_hooks.AsyncLocalStorage();
|
|
6
|
+
function withBox(boxOrFn, fn) {
|
|
7
|
+
if (typeof boxOrFn === "function") return storage.run(new require_index.Box(), boxOrFn);
|
|
8
|
+
return storage.run(boxOrFn, fn);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns the current {@link Box} from the active {@link withBox} scope.
|
|
12
|
+
* Throws if called outside a scope.
|
|
13
|
+
*/
|
|
14
|
+
function useBox() {
|
|
15
|
+
const box = storage.getStore();
|
|
16
|
+
if (!box) throw new Error("useBox() must be called within a withBox() scope");
|
|
17
|
+
return box;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves a cached instance from the current {@link withBox} scope.
|
|
21
|
+
* Shorthand for `useBox().get(constructor)`.
|
|
22
|
+
*/
|
|
23
|
+
function resolve(constructor) {
|
|
24
|
+
return useBox().get(constructor);
|
|
25
|
+
}
|
|
26
|
+
function resolveAll(constructors) {
|
|
27
|
+
return useBox().all.get(constructors);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns a builder for creating a class instance with resolved constructor
|
|
31
|
+
* parameters from the current {@link withBox} scope. Shorthand for `useBox().for(constructor)`.
|
|
32
|
+
*/
|
|
33
|
+
function construct(constructor) {
|
|
34
|
+
return useBox().for(constructor);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
exports.construct = construct;
|
|
39
|
+
exports.resolve = resolve;
|
|
40
|
+
exports.resolveAll = resolveAll;
|
|
41
|
+
exports.useBox = useBox;
|
|
42
|
+
exports.withBox = withBox;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Box, Construct, Constructor, ConstructorInstanceType } from "./index.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runs a function within a scoped {@link Box} context using AsyncLocalStorage.
|
|
7
|
+
* Creates a fresh Box if none is provided.
|
|
8
|
+
*/
|
|
9
|
+
declare function withBox<T>(fn: () => T): T;
|
|
10
|
+
declare function withBox<T>(box: Box, fn: () => T): T;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current {@link Box} from the active {@link withBox} scope.
|
|
13
|
+
* Throws if called outside a scope.
|
|
14
|
+
*/
|
|
15
|
+
declare function useBox(): Box;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a cached instance from the current {@link withBox} scope.
|
|
18
|
+
* Shorthand for `useBox().get(constructor)`.
|
|
19
|
+
*/
|
|
20
|
+
declare function resolve<T>(constructor: Constructor<T>): T;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves multiple cached instances from the current {@link withBox} scope.
|
|
23
|
+
* Shorthand for `useBox().all.get(constructors)`.
|
|
24
|
+
*/
|
|
25
|
+
declare function resolveAll<const T extends Constructor<any>[]>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
26
|
+
declare function resolveAll<T extends Record<string, Constructor<any>>>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
27
|
+
/**
|
|
28
|
+
* Returns a builder for creating a class instance with resolved constructor
|
|
29
|
+
* parameters from the current {@link withBox} scope. Shorthand for `useBox().for(constructor)`.
|
|
30
|
+
*/
|
|
31
|
+
declare function construct<T extends new (...args: any) => any>(constructor: T): Construct<T>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { construct, resolve, resolveAll, useBox, withBox };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Box, Construct, Constructor, ConstructorInstanceType } from "./index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runs a function within a scoped {@link Box} context using AsyncLocalStorage.
|
|
7
|
+
* Creates a fresh Box if none is provided.
|
|
8
|
+
*/
|
|
9
|
+
declare function withBox<T>(fn: () => T): T;
|
|
10
|
+
declare function withBox<T>(box: Box, fn: () => T): T;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current {@link Box} from the active {@link withBox} scope.
|
|
13
|
+
* Throws if called outside a scope.
|
|
14
|
+
*/
|
|
15
|
+
declare function useBox(): Box;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a cached instance from the current {@link withBox} scope.
|
|
18
|
+
* Shorthand for `useBox().get(constructor)`.
|
|
19
|
+
*/
|
|
20
|
+
declare function resolve<T>(constructor: Constructor<T>): T;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves multiple cached instances from the current {@link withBox} scope.
|
|
23
|
+
* Shorthand for `useBox().all.get(constructors)`.
|
|
24
|
+
*/
|
|
25
|
+
declare function resolveAll<const T extends Constructor<any>[]>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
26
|
+
declare function resolveAll<T extends Record<string, Constructor<any>>>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
27
|
+
/**
|
|
28
|
+
* Returns a builder for creating a class instance with resolved constructor
|
|
29
|
+
* parameters from the current {@link withBox} scope. Shorthand for `useBox().for(constructor)`.
|
|
30
|
+
*/
|
|
31
|
+
declare function construct<T extends new (...args: any) => any>(constructor: T): Construct<T>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { construct, resolve, resolveAll, useBox, withBox };
|
package/dist/context.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Box } from "./index.mjs";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
|
|
4
|
+
//#region src/context.ts
|
|
5
|
+
const storage = new AsyncLocalStorage();
|
|
6
|
+
function withBox(boxOrFn, fn) {
|
|
7
|
+
if (typeof boxOrFn === "function") return storage.run(new Box(), boxOrFn);
|
|
8
|
+
return storage.run(boxOrFn, fn);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns the current {@link Box} from the active {@link withBox} scope.
|
|
12
|
+
* Throws if called outside a scope.
|
|
13
|
+
*/
|
|
14
|
+
function useBox() {
|
|
15
|
+
const box = storage.getStore();
|
|
16
|
+
if (!box) throw new Error("useBox() must be called within a withBox() scope");
|
|
17
|
+
return box;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves a cached instance from the current {@link withBox} scope.
|
|
21
|
+
* Shorthand for `useBox().get(constructor)`.
|
|
22
|
+
*/
|
|
23
|
+
function resolve(constructor) {
|
|
24
|
+
return useBox().get(constructor);
|
|
25
|
+
}
|
|
26
|
+
function resolveAll(constructors) {
|
|
27
|
+
return useBox().all.get(constructors);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns a builder for creating a class instance with resolved constructor
|
|
31
|
+
* parameters from the current {@link withBox} scope. Shorthand for `useBox().for(constructor)`.
|
|
32
|
+
*/
|
|
33
|
+
function construct(constructor) {
|
|
34
|
+
return useBox().for(constructor);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { construct, resolve, resolveAll, useBox, withBox };
|
package/dist/index.cjs
CHANGED
|
@@ -1,38 +1,197 @@
|
|
|
1
1
|
|
|
2
2
|
//#region src/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates a {@link Constructor} from a factory function.
|
|
5
|
+
* The factory receives the box as an argument, allowing it to resolve other dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const LoggerFactory = factory((box: Box): Logger => {
|
|
10
|
+
* return new ConsoleLogger();
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const logger = box.get(LoggerFactory);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
3
16
|
function factory(init) {
|
|
4
17
|
return { init };
|
|
5
18
|
}
|
|
19
|
+
const noCacheSymbol = Symbol("Box constant");
|
|
20
|
+
/**
|
|
21
|
+
* Creates a {@link Constructor} from a factory function whose result is never cached.
|
|
22
|
+
* The factory is called on every resolution, always returning a fresh value
|
|
23
|
+
* even when retrieved via {@link Box.get}.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const RequestId = transient(() => crypto.randomUUID());
|
|
28
|
+
* const id1 = box.get(RequestId);
|
|
29
|
+
* const id2 = box.get(RequestId);
|
|
30
|
+
* console.log(id1 === id2); // false
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function transient(init) {
|
|
34
|
+
return {
|
|
35
|
+
init,
|
|
36
|
+
[noCacheSymbol]: true
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates a {@link Constructor} that always resolves to the given constant value.
|
|
41
|
+
* Constant values are never cached since they are already fixed.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const ApiUrl = constant("https://api.example.com");
|
|
46
|
+
* const port = box.get(ApiUrl); // "https://api.example.com"
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
6
49
|
function constant(value) {
|
|
7
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
init: () => value,
|
|
52
|
+
[noCacheSymbol]: true
|
|
53
|
+
};
|
|
8
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Dependency injection container that resolves and caches instances from
|
|
57
|
+
* a {@link Constructor}.
|
|
58
|
+
*
|
|
59
|
+
* A constructor is an object with an `init(box: Box)` method,
|
|
60
|
+
* a class with a `static init(box: Box)` method, or a class with a
|
|
61
|
+
* no-argument constructor. The {@link factory} and {@link constant} helpers
|
|
62
|
+
* create constructors from functions and values respectively.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* class Database {
|
|
67
|
+
* connect() {}
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* class UserService {
|
|
71
|
+
* constructor(private db: Database) {}
|
|
72
|
+
* static init(box: Box) {
|
|
73
|
+
* return new UserService(box.get(Database));
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* const box = new Box();
|
|
78
|
+
* const service = box.get(UserService);
|
|
79
|
+
* const db = box.get(Database);
|
|
80
|
+
*
|
|
81
|
+
* console.log(service.db === db); // true (cached)
|
|
82
|
+
* console.log(box.new(Database) === db); // false (transient)
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
9
85
|
var Box = class {
|
|
10
86
|
cache = /* @__PURE__ */ new Map();
|
|
87
|
+
/**
|
|
88
|
+
* Creates a new (transient) instance without caching. Useful for instances
|
|
89
|
+
* that should not be shared.
|
|
90
|
+
*/
|
|
11
91
|
new(constructor) {
|
|
12
92
|
return "init" in constructor ? constructor.init(this) : new constructor();
|
|
13
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolves an instance from the cache, or creates and caches a new one.
|
|
96
|
+
* Subsequent calls with the same constructor return the cached instance.
|
|
97
|
+
*/
|
|
14
98
|
get(constructor) {
|
|
15
99
|
if (this.cache.has(constructor)) return this.cache.get(constructor);
|
|
16
100
|
const value = this.new(constructor);
|
|
17
|
-
this.cache.set(constructor, value);
|
|
101
|
+
if (!(noCacheSymbol in constructor && constructor[noCacheSymbol])) this.cache.set(constructor, value);
|
|
18
102
|
return value;
|
|
19
103
|
}
|
|
104
|
+
/** Resolves multiple constructors at once. */
|
|
105
|
+
all = new BoxAll(this);
|
|
106
|
+
/**
|
|
107
|
+
* Returns a {@link Construct} builder for creating class instances by
|
|
108
|
+
* resolving constructors for each constructor parameter.
|
|
109
|
+
*
|
|
110
|
+
* Intended to be used inside a class's `static init` method. The instance
|
|
111
|
+
* returned by the builder is never cached itself, but can be cached when
|
|
112
|
+
* the class is retrieved via {@link Box.get} or kept transient via {@link Box.new}.
|
|
113
|
+
*/
|
|
20
114
|
for(constructor) {
|
|
21
115
|
return new Construct(this, constructor);
|
|
22
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Registers a mock value in the box's cache for a given constructor.
|
|
119
|
+
* Useful for replacing dependencies in tests.
|
|
120
|
+
*/
|
|
23
121
|
static mock(box, constructor, value) {
|
|
24
122
|
box.cache.set(constructor, value);
|
|
25
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Removes the instance from the box's cache for a given constructor.
|
|
126
|
+
* Removes all instances if no constructor is provided.
|
|
127
|
+
*/
|
|
128
|
+
static clear(box, constructor) {
|
|
129
|
+
if (!constructor) return box.cache.clear();
|
|
130
|
+
box.cache.delete(constructor);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
/** Resolves multiple constructors at once from a {@link Box}. */
|
|
134
|
+
var BoxAll = class {
|
|
135
|
+
constructor(box) {
|
|
136
|
+
this.box = box;
|
|
137
|
+
}
|
|
138
|
+
get(constructors) {
|
|
139
|
+
if (Array.isArray(constructors)) return constructors.map((c) => this.box.get(c));
|
|
140
|
+
const result = {};
|
|
141
|
+
for (const [key, constructor] of Object.entries(constructors)) result[key] = this.box.get(constructor);
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
new(constructors) {
|
|
145
|
+
if (Array.isArray(constructors)) return constructors.map((c) => this.box.new(c));
|
|
146
|
+
const result = {};
|
|
147
|
+
for (const [key, constructor] of Object.entries(constructors)) result[key] = this.box.new(constructor);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
26
150
|
};
|
|
151
|
+
/** Builder for creating class instances with constructor dependencies resolved from a {@link Box}. */
|
|
27
152
|
var Construct = class {
|
|
28
153
|
constructor(box, construct) {
|
|
29
154
|
this.box = box;
|
|
30
155
|
this.construct = construct;
|
|
31
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolves each dependency as a new transient instance via {@link Box.new},
|
|
159
|
+
* meaning dependencies are not cached or shared.
|
|
160
|
+
*
|
|
161
|
+
* The returned instance is cached or transient depending on whether the
|
|
162
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* class UserService {
|
|
167
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
168
|
+
* static init(box: Box) {
|
|
169
|
+
* return box.for(UserService).new(Database, LoggerFactory);
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
32
174
|
new(...args) {
|
|
33
175
|
const instances = args.map((arg) => this.box.new(arg));
|
|
34
176
|
return new this.construct(...instances);
|
|
35
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolves each dependency as a cached instance via {@link Box.get},
|
|
180
|
+
* meaning dependencies are shared across the box.
|
|
181
|
+
*
|
|
182
|
+
* The returned instance is cached or transient depending on whether the
|
|
183
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* class UserService {
|
|
188
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
189
|
+
* static init(box: Box) {
|
|
190
|
+
* return box.for(UserService).get(Database, LoggerFactory);
|
|
191
|
+
* }
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
36
195
|
get(...args) {
|
|
37
196
|
const instances = args.map((arg) => this.box.get(arg));
|
|
38
197
|
return new this.construct(...instances);
|
|
@@ -41,5 +200,7 @@ var Construct = class {
|
|
|
41
200
|
|
|
42
201
|
//#endregion
|
|
43
202
|
exports.Box = Box;
|
|
203
|
+
exports.Construct = Construct;
|
|
44
204
|
exports.constant = constant;
|
|
45
|
-
exports.factory = factory;
|
|
205
|
+
exports.factory = factory;
|
|
206
|
+
exports.transient = transient;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,29 +1,183 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A type that can be resolved by a {@link Box}. Either an object with an
|
|
4
|
+
* `init(box: Box)` method, a class with a `static init(box: Box)` method
|
|
5
|
+
* that returns an instance, or a class with a no-argument constructor.
|
|
6
|
+
*/
|
|
2
7
|
type Constructor<T> = {
|
|
3
8
|
init(box: Box): T;
|
|
4
9
|
} | {
|
|
5
10
|
new (): T;
|
|
6
11
|
};
|
|
12
|
+
/** Extracts the instance type from a {@link Constructor}. */
|
|
7
13
|
type ConstructorInstanceType<T> = T extends Constructor<infer U> ? U : never;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a {@link Constructor} from a factory function.
|
|
16
|
+
* The factory receives the box as an argument, allowing it to resolve other dependencies.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const LoggerFactory = factory((box: Box): Logger => {
|
|
21
|
+
* return new ConsoleLogger();
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* const logger = box.get(LoggerFactory);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
8
27
|
declare function factory<T>(init: (box: Box) => T): Constructor<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a {@link Constructor} from a factory function whose result is never cached.
|
|
30
|
+
* The factory is called on every resolution, always returning a fresh value
|
|
31
|
+
* even when retrieved via {@link Box.get}.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const RequestId = transient(() => crypto.randomUUID());
|
|
36
|
+
* const id1 = box.get(RequestId);
|
|
37
|
+
* const id2 = box.get(RequestId);
|
|
38
|
+
* console.log(id1 === id2); // false
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function transient<T>(init: (box: Box) => T): Constructor<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Creates a {@link Constructor} that always resolves to the given constant value.
|
|
44
|
+
* Constant values are never cached since they are already fixed.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const ApiUrl = constant("https://api.example.com");
|
|
49
|
+
* const port = box.get(ApiUrl); // "https://api.example.com"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
9
52
|
declare function constant<const T>(value: T): Constructor<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Dependency injection container that resolves and caches instances from
|
|
55
|
+
* a {@link Constructor}.
|
|
56
|
+
*
|
|
57
|
+
* A constructor is an object with an `init(box: Box)` method,
|
|
58
|
+
* a class with a `static init(box: Box)` method, or a class with a
|
|
59
|
+
* no-argument constructor. The {@link factory} and {@link constant} helpers
|
|
60
|
+
* create constructors from functions and values respectively.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* class Database {
|
|
65
|
+
* connect() {}
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* class UserService {
|
|
69
|
+
* constructor(private db: Database) {}
|
|
70
|
+
* static init(box: Box) {
|
|
71
|
+
* return new UserService(box.get(Database));
|
|
72
|
+
* }
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* const box = new Box();
|
|
76
|
+
* const service = box.get(UserService);
|
|
77
|
+
* const db = box.get(Database);
|
|
78
|
+
*
|
|
79
|
+
* console.log(service.db === db); // true (cached)
|
|
80
|
+
* console.log(box.new(Database) === db); // false (transient)
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
10
83
|
declare class Box {
|
|
11
84
|
private cache;
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new (transient) instance without caching. Useful for instances
|
|
87
|
+
* that should not be shared.
|
|
88
|
+
*/
|
|
12
89
|
new<T>(constructor: Constructor<T>): T;
|
|
90
|
+
/**
|
|
91
|
+
* Resolves an instance from the cache, or creates and caches a new one.
|
|
92
|
+
* Subsequent calls with the same constructor return the cached instance.
|
|
93
|
+
*/
|
|
13
94
|
get<T>(constructor: Constructor<T>): T;
|
|
95
|
+
/** Resolves multiple constructors at once. */
|
|
96
|
+
readonly all: BoxAll;
|
|
97
|
+
/**
|
|
98
|
+
* Returns a {@link Construct} builder for creating class instances by
|
|
99
|
+
* resolving constructors for each constructor parameter.
|
|
100
|
+
*
|
|
101
|
+
* Intended to be used inside a class's `static init` method. The instance
|
|
102
|
+
* returned by the builder is never cached itself, but can be cached when
|
|
103
|
+
* the class is retrieved via {@link Box.get} or kept transient via {@link Box.new}.
|
|
104
|
+
*/
|
|
14
105
|
for<T extends ClassConstructor<any>>(constructor: T): Construct<T>;
|
|
106
|
+
/**
|
|
107
|
+
* Registers a mock value in the box's cache for a given constructor.
|
|
108
|
+
* Useful for replacing dependencies in tests.
|
|
109
|
+
*/
|
|
15
110
|
static mock<T, V extends T = T>(box: Box, constructor: Constructor<T>, value: V): void;
|
|
111
|
+
/**
|
|
112
|
+
* Removes the instance from the box's cache for a given constructor.
|
|
113
|
+
* Removes all instances if no constructor is provided.
|
|
114
|
+
*/
|
|
115
|
+
static clear<T>(box: Box, constructor?: Constructor<T>): void;
|
|
16
116
|
}
|
|
117
|
+
/** Resolves multiple constructors at once from a {@link Box}. */
|
|
118
|
+
declare class BoxAll {
|
|
119
|
+
private box;
|
|
120
|
+
constructor(box: Box);
|
|
121
|
+
/**
|
|
122
|
+
* Resolves each constructor as a cached instance via {@link Box.get}.
|
|
123
|
+
* Accepts an array or object of constructors and returns instances in the same shape.
|
|
124
|
+
*/
|
|
125
|
+
get<const T extends Constructor<any>[]>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
126
|
+
get<T extends Record<string, Constructor<any>>>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
127
|
+
/**
|
|
128
|
+
* Resolves each constructor as a new transient instance via {@link Box.new}.
|
|
129
|
+
* Accepts an array or object of constructors and returns instances in the same shape.
|
|
130
|
+
*/
|
|
131
|
+
new<const T extends Constructor<any>[]>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
132
|
+
new<T extends Record<string, Constructor<any>>>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
133
|
+
}
|
|
134
|
+
/** Builder for creating class instances with constructor dependencies resolved from a {@link Box}. */
|
|
17
135
|
declare class Construct<T extends ClassConstructor<any>> {
|
|
18
136
|
private box;
|
|
19
137
|
private construct;
|
|
20
138
|
constructor(box: Box, construct: T);
|
|
139
|
+
/**
|
|
140
|
+
* Resolves each dependency as a new transient instance via {@link Box.new},
|
|
141
|
+
* meaning dependencies are not cached or shared.
|
|
142
|
+
*
|
|
143
|
+
* The returned instance is cached or transient depending on whether the
|
|
144
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* class UserService {
|
|
149
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
150
|
+
* static init(box: Box) {
|
|
151
|
+
* return box.for(UserService).new(Database, LoggerFactory);
|
|
152
|
+
* }
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
21
156
|
new(...args: ClassConstructorArgs<T>): InstanceType<T>;
|
|
157
|
+
/**
|
|
158
|
+
* Resolves each dependency as a cached instance via {@link Box.get},
|
|
159
|
+
* meaning dependencies are shared across the box.
|
|
160
|
+
*
|
|
161
|
+
* The returned instance is cached or transient depending on whether the
|
|
162
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* class UserService {
|
|
167
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
168
|
+
* static init(box: Box) {
|
|
169
|
+
* return box.for(UserService).get(Database, LoggerFactory);
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
22
174
|
get(...args: ClassConstructorArgs<T>): InstanceType<T>;
|
|
23
175
|
}
|
|
176
|
+
/** A class with any constructor signature. */
|
|
24
177
|
type ClassConstructor<T> = {
|
|
25
178
|
new (...args: any): T;
|
|
26
179
|
};
|
|
180
|
+
/** Maps each constructor parameter to its corresponding {@link Constructor} type. */
|
|
27
181
|
type ClassConstructorArgs<T extends ClassConstructor<any>, Args = ConstructorParameters<T>> = { [K in keyof Args]: Constructor<Args[K]> };
|
|
28
182
|
//#endregion
|
|
29
|
-
export { Box, Constructor, ConstructorInstanceType, constant, factory };
|
|
183
|
+
export { Box, Construct, Constructor, ConstructorInstanceType, constant, factory, transient };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,29 +1,183 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A type that can be resolved by a {@link Box}. Either an object with an
|
|
4
|
+
* `init(box: Box)` method, a class with a `static init(box: Box)` method
|
|
5
|
+
* that returns an instance, or a class with a no-argument constructor.
|
|
6
|
+
*/
|
|
2
7
|
type Constructor<T> = {
|
|
3
8
|
init(box: Box): T;
|
|
4
9
|
} | {
|
|
5
10
|
new (): T;
|
|
6
11
|
};
|
|
12
|
+
/** Extracts the instance type from a {@link Constructor}. */
|
|
7
13
|
type ConstructorInstanceType<T> = T extends Constructor<infer U> ? U : never;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a {@link Constructor} from a factory function.
|
|
16
|
+
* The factory receives the box as an argument, allowing it to resolve other dependencies.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const LoggerFactory = factory((box: Box): Logger => {
|
|
21
|
+
* return new ConsoleLogger();
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* const logger = box.get(LoggerFactory);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
8
27
|
declare function factory<T>(init: (box: Box) => T): Constructor<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a {@link Constructor} from a factory function whose result is never cached.
|
|
30
|
+
* The factory is called on every resolution, always returning a fresh value
|
|
31
|
+
* even when retrieved via {@link Box.get}.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const RequestId = transient(() => crypto.randomUUID());
|
|
36
|
+
* const id1 = box.get(RequestId);
|
|
37
|
+
* const id2 = box.get(RequestId);
|
|
38
|
+
* console.log(id1 === id2); // false
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function transient<T>(init: (box: Box) => T): Constructor<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Creates a {@link Constructor} that always resolves to the given constant value.
|
|
44
|
+
* Constant values are never cached since they are already fixed.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const ApiUrl = constant("https://api.example.com");
|
|
49
|
+
* const port = box.get(ApiUrl); // "https://api.example.com"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
9
52
|
declare function constant<const T>(value: T): Constructor<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Dependency injection container that resolves and caches instances from
|
|
55
|
+
* a {@link Constructor}.
|
|
56
|
+
*
|
|
57
|
+
* A constructor is an object with an `init(box: Box)` method,
|
|
58
|
+
* a class with a `static init(box: Box)` method, or a class with a
|
|
59
|
+
* no-argument constructor. The {@link factory} and {@link constant} helpers
|
|
60
|
+
* create constructors from functions and values respectively.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* class Database {
|
|
65
|
+
* connect() {}
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* class UserService {
|
|
69
|
+
* constructor(private db: Database) {}
|
|
70
|
+
* static init(box: Box) {
|
|
71
|
+
* return new UserService(box.get(Database));
|
|
72
|
+
* }
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* const box = new Box();
|
|
76
|
+
* const service = box.get(UserService);
|
|
77
|
+
* const db = box.get(Database);
|
|
78
|
+
*
|
|
79
|
+
* console.log(service.db === db); // true (cached)
|
|
80
|
+
* console.log(box.new(Database) === db); // false (transient)
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
10
83
|
declare class Box {
|
|
11
84
|
private cache;
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new (transient) instance without caching. Useful for instances
|
|
87
|
+
* that should not be shared.
|
|
88
|
+
*/
|
|
12
89
|
new<T>(constructor: Constructor<T>): T;
|
|
90
|
+
/**
|
|
91
|
+
* Resolves an instance from the cache, or creates and caches a new one.
|
|
92
|
+
* Subsequent calls with the same constructor return the cached instance.
|
|
93
|
+
*/
|
|
13
94
|
get<T>(constructor: Constructor<T>): T;
|
|
95
|
+
/** Resolves multiple constructors at once. */
|
|
96
|
+
readonly all: BoxAll;
|
|
97
|
+
/**
|
|
98
|
+
* Returns a {@link Construct} builder for creating class instances by
|
|
99
|
+
* resolving constructors for each constructor parameter.
|
|
100
|
+
*
|
|
101
|
+
* Intended to be used inside a class's `static init` method. The instance
|
|
102
|
+
* returned by the builder is never cached itself, but can be cached when
|
|
103
|
+
* the class is retrieved via {@link Box.get} or kept transient via {@link Box.new}.
|
|
104
|
+
*/
|
|
14
105
|
for<T extends ClassConstructor<any>>(constructor: T): Construct<T>;
|
|
106
|
+
/**
|
|
107
|
+
* Registers a mock value in the box's cache for a given constructor.
|
|
108
|
+
* Useful for replacing dependencies in tests.
|
|
109
|
+
*/
|
|
15
110
|
static mock<T, V extends T = T>(box: Box, constructor: Constructor<T>, value: V): void;
|
|
111
|
+
/**
|
|
112
|
+
* Removes the instance from the box's cache for a given constructor.
|
|
113
|
+
* Removes all instances if no constructor is provided.
|
|
114
|
+
*/
|
|
115
|
+
static clear<T>(box: Box, constructor?: Constructor<T>): void;
|
|
16
116
|
}
|
|
117
|
+
/** Resolves multiple constructors at once from a {@link Box}. */
|
|
118
|
+
declare class BoxAll {
|
|
119
|
+
private box;
|
|
120
|
+
constructor(box: Box);
|
|
121
|
+
/**
|
|
122
|
+
* Resolves each constructor as a cached instance via {@link Box.get}.
|
|
123
|
+
* Accepts an array or object of constructors and returns instances in the same shape.
|
|
124
|
+
*/
|
|
125
|
+
get<const T extends Constructor<any>[]>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
126
|
+
get<T extends Record<string, Constructor<any>>>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
127
|
+
/**
|
|
128
|
+
* Resolves each constructor as a new transient instance via {@link Box.new}.
|
|
129
|
+
* Accepts an array or object of constructors and returns instances in the same shape.
|
|
130
|
+
*/
|
|
131
|
+
new<const T extends Constructor<any>[]>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
132
|
+
new<T extends Record<string, Constructor<any>>>(constructors: T): { [K in keyof T]: ConstructorInstanceType<T[K]> };
|
|
133
|
+
}
|
|
134
|
+
/** Builder for creating class instances with constructor dependencies resolved from a {@link Box}. */
|
|
17
135
|
declare class Construct<T extends ClassConstructor<any>> {
|
|
18
136
|
private box;
|
|
19
137
|
private construct;
|
|
20
138
|
constructor(box: Box, construct: T);
|
|
139
|
+
/**
|
|
140
|
+
* Resolves each dependency as a new transient instance via {@link Box.new},
|
|
141
|
+
* meaning dependencies are not cached or shared.
|
|
142
|
+
*
|
|
143
|
+
* The returned instance is cached or transient depending on whether the
|
|
144
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* class UserService {
|
|
149
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
150
|
+
* static init(box: Box) {
|
|
151
|
+
* return box.for(UserService).new(Database, LoggerFactory);
|
|
152
|
+
* }
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
21
156
|
new(...args: ClassConstructorArgs<T>): InstanceType<T>;
|
|
157
|
+
/**
|
|
158
|
+
* Resolves each dependency as a cached instance via {@link Box.get},
|
|
159
|
+
* meaning dependencies are shared across the box.
|
|
160
|
+
*
|
|
161
|
+
* The returned instance is cached or transient depending on whether the
|
|
162
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* class UserService {
|
|
167
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
168
|
+
* static init(box: Box) {
|
|
169
|
+
* return box.for(UserService).get(Database, LoggerFactory);
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
22
174
|
get(...args: ClassConstructorArgs<T>): InstanceType<T>;
|
|
23
175
|
}
|
|
176
|
+
/** A class with any constructor signature. */
|
|
24
177
|
type ClassConstructor<T> = {
|
|
25
178
|
new (...args: any): T;
|
|
26
179
|
};
|
|
180
|
+
/** Maps each constructor parameter to its corresponding {@link Constructor} type. */
|
|
27
181
|
type ClassConstructorArgs<T extends ClassConstructor<any>, Args = ConstructorParameters<T>> = { [K in keyof Args]: Constructor<Args[K]> };
|
|
28
182
|
//#endregion
|
|
29
|
-
export { Box, Constructor, ConstructorInstanceType, constant, factory };
|
|
183
|
+
export { Box, Construct, Constructor, ConstructorInstanceType, constant, factory, transient };
|
package/dist/index.mjs
CHANGED
|
@@ -1,37 +1,196 @@
|
|
|
1
1
|
//#region src/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Creates a {@link Constructor} from a factory function.
|
|
4
|
+
* The factory receives the box as an argument, allowing it to resolve other dependencies.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const LoggerFactory = factory((box: Box): Logger => {
|
|
9
|
+
* return new ConsoleLogger();
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* const logger = box.get(LoggerFactory);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
2
15
|
function factory(init) {
|
|
3
16
|
return { init };
|
|
4
17
|
}
|
|
18
|
+
const noCacheSymbol = Symbol("Box constant");
|
|
19
|
+
/**
|
|
20
|
+
* Creates a {@link Constructor} from a factory function whose result is never cached.
|
|
21
|
+
* The factory is called on every resolution, always returning a fresh value
|
|
22
|
+
* even when retrieved via {@link Box.get}.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const RequestId = transient(() => crypto.randomUUID());
|
|
27
|
+
* const id1 = box.get(RequestId);
|
|
28
|
+
* const id2 = box.get(RequestId);
|
|
29
|
+
* console.log(id1 === id2); // false
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function transient(init) {
|
|
33
|
+
return {
|
|
34
|
+
init,
|
|
35
|
+
[noCacheSymbol]: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a {@link Constructor} that always resolves to the given constant value.
|
|
40
|
+
* Constant values are never cached since they are already fixed.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const ApiUrl = constant("https://api.example.com");
|
|
45
|
+
* const port = box.get(ApiUrl); // "https://api.example.com"
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
5
48
|
function constant(value) {
|
|
6
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
init: () => value,
|
|
51
|
+
[noCacheSymbol]: true
|
|
52
|
+
};
|
|
7
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Dependency injection container that resolves and caches instances from
|
|
56
|
+
* a {@link Constructor}.
|
|
57
|
+
*
|
|
58
|
+
* A constructor is an object with an `init(box: Box)` method,
|
|
59
|
+
* a class with a `static init(box: Box)` method, or a class with a
|
|
60
|
+
* no-argument constructor. The {@link factory} and {@link constant} helpers
|
|
61
|
+
* create constructors from functions and values respectively.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* class Database {
|
|
66
|
+
* connect() {}
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
* class UserService {
|
|
70
|
+
* constructor(private db: Database) {}
|
|
71
|
+
* static init(box: Box) {
|
|
72
|
+
* return new UserService(box.get(Database));
|
|
73
|
+
* }
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* const box = new Box();
|
|
77
|
+
* const service = box.get(UserService);
|
|
78
|
+
* const db = box.get(Database);
|
|
79
|
+
*
|
|
80
|
+
* console.log(service.db === db); // true (cached)
|
|
81
|
+
* console.log(box.new(Database) === db); // false (transient)
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
8
84
|
var Box = class {
|
|
9
85
|
cache = /* @__PURE__ */ new Map();
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new (transient) instance without caching. Useful for instances
|
|
88
|
+
* that should not be shared.
|
|
89
|
+
*/
|
|
10
90
|
new(constructor) {
|
|
11
91
|
return "init" in constructor ? constructor.init(this) : new constructor();
|
|
12
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolves an instance from the cache, or creates and caches a new one.
|
|
95
|
+
* Subsequent calls with the same constructor return the cached instance.
|
|
96
|
+
*/
|
|
13
97
|
get(constructor) {
|
|
14
98
|
if (this.cache.has(constructor)) return this.cache.get(constructor);
|
|
15
99
|
const value = this.new(constructor);
|
|
16
|
-
this.cache.set(constructor, value);
|
|
100
|
+
if (!(noCacheSymbol in constructor && constructor[noCacheSymbol])) this.cache.set(constructor, value);
|
|
17
101
|
return value;
|
|
18
102
|
}
|
|
103
|
+
/** Resolves multiple constructors at once. */
|
|
104
|
+
all = new BoxAll(this);
|
|
105
|
+
/**
|
|
106
|
+
* Returns a {@link Construct} builder for creating class instances by
|
|
107
|
+
* resolving constructors for each constructor parameter.
|
|
108
|
+
*
|
|
109
|
+
* Intended to be used inside a class's `static init` method. The instance
|
|
110
|
+
* returned by the builder is never cached itself, but can be cached when
|
|
111
|
+
* the class is retrieved via {@link Box.get} or kept transient via {@link Box.new}.
|
|
112
|
+
*/
|
|
19
113
|
for(constructor) {
|
|
20
114
|
return new Construct(this, constructor);
|
|
21
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Registers a mock value in the box's cache for a given constructor.
|
|
118
|
+
* Useful for replacing dependencies in tests.
|
|
119
|
+
*/
|
|
22
120
|
static mock(box, constructor, value) {
|
|
23
121
|
box.cache.set(constructor, value);
|
|
24
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Removes the instance from the box's cache for a given constructor.
|
|
125
|
+
* Removes all instances if no constructor is provided.
|
|
126
|
+
*/
|
|
127
|
+
static clear(box, constructor) {
|
|
128
|
+
if (!constructor) return box.cache.clear();
|
|
129
|
+
box.cache.delete(constructor);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/** Resolves multiple constructors at once from a {@link Box}. */
|
|
133
|
+
var BoxAll = class {
|
|
134
|
+
constructor(box) {
|
|
135
|
+
this.box = box;
|
|
136
|
+
}
|
|
137
|
+
get(constructors) {
|
|
138
|
+
if (Array.isArray(constructors)) return constructors.map((c) => this.box.get(c));
|
|
139
|
+
const result = {};
|
|
140
|
+
for (const [key, constructor] of Object.entries(constructors)) result[key] = this.box.get(constructor);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
new(constructors) {
|
|
144
|
+
if (Array.isArray(constructors)) return constructors.map((c) => this.box.new(c));
|
|
145
|
+
const result = {};
|
|
146
|
+
for (const [key, constructor] of Object.entries(constructors)) result[key] = this.box.new(constructor);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
25
149
|
};
|
|
150
|
+
/** Builder for creating class instances with constructor dependencies resolved from a {@link Box}. */
|
|
26
151
|
var Construct = class {
|
|
27
152
|
constructor(box, construct) {
|
|
28
153
|
this.box = box;
|
|
29
154
|
this.construct = construct;
|
|
30
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Resolves each dependency as a new transient instance via {@link Box.new},
|
|
158
|
+
* meaning dependencies are not cached or shared.
|
|
159
|
+
*
|
|
160
|
+
* The returned instance is cached or transient depending on whether the
|
|
161
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* class UserService {
|
|
166
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
167
|
+
* static init(box: Box) {
|
|
168
|
+
* return box.for(UserService).new(Database, LoggerFactory);
|
|
169
|
+
* }
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
31
173
|
new(...args) {
|
|
32
174
|
const instances = args.map((arg) => this.box.new(arg));
|
|
33
175
|
return new this.construct(...instances);
|
|
34
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolves each dependency as a cached instance via {@link Box.get},
|
|
179
|
+
* meaning dependencies are shared across the box.
|
|
180
|
+
*
|
|
181
|
+
* The returned instance is cached or transient depending on whether the
|
|
182
|
+
* class is retrieved via {@link Box.get} or {@link Box.new}.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* class UserService {
|
|
187
|
+
* constructor(private db: Database, private logger: Logger) {}
|
|
188
|
+
* static init(box: Box) {
|
|
189
|
+
* return box.for(UserService).get(Database, LoggerFactory);
|
|
190
|
+
* }
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
35
194
|
get(...args) {
|
|
36
195
|
const instances = args.map((arg) => this.box.get(arg));
|
|
37
196
|
return new this.construct(...instances);
|
|
@@ -39,4 +198,4 @@ var Construct = class {
|
|
|
39
198
|
};
|
|
40
199
|
|
|
41
200
|
//#endregion
|
|
42
|
-
export { Box, constant, factory };
|
|
201
|
+
export { Box, Construct, constant, factory, transient };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getbox",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Lightweight dependency injection for TypeScript",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -16,15 +16,18 @@
|
|
|
16
16
|
"types": "./dist/index.d.cts",
|
|
17
17
|
"default": "./dist/index.cjs"
|
|
18
18
|
}
|
|
19
|
+
},
|
|
20
|
+
"./context": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/context.d.mts",
|
|
23
|
+
"default": "./dist/context.mjs"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/context.d.cts",
|
|
27
|
+
"default": "./dist/context.cjs"
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsc && tsdown src/index.ts --format esm,cjs",
|
|
23
|
-
"release": "pnpm run build && changeset publish",
|
|
24
|
-
"watch": "vitest",
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"test:coverage": "vitest run --coverage"
|
|
27
|
-
},
|
|
28
31
|
"keywords": [
|
|
29
32
|
"DI",
|
|
30
33
|
"dependency injection",
|
|
@@ -44,9 +47,17 @@
|
|
|
44
47
|
"homepage": "https://github.com/eriicafes/getbox#readme",
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@changesets/cli": "^2.29.8",
|
|
50
|
+
"@types/node": "^25.2.3",
|
|
47
51
|
"@vitest/coverage-v8": "^4.0.16",
|
|
48
52
|
"tsdown": "^0.18.3",
|
|
49
53
|
"typescript": "^5.9.3",
|
|
50
54
|
"vitest": "^4.0.16"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc && tsdown src/index.ts src/context.ts --format esm,cjs",
|
|
58
|
+
"release": "pnpm run build && changeset publish",
|
|
59
|
+
"watch": "vitest",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:coverage": "vitest run --coverage"
|
|
51
62
|
}
|
|
52
|
-
}
|
|
63
|
+
}
|