@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 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
@@ -0,0 +1,2 @@
1
+ export * from "./network";
2
+ export * from "./is";
package/src/is.ts ADDED
@@ -0,0 +1,3 @@
1
+ export const isObject = (value: any): value is object => {
2
+ return value?.constructor === Object;
3
+ };
@@ -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
+ }