@amriogit/injector 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 amrio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @amriogit/injector
2
+
3
+ 轻量 DI(依赖注入)容器。零外部依赖,可在 Node.js、浏览器、SSR 中运行。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @amriogit/injector
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ### 核心 DI
14
+
15
+ ```ts
16
+ import { Injector, BaseService, InjectionToken } from '@amriogit/injector'
17
+
18
+ // 定义 Service
19
+ class Logger extends BaseService {
20
+ log = (msg: string) => console.log(msg)
21
+ }
22
+
23
+ class UserService extends BaseService {
24
+ private logger = this.$inject(Logger) // 声明依赖
25
+ private db = this.$inject(DB_POOL) // InjectionToken 也支持
26
+
27
+ state = reactive({ users: [] as User[] })
28
+
29
+ onInit() {
30
+ // 依赖已就绪,做初始化
31
+ }
32
+
33
+ fetchUsers = async () => { /* ... */ }
34
+ }
35
+
36
+ // 创建容器
37
+ const injector = new Injector()
38
+
39
+ // 覆写实现
40
+ injector.provide(Logger, MockLogger)
41
+
42
+ // 获取实例(自动实例化 + 单例)
43
+ const userService = injector.inject(UserService)
44
+ ```
45
+
46
+ ### Vue 3 集成
47
+
48
+ ```ts
49
+ import { createApp } from 'vue'
50
+ import { ServicePlugin, useInject, useProvide } from '@amriogit/injector/vue'
51
+
52
+ // 安装插件
53
+ createApp(App)
54
+ .use(ServicePlugin)
55
+ .mount('#app')
56
+
57
+ // 组件中注入 Service
58
+ const userService = useInject(UserService)
59
+
60
+ // 为子树覆写实现
61
+ useProvide(UserService, MockUserService)
62
+ ```
63
+
64
+ ## 特性
65
+
66
+ - **零依赖** — 纯 TypeScript,无运行时依赖
67
+ - **无全局单例** — 每个应用/请求创建独立 Injector
68
+ - **自动实例化** — 类 token 首次 `inject()` 时自动创建,无需手动注册
69
+ - **类型安全** — `provide()` 函数重载在编译期防止错误的实现类型
70
+ - **InjectionToken** — 为非类注入(配置、缓存等)提供类型安全
71
+ - **生命周期** — `onInit()` 钩子在依赖就绪后调用
72
+ - **SSR 兼容** — 无 DOM 依赖,InjectionToken 可序列化
73
+
74
+ ## 测试
75
+
76
+ ```bash
77
+ npm test
78
+ ```
@@ -0,0 +1,41 @@
1
+ import { Injector, InjectionToken, type Token } from './injector';
2
+ /**
3
+ * Vue 3 插件。创建作用域 Injector 并通过 provide/inject
4
+ * 使其对所有后代组件可用。
5
+ *
6
+ * 用法:
7
+ * ```ts
8
+ * createApp(App).use(ServicePlugin).mount('#app')
9
+ * ```
10
+ *
11
+ * 可通过第二个参数传入 setup 回调,在 Service 自动实例化前
12
+ * 为 Injector 提供基础设施 token(如拦截器集合):
13
+ * ```ts
14
+ * app.use(ServicePlugin, {
15
+ * setup(injector) {
16
+ * injector.provide(RESPONSE_INTERCEPTORS, new Set())
17
+ * }
18
+ * })
19
+ * ```
20
+ *
21
+ * Service 在首次 useInject() 时自动实例化。
22
+ * useProvide() 仅用于为某个子树覆写实现。
23
+ */
24
+ export declare const ServicePlugin: (app: any, options?: {
25
+ setup?: (injector: Injector) => void;
26
+ }) => void;
27
+ /** 从 Vue 作用域 Injector 中注入 Service 实例 */
28
+ export declare function useInject<T>(token: Token<T>): T;
29
+ /**
30
+ * 为组件子树提供/覆写 Service 绑定。
31
+ *
32
+ * 重载与 injector.provide() 保持一致,提供编译期类型安全:
33
+ * - 类 token 需要构造器(子类)实现
34
+ * - InjectionToken token 接受值类型
35
+ */
36
+ export declare function useProvide<T>(token: {
37
+ new (...args: any[]): T;
38
+ }, implementation: {
39
+ new (...args: any[]): T;
40
+ }): void;
41
+ export declare function useProvide<T>(token: InjectionToken<T>, implementation: T): void;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Vue 3 集成层,用于对接共享 DI 核心。
3
+ * 与 injector.ts 分离,使服务端不打包 Vue。
4
+ *
5
+ * SSR 行为:
6
+ * - ServicePlugin 通过 app.provide/use 创建每个应用的 Injector(无全局状态)
7
+ * - SSR 中每个 createApp() 调用获得独立的 Injector
8
+ * - useInject() 和 useProvide() 仅在 Vue setup() 内可用 —
9
+ * 在组件上下文外调用将抛出明确错误
10
+ *
11
+ * @module injector-vue
12
+ */
13
+ import { inject } from 'vue';
14
+ import { Injector } from './injector';
15
+ /** 从 Vue DI 树中访问 Injector 的注入 key */
16
+ const INJECTOR_KEY = Symbol('injector');
17
+ /**
18
+ * Vue 3 插件。创建作用域 Injector 并通过 provide/inject
19
+ * 使其对所有后代组件可用。
20
+ *
21
+ * 用法:
22
+ * ```ts
23
+ * createApp(App).use(ServicePlugin).mount('#app')
24
+ * ```
25
+ *
26
+ * 可通过第二个参数传入 setup 回调,在 Service 自动实例化前
27
+ * 为 Injector 提供基础设施 token(如拦截器集合):
28
+ * ```ts
29
+ * app.use(ServicePlugin, {
30
+ * setup(injector) {
31
+ * injector.provide(RESPONSE_INTERCEPTORS, new Set())
32
+ * }
33
+ * })
34
+ * ```
35
+ *
36
+ * Service 在首次 useInject() 时自动实例化。
37
+ * useProvide() 仅用于为某个子树覆写实现。
38
+ */
39
+ export const ServicePlugin = (app, options) => {
40
+ const injector = new Injector();
41
+ options?.setup?.(injector);
42
+ app.provide(INJECTOR_KEY, injector);
43
+ app.config.globalProperties.$inject = injector.inject.bind(injector);
44
+ app.onUnmount(() => {
45
+ injector.reset();
46
+ });
47
+ };
48
+ /** 从 Vue 作用域 Injector 中注入 Service 实例 */
49
+ export function useInject(token) {
50
+ const injector = inject(INJECTOR_KEY);
51
+ if (!injector)
52
+ throw new Error('[injector] ServicePlugin not installed — call app.use(ServicePlugin)');
53
+ return injector.inject(token);
54
+ }
55
+ export function useProvide(token, implementation) {
56
+ const injector = inject(INJECTOR_KEY);
57
+ if (!injector)
58
+ throw new Error('[injector] ServicePlugin not installed — call app.use(ServicePlugin)');
59
+ injector.provide(token, implementation);
60
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * 轻量 DI(依赖注入)容器。
3
+ *
4
+ * 架构决策:
5
+ * - 零外部依赖 — 可在 Node.js、浏览器、SSR 中运行
6
+ * - 无全局单例 — 每个应用或每个请求创建一个 Injector
7
+ * - 类 token 首次 inject() 时自动实例化 — 无需手动注册
8
+ * - provide() 使用函数重载:编译期防止非构造器实现传入类 token
9
+ * - InjectionToken<T> 为非类注入(值、缓存、配置)提供类型安全
10
+ * - onInit() 生命周期钩子在构造器注入的依赖就绪后调用
11
+ *
12
+ * SSR / Node.js 兼容:
13
+ * - 无 DOM、无 Node 内置模块导入
14
+ * - 全部同步逻辑 — 无异步初始化
15
+ * - InjectionToken 可完全序列化(toString()),支持 SSR 水合
16
+ * - 每个 Injector 实例完全隔离 — 无跨请求污染
17
+ */
18
+ export declare class InjectionToken<T> {
19
+ readonly description: string;
20
+ private defaultFactory?;
21
+ constructor(description: string, defaultFactory?: (() => T) | undefined);
22
+ hasDefault(): boolean;
23
+ createDefault(): T;
24
+ toString(): string;
25
+ }
26
+ export type Token<T> = {
27
+ new (...args: any[]): T;
28
+ } | InjectionToken<T>;
29
+ type RawToken = {
30
+ new (...args: any[]): any;
31
+ } | InjectionToken<any>;
32
+ export declare class Injector {
33
+ private bindings;
34
+ private instances;
35
+ /**
36
+ * 覆写 token 对应的实现。
37
+ *
38
+ * 两个重载在编译期保障类型安全:
39
+ * 1. 类 token → implementation 必须是构造器(子类),不能是普通对象
40
+ * 2. InjectionToken → implementation 必须是对应类型的值
41
+ *
42
+ * 必须在首次 inject() 之前调用才生效。
43
+ * 一旦 inject() 缓存了实例,provide() 对该 token 变成空操作。
44
+ *
45
+ * @returns this — 支持链式调用
46
+ */
47
+ provide<T>(token: {
48
+ new (...args: any[]): T;
49
+ }, implementation: {
50
+ new (...args: any[]): T;
51
+ }): this;
52
+ provide<T>(token: InjectionToken<T>, implementation: T): this;
53
+ /**
54
+ * 根据 token 解析服务或值。
55
+ *
56
+ * 按 token 类型不同行为:
57
+ * - 类 token 无 provide() → 自动实例化并缓存
58
+ * - 类 token 有 provide() → 实例化 provided 的实现并缓存(每个 Injector 单例)
59
+ * - InjectionToken 无 provide() → 使用默认值(如有),否则抛出 Error
60
+ * - InjectionToken 有 provide() → 返回 provided 的值
61
+ *
62
+ * 返回值在当前 Injector 内始终是单例,后续调用返回缓存实例。
63
+ */
64
+ inject<T>(token: Token<T>): T;
65
+ private _instantiate;
66
+ /** 检查 token 是否有绑定(不检查实例缓存) */
67
+ has(token: RawToken): boolean;
68
+ /** 清除所有绑定和缓存实例(会先调用所有缓存实例的 onDestroy) */
69
+ reset(): void;
70
+ /** 销毁指定 token 的缓存实例(调用 onDestroy 后移出缓存,下次 inject 重新创建) */
71
+ destroy<T>(token: Token<T>): void;
72
+ }
73
+ /**
74
+ * 所有可注入 Service 的基类。
75
+ *
76
+ * - 构造函数唯一参数为 Injector
77
+ * - 子类通过箭头函数字段声明依赖:`svc = this.$inject(Dep)`
78
+ * - onInit() 由 Injector 在实例构造并缓存后调用(而非构造函数内)
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * class UserService extends BaseService {
83
+ * private api = this.$inject(ApiService)
84
+ * private db = this.$inject(DB_POOL)
85
+ * state = reactive({ ... })
86
+ * fetchUsers = async () => { ... }
87
+ * }
88
+ * ```
89
+ */
90
+ export declare abstract class BaseService {
91
+ protected $injector: Injector;
92
+ constructor(injector: Injector);
93
+ /** 从父级 Injector 解析依赖 */
94
+ protected $inject<T>(token: Token<T>): T;
95
+ /**
96
+ * 生命周期钩子,在构造器注入的依赖全部就绪后调用。
97
+ * 不要在构造函数中做初始化逻辑,应使用 onInit()。
98
+ */
99
+ onInit?(): void;
100
+ onDestroy?(): void;
101
+ }
102
+ export {};
@@ -0,0 +1,137 @@
1
+ /**
2
+ * 轻量 DI(依赖注入)容器。
3
+ *
4
+ * 架构决策:
5
+ * - 零外部依赖 — 可在 Node.js、浏览器、SSR 中运行
6
+ * - 无全局单例 — 每个应用或每个请求创建一个 Injector
7
+ * - 类 token 首次 inject() 时自动实例化 — 无需手动注册
8
+ * - provide() 使用函数重载:编译期防止非构造器实现传入类 token
9
+ * - InjectionToken<T> 为非类注入(值、缓存、配置)提供类型安全
10
+ * - onInit() 生命周期钩子在构造器注入的依赖就绪后调用
11
+ *
12
+ * SSR / Node.js 兼容:
13
+ * - 无 DOM、无 Node 内置模块导入
14
+ * - 全部同步逻辑 — 无异步初始化
15
+ * - InjectionToken 可完全序列化(toString()),支持 SSR 水合
16
+ * - 每个 Injector 实例完全隔离 — 无跨请求污染
17
+ */
18
+ export class InjectionToken {
19
+ constructor(description, defaultFactory) {
20
+ this.description = description;
21
+ this.defaultFactory = defaultFactory;
22
+ }
23
+ hasDefault() {
24
+ return this.defaultFactory !== undefined;
25
+ }
26
+ createDefault() {
27
+ return this.defaultFactory();
28
+ }
29
+ toString() {
30
+ return `InjectionToken(${this.description})`;
31
+ }
32
+ }
33
+ export class Injector {
34
+ constructor() {
35
+ this.bindings = new Map();
36
+ this.instances = new Map();
37
+ }
38
+ provide(token, implementation) {
39
+ this.bindings.set(token, implementation);
40
+ return this;
41
+ }
42
+ /**
43
+ * 根据 token 解析服务或值。
44
+ *
45
+ * 按 token 类型不同行为:
46
+ * - 类 token 无 provide() → 自动实例化并缓存
47
+ * - 类 token 有 provide() → 实例化 provided 的实现并缓存(每个 Injector 单例)
48
+ * - InjectionToken 无 provide() → 使用默认值(如有),否则抛出 Error
49
+ * - InjectionToken 有 provide() → 返回 provided 的值
50
+ *
51
+ * 返回值在当前 Injector 内始终是单例,后续调用返回缓存实例。
52
+ */
53
+ inject(token) {
54
+ const existing = this.instances.get(token);
55
+ if (existing !== undefined)
56
+ return existing;
57
+ const binding = this.bindings.get(token);
58
+ if (binding === undefined) {
59
+ if (typeof token === 'function') {
60
+ return this._instantiate(token, token);
61
+ }
62
+ // InjectionToken — 有默认值则使用,否则抛异常
63
+ if (token.hasDefault()) {
64
+ const val = token.createDefault();
65
+ this.instances.set(token, val);
66
+ return val;
67
+ }
68
+ throw new Error(`Service not registered: ${token}`);
69
+ }
70
+ if (typeof binding !== 'function' || !binding.prototype) {
71
+ this.instances.set(token, binding);
72
+ return binding;
73
+ }
74
+ return this._instantiate(token, binding);
75
+ }
76
+ _instantiate(token, Class) {
77
+ const instance = new Class(this);
78
+ this.instances.set(token, instance);
79
+ if (typeof instance.onInit === 'function') {
80
+ ;
81
+ instance.onInit();
82
+ }
83
+ return instance;
84
+ }
85
+ /** 检查 token 是否有绑定(不检查实例缓存) */
86
+ has(token) {
87
+ return this.bindings.has(token);
88
+ }
89
+ /** 清除所有绑定和缓存实例(会先调用所有缓存实例的 onDestroy) */
90
+ reset() {
91
+ for (const [, instance] of this.instances) {
92
+ if (typeof instance === 'object' && instance !== null && typeof instance.onDestroy === 'function') {
93
+ ;
94
+ instance.onDestroy();
95
+ }
96
+ }
97
+ this.bindings.clear();
98
+ this.instances.clear();
99
+ }
100
+ /** 销毁指定 token 的缓存实例(调用 onDestroy 后移出缓存,下次 inject 重新创建) */
101
+ destroy(token) {
102
+ const instance = this.instances.get(token);
103
+ if (instance !== undefined && typeof instance === 'object' && instance !== null && typeof instance.onDestroy === 'function') {
104
+ ;
105
+ instance.onDestroy();
106
+ }
107
+ this.instances.delete(token);
108
+ }
109
+ }
110
+ /**
111
+ * 所有可注入 Service 的基类。
112
+ *
113
+ * - 构造函数唯一参数为 Injector
114
+ * - 子类通过箭头函数字段声明依赖:`svc = this.$inject(Dep)`
115
+ * - onInit() 由 Injector 在实例构造并缓存后调用(而非构造函数内)
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * class UserService extends BaseService {
120
+ * private api = this.$inject(ApiService)
121
+ * private db = this.$inject(DB_POOL)
122
+ * state = reactive({ ... })
123
+ * fetchUsers = async () => { ... }
124
+ * }
125
+ * ```
126
+ */
127
+ export class BaseService {
128
+ constructor(injector) {
129
+ if (!injector)
130
+ throw new Error('BaseService requires an Injector instance');
131
+ this.$injector = injector;
132
+ }
133
+ /** 从父级 Injector 解析依赖 */
134
+ $inject(token) {
135
+ return this.$injector.inject(token);
136
+ }
137
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@amriogit/injector",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "轻量 DI 容器 — Injector, BaseService, InjectionToken。零外部依赖,支持 SSR,Vue 集成。",
6
+ "keywords": ["di", "dependency-injection", "injector", "typescript", "vue", "ssr"],
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/amriogit/injector.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/amriogit/injector/issues"
14
+ },
15
+ "homepage": "https://github.com/amriogit/injector#readme",
16
+ "main": "./dist/injector.js",
17
+ "types": "./dist/injector.d.ts",
18
+ "exports": {
19
+ ".": "./dist/injector.js",
20
+ "./vue": "./dist/injector-vue.js"
21
+ },
22
+ "files": ["dist"],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "peerDependencies": {
27
+ "vue": "^3.4.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "vue": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "prepublishOnly": "npm test && npm run build"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.8.3",
42
+ "vitest": "^3.2.4",
43
+ "vue": "^3.5.34"
44
+ },
45
+ "license": "MIT"
46
+ }