@praxisjs/di 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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # @praxisjs/di
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - aaf7dab: Initial beta release
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [aaf7dab]
12
+ - @praxisjs/core@0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Mateus Martins
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.
@@ -0,0 +1,29 @@
1
+ export type Scope = "singleton" | "transient";
2
+ export interface InjectableOptions {
3
+ scope?: Scope;
4
+ }
5
+ export interface ServiceDescriptor {
6
+ target: Constructor;
7
+ scope: Scope;
8
+ instance?: unknown;
9
+ }
10
+ export type Constructor<T = unknown> = new (...args: unknown[]) => T;
11
+ export declare class Token<_T> {
12
+ readonly description: string;
13
+ constructor(description: string);
14
+ toString(): string;
15
+ }
16
+ export declare function token<T>(description: string): Token<T>;
17
+ export declare class Container {
18
+ private readonly services;
19
+ private readonly parent?;
20
+ constructor(parent?: Container);
21
+ register<T>(target: Constructor<T>, options?: InjectableOptions): this;
22
+ registerValue<T>(token: Token<T>, value: T): this;
23
+ registerFactory<T>(token: Token<T>, factory: (container: Container) => T): this;
24
+ resolve<T>(target: Constructor<T> | Token<T>): T;
25
+ private instantiate;
26
+ createChild(): Container;
27
+ }
28
+ export declare const container: Container;
29
+ //# sourceMappingURL=container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AAErE,qBAAa,KAAK,CAAC,EAAE;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBACjB,WAAW,EAAE,MAAM;IAG/B,QAAQ;CAGT;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAEtD;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAGrB;IACJ,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAY;gBAExB,MAAM,CAAC,EAAE,SAAS;IAI9B,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,iBAAsB,GAAG,IAAI;IAQ1E,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAKjD,eAAe,CAAC,CAAC,EACf,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EACf,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,CAAC,GACnC,IAAI;IAKP,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IA0BhD,OAAO,CAAC,WAAW;IAoBnB,WAAW,IAAI,SAAS;CAGzB;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
@@ -0,0 +1,69 @@
1
+ export class Token {
2
+ constructor(description) {
3
+ this.description = description;
4
+ }
5
+ toString() {
6
+ return `Token(${this.description})`;
7
+ }
8
+ }
9
+ export function token(description) {
10
+ return new Token(description);
11
+ }
12
+ export class Container {
13
+ constructor(parent) {
14
+ this.services = new Map();
15
+ this.parent = parent;
16
+ }
17
+ register(target, options = {}) {
18
+ this.services.set(target, {
19
+ target,
20
+ scope: options.scope ?? "singleton",
21
+ });
22
+ return this;
23
+ }
24
+ registerValue(token, value) {
25
+ this.services.set(token, value);
26
+ return this;
27
+ }
28
+ registerFactory(token, factory) {
29
+ this.services.set(token, factory(this));
30
+ return this;
31
+ }
32
+ resolve(target) {
33
+ const entry = this.services.get(target);
34
+ if (target instanceof Token) {
35
+ if (entry !== undefined)
36
+ return entry;
37
+ if (this.parent)
38
+ return this.parent.resolve(target);
39
+ throw new Error(`[DI] Token not registered: ${target.toString()}`);
40
+ }
41
+ if (!entry) {
42
+ if (this.parent)
43
+ return this.parent.resolve(target);
44
+ throw new Error(`[DI] Service not registered: ${target.name}`);
45
+ }
46
+ const descriptor = entry;
47
+ if (descriptor.scope === "singleton") {
48
+ descriptor.instance ??= this.instantiate(descriptor.target);
49
+ return descriptor.instance;
50
+ }
51
+ return this.instantiate(descriptor.target);
52
+ }
53
+ instantiate(target) {
54
+ const deps = Reflect.getMetadata("di:inject", target) ?? [];
55
+ const resolvedDeps = deps.map((dep) => this.resolve(dep));
56
+ const instance = new target(...resolvedDeps);
57
+ const rawPropInjections = Reflect.getMetadata("di:props", target.prototype);
58
+ const propInjections = rawPropInjections ?? new Map();
59
+ for (const [prop, dep] of propInjections) {
60
+ instance[prop] = this.resolve(dep);
61
+ }
62
+ return instance;
63
+ }
64
+ createChild() {
65
+ return new Container(this);
66
+ }
67
+ }
68
+ export const container = new Container();
69
+ //# sourceMappingURL=container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAcA,MAAM,OAAO,KAAK;IAEhB,YAAY,WAAmB;QAC7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IACD,QAAQ;QACN,OAAO,SAAS,IAAI,CAAC,WAAW,GAAG,CAAC;IACtC,CAAC;CACF;AAED,MAAM,UAAU,KAAK,CAAI,WAAmB;IAC1C,OAAO,IAAI,KAAK,CAAI,WAAW,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,OAAO,SAAS;IAOpB,YAAY,MAAkB;QANb,aAAQ,GAAG,IAAI,GAAG,EAGhC,CAAC;QAIF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,QAAQ,CAAI,MAAsB,EAAE,UAA6B,EAAE;QACjE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE;YACxB,MAAM;YACN,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,WAAW;SACR,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,aAAa,CAAI,KAAe,EAAE,KAAQ;QACxC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,eAAe,CACb,KAAe,EACf,OAAoC;QAEpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAI,MAAiC;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAsC,CAAC,CAAC;QAExE,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;YAC5B,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,KAAU,CAAC;YAC3C,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,gCAAiC,MAAsB,CAAC,IAAI,EAAE,CAC/D,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,KAA0B,CAAC;QAE9C,IAAI,UAAU,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YACrC,UAAU,CAAC,QAAQ,KAAK,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YAC5D,OAAO,UAAU,CAAC,QAAa,CAAC;QAClC,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAM,CAAC;IAClD,CAAC;IAEO,WAAW,CAAI,MAAsB;QAC3C,MAAM,IAAI,GACP,OAAO,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAqD,IAAI,EAAE,CAAC;QAEtG,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAkB,CAAC,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC;QAE7C,MAAM,iBAAiB,GAAY,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,SAAmB,CAAC,CAAC;QAC/F,MAAM,cAAc,GACjB,iBAA2E,IAAI,IAAI,GAAG,EAAwC,CAAC;QAElI,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,cAAc,EAAE,CAAC;YACxC,QAAoC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CACxD,GAAkB,CACnB,CAAC;QACJ,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,WAAW;QACT,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type Container, Token, type Constructor, type InjectableOptions } from "./container";
2
+ export declare function Injectable(options?: InjectableOptions): <T extends Constructor>(target: T) => T;
3
+ export declare function Inject<T>(dep: Constructor<T> | Token<T>): (target: object, propertyKey: string) => void;
4
+ export declare function InjectContainer(): (target: object, propertyKey: string) => void;
5
+ export declare function useService<T>(dep: Constructor<T> | Token<T>): T;
6
+ export declare function createScope(configure?: (c: Container) => void): Container;
7
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAG,MAAM,aAAa,CAAC;AAE1G,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,IACvC,CAAC,SAAS,WAAW,EAAE,QAAQ,CAAC,KAAG,CAAC,CAItD;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAGrC,QAAQ,MAAM,EAAE,aAAa,MAAM,KAAG,IAAI,CAiC5D;AAED,wBAAgB,eAAe,KACZ,QAAQ,MAAM,EAAE,aAAa,MAAM,KAAG,IAAI,CAS5D;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAE/D;AAED,wBAAgB,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,GAAG,SAAS,CAIzE"}
@@ -0,0 +1,56 @@
1
+ import { container, Token } from "./container";
2
+ export function Injectable(options = {}) {
3
+ return function (target) {
4
+ container.register(target, options);
5
+ return target;
6
+ };
7
+ }
8
+ export function Inject(dep) {
9
+ const cache = new WeakMap();
10
+ return function (target, propertyKey) {
11
+ Object.defineProperty(target, propertyKey, {
12
+ get() {
13
+ if (!cache.has(this)) {
14
+ let resolved;
15
+ try {
16
+ resolved = container.resolve(dep);
17
+ }
18
+ catch (err) {
19
+ throw new Error(`[Inject] Failed to resolve "${dep instanceof Token
20
+ ? dep.toString()
21
+ : dep.name}" in "${this.constructor.name}.${propertyKey}": ${err.message}`);
22
+ }
23
+ cache.set(this, resolved);
24
+ }
25
+ return cache.get(this);
26
+ },
27
+ set(_value) {
28
+ if (process.env.NODE_ENV !== "production") {
29
+ console.warn(`[Inject] "${propertyKey}" is managed by the DI container and cannot be assigned directly.`);
30
+ }
31
+ },
32
+ enumerable: true,
33
+ configurable: true,
34
+ });
35
+ };
36
+ }
37
+ export function InjectContainer() {
38
+ return function (target, propertyKey) {
39
+ Object.defineProperty(target, propertyKey, {
40
+ get() {
41
+ return container;
42
+ },
43
+ enumerable: true,
44
+ configurable: true,
45
+ });
46
+ };
47
+ }
48
+ export function useService(dep) {
49
+ return container.resolve(dep);
50
+ }
51
+ export function createScope(configure) {
52
+ const child = container.createChild();
53
+ configure?.(child);
54
+ return child;
55
+ }
56
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,KAAK,EAA6C,MAAM,aAAa,CAAC;AAE1G,MAAM,UAAU,UAAU,CAAC,UAA6B,EAAE;IACxD,OAAO,UAAiC,MAAS;QAC/C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CAAI,GAA8B;IACtD,MAAM,KAAK,GAAG,IAAI,OAAO,EAAa,CAAC;IAEvC,OAAO,UAAU,MAAc,EAAE,WAAmB;QAClD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE;YACzC,GAAG;gBACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrB,IAAI,QAAW,CAAC;oBAChB,IAAI,CAAC;wBACH,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,GAAqB,CAAC,CAAC;oBACtD,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,IAAI,KAAK,CACb,+BACE,GAAG,YAAY,KAAK;4BAClB,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;4BAChB,CAAC,CAAE,GAAmB,CAAC,IAC3B,SAAU,IAA0C,CAAC,WAAW,CAAC,IAAI,IAAI,WAAW,MAAO,GAAa,CAAC,OAAO,EAAE,CACnH,CAAC;oBACJ,CAAC;oBACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAC5B,CAAC;gBACD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAM,CAAC;YAC9B,CAAC;YAED,GAAG,CAAC,MAAe;gBACjB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;oBAC1C,OAAO,CAAC,IAAI,CACV,aAAa,WAAW,mEAAmE,CAC5F,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,MAAc,EAAE,WAAmB;QAClD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE;YACzC,GAAG;gBACD,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAI,GAA8B;IAC1D,OAAO,SAAS,CAAC,OAAO,CAAC,GAAqB,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAkC;IAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IACtC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ import "reflect-metadata";
2
+ export { Injectable, Inject, InjectContainer, useService, createScope, } from "./decorators";
3
+ export { Container, container, Token, token } from "./container";
4
+ export type { Scope, InjectableOptions, Constructor, ServiceDescriptor, } from "./container";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,UAAU,EACV,MAAM,EACN,eAAe,EACf,UAAU,EACV,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EACV,KAAK,EACL,iBAAiB,EACjB,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import "reflect-metadata";
2
+ export { Injectable, Inject, InjectContainer, useService, createScope, } from "./decorators";
3
+ export { Container, container, Token, token } from "./container";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,UAAU,EACV,MAAM,EACN,eAAe,EACf,UAAU,EACV,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC"}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@praxisjs/di",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "dependencies": {
14
+ "reflect-metadata": "^0.2.2",
15
+ "@praxisjs/core": "0.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^25.3.0",
19
+ "typescript": "^5.9.3"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch"
24
+ }
25
+ }
@@ -0,0 +1,112 @@
1
+ export type Scope = "singleton" | "transient";
2
+
3
+ export interface InjectableOptions {
4
+ scope?: Scope;
5
+ }
6
+
7
+ export interface ServiceDescriptor {
8
+ target: Constructor;
9
+ scope: Scope;
10
+ instance?: unknown;
11
+ }
12
+
13
+ export type Constructor<T = unknown> = new (...args: unknown[]) => T;
14
+
15
+ export class Token<_T> {
16
+ readonly description: string;
17
+ constructor(description: string) {
18
+ this.description = description;
19
+ }
20
+ toString() {
21
+ return `Token(${this.description})`;
22
+ }
23
+ }
24
+
25
+ export function token<T>(description: string): Token<T> {
26
+ return new Token<T>(description);
27
+ }
28
+
29
+ export class Container {
30
+ private readonly services = new Map<
31
+ Constructor | Token<unknown>,
32
+ unknown
33
+ >();
34
+ private readonly parent?: Container;
35
+
36
+ constructor(parent?: Container) {
37
+ this.parent = parent;
38
+ }
39
+
40
+ register<T>(target: Constructor<T>, options: InjectableOptions = {}): this {
41
+ this.services.set(target, {
42
+ target,
43
+ scope: options.scope ?? "singleton",
44
+ } satisfies ServiceDescriptor);
45
+ return this;
46
+ }
47
+
48
+ registerValue<T>(token: Token<T>, value: T): this {
49
+ this.services.set(token, value);
50
+ return this;
51
+ }
52
+
53
+ registerFactory<T>(
54
+ token: Token<T>,
55
+ factory: (container: Container) => T,
56
+ ): this {
57
+ this.services.set(token, factory(this));
58
+ return this;
59
+ }
60
+
61
+ resolve<T>(target: Constructor<T> | Token<T>): T {
62
+ const entry = this.services.get(target as Constructor | Token<unknown>);
63
+
64
+ if (target instanceof Token) {
65
+ if (entry !== undefined) return entry as T;
66
+ if (this.parent) return this.parent.resolve(target);
67
+ throw new Error(`[DI] Token not registered: ${target.toString()}`);
68
+ }
69
+
70
+ if (!entry) {
71
+ if (this.parent) return this.parent.resolve(target);
72
+ throw new Error(
73
+ `[DI] Service not registered: ${(target as Constructor).name}`,
74
+ );
75
+ }
76
+
77
+ const descriptor = entry as ServiceDescriptor;
78
+
79
+ if (descriptor.scope === "singleton") {
80
+ descriptor.instance ??= this.instantiate(descriptor.target);
81
+ return descriptor.instance as T;
82
+ }
83
+
84
+ return this.instantiate(descriptor.target) as T;
85
+ }
86
+
87
+ private instantiate<T>(target: Constructor<T>): T {
88
+ const deps: Array<Constructor | Token<unknown>> =
89
+ (Reflect.getMetadata("di:inject", target) as Array<Constructor | Token<unknown>> | undefined) ?? [];
90
+
91
+ const resolvedDeps = deps.map((dep) => this.resolve(dep as Constructor));
92
+ const instance = new target(...resolvedDeps);
93
+
94
+ const rawPropInjections: unknown = Reflect.getMetadata("di:props", target.prototype as object);
95
+ const propInjections =
96
+ (rawPropInjections as Map<string, Constructor | Token<unknown>> | undefined) ?? new Map<string, Constructor | Token<unknown>>();
97
+
98
+ for (const [prop, dep] of propInjections) {
99
+ (instance as Record<string, unknown>)[prop] = this.resolve(
100
+ dep as Constructor,
101
+ );
102
+ }
103
+
104
+ return instance;
105
+ }
106
+
107
+ createChild(): Container {
108
+ return new Container(this);
109
+ }
110
+ }
111
+
112
+ export const container = new Container();
@@ -0,0 +1,68 @@
1
+ import { container, type Container, Token, type Constructor, type InjectableOptions } from "./container";
2
+
3
+ export function Injectable(options: InjectableOptions = {}) {
4
+ return function <T extends Constructor>(target: T): T {
5
+ container.register(target, options);
6
+ return target;
7
+ };
8
+ }
9
+
10
+ export function Inject<T>(dep: Constructor<T> | Token<T>) {
11
+ const cache = new WeakMap<object, T>();
12
+
13
+ return function (target: object, propertyKey: string): void {
14
+ Object.defineProperty(target, propertyKey, {
15
+ get(this: object): T {
16
+ if (!cache.has(this)) {
17
+ let resolved: T;
18
+ try {
19
+ resolved = container.resolve(dep as Constructor<T>);
20
+ } catch (err) {
21
+ throw new Error(
22
+ `[Inject] Failed to resolve "${
23
+ dep instanceof Token
24
+ ? dep.toString()
25
+ : (dep as Constructor).name
26
+ }" in "${(this as { constructor: { name: string } }).constructor.name}.${propertyKey}": ${(err as Error).message}`,
27
+ );
28
+ }
29
+ cache.set(this, resolved);
30
+ }
31
+ return cache.get(this) as T;
32
+ },
33
+
34
+ set(_value: unknown): void {
35
+ if (process.env.NODE_ENV !== "production") {
36
+ console.warn(
37
+ `[Inject] "${propertyKey}" is managed by the DI container and cannot be assigned directly.`,
38
+ );
39
+ }
40
+ },
41
+
42
+ enumerable: true,
43
+ configurable: true,
44
+ });
45
+ };
46
+ }
47
+
48
+ export function InjectContainer() {
49
+ return function (target: object, propertyKey: string): void {
50
+ Object.defineProperty(target, propertyKey, {
51
+ get() {
52
+ return container;
53
+ },
54
+ enumerable: true,
55
+ configurable: true,
56
+ });
57
+ };
58
+ }
59
+
60
+ export function useService<T>(dep: Constructor<T> | Token<T>): T {
61
+ return container.resolve(dep as Constructor<T>);
62
+ }
63
+
64
+ export function createScope(configure?: (c: Container) => void): Container {
65
+ const child = container.createChild();
66
+ configure?.(child);
67
+ return child;
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ import "reflect-metadata";
2
+
3
+ export {
4
+ Injectable,
5
+ Inject,
6
+ InjectContainer,
7
+ useService,
8
+ createScope,
9
+ } from "./decorators";
10
+ export { Container, container, Token, token } from "./container";
11
+ export type {
12
+ Scope,
13
+ InjectableOptions,
14
+ Constructor,
15
+ ServiceDescriptor,
16
+ } from "./container";
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }