@dangao/bun-server 2.0.8 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts +6 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/server.d.ts +5 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/database/database-context.d.ts +25 -0
- package/dist/database/database-context.d.ts.map +1 -0
- package/dist/database/database-extension.d.ts +8 -9
- package/dist/database/database-extension.d.ts.map +1 -1
- package/dist/database/database-module.d.ts +7 -1
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/database/db-proxy.d.ts +12 -0
- package/dist/database/db-proxy.d.ts.map +1 -0
- package/dist/database/index.d.ts +6 -1
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/orm/transaction-interceptor.d.ts +0 -16
- package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
- package/dist/database/orm/transaction-manager.d.ts +10 -61
- package/dist/database/orm/transaction-manager.d.ts.map +1 -1
- package/dist/database/service.d.ts.map +1 -1
- package/dist/database/sql-manager.d.ts +14 -0
- package/dist/database/sql-manager.d.ts.map +1 -0
- package/dist/database/sqlite-adapter.d.ts +32 -0
- package/dist/database/sqlite-adapter.d.ts.map +1 -0
- package/dist/database/strategy-decorator.d.ts +8 -0
- package/dist/database/strategy-decorator.d.ts.map +1 -0
- package/dist/database/types.d.ts +122 -1
- package/dist/database/types.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3115 -2586
- package/dist/microservice/service-registry/service-registry-module.d.ts +16 -0
- package/dist/microservice/service-registry/service-registry-module.d.ts.map +1 -1
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/registry.d.ts +1 -1
- package/dist/router/registry.d.ts.map +1 -1
- package/dist/router/route.d.ts +2 -1
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/router.d.ts +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/timeout-decorator.d.ts +6 -0
- package/dist/router/timeout-decorator.d.ts.map +1 -0
- package/docs/database.md +48 -15
- package/docs/idle-timeout.md +42 -0
- package/docs/lifecycle.md +6 -0
- package/docs/microservice-nacos.md +1 -0
- package/docs/microservice-service-registry.md +7 -0
- package/docs/zh/database.md +48 -15
- package/docs/zh/idle-timeout.md +41 -0
- package/docs/zh/lifecycle.md +5 -0
- package/docs/zh/microservice-nacos.md +1 -0
- package/docs/zh/microservice-service-registry.md +6 -0
- package/package.json +1 -1
- package/src/controller/controller.ts +11 -1
- package/src/core/application.ts +60 -1
- package/src/core/server.ts +10 -0
- package/src/database/database-context.ts +43 -0
- package/src/database/database-extension.ts +12 -45
- package/src/database/database-module.ts +254 -11
- package/src/database/db-proxy.ts +75 -0
- package/src/database/index.ts +29 -0
- package/src/database/orm/transaction-interceptor.ts +12 -149
- package/src/database/orm/transaction-manager.ts +143 -210
- package/src/database/service.ts +28 -2
- package/src/database/sql-manager.ts +62 -0
- package/src/database/sqlite-adapter.ts +121 -0
- package/src/database/strategy-decorator.ts +42 -0
- package/src/database/types.ts +133 -1
- package/src/di/module-registry.ts +20 -4
- package/src/index.ts +27 -1
- package/src/microservice/service-registry/service-registry-module.ts +25 -1
- package/src/router/index.ts +1 -0
- package/src/router/registry.ts +10 -1
- package/src/router/route.ts +31 -3
- package/src/router/router.ts +10 -1
- package/src/router/timeout-decorator.ts +35 -0
- package/tests/core/application.test.ts +10 -0
- package/tests/database/database-module.test.ts +91 -430
- package/tests/database/db-proxy.test.ts +93 -0
- package/tests/database/sql-manager.test.ts +43 -0
- package/tests/database/sqlite-adapter.test.ts +45 -0
- package/tests/database/strategy-decorator.test.ts +29 -0
- package/tests/database/transaction.test.ts +84 -222
- package/tests/di/lifecycle.test.ts +37 -0
- package/tests/microservice/service-registry.test.ts +15 -0
- package/tests/router/timeout-decorator.test.ts +48 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SQL } from 'bun';
|
|
2
|
+
|
|
3
|
+
import type { BunSQLConfig } from './types';
|
|
4
|
+
|
|
5
|
+
export class BunSQLManager {
|
|
6
|
+
private readonly instances = new Map<string, SQL>();
|
|
7
|
+
private defaultTenantId = 'default';
|
|
8
|
+
|
|
9
|
+
public getOrCreate(tenantId: string, config: BunSQLConfig): SQL {
|
|
10
|
+
const existing = this.instances.get(tenantId);
|
|
11
|
+
if (existing) {
|
|
12
|
+
return existing;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const pool = config.pool ?? {};
|
|
16
|
+
const sql = new SQL(config.url, {
|
|
17
|
+
max: pool.max ?? 10,
|
|
18
|
+
idleTimeout: pool.idleTimeout ?? 30,
|
|
19
|
+
maxLifetime: pool.maxLifetime ?? 0,
|
|
20
|
+
connectionTimeout: pool.connectionTimeout ?? 30000,
|
|
21
|
+
});
|
|
22
|
+
this.instances.set(tenantId, sql);
|
|
23
|
+
return sql;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public hasTenant(tenantId: string): boolean {
|
|
27
|
+
return this.instances.has(tenantId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public get(tenantId: string): SQL | undefined {
|
|
31
|
+
return this.instances.get(tenantId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public setDefaultTenant(tenantId: string): void {
|
|
35
|
+
this.defaultTenantId = tenantId;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public getDefault(): SQL {
|
|
39
|
+
const sql = this.instances.get(this.defaultTenantId);
|
|
40
|
+
if (!sql) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`[BunSQLManager] default tenant '${this.defaultTenantId}' not initialized`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return sql;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async destroy(tenantId: string, timeout = 10): Promise<void> {
|
|
49
|
+
const sql = this.instances.get(tenantId);
|
|
50
|
+
if (!sql) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
await sql.close({ timeout });
|
|
54
|
+
this.instances.delete(tenantId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async destroyAll(timeout = 10): Promise<void> {
|
|
58
|
+
const tenantIds = Array.from(this.instances.keys());
|
|
59
|
+
await Promise.all(tenantIds.map((tenantId) => this.destroy(tenantId, timeout)));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Database, type SQLQueryBindings } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
import type { SqliteV2Config } from './types';
|
|
4
|
+
|
|
5
|
+
export interface DisposableLock {
|
|
6
|
+
[Symbol.dispose](): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Semaphore {
|
|
10
|
+
private active = 0;
|
|
11
|
+
private readonly queue: Array<() => void> = [];
|
|
12
|
+
|
|
13
|
+
public constructor(private readonly max: number) {}
|
|
14
|
+
|
|
15
|
+
public async acquire(): Promise<DisposableLock> {
|
|
16
|
+
if (this.active < this.max) {
|
|
17
|
+
this.active += 1;
|
|
18
|
+
return this.createDisposable();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await new Promise<void>((resolve) => this.queue.push(resolve));
|
|
22
|
+
this.active += 1;
|
|
23
|
+
return this.createDisposable();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private createDisposable(): DisposableLock {
|
|
27
|
+
let released = false;
|
|
28
|
+
return {
|
|
29
|
+
[Symbol.dispose]: () => {
|
|
30
|
+
if (released) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
released = true;
|
|
34
|
+
this.active = Math.max(0, this.active - 1);
|
|
35
|
+
const next = this.queue.shift();
|
|
36
|
+
next?.();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class SqliteAdapter {
|
|
43
|
+
private readonly db: Database;
|
|
44
|
+
public readonly semaphore: Semaphore;
|
|
45
|
+
|
|
46
|
+
public constructor(config: SqliteV2Config) {
|
|
47
|
+
this.db = new Database(config.database);
|
|
48
|
+
if (config.wal !== false) {
|
|
49
|
+
this.db.exec('PRAGMA journal_mode = WAL;');
|
|
50
|
+
}
|
|
51
|
+
this.semaphore = new Semaphore(config.maxWriteConcurrency ?? 1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public query<T = unknown>(sql: string, params: SQLQueryBindings[] = []): T[] {
|
|
55
|
+
const stmt = this.db.query(sql);
|
|
56
|
+
return stmt.all(...params) as T[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public async execute(sql: string, params: SQLQueryBindings[] = []): Promise<void> {
|
|
60
|
+
const stmt = this.db.query(sql);
|
|
61
|
+
stmt.run(...params);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public close(): void {
|
|
65
|
+
this.db.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class SqliteManager {
|
|
70
|
+
private readonly instances = new Map<string, SqliteAdapter>();
|
|
71
|
+
private defaultTenantId = 'default';
|
|
72
|
+
|
|
73
|
+
public getOrCreate(tenantId: string, config: SqliteV2Config): SqliteAdapter {
|
|
74
|
+
const existing = this.instances.get(tenantId);
|
|
75
|
+
if (existing) {
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
const adapter = new SqliteAdapter(config);
|
|
79
|
+
this.instances.set(tenantId, adapter);
|
|
80
|
+
return adapter;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public setDefaultTenant(tenantId: string): void {
|
|
84
|
+
this.defaultTenantId = tenantId;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public getDefault(): SqliteAdapter {
|
|
88
|
+
const adapter = this.instances.get(this.defaultTenantId);
|
|
89
|
+
if (!adapter) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`[SqliteManager] default tenant '${this.defaultTenantId}' not initialized`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return adapter;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public getAdapter(tenantId: string): SqliteAdapter {
|
|
98
|
+
const adapter = this.instances.get(tenantId);
|
|
99
|
+
if (!adapter) {
|
|
100
|
+
throw new Error(`[SqliteManager] tenant '${tenantId}' not initialized`);
|
|
101
|
+
}
|
|
102
|
+
return adapter;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public destroy(tenantId: string): void {
|
|
106
|
+
const adapter = this.instances.get(tenantId);
|
|
107
|
+
if (!adapter) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
adapter.close();
|
|
111
|
+
this.instances.delete(tenantId);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public destroyAll(): void {
|
|
115
|
+
for (const [tenantId, adapter] of this.instances.entries()) {
|
|
116
|
+
adapter.close();
|
|
117
|
+
this.instances.delete(tenantId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
import type { Constructor } from '@/core/types';
|
|
4
|
+
|
|
5
|
+
export const DB_STRATEGY_KEY = Symbol('@dangao/bun-server:database:strategy');
|
|
6
|
+
|
|
7
|
+
export type DbStrategyType = 'pool' | 'session';
|
|
8
|
+
|
|
9
|
+
export function DbStrategy(
|
|
10
|
+
strategy: DbStrategyType,
|
|
11
|
+
): MethodDecorator & ClassDecorator {
|
|
12
|
+
return (target: object, propertyKey?: string | symbol) => {
|
|
13
|
+
if (propertyKey !== undefined) {
|
|
14
|
+
Reflect.defineMetadata(DB_STRATEGY_KEY, strategy, target, propertyKey);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
Reflect.defineMetadata(DB_STRATEGY_KEY, strategy, target);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Session(): MethodDecorator & ClassDecorator {
|
|
22
|
+
return DbStrategy('session');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getDbStrategy(
|
|
26
|
+
controllerClass: Constructor<unknown>,
|
|
27
|
+
methodName: string,
|
|
28
|
+
): DbStrategyType | undefined {
|
|
29
|
+
const methodStrategy = Reflect.getMetadata(
|
|
30
|
+
DB_STRATEGY_KEY,
|
|
31
|
+
controllerClass.prototype,
|
|
32
|
+
methodName,
|
|
33
|
+
) as DbStrategyType | undefined;
|
|
34
|
+
if (methodStrategy) {
|
|
35
|
+
return methodStrategy;
|
|
36
|
+
}
|
|
37
|
+
return Reflect.getMetadata(
|
|
38
|
+
DB_STRATEGY_KEY,
|
|
39
|
+
controllerClass,
|
|
40
|
+
) as DbStrategyType | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
package/src/database/types.ts
CHANGED
|
@@ -78,6 +78,59 @@ export type DatabaseConfig =
|
|
|
78
78
|
| { type: 'postgres'; config: PostgresConfig }
|
|
79
79
|
| { type: 'mysql'; config: MysqlConfig };
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Bun.SQL 连接池配置
|
|
83
|
+
*/
|
|
84
|
+
export interface BunSQLPoolOptions {
|
|
85
|
+
/**
|
|
86
|
+
* 最大物理连接数
|
|
87
|
+
* @default 10
|
|
88
|
+
*/
|
|
89
|
+
max?: number;
|
|
90
|
+
/**
|
|
91
|
+
* 空闲连接超时(秒)
|
|
92
|
+
* @default 30
|
|
93
|
+
*/
|
|
94
|
+
idleTimeout?: number;
|
|
95
|
+
/**
|
|
96
|
+
* 连接最大生存时间(秒)
|
|
97
|
+
* @default 0
|
|
98
|
+
*/
|
|
99
|
+
maxLifetime?: number;
|
|
100
|
+
/**
|
|
101
|
+
* 获取连接超时(毫秒)
|
|
102
|
+
* @default 30000
|
|
103
|
+
*/
|
|
104
|
+
connectionTimeout?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Bun.SQL 配置(Postgres/MySQL)
|
|
109
|
+
*/
|
|
110
|
+
export interface BunSQLConfig {
|
|
111
|
+
type: 'postgres' | 'mysql';
|
|
112
|
+
url: string;
|
|
113
|
+
pool?: BunSQLPoolOptions;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* SQLite V2 配置
|
|
118
|
+
*/
|
|
119
|
+
export interface SqliteV2Config {
|
|
120
|
+
type: 'sqlite';
|
|
121
|
+
database: string;
|
|
122
|
+
wal?: boolean;
|
|
123
|
+
maxWriteConcurrency?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 多租户配置
|
|
128
|
+
*/
|
|
129
|
+
export interface TenantConfig {
|
|
130
|
+
id: string;
|
|
131
|
+
config: BunSQLConfig | SqliteV2Config;
|
|
132
|
+
}
|
|
133
|
+
|
|
81
134
|
/**
|
|
82
135
|
* 连接池配置
|
|
83
136
|
*/
|
|
@@ -108,12 +161,59 @@ export interface ConnectionPoolOptions {
|
|
|
108
161
|
* DatabaseModule 配置选项
|
|
109
162
|
*/
|
|
110
163
|
export interface DatabaseModuleOptions {
|
|
164
|
+
/**
|
|
165
|
+
* 全局默认连接策略
|
|
166
|
+
* - pool: 每次查询走共享池(默认)
|
|
167
|
+
* - session: 首次查询惰性 reserve,整个请求复用同一连接
|
|
168
|
+
* @default "pool"
|
|
169
|
+
*/
|
|
170
|
+
defaultStrategy?: 'pool' | 'session';
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 多租户配置
|
|
174
|
+
*/
|
|
175
|
+
tenants?: TenantConfig[];
|
|
176
|
+
/**
|
|
177
|
+
* 默认租户 ID
|
|
178
|
+
* @default "default"
|
|
179
|
+
*/
|
|
180
|
+
defaultTenant?: string;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 单租户:数据库类型(V2)
|
|
184
|
+
*/
|
|
185
|
+
type?: DatabaseType;
|
|
186
|
+
/**
|
|
187
|
+
* 单租户:Postgres/MySQL URL(V2)
|
|
188
|
+
*/
|
|
189
|
+
url?: string;
|
|
190
|
+
/**
|
|
191
|
+
* 单租户:SQLite 文件路径(V2)
|
|
192
|
+
*/
|
|
193
|
+
databasePath?: string;
|
|
194
|
+
/**
|
|
195
|
+
* 单租户:SQLite WAL 模式
|
|
196
|
+
* @default true
|
|
197
|
+
*/
|
|
198
|
+
wal?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* 单租户:SQLite 最大写并发
|
|
201
|
+
* @default 1
|
|
202
|
+
*/
|
|
203
|
+
maxWriteConcurrency?: number;
|
|
204
|
+
/**
|
|
205
|
+
* Bun.SQL 连接池参数(V2)
|
|
206
|
+
*/
|
|
207
|
+
bunSqlPool?: BunSQLPoolOptions;
|
|
208
|
+
|
|
111
209
|
/**
|
|
112
210
|
* 数据库配置
|
|
211
|
+
* @deprecated 使用 V2 字段(type/url/databasePath)或 tenants
|
|
113
212
|
*/
|
|
114
|
-
database
|
|
213
|
+
database?: DatabaseConfig;
|
|
115
214
|
/**
|
|
116
215
|
* 连接池配置
|
|
216
|
+
* @deprecated 旧连接池配置,仅用于兼容旧 DatabaseService
|
|
117
217
|
*/
|
|
118
218
|
pool?: ConnectionPoolOptions;
|
|
119
219
|
/**
|
|
@@ -135,6 +235,23 @@ export interface DatabaseModuleOptions {
|
|
|
135
235
|
*/
|
|
136
236
|
drizzle?: unknown;
|
|
137
237
|
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @deprecated 旧字段兼容(用于构造 URL)
|
|
241
|
+
*/
|
|
242
|
+
host?: string;
|
|
243
|
+
/**
|
|
244
|
+
* @deprecated 旧字段兼容(用于构造 URL)
|
|
245
|
+
*/
|
|
246
|
+
port?: number;
|
|
247
|
+
/**
|
|
248
|
+
* @deprecated 旧字段兼容(用于构造 URL)
|
|
249
|
+
*/
|
|
250
|
+
username?: string;
|
|
251
|
+
/**
|
|
252
|
+
* @deprecated 旧字段兼容(用于构造 URL)
|
|
253
|
+
*/
|
|
254
|
+
password?: string;
|
|
138
255
|
}
|
|
139
256
|
|
|
140
257
|
/**
|
|
@@ -169,3 +286,18 @@ export const DATABASE_SERVICE_TOKEN = Symbol('@dangao/bun-server:database:servic
|
|
|
169
286
|
* DatabaseModule Options Token
|
|
170
287
|
*/
|
|
171
288
|
export const DATABASE_OPTIONS_TOKEN = Symbol('@dangao/bun-server:database:options');
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* BunSQLManager Token
|
|
292
|
+
*/
|
|
293
|
+
export const BUN_SQL_MANAGER_TOKEN = Symbol('@dangao/bun-server:database:bun-sql-manager');
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* SQLite manager Token
|
|
297
|
+
*/
|
|
298
|
+
export const SQLITE_MANAGER_TOKEN = Symbol('@dangao/bun-server:database:sqlite-manager');
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* db proxy Token
|
|
302
|
+
*/
|
|
303
|
+
export const DB_TOKEN = Symbol('@dangao/bun-server:database:db');
|
|
@@ -229,18 +229,34 @@ export class ModuleRegistry {
|
|
|
229
229
|
*/
|
|
230
230
|
public resolveAllProviderInstances(): unknown[] {
|
|
231
231
|
const instances: unknown[] = [];
|
|
232
|
+
const seen = new Set<unknown>();
|
|
232
233
|
for (const [, ref] of this.moduleRefs) {
|
|
233
234
|
for (const provider of ref.metadata.providers) {
|
|
234
235
|
try {
|
|
235
236
|
if (typeof provider === 'function') {
|
|
236
|
-
|
|
237
|
+
const instance = ref.container.resolve(provider);
|
|
238
|
+
if (!seen.has(instance)) {
|
|
239
|
+
seen.add(instance);
|
|
240
|
+
instances.push(instance);
|
|
241
|
+
}
|
|
237
242
|
} else if ('useClass' in provider) {
|
|
238
243
|
const token = provider.provide ?? provider.useClass;
|
|
239
|
-
|
|
244
|
+
const instance = ref.container.resolve(token as Constructor<unknown>);
|
|
245
|
+
if (!seen.has(instance)) {
|
|
246
|
+
seen.add(instance);
|
|
247
|
+
instances.push(instance);
|
|
248
|
+
}
|
|
240
249
|
} else if ('useValue' in provider) {
|
|
241
|
-
|
|
250
|
+
if (!seen.has(provider.useValue)) {
|
|
251
|
+
seen.add(provider.useValue);
|
|
252
|
+
instances.push(provider.useValue);
|
|
253
|
+
}
|
|
242
254
|
} else if ('useFactory' in provider) {
|
|
243
|
-
|
|
255
|
+
const instance = ref.container.resolve(provider.provide as Constructor<unknown>);
|
|
256
|
+
if (!seen.has(instance)) {
|
|
257
|
+
seen.add(instance);
|
|
258
|
+
instances.push(instance);
|
|
259
|
+
}
|
|
244
260
|
}
|
|
245
261
|
} catch (_error) {
|
|
246
262
|
// skip providers that can't be resolved (e.g. pending async providers)
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { BunServer, type ServerOptions } from './core/server';
|
|
|
4
4
|
export { ClusterManager, type ClusterOptions, type ClusterMode } from './core/cluster';
|
|
5
5
|
export { Context } from './core/context';
|
|
6
6
|
export { ContextService, CONTEXT_SERVICE_TOKEN, contextStore } from './core/context-service';
|
|
7
|
-
export { Route, Router, RouteRegistry } from './router';
|
|
7
|
+
export { Route, Router, RouteRegistry, IdleTimeout } from './router';
|
|
8
8
|
export { GET, POST, PUT, DELETE, PATCH } from './router/decorators';
|
|
9
9
|
export type { HttpMethod, RouteHandler, RouteMatch } from './router/types';
|
|
10
10
|
export { BodyParser, RequestWrapper, ResponseBuilder } from './request';
|
|
@@ -307,16 +307,41 @@ export {
|
|
|
307
307
|
ConnectionPool,
|
|
308
308
|
DatabaseHealthIndicator,
|
|
309
309
|
DatabaseExtension,
|
|
310
|
+
BunSQLManager,
|
|
311
|
+
SqliteAdapter,
|
|
312
|
+
SqliteManager,
|
|
313
|
+
Semaphore,
|
|
314
|
+
db,
|
|
315
|
+
initDbProxy,
|
|
316
|
+
DbStrategy,
|
|
317
|
+
DbSession,
|
|
318
|
+
DB_STRATEGY_KEY,
|
|
319
|
+
getDbStrategy,
|
|
320
|
+
databaseSessionStore,
|
|
321
|
+
getCurrentSession,
|
|
322
|
+
runWithSession,
|
|
310
323
|
DATABASE_SERVICE_TOKEN,
|
|
311
324
|
DATABASE_OPTIONS_TOKEN,
|
|
325
|
+
BUN_SQL_MANAGER_TOKEN,
|
|
326
|
+
SQLITE_MANAGER_TOKEN,
|
|
327
|
+
DB_TOKEN,
|
|
312
328
|
type DatabaseModuleOptions,
|
|
313
329
|
type DatabaseConfig,
|
|
314
330
|
type DatabaseType,
|
|
315
331
|
type ConnectionInfo,
|
|
316
332
|
type ConnectionPoolOptions,
|
|
333
|
+
type BunSQLConfig,
|
|
334
|
+
type BunSQLPoolOptions,
|
|
317
335
|
type SqliteConfig,
|
|
336
|
+
type SqliteV2Config,
|
|
318
337
|
type PostgresConfig,
|
|
319
338
|
type MysqlConfig,
|
|
339
|
+
type TenantConfig,
|
|
340
|
+
type DbStrategyType,
|
|
341
|
+
type DbProxy,
|
|
342
|
+
type DatabaseSession,
|
|
343
|
+
type TransactionState as DatabaseTransactionState,
|
|
344
|
+
type ReservedSqlSession,
|
|
320
345
|
// ORM exports
|
|
321
346
|
Entity,
|
|
322
347
|
Column,
|
|
@@ -472,6 +497,7 @@ export {
|
|
|
472
497
|
} from './microservice';
|
|
473
498
|
export {
|
|
474
499
|
ServiceRegistryModule,
|
|
500
|
+
ServiceRegistryDecorator,
|
|
475
501
|
NacosServiceRegistry,
|
|
476
502
|
SERVICE_REGISTRY_TOKEN,
|
|
477
503
|
type ServiceRegistryModuleOptions,
|
|
@@ -2,7 +2,11 @@ import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../../di/modul
|
|
|
2
2
|
import { NacosClient } from '@dangao/nacos-client';
|
|
3
3
|
import type { NacosClientOptions } from '@dangao/nacos-client';
|
|
4
4
|
import { NacosServiceRegistry } from './nacos-service-registry';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
SERVICE_REGISTRY_TOKEN,
|
|
7
|
+
type ServiceRegistry,
|
|
8
|
+
type ServiceInstance,
|
|
9
|
+
} from './types';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* 服务注册中心 Provider 类型
|
|
@@ -45,6 +49,21 @@ export interface ServiceRegistryModuleOptions {
|
|
|
45
49
|
*/
|
|
46
50
|
nacos?: NacosServiceRegistryOptions;
|
|
47
51
|
|
|
52
|
+
/**
|
|
53
|
+
* 是否自动在 Application.listen 时注册带 @ServiceRegistry 的服务
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
autoRegister?: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 通过模块配置自动注册服务(无需 @ServiceRegistry 装饰器)
|
|
60
|
+
* 当 autoRegister=true 时在 Application.listen 阶段自动注册
|
|
61
|
+
*/
|
|
62
|
+
autoRegisterService?: Omit<ServiceInstance, 'ip' | 'port'> & {
|
|
63
|
+
ip?: string;
|
|
64
|
+
port?: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
48
67
|
// 未来可以添加其他 provider 的配置
|
|
49
68
|
// consul?: ConsulServiceRegistryOptions;
|
|
50
69
|
// eureka?: EurekaServiceRegistryOptions;
|
|
@@ -57,11 +76,16 @@ export interface ServiceRegistryModuleOptions {
|
|
|
57
76
|
providers: [],
|
|
58
77
|
})
|
|
59
78
|
export class ServiceRegistryModule {
|
|
79
|
+
public static autoRegister = true;
|
|
80
|
+
public static autoRegisterService: ServiceRegistryModuleOptions['autoRegisterService'];
|
|
81
|
+
|
|
60
82
|
/**
|
|
61
83
|
* 创建服务注册中心模块
|
|
62
84
|
* @param options - 模块配置
|
|
63
85
|
*/
|
|
64
86
|
public static forRoot(options: ServiceRegistryModuleOptions): typeof ServiceRegistryModule {
|
|
87
|
+
ServiceRegistryModule.autoRegister = options.autoRegister ?? true;
|
|
88
|
+
ServiceRegistryModule.autoRegisterService = options.autoRegisterService;
|
|
65
89
|
const providers: ModuleProvider[] = [];
|
|
66
90
|
|
|
67
91
|
let serviceRegistry: ServiceRegistry;
|
package/src/router/index.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { Route } from './route';
|
|
|
2
2
|
export { Router } from './router';
|
|
3
3
|
export { RouteRegistry } from './registry';
|
|
4
4
|
export { GET, POST, PUT, DELETE, PATCH } from './decorators';
|
|
5
|
+
export { IdleTimeout, IDLE_TIMEOUT_KEY, getIdleTimeout } from './timeout-decorator';
|
|
5
6
|
export type { HttpMethod, RouteHandler, RouteMatch } from './types';
|
|
6
7
|
|
package/src/router/registry.ts
CHANGED
|
@@ -42,8 +42,17 @@ export class RouteRegistry {
|
|
|
42
42
|
middlewares: Middleware[] = [],
|
|
43
43
|
controllerClass?: Constructor<unknown>,
|
|
44
44
|
methodName?: string,
|
|
45
|
+
timeout?: number,
|
|
45
46
|
): void {
|
|
46
|
-
this.router.register(
|
|
47
|
+
this.router.register(
|
|
48
|
+
method,
|
|
49
|
+
path,
|
|
50
|
+
handler,
|
|
51
|
+
middlewares,
|
|
52
|
+
controllerClass,
|
|
53
|
+
methodName,
|
|
54
|
+
timeout,
|
|
55
|
+
);
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
/**
|
package/src/router/route.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Constructor } from '../core/types';
|
|
|
3
3
|
import type { HttpMethod, RouteHandler, RouteMatch } from './types';
|
|
4
4
|
import { MiddlewarePipeline } from '../middleware/pipeline';
|
|
5
5
|
import type { Middleware } from '../middleware';
|
|
6
|
+
import { HttpException } from '../error';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 路由类
|
|
@@ -47,6 +48,7 @@ export class Route {
|
|
|
47
48
|
private readonly middlewarePipeline: MiddlewarePipeline | null;
|
|
48
49
|
private readonly staticKey?: string;
|
|
49
50
|
public readonly isStatic: boolean;
|
|
51
|
+
private readonly timeout?: number;
|
|
50
52
|
|
|
51
53
|
public constructor(
|
|
52
54
|
method: HttpMethod,
|
|
@@ -55,12 +57,14 @@ export class Route {
|
|
|
55
57
|
middlewares: Middleware[] = [],
|
|
56
58
|
controllerClass?: Constructor<unknown>,
|
|
57
59
|
methodName?: string,
|
|
60
|
+
timeout?: number,
|
|
58
61
|
) {
|
|
59
62
|
this.method = method;
|
|
60
63
|
this.path = path;
|
|
61
64
|
this.handler = handler;
|
|
62
65
|
this.controllerClass = controllerClass;
|
|
63
66
|
this.methodName = methodName;
|
|
67
|
+
this.timeout = timeout;
|
|
64
68
|
|
|
65
69
|
this.isStatic = !path.includes(':') && !path.includes('*');
|
|
66
70
|
if (this.isStatic) {
|
|
@@ -124,11 +128,35 @@ export class Route {
|
|
|
124
128
|
* @returns 响应对象
|
|
125
129
|
*/
|
|
126
130
|
public async execute(context: Context): Promise<Response> {
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
const executeHandler = async (): Promise<Response> => {
|
|
132
|
+
if (!this.middlewarePipeline || !this.middlewarePipeline.hasMiddlewares()) {
|
|
133
|
+
return await this.handler(context);
|
|
134
|
+
}
|
|
135
|
+
return await this.middlewarePipeline.run(
|
|
136
|
+
context,
|
|
137
|
+
async () => await this.handler(context),
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (!this.timeout || this.timeout <= 0) {
|
|
142
|
+
return await executeHandler();
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
|
|
145
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
146
|
+
const timeoutPromise = new Promise<Response>((_, reject) => {
|
|
147
|
+
timer = setTimeout(
|
|
148
|
+
() => reject(new HttpException(408, 'Request Timeout')),
|
|
149
|
+
this.timeout,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
return await Promise.race([executeHandler(), timeoutPromise]);
|
|
155
|
+
} finally {
|
|
156
|
+
if (timer) {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
132
160
|
}
|
|
133
161
|
|
|
134
162
|
/**
|
package/src/router/router.ts
CHANGED
|
@@ -49,10 +49,19 @@ export class Router {
|
|
|
49
49
|
middlewares: Middleware[] = [],
|
|
50
50
|
controllerClass?: Constructor<unknown>,
|
|
51
51
|
methodName?: string,
|
|
52
|
+
timeout?: number,
|
|
52
53
|
): void {
|
|
53
54
|
// 规范化路径
|
|
54
55
|
const normalizedPath = this.normalizePath(path);
|
|
55
|
-
const route = new Route(
|
|
56
|
+
const route = new Route(
|
|
57
|
+
method,
|
|
58
|
+
normalizedPath,
|
|
59
|
+
handler,
|
|
60
|
+
middlewares,
|
|
61
|
+
controllerClass,
|
|
62
|
+
methodName,
|
|
63
|
+
timeout,
|
|
64
|
+
);
|
|
56
65
|
this.routes.push(route);
|
|
57
66
|
const staticKey = route.getStaticKey();
|
|
58
67
|
if (staticKey) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
import type { Constructor } from '../core/types';
|
|
4
|
+
|
|
5
|
+
export const IDLE_TIMEOUT_KEY = Symbol('@dangao/bun-server:route:idle-timeout');
|
|
6
|
+
|
|
7
|
+
export function IdleTimeout(ms: number): MethodDecorator & ClassDecorator {
|
|
8
|
+
return (target: object, propertyKey?: string | symbol) => {
|
|
9
|
+
if (propertyKey !== undefined) {
|
|
10
|
+
Reflect.defineMetadata(IDLE_TIMEOUT_KEY, ms, target, propertyKey);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
Reflect.defineMetadata(IDLE_TIMEOUT_KEY, ms, target);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getIdleTimeout(
|
|
18
|
+
controllerClass: Constructor<unknown>,
|
|
19
|
+
methodName: string,
|
|
20
|
+
): number | undefined {
|
|
21
|
+
const methodTimeout = Reflect.getMetadata(
|
|
22
|
+
IDLE_TIMEOUT_KEY,
|
|
23
|
+
controllerClass.prototype,
|
|
24
|
+
methodName,
|
|
25
|
+
) as number | undefined;
|
|
26
|
+
if (typeof methodTimeout === 'number') {
|
|
27
|
+
return methodTimeout;
|
|
28
|
+
}
|
|
29
|
+
const classTimeout = Reflect.getMetadata(
|
|
30
|
+
IDLE_TIMEOUT_KEY,
|
|
31
|
+
controllerClass,
|
|
32
|
+
) as number | undefined;
|
|
33
|
+
return typeof classTimeout === 'number' ? classTimeout : undefined;
|
|
34
|
+
}
|
|
35
|
+
|