@kevisual/router 0.0.10 → 0.0.12

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/src/static.ts ADDED
@@ -0,0 +1,97 @@
1
+ const http = require('http');
2
+ const fs = require('fs').promises;
3
+ const path = require('path');
4
+ const fetch = require('node-fetch'); // 如果使用 Node.js 18 以上版本,可以改用内置 fetch
5
+ const url = require('url');
6
+
7
+ // 配置远端静态文件服务器和本地缓存目录
8
+ const remoteServer = 'https://example.com/static'; // 远端服务器的 URL
9
+ const cacheDir = path.join(__dirname, 'cache'); // 本地缓存目录
10
+ const PORT = process.env.PORT || 3000;
11
+
12
+ // 确保本地缓存目录存在
13
+ fs.mkdir(cacheDir, { recursive: true }).catch(console.error);
14
+
15
+ // 获取文件的 content-type
16
+ function getContentType(filePath) {
17
+ const extname = path.extname(filePath);
18
+ const contentType = {
19
+ '.html': 'text/html',
20
+ '.js': 'text/javascript',
21
+ '.css': 'text/css',
22
+ '.json': 'application/json',
23
+ '.png': 'image/png',
24
+ '.jpg': 'image/jpg',
25
+ '.gif': 'image/gif',
26
+ '.svg': 'image/svg+xml',
27
+ '.wav': 'audio/wav',
28
+ '.mp4': 'video/mp4'
29
+ };
30
+ return contentType[extname] || 'application/octet-stream';
31
+ }
32
+
33
+ // 处理请求文件
34
+ async function serveFile(filePath, remoteUrl, res) {
35
+ try {
36
+ // 检查文件是否存在于本地缓存中
37
+ const fileContent = await fs.readFile(filePath);
38
+ res.writeHead(200, { 'Content-Type': getContentType(filePath) });
39
+ res.end(fileContent, 'utf-8');
40
+ } catch (err) {
41
+ if (err.code === 'ENOENT') {
42
+ // 本地缓存中不存在,向远端服务器请求文件
43
+ try {
44
+ const response = await fetch(remoteUrl);
45
+
46
+ if (response.ok) {
47
+ // 远端请求成功,获取文件内容
48
+ const data = await response.buffer();
49
+
50
+ // 将文件缓存到本地
51
+ await fs.writeFile(filePath, data);
52
+
53
+ // 返回文件内容
54
+ res.writeHead(200, { 'Content-Type': getContentType(filePath) });
55
+ res.end(data, 'utf-8');
56
+ } else {
57
+ // 远端文件未找到或错误,返回 404
58
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
59
+ res.end(`Error 404: File not found at ${remoteUrl}`);
60
+ }
61
+ } catch (fetchErr) {
62
+ // 处理请求错误
63
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
64
+ res.end(`Server Error: Unable to fetch ${remoteUrl}`);
65
+ }
66
+ } else {
67
+ // 其他文件系统错误
68
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
69
+ res.end(`Server Error: ${err.message}`);
70
+ }
71
+ }
72
+ }
73
+
74
+ // 创建 HTTP 服务器
75
+ http.createServer(async (req, res) => {
76
+ let reqPath = req.url;
77
+
78
+ // 如果路径是根路径 `/`,将其设置为 `index.html`
79
+ if (reqPath === '/') reqPath = '/index.html';
80
+
81
+ // 构建本地缓存路径和远端 URL
82
+ const localFilePath = path.join(cacheDir, reqPath); // 本地文件路径
83
+ const remoteFileUrl = url.resolve(remoteServer, reqPath); // 远端文件 URL
84
+
85
+ // 根据请求路径处理文件或返回 index.html(单页面应用处理)
86
+ await serveFile(localFilePath, remoteFileUrl, res);
87
+
88
+ // 单页面应用的路由处理
89
+ if (res.headersSent) return; // 如果响应已发送,不再处理
90
+
91
+ // 如果未匹配到任何文件,返回 index.html
92
+ const indexFilePath = path.join(cacheDir, 'index.html');
93
+ const indexRemoteUrl = url.resolve(remoteServer, '/index.html');
94
+ await serveFile(indexFilePath, indexRemoteUrl, res);
95
+ }).listen(PORT, () => {
96
+ console.log(`Server running at http://localhost:${PORT}`);
97
+ });
@@ -0,0 +1,13 @@
1
+ export const parseIfJson = (input: string): { [key: string]: any } | string => {
2
+ try {
3
+ // 尝试解析 JSON
4
+ const parsed = JSON.parse(input);
5
+ // 检查解析结果是否为对象(数组或普通对象)
6
+ if (typeof parsed === 'object' && parsed !== null) {
7
+ return parsed;
8
+ }
9
+ } catch (e) {
10
+ // 如果解析失败,直接返回原始字符串
11
+ }
12
+ return input;
13
+ };
@@ -0,0 +1,9 @@
1
+ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
2
+ const result = {} as Pick<T, K>;
3
+ keys.forEach((key) => {
4
+ if (key in obj) {
5
+ result[key] = obj[key];
6
+ }
7
+ });
8
+ return result;
9
+ }
@@ -0,0 +1,47 @@
1
+ export type RouteMapInfo = {
2
+ pathKey?: string;
3
+ id: string;
4
+ };
5
+
6
+ export class RouteMap {
7
+ private keyMap: Map<string, RouteMapInfo> = new Map(); // 通过 path key 查找
8
+ private idMap: Map<string, RouteMapInfo> = new Map(); // 通过 id 查找
9
+ // 添加数据
10
+ add(info: RouteMapInfo) {
11
+ if (!info.pathKey && !info.id) {
12
+ console.error('appKey 和 appId 不能同时为空');
13
+ return;
14
+ }
15
+ this.keyMap.set(info.pathKey, info);
16
+ if (info.id) {
17
+ this.idMap.set(info.id, info);
18
+ }
19
+ }
20
+ // 删除数据
21
+ removeByKey(key: string) {
22
+ const info = this.keyMap.get(key);
23
+ if (info) {
24
+ this.keyMap.delete(info.pathKey);
25
+ this.idMap.delete(info.id);
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+
31
+ removeByAppId(appId: string) {
32
+ const info = this.idMap.get(appId);
33
+ if (info) {
34
+ this.keyMap.delete(info.pathKey);
35
+ this.idMap.delete(info.id);
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ // 查询数据
41
+ getByKey(key: string): RouteMapInfo | undefined {
42
+ return this.keyMap.get(key);
43
+ }
44
+ getByAppId(appId: string): RouteMapInfo | undefined {
45
+ return this.idMap.get(appId);
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export * from './rule.ts';
@@ -0,0 +1,95 @@
1
+ import { z, ZodError, Schema } from 'zod';
2
+ export { Schema };
3
+ type BaseRule = {
4
+ value?: any;
5
+ required?: boolean;
6
+ message?: string;
7
+ };
8
+
9
+ type RuleString = {
10
+ type: 'string';
11
+ minLength?: number;
12
+ maxLength?: number;
13
+ regex?: string;
14
+ } & BaseRule;
15
+
16
+ type RuleNumber = {
17
+ type: 'number';
18
+ min?: number;
19
+ max?: number;
20
+ } & BaseRule;
21
+
22
+ type RuleBoolean = {
23
+ type: 'boolean';
24
+ } & BaseRule;
25
+
26
+ type RuleArray = {
27
+ type: 'array';
28
+ items: Rule;
29
+ minItems?: number;
30
+ maxItems?: number;
31
+ } & BaseRule;
32
+
33
+ type RuleObject = {
34
+ type: 'object';
35
+ properties: { [key: string]: Rule };
36
+ } & BaseRule;
37
+
38
+ type RuleAny = {
39
+ type: 'any';
40
+ } & BaseRule;
41
+
42
+ export type Rule = RuleString | RuleNumber | RuleBoolean | RuleArray | RuleObject | RuleAny;
43
+
44
+ export const schemaFormRule = (rule: Rule): z.ZodType<any, any, any> => {
45
+ switch (rule.type) {
46
+ case 'string':
47
+ let stringSchema = z.string();
48
+ if (rule.minLength) stringSchema = stringSchema.min(rule.minLength, `String must be at least ${rule.minLength} characters long.`);
49
+ if (rule.maxLength) stringSchema = stringSchema.max(rule.maxLength, `String must not exceed ${rule.maxLength} characters.`);
50
+ if (rule.regex) stringSchema = stringSchema.regex(new RegExp(rule.regex), 'Invalid format');
51
+ return stringSchema;
52
+ case 'number':
53
+ let numberSchema = z.number();
54
+ if (rule.min) numberSchema = numberSchema.min(rule.min, `Number must be at least ${rule.min}.`);
55
+ if (rule.max) numberSchema = numberSchema.max(rule.max, `Number must not exceed ${rule.max}.`);
56
+ return numberSchema;
57
+ case 'boolean':
58
+ return z.boolean();
59
+ case 'array':
60
+ return z.array(createSchema(rule.items));
61
+ case 'object':
62
+ return z.object(Object.fromEntries(Object.entries(rule.properties).map(([key, value]) => [key, createSchema(value)])));
63
+ case 'any':
64
+ return z.any();
65
+ default:
66
+ throw new Error(`Unknown rule type: ${(rule as any)?.type}`);
67
+ }
68
+ };
69
+ export const createSchema = (rule: Rule): Schema => {
70
+ try {
71
+ rule.required = rule.required ?? false;
72
+ if (!rule.required) {
73
+ // nullable is null
74
+ // nullish is null or undefined
75
+ // optional is undefined
76
+ return schemaFormRule(rule).optional();
77
+ }
78
+ return schemaFormRule(rule);
79
+ } catch (e) {
80
+ if (e instanceof ZodError) {
81
+ console.error(e.format());
82
+ }
83
+ throw e;
84
+ }
85
+ };
86
+
87
+ export const createSchemaList = (rules: Rule[]) => {
88
+ try {
89
+ return rules.map((rule) => createSchema(rule));
90
+ } catch (e) {
91
+ if (e instanceof ZodError) {
92
+ console.error(e.format());
93
+ }
94
+ }
95
+ };