@dangao/bun-server 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -3
- package/dist/ai/providers/anthropic-provider.d.ts.map +1 -1
- package/dist/ai/providers/google-provider.d.ts.map +1 -1
- package/dist/ai/providers/ollama-provider.d.ts.map +1 -1
- package/dist/ai/providers/openai-provider.d.ts.map +1 -1
- package/dist/ai/service.d.ts.map +1 -1
- package/dist/ai/types.d.ts +5 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/config/service.d.ts +0 -1
- package/dist/config/service.d.ts.map +1 -1
- package/dist/core/application.d.ts +30 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/context.d.ts +5 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/server.d.ts +29 -9
- package/dist/core/server.d.ts.map +1 -1
- package/dist/dashboard/controller.d.ts.map +1 -1
- package/dist/database/connection-pool.d.ts +3 -3
- package/dist/database/connection-pool.d.ts.map +1 -1
- package/dist/database/sql-manager.d.ts +8 -4
- package/dist/database/sql-manager.d.ts.map +1 -1
- package/dist/database/sqlite-adapter.d.ts +7 -3
- package/dist/database/sqlite-adapter.d.ts.map +1 -1
- package/dist/debug/recorder.d.ts +0 -1
- package/dist/debug/recorder.d.ts.map +1 -1
- package/dist/files/static-middleware.d.ts.map +1 -1
- package/dist/files/storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40335 -3523
- package/dist/index.node.mjs +17689 -0
- package/dist/mcp/server.d.ts +5 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/middleware/builtin/static-file.d.ts +4 -2
- package/dist/middleware/builtin/static-file.d.ts.map +1 -1
- package/dist/platform/bun/crypto.d.ts +3 -0
- package/dist/platform/bun/crypto.d.ts.map +1 -0
- package/dist/platform/bun/fs.d.ts +3 -0
- package/dist/platform/bun/fs.d.ts.map +1 -0
- package/dist/platform/bun/http.d.ts +15 -0
- package/dist/platform/bun/http.d.ts.map +1 -0
- package/dist/platform/bun/index.d.ts +3 -0
- package/dist/platform/bun/index.d.ts.map +1 -0
- package/dist/platform/bun/parser.d.ts +3 -0
- package/dist/platform/bun/parser.d.ts.map +1 -0
- package/dist/platform/bun/process.d.ts +3 -0
- package/dist/platform/bun/process.d.ts.map +1 -0
- package/dist/platform/detector.d.ts +9 -0
- package/dist/platform/detector.d.ts.map +1 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/node/crypto.d.ts +3 -0
- package/dist/platform/node/crypto.d.ts.map +1 -0
- package/dist/platform/node/fs.d.ts +3 -0
- package/dist/platform/node/fs.d.ts.map +1 -0
- package/dist/platform/node/http.d.ts +3 -0
- package/dist/platform/node/http.d.ts.map +1 -0
- package/dist/platform/node/index.d.ts +3 -0
- package/dist/platform/node/index.d.ts.map +1 -0
- package/dist/platform/node/parser.d.ts +3 -0
- package/dist/platform/node/parser.d.ts.map +1 -0
- package/dist/platform/node/process.d.ts +3 -0
- package/dist/platform/node/process.d.ts.map +1 -0
- package/dist/platform/runtime.d.ts +14 -0
- package/dist/platform/runtime.d.ts.map +1 -0
- package/dist/platform/types.d.ts +139 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/prompt/stores/file-store.d.ts.map +1 -1
- package/dist/rag/service.d.ts.map +1 -1
- package/dist/request/response.d.ts +3 -1
- package/dist/request/response.d.ts.map +1 -1
- package/dist/security/guards/execution-context.d.ts +2 -2
- package/dist/security/guards/execution-context.d.ts.map +1 -1
- package/dist/security/guards/types.d.ts +2 -2
- package/dist/security/guards/types.d.ts.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/websocket/registry.d.ts +4 -4
- package/dist/websocket/registry.d.ts.map +1 -1
- package/docs/deployment.md +31 -7
- package/docs/design/query-interceptor-design.md +381 -0
- package/docs/idle-timeout.md +101 -8
- package/docs/migration.md +43 -0
- package/docs/platform.md +299 -0
- package/docs/testing.md +60 -0
- package/docs/zh/deployment.md +30 -7
- package/docs/zh/idle-timeout.md +99 -6
- package/docs/zh/migration.md +42 -0
- package/docs/zh/platform.md +299 -0
- package/docs/zh/testing.md +60 -0
- package/package.json +24 -6
- package/src/ai/providers/anthropic-provider.ts +5 -2
- package/src/ai/providers/google-provider.ts +3 -0
- package/src/ai/providers/ollama-provider.ts +3 -0
- package/src/ai/providers/openai-provider.ts +5 -2
- package/src/ai/service.ts +17 -5
- package/src/ai/types.ts +5 -0
- package/src/auth/jwt.ts +4 -3
- package/src/config/service.ts +7 -6
- package/src/core/application.ts +38 -1
- package/src/core/cluster.ts +16 -14
- package/src/core/context.ts +7 -0
- package/src/core/server.ts +162 -46
- package/src/dashboard/controller.ts +3 -2
- package/src/database/connection-pool.ts +32 -20
- package/src/database/database-module.ts +1 -1
- package/src/database/db-proxy.ts +2 -2
- package/src/database/orm/transaction-manager.ts +1 -1
- package/src/database/sql-manager.ts +48 -13
- package/src/database/sqlite-adapter.ts +45 -12
- package/src/debug/recorder.ts +4 -3
- package/src/files/static-middleware.ts +3 -2
- package/src/files/storage.ts +2 -1
- package/src/index.ts +13 -0
- package/src/mcp/server.ts +6 -15
- package/src/middleware/builtin/static-file.ts +8 -5
- package/src/platform/bun/crypto.ts +30 -0
- package/src/platform/bun/fs.ts +52 -0
- package/src/platform/bun/http.ts +106 -0
- package/src/platform/bun/index.ts +17 -0
- package/src/platform/bun/parser.ts +19 -0
- package/src/platform/bun/process.ts +37 -0
- package/src/platform/detector.ts +36 -0
- package/src/platform/index.ts +20 -0
- package/src/platform/node/crypto.ts +40 -0
- package/src/platform/node/fs.ts +115 -0
- package/src/platform/node/http.ts +196 -0
- package/src/platform/node/index.ts +17 -0
- package/src/platform/node/parser.ts +34 -0
- package/src/platform/node/process.ts +51 -0
- package/src/platform/runtime.ts +50 -0
- package/src/platform/types.ts +150 -0
- package/src/prompt/stores/file-store.ts +6 -5
- package/src/rag/service.ts +2 -1
- package/src/request/response.ts +7 -4
- package/src/security/guards/execution-context.ts +4 -4
- package/src/security/guards/types.ts +2 -2
- package/src/swagger/generator.ts +2 -1
- package/src/websocket/registry.ts +6 -7
- package/tests/controller/path-combination.test.ts +196 -2
- package/tests/files/static-middleware.test.ts +5 -2
- package/tests/middleware/static-file.test.ts +5 -2
- package/tests/platform/bun/crypto.test.ts +8 -0
- package/tests/platform/bun/database.test.ts +8 -0
- package/tests/platform/bun/fs.test.ts +8 -0
- package/tests/platform/bun/parser.test.ts +8 -0
- package/tests/platform/bun/process.test.ts +8 -0
- package/tests/platform/bun/websocket.test.ts +8 -0
- package/tests/platform/detector.test.ts +57 -0
- package/tests/platform/node/build-smoke.test.ts +92 -0
- package/tests/platform/node/crypto.test.ts +9 -0
- package/tests/platform/node/database.test.ts +9 -0
- package/tests/platform/node/fs.test.ts +9 -0
- package/tests/platform/node/parser.test.ts +9 -0
- package/tests/platform/node/process.test.ts +9 -0
- package/tests/platform/node/websocket.test.ts +9 -0
- package/tests/platform/shared/crypto.cases.ts +49 -0
- package/tests/platform/shared/database.cases.ts +43 -0
- package/tests/platform/shared/fs.cases.ts +82 -0
- package/tests/platform/shared/parser.cases.ts +55 -0
- package/tests/platform/shared/process.cases.ts +26 -0
- package/tests/platform/shared/suite.ts +33 -0
- package/tests/platform/shared/websocket.cases.ts +61 -0
- package/tests/request/response.test.ts +5 -2
- package/tests/router/router-extended.test.ts +53 -0
package/src/database/db-proxy.ts
CHANGED
|
@@ -49,10 +49,10 @@ const baseDb = async (
|
|
|
49
49
|
if (tenantId) {
|
|
50
50
|
const tenantSql = sqlManager.get(tenantId);
|
|
51
51
|
if (tenantSql) {
|
|
52
|
-
return await tenantSql(strings, ...values);
|
|
52
|
+
return await (tenantSql as any)(strings, ...values);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
return await sqlManager.getDefault()(strings, ...values);
|
|
55
|
+
return await (sqlManager.getDefault() as any)(strings, ...values);
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
function createDb(tenantId?: string): DbProxy {
|
|
@@ -86,7 +86,7 @@ export class TransactionManager {
|
|
|
86
86
|
fn: () => Promise<T>,
|
|
87
87
|
options: TransactionOptions = {},
|
|
88
88
|
): Promise<T> {
|
|
89
|
-
const reserved = await this.sqlManager.getDefault().reserve();
|
|
89
|
+
const reserved = await (this.sqlManager.getDefault() as any).reserve();
|
|
90
90
|
try {
|
|
91
91
|
const tenantId = getCurrentSession()?.tenantId ?? 'default';
|
|
92
92
|
return await runWithSession(
|
|
@@ -1,24 +1,55 @@
|
|
|
1
|
-
import { SQL } from 'bun';
|
|
2
|
-
|
|
3
1
|
import type { BunSQLConfig } from './types';
|
|
2
|
+
import { getRuntime } from '../platform/runtime';
|
|
4
3
|
|
|
4
|
+
/**
|
|
5
|
+
* SQL 连接管理器
|
|
6
|
+
* 在 Bun 平台下使用 Bun.SQL,在 Node.js 平台下使用 postgres/mysql2
|
|
7
|
+
* 内部自动感知运行时,用户无需关心底层实现
|
|
8
|
+
*/
|
|
5
9
|
export class BunSQLManager {
|
|
6
|
-
private readonly instances = new Map<string,
|
|
10
|
+
private readonly instances = new Map<string, unknown>();
|
|
7
11
|
private defaultTenantId = 'default';
|
|
8
12
|
|
|
9
|
-
public getOrCreate(tenantId: string, config: BunSQLConfig):
|
|
13
|
+
public getOrCreate(tenantId: string, config: BunSQLConfig): unknown {
|
|
10
14
|
const existing = this.instances.get(tenantId);
|
|
11
15
|
if (existing) {
|
|
12
16
|
return existing;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
const pool = config.pool ?? {};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
let sql: unknown;
|
|
21
|
+
|
|
22
|
+
if (getRuntime().engine === 'bun') {
|
|
23
|
+
const { SQL } = require('bun') as typeof import('bun');
|
|
24
|
+
sql = new SQL(config.url, {
|
|
25
|
+
max: pool.max ?? 10,
|
|
26
|
+
idleTimeout: pool.idleTimeout ?? 30,
|
|
27
|
+
maxLifetime: pool.maxLifetime ?? 0,
|
|
28
|
+
connectionTimeout: pool.connectionTimeout ?? 30000,
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
// Node.js: detect dialect from URL
|
|
32
|
+
const url = config.url.toLowerCase();
|
|
33
|
+
if (url.startsWith('mysql://') || url.startsWith('mysql2://')) {
|
|
34
|
+
const mysql2 = require('mysql2/promise') as typeof import('mysql2/promise');
|
|
35
|
+
// Create a pool in Node.js
|
|
36
|
+
sql = mysql2.createPool({
|
|
37
|
+
uri: config.url,
|
|
38
|
+
connectionLimit: pool.max ?? 10,
|
|
39
|
+
waitForConnections: true,
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
// postgres (default)
|
|
43
|
+
const postgres = require('postgres') as typeof import('postgres');
|
|
44
|
+
sql = postgres(config.url, {
|
|
45
|
+
max: pool.max ?? 10,
|
|
46
|
+
idle_timeout: pool.idleTimeout ?? 30,
|
|
47
|
+
max_lifetime: pool.maxLifetime ?? 0,
|
|
48
|
+
connect_timeout: (pool.connectionTimeout ?? 30000) / 1000,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
this.instances.set(tenantId, sql);
|
|
23
54
|
return sql;
|
|
24
55
|
}
|
|
@@ -27,7 +58,7 @@ export class BunSQLManager {
|
|
|
27
58
|
return this.instances.has(tenantId);
|
|
28
59
|
}
|
|
29
60
|
|
|
30
|
-
public get(tenantId: string):
|
|
61
|
+
public get(tenantId: string): unknown {
|
|
31
62
|
return this.instances.get(tenantId);
|
|
32
63
|
}
|
|
33
64
|
|
|
@@ -35,7 +66,7 @@ export class BunSQLManager {
|
|
|
35
66
|
this.defaultTenantId = tenantId;
|
|
36
67
|
}
|
|
37
68
|
|
|
38
|
-
public getDefault():
|
|
69
|
+
public getDefault(): unknown {
|
|
39
70
|
const sql = this.instances.get(this.defaultTenantId);
|
|
40
71
|
if (!sql) {
|
|
41
72
|
throw new Error(
|
|
@@ -50,7 +81,11 @@ export class BunSQLManager {
|
|
|
50
81
|
if (!sql) {
|
|
51
82
|
return;
|
|
52
83
|
}
|
|
53
|
-
|
|
84
|
+
if (typeof (sql as any).close === 'function') {
|
|
85
|
+
await (sql as any).close({ timeout });
|
|
86
|
+
} else if (typeof (sql as any).end === 'function') {
|
|
87
|
+
await (sql as any).end();
|
|
88
|
+
}
|
|
54
89
|
this.instances.delete(tenantId);
|
|
55
90
|
}
|
|
56
91
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Database, type SQLQueryBindings } from 'bun:sqlite';
|
|
2
|
-
|
|
3
1
|
import type { SqliteV2Config } from './types';
|
|
2
|
+
import { getRuntime } from '../platform/runtime';
|
|
4
3
|
|
|
5
4
|
export interface DisposableLock {
|
|
6
5
|
[Symbol.dispose](): void;
|
|
@@ -39,30 +38,64 @@ export class Semaphore {
|
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
/**
|
|
42
|
+
* SQLite 适配器(自动感知运行时)
|
|
43
|
+
* Bun 平台下使用 bun:sqlite,Node.js 平台下使用 better-sqlite3
|
|
44
|
+
*/
|
|
42
45
|
export class SqliteAdapter {
|
|
43
|
-
private readonly db:
|
|
46
|
+
private readonly db: unknown;
|
|
44
47
|
public readonly semaphore: Semaphore;
|
|
48
|
+
private readonly isBun: boolean;
|
|
45
49
|
|
|
46
50
|
public constructor(config: SqliteV2Config) {
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
this.isBun = getRuntime().engine === 'bun';
|
|
52
|
+
|
|
53
|
+
if (this.isBun) {
|
|
54
|
+
const { Database } = require('bun:sqlite') as typeof import('bun:sqlite');
|
|
55
|
+
const db = new Database(config.database);
|
|
56
|
+
if (config.wal !== false) {
|
|
57
|
+
db.exec('PRAGMA journal_mode = WAL;');
|
|
58
|
+
}
|
|
59
|
+
this.db = db;
|
|
60
|
+
} else {
|
|
61
|
+
const BetterSqlite3 = require('better-sqlite3') as typeof import('better-sqlite3');
|
|
62
|
+
const db = BetterSqlite3(config.database);
|
|
63
|
+
if (config.wal !== false) {
|
|
64
|
+
db.exec('PRAGMA journal_mode = WAL;');
|
|
65
|
+
}
|
|
66
|
+
this.db = db;
|
|
50
67
|
}
|
|
68
|
+
|
|
51
69
|
this.semaphore = new Semaphore(config.maxWriteConcurrency ?? 1);
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
public query<T = unknown>(sql: string, params:
|
|
55
|
-
|
|
72
|
+
public query<T = unknown>(sql: string, params: unknown[] = []): T[] {
|
|
73
|
+
if (this.isBun) {
|
|
74
|
+
const db = this.db as import('bun:sqlite').Database;
|
|
75
|
+
const stmt = db.query(sql);
|
|
76
|
+
return stmt.all(...params as Parameters<typeof stmt.all>) as T[];
|
|
77
|
+
}
|
|
78
|
+
const db = this.db as import('better-sqlite3').Database;
|
|
79
|
+
const stmt = db.prepare(sql);
|
|
56
80
|
return stmt.all(...params) as T[];
|
|
57
81
|
}
|
|
58
82
|
|
|
59
|
-
public async execute(sql: string, params:
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
public async execute(sql: string, params: unknown[] = []): Promise<void> {
|
|
84
|
+
if (this.isBun) {
|
|
85
|
+
const db = this.db as import('bun:sqlite').Database;
|
|
86
|
+
const stmt = db.query(sql);
|
|
87
|
+
stmt.run(...params as Parameters<typeof stmt.run>);
|
|
88
|
+
} else {
|
|
89
|
+
const db = this.db as import('better-sqlite3').Database;
|
|
90
|
+
const stmt = db.prepare(sql);
|
|
91
|
+
stmt.run(...params);
|
|
92
|
+
}
|
|
62
93
|
}
|
|
63
94
|
|
|
64
95
|
public close(): void {
|
|
65
|
-
this.db.close
|
|
96
|
+
if (typeof (this.db as any).close === 'function') {
|
|
97
|
+
(this.db as any).close();
|
|
98
|
+
}
|
|
66
99
|
}
|
|
67
100
|
}
|
|
68
101
|
|
package/src/debug/recorder.ts
CHANGED
|
@@ -110,11 +110,11 @@ export class RequestRecorder {
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* 从 JSONL 内容导入请求记录
|
|
113
|
-
* 利用 Bun 1.3.7+ 原生 Bun.JSONL.parse() 高性能解析
|
|
114
113
|
* @param content - JSONL 格式文本
|
|
115
114
|
*/
|
|
116
115
|
public importFromJsonl(content: string): void {
|
|
117
|
-
const
|
|
116
|
+
const { getRuntime } = require('../platform/runtime') as typeof import('../platform/runtime');
|
|
117
|
+
const records = getRuntime().parser.parseJSONL(content) as RequestRecord[];
|
|
118
118
|
for (const record of records) {
|
|
119
119
|
if (record.id) {
|
|
120
120
|
const oldRecord = this.buffer[this.writeIndex];
|
|
@@ -136,6 +136,7 @@ export class RequestRecorder {
|
|
|
136
136
|
* @param content - JSONL 格式文本
|
|
137
137
|
*/
|
|
138
138
|
public static parseJsonl(content: string): RequestRecord[] {
|
|
139
|
-
|
|
139
|
+
const { getRuntime } = require('../platform/runtime') as typeof import('../platform/runtime');
|
|
140
|
+
return getRuntime().parser.parseJSONL(content) as RequestRecord[];
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -2,6 +2,7 @@ import { join, normalize } from 'node:path';
|
|
|
2
2
|
import { stat } from 'node:fs/promises';
|
|
3
3
|
|
|
4
4
|
import type { Middleware } from '../middleware';
|
|
5
|
+
import { getRuntime } from '../platform/runtime';
|
|
5
6
|
|
|
6
7
|
export interface StaticFileOptions {
|
|
7
8
|
root: string;
|
|
@@ -35,8 +36,8 @@ export function createStaticFileMiddleware(options: StaticFileOptions): Middlewa
|
|
|
35
36
|
return await next();
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const file =
|
|
39
|
-
return new Response(file, {
|
|
39
|
+
const file = getRuntime().fs.file(filePath);
|
|
40
|
+
return new Response(file.stream(), {
|
|
40
41
|
headers: {
|
|
41
42
|
'Content-Type': file.type || 'application/octet-stream',
|
|
42
43
|
},
|
package/src/files/storage.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { constants } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import type { UploadedFileInfo } from './types';
|
|
6
|
+
import { getRuntime } from '../platform/runtime';
|
|
6
7
|
|
|
7
8
|
export interface SaveFileOptions {
|
|
8
9
|
dest: string;
|
|
@@ -51,7 +52,7 @@ export class FileStorage {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
await
|
|
55
|
+
await getRuntime().fs.write(targetPath, await file.arrayBuffer());
|
|
55
56
|
|
|
56
57
|
return {
|
|
57
58
|
fieldName,
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export { Application, type ApplicationOptions } from './core/application';
|
|
2
|
+
export type {
|
|
3
|
+
PlatformEngine,
|
|
4
|
+
IServerHandle,
|
|
5
|
+
IWebSocket,
|
|
6
|
+
WebSocketHandlers,
|
|
7
|
+
IFsAdapter,
|
|
8
|
+
ICryptoAdapter,
|
|
9
|
+
IParserAdapter,
|
|
10
|
+
IProcessAdapter,
|
|
11
|
+
IHttpDriver,
|
|
12
|
+
IPlatform,
|
|
13
|
+
} from './platform';
|
|
14
|
+
export { getRuntime, initRuntime, resolvePlatform } from './platform';
|
|
2
15
|
export { applyDecorators } from './core/apply-decorators';
|
|
3
16
|
export { BunServer, type ServerOptions } from './core/server';
|
|
4
17
|
export { ClusterManager, type ClusterOptions, type ClusterMode } from './core/cluster';
|
package/src/mcp/server.ts
CHANGED
|
@@ -48,31 +48,22 @@ export class McpServer {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Create an SSE response that keeps the connection open for streaming
|
|
52
|
-
*
|
|
51
|
+
* Create an SSE response that keeps the connection open for streaming.
|
|
52
|
+
*
|
|
53
|
+
* Heartbeat / keep-alive pings are handled automatically by the framework's
|
|
54
|
+
* SSE post-processor (`sseKeepAlive`), so this method no longer injects its
|
|
55
|
+
* own `setInterval`.
|
|
53
56
|
*/
|
|
54
57
|
public createSseResponse(): Response {
|
|
55
|
-
const registry = this.registry;
|
|
56
|
-
const serverInfo = this.serverInfo;
|
|
57
58
|
const encoder = new TextEncoder();
|
|
58
59
|
|
|
59
60
|
const stream = new ReadableStream({
|
|
60
61
|
start(controller) {
|
|
61
|
-
// Send initial server info event
|
|
62
62
|
const initEvent = `event: endpoint\ndata: ${JSON.stringify({
|
|
63
63
|
type: 'endpoint',
|
|
64
64
|
method: 'POST',
|
|
65
65
|
})}\n\n`;
|
|
66
66
|
controller.enqueue(encoder.encode(initEvent));
|
|
67
|
-
|
|
68
|
-
// Keep connection alive with periodic pings
|
|
69
|
-
const pingInterval = setInterval(() => {
|
|
70
|
-
try {
|
|
71
|
-
controller.enqueue(encoder.encode(': ping\n\n'));
|
|
72
|
-
} catch (_error) {
|
|
73
|
-
clearInterval(pingInterval);
|
|
74
|
-
}
|
|
75
|
-
}, 15000);
|
|
76
67
|
},
|
|
77
68
|
});
|
|
78
69
|
|
|
@@ -81,7 +72,7 @@ export class McpServer {
|
|
|
81
72
|
'Content-Type': 'text/event-stream',
|
|
82
73
|
'Cache-Control': 'no-cache',
|
|
83
74
|
'Connection': 'keep-alive',
|
|
84
|
-
'X-MCP-Server': `${serverInfo.name}/${serverInfo.version}`,
|
|
75
|
+
'X-MCP-Server': `${this.serverInfo.name}/${this.serverInfo.version}`,
|
|
85
76
|
},
|
|
86
77
|
});
|
|
87
78
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { normalize, resolve, sep } from 'path';
|
|
2
2
|
|
|
3
3
|
import type { Middleware } from '../middleware';
|
|
4
|
-
import
|
|
4
|
+
import { getRuntime } from '../../platform/runtime';
|
|
5
|
+
|
|
6
|
+
/** Cross-runtime compatible headers input type */
|
|
7
|
+
type HeadersInput = Headers | string[][] | Record<string, string>;
|
|
5
8
|
|
|
6
9
|
export interface StaticFileOptions {
|
|
7
10
|
root: string;
|
|
8
11
|
prefix?: string;
|
|
9
12
|
indexFile?: string;
|
|
10
13
|
enableCache?: boolean;
|
|
11
|
-
headers?:
|
|
14
|
+
headers?: HeadersInput;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
function isSubPath(root: string, target: string): boolean {
|
|
@@ -65,12 +68,12 @@ export function createStaticFileMiddleware(options: StaticFileOptions): Middlewa
|
|
|
65
68
|
return context.createErrorResponse({ error: 'Forbidden' });
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
const file =
|
|
71
|
+
const file = getRuntime().fs.file(targetPath);
|
|
69
72
|
if (!(await file.exists())) {
|
|
70
73
|
return await next();
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
const headers = new Headers(options.headers);
|
|
76
|
+
const headers = new Headers(options.headers as HeadersInput);
|
|
74
77
|
if (enableCache) {
|
|
75
78
|
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
|
76
79
|
}
|
|
@@ -78,7 +81,7 @@ export function createStaticFileMiddleware(options: StaticFileOptions): Middlewa
|
|
|
78
81
|
headers.set('Content-Type', file.type);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
return new Response(file, {
|
|
84
|
+
return new Response(file.stream(), {
|
|
82
85
|
status: 200,
|
|
83
86
|
headers,
|
|
84
87
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ICryptoAdapter, IHasher } from '../types';
|
|
2
|
+
|
|
3
|
+
class BunHasher implements IHasher {
|
|
4
|
+
private readonly hasher: Bun.CryptoHasher;
|
|
5
|
+
|
|
6
|
+
public constructor(algorithm: string) {
|
|
7
|
+
this.hasher = new Bun.CryptoHasher(algorithm as any);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public update(data: string | Uint8Array | ArrayBuffer): IHasher {
|
|
11
|
+
this.hasher.update(data as Parameters<Bun.CryptoHasher['update']>[0]);
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public digest(): Uint8Array;
|
|
16
|
+
public digest(encoding: 'hex' | 'base64' | 'base64url'): string;
|
|
17
|
+
public digest(encoding?: 'hex' | 'base64' | 'base64url'): Uint8Array | string {
|
|
18
|
+
if (encoding) {
|
|
19
|
+
return this.hasher.digest(encoding) as string;
|
|
20
|
+
}
|
|
21
|
+
// Bun.CryptoHasher.digest() returns Buffer which extends Uint8Array
|
|
22
|
+
return this.hasher.digest() as unknown as Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const bunCryptoAdapter: ICryptoAdapter = {
|
|
27
|
+
createHasher(algorithm: string): IHasher {
|
|
28
|
+
return new BunHasher(algorithm);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { IFsAdapter, IFileRef } from '../types';
|
|
2
|
+
|
|
3
|
+
class BunFileRef implements IFileRef {
|
|
4
|
+
private readonly bunFile: ReturnType<typeof Bun.file>;
|
|
5
|
+
|
|
6
|
+
public constructor(bunFile: ReturnType<typeof Bun.file>) {
|
|
7
|
+
this.bunFile = bunFile;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get type(): string {
|
|
11
|
+
return this.bunFile.type;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public get size(): number {
|
|
15
|
+
return this.bunFile.size;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public text(): Promise<string> {
|
|
19
|
+
return this.bunFile.text();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public bytes(): Promise<Uint8Array> {
|
|
23
|
+
return this.bunFile.bytes();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public arrayBuffer(): Promise<ArrayBuffer> {
|
|
27
|
+
return this.bunFile.arrayBuffer();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public exists(): Promise<boolean> {
|
|
31
|
+
return this.bunFile.exists();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public stream(): ReadableStream<Uint8Array> {
|
|
35
|
+
return this.bunFile.stream() as ReadableStream<Uint8Array>;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const bunFsAdapter: IFsAdapter = {
|
|
40
|
+
file(path: string): IFileRef {
|
|
41
|
+
return new BunFileRef(Bun.file(path));
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async write(path: string, data: string | Uint8Array | ArrayBuffer): Promise<void> {
|
|
45
|
+
await Bun.write(path, data as any);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
glob(pattern: string, cwd?: string): string[] {
|
|
49
|
+
const g = new Bun.Glob(pattern);
|
|
50
|
+
return Array.from(g.scanSync(cwd ?? '.'));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { IHttpDriver, IServerHandle, HttpServeOptions, IWebSocket, WebSocketHandlers } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bun ServerWebSocket 的 IWebSocket 包装
|
|
5
|
+
*/
|
|
6
|
+
export class BunWebSocket<T> implements IWebSocket<T> {
|
|
7
|
+
public constructor(private readonly ws: import('bun').ServerWebSocket<T>) {}
|
|
8
|
+
|
|
9
|
+
public get data(): T {
|
|
10
|
+
return this.ws.data;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public get readyState(): number {
|
|
14
|
+
return this.ws.readyState;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public send(data: string | Buffer | Uint8Array): void {
|
|
18
|
+
this.ws.send(data as string);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public close(code?: number, reason?: string): void {
|
|
22
|
+
this.ws.close(code, reason);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public getNative(): import('bun').ServerWebSocket<T> {
|
|
26
|
+
return this.ws;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class BunServerHandle implements IServerHandle {
|
|
31
|
+
public constructor(private readonly server: import('bun').Server<unknown>) {}
|
|
32
|
+
|
|
33
|
+
public get port(): number {
|
|
34
|
+
return this.server.port ?? 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public get hostname(): string | undefined {
|
|
38
|
+
return this.server.hostname ?? undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public stop(): void {
|
|
42
|
+
this.server.stop();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public upgrade(request: Request, options?: { data?: unknown }): boolean {
|
|
46
|
+
return this.server.upgrade(request, options as any);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public timeout(request: Request, seconds: number): void {
|
|
50
|
+
this.server.timeout(request, seconds);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public getNative(): unknown {
|
|
54
|
+
return this.server;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildWebSocketHandlers<T>(
|
|
59
|
+
handlers?: WebSocketHandlers<T>,
|
|
60
|
+
): import('bun').WebSocketHandler<T> | undefined {
|
|
61
|
+
if (!handlers) return undefined;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
open: handlers.open
|
|
65
|
+
? (ws) => handlers.open!(new BunWebSocket(ws) as IWebSocket<T>)
|
|
66
|
+
: undefined,
|
|
67
|
+
message: handlers.message
|
|
68
|
+
? (ws, msg) => handlers.message!(new BunWebSocket(ws) as IWebSocket<T>, msg as string | Buffer)
|
|
69
|
+
: undefined,
|
|
70
|
+
close: handlers.close
|
|
71
|
+
? (ws, code, reason) => handlers.close!(new BunWebSocket(ws) as IWebSocket<T>, code, reason)
|
|
72
|
+
: undefined,
|
|
73
|
+
} as import('bun').WebSocketHandler<T>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const bunHttpAdapter: IHttpDriver = {
|
|
77
|
+
async serve<T>(options: HttpServeOptions<T>): Promise<IServerHandle> {
|
|
78
|
+
const wsHandlers = buildWebSocketHandlers(options.websocket);
|
|
79
|
+
|
|
80
|
+
const fetchHandler = (request: Request, bunServer: import('bun').Server<unknown>): Response | Promise<Response | undefined> | undefined => {
|
|
81
|
+
const handle = new BunServerHandle(bunServer);
|
|
82
|
+
return options.fetch(request, handle);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let server: import('bun').Server<unknown>;
|
|
86
|
+
|
|
87
|
+
if (options.unix) {
|
|
88
|
+
server = Bun.serve({
|
|
89
|
+
unix: options.unix,
|
|
90
|
+
fetch: fetchHandler,
|
|
91
|
+
websocket: wsHandlers,
|
|
92
|
+
} as any);
|
|
93
|
+
} else {
|
|
94
|
+
server = Bun.serve({
|
|
95
|
+
port: options.port ?? 3000,
|
|
96
|
+
hostname: options.hostname,
|
|
97
|
+
reusePort: options.reusePort,
|
|
98
|
+
idleTimeout: options.idleTimeout,
|
|
99
|
+
fetch: fetchHandler,
|
|
100
|
+
websocket: wsHandlers,
|
|
101
|
+
} as any);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new BunServerHandle(server);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IPlatform } from '../types';
|
|
2
|
+
import { bunFsAdapter } from './fs';
|
|
3
|
+
import { bunCryptoAdapter } from './crypto';
|
|
4
|
+
import { bunParserAdapter } from './parser';
|
|
5
|
+
import { bunProcessAdapter } from './process';
|
|
6
|
+
import { bunHttpAdapter } from './http';
|
|
7
|
+
|
|
8
|
+
export function createBunPlatform(): IPlatform {
|
|
9
|
+
return {
|
|
10
|
+
engine: 'bun',
|
|
11
|
+
fs: bunFsAdapter,
|
|
12
|
+
crypto: bunCryptoAdapter,
|
|
13
|
+
parser: bunParserAdapter,
|
|
14
|
+
process: bunProcessAdapter,
|
|
15
|
+
http: bunHttpAdapter,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IParserAdapter } from '../types';
|
|
2
|
+
|
|
3
|
+
export const bunParserAdapter: IParserAdapter = {
|
|
4
|
+
parseJSONC(content: string): unknown {
|
|
5
|
+
return Bun.JSONC.parse(content);
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
parseJSON5(content: string): unknown {
|
|
9
|
+
return Bun.JSON5.parse(content);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
parseJSONL(content: string): unknown[] {
|
|
13
|
+
return Bun.JSONL.parse(content) as unknown[];
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
renderMarkdown(md: string): string {
|
|
17
|
+
return Bun.markdown.html(md, { headings: true });
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IProcessAdapter, IChildProcess, SpawnOptions } from '../types';
|
|
2
|
+
|
|
3
|
+
class BunChildProcess implements IChildProcess {
|
|
4
|
+
private readonly child: ReturnType<typeof Bun.spawn>;
|
|
5
|
+
|
|
6
|
+
public constructor(child: ReturnType<typeof Bun.spawn>) {
|
|
7
|
+
this.child = child;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get pid(): number {
|
|
11
|
+
return this.child.pid;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public get exited(): Promise<number | null> {
|
|
15
|
+
return this.child.exited;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public kill(signal?: string | number): void {
|
|
19
|
+
this.child.kill(signal as Parameters<ReturnType<typeof Bun.spawn>['kill']>[0]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const bunProcessAdapter: IProcessAdapter = {
|
|
24
|
+
spawn(options: SpawnOptions): IChildProcess {
|
|
25
|
+
const child = Bun.spawn({
|
|
26
|
+
cmd: options.cmd,
|
|
27
|
+
env: options.env as Record<string, string>,
|
|
28
|
+
stdout: options.stdout ?? 'inherit',
|
|
29
|
+
stderr: options.stderr ?? 'inherit',
|
|
30
|
+
});
|
|
31
|
+
return new BunChildProcess(child);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async sleep(ms: number): Promise<void> {
|
|
35
|
+
await Bun.sleep(ms);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PlatformEngine } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 解析运行时平台引擎
|
|
5
|
+
* 优先级:bootstrap config > CLI arg (--platform=) > env var (BUN_SERVER_PLATFORM) > auto-detect
|
|
6
|
+
*
|
|
7
|
+
* @param bootstrapConfig - 应用启动时显式指定的平台(最高优先级)
|
|
8
|
+
*/
|
|
9
|
+
export function resolvePlatform(bootstrapConfig?: PlatformEngine): PlatformEngine {
|
|
10
|
+
if (bootstrapConfig) {
|
|
11
|
+
return bootstrapConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// CLI arg: --platform=node or --platform=bun
|
|
15
|
+
for (const arg of process.argv) {
|
|
16
|
+
if (arg.startsWith('--platform=')) {
|
|
17
|
+
const value = arg.slice('--platform='.length).trim();
|
|
18
|
+
if (value === 'node' || value === 'bun') {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Env var: BUN_SERVER_PLATFORM=node or BUN_SERVER_PLATFORM=bun
|
|
25
|
+
const envValue = process.env['BUN_SERVER_PLATFORM'];
|
|
26
|
+
if (envValue === 'node' || envValue === 'bun') {
|
|
27
|
+
return envValue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Auto-detect: check if running inside Bun
|
|
31
|
+
if (typeof Bun !== 'undefined') {
|
|
32
|
+
return 'bun';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return 'node';
|
|
36
|
+
}
|