fragment-ts 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.
Files changed (123) hide show
  1. package/.env.example +0 -0
  2. package/base.ts +1810 -0
  3. package/base2.ts +968 -0
  4. package/bin/frg.ts +5 -0
  5. package/config/fragment.lock.yaml +0 -0
  6. package/config/fragment.yaml +0 -0
  7. package/dist/app.d.ts +15 -0
  8. package/dist/app.js +90 -0
  9. package/dist/auth/auth.controller.d.ts +10 -0
  10. package/dist/auth/auth.controller.js +87 -0
  11. package/dist/auth/auth.middleware.d.ts +2 -0
  12. package/dist/auth/auth.middleware.js +24 -0
  13. package/dist/auth/auth.service.d.ts +20 -0
  14. package/dist/auth/auth.service.js +143 -0
  15. package/dist/auth/dto/login.dto.d.ts +9 -0
  16. package/dist/auth/dto/login.dto.js +2 -0
  17. package/dist/cli/cli.d.ts +12 -0
  18. package/dist/cli/cli.js +186 -0
  19. package/dist/cli/commands/build.command.d.ts +3 -0
  20. package/dist/cli/commands/build.command.js +23 -0
  21. package/dist/cli/commands/config.command.d.ts +6 -0
  22. package/dist/cli/commands/config.command.js +284 -0
  23. package/dist/cli/commands/generate.command.d.ts +8 -0
  24. package/dist/cli/commands/generate.command.js +180 -0
  25. package/dist/cli/commands/init.command.d.ts +7 -0
  26. package/dist/cli/commands/init.command.js +380 -0
  27. package/dist/cli/commands/migrate.command.d.ts +7 -0
  28. package/dist/cli/commands/migrate.command.js +116 -0
  29. package/dist/cli/commands/serve.command.d.ts +6 -0
  30. package/dist/cli/commands/serve.command.js +31 -0
  31. package/dist/cli/templates/controller.template.d.ts +1 -0
  32. package/dist/cli/templates/controller.template.js +52 -0
  33. package/dist/cli/templates/entity.template.d.ts +1 -0
  34. package/dist/cli/templates/entity.template.js +23 -0
  35. package/dist/cli/templates/repository.template.d.ts +1 -0
  36. package/dist/cli/templates/repository.template.js +43 -0
  37. package/dist/cli/templates/service.template.d.ts +1 -0
  38. package/dist/cli/templates/service.template.js +43 -0
  39. package/dist/cli/utils/file-generator.d.ts +9 -0
  40. package/dist/cli/utils/file-generator.js +67 -0
  41. package/dist/cli/utils/logger.d.ts +14 -0
  42. package/dist/cli/utils/logger.js +49 -0
  43. package/dist/controllers/health.controller.d.ts +13 -0
  44. package/dist/controllers/health.controller.js +50 -0
  45. package/dist/core/config/config-loader.d.ts +31 -0
  46. package/dist/core/config/config-loader.js +98 -0
  47. package/dist/core/container/di-container.d.ts +9 -0
  48. package/dist/core/container/di-container.js +37 -0
  49. package/dist/core/decorators/auth-guard.decorator.d.ts +3 -0
  50. package/dist/core/decorators/auth-guard.decorator.js +18 -0
  51. package/dist/core/decorators/autowire.decorator.d.ts +3 -0
  52. package/dist/core/decorators/autowire.decorator.js +17 -0
  53. package/dist/core/decorators/controller.decorator.d.ts +4 -0
  54. package/dist/core/decorators/controller.decorator.js +16 -0
  55. package/dist/core/decorators/injectable.decorator.d.ts +3 -0
  56. package/dist/core/decorators/injectable.decorator.js +14 -0
  57. package/dist/core/decorators/middleware.decorator.d.ts +3 -0
  58. package/dist/core/decorators/middleware.decorator.js +20 -0
  59. package/dist/core/decorators/repository.decorator.d.ts +1 -0
  60. package/dist/core/decorators/repository.decorator.js +7 -0
  61. package/dist/core/decorators/route.decorator.d.ts +14 -0
  62. package/dist/core/decorators/route.decorator.js +32 -0
  63. package/dist/core/decorators/service.decorator.d.ts +1 -0
  64. package/dist/core/decorators/service.decorator.js +7 -0
  65. package/dist/core/openai/openai-client.d.ts +12 -0
  66. package/dist/core/openai/openai-client.js +93 -0
  67. package/dist/database/data-source.d.ts +4 -0
  68. package/dist/database/data-source.js +26 -0
  69. package/dist/entities/session.entity.d.ts +9 -0
  70. package/dist/entities/session.entity.js +45 -0
  71. package/dist/entities/user.entity.d.ts +10 -0
  72. package/dist/entities/user.entity.js +48 -0
  73. package/dist/middlewares/logging.middleware.d.ts +2 -0
  74. package/dist/middlewares/logging.middleware.js +28 -0
  75. package/dist/repositories/session.repository.d.ts +9 -0
  76. package/dist/repositories/session.repository.js +50 -0
  77. package/dist/repositories/user.repository.d.ts +10 -0
  78. package/dist/repositories/user.repository.js +43 -0
  79. package/dist/server.d.ts +1 -0
  80. package/dist/server.js +30 -0
  81. package/dist/services/health.service.d.ts +13 -0
  82. package/dist/services/health.service.js +44 -0
  83. package/package.json +46 -0
  84. package/readme.md +120 -0
  85. package/src/app.ts +121 -0
  86. package/src/auth/auth.controller.ts +52 -0
  87. package/src/auth/auth.middleware.ts +27 -0
  88. package/src/auth/auth.service.ts +110 -0
  89. package/src/auth/dto/login.dto.ts +11 -0
  90. package/src/cli/cli.ts +212 -0
  91. package/src/cli/commands/build.command.ts +24 -0
  92. package/src/cli/commands/config.command.ts +280 -0
  93. package/src/cli/commands/generate.command.ts +170 -0
  94. package/src/cli/commands/init.command.ts +395 -0
  95. package/src/cli/commands/migrate.command.ts +118 -0
  96. package/src/cli/commands/serve.command.ts +37 -0
  97. package/src/cli/templates/controller.template.ts +51 -0
  98. package/src/cli/templates/entity.template.ts +22 -0
  99. package/src/cli/templates/repository.template.ts +42 -0
  100. package/src/cli/templates/service.template.ts +42 -0
  101. package/src/cli/utils/file-generator.ts +37 -0
  102. package/src/cli/utils/logger.ts +52 -0
  103. package/src/controllers/health.controller.ts +24 -0
  104. package/src/core/config/config-loader.ts +98 -0
  105. package/src/core/container/di-container.ts +43 -0
  106. package/src/core/decorators/auth-guard.decorator.ts +15 -0
  107. package/src/core/decorators/autowire.decorator.ts +18 -0
  108. package/src/core/decorators/controller.decorator.ts +15 -0
  109. package/src/core/decorators/injectable.decorator.ts +13 -0
  110. package/src/core/decorators/middleware.decorator.ts +18 -0
  111. package/src/core/decorators/repository.decorator.ts +6 -0
  112. package/src/core/decorators/route.decorator.ts +33 -0
  113. package/src/core/decorators/service.decorator.ts +6 -0
  114. package/src/core/openai/openai-client.ts +99 -0
  115. package/src/database/data-source.ts +29 -0
  116. package/src/entities/session.entity.ts +25 -0
  117. package/src/entities/user.entity.ts +27 -0
  118. package/src/middlewares/logging.middleware.ts +28 -0
  119. package/src/repositories/session.repository.ts +42 -0
  120. package/src/repositories/user.repository.ts +37 -0
  121. package/src/server.ts +32 -0
  122. package/src/services/health.service.ts +29 -0
  123. package/tsconfig.json +20 -0
