xtaskjs 1.0.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/.github/workflows/publish.yml +44 -0
- package/app.ts +7 -0
- package/babel.config.js +4 -0
- package/dist/packages/common/index.js +17 -0
- package/dist/packages/common/src/decorators/core/server/applicationrunner.js +11 -0
- package/dist/packages/common/src/decorators/core/server/commandlinerunner.js +11 -0
- package/dist/packages/common/src/decorators/core/server/constants.js +5 -0
- package/dist/packages/common/src/decorators/core/server/index.js +20 -0
- package/dist/packages/common/src/decorators/core/server/onevent.js +11 -0
- package/dist/packages/common/src/index.js +24 -0
- package/dist/packages/common/src/logger/index.js +17 -0
- package/dist/packages/common/src/logger/logger.js +40 -0
- package/dist/packages/common/src/types/eventhandlermeta.js +2 -0
- package/dist/packages/common/src/types/index.js +19 -0
- package/dist/packages/common/src/types/lifecycle-events.js +2 -0
- package/dist/packages/common/src/types/runnermeta.js +2 -0
- package/dist/packages/common/test/logger/logger.test.js +12 -0
- package/dist/packages/core/index.js +17 -0
- package/dist/packages/core/src/bootstrap.js +13 -0
- package/dist/packages/core/src/di/autowired.js +41 -0
- package/dist/packages/core/src/di/component.js +19 -0
- package/dist/packages/core/src/di/container.js +225 -0
- package/dist/packages/core/src/di/index.js +27 -0
- package/dist/packages/core/src/di/lifecycle.js +27 -0
- package/dist/packages/core/src/di/managedinstance.js +2 -0
- package/dist/packages/core/src/di/qualifier.js +26 -0
- package/dist/packages/core/src/di/stereotypes.js +10 -0
- package/dist/packages/core/src/index.js +21 -0
- package/dist/packages/core/src/kernel/index.js +18 -0
- package/dist/packages/core/src/kernel/kernel.js +26 -0
- package/dist/packages/core/src/kernel/kernellisteners.js +72 -0
- package/dist/packages/core/src/server/application-lifecycle.js +103 -0
- package/dist/packages/core/src/server/index.js +19 -0
- package/dist/packages/core/src/server/registereventhandlers.js +15 -0
- package/dist/packages/core/test/di/autowired.test.js +92 -0
- package/dist/packages/core/test/di/container.test.js +43 -0
- package/dist/packages/core/test/di/qualifier.test.js +71 -0
- package/dist/packages/core/test/logger/logger.test.js +12 -0
- package/dist/packages/core/test/server/applicationlifecycle.test.js +28 -0
- package/jest.config.js +9 -0
- package/package.json +36 -0
- package/packages/common/README.md +21 -0
- package/packages/common/babel.config.js +3 -0
- package/packages/common/index.ts +1 -0
- package/packages/common/jest.config.js +11 -0
- package/packages/common/package.json +50 -0
- package/packages/common/src/decorators/core/server/applicationrunner.ts +10 -0
- package/packages/common/src/decorators/core/server/commandlinerunner.ts +10 -0
- package/packages/common/src/decorators/core/server/constants.ts +2 -0
- package/packages/common/src/decorators/core/server/index.ts +4 -0
- package/packages/common/src/decorators/core/server/onevent.ts +10 -0
- package/packages/common/src/index.ts +5 -0
- package/packages/common/src/logger/index.ts +1 -0
- package/packages/common/src/logger/logger.ts +26 -0
- package/packages/common/src/types/eventhandlermeta.ts +7 -0
- package/packages/common/src/types/index.ts +3 -0
- package/packages/common/src/types/lifecycle-events.ts +10 -0
- package/packages/common/src/types/runnermeta.ts +5 -0
- package/packages/common/test/logger/logger.test.ts +11 -0
- package/packages/common/tsconfig.json +13 -0
- package/packages/core/README.md +21 -0
- package/packages/core/babel.config.js +3 -0
- package/packages/core/index.ts +1 -0
- package/packages/core/jest.config.js +15 -0
- package/packages/core/package.json +51 -0
- package/packages/core/src/bootstrap.ts +11 -0
- package/packages/core/src/di/autowired.ts +56 -0
- package/packages/core/src/di/component.ts +34 -0
- package/packages/core/src/di/container.ts +216 -0
- package/packages/core/src/di/index.ts +8 -0
- package/packages/core/src/di/lifecycle.ts +25 -0
- package/packages/core/src/di/managedinstance.ts +4 -0
- package/packages/core/src/di/qualifier.ts +25 -0
- package/packages/core/src/di/stereotypes.ts +10 -0
- package/packages/core/src/index.ts +5 -0
- package/packages/core/src/kernel/index.ts +2 -0
- package/packages/core/src/kernel/kernel.ts +31 -0
- package/packages/core/src/kernel/kernellisteners.ts +36 -0
- package/packages/core/src/server/application-lifecycle.ts +85 -0
- package/packages/core/src/server/index.ts +3 -0
- package/packages/core/src/server/registereventhandlers.ts +18 -0
- package/packages/core/test/di/autowired.test.ts +83 -0
- package/packages/core/test/di/container.test.ts +49 -0
- package/packages/core/test/di/qualifier.test.ts +60 -0
- package/packages/core/test/logger/logger.test.ts +11 -0
- package/packages/core/test/server/applicationlifecycle.test.ts +13 -0
- package/packages/core/tsconfig.json +19 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xtaskjs/common",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "xtaskjs - modern, fast, node.js web framework (@common)",
|
|
5
|
+
"author": "Javier Rodríguez Soler",
|
|
6
|
+
"homepage": "https://xtaskjs.com",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test":" jest --passWithNoTests"
|
|
12
|
+
},
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "opencollective",
|
|
15
|
+
"url": "https://opencollective.com/xtaskjs"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/xtaskjs/xtaskjs.git",
|
|
20
|
+
"directory": "packages/common"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependenciesMeta": {
|
|
32
|
+
"class-validator": {
|
|
33
|
+
"optional": true
|
|
34
|
+
},
|
|
35
|
+
"class-transformer": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@jest/globals": "^30.1.2",
|
|
41
|
+
"@types/express": "^5.0.3",
|
|
42
|
+
"@types/jest": "^30.0.0",
|
|
43
|
+
"jest": "^30.1.3",
|
|
44
|
+
"nodemon": "^3.1.10",
|
|
45
|
+
"ts-jest": "^29.4.4",
|
|
46
|
+
"ts-node": "^10.9.2",
|
|
47
|
+
"tsconfig-paths": "^4.2.0",
|
|
48
|
+
"typescript": "^5.9.2"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RunnerMeta } from '@xtaskjs/core';
|
|
2
|
+
import { RUNNERS_KEY } from './constants';
|
|
3
|
+
|
|
4
|
+
export function ApplicationRunner(priority = 0): MethodDecorator {
|
|
5
|
+
return (target, propertyKey) => {
|
|
6
|
+
const runners: RunnerMeta[] = Reflect.getMetadata(RUNNERS_KEY, target.constructor) || [];
|
|
7
|
+
runners.push({ type: "ApplicationRunner", method: propertyKey, priority });
|
|
8
|
+
Reflect.defineMetadata(RUNNERS_KEY, runners, target.constructor);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RunnerMeta } from '@xtaskjs/core';
|
|
2
|
+
import { RUNNERS_KEY } from './constants';
|
|
3
|
+
|
|
4
|
+
export function CommandLineRunner(priority = 0): MethodDecorator {
|
|
5
|
+
return (target, propertyKey) => {
|
|
6
|
+
const runners: RunnerMeta[] = Reflect.getMetadata(RUNNERS_KEY, target.constructor) || [];
|
|
7
|
+
runners.push({ type: "CommandLineRunner", method: propertyKey, priority });
|
|
8
|
+
Reflect.defineMetadata(RUNNERS_KEY, runners, target.constructor);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HANDLERS_KEY } from "./constants";
|
|
2
|
+
import { EventHandlerMeta , LifeCyclePhase } from "../../../types";
|
|
3
|
+
|
|
4
|
+
export function OnEvent(phase: LifeCyclePhase, priority = 0): MethodDecorator {
|
|
5
|
+
return (target, propertyKey) => {
|
|
6
|
+
const handlers: EventHandlerMeta[] = Reflect.getMetadata(HANDLERS_KEY, target.constructor) || [];
|
|
7
|
+
handlers.push({ phase, method: propertyKey, priority });
|
|
8
|
+
Reflect.defineMetadata(HANDLERS_KEY, handlers, target.constructor);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./logger"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Service } from '../../../core/src/di/stereotypes';
|
|
2
|
+
import { PostConstruct } from '../../../core/src/di/lifecycle';
|
|
3
|
+
|
|
4
|
+
@Service({ scope: "singleton" })
|
|
5
|
+
export class Logger {
|
|
6
|
+
|
|
7
|
+
constructor() {}
|
|
8
|
+
|
|
9
|
+
@PostConstruct()
|
|
10
|
+
init() {
|
|
11
|
+
console.log("Logger Initialized");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
info(message: string): void {
|
|
16
|
+
console.log(`INFO: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
warn(message: string): void {
|
|
20
|
+
console.warn(`WARN: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
error(message: string): void {
|
|
24
|
+
console.error(`ERROR: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Logger } from "../../src/logger/logger";
|
|
2
|
+
|
|
3
|
+
describe("Logger", () => {
|
|
4
|
+
it("Should print logs", () => {
|
|
5
|
+
const logger = new Logger();
|
|
6
|
+
const spy = jest.spyOn(console, "log").mockImplementation();
|
|
7
|
+
logger.info("hello");
|
|
8
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining("hello"));
|
|
9
|
+
spy.mockRestore();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2021",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"emitDecoratorMetadata": true,
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*"],
|
|
12
|
+
"exclude": ["node_modules", "dist"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @xtaskjs/core
|
|
2
|
+
|
|
3
|
+
Core package for xtaskjs, a modern, fast Node.js web framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
npm install @xtaskjs/core
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
```typescript
|
|
12
|
+
import { ... } from '@xtaskjs/core';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
- Dependency Injection
|
|
17
|
+
- Application Lifecycle
|
|
18
|
+
- Kernel and Server utilities
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
See [xtaskjs.com](https://xtaskjs.com) for full documentation.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { createDefaultPreset } = require("ts-jest");
|
|
2
|
+
|
|
3
|
+
const tsJestTransformCfg = createDefaultPreset().transform;
|
|
4
|
+
|
|
5
|
+
/** @type {import("jest").Config} **/
|
|
6
|
+
module.exports = {
|
|
7
|
+
testEnvironment: "node",
|
|
8
|
+
transform: {
|
|
9
|
+
...tsJestTransformCfg,
|
|
10
|
+
},
|
|
11
|
+
moduleNameMapper: {
|
|
12
|
+
"^@xtaskjs/common$": "<rootDir>/../common/dist/index.js",
|
|
13
|
+
"^@xtaskjs/common/(.*)$": "<rootDir>/../common/dist/$1"
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xtaskjs/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "xtaskjs - modern, fast, node.js web framework (@core)",
|
|
5
|
+
"author": "Javier Rodríguez Soler",
|
|
6
|
+
"homepage": "https://xtaskjs.com",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test":" jest --passWithNoTests"
|
|
12
|
+
},
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "opencollective",
|
|
15
|
+
"url": "https://opencollective.com/xtaskjs"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/xtaskjs/xtaskjs.git",
|
|
20
|
+
"directory": "packages/core"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@xtaskjs/common": "^1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"class-validator": {
|
|
34
|
+
"optional": true
|
|
35
|
+
},
|
|
36
|
+
"class-transformer": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@jest/globals": "^30.1.2",
|
|
42
|
+
"@types/express": "^5.0.3",
|
|
43
|
+
"@types/jest": "^30.0.0",
|
|
44
|
+
"jest": "^30.1.3",
|
|
45
|
+
"nodemon": "^3.1.10",
|
|
46
|
+
"ts-jest": "^29.4.4",
|
|
47
|
+
"ts-node": "^10.9.2",
|
|
48
|
+
"tsconfig-paths": "^4.2.0",
|
|
49
|
+
"typescript": "^5.9.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { ApplicationLifeCycle, Kernel, KernelListeners, registerEventHandlers } from "@xtaskjs/core";
|
|
3
|
+
|
|
4
|
+
export async function Bootstrap(): Promise<Kernel> {
|
|
5
|
+
const lifecycle = new ApplicationLifeCycle();
|
|
6
|
+
const kernel = new Kernel();
|
|
7
|
+
const listeners = new KernelListeners();
|
|
8
|
+
registerEventHandlers(listeners, lifecycle);
|
|
9
|
+
await lifecycle.boot(() => kernel.boot());
|
|
10
|
+
return kernel;
|
|
11
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
|
|
3
|
+
const AUTOWIRED_KEY = Symbol("xtaskjs:autowired");
|
|
4
|
+
const AUTOWIRED_PROPS_KEY = Symbol("xtaskjs:autowired:props");
|
|
5
|
+
|
|
6
|
+
export interface AutoWiredMetaData {
|
|
7
|
+
type: any;
|
|
8
|
+
required: boolean;
|
|
9
|
+
qualifier?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function AutoWired(options: { required?: boolean; qualifier?: string } = {}) {
|
|
13
|
+
return function (target: any, propertyKey: string | symbol) {
|
|
14
|
+
const type = Reflect.getMetadata("design:type", target, propertyKey);
|
|
15
|
+
const metaData: AutoWiredMetaData = {
|
|
16
|
+
type,
|
|
17
|
+
required: options.required !== false,
|
|
18
|
+
qualifier: options.qualifier,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Store metadata for the property
|
|
22
|
+
Reflect.defineMetadata(AUTOWIRED_KEY, metaData, target, propertyKey);
|
|
23
|
+
|
|
24
|
+
// Store list of autowired properties on the class prototype
|
|
25
|
+
const existingProps=
|
|
26
|
+
Reflect.getMetadata(AUTOWIRED_PROPS_KEY, target.constructor) || [];
|
|
27
|
+
if (!existingProps.includes(propertyKey)) {
|
|
28
|
+
existingProps.push(propertyKey);
|
|
29
|
+
Reflect.defineMetadata(AUTOWIRED_PROPS_KEY, existingProps, target.constructor);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getAutoWiredProperties(target: any): Map<string | symbol, AutoWiredMetaData>{
|
|
35
|
+
|
|
36
|
+
const properties = new Map<string | symbol, AutoWiredMetaData>();
|
|
37
|
+
let currentClass = target.contructor;
|
|
38
|
+
|
|
39
|
+
while (currentClass && currentClass !== Object) {
|
|
40
|
+
const autowiredProps = Reflect.getMetadata(AUTOWIRED_PROPS_KEY, currentClass) || [];
|
|
41
|
+
const prototype = currentClass.prototype;
|
|
42
|
+
|
|
43
|
+
autowiredProps.forEach((prop: string | symbol) => {
|
|
44
|
+
const metaData = Reflect.getMetadata(AUTOWIRED_KEY, prototype, prop);
|
|
45
|
+
if (metaData && !properties.has(prop)) {
|
|
46
|
+
properties.set(prop, metaData);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
currentClass = Object.getPrototypeOf(currentClass);
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return properties;
|
|
55
|
+
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
|
|
3
|
+
export type ComponentType = "service" | "controller" | "repository";
|
|
4
|
+
export type ScoreType = "singleton" | "transient";
|
|
5
|
+
|
|
6
|
+
const COMPONENT_KEY = Symbol("xtaskjs:component");
|
|
7
|
+
|
|
8
|
+
export interface ComponentMetadata {
|
|
9
|
+
scope ?: "singleton" | "transient";
|
|
10
|
+
condition?:() => boolean;
|
|
11
|
+
name?: string;
|
|
12
|
+
primary?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ComponentOptions {
|
|
16
|
+
type?: ComponentType;
|
|
17
|
+
scope?: ScoreType;
|
|
18
|
+
condition?:() => boolean;
|
|
19
|
+
name?: string;
|
|
20
|
+
primary?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Component (options: ComponentOptions = {}) {
|
|
24
|
+
return function (target: any) {
|
|
25
|
+
Reflect.defineMetadata(COMPONENT_KEY, options, target);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getComponentMetadata(target: any): ComponentOptions | undefined {
|
|
30
|
+
return Reflect.getMetadata(COMPONENT_KEY, target);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Service = (meta: ComponentMetadata = {}) => Component(meta);
|
|
34
|
+
export const Repository = (meta: ComponentMetadata = {}) => Component(meta);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import "reflect-metadata"
|
|
2
|
+
import { ComponentMetadata, getComponentMetadata } from "./component";
|
|
3
|
+
import { getPostConstructMethod, getPreDestroyMethod } from "./lifecycle";
|
|
4
|
+
import { readdirSync , statSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { ManagedInstance } from "./managedinstance";
|
|
7
|
+
import { Project } from "ts-morph";
|
|
8
|
+
import { getAutoWiredProperties } from "./autowired";
|
|
9
|
+
import { getConstructorQualifiers } from "./qualifier";
|
|
10
|
+
import { registerEventHandlers} from "../server";
|
|
11
|
+
import { HANDLERS_KEY, RUNNERS_KEY } from "@xtaskjs/common";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export class Container{
|
|
15
|
+
private providers = new Map<any, () => any>();
|
|
16
|
+
private singletons = new Map<any, any>();
|
|
17
|
+
private nameToType = new Map<string, any>();
|
|
18
|
+
private typeToNames = new Map<any, string[]>();
|
|
19
|
+
private primaryBeans = new Map<any, any>();
|
|
20
|
+
private resolving = new Set<any>();
|
|
21
|
+
public managedInstances : ManagedInstance[] = [];
|
|
22
|
+
|
|
23
|
+
// SCAN FOLDER BASE DIR FOR @Service() AND @Component()
|
|
24
|
+
|
|
25
|
+
async autoload(baseDir = "packages"){
|
|
26
|
+
const files = await this.scanDir(join(process.cwd(), baseDir));
|
|
27
|
+
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const name = file.toString();
|
|
30
|
+
if (name.includes("test")) continue; //skip test files
|
|
31
|
+
if (name.endsWith("d.ts")) continue; // skip TypeScript declarations Files.
|
|
32
|
+
if (name.includes("js")) continue; //skip configuration files
|
|
33
|
+
if (name.includes("json")) continue; //skip configuration files
|
|
34
|
+
if (name.includes("index.ts")) continue; //skip export files
|
|
35
|
+
const module = await import(file);
|
|
36
|
+
const project = new Project();
|
|
37
|
+
const sourceFile = project.addSourceFileAtPath(file);
|
|
38
|
+
const classes = sourceFile.getClasses();
|
|
39
|
+
|
|
40
|
+
for (const cls of classes) {
|
|
41
|
+
if (cls.getDecorator("Service") || cls.getDecorator("Component") || cls.getDecorator("Controller") || cls.getDecorator("Repository")) {
|
|
42
|
+
const className = cls.getName();
|
|
43
|
+
if (className && module[className]){
|
|
44
|
+
const classConstructor = module[className];
|
|
45
|
+
const metaData = getComponentMetadata(classConstructor) || { scope: "singleton"};
|
|
46
|
+
const beanName = metaData.name || className;
|
|
47
|
+
this.registerWithName(classConstructor, metaData, beanName);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
public registerWithName(target: any, meta: ComponentMetadata, name?: string){
|
|
56
|
+
if (name){
|
|
57
|
+
this.nameToType.set(name, target);
|
|
58
|
+
const existingNames = this.typeToNames.get(target) || [];
|
|
59
|
+
existingNames.push(name);
|
|
60
|
+
this.typeToNames.set(target, existingNames);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (meta.primary){
|
|
64
|
+
this.primaryBeans.set(target,target);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.register(target, meta);
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Register aa class with the container
|
|
72
|
+
|
|
73
|
+
public register(target: any, meta: ComponentMetadata){
|
|
74
|
+
const paramTypes: any[] =
|
|
75
|
+
Reflect.getMetadata("design:paramtypes", target) || [];
|
|
76
|
+
|
|
77
|
+
console.log(`Registering component: ${target.name} with dependencies:`, paramTypes.map(t => t?.name || 'unknown'));
|
|
78
|
+
|
|
79
|
+
const provider = () => {
|
|
80
|
+
if (this.resolving.has(target)) {
|
|
81
|
+
const resolvingNames = Array.from(this.resolving).map(t => t.name || 'unknown').join(" -> ");
|
|
82
|
+
throw new Error(`Circular dependency detected: ${resolvingNames} -> ${target.name}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.resolving.add(target);
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
try{
|
|
89
|
+
const qualifiers = getConstructorQualifiers(target);
|
|
90
|
+
const dependencies = paramTypes.map((dep, index) => {
|
|
91
|
+
const qualifier = qualifiers?.[index];
|
|
92
|
+
return this.getWithQualifier(dep, qualifier);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const instance = new target(...dependencies);
|
|
96
|
+
|
|
97
|
+
this.injectAutoWiredFields(instance);
|
|
98
|
+
|
|
99
|
+
//PostConstruct
|
|
100
|
+
const postMethod = getPostConstructMethod(instance);
|
|
101
|
+
if (postMethod && typeof instance[postMethod] === "function") {
|
|
102
|
+
instance[postMethod]();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//PreDestroy
|
|
106
|
+
const preMethod = getPreDestroyMethod(instance);
|
|
107
|
+
if (preMethod && typeof instance[preMethod] === "function") {
|
|
108
|
+
this.managedInstances.push({
|
|
109
|
+
instance,
|
|
110
|
+
preDestroy: () => instance[preMethod](),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return instance;
|
|
115
|
+
} finally {
|
|
116
|
+
this.resolving.delete(target);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (meta.scope === "transient") {
|
|
121
|
+
this.providers.set(target, provider);
|
|
122
|
+
} else { //singleton by default
|
|
123
|
+
this.providers.set(target,()=> {
|
|
124
|
+
if (!this.singletons.has(target)) {
|
|
125
|
+
const instance = provider();
|
|
126
|
+
this.singletons.set(target, instance);
|
|
127
|
+
}
|
|
128
|
+
return this.singletons.get(target);
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public getByName<T>(name: string): T {
|
|
134
|
+
const type = this.nameToType.get(name);
|
|
135
|
+
if (!type) {
|
|
136
|
+
throw new Error(`No component found with name: ${name}`);
|
|
137
|
+
}
|
|
138
|
+
return this.get(type);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private getWithQualifier<T>(type: new (...args: any[]) => T, qualifier?: string): T {
|
|
142
|
+
if(qualifier){
|
|
143
|
+
return this.getByName<T>(qualifier);
|
|
144
|
+
}
|
|
145
|
+
return this.get(type);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private injectAutoWiredFields(instance: any) {
|
|
149
|
+
const autoWiredProperties = getAutoWiredProperties(instance);
|
|
150
|
+
autoWiredProperties.forEach((metaData, propertyKey) => {
|
|
151
|
+
try {
|
|
152
|
+
let value;
|
|
153
|
+
if (metaData.qualifier) {
|
|
154
|
+
value = this.getByName(metaData.qualifier);
|
|
155
|
+
} else {
|
|
156
|
+
value = this.get(metaData.type);
|
|
157
|
+
}
|
|
158
|
+
instance[propertyKey] = value;
|
|
159
|
+
}catch (error) {
|
|
160
|
+
if (metaData.required) {
|
|
161
|
+
throw new Error(`Failed to inject required dependency for property "${String(propertyKey)}": ${error.message}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get instance of class
|
|
168
|
+
get<T>(target: new (...args: any[]) => T): T {
|
|
169
|
+
const provider = this.providers.get(target);
|
|
170
|
+
if (!provider) {
|
|
171
|
+
throw new Error(`No provider found for ${target.name}`);
|
|
172
|
+
}
|
|
173
|
+
return provider();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Execute all @PreDestroy in reverse order
|
|
177
|
+
|
|
178
|
+
destroy() {
|
|
179
|
+
this.managedInstances.reverse().forEach((m) => m.preDestroy?.());
|
|
180
|
+
this.managedInstances = [];
|
|
181
|
+
this.singletons.clear();
|
|
182
|
+
this.providers.clear();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Scan folder recursively for .ts or .js files
|
|
186
|
+
public async scanDir(dir: string): Promise<string[]> {
|
|
187
|
+
let results: string[] = [];
|
|
188
|
+
for (const file of readdirSync(dir)) {
|
|
189
|
+
const full = join (dir, file);
|
|
190
|
+
const stat = statSync(full);
|
|
191
|
+
if (stat && stat.isDirectory()) {
|
|
192
|
+
const res = await this.scanDir(full);
|
|
193
|
+
results = results.concat(res);
|
|
194
|
+
} else if (/\.(ts|js)$/.test(file)) {
|
|
195
|
+
results.push(full);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return results;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
public registerLifeCycleListeners(app: any)
|
|
203
|
+
{
|
|
204
|
+
|
|
205
|
+
for (const [type] of this.providers.entries()){
|
|
206
|
+
// Check if class has lifecycle decorators
|
|
207
|
+
const handlers = Reflect.getMetadata(HANDLERS_KEY, type) || [];
|
|
208
|
+
const runners = Reflect.getMetadata(RUNNERS_KEY, type) || [];
|
|
209
|
+
|
|
210
|
+
if(handlers.length > 0 || runners.length > 0){
|
|
211
|
+
const instance = this.get(type);
|
|
212
|
+
registerEventHandlers(instance, app);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export {Component, ComponentOptions} from "./component"
|
|
2
|
+
export {Container} from "./container"
|
|
3
|
+
export * from "./lifecycle"
|
|
4
|
+
export {ManagedInstance} from "./managedinstance"
|
|
5
|
+
export {getComponentMetadata} from "./component"
|
|
6
|
+
export * from "./stereotypes"
|
|
7
|
+
export * from "./autowired"
|
|
8
|
+
export * from "./qualifier"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
|
|
3
|
+
export const POST_CONSTRUCT_KEY = Symbol("post_construct");
|
|
4
|
+
export const PRE_DESTROY_KEY = Symbol("pre_destroy");
|
|
5
|
+
|
|
6
|
+
export function PostConstruct() {
|
|
7
|
+
return function (target: any, propertyKey: string) {
|
|
8
|
+
Reflect.defineMetadata(POST_CONSTRUCT_KEY, propertyKey, target);
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export function PreDestroy() {
|
|
14
|
+
return function (target: any, propertyKey: string) {
|
|
15
|
+
Reflect.defineMetadata(PRE_DESTROY_KEY, propertyKey, target);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getPostConstructMethod(target: any): string | undefined {
|
|
20
|
+
return Reflect.getMetadata(POST_CONSTRUCT_KEY, target);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getPreDestroyMethod(target: any): string | undefined {
|
|
24
|
+
return Reflect.getMetadata(PRE_DESTROY_KEY, target);
|
|
25
|
+
}
|