@nickyzj2023/utils 1.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 +15 -0
- package/package.json +15 -0
- package/src/index.ts +2 -0
- package/src/is.ts +3 -0
- package/src/lru-cache.ts +50 -0
- package/src/network.ts +90 -0
- package/tsconfig.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @nickyzj/utils
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run src/index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.3.2. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nickyzj2023/utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "bun build ./src/index.ts --outdir ./dist --minify"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/bun": "latest"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"typescript": "^5"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/index.ts
ADDED
package/src/is.ts
ADDED
package/src/lru-cache.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 简易 LRU 缓存
|
|
3
|
+
* @example
|
|
4
|
+
* const cache = new LRUCache<string, number>(2);
|
|
5
|
+
* cache.set("a", 1);
|
|
6
|
+
* cache.set("b", 2);
|
|
7
|
+
* cache.set("c", 3); // 缓存已满,a 被淘汰
|
|
8
|
+
* cache.get("a"); // undefined
|
|
9
|
+
*/
|
|
10
|
+
class LRUCache<K, V> {
|
|
11
|
+
private cache: Map<K, V>;
|
|
12
|
+
private maxSize: number;
|
|
13
|
+
|
|
14
|
+
constructor(maxSize = 10) {
|
|
15
|
+
this.cache = new Map();
|
|
16
|
+
this.maxSize = maxSize;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(key: K): V | undefined {
|
|
20
|
+
const value = this.cache.get(key);
|
|
21
|
+
if (!value) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
// 重置缓存顺序
|
|
25
|
+
this.cache.delete(key);
|
|
26
|
+
this.cache.set(key, value);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set(key: K, value: V) {
|
|
31
|
+
// 刷新缓存
|
|
32
|
+
if (this.cache.has(key)) {
|
|
33
|
+
this.cache.delete(key);
|
|
34
|
+
}
|
|
35
|
+
// 删除最旧的缓存
|
|
36
|
+
else if (this.cache.size >= this.maxSize) {
|
|
37
|
+
const oldestKey = [...this.cache.keys()][0];
|
|
38
|
+
if (oldestKey) {
|
|
39
|
+
this.cache.delete(oldestKey);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.cache.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
has(key: K) {
|
|
46
|
+
return this.cache.has(key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default LRUCache;
|
package/src/network.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isObject } from "./is";
|
|
2
|
+
import LRUCache from "./lru-cache";
|
|
3
|
+
|
|
4
|
+
const cachedRequests = new LRUCache<string, Promise<Response>>();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 基于 Fetch API 的请求客户端
|
|
8
|
+
* @example
|
|
9
|
+
* // 用法1:创建客户端
|
|
10
|
+
* const api = fetcher("https://nickyzj.run:3030");
|
|
11
|
+
* const res = await api.get<Blog>("/blogs/2025/猩猩也能懂的Node.js部署教程");
|
|
12
|
+
*
|
|
13
|
+
* // 用法2:直接发送请求
|
|
14
|
+
* const res = await fetcher().get<Blog>("https://nickyzj.run:3030/blogs/2025/猩猩也能懂的Node.js部署教程");
|
|
15
|
+
*/
|
|
16
|
+
export const fetcher = (baseURL = "") => {
|
|
17
|
+
const createRequest = async <T>(path: string, options: RequestInit = {}) => {
|
|
18
|
+
// 构建完整 URL
|
|
19
|
+
const url = baseURL ? `${baseURL}${path}` : path;
|
|
20
|
+
|
|
21
|
+
// 处理 body 为对象的情况
|
|
22
|
+
if (isObject(options.body)) {
|
|
23
|
+
options.body = JSON.stringify(options.body);
|
|
24
|
+
options.headers = {
|
|
25
|
+
...options.headers,
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const request = () => fetch(url, options);
|
|
31
|
+
let promise: Promise<Response>;
|
|
32
|
+
|
|
33
|
+
const canCache = options.method === "GET" || !options.method;
|
|
34
|
+
if (!canCache) {
|
|
35
|
+
promise = request();
|
|
36
|
+
} else {
|
|
37
|
+
let tempPromise = cachedRequests.get(url);
|
|
38
|
+
if (!tempPromise) {
|
|
39
|
+
tempPromise = request();
|
|
40
|
+
cachedRequests.set(url, tempPromise);
|
|
41
|
+
}
|
|
42
|
+
promise = tempPromise;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 必须使用 clone() 消费一个新的响应体,否则下次从 cache 中获取的响应体会报错(无法被重复消费)
|
|
46
|
+
const response = (await promise).clone();
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(response.statusText);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
return data as T;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
get: <T>(url: string, options: Omit<RequestInit, "method"> = {}) =>
|
|
57
|
+
createRequest<T>(url, { ...options, method: "GET" }),
|
|
58
|
+
|
|
59
|
+
post: <T>(
|
|
60
|
+
url: string,
|
|
61
|
+
body?: any,
|
|
62
|
+
options: Omit<RequestInit, "method" | "body"> = {},
|
|
63
|
+
) => createRequest<T>(url, { ...options, method: "POST", body }),
|
|
64
|
+
|
|
65
|
+
put: <T>(
|
|
66
|
+
url: string,
|
|
67
|
+
body?: any,
|
|
68
|
+
options: Omit<RequestInit, "method" | "body"> = {},
|
|
69
|
+
) => createRequest<T>(url, { ...options, method: "PUT", body }),
|
|
70
|
+
|
|
71
|
+
delete: <T>(url: string, options: Omit<RequestInit, "method"> = {}) =>
|
|
72
|
+
createRequest<T>(url, { ...options, method: "DELETE" }),
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Go 语言风格的异步处理方式
|
|
78
|
+
* @example
|
|
79
|
+
* const [error, response] = await to(request<Resp>("/blogs/2025/猩猩也能懂的Node.js部署教程"));
|
|
80
|
+
*/
|
|
81
|
+
export const to = async <T, U = Error>(
|
|
82
|
+
promise: Promise<T>,
|
|
83
|
+
): Promise<[null, T] | [U, undefined]> => {
|
|
84
|
+
try {
|
|
85
|
+
const response = await promise;
|
|
86
|
+
return [null, response];
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return [error as U, undefined];
|
|
89
|
+
}
|
|
90
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|