@enshou/di 0.0.1 → 0.1.1

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 ADDED
@@ -0,0 +1,137 @@
1
+ # @enshou/di
2
+
3
+ A small dependency injection container for TypeScript.
4
+
5
+ It gives you a typed token system, a simple container, and an explicit
6
+ `@Inject(...)` decorator for constructor dependencies.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pnpm add @enshou/di
12
+ ```
13
+
14
+ ## Quick example
15
+
16
+ ```ts
17
+ import { Container, Inject, createToken } from '@enshou/di'
18
+
19
+ type AppConfig = {
20
+ apiUrl: string
21
+ }
22
+
23
+ const CONFIG = createToken<AppConfig>('config')
24
+ const LOGGER = createToken<Logger>('logger')
25
+ const API = createToken<ApiClient>('api')
26
+
27
+ class Logger {
28
+ log(message: string) {
29
+ console.log(message)
30
+ }
31
+ }
32
+
33
+ @Inject([CONFIG, LOGGER])
34
+ class ApiClient {
35
+ constructor(
36
+ private readonly config: AppConfig,
37
+ private readonly logger: Logger,
38
+ ) {}
39
+
40
+ ping() {
41
+ this.logger.log(`GET ${this.config.apiUrl}/ping`)
42
+ }
43
+ }
44
+
45
+ const container = new Container()
46
+
47
+ container.registerValue(CONFIG, {
48
+ apiUrl: 'https://example.dev',
49
+ })
50
+
51
+ container.registerClass(LOGGER, Logger)
52
+ container.registerClass(API, ApiClient)
53
+
54
+ const api = container.resolve(API)
55
+ api.ping()
56
+ ```
57
+
58
+ ## API
59
+
60
+ ### `createToken<T>(description)`
61
+
62
+ Creates a unique, typed token.
63
+
64
+ ```ts
65
+ const USER_REPOSITORY = createToken<UserRepository>('user-repository')
66
+ ```
67
+
68
+ ### `new Container()`
69
+
70
+ Creates a new dependency container.
71
+
72
+ ### `container.registerValue(token, value)`
73
+
74
+ Registers a prebuilt value.
75
+
76
+ Values are stored in the singleton cache, so resolving the same token always
77
+ returns the same instance.
78
+
79
+ ```ts
80
+ container.registerValue(CONFIG, { apiUrl: 'https://example.dev' })
81
+ ```
82
+
83
+ ### `container.registerClass(token, Class, scope?)`
84
+
85
+ Registers a class provider for a token.
86
+
87
+ - `singleton` by default: the instance is created once and reused
88
+ - `transient`: a new instance is created on every `resolve`
89
+
90
+ ```ts
91
+ container.registerClass(LOGGER, Logger, 'singleton')
92
+ container.registerClass(API, ApiClient, 'transient')
93
+ ```
94
+
95
+ ### `container.resolve(token)`
96
+
97
+ Resolves a dependency by token.
98
+
99
+ Throws if no provider has been registered for that token.
100
+
101
+ ```ts
102
+ const logger = container.resolve(LOGGER)
103
+ ```
104
+
105
+ ### `@Inject(tokens)`
106
+
107
+ Declares the constructor dependencies for a class.
108
+
109
+ The order of tokens must match the order of constructor parameters.
110
+
111
+ ```ts
112
+ @Inject([CONFIG, LOGGER])
113
+ class ApiClient {
114
+ constructor(config: AppConfig, logger: Logger) {}
115
+ }
116
+ ```
117
+
118
+ ## Container behavior
119
+
120
+ - `registerValue` always stores the value in the singleton cache
121
+ - class dependencies are resolved recursively through `resolve`
122
+ - singleton instances are created lazily on first resolution
123
+ - transient instances are never cached
124
+
125
+ ## Limitations
126
+
127
+ - dependencies are declared explicitly with `@Inject(...)`; constructor types are not read automatically
128
+ - the container only supports class providers and value providers
129
+ - circular dependencies are not handled specially
130
+ - tokens are compared by identity, so you must use the same token instance for registration and resolution
131
+
132
+ ## Exports
133
+
134
+ ```ts
135
+ import { Container, Inject, createToken } from '@enshou/di'
136
+ import type { Scope, Token } from '@enshou/di'
137
+ ```
package/dist/index.d.ts CHANGED
@@ -1,4 +1,22 @@
1
- //#region src/index.d.ts
2
- declare const DI = "DI";
1
+ //#region src/token.d.ts
2
+ type Token<T> = symbol & {
3
+ __type: T;
4
+ };
5
+ declare function createToken<T>(description: string): Token<T>;
3
6
  //#endregion
4
- export { DI };
7
+ //#region src/container.d.ts
8
+ type Class$1<T> = new (...args: any[]) => T;
9
+ type Scope = "singleton" | "transient";
10
+ declare class Container {
11
+ private readonly providers;
12
+ private readonly singletonCache;
13
+ registerValue(token: Token<unknown>, value: unknown): void;
14
+ registerClass(token: Token<unknown>, value: Class$1<any>, scope?: Scope): void;
15
+ resolve<T>(token: Token<T>): T;
16
+ }
17
+ //#endregion
18
+ //#region src/inject.d.ts
19
+ type Class<T> = new (...args: any[]) => T;
20
+ declare function Inject(tokens: Array<Token<any>>): <T extends Class<any>>(target: T, _context?: ClassDecoratorContext<T>) => void;
21
+ //#endregion
22
+ export { Container, Inject, type Scope, type Token, createToken };
package/dist/index.js CHANGED
@@ -1,4 +1,41 @@
1
- //#region src/index.ts
2
- const DI = "DI";
1
+ //#region src/metadata.ts
2
+ const INJECTS_KEY = Symbol("injects");
3
3
  //#endregion
4
- export { DI };
4
+ //#region src/container.ts
5
+ var Container = class {
6
+ providers = /* @__PURE__ */ new Map();
7
+ singletonCache = /* @__PURE__ */ new Map();
8
+ registerValue(token, value) {
9
+ this.singletonCache.set(token, value);
10
+ }
11
+ registerClass(token, value, scope = "singleton") {
12
+ this.providers.set(token, {
13
+ kind: "class",
14
+ useClass: value,
15
+ scope
16
+ });
17
+ }
18
+ resolve(token) {
19
+ if (this.singletonCache.has(token)) return this.singletonCache.get(token);
20
+ const provider = this.providers.get(token);
21
+ if (!provider) throw Error(`No provider for ${String(token)}`);
22
+ const deps = (provider.useClass[INJECTS_KEY] ?? []).map(this.resolve.bind(this));
23
+ const value = new provider.useClass(...deps);
24
+ if (provider.scope === "singleton") this.singletonCache.set(token, value);
25
+ return value;
26
+ }
27
+ };
28
+ //#endregion
29
+ //#region src/inject.ts
30
+ function Inject(tokens) {
31
+ return function(target, _context) {
32
+ target[INJECTS_KEY] = tokens;
33
+ };
34
+ }
35
+ //#endregion
36
+ //#region src/token.ts
37
+ function createToken(description) {
38
+ return Symbol(description);
39
+ }
40
+ //#endregion
41
+ export { Container, Inject, createToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enshou/di",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "author": "Ivan Popov <iiivanpopov999@gmail.com>",
@@ -20,7 +20,8 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "@typescript/native-preview": "^7.0.0-dev.20260328.1",
23
- "tsdown": "^0.21.7"
23
+ "tsdown": "^0.21.7",
24
+ "vitest": "^4.1.2"
24
25
  },
25
26
  "scripts": {
26
27
  "build": "tsdown"