@dangao/bun-server 1.0.0 → 1.0.3
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/package.json +4 -2
- package/readme.md +163 -2
- package/src/auth/controller.ts +148 -0
- package/src/auth/decorators.ts +81 -0
- package/src/auth/index.ts +12 -0
- package/src/auth/jwt.ts +169 -0
- package/src/auth/oauth2.ts +244 -0
- package/src/auth/types.ts +248 -0
- package/src/cache/cache-module.ts +67 -0
- package/src/cache/decorators.ts +202 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/service.ts +151 -0
- package/src/cache/types.ts +420 -0
- package/src/config/config-module.ts +76 -0
- package/src/config/index.ts +8 -0
- package/src/config/service.ts +93 -0
- package/src/config/types.ts +27 -0
- package/src/controller/controller.ts +251 -0
- package/src/controller/decorators.ts +84 -0
- package/src/controller/index.ts +7 -0
- package/src/controller/metadata.ts +27 -0
- package/src/controller/param-binder.ts +157 -0
- package/src/core/application.ts +233 -0
- package/src/core/context.ts +228 -0
- package/src/core/index.ts +4 -0
- package/src/core/server.ts +128 -0
- package/src/core/types.ts +2 -0
- package/src/database/connection-manager.ts +239 -0
- package/src/database/connection-pool.ts +322 -0
- package/src/database/database-extension.ts +62 -0
- package/src/database/database-module.ts +115 -0
- package/src/database/health-indicator.ts +51 -0
- package/src/database/index.ts +47 -0
- package/src/database/orm/decorators.ts +155 -0
- package/src/database/orm/drizzle-repository.ts +39 -0
- package/src/database/orm/index.ts +23 -0
- package/src/database/orm/repository-decorator.ts +39 -0
- package/src/database/orm/repository.ts +103 -0
- package/src/database/orm/service.ts +49 -0
- package/src/database/orm/transaction-decorator.ts +45 -0
- package/src/database/orm/transaction-interceptor.ts +243 -0
- package/src/database/orm/transaction-manager.ts +276 -0
- package/src/database/orm/transaction-types.ts +140 -0
- package/src/database/orm/types.ts +99 -0
- package/src/database/service.ts +221 -0
- package/src/database/types.ts +171 -0
- package/src/di/container.ts +398 -0
- package/src/di/decorators.ts +228 -0
- package/src/di/index.ts +4 -0
- package/src/di/module-registry.ts +188 -0
- package/src/di/module.ts +65 -0
- package/src/di/types.ts +67 -0
- package/src/error/error-codes.ts +222 -0
- package/src/error/filter.ts +43 -0
- package/src/error/handler.ts +66 -0
- package/src/error/http-exception.ts +115 -0
- package/src/error/i18n.ts +217 -0
- package/src/error/index.ts +16 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/logger-extension.ts +31 -0
- package/src/extensions/logger-module.ts +69 -0
- package/src/extensions/types.ts +14 -0
- package/src/files/index.ts +5 -0
- package/src/files/static-middleware.ts +53 -0
- package/src/files/storage.ts +67 -0
- package/src/files/types.ts +33 -0
- package/src/files/upload-middleware.ts +45 -0
- package/src/health/controller.ts +76 -0
- package/src/health/health-module.ts +51 -0
- package/src/health/index.ts +12 -0
- package/src/health/types.ts +28 -0
- package/src/index.ts +270 -0
- package/src/metrics/collector.ts +209 -0
- package/src/metrics/controller.ts +40 -0
- package/src/metrics/index.ts +15 -0
- package/src/metrics/metrics-module.ts +58 -0
- package/src/metrics/middleware.ts +46 -0
- package/src/metrics/prometheus.ts +79 -0
- package/src/metrics/types.ts +103 -0
- package/src/middleware/builtin/cors.ts +60 -0
- package/src/middleware/builtin/error-handler.ts +90 -0
- package/src/middleware/builtin/file-upload.ts +42 -0
- package/src/middleware/builtin/index.ts +14 -0
- package/src/middleware/builtin/logger.ts +91 -0
- package/src/middleware/builtin/rate-limit.ts +252 -0
- package/src/middleware/builtin/static-file.ts +88 -0
- package/src/middleware/decorators.ts +91 -0
- package/src/middleware/index.ts +11 -0
- package/src/middleware/middleware.ts +13 -0
- package/src/middleware/pipeline.ts +93 -0
- package/src/queue/decorators.ts +110 -0
- package/src/queue/index.ts +26 -0
- package/src/queue/queue-module.ts +64 -0
- package/src/queue/service.ts +302 -0
- package/src/queue/types.ts +341 -0
- package/src/request/body-parser.ts +133 -0
- package/src/request/file-handler.ts +46 -0
- package/src/request/index.ts +5 -0
- package/src/request/request.ts +107 -0
- package/src/request/response.ts +150 -0
- package/src/router/decorators.ts +122 -0
- package/src/router/index.ts +6 -0
- package/src/router/registry.ts +98 -0
- package/src/router/route.ts +140 -0
- package/src/router/router.ts +241 -0
- package/src/router/types.ts +27 -0
- package/src/security/access-decision-manager.ts +34 -0
- package/src/security/authentication-manager.ts +47 -0
- package/src/security/context.ts +92 -0
- package/src/security/filter.ts +162 -0
- package/src/security/index.ts +8 -0
- package/src/security/providers/index.ts +3 -0
- package/src/security/providers/jwt-provider.ts +60 -0
- package/src/security/providers/oauth2-provider.ts +70 -0
- package/src/security/security-module.ts +145 -0
- package/src/security/types.ts +165 -0
- package/src/session/decorators.ts +45 -0
- package/src/session/index.ts +19 -0
- package/src/session/middleware.ts +143 -0
- package/src/session/service.ts +218 -0
- package/src/session/session-module.ts +69 -0
- package/src/session/types.ts +373 -0
- package/src/swagger/decorators.ts +133 -0
- package/src/swagger/generator.ts +234 -0
- package/src/swagger/index.ts +7 -0
- package/src/swagger/swagger-extension.ts +41 -0
- package/src/swagger/swagger-module.ts +83 -0
- package/src/swagger/types.ts +188 -0
- package/src/swagger/ui.ts +98 -0
- package/src/testing/harness.ts +96 -0
- package/src/validation/decorators.ts +95 -0
- package/src/validation/errors.ts +28 -0
- package/src/validation/index.ts +14 -0
- package/src/validation/types.ts +35 -0
- package/src/validation/validator.ts +63 -0
- package/src/websocket/decorators.ts +51 -0
- package/src/websocket/index.ts +12 -0
- package/src/websocket/registry.ts +133 -0
- package/tests/cache/cache-module.test.ts +212 -0
- package/tests/config/config-module.test.ts +151 -0
- package/tests/controller/controller.test.ts +189 -0
- package/tests/core/application.test.ts +57 -0
- package/tests/core/context-body.test.ts +44 -0
- package/tests/core/context.test.ts +86 -0
- package/tests/core/edge-cases.test.ts +432 -0
- package/tests/database/database-module.test.ts +385 -0
- package/tests/database/orm.test.ts +164 -0
- package/tests/database/postgres-mysql-integration.test.ts +395 -0
- package/tests/database/transaction.test.ts +238 -0
- package/tests/di/container.test.ts +264 -0
- package/tests/di/module.test.ts +128 -0
- package/tests/error/error-codes.test.ts +121 -0
- package/tests/error/error-handler.test.ts +68 -0
- package/tests/error/error-handling.test.ts +254 -0
- package/tests/error/http-exception.test.ts +37 -0
- package/tests/error/i18n-integration.test.ts +175 -0
- package/tests/extensions/logger-extension.test.ts +40 -0
- package/tests/files/static-middleware.test.ts +67 -0
- package/tests/files/upload-middleware.test.ts +43 -0
- package/tests/health/health-module.test.ts +116 -0
- package/tests/integration/application-router.test.ts +85 -0
- package/tests/integration/body-parsing.test.ts +88 -0
- package/tests/integration/cache-e2e.test.ts +114 -0
- package/tests/integration/oauth2-e2e.test.ts +615 -0
- package/tests/integration/session-e2e.test.ts +207 -0
- package/tests/metrics/metrics-module.test.ts +178 -0
- package/tests/middleware/builtin.test.ts +206 -0
- package/tests/middleware/file-upload.test.ts +41 -0
- package/tests/middleware/middleware.test.ts +120 -0
- package/tests/middleware/pipeline.test.ts +72 -0
- package/tests/middleware/rate-limit.test.ts +314 -0
- package/tests/middleware/static-file.test.ts +62 -0
- package/tests/perf/harness.test.ts +48 -0
- package/tests/perf/optimization.test.ts +183 -0
- package/tests/perf/regression.test.ts +120 -0
- package/tests/queue/queue-module.test.ts +217 -0
- package/tests/request/body-parser.test.ts +96 -0
- package/tests/request/response.test.ts +99 -0
- package/tests/router/decorators.test.ts +48 -0
- package/tests/router/registry.test.ts +51 -0
- package/tests/router/route.test.ts +71 -0
- package/tests/router/router-normalization.test.ts +106 -0
- package/tests/router/router.test.ts +133 -0
- package/tests/security/access-decision-manager.test.ts +84 -0
- package/tests/security/authentication-manager.test.ts +81 -0
- package/tests/security/context.test.ts +302 -0
- package/tests/security/filter.test.ts +225 -0
- package/tests/security/jwt-provider.test.ts +106 -0
- package/tests/security/oauth2-provider.test.ts +269 -0
- package/tests/security/security-module.test.ts +143 -0
- package/tests/session/session-module.test.ts +307 -0
- package/tests/stress/di-stress.test.ts +30 -0
- package/tests/swagger/decorators.test.ts +153 -0
- package/tests/swagger/generator.test.ts +202 -0
- package/tests/swagger/swagger-extension.test.ts +72 -0
- package/tests/swagger/swagger-module.test.ts +79 -0
- package/tests/utils/test-port.ts +10 -0
- package/tests/validation/controller-validation.test.ts +64 -0
- package/tests/validation/validation.test.ts +42 -0
- package/tests/websocket/gateway.test.ts +68 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../di/module';
|
|
2
|
+
import type { ApplicationExtension } from '../extensions/types';
|
|
3
|
+
|
|
4
|
+
import { DatabaseExtension } from './database-extension';
|
|
5
|
+
import { DatabaseHealthIndicator } from './health-indicator';
|
|
6
|
+
import { OrmService } from './orm/service';
|
|
7
|
+
import { TransactionManager } from './orm/transaction-manager';
|
|
8
|
+
import { DatabaseService } from './service';
|
|
9
|
+
import {
|
|
10
|
+
DATABASE_OPTIONS_TOKEN,
|
|
11
|
+
DATABASE_SERVICE_TOKEN,
|
|
12
|
+
type DatabaseModuleOptions,
|
|
13
|
+
} from './types';
|
|
14
|
+
import { ORM_SERVICE_TOKEN } from './orm/types';
|
|
15
|
+
import { TRANSACTION_SERVICE_TOKEN } from './orm/transaction-types';
|
|
16
|
+
|
|
17
|
+
@Module({
|
|
18
|
+
providers: [],
|
|
19
|
+
})
|
|
20
|
+
export class DatabaseModule {
|
|
21
|
+
/**
|
|
22
|
+
* 创建数据库模块
|
|
23
|
+
* @param options - 模块配置
|
|
24
|
+
*/
|
|
25
|
+
public static forRoot(
|
|
26
|
+
options: DatabaseModuleOptions,
|
|
27
|
+
): typeof DatabaseModule {
|
|
28
|
+
const providers: ModuleProvider[] = [];
|
|
29
|
+
|
|
30
|
+
const service = new DatabaseService(options);
|
|
31
|
+
|
|
32
|
+
providers.push(
|
|
33
|
+
{
|
|
34
|
+
provide: DATABASE_SERVICE_TOKEN,
|
|
35
|
+
useValue: service,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
provide: DATABASE_OPTIONS_TOKEN,
|
|
39
|
+
useValue: options,
|
|
40
|
+
},
|
|
41
|
+
DatabaseService,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// 如果启用了 ORM,注册 ORM 服务
|
|
45
|
+
if (options.orm?.enabled) {
|
|
46
|
+
const ormService = new OrmService(service, {
|
|
47
|
+
enabled: true,
|
|
48
|
+
drizzle: options.orm.drizzle,
|
|
49
|
+
databaseService: service,
|
|
50
|
+
});
|
|
51
|
+
providers.push(
|
|
52
|
+
{
|
|
53
|
+
provide: ORM_SERVICE_TOKEN,
|
|
54
|
+
useValue: ormService,
|
|
55
|
+
},
|
|
56
|
+
OrmService,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 注册事务管理器(总是注册,即使 ORM 未启用)
|
|
61
|
+
const transactionManager = new TransactionManager(service);
|
|
62
|
+
providers.push(
|
|
63
|
+
{
|
|
64
|
+
provide: TRANSACTION_SERVICE_TOKEN,
|
|
65
|
+
useValue: transactionManager,
|
|
66
|
+
},
|
|
67
|
+
TransactionManager,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// 数据库健康检查指示器可以通过 DatabaseModule.createHealthIndicator() 方法获取
|
|
71
|
+
// 然后在 HealthModule.forRoot() 中手动添加
|
|
72
|
+
|
|
73
|
+
// 动态更新模块元数据
|
|
74
|
+
const existingMetadata =
|
|
75
|
+
Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule) || {};
|
|
76
|
+
|
|
77
|
+
// 创建数据库扩展,用于在应用启动时初始化连接
|
|
78
|
+
const databaseExtension = new DatabaseExtension();
|
|
79
|
+
|
|
80
|
+
const metadata = {
|
|
81
|
+
...existingMetadata,
|
|
82
|
+
providers: [...(existingMetadata.providers || []), ...providers],
|
|
83
|
+
exports: [
|
|
84
|
+
...(existingMetadata.exports || []),
|
|
85
|
+
DATABASE_SERVICE_TOKEN,
|
|
86
|
+
DATABASE_OPTIONS_TOKEN,
|
|
87
|
+
DatabaseService,
|
|
88
|
+
TRANSACTION_SERVICE_TOKEN,
|
|
89
|
+
TransactionManager,
|
|
90
|
+
...(options.orm?.enabled
|
|
91
|
+
? [ORM_SERVICE_TOKEN, OrmService]
|
|
92
|
+
: []),
|
|
93
|
+
],
|
|
94
|
+
extensions: [
|
|
95
|
+
...(existingMetadata.extensions || []),
|
|
96
|
+
databaseExtension,
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, DatabaseModule);
|
|
100
|
+
|
|
101
|
+
return DatabaseModule;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 创建数据库健康检查指示器
|
|
106
|
+
* 用于在 HealthModule 中注册数据库健康检查
|
|
107
|
+
* @param databaseService - 数据库服务实例
|
|
108
|
+
* @returns 数据库健康检查指示器
|
|
109
|
+
*/
|
|
110
|
+
public static createHealthIndicator(
|
|
111
|
+
databaseService: DatabaseService,
|
|
112
|
+
): DatabaseHealthIndicator {
|
|
113
|
+
return new DatabaseHealthIndicator(databaseService);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HealthIndicator,
|
|
3
|
+
HealthIndicatorResult,
|
|
4
|
+
} from '../health/types';
|
|
5
|
+
import { DATABASE_SERVICE_TOKEN } from './types';
|
|
6
|
+
import type { DatabaseService } from './service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 数据库健康检查指示器
|
|
10
|
+
*/
|
|
11
|
+
export class DatabaseHealthIndicator implements HealthIndicator {
|
|
12
|
+
public readonly name = 'database';
|
|
13
|
+
|
|
14
|
+
public constructor(private readonly databaseService: DatabaseService) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 执行数据库健康检查
|
|
18
|
+
*/
|
|
19
|
+
public async check(): Promise<HealthIndicatorResult> {
|
|
20
|
+
try {
|
|
21
|
+
const isHealthy = await this.databaseService.healthCheck();
|
|
22
|
+
const connectionInfo = this.databaseService.getConnectionInfo();
|
|
23
|
+
|
|
24
|
+
if (isHealthy) {
|
|
25
|
+
return {
|
|
26
|
+
status: 'up',
|
|
27
|
+
details: {
|
|
28
|
+
type: connectionInfo.type,
|
|
29
|
+
status: connectionInfo.status,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status: 'down',
|
|
36
|
+
details: {
|
|
37
|
+
type: connectionInfo.type,
|
|
38
|
+
status: connectionInfo.status,
|
|
39
|
+
error: connectionInfo.error,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
status: 'down',
|
|
45
|
+
details: {
|
|
46
|
+
error: error instanceof Error ? error.message : String(error),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export { DatabaseModule } from './database-module';
|
|
2
|
+
export { DatabaseService } from './service';
|
|
3
|
+
export { DatabaseConnectionManager } from './connection-manager';
|
|
4
|
+
export { ConnectionPool } from './connection-pool';
|
|
5
|
+
export { DatabaseHealthIndicator } from './health-indicator';
|
|
6
|
+
export { DatabaseExtension } from './database-extension';
|
|
7
|
+
export {
|
|
8
|
+
DATABASE_OPTIONS_TOKEN,
|
|
9
|
+
DATABASE_SERVICE_TOKEN,
|
|
10
|
+
type ConnectionInfo,
|
|
11
|
+
type ConnectionPoolOptions,
|
|
12
|
+
type DatabaseConfig,
|
|
13
|
+
type DatabaseModuleOptions,
|
|
14
|
+
type DatabaseType,
|
|
15
|
+
type MysqlConfig,
|
|
16
|
+
type PostgresConfig,
|
|
17
|
+
type SqliteConfig,
|
|
18
|
+
} from './types';
|
|
19
|
+
// ORM 导出
|
|
20
|
+
export {
|
|
21
|
+
Entity,
|
|
22
|
+
Column,
|
|
23
|
+
PrimaryKey,
|
|
24
|
+
Repository,
|
|
25
|
+
BaseRepository,
|
|
26
|
+
DrizzleBaseRepository,
|
|
27
|
+
OrmService,
|
|
28
|
+
ORM_SERVICE_TOKEN,
|
|
29
|
+
getEntityMetadata,
|
|
30
|
+
getColumnMetadata,
|
|
31
|
+
getRepositoryMetadata,
|
|
32
|
+
type OrmModuleOptions,
|
|
33
|
+
type BaseRepository as BaseRepositoryInterface,
|
|
34
|
+
type EntityMetadata,
|
|
35
|
+
type ColumnMetadata,
|
|
36
|
+
// Transaction exports
|
|
37
|
+
Transactional,
|
|
38
|
+
TransactionManager,
|
|
39
|
+
TransactionInterceptor,
|
|
40
|
+
Propagation,
|
|
41
|
+
IsolationLevel,
|
|
42
|
+
TransactionStatus,
|
|
43
|
+
TRANSACTION_SERVICE_TOKEN,
|
|
44
|
+
getTransactionMetadata,
|
|
45
|
+
type TransactionOptions,
|
|
46
|
+
type TransactionContext,
|
|
47
|
+
} from './orm';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Entity 元数据键
|
|
5
|
+
*/
|
|
6
|
+
export const ENTITY_METADATA_KEY = Symbol('@dangao/bun-server:orm:entity');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Column 元数据键
|
|
10
|
+
*/
|
|
11
|
+
export const COLUMN_METADATA_KEY = Symbol('@dangao/bun-server:orm:column');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Entity 装饰器
|
|
15
|
+
* 标记一个类为数据库实体
|
|
16
|
+
* @param tableName - 表名
|
|
17
|
+
*/
|
|
18
|
+
export function Entity(tableName: string): ClassDecorator {
|
|
19
|
+
return (target) => {
|
|
20
|
+
Reflect.defineMetadata(ENTITY_METADATA_KEY, { tableName }, target);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Column 装饰器
|
|
26
|
+
* 标记一个属性为数据库列
|
|
27
|
+
* @param options - 列选项
|
|
28
|
+
*/
|
|
29
|
+
export function Column(options?: {
|
|
30
|
+
/**
|
|
31
|
+
* 列名(默认为属性名)
|
|
32
|
+
*/
|
|
33
|
+
name?: string;
|
|
34
|
+
/**
|
|
35
|
+
* 列类型
|
|
36
|
+
*/
|
|
37
|
+
type?: string;
|
|
38
|
+
/**
|
|
39
|
+
* 是否为主键
|
|
40
|
+
*/
|
|
41
|
+
primaryKey?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 是否自动递增
|
|
44
|
+
*/
|
|
45
|
+
autoIncrement?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* 是否可为空
|
|
48
|
+
*/
|
|
49
|
+
nullable?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 默认值
|
|
52
|
+
*/
|
|
53
|
+
defaultValue?: unknown;
|
|
54
|
+
}): PropertyDecorator {
|
|
55
|
+
return (target, propertyKey) => {
|
|
56
|
+
const existingColumns =
|
|
57
|
+
Reflect.getMetadata(COLUMN_METADATA_KEY, target.constructor) || [];
|
|
58
|
+
|
|
59
|
+
// 检查是否已存在该属性的列定义
|
|
60
|
+
const existingIndex = existingColumns.findIndex(
|
|
61
|
+
(col: { propertyKey: string }) => col.propertyKey === String(propertyKey),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const columnDef = {
|
|
65
|
+
name: options?.name ?? String(propertyKey),
|
|
66
|
+
type: options?.type ?? 'TEXT',
|
|
67
|
+
primaryKey: options?.primaryKey ?? false,
|
|
68
|
+
autoIncrement: options?.autoIncrement ?? false,
|
|
69
|
+
nullable: options?.nullable !== undefined ? options.nullable : true,
|
|
70
|
+
defaultValue: options?.defaultValue,
|
|
71
|
+
propertyKey: String(propertyKey),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (existingIndex >= 0) {
|
|
75
|
+
// 合并现有定义(保留已有的 primaryKey、nullable 等设置)
|
|
76
|
+
const existing = existingColumns[existingIndex] as {
|
|
77
|
+
primaryKey: boolean;
|
|
78
|
+
nullable: boolean;
|
|
79
|
+
autoIncrement: boolean;
|
|
80
|
+
type: string;
|
|
81
|
+
name: string;
|
|
82
|
+
defaultValue?: unknown;
|
|
83
|
+
};
|
|
84
|
+
existingColumns[existingIndex] = {
|
|
85
|
+
...existing,
|
|
86
|
+
// 合并时,如果新定义中明确设置了属性,使用新值;否则保留旧值
|
|
87
|
+
name: options?.name ?? existing.name,
|
|
88
|
+
type: options?.type ?? existing.type,
|
|
89
|
+
primaryKey:
|
|
90
|
+
options?.primaryKey !== undefined
|
|
91
|
+
? options.primaryKey
|
|
92
|
+
: existing.primaryKey,
|
|
93
|
+
nullable:
|
|
94
|
+
options?.nullable !== undefined ? options.nullable : existing.nullable,
|
|
95
|
+
autoIncrement:
|
|
96
|
+
options?.autoIncrement !== undefined
|
|
97
|
+
? options.autoIncrement
|
|
98
|
+
: existing.autoIncrement,
|
|
99
|
+
defaultValue:
|
|
100
|
+
options?.defaultValue !== undefined
|
|
101
|
+
? options.defaultValue
|
|
102
|
+
: existing.defaultValue,
|
|
103
|
+
propertyKey: String(propertyKey),
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
existingColumns.push(columnDef);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Reflect.defineMetadata(
|
|
110
|
+
COLUMN_METADATA_KEY,
|
|
111
|
+
existingColumns,
|
|
112
|
+
target.constructor,
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* PrimaryKey 装饰器
|
|
119
|
+
* 标记一个属性为主键
|
|
120
|
+
*/
|
|
121
|
+
export function PrimaryKey(): PropertyDecorator {
|
|
122
|
+
return Column({ primaryKey: true, nullable: false });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 获取 Entity 元数据
|
|
127
|
+
*/
|
|
128
|
+
export function getEntityMetadata(
|
|
129
|
+
target: unknown,
|
|
130
|
+
): { tableName: string } | undefined {
|
|
131
|
+
if (typeof target === 'function' || (typeof target === 'object' && target !== null)) {
|
|
132
|
+
return Reflect.getMetadata(ENTITY_METADATA_KEY, target as object);
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 获取 Column 元数据
|
|
139
|
+
*/
|
|
140
|
+
export function getColumnMetadata(
|
|
141
|
+
target: unknown,
|
|
142
|
+
): Array<{
|
|
143
|
+
name: string;
|
|
144
|
+
type: string;
|
|
145
|
+
primaryKey: boolean;
|
|
146
|
+
autoIncrement: boolean;
|
|
147
|
+
nullable: boolean;
|
|
148
|
+
defaultValue?: unknown;
|
|
149
|
+
propertyKey: string;
|
|
150
|
+
}> {
|
|
151
|
+
if (typeof target === 'function' || (typeof target === 'object' && target !== null)) {
|
|
152
|
+
return Reflect.getMetadata(COLUMN_METADATA_KEY, target as object) || [];
|
|
153
|
+
}
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DatabaseService } from '../service';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drizzle Repository 基类
|
|
5
|
+
* 使用 Drizzle ORM 提供类型安全的查询
|
|
6
|
+
* 注意:这是一个抽象基类,供使用 Drizzle ORM 的 Repository 继承
|
|
7
|
+
* 继承此类的 Repository 需要自己添加 @Injectable() 装饰器
|
|
8
|
+
*/
|
|
9
|
+
export abstract class DrizzleBaseRepository<TTable, TSelect, TInsert, TUpdate> {
|
|
10
|
+
protected abstract table: TTable;
|
|
11
|
+
protected abstract drizzle: unknown;
|
|
12
|
+
|
|
13
|
+
public constructor(protected readonly databaseService: DatabaseService) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 查找所有记录
|
|
17
|
+
*/
|
|
18
|
+
public abstract findAll(): Promise<TSelect[]>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 根据 ID 查找记录
|
|
22
|
+
*/
|
|
23
|
+
public abstract findById(id: string | number): Promise<TSelect | null>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 创建记录
|
|
27
|
+
*/
|
|
28
|
+
public abstract create(data: TInsert): Promise<TSelect>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 更新记录
|
|
32
|
+
*/
|
|
33
|
+
public abstract update(id: string | number, data: TUpdate): Promise<TSelect>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 删除记录
|
|
37
|
+
*/
|
|
38
|
+
public abstract delete(id: string | number): Promise<boolean>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { Entity, Column, PrimaryKey, getEntityMetadata, getColumnMetadata } from './decorators';
|
|
2
|
+
export { Repository, getRepositoryMetadata } from './repository-decorator';
|
|
3
|
+
export { Transactional, getTransactionMetadata } from './transaction-decorator';
|
|
4
|
+
export { BaseRepository } from './repository';
|
|
5
|
+
export { DrizzleBaseRepository } from './drizzle-repository';
|
|
6
|
+
export { OrmService } from './service';
|
|
7
|
+
export { TransactionManager } from './transaction-manager';
|
|
8
|
+
export { TransactionInterceptor } from './transaction-interceptor';
|
|
9
|
+
export {
|
|
10
|
+
ORM_SERVICE_TOKEN,
|
|
11
|
+
type OrmModuleOptions,
|
|
12
|
+
type BaseRepository as BaseRepositoryInterface,
|
|
13
|
+
type EntityMetadata,
|
|
14
|
+
type ColumnMetadata,
|
|
15
|
+
} from './types';
|
|
16
|
+
export {
|
|
17
|
+
Propagation,
|
|
18
|
+
IsolationLevel,
|
|
19
|
+
TransactionStatus,
|
|
20
|
+
TRANSACTION_SERVICE_TOKEN,
|
|
21
|
+
type TransactionOptions,
|
|
22
|
+
type TransactionContext,
|
|
23
|
+
} from './transaction-types';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Injectable } from '../../di/decorators';
|
|
3
|
+
import type { Constructor } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Repository 元数据键
|
|
7
|
+
*/
|
|
8
|
+
export const REPOSITORY_METADATA_KEY = Symbol('@dangao/bun-server:orm:repository');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Repository 装饰器
|
|
12
|
+
* 标记一个类为 Repository
|
|
13
|
+
* @param tableName - 表名
|
|
14
|
+
* @param primaryKey - 主键字段名(默认为 'id')
|
|
15
|
+
*/
|
|
16
|
+
export function Repository(tableName: string, primaryKey: string = 'id') {
|
|
17
|
+
return function <T extends Constructor<unknown>>(target: T): T {
|
|
18
|
+
// 标记为 Injectable
|
|
19
|
+
Injectable()(target);
|
|
20
|
+
|
|
21
|
+
// 添加元数据
|
|
22
|
+
Reflect.defineMetadata(REPOSITORY_METADATA_KEY, { tableName, primaryKey }, target);
|
|
23
|
+
|
|
24
|
+
return target;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 获取 Repository 元数据
|
|
30
|
+
*/
|
|
31
|
+
export function getRepositoryMetadata(target: unknown): {
|
|
32
|
+
tableName: string;
|
|
33
|
+
primaryKey: string;
|
|
34
|
+
} | undefined {
|
|
35
|
+
if (typeof target === 'function' || (typeof target === 'object' && target !== null)) {
|
|
36
|
+
return Reflect.getMetadata(REPOSITORY_METADATA_KEY, target as object);
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Injectable, Inject } from '../../di/decorators';
|
|
2
|
+
import { DATABASE_SERVICE_TOKEN } from '../types';
|
|
3
|
+
import type { DatabaseService } from '../service';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Repository 基类
|
|
7
|
+
* 提供基础的 CRUD 操作
|
|
8
|
+
*/
|
|
9
|
+
export abstract class BaseRepository<T = unknown> {
|
|
10
|
+
protected abstract tableName: string;
|
|
11
|
+
protected abstract primaryKey: string;
|
|
12
|
+
|
|
13
|
+
public constructor(
|
|
14
|
+
@Inject(DATABASE_SERVICE_TOKEN)
|
|
15
|
+
protected readonly databaseService: DatabaseService,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 查找所有记录
|
|
20
|
+
*/
|
|
21
|
+
public async findAll(): Promise<T[]> {
|
|
22
|
+
const sql = `SELECT * FROM ${this.tableName}`;
|
|
23
|
+
const result = await this.executeQuery<T>(sql);
|
|
24
|
+
return Array.isArray(result) ? result : [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 根据 ID 查找记录
|
|
29
|
+
*/
|
|
30
|
+
public async findById(id: string | number): Promise<T | null> {
|
|
31
|
+
const sql = `SELECT * FROM ${this.tableName} WHERE ${this.primaryKey} = ?`;
|
|
32
|
+
const result = await this.executeQuery<T>(sql, [id]);
|
|
33
|
+
if (Array.isArray(result) && result.length > 0) {
|
|
34
|
+
return result[0] as T;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 创建记录
|
|
41
|
+
*/
|
|
42
|
+
public async create(data: Partial<T>): Promise<T> {
|
|
43
|
+
const keys = Object.keys(data);
|
|
44
|
+
const values = Object.values(data);
|
|
45
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
46
|
+
const sql = `INSERT INTO ${this.tableName} (${keys.join(', ')}) VALUES (${placeholders})`;
|
|
47
|
+
|
|
48
|
+
await this.executeQuery(sql, values);
|
|
49
|
+
|
|
50
|
+
// 获取最后插入的 ID
|
|
51
|
+
const lastIdResult = await this.executeQuery<{ id: number }>(
|
|
52
|
+
'SELECT last_insert_rowid() as id',
|
|
53
|
+
);
|
|
54
|
+
const lastId = Array.isArray(lastIdResult) && lastIdResult[0]
|
|
55
|
+
? lastIdResult[0].id
|
|
56
|
+
: null;
|
|
57
|
+
|
|
58
|
+
if (lastId !== null) {
|
|
59
|
+
return (await this.findById(lastId)) as T;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return data as T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 更新记录
|
|
67
|
+
*/
|
|
68
|
+
public async update(id: string | number, data: Partial<T>): Promise<T> {
|
|
69
|
+
const keys = Object.keys(data);
|
|
70
|
+
const values = Object.values(data);
|
|
71
|
+
const setClause = keys.map((key) => `${key} = ?`).join(', ');
|
|
72
|
+
const sql = `UPDATE ${this.tableName} SET ${setClause} WHERE ${this.primaryKey} = ?`;
|
|
73
|
+
|
|
74
|
+
await this.executeQuery(sql, [...values, id]);
|
|
75
|
+
|
|
76
|
+
return (await this.findById(id)) as T;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 删除记录
|
|
81
|
+
*/
|
|
82
|
+
public async delete(id: string | number): Promise<boolean> {
|
|
83
|
+
const sql = `DELETE FROM ${this.tableName} WHERE ${this.primaryKey} = ?`;
|
|
84
|
+
await this.executeQuery(sql, [id]);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 执行查询
|
|
90
|
+
*/
|
|
91
|
+
protected async executeQuery<T = unknown>(
|
|
92
|
+
sql: string,
|
|
93
|
+
params?: unknown[],
|
|
94
|
+
): Promise<T[]> {
|
|
95
|
+
const result = this.databaseService.query<T>(sql, params);
|
|
96
|
+
// SQLite 返回同步结果,PostgreSQL/MySQL 返回 Promise
|
|
97
|
+
if (result instanceof Promise) {
|
|
98
|
+
const resolved = await result;
|
|
99
|
+
return Array.isArray(resolved) ? resolved : [resolved];
|
|
100
|
+
}
|
|
101
|
+
return Array.isArray(result) ? result : [result];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Injectable } from '../../di/decorators';
|
|
2
|
+
import type { DatabaseService } from '../service';
|
|
3
|
+
import type { OrmModuleOptions } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ORM 服务
|
|
7
|
+
* 提供 ORM 相关的功能,如 Drizzle 实例管理
|
|
8
|
+
*/
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class OrmService {
|
|
11
|
+
private drizzleInstance: unknown | null = null;
|
|
12
|
+
|
|
13
|
+
public constructor(
|
|
14
|
+
private readonly databaseService: DatabaseService,
|
|
15
|
+
private readonly options: OrmModuleOptions = {},
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 获取 Drizzle 实例
|
|
20
|
+
* 如果未提供,则根据数据库类型创建
|
|
21
|
+
*/
|
|
22
|
+
public getDrizzle(): unknown {
|
|
23
|
+
if (this.options.drizzle) {
|
|
24
|
+
return this.options.drizzle;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (this.drizzleInstance) {
|
|
28
|
+
return this.drizzleInstance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 如果未提供 Drizzle 实例,返回 null
|
|
32
|
+
// 用户需要手动创建并传入
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 设置 Drizzle 实例
|
|
38
|
+
*/
|
|
39
|
+
public setDrizzle(drizzle: unknown): void {
|
|
40
|
+
this.drizzleInstance = drizzle;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取数据库服务
|
|
45
|
+
*/
|
|
46
|
+
public getDatabaseService(): DatabaseService {
|
|
47
|
+
return this.databaseService;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Propagation } from './transaction-types';
|
|
3
|
+
import type { TransactionOptions } from './transaction-types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Transaction 元数据键
|
|
7
|
+
*/
|
|
8
|
+
export const TRANSACTION_METADATA_KEY = Symbol('@dangao/bun-server:orm:transaction');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Transactional 装饰器
|
|
12
|
+
* 标记方法需要在事务中执行
|
|
13
|
+
* @param options - 事务配置选项
|
|
14
|
+
*/
|
|
15
|
+
export function Transactional(options?: TransactionOptions): MethodDecorator {
|
|
16
|
+
return (target, propertyKey, descriptor) => {
|
|
17
|
+
if (!descriptor || typeof descriptor.value !== 'function') {
|
|
18
|
+
throw new Error('@Transactional() can only be applied to methods');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const metadata = {
|
|
22
|
+
propagation: options?.propagation ?? Propagation.REQUIRED,
|
|
23
|
+
isolationLevel: options?.isolationLevel,
|
|
24
|
+
timeout: options?.timeout,
|
|
25
|
+
readOnly: options?.readOnly ?? false,
|
|
26
|
+
rollbackFor: options?.rollbackFor ?? [],
|
|
27
|
+
noRollbackFor: options?.noRollbackFor ?? [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Reflect.defineMetadata(TRANSACTION_METADATA_KEY, metadata, target, propertyKey);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 获取 Transaction 元数据
|
|
36
|
+
*/
|
|
37
|
+
export function getTransactionMetadata(
|
|
38
|
+
target: unknown,
|
|
39
|
+
propertyKey: string | symbol,
|
|
40
|
+
): TransactionOptions | undefined {
|
|
41
|
+
if (typeof target === 'object' && target !== null) {
|
|
42
|
+
return Reflect.getMetadata(TRANSACTION_METADATA_KEY, target, propertyKey);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|