fragment-ts 1.0.28 ā 1.0.30
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/dist/cli/commands/init.command.js +1 -1
- package/dist/core/container/di-container.d.ts +7 -6
- package/dist/core/container/di-container.d.ts.map +1 -1
- package/dist/core/container/di-container.js +162 -129
- package/dist/core/container/di-container.js.map +1 -1
- package/dist/core/decorators/application.decorator.d.ts.map +1 -1
- package/dist/core/decorators/application.decorator.js +2 -1
- package/dist/core/decorators/application.decorator.js.map +1 -1
- package/dist/core/decorators/auto-configuration.decorator.d.ts.map +1 -1
- package/dist/core/decorators/auto-configuration.decorator.js +4 -3
- package/dist/core/decorators/auto-configuration.decorator.js.map +1 -1
- package/dist/core/decorators/controller.decorator.d.ts.map +1 -1
- package/dist/core/decorators/controller.decorator.js +1 -0
- package/dist/core/decorators/controller.decorator.js.map +1 -1
- package/dist/core/decorators/http.decorators.d.ts.map +1 -1
- package/dist/core/decorators/http.decorators.js +9 -0
- package/dist/core/decorators/http.decorators.js.map +1 -1
- package/dist/core/decorators/injectable.decorator.d.ts.map +1 -1
- package/dist/core/decorators/injectable.decorator.js +1 -0
- package/dist/core/decorators/injectable.decorator.js.map +1 -1
- package/dist/core/decorators/injection.decorators.d.ts.map +1 -1
- package/dist/core/decorators/injection.decorators.js +49 -19
- package/dist/core/decorators/injection.decorators.js.map +1 -1
- package/dist/core/decorators/repository.decorator.d.ts.map +1 -1
- package/dist/core/decorators/repository.decorator.js +1 -0
- package/dist/core/decorators/repository.decorator.js.map +1 -1
- package/dist/core/decorators/service.decorator.d.ts.map +1 -1
- package/dist/core/decorators/service.decorator.js +1 -0
- package/dist/core/decorators/service.decorator.js.map +1 -1
- package/dist/core/metadata/metadata-storage.d.ts +11 -0
- package/dist/core/metadata/metadata-storage.d.ts.map +1 -1
- package/dist/core/metadata/metadata-storage.js +22 -0
- package/dist/core/metadata/metadata-storage.js.map +1 -1
- package/dist/web/application.d.ts +0 -6
- package/dist/web/application.d.ts.map +1 -1
- package/dist/web/application.js +75 -38
- package/dist/web/application.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/init.command.ts +1 -1
- package/src/core/container/di-container.ts +247 -181
- package/src/core/decorators/application.decorator.ts +2 -1
- package/src/core/decorators/auto-configuration.decorator.ts +9 -8
- package/src/core/decorators/controller.decorator.ts +2 -1
- package/src/core/decorators/http.decorators.ts +17 -0
- package/src/core/decorators/injectable.decorator.ts +1 -0
- package/src/core/decorators/injection.decorators.ts +62 -22
- package/src/core/decorators/repository.decorator.ts +1 -0
- package/src/core/decorators/service.decorator.ts +1 -0
- package/src/core/metadata/metadata-storage.ts +47 -0
- package/src/web/application.ts +107 -64
- package/tsconfig.json +4 -3
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { Injectable } from
|
|
2
|
-
import { METADATA_KEYS } from
|
|
3
|
-
import { MetadataStorage } from
|
|
1
|
+
import { Injectable } from "./injectable.decorator";
|
|
2
|
+
import { METADATA_KEYS } from "../metadata/metadata-keys";
|
|
3
|
+
import { MetadataStorage } from "../metadata/metadata-storage";
|
|
4
4
|
|
|
5
5
|
export function AutoConfiguration(): ClassDecorator {
|
|
6
6
|
return (target: any) => {
|
|
7
|
-
|
|
7
|
+
console.log(`š§ Registering AutoConfiguration: ${target.name}`);
|
|
8
|
+
Injectable("singleton")(target);
|
|
8
9
|
Reflect.defineMetadata(METADATA_KEYS.AUTO_CONFIGURATION, true, target);
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
const storage = MetadataStorage.getInstance();
|
|
11
12
|
storage.addClass({
|
|
12
13
|
target,
|
|
13
|
-
type:
|
|
14
|
-
scope:
|
|
14
|
+
type: "auto-configuration",
|
|
15
|
+
scope: "singleton",
|
|
15
16
|
});
|
|
16
17
|
};
|
|
17
|
-
}
|
|
18
|
+
}
|
|
@@ -4,9 +4,10 @@ import { MetadataStorage } from "../metadata/metadata-storage";
|
|
|
4
4
|
|
|
5
5
|
export function Controller(path: string = ""): ClassDecorator {
|
|
6
6
|
return (target: any) => {
|
|
7
|
+
console.log(`š® Registering Controller: ${target.name} at ${path || '/'}`);
|
|
7
8
|
Injectable("singleton")(target);
|
|
8
9
|
Reflect.defineMetadata(METADATA_KEYS.CONTROLLER, path, target);
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
const storage = MetadataStorage.getInstance();
|
|
11
12
|
storage.addClass({
|
|
12
13
|
target,
|
|
@@ -8,6 +8,13 @@ function createHttpMethodDecorator(method: string) {
|
|
|
8
8
|
propertyKey: string | symbol,
|
|
9
9
|
descriptor: PropertyDescriptor,
|
|
10
10
|
) => {
|
|
11
|
+
const className = target.constructor.name;
|
|
12
|
+
const methodName = String(propertyKey);
|
|
13
|
+
|
|
14
|
+
console.log(
|
|
15
|
+
`ā” Registering ${method.toUpperCase()} route: ${className}.${methodName} at ${path || "/"}`,
|
|
16
|
+
);
|
|
17
|
+
|
|
11
18
|
Reflect.defineMetadata(
|
|
12
19
|
METADATA_KEYS.HTTP_METHOD,
|
|
13
20
|
method,
|
|
@@ -50,6 +57,16 @@ function createParamDecorator(type: string) {
|
|
|
50
57
|
throw new Error(`@${type}() cannot be used on constructor parameters`);
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
const className = target.constructor.name;
|
|
61
|
+
const methodName = String(propertyKey);
|
|
62
|
+
const paramDisplay = paramName
|
|
63
|
+
? `${paramName} (index: ${parameterIndex})`
|
|
64
|
+
: `index: ${parameterIndex}`;
|
|
65
|
+
|
|
66
|
+
console.log(
|
|
67
|
+
` š„ Registering @${type} parameter for ${className}.${methodName}: ${paramDisplay}`,
|
|
68
|
+
);
|
|
69
|
+
|
|
53
70
|
const storage = MetadataStorage.getInstance();
|
|
54
71
|
|
|
55
72
|
storage.addParam({
|
|
@@ -5,6 +5,7 @@ export type Scope = 'singleton' | 'request' | 'transient';
|
|
|
5
5
|
|
|
6
6
|
export function Injectable(scope: Scope = 'singleton'): ClassDecorator {
|
|
7
7
|
return (target: any) => {
|
|
8
|
+
console.log(`š Registering Injectable: ${target.name} [${scope}]`);
|
|
8
9
|
Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
9
10
|
Reflect.defineMetadata(METADATA_KEYS.SCOPE, scope, target);
|
|
10
11
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { METADATA_KEYS } from "../metadata/metadata-keys";
|
|
2
|
+
import { MetadataStorage } from "../metadata/metadata-storage";
|
|
3
|
+
import { DIContainer } from "../container/di-container";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @Autowired - Automatically inject dependencies by type
|
|
@@ -8,15 +10,22 @@ export function Autowired(): PropertyDecorator {
|
|
|
8
10
|
return (target: any, propertyKey: string | symbol) => {
|
|
9
11
|
// Get the design type from TypeScript metadata
|
|
10
12
|
const type = Reflect.getMetadata("design:type", target, propertyKey);
|
|
11
|
-
|
|
12
13
|
if (!type || type === Object) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`
|
|
14
|
+
console.warn(
|
|
15
|
+
`ā ļø Could not determine type for ${target.constructor.name}.${String(propertyKey)}. ` +
|
|
16
|
+
`Ensure emitDecoratorMetadata is enabled in tsconfig.json`,
|
|
16
17
|
);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
// Store
|
|
20
|
+
// Store metadata for property injection
|
|
21
|
+
const storage = MetadataStorage.getInstance();
|
|
22
|
+
storage.addPropertyInjection?.(target.constructor, propertyKey.toString(), {
|
|
23
|
+
type: "autowired",
|
|
24
|
+
key: propertyKey.toString(),
|
|
25
|
+
metadata: { type },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Also store in Reflect metadata for backward compatibility
|
|
20
29
|
Reflect.defineMetadata(METADATA_KEYS.AUTOWIRED, type, target, propertyKey);
|
|
21
30
|
};
|
|
22
31
|
}
|
|
@@ -28,6 +37,15 @@ export function Autowired(): PropertyDecorator {
|
|
|
28
37
|
*/
|
|
29
38
|
export function Inject(token: string | Function): PropertyDecorator {
|
|
30
39
|
return (target: any, propertyKey: string | symbol) => {
|
|
40
|
+
// Store metadata for property injection
|
|
41
|
+
const storage = MetadataStorage.getInstance();
|
|
42
|
+
storage.addPropertyInjection?.(target.constructor, propertyKey.toString(), {
|
|
43
|
+
type: "inject",
|
|
44
|
+
key: propertyKey.toString(),
|
|
45
|
+
metadata: { token },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Also store in Reflect metadata for backward compatibility
|
|
31
49
|
Reflect.defineMetadata(METADATA_KEYS.INJECT, token, target, propertyKey);
|
|
32
50
|
};
|
|
33
51
|
}
|
|
@@ -38,6 +56,15 @@ export function Inject(token: string | Function): PropertyDecorator {
|
|
|
38
56
|
*/
|
|
39
57
|
export function InjectRepository(entity: Function): PropertyDecorator {
|
|
40
58
|
return (target: any, propertyKey: string | symbol) => {
|
|
59
|
+
// Store metadata for property injection
|
|
60
|
+
const storage = MetadataStorage.getInstance();
|
|
61
|
+
storage.addPropertyInjection?.(target.constructor, propertyKey.toString(), {
|
|
62
|
+
type: "repository",
|
|
63
|
+
key: propertyKey.toString(),
|
|
64
|
+
metadata: { entity },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Also store in Reflect metadata for backward compatibility
|
|
41
68
|
Reflect.defineMetadata(
|
|
42
69
|
METADATA_KEYS.INJECT_REPOSITORY,
|
|
43
70
|
entity,
|
|
@@ -64,6 +91,15 @@ export function Qualifier(name: string): PropertyDecorator {
|
|
|
64
91
|
*/
|
|
65
92
|
export function Value(expression: string): PropertyDecorator {
|
|
66
93
|
return (target: any, propertyKey: string | symbol) => {
|
|
94
|
+
// Store metadata for property injection
|
|
95
|
+
const storage = MetadataStorage.getInstance();
|
|
96
|
+
storage.addPropertyInjection?.(target.constructor, propertyKey.toString(), {
|
|
97
|
+
type: "value",
|
|
98
|
+
key: propertyKey.toString(),
|
|
99
|
+
metadata: { expression },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Also store in Reflect metadata for backward compatibility
|
|
67
103
|
Reflect.defineMetadata(
|
|
68
104
|
METADATA_KEYS.VALUE,
|
|
69
105
|
expression,
|
|
@@ -89,27 +125,21 @@ export function Optional(): PropertyDecorator {
|
|
|
89
125
|
*/
|
|
90
126
|
export function Lazy(): PropertyDecorator {
|
|
91
127
|
return (target: any, propertyKey: string | symbol) => {
|
|
128
|
+
// Mark as lazy in metadata
|
|
92
129
|
Reflect.defineMetadata(METADATA_KEYS.LAZY, true, target, propertyKey);
|
|
93
130
|
|
|
131
|
+
// Store metadata for property injection
|
|
132
|
+
const storage = MetadataStorage.getInstance();
|
|
133
|
+
storage.addPropertyInjection?.(target.constructor, propertyKey.toString(), {
|
|
134
|
+
type: "lazy",
|
|
135
|
+
key: propertyKey.toString(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Get the type for later resolution
|
|
94
139
|
const type = Reflect.getMetadata("design:type", target, propertyKey);
|
|
95
140
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
let resolved = false;
|
|
99
|
-
|
|
100
|
-
Object.defineProperty(target, propertyKey, {
|
|
101
|
-
get() {
|
|
102
|
-
if (!resolved) {
|
|
103
|
-
const { DIContainer } = require("../container/di-container");
|
|
104
|
-
const container = DIContainer.getInstance();
|
|
105
|
-
cached = container.resolve(type);
|
|
106
|
-
resolved = true;
|
|
107
|
-
}
|
|
108
|
-
return cached;
|
|
109
|
-
},
|
|
110
|
-
enumerable: true,
|
|
111
|
-
configurable: true,
|
|
112
|
-
});
|
|
141
|
+
// This will be handled by the container during injection
|
|
142
|
+
// (not directly creating the getter here to avoid container dependency)
|
|
113
143
|
};
|
|
114
144
|
}
|
|
115
145
|
|
|
@@ -123,6 +153,11 @@ export function PostConstruct(): MethodDecorator {
|
|
|
123
153
|
propertyKey: string | symbol,
|
|
124
154
|
descriptor: PropertyDescriptor,
|
|
125
155
|
) => {
|
|
156
|
+
const className = target.constructor.name;
|
|
157
|
+
console.log(
|
|
158
|
+
` šļø Registering @PostConstruct: ${className}.${String(propertyKey)}`,
|
|
159
|
+
);
|
|
160
|
+
|
|
126
161
|
Reflect.defineMetadata(
|
|
127
162
|
METADATA_KEYS.POST_CONSTRUCT,
|
|
128
163
|
propertyKey,
|
|
@@ -141,6 +176,11 @@ export function PreDestroy(): MethodDecorator {
|
|
|
141
176
|
propertyKey: string | symbol,
|
|
142
177
|
descriptor: PropertyDescriptor,
|
|
143
178
|
) => {
|
|
179
|
+
const className = target.constructor.name;
|
|
180
|
+
console.log(
|
|
181
|
+
` š§¹ Registering @PreDestroy: ${className}.${String(propertyKey)}`,
|
|
182
|
+
);
|
|
183
|
+
|
|
144
184
|
Reflect.defineMetadata(
|
|
145
185
|
METADATA_KEYS.PRE_DESTROY,
|
|
146
186
|
propertyKey,
|
|
@@ -4,6 +4,7 @@ import { MetadataStorage } from '../metadata/metadata-storage';
|
|
|
4
4
|
|
|
5
5
|
export function Repository(): ClassDecorator {
|
|
6
6
|
return (target: any) => {
|
|
7
|
+
console.log(`š¾ Registering Repository: ${target.name}`);
|
|
7
8
|
Injectable('singleton')(target);
|
|
8
9
|
Reflect.defineMetadata(METADATA_KEYS.REPOSITORY, true, target);
|
|
9
10
|
|
|
@@ -4,6 +4,7 @@ import { MetadataStorage } from '../metadata/metadata-storage';
|
|
|
4
4
|
|
|
5
5
|
export function Service(): ClassDecorator {
|
|
6
6
|
return (target: any) => {
|
|
7
|
+
console.log(`āļø Registering Service: ${target.name}`);
|
|
7
8
|
Injectable('singleton')(target);
|
|
8
9
|
Reflect.defineMetadata(METADATA_KEYS.SERVICE, true, target);
|
|
9
10
|
|
|
@@ -28,11 +28,19 @@ export interface ParamMetadata {
|
|
|
28
28
|
paramName?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export interface PropertyInjectionMetadata {
|
|
32
|
+
type: "autowired" | "inject" | "repository" | "value" | "lazy";
|
|
33
|
+
key: string;
|
|
34
|
+
metadata?: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
export class MetadataStorage {
|
|
32
38
|
private static instance: MetadataStorage;
|
|
33
39
|
private classes: Map<any, ClassMetadata> = new Map();
|
|
34
40
|
private methods: Map<string, MethodMetadata> = new Map();
|
|
35
41
|
private params: Map<string, ParamMetadata[]> = new Map();
|
|
42
|
+
private propertyInjections: Map<string, PropertyInjectionMetadata[]> =
|
|
43
|
+
new Map();
|
|
36
44
|
|
|
37
45
|
static getInstance(): MetadataStorage {
|
|
38
46
|
if (!MetadataStorage.instance) {
|
|
@@ -88,4 +96,43 @@ export class MetadataStorage {
|
|
|
88
96
|
const key = `${targetName}.${propertyKey}`;
|
|
89
97
|
return this.params.get(key) || [];
|
|
90
98
|
}
|
|
99
|
+
|
|
100
|
+
addPropertyInjection(
|
|
101
|
+
target: any,
|
|
102
|
+
propertyKey: string,
|
|
103
|
+
injection: PropertyInjectionMetadata,
|
|
104
|
+
): void {
|
|
105
|
+
const className = target.name || target.constructor?.name;
|
|
106
|
+
const key = `${className}.${propertyKey}`;
|
|
107
|
+
|
|
108
|
+
if (!this.propertyInjections.has(key)) {
|
|
109
|
+
this.propertyInjections.set(key, []);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const injections = this.propertyInjections.get(key)!;
|
|
113
|
+
injections.push(injection);
|
|
114
|
+
|
|
115
|
+
console.log(
|
|
116
|
+
` šÆ Registered property injection: ${className}.${propertyKey} [${injection.type}]`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getPropertyInjections(
|
|
121
|
+
target: any,
|
|
122
|
+
): { propertyKey: string; injections: PropertyInjectionMetadata[] }[] {
|
|
123
|
+
const className = target.name || target.constructor?.name;
|
|
124
|
+
const result: {
|
|
125
|
+
propertyKey: string;
|
|
126
|
+
injections: PropertyInjectionMetadata[];
|
|
127
|
+
}[] = [];
|
|
128
|
+
|
|
129
|
+
this.propertyInjections.forEach((injections, key) => {
|
|
130
|
+
if (key.startsWith(`${className}.`)) {
|
|
131
|
+
const propertyKey = key.split(".")[1];
|
|
132
|
+
result.push({ propertyKey, injections });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
91
138
|
}
|
package/src/web/application.ts
CHANGED
|
@@ -16,14 +16,15 @@ export class FragmentWebApplication {
|
|
|
16
16
|
private metadataStorage: MetadataStorage;
|
|
17
17
|
|
|
18
18
|
constructor() {
|
|
19
|
+
console.log("š± Initializing Fragment Framework");
|
|
19
20
|
this.app = express();
|
|
20
21
|
this.container = DIContainer.getInstance();
|
|
21
22
|
this.metadataStorage = MetadataStorage.getInstance();
|
|
22
23
|
this.setupMiddleware();
|
|
23
|
-
this;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
private setupMiddleware(): void {
|
|
27
|
+
console.log("āļø Setting up middleware");
|
|
27
28
|
this.app.use(helmet());
|
|
28
29
|
this.app.use(cors());
|
|
29
30
|
this.app.use(compression());
|
|
@@ -32,17 +33,27 @@ export class FragmentWebApplication {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
async bootstrap(appClass: any): Promise<void> {
|
|
35
|
-
|
|
36
|
+
console.log("\nš Bootstrapping application");
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await TypeORMModule.initialize();
|
|
40
|
+
console.log("ā
TypeORM initialized successfully");
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("ā Failed to initialize TypeORM:", error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
36
45
|
|
|
37
46
|
const appMetadata = Reflect.getMetadata(
|
|
38
47
|
METADATA_KEYS.APPLICATION,
|
|
39
48
|
appClass,
|
|
40
49
|
);
|
|
50
|
+
|
|
51
|
+
console.log(`šÆ Application metadata:`, appMetadata);
|
|
41
52
|
|
|
42
53
|
// CRITICAL: Scan and load all component files first
|
|
43
54
|
// This triggers decorator execution
|
|
44
55
|
if (appMetadata?.autoScan !== false) {
|
|
45
|
-
console.log("š Scanning for components...");
|
|
56
|
+
console.log("\nš Scanning for components...");
|
|
46
57
|
await this.scanComponents();
|
|
47
58
|
}
|
|
48
59
|
|
|
@@ -52,51 +63,51 @@ export class FragmentWebApplication {
|
|
|
52
63
|
|
|
53
64
|
const port = appMetadata?.port || 3000;
|
|
54
65
|
const host = appMetadata?.host || "0.0.0.0";
|
|
55
|
-
|
|
66
|
+
|
|
56
67
|
this.app.use(this.errorHandler.bind(this));
|
|
57
|
-
|
|
68
|
+
|
|
58
69
|
this.app.listen(port, host, () => {
|
|
59
|
-
console.log(
|
|
70
|
+
console.log(`\n⨠Fragment application running on http://${host}:${port}`);
|
|
71
|
+
console.log("========================================\n");
|
|
60
72
|
});
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
/**
|
|
64
|
-
* Detects environment and scans appropriate files
|
|
65
|
-
*/
|
|
66
75
|
private async scanComponents(): Promise<void> {
|
|
67
76
|
const cwd = process.cwd();
|
|
68
77
|
const distExists = fs.existsSync(path.join(cwd, "dist"));
|
|
69
78
|
const srcExists = fs.existsSync(path.join(cwd, "src"));
|
|
79
|
+
|
|
80
|
+
console.log(`š Current working directory: ${cwd}`);
|
|
81
|
+
console.log(`š dist/ exists: ${distExists}`);
|
|
82
|
+
console.log(`š src/ exists: ${srcExists}`);
|
|
70
83
|
|
|
71
84
|
// Determine if we're running TypeScript directly (dev) or compiled JS (prod)
|
|
72
85
|
const isDevMode = this.isRunningTypeScript();
|
|
73
|
-
|
|
86
|
+
console.log(`š» Running in ${isDevMode ? 'development' : 'production'} mode`);
|
|
87
|
+
|
|
74
88
|
if (isDevMode && srcExists) {
|
|
75
89
|
// Development mode: scan TypeScript source files
|
|
76
|
-
console.log("
|
|
90
|
+
console.log(" š Development mode: scanning TypeScript files");
|
|
77
91
|
await ComponentScanner.scanSource();
|
|
78
92
|
} else if (distExists) {
|
|
79
93
|
// Production mode: scan compiled JavaScript files
|
|
80
|
-
console.log("
|
|
94
|
+
console.log(" š¦ Production mode: scanning compiled files");
|
|
81
95
|
await ComponentScanner.scan();
|
|
82
96
|
} else {
|
|
83
|
-
console.warn("
|
|
97
|
+
console.warn(" ā ļø Warning: No src/ or dist/ directory found");
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
|
|
87
|
-
/**
|
|
88
|
-
* Detects if we're running TypeScript directly (ts-node or similar)
|
|
89
|
-
*/
|
|
90
101
|
private isRunningTypeScript(): boolean {
|
|
91
102
|
// Check if ts-node is in require.extensions
|
|
92
103
|
if (require.extensions[".ts"]) {
|
|
93
104
|
return true;
|
|
94
105
|
}
|
|
95
|
-
|
|
106
|
+
|
|
96
107
|
// Check if process is running with ts-node or tsx
|
|
97
108
|
const execPath = process.argv[0];
|
|
98
109
|
const scriptPath = process.argv[1] || "";
|
|
99
|
-
|
|
110
|
+
|
|
100
111
|
if (
|
|
101
112
|
execPath.includes("ts-node") ||
|
|
102
113
|
execPath.includes("tsx") ||
|
|
@@ -105,25 +116,24 @@ export class FragmentWebApplication {
|
|
|
105
116
|
) {
|
|
106
117
|
return true;
|
|
107
118
|
}
|
|
108
|
-
|
|
119
|
+
|
|
109
120
|
// Check if main module has .ts extension
|
|
110
121
|
if (require.main?.filename.endsWith(".ts")) {
|
|
111
122
|
return true;
|
|
112
123
|
}
|
|
113
|
-
|
|
124
|
+
|
|
114
125
|
// Check NODE_ENV or explicit flag
|
|
115
126
|
if (process.env.FRAGMENT_DEV_MODE === "true") {
|
|
116
127
|
return true;
|
|
117
128
|
}
|
|
118
|
-
|
|
129
|
+
|
|
119
130
|
return false;
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
private discoverAndRegisterComponents(): void {
|
|
123
134
|
const classes = this.metadataStorage.getAllClasses();
|
|
124
|
-
|
|
125
135
|
console.log(`\nš¦ Discovered ${classes.length} component(s)`);
|
|
126
|
-
|
|
136
|
+
|
|
127
137
|
// Group by type for display
|
|
128
138
|
const grouped = classes.reduce(
|
|
129
139
|
(acc, cls) => {
|
|
@@ -133,38 +143,50 @@ export class FragmentWebApplication {
|
|
|
133
143
|
},
|
|
134
144
|
{} as Record<string, any[]>,
|
|
135
145
|
);
|
|
136
|
-
|
|
146
|
+
|
|
137
147
|
Object.entries(grouped).forEach(([type, items]) => {
|
|
138
148
|
const icon = this.getTypeIcon(type);
|
|
139
|
-
console.log(`
|
|
149
|
+
console.log(` ${icon} ${items.length} ${type}(s)`);
|
|
150
|
+
|
|
151
|
+
items.forEach(item => {
|
|
152
|
+
console.log(` ⢠${item.target.name}${item.path ? ` (${item.path})` : ''}`);
|
|
153
|
+
});
|
|
140
154
|
});
|
|
141
155
|
|
|
142
156
|
let registered = 0;
|
|
143
157
|
let skipped = 0;
|
|
144
|
-
|
|
158
|
+
|
|
145
159
|
classes.forEach((metadata) => {
|
|
146
160
|
if (this.shouldRegister(metadata.target)) {
|
|
147
161
|
// CRITICAL: Register with container
|
|
148
162
|
if (!this.container.has(metadata.target)) {
|
|
149
163
|
this.container.register(metadata.target);
|
|
150
164
|
registered++;
|
|
151
|
-
console.log(`
|
|
165
|
+
console.log(` ā Registered: ${metadata.target.name}`);
|
|
166
|
+
} else {
|
|
167
|
+
console.log(` ā Already registered: ${metadata.target.name}`);
|
|
152
168
|
}
|
|
153
169
|
} else {
|
|
154
170
|
skipped++;
|
|
155
171
|
console.log(
|
|
156
|
-
`
|
|
172
|
+
` ā Skipped: ${metadata.target.name} (conditional check failed)`,
|
|
157
173
|
);
|
|
158
174
|
}
|
|
159
175
|
});
|
|
160
176
|
|
|
161
|
-
console.log(
|
|
162
|
-
`\n ā Registered ${registered}/${classes.length} component(s)`,
|
|
163
|
-
);
|
|
177
|
+
console.log(`\nā Registered ${registered} component(s)`);
|
|
164
178
|
if (skipped > 0) {
|
|
165
|
-
console.log(
|
|
179
|
+
console.log(`ā Skipped ${skipped} component(s) (conditions not met)`);
|
|
166
180
|
}
|
|
167
|
-
|
|
181
|
+
|
|
182
|
+
// Pre-resolve all singleton components to ensure dependencies are injected
|
|
183
|
+
console.log("\nš§ Initializing components");
|
|
184
|
+
classes.forEach(metadata => {
|
|
185
|
+
if (this.shouldRegister(metadata.target)) {
|
|
186
|
+
const instance = this.container.resolve(metadata.target);
|
|
187
|
+
console.log(` ā Initialized: ${metadata.target.name}`);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
private getTypeIcon(type: string): string {
|
|
@@ -183,7 +205,9 @@ export class FragmentWebApplication {
|
|
|
183
205
|
METADATA_KEYS.CONDITIONAL_ON_CLASS,
|
|
184
206
|
target,
|
|
185
207
|
);
|
|
208
|
+
|
|
186
209
|
if (conditionalClass && !this.isClassAvailable(conditionalClass)) {
|
|
210
|
+
console.log(` š« Conditional check failed for ${target.name}: Class not available`);
|
|
187
211
|
return false;
|
|
188
212
|
}
|
|
189
213
|
|
|
@@ -191,7 +215,9 @@ export class FragmentWebApplication {
|
|
|
191
215
|
METADATA_KEYS.CONDITIONAL_ON_MISSING_BEAN,
|
|
192
216
|
target,
|
|
193
217
|
);
|
|
218
|
+
|
|
194
219
|
if (conditionalMissingBean && this.container.has(conditionalMissingBean)) {
|
|
220
|
+
console.log(` š« Conditional check failed for ${target.name}: Bean already exists`);
|
|
195
221
|
return false;
|
|
196
222
|
}
|
|
197
223
|
|
|
@@ -199,15 +225,17 @@ export class FragmentWebApplication {
|
|
|
199
225
|
METADATA_KEYS.CONDITIONAL_ON_PROPERTY,
|
|
200
226
|
target,
|
|
201
227
|
);
|
|
228
|
+
|
|
202
229
|
if (conditionalProperty) {
|
|
203
230
|
const value = process.env[conditionalProperty.key];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
value !== conditionalProperty.expectedValue
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!value) {
|
|
231
|
+
|
|
232
|
+
if (conditionalProperty.expectedValue !== undefined) {
|
|
233
|
+
if (value !== conditionalProperty.expectedValue) {
|
|
234
|
+
console.log(` š« Conditional check failed for ${target.name}: Expected ${conditionalProperty.expectedValue}, got ${value}`);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
} else if (!value) {
|
|
238
|
+
console.log(` š« Conditional check failed for ${target.name}: Property not set`);
|
|
211
239
|
return false;
|
|
212
240
|
}
|
|
213
241
|
}
|
|
@@ -227,37 +255,44 @@ export class FragmentWebApplication {
|
|
|
227
255
|
const controllers = this.metadataStorage
|
|
228
256
|
.getAllClasses()
|
|
229
257
|
.filter((c) => c.type === "controller");
|
|
230
|
-
|
|
258
|
+
|
|
231
259
|
if (controllers.length === 0) {
|
|
232
|
-
console.log("š£ļø
|
|
260
|
+
console.log("\nš£ļø No routes to register");
|
|
233
261
|
return;
|
|
234
262
|
}
|
|
235
|
-
|
|
263
|
+
|
|
236
264
|
let totalRoutes = 0;
|
|
237
|
-
console.log(
|
|
238
|
-
|
|
265
|
+
console.log(`\nš£ļø Registering routes...`);
|
|
266
|
+
|
|
239
267
|
controllers.forEach((controllerMetadata) => {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
268
|
+
try {
|
|
269
|
+
console.log(`\nš Controller: ${controllerMetadata.target.name}`);
|
|
270
|
+
console.log(` Base path: ${controllerMetadata.path || '/'}`);
|
|
271
|
+
|
|
272
|
+
const controller = this.container.resolve(controllerMetadata.target);
|
|
273
|
+
const basePath = controllerMetadata.path || "";
|
|
274
|
+
|
|
275
|
+
const methods = this.metadataStorage
|
|
276
|
+
.getAllMethods()
|
|
277
|
+
.filter((m) => m.target === controllerMetadata.target);
|
|
278
|
+
|
|
279
|
+
if (methods.length === 0) {
|
|
280
|
+
console.log(` ā ļø No routes defined for this controller`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
250
284
|
methods.forEach((methodMetadata) => {
|
|
251
285
|
const fullPath = this.normalizePath(basePath + methodMetadata.path);
|
|
252
286
|
const httpMethod = methodMetadata.method.toLowerCase();
|
|
253
287
|
const methodColor = this.getMethodColor(httpMethod);
|
|
254
288
|
const methodIcon = this.getMethodIcon(httpMethod);
|
|
255
|
-
|
|
289
|
+
|
|
256
290
|
console.log(
|
|
257
|
-
`
|
|
291
|
+
` ${methodIcon} ${methodColor}${httpMethod.toUpperCase().padEnd(7)}\x1b[0m ${fullPath}`,
|
|
258
292
|
);
|
|
293
|
+
|
|
259
294
|
totalRoutes++;
|
|
260
|
-
|
|
295
|
+
|
|
261
296
|
(this.app as any)[httpMethod](
|
|
262
297
|
fullPath,
|
|
263
298
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
@@ -267,23 +302,30 @@ export class FragmentWebApplication {
|
|
|
267
302
|
req,
|
|
268
303
|
res,
|
|
269
304
|
);
|
|
305
|
+
|
|
306
|
+
console.log(`\nš Handling ${httpMethod.toUpperCase()} ${fullPath}`);
|
|
307
|
+
// console.log(` Parameters:`, args);
|
|
308
|
+
|
|
270
309
|
const result = await (controller as any)[
|
|
271
310
|
methodMetadata.propertyKey
|
|
272
311
|
](...args);
|
|
312
|
+
|
|
273
313
|
if (!res.headersSent) {
|
|
274
314
|
res.json(result);
|
|
275
315
|
}
|
|
276
316
|
} catch (error) {
|
|
317
|
+
console.error(`ā Error handling route ${fullPath}:`, error);
|
|
277
318
|
next(error);
|
|
278
319
|
}
|
|
279
320
|
},
|
|
280
321
|
);
|
|
281
322
|
});
|
|
282
|
-
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(`ā Failed to register controller ${controllerMetadata.target.name}:`, error);
|
|
283
325
|
}
|
|
284
326
|
});
|
|
285
327
|
|
|
286
|
-
console.log(
|
|
328
|
+
console.log(`\nā Registered ${totalRoutes} route(s)`);
|
|
287
329
|
}
|
|
288
330
|
|
|
289
331
|
private getMethodColor(method: string): string {
|
|
@@ -313,10 +355,10 @@ export class FragmentWebApplication {
|
|
|
313
355
|
req: Request,
|
|
314
356
|
res: Response,
|
|
315
357
|
): any[] {
|
|
316
|
-
const params = methodMetadata.paramMetadata.sort(
|
|
358
|
+
const params = [...methodMetadata.paramMetadata].sort(
|
|
317
359
|
(a: any, b: any) => a.index - b.index,
|
|
318
360
|
);
|
|
319
|
-
|
|
361
|
+
|
|
320
362
|
return params.map((param: any) => {
|
|
321
363
|
switch (param.type) {
|
|
322
364
|
case "body":
|
|
@@ -349,14 +391,15 @@ export class FragmentWebApplication {
|
|
|
349
391
|
res: Response,
|
|
350
392
|
next: NextFunction,
|
|
351
393
|
): void {
|
|
352
|
-
console.error(err
|
|
394
|
+
console.error(`\nā Global error handler:`, err);
|
|
353
395
|
res.status(500).json({
|
|
354
396
|
error: "Internal Server Error",
|
|
355
397
|
message: err.message,
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
356
399
|
});
|
|
357
400
|
}
|
|
358
401
|
|
|
359
402
|
getExpressApp(): Express {
|
|
360
403
|
return this.app;
|
|
361
404
|
}
|
|
362
|
-
}
|
|
405
|
+
}
|