inversify-typesafe-spring-like 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,11 +65,16 @@ class ArticleQueryService implements GetArticleUseCase {
65
65
  }
66
66
  }
67
67
 
68
- type Beans = {
68
+ type UseCaseBeans = {
69
69
  GetArticleUseCase: GetArticleUseCase; // interface (class is also possible)
70
+ }
71
+
72
+ type InfraBeans = {
70
73
  ArticleOutgoingPort: ArticleOutgoingPort; // interface (class is also possible)
71
74
  }
72
75
 
76
+ type Beans = UseCaseBeans & InfraBeans
77
+
73
78
  const beanConfig: BeanConfig<Beans> = {
74
79
  // compile error if ArticleQueryService is not compatible with GetArticleUseCase.
75
80
  GetArticleUseCase: (bind) => bind().to(ArticleQueryService),
@@ -79,26 +84,53 @@ const beanConfig: BeanConfig<Beans> = {
79
84
 
80
85
  const applicationContext = ApplicationContext(beanConfig);
81
86
 
82
- const getArticleUseCase = applicationContext.get("GetArticleUseCase")
87
+ function getUseCase<TUseCaseName extends keyof UseCaseBeans>(
88
+ useCaseName: TUseCaseName,
89
+ ): UseCaseBeans[TUseCaseName] {
90
+ return applicationContext.get(useCaseName)
91
+ }
92
+
93
+ // Only use-case beans can be resolved from application entry points.
94
+ const getArticleUseCase = getUseCase("GetArticleUseCase")
83
95
 
84
96
  getArticleUseCase.execute(1).then(console.log)
85
97
  ```
86
98
 
87
99
  ## TL;DR
88
100
 
89
- `inversify-typesafe-spring-like` is a Spring-flavored add-on for `inversify-typesafe`. Define a `Beans` map, register every bean with `BeanConfig`, create an `Autowired` decorator with `returnAutowired`, and initialize the container with `ApplicationContext`. Bean names are checked and return types are inferred at compile time, while beans use singleton scope by default.
101
+ `inversify-typesafe-spring-like` is a Spring-flavored add-on for `inversify-typesafe`. Define separate `UseCaseBeans` and `InfraBeans` maps, combine them into `Beans`, register every bean with `BeanConfig`, and create an `Autowired` decorator with `returnAutowired`. Initialize the container with `ApplicationContext`, and resolve only use cases through a `getUseCase` helper at application entry points. Bean names are checked and return types are inferred at compile time, while beans use singleton scope by default.
102
+
103
+ See the packaged [use-case beans guide](./docs/use-case-beans.md) for the complete recommended structure.
104
+
105
+ ## AI Agent Setup
106
+
107
+ Add the following block to the consuming project's `AGENTS.md` or `CLAUDE.md` so coding agents read this library's guidance before changing dependency-injection code. Installing this library does not create or modify either file automatically.
108
+
109
+ ```md
110
+ <!-- BEGIN:inversify-typesafe-spring-like-agent-rules -->
111
+ # `inversify-typesafe-spring-like` rules
112
+
113
+ Before adding or changing dependency-injection code, read `node_modules/inversify-typesafe-spring-like/docs/index.md` and the guide that matches the task.
114
+
115
+ - Use `UseCaseBeans` and `InfraBeans` as separate maps. Combine them for container setup, and expose use cases through `getUseCase` at application entry points.
116
+ - Read `node_modules/inversify-typesafe-spring-like/docs/lazy-application-context.md` when container creation should be deferred, such as in serverless applications.
117
+ - Read `node_modules/inversify-typesafe-spring-like/docs/domain-specific-bean-config.md` when a domain owns its own bean configuration while sharing common beans.
118
+ - Read `node_modules/inversify-typesafe-spring-like/docs/to-resolved-value.md` when one instance must be exposed through multiple bean keys.
119
+ - Do not bypass bean-key or resolved-value type checking with casts. Keep bean maps, `BeanConfig`, `Autowired`, and `getUseCase` definitions synchronized when bean names change.
120
+ <!-- END:inversify-typesafe-spring-like-agent-rules -->
121
+ ```
90
122
 
91
123
  <details>
92
124
  <summary><strong>For AI Agents Using This Library</strong></summary>
93
125
 
94
- - Define one `Beans` map whose string keys are bean names and whose values are the types returned for those names.
95
- - Declare a `BeanConfig<Beans>` containing every key in `Beans`, and bind each key to a compatible implementation with the provided `bind()` function.
126
+ - Define `UseCaseBeans` for application entry points and `InfraBeans` for infrastructure dependencies, then combine them as `type Beans = UseCaseBeans & InfraBeans`.
127
+ - Declare a `BeanConfig<Beans>` containing every key in the combined map, and bind each key to a compatible implementation with the provided `bind()` function.
96
128
  - Create a project-local decorator by destructuring `const { Autowired } = returnAutowired<Beans>()`, then use `@Autowired("BeanName")` for constructor injection.
97
129
  - Pass the bean config to `ApplicationContext`; let TypeScript infer the context type instead of adding a generic argument manually.
98
- - Resolve beans with `applicationContext.get("BeanName")`. Do not cast bean names or result types, because doing so bypasses compile-time checks.
130
+ - Expose a `getUseCase<T extends keyof UseCaseBeans>` helper that calls `applicationContext.get`; use it at application entry points so infrastructure beans cannot be resolved there.
99
131
  - Assume singleton scope unless an explicit `ContainerOptions` argument passed to `ApplicationContext` overrides `defaultScope`.
100
132
  - Use the underlying `inversify-typesafe` capabilities for advanced bindings and the original InversifyJS API. `BeanConfig`, `ApplicationContext`, and `returnAutowired` correspond to `TypesafeServiceConfig`, `createTypesafeContainer`, and `returnTypesafeInject` respectively.
101
- - Keep the `Beans` map, bean config, `get` calls, and `Autowired` decorators synchronized whenever a bean is added, removed, or renamed.
133
+ - Keep the bean maps, bean config, `getUseCase` helper, and `Autowired` decorators synchronized whenever a bean is added, removed, or renamed.
102
134
 
103
135
  </details>
104
136
 
@@ -150,6 +182,8 @@ For complete usage documentation and advanced features, please refer to the [inv
150
182
 
151
183
  ### Lazy ApplicationContext Initialization
152
184
 
185
+ For a self-contained reference, see the packaged [lazy ApplicationContext guide](./docs/lazy-application-context.md).
186
+
153
187
  In production applications, you may want to defer the initialization of the ApplicationContext until it's actually needed. This is especially useful in serverless environments or when you want to avoid initialization overhead during module loading.
154
188
 
155
189
  **The `lazy` utility function:**
@@ -225,6 +259,8 @@ const useCase = applicationContext().get("GetArticleUseCase");
225
259
 
226
260
  ### Domain-Specific BeanConfig
227
261
 
262
+ For a self-contained reference, see the packaged [domain-specific BeanConfig guide](./docs/domain-specific-bean-config.md).
263
+
228
264
  As your application grows, you may want to organize beans by domain. This pattern allows each domain module to define its own beans while extending a common configuration.
229
265
 
230
266
  ```ts
@@ -297,6 +333,8 @@ class ArticleQueryService implements GetArticleUseCase {
297
333
 
298
334
  ### Reusing a Single Class for Multiple Interfaces with `toResolvedValue`
299
335
 
336
+ For a self-contained reference, see the packaged [`toResolvedValue` guide](./docs/to-resolved-value.md).
337
+
300
338
  When a single class implements multiple interfaces (e.g., both `QueryPort` and `CommandPort`), you can use `toResolvedValue` to register the same instance under different bean names. This avoids creating separate instances and ensures consistency.
301
339
 
302
340
  ```ts
@@ -0,0 +1,57 @@
1
+ # Domain-specific BeanConfig
2
+
3
+ Let a domain extend common bean definitions when it owns its application services but shares cross-cutting infrastructure such as logging.
4
+
5
+ ```ts
6
+ import {
7
+ BeanConfig,
8
+ returnAutowired,
9
+ } from "inversify-typesafe-spring-like";
10
+
11
+ interface LoggingPort {
12
+ log(message: string): void;
13
+ }
14
+
15
+ class ConsoleLogger implements LoggingPort {
16
+ log(message: string): void {
17
+ console.log(message);
18
+ }
19
+ }
20
+
21
+ type CommonBeans = {
22
+ LoggingPort: LoggingPort;
23
+ };
24
+
25
+ const commonBeanConfig: BeanConfig<CommonBeans> = {
26
+ LoggingPort: (bind) => bind().to(ConsoleLogger),
27
+ };
28
+
29
+ interface GetArticleUseCase {
30
+ execute(id: number): string;
31
+ }
32
+
33
+ type ArticleBeans = CommonBeans & {
34
+ GetArticleUseCase: GetArticleUseCase;
35
+ };
36
+
37
+ const { Autowired: ArticleAutowired } = returnAutowired<ArticleBeans>();
38
+
39
+ class ArticleQueryService implements GetArticleUseCase {
40
+ constructor(
41
+ @ArticleAutowired("LoggingPort")
42
+ private readonly loggingPort: LoggingPort,
43
+ ) {}
44
+
45
+ execute(id: number): string {
46
+ this.loggingPort.log(`Loading article ${id}`);
47
+ return `Article #${id}`;
48
+ }
49
+ }
50
+
51
+ export const articleBeanConfig: BeanConfig<ArticleBeans> = {
52
+ ...commonBeanConfig,
53
+ GetArticleUseCase: (bind) => bind().to(ArticleQueryService),
54
+ };
55
+ ```
56
+
57
+ The domain map must include every common key that its services inject. Keep the domain's own bean names and bindings near the domain; compose the resulting config at the application composition root.
package/docs/index.md ADDED
@@ -0,0 +1,12 @@
1
+ # `inversify-typesafe-spring-like` guides
2
+
3
+ Use these guides when configuring dependency injection in an application. They are shipped with the package, so installed copies are available at `node_modules/inversify-typesafe-spring-like/docs/`.
4
+
5
+ | Need | Guide |
6
+ | --- | --- |
7
+ | Define application and infrastructure boundaries | [Use-case beans](./use-case-beans.md) |
8
+ | Defer container initialization | [Lazy ApplicationContext](./lazy-application-context.md) |
9
+ | Compose common and domain-specific beans | [Domain-specific BeanConfig](./domain-specific-bean-config.md) |
10
+ | Expose one instance through multiple bean keys | [`toResolvedValue`](./to-resolved-value.md) |
11
+
12
+ Start with [Use-case beans](./use-case-beans.md) unless the application already has an established dependency-injection structure.
@@ -0,0 +1,42 @@
1
+ # Lazy ApplicationContext initialization
2
+
3
+ Create the container on first use when eager initialization would add unnecessary startup work, such as in serverless handlers. The lazy function caches the first container instance.
4
+
5
+ ```ts
6
+ import "reflect-metadata";
7
+ import {
8
+ ApplicationContext,
9
+ BeanConfig,
10
+ } from "inversify-typesafe-spring-like";
11
+
12
+ const lazy = <T>(factory: () => T) => {
13
+ let value: T | undefined;
14
+ return (): T => value ?? (value = factory());
15
+ };
16
+
17
+ interface HealthCheckUseCase {
18
+ execute(): { status: "ok" };
19
+ }
20
+
21
+ class HealthCheckService implements HealthCheckUseCase {
22
+ execute() {
23
+ return { status: "ok" } as const;
24
+ }
25
+ }
26
+
27
+ type Beans = {
28
+ HealthCheckUseCase: HealthCheckUseCase;
29
+ };
30
+
31
+ const beanConfig: BeanConfig<Beans> = {
32
+ HealthCheckUseCase: (bind) => bind().to(HealthCheckService),
33
+ };
34
+
35
+ export const applicationContext = lazy(() => ApplicationContext(beanConfig));
36
+
37
+ export function handleHealthCheck() {
38
+ return applicationContext().get("HealthCheckUseCase").execute();
39
+ }
40
+ ```
41
+
42
+ Use this pattern at the composition root. Call `applicationContext()` only when a request or job needs a bean; subsequent calls reuse the same `ApplicationContext` and its singleton bindings.
@@ -0,0 +1,58 @@
1
+ # Reusing one instance with `toResolvedValue`
2
+
3
+ Use `toResolvedValue` when one implementation must be exposed through multiple bean keys without creating multiple instances. Bind the concrete implementation once, then derive each additional bean from the resolved dependency.
4
+
5
+ ```ts
6
+ import {
7
+ ApplicationContext,
8
+ BeanConfig,
9
+ } from "inversify-typesafe-spring-like";
10
+
11
+ interface Stay {
12
+ id: number;
13
+ name: string;
14
+ }
15
+
16
+ interface StayQueryPort {
17
+ findById(id: number): Promise<Stay | undefined>;
18
+ }
19
+
20
+ interface StayCommandPort {
21
+ save(stay: Stay): Promise<Stay>;
22
+ }
23
+
24
+ class StayPersistenceAdapter implements StayQueryPort, StayCommandPort {
25
+ private readonly stays = new Map<number, Stay>();
26
+
27
+ async findById(id: number): Promise<Stay | undefined> {
28
+ return this.stays.get(id);
29
+ }
30
+
31
+ async save(stay: Stay): Promise<Stay> {
32
+ this.stays.set(stay.id, stay);
33
+ return stay;
34
+ }
35
+ }
36
+
37
+ type Beans = {
38
+ StayQueryPort: StayQueryPort;
39
+ StayCommandPort: StayCommandPort;
40
+ };
41
+
42
+ const beanConfig: BeanConfig<Beans> = {
43
+ StayQueryPort: (bind) => bind().to(StayPersistenceAdapter),
44
+ StayCommandPort: (bind) =>
45
+ bind().toResolvedValue(
46
+ (queryPort) => queryPort as StayCommandPort,
47
+ ["StayQueryPort"],
48
+ ),
49
+ };
50
+
51
+ const applicationContext = ApplicationContext(beanConfig);
52
+ const queryPort = applicationContext.get("StayQueryPort");
53
+ const commandPort = applicationContext.get("StayCommandPort");
54
+
55
+ console.log(queryPort === commandPort); // true
56
+ ```
57
+
58
+ The dependency array is resolved before the transform function runs. The transform function receives those resolved values and returns the value for the new bean key. Use this for aliases, derived values, or a single adapter that fulfills multiple interfaces. Keep the cast localized to the transform only when TypeScript cannot infer that the resolved implementation satisfies the second interface.
@@ -0,0 +1,74 @@
1
+ # Use-case and infrastructure beans
2
+
3
+ Use separate maps for application entry points and infrastructure dependencies. Combine them only when configuring the container, and expose a `getUseCase` helper so controllers and handlers cannot resolve infrastructure beans directly.
4
+
5
+ ```ts
6
+ import "reflect-metadata";
7
+ import {
8
+ ApplicationContext,
9
+ BeanConfig,
10
+ returnAutowired,
11
+ } from "inversify-typesafe-spring-like";
12
+
13
+ interface Article {
14
+ id: number;
15
+ title: string;
16
+ }
17
+
18
+ interface ArticleOutgoingPort {
19
+ getById(id: number): Promise<Article>;
20
+ }
21
+
22
+ interface GetArticleUseCase {
23
+ execute(id: number): Promise<Article>;
24
+ }
25
+
26
+ class ArticleRepository implements ArticleOutgoingPort {
27
+ async getById(id: number): Promise<Article> {
28
+ return { id, title: `Article #${id}` };
29
+ }
30
+ }
31
+
32
+ type UseCaseBeans = {
33
+ GetArticleUseCase: GetArticleUseCase;
34
+ };
35
+
36
+ type InfraBeans = {
37
+ ArticleOutgoingPort: ArticleOutgoingPort;
38
+ };
39
+
40
+ type Beans = UseCaseBeans & InfraBeans;
41
+
42
+ const { Autowired } = returnAutowired<Beans>();
43
+
44
+ class ArticleQueryService implements GetArticleUseCase {
45
+ constructor(
46
+ @Autowired("ArticleOutgoingPort")
47
+ private readonly articleOutgoingPort: ArticleOutgoingPort,
48
+ ) {}
49
+
50
+ execute(id: number): Promise<Article> {
51
+ return this.articleOutgoingPort.getById(id);
52
+ }
53
+ }
54
+
55
+ const beanConfig: BeanConfig<Beans> = {
56
+ GetArticleUseCase: (bind) => bind().to(ArticleQueryService),
57
+ ArticleOutgoingPort: (bind) => bind().to(ArticleRepository),
58
+ };
59
+
60
+ const applicationContext = ApplicationContext(beanConfig);
61
+
62
+ export function getUseCase<TUseCaseName extends keyof UseCaseBeans>(
63
+ useCaseName: TUseCaseName,
64
+ ): UseCaseBeans[TUseCaseName] {
65
+ return applicationContext.get(useCaseName);
66
+ }
67
+
68
+ const useCase = getUseCase("GetArticleUseCase");
69
+ useCase.execute(1).then(console.log);
70
+ ```
71
+
72
+ `Beans` is the complete container contract, so application services can inject infrastructure ports. `getUseCase` accepts only `keyof UseCaseBeans`, which keeps infrastructure resolution inside composition code instead of application entry points.
73
+
74
+ Do not call `applicationContext.get` from controllers or cast bean keys to bypass this boundary.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "inversify-typesafe-spring-like",
3
3
  "type": "module",
4
- "version": "0.5.10",
4
+ "version": "0.5.11",
5
5
  "description": "Add-On Library for inversify-typesafe to make it more like Spring.",
6
6
  "source": "src/index.ts",
7
7
  "exports": {
@@ -25,7 +25,8 @@
25
25
  "unpkg": "dist/index.umd.js",
26
26
  "files": [
27
27
  "dist/**/*",
28
- "package.json"
28
+ "package.json",
29
+ "docs/**/*.md"
29
30
  ],
30
31
  "scripts": {
31
32
  "preinstall": "npx only-allow pnpm",
@@ -61,7 +62,7 @@
61
62
  "author": "Myeongjae Kim",
62
63
  "license": "MIT",
63
64
  "dependencies": {
64
- "inversify-typesafe": "^0.5.10"
65
+ "inversify-typesafe": "^0.5.11"
65
66
  },
66
67
  "devDependencies": {
67
68
  "inversify": "^8.1.3",