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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/index.js +90 -42
  3. 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.PreDestroy = exports.PostConstruct = exports.InjectArray = exports.Inject = exports.Injectable = exports.SmartDI = void 0;
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
- let methods = Reflect.getMetadata('smartdi:post-construct', instance);
48
- if (!(methods === null || methods === void 0 ? void 0 : methods.length))
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
- let methods = Reflect.getMetadata('smartdi:pre-destroy', instance);
71
- if (!(methods === null || methods === void 0 ? void 0 : methods.length))
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 = Reflect.getMetadata('smartdi:injects', instance);
119
- if (!injects)
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 Reflect.ownKeys(injects))
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
- let matched = [];
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 (!isSubclass(item.class, constructor))
150
- continue;
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 items = SmartDI.resolveByConstructor(constructor);
157
- if (items.length > 1)
158
- throw new Error(`Multiple injectable classes found for '${constructor.name}': ${items.map(item => item.name).join(', ')}`);
159
- return items[0];
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
- let injects = Reflect.getMetadata('smartdi:injects', target) || {};
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
- let injects = Reflect.getMetadata('smartdi:injects', target) || {};
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
- let methods = Reflect.getMetadata('smartdi:post-construct', target) || [];
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
- let methods = Reflect.getMetadata('smartdi:pre-destroy', target) || [];
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.0",
3
+ "version": "0.4.1",
4
4
  "description": "A simple dependency injector based on decorators.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
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
- "typescript": "^5.1.6"
35
+ "ts-jest": "^29.4.15",
36
+ "typescript": "^6.0.3"
33
37
  }
34
38
  }