smartdi 0.4.0 → 0.4.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 +11 -0
- package/index.js +90 -42
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -232,6 +232,17 @@ Decorate a method as a lifecycle hook, which will be invoked when `SmartDI.initi
|
|
|
232
232
|
Decorate a method as a lifecycle hook, which will be invoked when `SmartDI.uninitialize()` is called.
|
|
233
233
|
将方法装饰为生命周期钩子,在调用 `SmartDI.uninitialize()` 时执行。
|
|
234
234
|
|
|
235
|
+
## Inheritance | 继承
|
|
236
|
+
|
|
237
|
+
Injected properties and lifecycle hooks declared on base classes apply to subclass instances. Decorators write only the metadata of the class they decorate; the prototype chain is merged when an instance is created.
|
|
238
|
+
|
|
239
|
+
基类声明的注入属性与生命周期钩子会对子类实例生效。装饰器只写入所在类自己的元数据,实例创建时再沿原型链合并。
|
|
240
|
+
|
|
241
|
+
- Injected properties are merged base-first, so a subclass property with the same name overrides the base one.
|
|
242
|
+
注入属性按基类在前合并,子类同名字段覆盖基类字段。
|
|
243
|
+
- `@PostConstruct` / `@PreDestroy` methods run once per instance, base-class hooks first.
|
|
244
|
+
每个实例的 `@PostConstruct` / `@PreDestroy` 各执行一次,基类钩子在前。
|
|
245
|
+
|
|
235
246
|
## Limitations
|
|
236
247
|
|
|
237
248
|
Injection is processed after construction, so do not access injectable values in the constructor.
|
package/index.js
CHANGED
|
@@ -10,9 +10,73 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
var _a, _b;
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.
|
|
13
|
+
exports.SmartDI = void 0;
|
|
14
|
+
exports.Injectable = Injectable;
|
|
15
|
+
exports.Inject = Inject;
|
|
16
|
+
exports.InjectArray = InjectArray;
|
|
17
|
+
exports.PostConstruct = PostConstruct;
|
|
18
|
+
exports.PreDestroy = PreDestroy;
|
|
14
19
|
const $registry = Symbol('registry');
|
|
15
20
|
const $singletons = Symbol('singletons');
|
|
21
|
+
const METADATA_INJECTS = 'smartdi:injects';
|
|
22
|
+
const METADATA_POST_CONSTRUCT = 'smartdi:post-construct';
|
|
23
|
+
const METADATA_PRE_DESTROY = 'smartdi:pre-destroy';
|
|
24
|
+
function prototypeChain(instance) {
|
|
25
|
+
const chain = [];
|
|
26
|
+
let proto = Object.getPrototypeOf(instance);
|
|
27
|
+
while (proto && proto !== Object.prototype) {
|
|
28
|
+
chain.push(proto);
|
|
29
|
+
proto = Object.getPrototypeOf(proto);
|
|
30
|
+
}
|
|
31
|
+
return chain.reverse();
|
|
32
|
+
}
|
|
33
|
+
function isSubclass(sub, sup) {
|
|
34
|
+
let proto = Object.getPrototypeOf(sub);
|
|
35
|
+
while (proto) {
|
|
36
|
+
if (proto === sup)
|
|
37
|
+
return true;
|
|
38
|
+
proto = Object.getPrototypeOf(proto);
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const injectsCache = new WeakMap();
|
|
43
|
+
function collectInjects(instance) {
|
|
44
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
45
|
+
let merged = injectsCache.get(prototype);
|
|
46
|
+
if (merged)
|
|
47
|
+
return merged;
|
|
48
|
+
merged = {};
|
|
49
|
+
for (const proto of prototypeChain(instance))
|
|
50
|
+
Object.assign(merged, Reflect.getOwnMetadata(METADATA_INJECTS, proto));
|
|
51
|
+
injectsCache.set(prototype, merged);
|
|
52
|
+
return merged;
|
|
53
|
+
}
|
|
54
|
+
function collectLifecycle(instance, key) {
|
|
55
|
+
var _c;
|
|
56
|
+
const methods = [];
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
for (const proto of prototypeChain(instance)) {
|
|
59
|
+
for (const method of (_c = Reflect.getOwnMetadata(key, proto)) !== null && _c !== void 0 ? _c : []) {
|
|
60
|
+
if (seen.has(method))
|
|
61
|
+
continue;
|
|
62
|
+
seen.add(method);
|
|
63
|
+
methods.push(method);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return methods;
|
|
67
|
+
}
|
|
68
|
+
function defineInject(target, prop, item) {
|
|
69
|
+
var _c;
|
|
70
|
+
const injects = (_c = Reflect.getOwnMetadata(METADATA_INJECTS, target)) !== null && _c !== void 0 ? _c : {};
|
|
71
|
+
injects[prop] = item;
|
|
72
|
+
Reflect.defineMetadata(METADATA_INJECTS, injects, target);
|
|
73
|
+
}
|
|
74
|
+
function appendLifecycle(target, key, method) {
|
|
75
|
+
var _c;
|
|
76
|
+
const methods = (_c = Reflect.getOwnMetadata(key, target)) !== null && _c !== void 0 ? _c : [];
|
|
77
|
+
methods.push(method);
|
|
78
|
+
Reflect.defineMetadata(key, methods, target);
|
|
79
|
+
}
|
|
16
80
|
class SmartDI {
|
|
17
81
|
static register(clazz, options) {
|
|
18
82
|
const name = (options === null || options === void 0 ? void 0 : options.name) || clazz.name;
|
|
@@ -44,8 +108,8 @@ class SmartDI {
|
|
|
44
108
|
instances.push(SmartDI.obtainInstance(item, new Set()));
|
|
45
109
|
const promises = [];
|
|
46
110
|
for (const instance of instances) {
|
|
47
|
-
|
|
48
|
-
if (!
|
|
111
|
+
const methods = collectLifecycle(instance, METADATA_POST_CONSTRUCT);
|
|
112
|
+
if (!methods.length)
|
|
49
113
|
continue;
|
|
50
114
|
for (const method of methods) {
|
|
51
115
|
const result = instance[method]();
|
|
@@ -67,8 +131,8 @@ class SmartDI {
|
|
|
67
131
|
if (!instance)
|
|
68
132
|
continue;
|
|
69
133
|
item.instance = undefined;
|
|
70
|
-
|
|
71
|
-
if (!
|
|
134
|
+
const methods = collectLifecycle(instance, METADATA_PRE_DESTROY);
|
|
135
|
+
if (!methods.length)
|
|
72
136
|
continue;
|
|
73
137
|
for (const method of methods) {
|
|
74
138
|
const result = instance[method]();
|
|
@@ -115,14 +179,15 @@ class SmartDI {
|
|
|
115
179
|
const instance = new item.class(...args);
|
|
116
180
|
if (item.singleton)
|
|
117
181
|
item.instance = instance;
|
|
118
|
-
const injects =
|
|
119
|
-
|
|
182
|
+
const injects = collectInjects(instance);
|
|
183
|
+
const injectKeys = Reflect.ownKeys(injects);
|
|
184
|
+
if (!injectKeys.length)
|
|
120
185
|
return instance;
|
|
121
186
|
if (!item.singleton && creatings.has(item.name))
|
|
122
187
|
throw new Error('Circular dependency detected!');
|
|
123
188
|
creatings.add(item.name);
|
|
124
189
|
try {
|
|
125
|
-
for (const prop of
|
|
190
|
+
for (const prop of injectKeys)
|
|
126
191
|
instance[prop] = injects[prop].array
|
|
127
192
|
? SmartDI.internalGetArray(injects[prop].clue, creatings, ...injects[prop].args)
|
|
128
193
|
: SmartDI.internalGet(injects[prop].clue, creatings, ...injects[prop].args);
|
|
@@ -135,28 +200,24 @@ class SmartDI {
|
|
|
135
200
|
return instance;
|
|
136
201
|
}
|
|
137
202
|
static resolveByConstructor(constructor) {
|
|
138
|
-
|
|
139
|
-
function isSubclass(sub, sup) {
|
|
140
|
-
let proto = Object.getPrototypeOf(sub);
|
|
141
|
-
while (proto) {
|
|
142
|
-
if (proto === sup)
|
|
143
|
-
return true;
|
|
144
|
-
proto = Object.getPrototypeOf(proto);
|
|
145
|
-
}
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
203
|
+
const matched = [];
|
|
148
204
|
for (const item of Object.values(SmartDI[$registry])) {
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
matched.push(item);
|
|
205
|
+
if (isSubclass(item.class, constructor))
|
|
206
|
+
matched.push(item);
|
|
152
207
|
}
|
|
153
208
|
return matched;
|
|
154
209
|
}
|
|
155
210
|
static resolveOneByConstructor(constructor) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
211
|
+
const matched = [];
|
|
212
|
+
for (const item of Object.values(SmartDI[$registry])) {
|
|
213
|
+
if (item.class === constructor)
|
|
214
|
+
return item;
|
|
215
|
+
if (isSubclass(item.class, constructor))
|
|
216
|
+
matched.push(item);
|
|
217
|
+
}
|
|
218
|
+
if (matched.length > 1)
|
|
219
|
+
throw new Error(`Multiple injectable classes found for '${constructor.name}': ${matched.map(item => item.name).join(', ')}`);
|
|
220
|
+
return matched[0];
|
|
160
221
|
}
|
|
161
222
|
}
|
|
162
223
|
exports.SmartDI = SmartDI;
|
|
@@ -168,36 +229,23 @@ function Injectable(options) {
|
|
|
168
229
|
SmartDI.register(target, options);
|
|
169
230
|
};
|
|
170
231
|
}
|
|
171
|
-
exports.Injectable = Injectable;
|
|
172
232
|
function Inject(clue, ...args) {
|
|
173
233
|
return function (target, prop) {
|
|
174
|
-
|
|
175
|
-
injects[prop] = { array: false, clue, args };
|
|
176
|
-
Reflect.defineMetadata('smartdi:injects', injects, target);
|
|
234
|
+
defineInject(target, prop, { array: false, clue, args });
|
|
177
235
|
};
|
|
178
236
|
}
|
|
179
|
-
exports.Inject = Inject;
|
|
180
237
|
function InjectArray(clue, ...args) {
|
|
181
238
|
return function (target, prop) {
|
|
182
|
-
|
|
183
|
-
injects[prop] = { array: true, clue, args };
|
|
184
|
-
Reflect.defineMetadata('smartdi:injects', injects, target);
|
|
239
|
+
defineInject(target, prop, { array: true, clue, args });
|
|
185
240
|
};
|
|
186
241
|
}
|
|
187
|
-
exports.InjectArray = InjectArray;
|
|
188
242
|
function PostConstruct() {
|
|
189
243
|
return function (target, key, _descriptor) {
|
|
190
|
-
|
|
191
|
-
methods.push(key);
|
|
192
|
-
Reflect.defineMetadata('smartdi:post-construct', methods, target);
|
|
244
|
+
appendLifecycle(target, METADATA_POST_CONSTRUCT, key);
|
|
193
245
|
};
|
|
194
246
|
}
|
|
195
|
-
exports.PostConstruct = PostConstruct;
|
|
196
247
|
function PreDestroy() {
|
|
197
248
|
return function (target, key, _descriptor) {
|
|
198
|
-
|
|
199
|
-
methods.push(key);
|
|
200
|
-
Reflect.defineMetadata('smartdi:pre-destroy', methods, target);
|
|
249
|
+
appendLifecycle(target, METADATA_PRE_DESTROY, key);
|
|
201
250
|
};
|
|
202
251
|
}
|
|
203
|
-
exports.PreDestroy = PreDestroy;
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smartdi",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A simple dependency injector based on decorators.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
7
|
+
"build": "tsc -p tsconfig.build.json",
|
|
8
|
+
"test": "jest"
|
|
8
9
|
},
|
|
9
10
|
"keywords": [
|
|
10
11
|
"DI",
|
|
@@ -27,8 +28,11 @@
|
|
|
27
28
|
},
|
|
28
29
|
"homepage": "https://github.com/iwares/smartdi#readme",
|
|
29
30
|
"devDependencies": {
|
|
31
|
+
"@types/jest": "^30.0.0",
|
|
30
32
|
"@types/node": "^22.13.10",
|
|
33
|
+
"jest": "^30.5.2",
|
|
31
34
|
"reflect-metadata": "^0.2.2",
|
|
32
|
-
"
|
|
35
|
+
"ts-jest": "^29.4.15",
|
|
36
|
+
"typescript": "^6.0.3"
|
|
33
37
|
}
|
|
34
38
|
}
|