@@ -0,0 +1,42 @@
1
+
2
+ export function serviceTemplate(name: string): string {
3
+ const className = name.endsWith('Service') ? name : `${name}Service`;
4
+ const repoName = name.replace('Service', '') + 'Repository';
5
+
6
+ return `import { Service } from '../core/decorators/service.decorator';
7
+ import { Autowire } from '../core/decorators/autowire.decorator';
8
+ import { ${repoName} } from '../repositories/${name.toLowerCase()}.repository';
9
+
10
+ @Service()
11
+ export class ${className} {
12
+ constructor(
13
+ @Autowire() private ${repoName.charAt(0).toLowerCase() + repoName.slice(1)}: ${repoName}
14
+ ) {}
15
+
16
+ async findAll() {
17
+ // Implement business logic
18
+ return [];
19
+ }
20
+
21
+ async findById(id: number) {
22
+ // Implement business logic
23
+ return null;
24
+ }
25
+
26
+ async create(data: any) {
27
+ // Implement business logic
28
+ return data;
29
+ }
30
+
31
+ async update(id: number, data: any) {
32
+ // Implement business logic
33
+ return data;
34
+ }
35
+
36
+ async delete(id: number) {
37
+ // Implement business logic
38
+ return true;
39
+ }
40
+ }
41
+ `;
42
+ }
@@ -0,0 +1,37 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ export class FileGenerator {
5
+ static createDirectory(dirPath: string): void {
6
+ if (!fs.existsSync(dirPath)) {
7
+ fs.mkdirSync(dirPath, { recursive: true });
8
+ }
9
+ }
10
+
11
+ static writeFile(filePath: string, content: string): void {
12
+ const dir = path.dirname(filePath);
13
+ this.createDirectory(dir);
14
+ fs.writeFileSync(filePath, content, 'utf-8');
15
+ }
16
+
17
+ static fileExists(filePath: string): boolean {
18
+ return fs.existsSync(filePath);
19
+ }
20
+
21
+ static readFile(filePath: string): string {
22
+ return fs.readFileSync(filePath, 'utf-8');
23
+ }
24
+
25
+ static toCamelCase(str: string): string {
26
+ return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
27
+ }
28
+
29
+ static toPascalCase(str: string): string {
30
+ const camel = this.toCamelCase(str);
31
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
32
+ }
33
+
34
+ static toKebabCase(str: string): string {
35
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
36
+ }
37
+ }
@@ -0,0 +1,52 @@
1
+ import chalk from 'chalk';
2
+ import ora, { Ora } from 'ora';
3
+
4
+ export class CLILogger {
5
+ static success(message: string): void {
6
+ console.log(chalk.green('✓'), message);
7
+ }
8
+
9
+ static error(message: string): void {
10
+ console.log(chalk.red('✗'), message);
11
+ }
12
+
13
+ static info(message: string): void {
14
+ console.log(chalk.blue('ℹ'), message);
15
+ }
16
+
17
+ static warning(message: string): void {
18
+ console.log(chalk.yellow('⚠'), message);
19
+ }
20
+
21
+ static title(message: string): void {
22
+ console.log(chalk.bold.cyan(`\n${message}\n`));
23
+ }
24
+
25
+ static section(message: string): void {
26
+ console.log(chalk.bold(`\n${message}`));
27
+ }
28
+
29
+ static spinner(text: string): Ora {
30
+ return ora(text).start();
31
+ }
32
+
33
+ static box(title: string, content: string[]): void {
34
+ const maxLength = Math.max(title.length, ...content.map(c => c.length)) + 4;
35
+ const border = '═'.repeat(maxLength);
36
+
37
+ console.log(chalk.cyan(`╔${border}╗`));
38
+ console.log(chalk.cyan('║') + chalk.bold(` ${title.padEnd(maxLength)} `) + chalk.cyan('║'));
39
+ console.log(chalk.cyan(`╠${border}╣`));
40
+ content.forEach(line => {
41
+ console.log(chalk.cyan('║') + ` ${line.padEnd(maxLength)} ` + chalk.cyan('║'));
42
+ });
43
+ console.log(chalk.cyan(`╚${border}╝`));
44
+ }
45
+
46
+ static table(data: { [key: string]: string }): void {
47
+ const maxKeyLength = Math.max(...Object.keys(data).map(k => k.length));
48
+ Object.entries(data).forEach(([key, value]) => {
49
+ console.log(` ${chalk.cyan(key.padEnd(maxKeyLength))} : ${value}`);
50
+ });
51
+ }
52
+ }
@@ -0,0 +1,24 @@
1
+
2
+ import { Controller } from '../core/decorators/controller.decorator';
3
+ import { Get } from '../core/decorators/route.decorator';
4
+ import { Injectable } from '../core/decorators/injectable.decorator';
5
+ import { Autowire } from '../core/decorators/autowire.decorator';
6
+ import { HealthService } from '../services/health.service';
7
+
8
+ @Controller('/api/health')
9
+ @Injectable()
10
+ export class HealthController {
11
+ constructor(
12
+ @Autowire() private healthService: HealthService
13
+ ) {}
14
+
15
+ @Get('/')
16
+ async getStatus() {
17
+ return await this.healthService.getStatus();
18
+ }
19
+
20
+ @Get('/tip')
21
+ async getHealthTip() {
22
+ return await this.healthService.generateHealthTip();
23
+ }
24
+ }
@@ -0,0 +1,98 @@
1
+
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as yaml from 'js-yaml';
5
+
6
+ export interface AppConfig {
7
+ app: {
8
+ name: string;
9
+ port: number;
10
+ env: string;
11
+ };
12
+ database: {
13
+ type: string;
14
+ host: string;
15
+ port: number;
16
+ username: string;
17
+ password: string;
18
+ database: string;
19
+ synchronize: boolean;
20
+ logging: boolean;
21
+ };
22
+ openai: {
23
+ apiKey: string;
24
+ model: string;
25
+ };
26
+ auth: {
27
+ tokenExpiry: string;
28
+ sessionExpiry: string;
29
+ };
30
+ }
31
+
32
+ export class ConfigLoader {
33
+ private static config: AppConfig | null = null;
34
+
35
+ static load(useYaml: boolean = true): AppConfig {
36
+ if (this.config) {
37
+ return this.config;
38
+ }
39
+
40
+ const configDir = path.join(process.cwd(), 'config');
41
+ let configPath: string;
42
+
43
+ if (useYaml) {
44
+ // Try .lock.yaml first, then .yaml
45
+ const lockPath = path.join(configDir, 'app.lock.yaml');
46
+ const yamlPath = path.join(configDir, 'app.yaml');
47
+
48
+ configPath = fs.existsSync(lockPath) ? lockPath : yamlPath;
49
+
50
+ if (!fs.existsSync(configPath)) {
51
+ throw new Error('Configuration file not found');
52
+ }
53
+
54
+ const fileContent = fs.readFileSync(configPath, 'utf8');
55
+ this.config = yaml.load(fileContent) as AppConfig;
56
+ } else {
57
+ // JSON fallback
58
+ configPath = path.join(configDir, 'app.json');
59
+ const fileContent = fs.readFileSync(configPath, 'utf8');
60
+ this.config = JSON.parse(fileContent);
61
+ }
62
+
63
+ // Override with environment variables
64
+ this.config = this.mergeEnvVariables(this.config!);
65
+
66
+ return this.config;
67
+ }
68
+
69
+ private static mergeEnvVariables(config: AppConfig): AppConfig {
70
+ return {
71
+ ...config,
72
+ app: {
73
+ ...config.app,
74
+ port: process.env.PORT ? parseInt(process.env.PORT) : config.app.port,
75
+ env: process.env.NODE_ENV || config.app.env
76
+ },
77
+ database: {
78
+ ...config.database,
79
+ host: process.env.DB_HOST || config.database.host,
80
+ port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : config.database.port,
81
+ username: process.env.DB_USERNAME || config.database.username,
82
+ password: process.env.DB_PASSWORD || config.database.password,
83
+ database: process.env.DB_DATABASE || config.database.database
84
+ },
85
+ openai: {
86
+ ...config.openai,
87
+ apiKey: process.env.OPENAI_API_KEY || config.openai.apiKey
88
+ }
89
+ };
90
+ }
91
+
92
+ static get(): AppConfig {
93
+ if (!this.config) {
94
+ throw new Error('Config not loaded. Call ConfigLoader.load() first.');
95
+ }
96
+ return this.config;
97
+ }
98
+ }
@@ -0,0 +1,43 @@
1
+
2
+ import 'reflect-metadata';
3
+ import { AUTOWIRE_METADATA } from '../decorators/autowire.decorator';
4
+
5
+ export class DIContainer {
6
+ private static instances = new Map<any, any>();
7
+ private static classes = new Set<any>();
8
+
9
+ static register(target: any): void {
10
+ this.classes.add(target);
11
+ }
12
+
13
+ static resolve<T>(target: any): T {
14
+ // Return existing singleton if available
15
+ if (this.instances.has(target)) {
16
+ return this.instances.get(target);
17
+ }
18
+
19
+ // Get constructor parameter metadata
20
+ const params = Reflect.getOwnMetadata(AUTOWIRE_METADATA, target) || [];
21
+ const sortedParams = params.sort((a: any, b: any) => a.index - b.index);
22
+
23
+ // Resolve dependencies recursively
24
+ const dependencies = sortedParams.map((param: any) => {
25
+ return this.resolve(param.type);
26
+ });
27
+
28
+ // Create instance
29
+ const instance = new target(...dependencies);
30
+ this.instances.set(target, instance);
31
+
32
+ return instance;
33
+ }
34
+
35
+ static getRegisteredClasses(): any[] {
36
+ return Array.from(this.classes);
37
+ }
38
+
39
+ static clear(): void {
40
+ this.instances.clear();
41
+ this.classes.clear();
42
+ }
43
+ }
@@ -0,0 +1,15 @@
1
+ import 'reflect-metadata';
2
+
3
+ export const AUTH_GUARD_METADATA = 'auth:guard';
4
+
5
+ export function AuthGuard(): MethodDecorator & ClassDecorator {
6
+ return (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => {
7
+ if (propertyKey) {
8
+ // method decorator
9
+ Reflect.defineMetadata(AUTH_GUARD_METADATA, true, target.constructor, propertyKey);
10
+ } else {
11
+ // class decorator
12
+ Reflect.defineMetadata(AUTH_GUARD_METADATA, true, target);
13
+ }
14
+ };
15
+ }
@@ -0,0 +1,18 @@
1
+
2
+ import 'reflect-metadata';
3
+
4
+ export const AUTOWIRE_METADATA = 'autowire:metadata';
5
+
6
+ export function Autowire(): ParameterDecorator {
7
+ return (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) => {
8
+ const existingParams = Reflect.getOwnMetadata(AUTOWIRE_METADATA, target) || [];
9
+ const types = Reflect.getMetadata('design:paramtypes', target) || [];
10
+
11
+ existingParams.push({
12
+ index: parameterIndex,
13
+ type: types[parameterIndex]
14
+ });
15
+
16
+ Reflect.defineMetadata(AUTOWIRE_METADATA, existingParams, target);
17
+ };
18
+ }
@@ -0,0 +1,15 @@
1
+
2
+ import 'reflect-metadata';
3
+ import { DIContainer } from '../container/di-container';
4
+
5
+ export const CONTROLLER_METADATA = 'controller:metadata';
6
+ export const CONTROLLER_PATH = 'controller:path';
7
+
8
+ export function Controller(path: string = ''): ClassDecorator {
9
+ return (target: any) => {
10
+ Reflect.defineMetadata(CONTROLLER_METADATA, true, target);
11
+ Reflect.defineMetadata(CONTROLLER_PATH, path, target);
12
+ DIContainer.register(target);
13
+ return target;
14
+ };
15
+ }
@@ -0,0 +1,13 @@
1
+
2
+ import 'reflect-metadata';
3
+ import { DIContainer } from '../container/di-container';
4
+
5
+ export const INJECTABLE_METADATA = 'injectable:metadata';
6
+
7
+ export function Injectable(): ClassDecorator {
8
+ return (target: any) => {
9
+ Reflect.defineMetadata(INJECTABLE_METADATA, true, target);
10
+ DIContainer.register(target);
11
+ return target;
12
+ };
13
+ }
@@ -0,0 +1,18 @@
1
+
2
+ import 'reflect-metadata';
3
+
4
+ export const MIDDLEWARE_METADATA = 'middleware:metadata';
5
+
6
+ export function Middleware(...middlewares: Function[]): ClassDecorator | MethodDecorator {
7
+ return (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => {
8
+ if (propertyKey) {
9
+ // Method-level middleware
10
+ const existing = Reflect.getOwnMetadata(MIDDLEWARE_METADATA, target.constructor, propertyKey) || [];
11
+ Reflect.defineMetadata(MIDDLEWARE_METADATA, [...existing, ...middlewares], target.constructor, propertyKey);
12
+ } else {
13
+ // Class-level middleware
14
+ const existing = Reflect.getOwnMetadata(MIDDLEWARE_METADATA, target) || [];
15
+ Reflect.defineMetadata(MIDDLEWARE_METADATA, [...existing, ...middlewares], target);
16
+ }
17
+ };
18
+ }
@@ -0,0 +1,6 @@
1
+
2
+ import { Injectable } from './injectable.decorator';
3
+
4
+ export function Repository(): ClassDecorator {
5
+ return Injectable();
6
+ }
@@ -0,0 +1,33 @@
1
+
2
+ import 'reflect-metadata';
3
+
4
+ export const ROUTE_METADATA = 'route:metadata';
5
+
6
+ export enum HttpMethod {
7
+ GET = 'get',
8
+ POST = 'post',
9
+ PUT = 'put',
10
+ DELETE = 'delete',
11
+ PATCH = 'patch'
12
+ }
13
+
14
+ function createRouteDecorator(method: HttpMethod) {
15
+ return (path: string = ''): MethodDecorator => {
16
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
17
+ const routes = Reflect.getOwnMetadata(ROUTE_METADATA, target.constructor) || [];
18
+ routes.push({
19
+ method,
20
+ path,
21
+ handlerName: propertyKey
22
+ });
23
+ Reflect.defineMetadata(ROUTE_METADATA, routes, target.constructor);
24
+ return descriptor;
25
+ };
26
+ };
27
+ }
28
+
29
+ export const Get = createRouteDecorator(HttpMethod.GET);
30
+ export const Post = createRouteDecorator(HttpMethod.POST);
31
+ export const Put = createRouteDecorator(HttpMethod.PUT);
32
+ export const Delete = createRouteDecorator(HttpMethod.DELETE);
33
+ export const Patch = createRouteDecorator(HttpMethod.PATCH);
@@ -0,0 +1,6 @@
1
+
2
+ import { Injectable } from './injectable.decorator';
3
+
4
+ export function Service(): ClassDecorator {
5
+ return Injectable();
6
+ }
@@ -0,0 +1,99 @@
1
+ import { Injectable } from "../decorators/injectable.decorator";
2
+ import { ConfigLoader } from "../config/config-loader";
3
+
4
+ @Injectable()
5
+ export class OpenAIClient {
6
+ private apiKey: string;
7
+ private model: string;
8
+ private baseURL: string = "https://api.openai.com/v1";
9
+
10
+ constructor() {
11
+ const config = ConfigLoader.get();
12
+ this.apiKey = config.openai.apiKey;
13
+ this.model = config.openai.model;
14
+ }
15
+
16
+ async complete(
17
+ prompt: string,
18
+ options?: {
19
+ model?: string;
20
+ maxTokens?: number;
21
+ temperature?: number;
22
+ }
23
+ ): Promise<string> {
24
+ const response = await fetch(`${this.baseURL}/chat/completions`, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ Authorization: `Bearer ${this.apiKey}`,
29
+ },
30
+ body: JSON.stringify({
31
+ model: options?.model || this.model,
32
+ messages: [{ role: "user", content: prompt }],
33
+ max_tokens: options?.maxTokens || 1000,
34
+ temperature: options?.temperature || 0.7,
35
+ }),
36
+ });
37
+
38
+ if (!response.ok) {
39
+ throw new Error(`OpenAI API error: ${response.statusText}`);
40
+ }
41
+
42
+ const data = (await response.json()) as any;
43
+ return data.choices[0].message.content;
44
+ }
45
+
46
+ async streamComplete(
47
+ prompt: string,
48
+ onChunk: (chunk: string) => void
49
+ ): Promise<void> {
50
+ const response = await fetch(`${this.baseURL}/chat/completions`, {
51
+ method: "POST",
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ Authorization: `Bearer ${this.apiKey}`,
55
+ },
56
+ body: JSON.stringify({
57
+ model: this.model,
58
+ messages: [{ role: "user", content: prompt }],
59
+ stream: true,
60
+ }),
61
+ });
62
+
63
+ if (!response.ok) {
64
+ throw new Error(`OpenAI API error: ${response.statusText}`);
65
+ }
66
+
67
+ const reader = response.body?.getReader();
68
+ const decoder = new TextDecoder();
69
+
70
+ if (!reader) {
71
+ throw new Error("No response body");
72
+ }
73
+
74
+ while (true) {
75
+ const { done, value } = await reader.read();
76
+ if (done) break;
77
+
78
+ const chunk = decoder.decode(value);
79
+ const lines = chunk.split("\n").filter((line) => line.trim() !== "");
80
+
81
+ for (const line of lines) {
82
+ if (line.startsWith("data: ")) {
83
+ const data = line.slice(6);
84
+ if (data === "[DONE]") continue;
85
+
86
+ try {
87
+ const parsed = JSON.parse(data);
88
+ const content = parsed.choices[0]?.delta?.content;
89
+ if (content) {
90
+ onChunk(content);
91
+ }
92
+ } catch (e) {
93
+ // Skip invalid JSON
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,29 @@
1
+
2
+ import { DataSource } from 'typeorm';
3
+ import { ConfigLoader } from '../core/config/config-loader';
4
+ import { User } from '../entities/user.entity';
5
+ import { Session } from '../entities/session.entity';
6
+
7
+ let AppDataSource: DataSource;
8
+
9
+ export function initializeDataSource(): DataSource {
10
+ const config = ConfigLoader.get();
11
+
12
+ AppDataSource = new DataSource({
13
+ type: config.database.type as any,
14
+ host: config.database.host,
15
+ port: config.database.port,
16
+ username: config.database.username,
17
+ password: config.database.password,
18
+ database: config.database.database,
19
+ synchronize: config.database.synchronize,
20
+ logging: config.database.logging,
21
+ entities: [User, Session],
22
+ migrations: [],
23
+ subscribers: []
24
+ });
25
+
26
+ return AppDataSource;
27
+ }
28
+
29
+ export { AppDataSource };
@@ -0,0 +1,25 @@
1
+
2
+ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
3
+ import { User } from './user.entity';
4
+
5
+ @Entity('sessions')
6
+ export class Session {
7
+ @PrimaryGeneratedColumn()
8
+ id!: number;
9
+
10
+ @Column({ unique: true, length: 255 })
11
+ token!: string;
12
+
13
+ @Column()
14
+ userId!: number;
15
+
16
+ @ManyToOne(() => User, (user) => user.sessions, { onDelete: 'CASCADE' })
17
+ @JoinColumn({ name: 'userId' })
18
+ user!: User;
19
+
20
+ @Column()
21
+ expiresAt!: Date;
22
+
23
+ @CreateDateColumn()
24
+ createdAt!: Date;
25
+ }
@@ -0,0 +1,27 @@
1
+
2
+ import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
3
+ import { Session } from './session.entity';
4
+
5
+ @Entity('users')
6
+ export class User {
7
+ @PrimaryGeneratedColumn()
8
+ id!: number;
9
+
10
+ @Column({ length: 100 })
11
+ name!: string;
12
+
13
+ @Column({ unique: true, length: 255 })
14
+ email!: string;
15
+
16
+ @Column({ length: 255 })
17
+ passwordHash!: string;
18
+
19
+ @OneToMany(() => Session, (session) => session.user)
20
+ sessions!: Session[];
21
+
22
+ @CreateDateColumn()
23
+ createdAt!: Date;
24
+
25
+ @UpdateDateColumn()
26
+ updatedAt!: Date;
27
+ }
@@ -0,0 +1,28 @@
1
+
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import pino from 'pino';
4
+
5
+ const logger = pino({
6
+ transport: {
7
+ target: 'pino-pretty',
8
+ options: {
9
+ colorize: true
10
+ }
11
+ }
12
+ });
13
+
14
+ export function loggingMiddleware(req: Request, res: Response, next: NextFunction) {
15
+ const start = Date.now();
16
+
17
+ res.on('finish', () => {
18
+ const duration = Date.now() - start;
19
+ logger.info({
20
+ method: req.method,
21
+ url: req.url,
22
+ status: res.statusCode,
23
+ duration: `${duration}ms`
24
+ });
25
+ });
26
+
27
+ next();
28
+ }
@@ -0,0 +1,42 @@
1
+
2
+ import { Repository as TypeORMRepository, MoreThan } from 'typeorm';
3
+ import { Repository } from '../core/decorators/repository.decorator';
4
+ import { Session } from '../entities/session.entity';
5
+ import { AppDataSource } from '../database/data-source';
6
+
7
+ @Repository()
8
+ export class SessionRepository {
9
+ private repository: TypeORMRepository<Session>;
10
+
11
+ constructor() {
12
+ this.repository = AppDataSource.getRepository(Session);
13
+ }
14
+
15
+ async findByToken(token: string): Promise<Session | null> {
16
+ return await this.repository.findOne({
17
+ where: {
18
+ token,
19
+ expiresAt: MoreThan(new Date())
20
+ },
21
+ relations: ['user']
22
+ });
23
+ }
24
+
25
+ async create(sessionData: Partial<Session>): Promise<Session> {
26
+ const session = this.repository.create(sessionData);
27
+ return await this.repository.save(session);
28
+ }
29
+
30
+ async deleteByToken(token: string): Promise<boolean> {
31
+ const result = await this.repository.delete({ token });
32
+ return result.affected ? result.affected > 0 : false;
33
+ }
34
+
35
+ async deleteExpired(): Promise<void> {
36
+ await this.repository
37
+ .createQueryBuilder()
38
+ .delete()
39
+ .where('expiresAt < :now', { now: new Date() })
40
+ .execute();
41
+ }
42
+ }