@kevisual/router 0.0.26-alpha.5 → 0.0.27

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.
@@ -1,50 +0,0 @@
1
- import { getRuntime } from '../runtime.ts';
2
-
3
- export const getPid = async () => {
4
- const runtime = getRuntime();
5
-
6
- let pid = 0;
7
- if (runtime.isDeno) {
8
- pid = Deno.pid;
9
- } else {
10
- pid = process.pid;
11
- }
12
- return pid;
13
- };
14
- export const writeAppid = async (pidPath = './app.pid') => {
15
- const fs = await import('node:fs');
16
- const pid = await getPid();
17
- fs.writeFileSync(pidPath, pid + '');
18
- };
19
-
20
- export const getPidFromFileAndStop = async () => {
21
- const fs = await import('node:fs');
22
- if (fs.existsSync('./app.pid')) {
23
- const pid = parseInt(fs.readFileSync('./app.pid', 'utf-8'), 10);
24
- if (!isNaN(pid)) {
25
- if (pid === 0) {
26
- return;
27
- }
28
- try {
29
- process.kill(pid);
30
- console.log(`Stopped process with PID ${pid}`);
31
- } catch (error) {
32
- console.error(`Failed to stop process with PID ${pid}:`, error);
33
- }
34
- }
35
- }
36
- };
37
-
38
- export const runFirstCheck = async (path: string, pidPath: string) => {
39
- await getPidFromFileAndStop();
40
- await writeAppid(pidPath);
41
- try {
42
- const fs = await import('node:fs');
43
- if (fs.existsSync(path)) {
44
- fs.unlinkSync(path);
45
- console.log(`Socket file ${path} cleaned up during first check`);
46
- }
47
- } catch (error) {
48
- console.error(`Failed to clean up socket file ${path} during first check:`, error);
49
- }
50
- };
@@ -1,33 +0,0 @@
1
- export class ServerTimer {
2
- updatedAt: number;
3
- timer: any;
4
- timeout: number;
5
- onTimeout: any;
6
- interval = 10 * 1000;
7
- constructor(opts?: { timeout?: number }) {
8
- this.timeout = opts?.timeout || 15 * 60 * 1000;
9
- this.run();
10
- }
11
- startTimer() {
12
- const that = this;
13
- if (this.timer) {
14
- clearInterval(this.timer);
15
- }
16
- this.timer = setInterval(() => {
17
- const updatedAt = Date.now();
18
- const timeout = that.timeout;
19
- const onTimeout = that.onTimeout;
20
- const isExpired = updatedAt - that.updatedAt > timeout;
21
- if (isExpired) {
22
- onTimeout?.();
23
- clearInterval(that.timer);
24
- that.timer = null;
25
- }
26
- }, that.interval);
27
- }
28
-
29
- run(): number {
30
- this.updatedAt = Date.now();
31
- return this.updatedAt;
32
- }
33
- }
@@ -1,264 +0,0 @@
1
- import type { IncomingMessage } from 'http';
2
- import { QueryRouterServer } from '../route.ts';
3
- import { getRuntime } from './runtime.ts';
4
- import { runFirstCheck } from './listen/run-check.ts';
5
- import { cleanup } from './listen/cleanup.ts';
6
- import { ServerTimer } from './listen/server-time.ts';
7
-
8
- type ListenSocketOptions = {
9
- path?: string;
10
- app?: QueryRouterServer;
11
- pidPath?: string;
12
- timeout?: number;
13
- };
14
-
15
- const server = async (req, app: QueryRouterServer) => {
16
- const runtime = getRuntime();
17
- let data;
18
- if (!runtime.isNode) {
19
- data = await getRequestParams(req);
20
- } else {
21
- data = await parseBody(req);
22
- }
23
- // @ts-ignore
24
- const serverTimer = app.serverTimer;
25
- if (serverTimer) {
26
- serverTimer?.run?.();
27
- }
28
- const result = await app.queryRoute(data as any);
29
- const response = new Response(JSON.stringify(result));
30
- response.headers.set('Content-Type', 'application/json');
31
- return response;
32
- };
33
- export const closeListenSocket = () => {
34
- console.log('Closing listen socket');
35
- process.emit('SIGINT');
36
- };
37
- export const serverTimer = new ServerTimer();
38
- export const listenSocket = async (options?: ListenSocketOptions) => {
39
- const path = options?.path || './app.sock';
40
- const pidPath = options?.pidPath || './app.pid';
41
- const timeout = options?.timeout || 10 * 1000; // 10 seconds
42
- const runtime = getRuntime();
43
-
44
- serverTimer.timeout = timeout;
45
- serverTimer.startTimer();
46
- serverTimer.onTimeout = closeListenSocket;
47
-
48
- let app = options?.app || globalThis.context?.app;
49
- if (!app) {
50
- app = new QueryRouterServer();
51
- }
52
- app.serverTimer = serverTimer;
53
- await runFirstCheck(path, pidPath);
54
- let close = async () => {};
55
- cleanup({ path, close });
56
- if (runtime.isDeno) {
57
- // 检查 Deno 版本是否支持 Unix domain socket
58
- try {
59
- // @ts-ignore
60
- const listener = Deno.listen({
61
- transport: 'unix',
62
- path: path,
63
- });
64
-
65
- // 处理连接
66
- (async () => {
67
- for await (const conn of listener) {
68
- (async () => {
69
- // @ts-ignore
70
- const httpConn = Deno.serveHttp(conn);
71
- for await (const requestEvent of httpConn) {
72
- try {
73
- const response = await server(requestEvent.request, app);
74
- await requestEvent.respondWith(response);
75
- } catch (error) {
76
- await requestEvent.respondWith(new Response('Internal Server Error', { status: 500 }));
77
- }
78
- }
79
- })();
80
- }
81
- })();
82
- close = async () => {
83
- listener.close();
84
- };
85
- return listener;
86
- } catch (error) {
87
- // 如果 Unix socket 不支持,回退到 HTTP 服务器
88
- console.warn('Unix socket not supported in this Deno environment, falling back to HTTP server');
89
-
90
- // @ts-ignore
91
- const listener = Deno.listen({ port: 0 }); // 使用随机端口
92
-
93
- // @ts-ignore
94
- console.log(`Deno server listening on port ${listener.addr.port}`);
95
-
96
- (async () => {
97
- for await (const conn of listener) {
98
- (async () => {
99
- // @ts-ignore
100
- const httpConn = Deno.serveHttp(conn);
101
- for await (const requestEvent of httpConn) {
102
- try {
103
- const response = await server(requestEvent.request, app);
104
- await requestEvent.respondWith(response);
105
- } catch (error) {
106
- await requestEvent.respondWith(new Response('Internal Server Error', { status: 500 }));
107
- }
108
- }
109
- })();
110
- }
111
- })();
112
-
113
- return listener;
114
- }
115
- }
116
-
117
- if (runtime.isBun) {
118
- // @ts-ignore
119
- const bunServer = Bun.serve({
120
- unix: path,
121
- fetch(req) {
122
- return server(req, app);
123
- },
124
- });
125
- close = async () => {
126
- await bunServer.stop();
127
- };
128
- return bunServer;
129
- }
130
-
131
- // Node.js 环境
132
- const http = await import('http');
133
-
134
- const httpServer = http.createServer(async (req, res) => {
135
- try {
136
- const response = await server(req, app);
137
-
138
- // 设置响应头
139
- response.headers.forEach((value, key) => {
140
- res.setHeader(key, value);
141
- });
142
-
143
- // 设置状态码
144
- res.statusCode = response.status;
145
-
146
- // 读取响应体并写入
147
- const body = await response.text();
148
- res.end(body);
149
- } catch (error) {
150
- console.error('Error handling request:', error);
151
- res.statusCode = 500;
152
- res.end('Internal Server Error');
153
- }
154
- });
155
-
156
- httpServer.listen(path);
157
- close = async () => {
158
- httpServer.close();
159
- };
160
- return httpServer;
161
- };
162
-
163
- export const getRequestParams = async (req: Request) => {
164
- let urlParams: Record<string, any> = {};
165
- let bodyParams: Record<string, any> = {};
166
-
167
- // 获取URL参数
168
- const url = new URL(req.url);
169
- for (const [key, value] of url.searchParams.entries()) {
170
- // 尝试解析JSON payload
171
- if (key === 'payload') {
172
- try {
173
- urlParams[key] = JSON.parse(value);
174
- } catch {
175
- urlParams[key] = value;
176
- }
177
- } else {
178
- urlParams[key] = value;
179
- }
180
- }
181
-
182
- // 获取body参数
183
- if (req.method.toLowerCase() === 'post' && req.body) {
184
- const contentType = req.headers.get('content-type') || '';
185
- if (contentType.includes('application/json')) {
186
- try {
187
- bodyParams = await req.json();
188
- } catch {
189
- // 如果解析失败,保持空对象
190
- }
191
- } else if (contentType.includes('application/x-www-form-urlencoded')) {
192
- const formData = await req.text();
193
- const params = new URLSearchParams(formData);
194
- for (const [key, value] of params.entries()) {
195
- bodyParams[key] = value;
196
- }
197
- } else if (contentType.includes('multipart/form-data')) {
198
- try {
199
- const formData = await req.formData();
200
- for (const [key, value] of formData.entries()) {
201
- // @ts-ignore
202
- bodyParams[key] = value instanceof File ? value : value.toString();
203
- }
204
- } catch {
205
- // 如果解析失败,保持空对象
206
- }
207
- }
208
- }
209
-
210
- // body参数优先,合并数据
211
- return {
212
- ...urlParams,
213
- ...bodyParams,
214
- };
215
- };
216
-
217
- export const parseBody = async <T = Record<string, any>>(req: IncomingMessage) => {
218
- return new Promise<T>((resolve, reject) => {
219
- const arr: any[] = [];
220
- req.on('data', (chunk) => {
221
- arr.push(chunk);
222
- });
223
- req.on('end', () => {
224
- try {
225
- const body = Buffer.concat(arr).toString();
226
-
227
- // 获取 Content-Type 头信息
228
- const contentType = req.headers['content-type'] || '';
229
-
230
- // 处理 application/json
231
- if (contentType.includes('application/json')) {
232
- resolve(JSON.parse(body) as T);
233
- return;
234
- }
235
- // 处理 application/x-www-form-urlencoded
236
- if (contentType.includes('application/x-www-form-urlencoded')) {
237
- const formData = new URLSearchParams(body);
238
- const result: Record<string, any> = {};
239
-
240
- formData.forEach((value, key) => {
241
- // 尝试将值解析为 JSON,如果失败则保留原始字符串
242
- try {
243
- result[key] = JSON.parse(value);
244
- } catch {
245
- result[key] = value;
246
- }
247
- });
248
-
249
- resolve(result as T);
250
- return;
251
- }
252
-
253
- // 默认尝试 JSON 解析
254
- try {
255
- resolve(JSON.parse(body) as T);
256
- } catch {
257
- resolve({} as T);
258
- }
259
- } catch (e) {
260
- resolve({} as T);
261
- }
262
- });
263
- });
264
- };
@@ -1,38 +0,0 @@
1
- import { getRuntime } from './runtime.ts';
2
- import { glob } from './utils/glob.ts';
3
- type GlobOptions = {
4
- cwd?: string;
5
- load?: (args?: any) => Promise<any>;
6
- };
7
-
8
- export const getMatchFiles = async (match: string = './*.ts', { cwd = process.cwd() }: GlobOptions = {}): Promise<string[]> => {
9
- const runtime = getRuntime();
10
- if (runtime.isNode) {
11
- console.error(`Node.js is not supported`);
12
- return [];
13
- }
14
- if (runtime.isDeno) {
15
- // Deno 环境下
16
- return await glob(match);
17
- }
18
- if (runtime.isBun) {
19
- // Bun 环境下
20
- // @ts-ignore
21
- const { Glob } = await import('bun');
22
- const path = await import('path');
23
- // @ts-ignore
24
- const glob = new Glob(match, { cwd, absolute: true, onlyFiles: true });
25
- const files: string[] = [];
26
- for await (const file of glob.scan('.')) {
27
- files.push(path.join(cwd, file));
28
- }
29
- // @ts-ignore
30
- return Array.from(files);
31
- }
32
- return [];
33
- };
34
-
35
- export const loadTS = async (match: string = './*.ts', { cwd = process.cwd(), load }: GlobOptions = {}): Promise<any[]> => {
36
- const files = await getMatchFiles(match, { cwd });
37
- return Promise.all(files.map((file) => (load ? load(file) : import(file))));
38
- };
@@ -1,19 +0,0 @@
1
- type RuntimeEngine = 'node' | 'deno' | 'bun';
2
-
3
- type Runtime = {
4
- isNode?: boolean;
5
- isDeno?: boolean;
6
- isBun?: boolean;
7
- engine: RuntimeEngine;
8
- };
9
- export const getRuntime = (): Runtime => {
10
- // @ts-ignore
11
- if (typeof Deno !== 'undefined') {
12
- return { isDeno: true, engine: 'deno' };
13
- }
14
- // @ts-ignore
15
- if (typeof Bun !== 'undefined') {
16
- return { isBun: true, engine: 'bun' };
17
- }
18
- return { isNode: true, engine: 'node' };
19
- };
@@ -1,83 +0,0 @@
1
- type GlobOptions = {
2
- cwd?: string;
3
- };
4
-
5
- export const glob = async (match: string = './*.ts', { cwd = process.cwd() }: GlobOptions = {}) => {
6
- const fs = await import('node:fs');
7
- const path = await import('node:path');
8
-
9
- // 将 glob 模式转换为正则表达式
10
- const globToRegex = (pattern: string): RegExp => {
11
- const escaped = pattern
12
- .replace(/\./g, '\\.')
13
- .replace(/\*\*/g, '__DOUBLE_STAR__') // 临时替换 **
14
- .replace(/\*/g, '[^/]*') // * 匹配除 / 外的任意字符
15
- .replace(/__DOUBLE_STAR__/g, '.*') // ** 匹配任意字符包括 /
16
- .replace(/\?/g, '[^/]'); // ? 匹配除 / 外的单个字符
17
- return new RegExp(`^${escaped}$`);
18
- };
19
-
20
- // 递归读取目录
21
- const readDirRecursive = async (dir: string): Promise<string[]> => {
22
- const files: string[] = [];
23
-
24
- try {
25
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
26
-
27
- for (const entry of entries) {
28
- const fullPath = path.join(dir, entry.name);
29
-
30
- if (entry.isFile()) {
31
- files.push(fullPath);
32
- } else if (entry.isDirectory()) {
33
- // 递归搜索子目录
34
- const subFiles = await readDirRecursive(fullPath);
35
- files.push(...subFiles);
36
- }
37
- }
38
- } catch (error) {
39
- // 忽略无法访问的目录
40
- }
41
-
42
- return files;
43
- };
44
-
45
- // 解析模式是否包含递归搜索
46
- const hasRecursive = match.includes('**');
47
-
48
- try {
49
- let allFiles: string[] = [];
50
-
51
- if (hasRecursive) {
52
- // 处理递归模式
53
- const basePath = match.split('**')[0];
54
- const startDir = path.resolve(cwd, basePath || '.');
55
- allFiles = await readDirRecursive(startDir);
56
- } else {
57
- // 处理非递归模式
58
- const dir = path.resolve(cwd, path.dirname(match));
59
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
60
-
61
- for (const entry of entries) {
62
- if (entry.isFile()) {
63
- allFiles.push(path.join(dir, entry.name));
64
- }
65
- }
66
- }
67
-
68
- // 创建相对于 cwd 的匹配模式
69
- const normalizedMatch = path.resolve(cwd, match);
70
- const regex = globToRegex(normalizedMatch);
71
-
72
- // 过滤匹配的文件
73
- const matchedFiles = allFiles.filter(file => {
74
- const normalizedFile = path.resolve(file);
75
- return regex.test(normalizedFile);
76
- });
77
-
78
- return matchedFiles;
79
- } catch (error) {
80
- console.error(`Error in glob pattern "${match}":`, error);
81
- return [];
82
- }
83
- };