@isdk/proxy 0.1.1 → 0.1.2
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.cn.md +191 -9
- package/README.md +191 -7
- package/dist/index.d.mts +272 -30
- package/dist/index.d.ts +272 -30
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/docs/README.md +191 -7
- package/docs/classes/SmartCache.md +59 -11
- package/docs/functions/createCachedFetch.md +1 -1
- package/docs/functions/createFetchWithCache.md +1 -1
- package/docs/functions/extractData.md +34 -5
- package/docs/functions/fetchWithCache.md +11 -1
- package/docs/functions/generateCacheKey.md +34 -4
- package/docs/functions/getSiteConfig.md +39 -0
- package/docs/functions/isAllowed.md +35 -8
- package/docs/functions/isGlob.md +23 -0
- package/docs/functions/isMatch.md +44 -0
- package/docs/globals.md +5 -0
- package/docs/interfaces/BodyFilterConfig.md +77 -0
- package/docs/interfaces/CacheEntry.md +9 -9
- package/docs/interfaces/CacheMetadata.md +8 -8
- package/docs/interfaces/CacheRule.md +80 -0
- package/docs/interfaces/FetchWithCacheContext.md +43 -13
- package/docs/interfaces/FetchWithCacheOptions.md +40 -10
- package/docs/interfaces/KeyFilterConfig.md +11 -7
- package/docs/interfaces/ProxyConfig.md +4 -4
- package/docs/interfaces/SiteCacheConfig.md +46 -11
- package/docs/interfaces/SmartCacheOptions.md +32 -6
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,107 @@
|
|
|
1
|
-
import { KeyvCacheableMemoryOptions } from '@cacheable/memory';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* 缓存键过滤配置
|
|
5
3
|
*
|
|
6
4
|
* 用于定义在生成缓存指纹时,哪些字段应该被包含或排除。
|
|
7
5
|
*/
|
|
8
6
|
interface KeyFilterConfig {
|
|
9
|
-
/** 仅包含(白名单):如果设置,只有这些字段会参与 Key
|
|
10
|
-
include?: string[];
|
|
11
|
-
/** 排除(黑名单):用于排除像 `timestamp`、`nonce`
|
|
12
|
-
exclude?: string[];
|
|
7
|
+
/** 仅包含(白名单):如果设置,只有这些字段会参与 Key 的计算。支持字符串、Glob 模式或正则表达式。 */
|
|
8
|
+
include?: (string | RegExp)[];
|
|
9
|
+
/** 排除(黑名单):用于排除像 `timestamp`、`nonce` 等干扰缓存命中的动态字段。支持字符串、Glob 模式或正则表达式。 */
|
|
10
|
+
exclude?: (string | RegExp)[];
|
|
11
|
+
}
|
|
12
|
+
interface BodyFilterConfig extends KeyFilterConfig {
|
|
13
|
+
/**
|
|
14
|
+
* 用于非 JSON (文本) Body 的提取正则表达式。
|
|
15
|
+
* 如果包含捕获组,则提取捕获组内容作为指纹;否则提取整个匹配部分。
|
|
16
|
+
*/
|
|
17
|
+
extract?: string | RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* 是否对提取出的捕获组进行排序。
|
|
20
|
+
* 开启后可解决 Body 中参数顺序不一致导致的指纹失效问题。
|
|
21
|
+
*/
|
|
22
|
+
sort?: boolean;
|
|
23
|
+
/** 用于正则匹配/提取 Body 时的最大长度限制,默认 1024 (1KB) */
|
|
24
|
+
maxLength?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 精细化缓存匹配规则
|
|
28
|
+
*
|
|
29
|
+
* 用于在 `methods` 过滤的基础上,进一步限定哪些具体的请求路径或参数需要被缓存。
|
|
30
|
+
* 多个规则之间是 **OR (逻辑或)** 关系,即请求只需匹配其中一条规则即可。
|
|
31
|
+
* 在单个规则对象内部,各字段之间是 **AND (逻辑与)** 关系。
|
|
32
|
+
*/
|
|
33
|
+
interface CacheRule {
|
|
34
|
+
/**
|
|
35
|
+
* 匹配的方法 (如 "POST")。
|
|
36
|
+
* 如果指定,则必须方法完全一致;如果不指定,则匹配所有 `methods` 中允许的方法。
|
|
37
|
+
*/
|
|
38
|
+
method?: string;
|
|
39
|
+
/**
|
|
40
|
+
* 路径匹配。
|
|
41
|
+
* - 字符串: 默认进行 Glob 模式匹配(支持 `!` 否定),若非 Glob 且非正则字符串则退化为前缀匹配。
|
|
42
|
+
* - 正则表达式: 检查 `url.pathname` 是否匹配。
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* 路径匹配。
|
|
46
|
+
* - 字符串: 默认进行 Glob 模式匹配(支持 `!` 否定),若非 Glob 且非正则字符串则退化为前缀匹配。
|
|
47
|
+
* - 正则表达式: 检查 `url.pathname` 是否匹配。
|
|
48
|
+
* - 数组: 支持传入多个模式(含否定模式),只要其中一个匹配即可。
|
|
49
|
+
*/
|
|
50
|
+
path?: string | RegExp | (string | RegExp)[];
|
|
51
|
+
/**
|
|
52
|
+
* Query 参数匹配规则。
|
|
53
|
+
* - 键名: 支持字符串、Glob 或正则。
|
|
54
|
+
* - 值:
|
|
55
|
+
* - 字符串: 支持 Glob 模式匹配。
|
|
56
|
+
* - 正则表达式: 检查参数值是否匹配。
|
|
57
|
+
* - `true`: 要求该参数必须存在于 URL 中。
|
|
58
|
+
* - `false`: 要求该参数必须 **不** 存在于 URL 中。
|
|
59
|
+
*/
|
|
60
|
+
query?: Record<string, string | boolean | RegExp>;
|
|
61
|
+
/**
|
|
62
|
+
* 强制指定 Body 类型。
|
|
63
|
+
* 如果不指定,则根据 `Content-Type` 自动判断。
|
|
64
|
+
*/
|
|
65
|
+
bodyType?: 'json' | 'text' | 'binary';
|
|
66
|
+
/**
|
|
67
|
+
* Body 内容匹配。
|
|
68
|
+
* 仅当 Body 为文本或 JSON 时有效。
|
|
69
|
+
* - 字符串: 支持 Glob 模式匹配。
|
|
70
|
+
* - 正则表达式: 检查 Body 内容是否匹配。
|
|
71
|
+
* - 数组: 支持传入多个模式。
|
|
72
|
+
*/
|
|
73
|
+
body?: string | RegExp | (string | RegExp)[];
|
|
13
74
|
}
|
|
14
75
|
/**
|
|
15
76
|
* 站点级缓存配置
|
|
16
77
|
*/
|
|
17
78
|
interface SiteCacheConfig {
|
|
18
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* 允许缓存的 HTTP 方法列表。
|
|
81
|
+
* 默认值: ['GET', 'HEAD']。
|
|
82
|
+
* 若要缓存 POST/PUT,必须在此显式添加,并确保后端响应满足缓存条件(或开启 `forceCache`)。
|
|
83
|
+
*/
|
|
84
|
+
methods?: string[];
|
|
85
|
+
/**
|
|
86
|
+
* 精细化缓存规则列表。
|
|
87
|
+
* 如果配置了此项,请求必须匹配其中至少一条规则才会被允许进入缓存流程。
|
|
88
|
+
* 适用于只希望缓存特定 API 接口的场景。
|
|
89
|
+
*/
|
|
90
|
+
cacheRules?: CacheRule[];
|
|
91
|
+
/** Query 参数过滤配置:决定哪些查询参数参与缓存指纹 (Cache Key) 的计算 */
|
|
19
92
|
query?: KeyFilterConfig;
|
|
20
|
-
/**
|
|
93
|
+
/** 请求头过滤配置:决定哪些 Header 参与缓存指纹计算 */
|
|
21
94
|
headers?: KeyFilterConfig;
|
|
22
|
-
/** Cookie
|
|
95
|
+
/** Cookie 过滤配置:决定哪些 Cookie 字段参与缓存指纹计算 */
|
|
23
96
|
cookies?: KeyFilterConfig;
|
|
24
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* 请求体过滤配置 (仅限 JSON 类型)。
|
|
99
|
+
* 当方法为 POST/PUT/PATCH 且为 JSON 格式时,用于从 Body 中提取特定字段参与指纹计算。
|
|
100
|
+
*/
|
|
101
|
+
body?: BodyFilterConfig;
|
|
102
|
+
/** 容错机制:当后端请求失败(网络错误或 5xx)且存在旧缓存时,是否强制返回旧缓存 */
|
|
25
103
|
staleIfError?: boolean;
|
|
26
|
-
/**
|
|
104
|
+
/** 强制缓存:是否忽略 `Cache-Control: no-store` 等指令强制入库。 */
|
|
27
105
|
forceCache?: boolean;
|
|
28
106
|
}
|
|
29
107
|
/**
|
|
@@ -75,11 +153,28 @@ interface SmartCacheOptions {
|
|
|
75
153
|
storagePath?: string;
|
|
76
154
|
/** 内存缓存阈值(字节)。响应体大小超过此值时,Body 将只存入磁盘,而 Meta 仍保留在内存。默认 1MB。 */
|
|
77
155
|
maxMemorySize?: number;
|
|
78
|
-
/**
|
|
79
|
-
|
|
156
|
+
/** 内存缓存总大小阈值(字节)。默认 100MB。超过此值将清空内存缓存。 */
|
|
157
|
+
maxTotalMemorySize?: number;
|
|
158
|
+
/** 透传给 L1 (Memory) 的高级配置 (secondary-cache LRUCache options) */
|
|
159
|
+
memoryOptions?: {
|
|
160
|
+
capacity?: number;
|
|
161
|
+
expires?: number;
|
|
162
|
+
cleanInterval?: number;
|
|
163
|
+
[key: string]: any;
|
|
164
|
+
};
|
|
80
165
|
}
|
|
81
166
|
/**
|
|
82
|
-
* 智能混合缓存类 (Hybrid Cache)
|
|
167
|
+
* 智能混合缓存类 (Hybrid Multi-tier Cache)
|
|
168
|
+
*
|
|
169
|
+
* 该类实现了 L1 (内存) 和 L2 (磁盘) 的双层混合存储架构,旨在提供高性能且大容量的缓存能力。
|
|
170
|
+
*
|
|
171
|
+
* ### 核心特性:
|
|
172
|
+
* - **双层架构**: L1 使用 LRU 内存缓存(基于 `secondary-cache` 的 LRUCache),L2 使用持久化磁盘缓存(基于 `cacache`)。
|
|
173
|
+
* - **大小感知存储**: 自动识别响应体大小。小于阈值的文件同时存于内存和磁盘;超过阈值的文件仅存于磁盘,但其元数据仍保留在内存中。
|
|
174
|
+
* - **元数据驻留 (Meta-Residency)**: 无论 Body 多大,Headers、Status、Policy 等信息始终优先从内存读取,确保缓存判定性能。
|
|
175
|
+
* - **流式支持**: 支持通过 `setStream` 和 `getStream` 直接操作大数据流,防止 OOM。
|
|
176
|
+
* - **一致性保障**: 在并发写入时自动清理内存,确保后续读取不会拿到被污染的旧数据。
|
|
177
|
+
* - **内存限制**: 通过 `maxTotalMemorySize` 控制 L1 缓存的总内存占用。
|
|
83
178
|
*/
|
|
84
179
|
declare class SmartCache {
|
|
85
180
|
private memory;
|
|
@@ -88,27 +183,88 @@ declare class SmartCache {
|
|
|
88
183
|
constructor(options?: SmartCacheOptions);
|
|
89
184
|
/**
|
|
90
185
|
* 获取缓存条目
|
|
91
|
-
*
|
|
186
|
+
*
|
|
187
|
+
* 逻辑:
|
|
188
|
+
* 1. 首先尝试从 L1 内存获取。
|
|
189
|
+
* 2. 如果内存中有 Body,直接返回(Buffer 类型)。
|
|
190
|
+
* 3. 如果内存中只有 Meta(大文件),则从 L2 磁盘创建并返回 ReadStream。
|
|
191
|
+
* 4. 如果内存完全未命中,从磁盘 L2 检索,并根据大小决定是否回填 L1。
|
|
192
|
+
*
|
|
193
|
+
* @param key - 缓存指纹键
|
|
194
|
+
* @returns 完整的缓存条目(带 Buffer 或 Stream 的 Body),未命中返回 null
|
|
92
195
|
*/
|
|
93
196
|
get(key: string): Promise<CacheEntry | null>;
|
|
94
197
|
/**
|
|
95
|
-
*
|
|
198
|
+
* 写入缓存条目 (原子写入)
|
|
199
|
+
*
|
|
200
|
+
* 适用于已知长度的小型数据块。该操作会同时写入磁盘并回填内存(如果大小未超标)。
|
|
201
|
+
*
|
|
202
|
+
* @param key - 缓存指纹键
|
|
203
|
+
* @param body - 响应体数据 Buffer
|
|
204
|
+
* @param metadata - 响应元数据(不含 size,由本方法自动计算)
|
|
96
205
|
*/
|
|
97
206
|
set(key: string, body: Buffer, metadata: Omit<CacheMetadata, 'size'>): Promise<void>;
|
|
98
207
|
/**
|
|
99
208
|
* 内部方法:处理内存回填
|
|
100
209
|
*/
|
|
101
210
|
private saveToMemory;
|
|
211
|
+
/**
|
|
212
|
+
* 获取磁盘读取流
|
|
213
|
+
*
|
|
214
|
+
* 允许直接从 L2 磁盘层以流的形式读取数据,适用于大文件代理。
|
|
215
|
+
*
|
|
216
|
+
* @param key - 缓存指纹键
|
|
217
|
+
* @returns Node.js 可读流
|
|
218
|
+
*/
|
|
102
219
|
getStream(key: string): NodeJS.ReadableStream;
|
|
220
|
+
/**
|
|
221
|
+
* 获取磁盘写入流 (流式缓存)
|
|
222
|
+
*
|
|
223
|
+
* 该方法用于支持真正的流式代理。它会执行以下一致性操作:
|
|
224
|
+
* 1. 立即清除 L1 内存中的对应键,防止读到旧数据。
|
|
225
|
+
* 2. 返回一个可写流,数据将直接流入磁盘。
|
|
226
|
+
* 3. **一致性修复**: 在流写入完成(finish)时再次清理内存,防止写入期间的并发读取将旧数据再次回填进内存。
|
|
227
|
+
*
|
|
228
|
+
* @param key - 缓存指纹键
|
|
229
|
+
* @param metadata - 响应元数据
|
|
230
|
+
* @returns Node.js 可写流
|
|
231
|
+
*/
|
|
103
232
|
setStream(key: string, metadata: Omit<CacheMetadata, 'size'>): NodeJS.WritableStream;
|
|
104
233
|
delete(key: string): Promise<void>;
|
|
105
234
|
clear(): Promise<void>;
|
|
106
235
|
}
|
|
107
236
|
|
|
108
237
|
/**
|
|
109
|
-
* 根据 Request
|
|
238
|
+
* 根据 Request 对象和站点配置生成唯一的缓存指纹 (异步)
|
|
239
|
+
*
|
|
240
|
+
* 该函数是缓存系统的核心组件,用于将复杂的 HTTP 请求对象转换为唯一的 SHA-256 字符串。
|
|
241
|
+
* 它实现了高度可定制的提取逻辑,允许通过配置排除掉请求中不稳定的因素(如时间戳、Nonce 等)。
|
|
242
|
+
*
|
|
243
|
+
* ### 生成指纹包含的要素:
|
|
244
|
+
* 1. **Method**: 请求方法(统一转为大写)。
|
|
245
|
+
* 2. **Host & Path**: 请求的域名和路径。
|
|
246
|
+
* 3. **Query Params**: URL 查询参数,受 `config.query` 过滤影响。
|
|
247
|
+
* 4. **Headers**: 请求头信息,受 `config.headers` 过滤影响。默认排除 `cookie` 头。
|
|
248
|
+
* 5. **Cookies**: 特别提取的 Cookie 字段,受 `config.cookies` 过滤影响。
|
|
249
|
+
* 6. **Request Body**:
|
|
250
|
+
* - 对于 `POST`, `PUT`, `PATCH` 请求,会自动尝试读取 Body。
|
|
251
|
+
* - **JSON 类型**: 如果 `Content-Type` 包含 `application/json`,则解析为对象并应用 `config.body` 过滤。
|
|
252
|
+
* - **非 JSON/流类型**: 回退到对原始 Body 字节流进行 SHA-256 哈希计算。
|
|
253
|
+
* - **安全性**: 使用 `req.clone()` 读取 Body,确保不影响后续真实的 Fetch 请求流消费。
|
|
254
|
+
*
|
|
255
|
+
* @param req - 原始 Web 标准 Request 对象。
|
|
256
|
+
* @param config - 站点级缓存配置,决定了哪些字段参与指纹计算。
|
|
257
|
+
* @returns 返回一个 64 位十六进制的 SHA-256 哈希字符串作为缓存键。
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const cacheKey = await generateCacheKey(request, {
|
|
262
|
+
* query: { exclude: ['timestamp'] },
|
|
263
|
+
* body: { include: ['id', 'action'] }
|
|
264
|
+
* });
|
|
265
|
+
* ```
|
|
110
266
|
*/
|
|
111
|
-
declare
|
|
267
|
+
declare function generateCacheKey(req: Request, config: SiteCacheConfig): Promise<string>;
|
|
112
268
|
|
|
113
269
|
/**
|
|
114
270
|
* fetchWithCache 选项
|
|
@@ -142,12 +298,19 @@ interface FetchWithCacheContext extends FetchWithCacheOptions {
|
|
|
142
298
|
* 核心协调函数 (Fetcher Orchestrator)
|
|
143
299
|
*
|
|
144
300
|
* 实现了基于流的混合缓存代理核心逻辑,主要机制包括:
|
|
301
|
+
* - **多方法支持与过滤**:支持通过 `allowedMethods` 配置可缓存的方法(如 POST, PUT),并通过 `cacheRules` 进行精细化的路径与参数匹配拦截。
|
|
302
|
+
* - **异步 Request Body 处理**:当缓存 POST/PUT 请求时,会自动读取 Body 并计算唯一指纹(支持 JSON 字段过滤)。
|
|
145
303
|
* - **大文件流式处理**:底层完全通过 Streams 实现,代理大文件时自动写入磁盘且防 OOM。
|
|
146
304
|
* - **SWR (Stale-While-Revalidate)**:后台静默更新机制。
|
|
147
305
|
* - **并发防击穿 (Request Coalescing)**:利用 `activeCacheWrites` 将并发请求合并。
|
|
148
306
|
* - **强制离线容灾**:支持 `staleIfError` 和 `forceCache`(无视 Cache-Control 强制入库)。
|
|
149
307
|
*
|
|
150
308
|
* 并且会在响应头中自动注入 `x-proxy-cache` 标明缓存命中状态 (`HIT`, `STALE`, `MISS`, `STALE_IF_ERROR`)。
|
|
309
|
+
*
|
|
310
|
+
* @param request - 原始 Web 标准 Request 对象
|
|
311
|
+
* @param fetcher - 实际执行网络请求的函数
|
|
312
|
+
* @param options - 缓存配置选项
|
|
313
|
+
* @returns 带有缓存标识头和流式 Body 的 Response 对象
|
|
151
314
|
*/
|
|
152
315
|
declare function fetchWithCache(request: Request, fetcher: (req: Request) => Promise<Response>, options: FetchWithCacheOptions): Promise<Response>;
|
|
153
316
|
|
|
@@ -185,29 +348,108 @@ declare function createCachedFetch(defaultOptions: FetchWithCacheOptions): (requ
|
|
|
185
348
|
* 从源对象中根据过滤配置提取数据并标准化。
|
|
186
349
|
*
|
|
187
350
|
* 此函数主要用于生成缓存指纹。它会:
|
|
188
|
-
* 1. 根据 `config` (include/exclude)
|
|
351
|
+
* 1. 根据 `config` (include/exclude) 过滤键,调用 `isAllowed` 判断每个键是否允许。
|
|
189
352
|
* 2. 对键进行排序以保证指纹的一致性。
|
|
190
353
|
* 3. 将所有键转换为小写。
|
|
191
354
|
* 4. 将值统一包装为数组并进行排序,消除数组项顺序差异。
|
|
192
355
|
*
|
|
356
|
+
* **关于 `defaultAllowed` 参数**:
|
|
357
|
+
* - 只有当没有配置 `include` 和 `exclude` 时,`defaultAllowed` 才会生效。
|
|
358
|
+
* - 如果配置了 `include`(即使为空数组),`defaultAllowed` 也不会生效。
|
|
359
|
+
* - 详见 `isAllowed` 函数的优先级逻辑。
|
|
360
|
+
*
|
|
193
361
|
* @param source 原始数据对象 (如 QueryParams, Headers, Cookies)
|
|
194
|
-
* @param config
|
|
195
|
-
* @
|
|
362
|
+
* @param config 过滤配置,支持 `include`(白名单)和 `exclude`(黑名单)
|
|
363
|
+
* @param defaultAllowed 当没有配置时的默认值(默认 `false`,即不提取任何键)
|
|
364
|
+
* @returns 标准化后的数据 Map,键为小写,值为排序后的字符串数组
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```typescript
|
|
368
|
+
* const headers = { 'Content-Type': 'application/json', 'X-Request-Id': '123' };
|
|
369
|
+
*
|
|
370
|
+
* // 默认不提取任何键
|
|
371
|
+
* extractData(headers); // {}
|
|
372
|
+
*
|
|
373
|
+
* // 提取所有键
|
|
374
|
+
* extractData(headers, undefined, true); // { 'content-type': ['application/json'], 'x-request-id': ['123'] }
|
|
375
|
+
*
|
|
376
|
+
* // 白名单
|
|
377
|
+
* extractData(headers, { include: ['content-type'] }); // { 'content-type': ['application/json'] }
|
|
378
|
+
*
|
|
379
|
+
* // 黑名单(需要 include 或 defaultAllowed)
|
|
380
|
+
* extractData(headers, { include: ['*'], exclude: ['x-request-id'] }, true); // { 'content-type': ['application/json'] }
|
|
381
|
+
* ```
|
|
196
382
|
*/
|
|
197
|
-
declare const extractData: (source: Record<string, any>, config?: KeyFilterConfig) => Record<string, string[]>;
|
|
383
|
+
declare const extractData: (source: Record<string, any>, config?: KeyFilterConfig, defaultAllowed?: boolean) => Record<string, string[]>;
|
|
198
384
|
|
|
199
385
|
/**
|
|
200
386
|
* 判断给定的键是否允许参与缓存指纹计算。
|
|
201
387
|
*
|
|
202
|
-
*
|
|
203
|
-
* 1.
|
|
204
|
-
* 2.
|
|
205
|
-
* 3.
|
|
388
|
+
* **优先级逻辑**:
|
|
389
|
+
* 1. `exclude` 命中 → 返回 `false`(优先级最高,会覆盖前面的结果)
|
|
390
|
+
* 2. `include` 存在且命中 → 返回 `true`
|
|
391
|
+
* 3. `include` 存在但不命中 → 返回 `false`
|
|
392
|
+
* 4. 都没有配置 → 使用 `defaultAllowed`(未传则返回 `undefined`)
|
|
393
|
+
*
|
|
394
|
+
* **注意**:`include` 和 `exclude` 可以同时配置,此时 `exclude` 优先级更高。
|
|
206
395
|
*
|
|
207
396
|
* @param key 要检查的键名
|
|
208
|
-
* @param config
|
|
209
|
-
* @
|
|
397
|
+
* @param config 过滤配置,支持 `include`(白名单)和 `exclude`(黑名单)
|
|
398
|
+
* @param defaultAllowed 当没有配置或配置未命中时的默认值(可选)
|
|
399
|
+
* @returns 是否允许。返回 `boolean` 或 `undefined`(当没有配置且未传 defaultAllowed 时)
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```typescript
|
|
403
|
+
* // 无配置
|
|
404
|
+
* isAllowed('key'); // undefined
|
|
405
|
+
*
|
|
406
|
+
* // 白名单
|
|
407
|
+
* isAllowed('id', { include: ['id', 'name'] }); // true
|
|
408
|
+
* isAllowed('email', { include: ['id', 'name'] }); // false
|
|
409
|
+
*
|
|
410
|
+
* // 黑名单
|
|
411
|
+
* isAllowed('password', { exclude: ['password'] }); // false
|
|
412
|
+
* isAllowed('name', { exclude: ['password'] }); // undefined
|
|
413
|
+
*
|
|
414
|
+
* // 设置默认值
|
|
415
|
+
* isAllowed('name', { exclude: ['password'] }, true); // true
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
declare function isAllowed(key: string, config?: KeyFilterConfig, defaultAllowed?: boolean): boolean;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 判断一个模式是否为 Glob 模式
|
|
422
|
+
*/
|
|
423
|
+
declare function isGlob(str: string): boolean;
|
|
424
|
+
/**
|
|
425
|
+
* 通用匹配函数
|
|
426
|
+
*
|
|
427
|
+
* 逻辑优先级:
|
|
428
|
+
* 1. 如果 pattern 是数组,遵循:(匹配任一正向模式) 且 (不匹配任一负向模式)。
|
|
429
|
+
* 2. 如果 pattern 是 RegExp 对象,直接使用 regex.test(value)。
|
|
430
|
+
* 3. 如果 pattern 是 "/regex/flags" 格式的字符串,转为 RegExp 后使用 test。
|
|
431
|
+
* 4. 如果 pattern 是 Glob 字符串,使用 picomatch 进行匹配。
|
|
432
|
+
* 5. 否则,根据 usePrefix 参数进行前缀匹配或精确匹配。
|
|
433
|
+
*
|
|
434
|
+
* @param pattern 匹配模式 (RegExp 或 字符串 或 数组)
|
|
435
|
+
* @param value 要匹配的值
|
|
436
|
+
* @param usePrefix 是否在普通字符串匹配时启用前缀匹配 (默认为 false,即精确匹配)
|
|
437
|
+
*/
|
|
438
|
+
declare function isMatch(pattern: string | RegExp | (string | RegExp)[], value: string, usePrefix?: boolean): boolean;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* 根据 URL 获取对应的站点缓存配置
|
|
442
|
+
*
|
|
443
|
+
* 匹配逻辑:
|
|
444
|
+
* 1. 遍历 sites 中的所有 key。
|
|
445
|
+
* 2. 如果 key 是正则或 Glob 格式字符串,则对完整 URL 进行匹配。
|
|
446
|
+
* 3. 如果 key 是普通字符串,则作为 URL 前缀进行匹配。
|
|
447
|
+
* 4. 返回第一个匹配到的配置;若均未匹配,则返回 defaultConfig。
|
|
448
|
+
*
|
|
449
|
+
* @param urlString 请求的完整 URL
|
|
450
|
+
* @param proxyConfig 全局代理配置
|
|
451
|
+
* @returns 匹配到的站点配置
|
|
210
452
|
*/
|
|
211
|
-
declare function
|
|
453
|
+
declare function getSiteConfig(urlString: string, proxyConfig: ProxyConfig): SiteCacheConfig;
|
|
212
454
|
|
|
213
|
-
export { type CacheEntry, type CacheMetadata, type FetchWithCacheContext, type FetchWithCacheOptions, type KeyFilterConfig, type ProxyConfig, type SiteCacheConfig, SmartCache, type SmartCacheOptions, createCachedFetch, createFetchWithCache, extractData, fetchWithCache, generateCacheKey, isAllowed };
|
|
455
|
+
export { type BodyFilterConfig, type CacheEntry, type CacheMetadata, type CacheRule, type FetchWithCacheContext, type FetchWithCacheOptions, type KeyFilterConfig, type ProxyConfig, type SiteCacheConfig, SmartCache, type SmartCacheOptions, createCachedFetch, createFetchWithCache, extractData, fetchWithCache, generateCacheKey, getSiteConfig, isAllowed, isGlob, isMatch };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t,e=Object.create,
|
|
1
|
+
"use strict";var t,e=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(t,e,a,c)=>{if(e&&"object"==typeof e||"function"==typeof e)for(let o of i(e))s.call(t,o)||o===a||n(t,o,{get:()=>e[o],enumerable:!(c=r(e,o))||c.enumerable});return t},o=(t,r,i)=>(i=null!=t?e(a(t)):{},c(!r&&t&&t.__esModule?i:n(i,"default",{value:t,enumerable:!0}),t)),u={};((t,e)=>{for(var r in e)n(t,r,{get:e[r],enumerable:!0})})(u,{SmartCache:()=>w,createCachedFetch:()=>I,createFetchWithCache:()=>H,extractData:()=>T,fetchWithCache:()=>C,generateCacheKey:()=>R,getSiteConfig:()=>g,isAllowed:()=>j,isGlob:()=>x,isMatch:()=>O}),module.exports=(t=u,c(n({},"__esModule",{value:!0}),t));var f=require("secondary-cache"),h=o(require("cacache")),l=o(require("os")),y=o(require("path")),w=class{memory;storagePath;maxMemorySize;constructor(t={}){this.storagePath=t.storagePath||y.default.join(l.default.tmpdir(),"isdk-proxy-cache"),this.maxMemorySize=t.maxMemorySize??1048576;const e={capacity:0,expires:3e5,maxWeight:t.maxTotalMemorySize||104857600,weightOf:t=>{let e=0;return t.body&&Buffer.isBuffer(t.body)&&(e+=t.body.length),e+=512,e},...t.memoryOptions};this.memory=new f.LRUCache(e)}async get(t){const e=this.memory.get(t);if(e){if(e.body)return e;if(0===e.size)return{...e,body:Buffer.alloc(0)};const n=h.default.get.stream(this.storagePath,t);return{...e,body:n}}try{const e=await h.default.get.info(this.storagePath,t);if(!e)return null;if(e.size<=this.maxMemorySize){const{data:e,metadata:n}=await h.default.get(this.storagePath,t),r=n,i={...r,body:e};return this.saveToMemory(t,e,r),i}{const e=(await h.default.get.info(this.storagePath,t)).metadata;return this.saveToMemory(t,null,e),{...e,body:h.default.get.stream(this.storagePath,t)}}}catch(t){return null}}async set(t,e,n){const r={...n,size:e.length};await h.default.put(this.storagePath,t,e,{metadata:r}),this.saveToMemory(t,e,r)}saveToMemory(t,e,n){if(e&&e.length>0&&e.length<=this.maxMemorySize)this.memory.set(t,{...n,body:e});else{const{...e}=n;this.memory.set(t,e)}}getStream(t){return h.default.get.stream(this.storagePath,t)}setStream(t,e){this.memory.del(t);const n=h.default.put.stream(this.storagePath,t,{metadata:e});return n.on("finish",()=>{this.memory.del(t)}),n}async delete(t){this.memory.del(t),await h.default.rm.entry(this.storagePath,t)}async clear(){this.memory.clear(),await h.default.rm.all(this.storagePath)}},d=require("crypto"),p=require("util-ex"),m=o(require("picomatch")),b=require("util-ex");function x(t){return/[!*?{}[\]()]/.test(t)}function O(t,e,n=!1){if(Array.isArray(t)&&t.length){const r=[],i=[];return t.forEach(t=>{"string"==typeof t&&t.startsWith("!")?i.push(t.slice(1)):r.push(t)}),!(i.length>0&&i.some(t=>O(t,e,n)))&&(0===r.length||r.some(t=>O(t,e,n)))}return t instanceof RegExp?t.test(e):"string"==typeof t&&((0,b.isRegExpStr)(t)?(0,b.toRegExp)(t).test(e):x(t)?(0,m.default)(t,{dot:!0})(e):n?e.startsWith(t):e===t)}function j(t,e,n){let r;return e?.include&&(r=O(e.include,t)),e?.exclude&&O(e.exclude,t)&&(r=!1),void 0===r&&(r=n),r}var T=(t,e,n)=>{const r={};return Object.keys(t).filter(t=>j(t,e,n)).sort().forEach(e=>{const n=t[e];null!=n&&(r[e.toLowerCase()]=Array.isArray(n)?[...n].sort():[n])}),r};function g(t,e){const{sites:n,default:r}=e;if(!n)return r;for(const[e,r]of Object.entries(n))if(O(e,t,!0))return r;return r}async function R(t,e){const n=new URL(t.url),r=t.headers.get("cookie")||"",i=Object.fromEntries(r.split(";").map(t=>t.trim()).filter(Boolean).map(t=>{const e=t.split("=");return[e[0],e.slice(1).join("=")]}));let a=null;const s=t.method.toUpperCase();if(["POST","PUT","PATCH"].includes(s))try{const n=t.headers.get("content-type")||"";if(n.includes("application/json")){const n=await t.clone().json();a=T(n,e.body,!0)}else if(e.body?.extract&&(n.includes("text/")||n.includes("application/xml")||n.includes("x-www-form-urlencoded"))){const n=e.body?.maxLength||1024,r=(await t.clone().text()).slice(0,n),i=e.body.extract,s="string"==typeof i&&(0,p.isRegExpStr)(i)?(0,p.toRegExp)(i):i instanceof RegExp?i:null;if(s){const t=r.match(s);if(t)if(t.length>1){const n=t.slice(1);e.body?.sort&&n.sort(),a=n.join(":")}else a=t[0]}else a=(0,d.createHash)("sha256").update(r).digest("hex")}else{const e=await t.clone().arrayBuffer();e.byteLength>0&&(a=(0,d.createHash)("sha256").update(new Uint8Array(e)).digest("hex"))}}catch(t){}const c={m:s,h:n.host,p:n.pathname,q:T(Object.fromEntries(n.searchParams),e.query,!0),hd:T(Object.fromEntries(t.headers),{...e.headers,exclude:[...e.headers?.exclude||[],"cookie"]}),ck:T(i,e.cookies)};return null!==a&&(c.b=a),(0,d.createHash)("sha256").update(JSON.stringify(c)).digest("hex")}var S=require("stream"),q=require("stream/promises"),A=o(require("http-cache-semantics"));function E(t,e){const n=204===t.status||304===t.status||t.status<200?null:function(t){return t instanceof Buffer?new Uint8Array(t):t&&"function"==typeof t.pipe?S.Readable.toWeb(t):t}(t.body);return new Response(n,{status:t.status,headers:{...t.headers,"x-proxy-cache":e}})}async function v(t,e){let n,r;const i=new Promise((e,i)=>{n=()=>{t.activeCacheWrites.delete(t.cacheKey),e()},r=e=>{t.activeCacheWrites.delete(t.cacheKey),i(e)}});i.catch(()=>{}),t.activeCacheWrites.set(t.cacheKey,i);try{const e=await t.fetcher(t.request.clone()),i=new A.default({url:t.request.url,method:t.request.method,headers:Object.fromEntries(t.request.headers)},{status:e.status,headers:Object.fromEntries(e.headers)}),a=new Headers(e.headers);if(a.set("x-proxy-cache","MISS"),!i.storable()&&!t.config.forceCache)return n(),new Response(e.body,{status:e.status,statusText:e.statusText,headers:a});const s={status:e.status,headers:Object.fromEntries(e.headers),policy:i.toObject(),url:t.request.url,method:t.request.method,timestamp:Date.now()};if(!e.body)return await t.cache.set(t.cacheKey,Buffer.alloc(0),s),n(),new Response(null,{status:e.status,statusText:e.statusText,headers:a});const[c,o]=e.body.tee();return(0,q.pipeline)(S.Readable.fromWeb(o),t.cache.setStream(t.cacheKey,s)).then(n).catch(r),new Response(c,{status:e.status,statusText:e.statusText,headers:a})}catch(n){if(r(n),e&&t.config.staleIfError)return E(e,"STALE_IF_ERROR");throw n}}async function C(t,e,n){if(!await async function(t,e){const n=t.method.toUpperCase();if(!(e.methods||["GET","HEAD"]).includes(n))return!1;if(e.cacheRules&&e.cacheRules.length>0){const r=new URL(t.url),i=r.searchParams;let a=null,s=!1;for(const o of e.cacheRules)if(await c(o,n,r,i,t))return!0;return!1;async function c(t,n,r,i,c){if(t.method&&t.method.toUpperCase()!==n)return!1;if(t.path&&!O(t.path,r.pathname,!0))return!1;if(t.query)for(const[e,n]of Object.entries(t.query)){const t=i.has(e),r=i.get(e)||"";if("boolean"==typeof n){if(n&&!t)return!1;if(!n&&t)return!1}else if(!O(n,r))return!1}if(t.bodyType||t.body){const n=c.headers.get("content-type")||"",r=n.includes("application/json")?"json":n.includes("text/")||n.includes("application/xml")||n.includes("x-www-form-urlencoded")?"text":"binary";if(t.bodyType&&t.bodyType!==r)return!1;if(t.body){if("binary"===r)return!1;if(!s){try{const t=e.body?.maxLength||1024,n=await c.clone().text();a=n.slice(0,t)}catch(t){a=""}s=!0}if(!a||!O(t.body,a))return!1}}return!0}}return!0}(t,n.config))return e(t);const r=await async function(t,e,n){const r=n.generateKey||R,i=await r(t,n.config);return{...n,request:t,fetcher:e,cacheKey:i,activeCacheWrites:n.activeCacheWrites||new Map}}(t,e,n),i=await r.cache.get(r.cacheKey);if(i){const t=function(t,e){const n=A.default.fromObject(e.policy),r={url:e.url,method:t.request.method,headers:Object.fromEntries(t.request.headers)};return n.satisfiesWithoutRevalidation(r)?"HIT":"STALE"}(r,i);if("HIT"===t)return E(i,"HIT");if("STALE"===t&&!1!==r.backgroundUpdate)return function(t,e){if(t.activeCacheWrites.has(t.cacheKey))return;const n=v(t,e).catch(n=>(console.error(`[SWR Error] Background update failed for ${t.cacheKey}:`,n),E(e,"STALE_IF_ERROR")));t.onBackgroundUpdate?.(n)}(r,i),E(i,"STALE")}if(r.activeCacheWrites.has(r.cacheKey)){const t=await async function(t){const e=t.activeCacheWrites.get(t.cacheKey);if(!e)return null;await e;const n=await t.cache.get(t.cacheKey);return n?E(n,"HIT"):null}(r);if(t)return t}return v(r,i)}function H(t){return t||(t=new Map),async function(e,n,r){return C(e,n,{...r,activeCacheWrites:t})}}function I(t){const e=H(t.activeCacheWrites);return async function(n,r,i){return e(n,r,{...t,...i})}}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{LRUCache as t}from"secondary-cache";import e from"cacache";import n from"os";import r from"path";var s=class{memory;storagePath;maxMemorySize;constructor(e={}){this.storagePath=e.storagePath||r.join(n.tmpdir(),"isdk-proxy-cache"),this.maxMemorySize=e.maxMemorySize??1048576;const s={capacity:0,expires:3e5,maxWeight:e.maxTotalMemorySize||104857600,weightOf:t=>{let e=0;return t.body&&Buffer.isBuffer(t.body)&&(e+=t.body.length),e+=512,e},...e.memoryOptions};this.memory=new t(s)}async get(t){const n=this.memory.get(t);if(n){if(n.body)return n;if(0===n.size)return{...n,body:Buffer.alloc(0)};const r=e.get.stream(this.storagePath,t);return{...n,body:r}}try{const n=await e.get.info(this.storagePath,t);if(!n)return null;if(n.size<=this.maxMemorySize){const{data:n,metadata:r}=await e.get(this.storagePath,t),s=r,i={...s,body:n};return this.saveToMemory(t,n,s),i}{const n=(await e.get.info(this.storagePath,t)).metadata;return this.saveToMemory(t,null,n),{...n,body:e.get.stream(this.storagePath,t)}}}catch(t){return null}}async set(t,n,r){const s={...r,size:n.length};await e.put(this.storagePath,t,n,{metadata:s}),this.saveToMemory(t,n,s)}saveToMemory(t,e,n){if(e&&e.length>0&&e.length<=this.maxMemorySize)this.memory.set(t,{...n,body:e});else{const{...e}=n;this.memory.set(t,e)}}getStream(t){return e.get.stream(this.storagePath,t)}setStream(t,n){this.memory.del(t);const r=e.put.stream(this.storagePath,t,{metadata:n});return r.on("finish",()=>{this.memory.del(t)}),r}async delete(t){this.memory.del(t),await e.rm.entry(this.storagePath,t)}async clear(){this.memory.clear(),await e.rm.all(this.storagePath)}};import{createHash as i}from"crypto";import{isRegExpStr as o,toRegExp as a}from"util-ex";import c from"picomatch";import{isRegExpStr as u,toRegExp as f}from"util-ex";function h(t){return/[!*?{}[\]()]/.test(t)}function l(t,e,n=!1){if(Array.isArray(t)&&t.length){const r=[],s=[];return t.forEach(t=>{"string"==typeof t&&t.startsWith("!")?s.push(t.slice(1)):r.push(t)}),!(s.length>0&&s.some(t=>l(t,e,n)))&&(0===r.length||r.some(t=>l(t,e,n)))}return t instanceof RegExp?t.test(e):"string"==typeof t&&(u(t)?f(t).test(e):h(t)?c(t,{dot:!0})(e):n?e.startsWith(t):e===t)}function p(t,e,n){let r;return e?.include&&(r=l(e.include,t)),e?.exclude&&l(e.exclude,t)&&(r=!1),void 0===r&&(r=n),r}var m=(t,e,n)=>{const r={};return Object.keys(t).filter(t=>p(t,e,n)).sort().forEach(e=>{const n=t[e];null!=n&&(r[e.toLowerCase()]=Array.isArray(n)?[...n].sort():[n])}),r};function y(t,e){const{sites:n,default:r}=e;if(!n)return r;for(const[e,r]of Object.entries(n))if(l(e,t,!0))return r;return r}async function w(t,e){const n=new URL(t.url),r=t.headers.get("cookie")||"",s=Object.fromEntries(r.split(";").map(t=>t.trim()).filter(Boolean).map(t=>{const e=t.split("=");return[e[0],e.slice(1).join("=")]}));let c=null;const u=t.method.toUpperCase();if(["POST","PUT","PATCH"].includes(u))try{const n=t.headers.get("content-type")||"";if(n.includes("application/json")){const n=await t.clone().json();c=m(n,e.body,!0)}else if(e.body?.extract&&(n.includes("text/")||n.includes("application/xml")||n.includes("x-www-form-urlencoded"))){const n=e.body?.maxLength||1024,r=(await t.clone().text()).slice(0,n),s=e.body.extract,u="string"==typeof s&&o(s)?a(s):s instanceof RegExp?s:null;if(u){const t=r.match(u);if(t)if(t.length>1){const n=t.slice(1);e.body?.sort&&n.sort(),c=n.join(":")}else c=t[0]}else c=i("sha256").update(r).digest("hex")}else{const e=await t.clone().arrayBuffer();e.byteLength>0&&(c=i("sha256").update(new Uint8Array(e)).digest("hex"))}}catch(t){}const f={m:u,h:n.host,p:n.pathname,q:m(Object.fromEntries(n.searchParams),e.query,!0),hd:m(Object.fromEntries(t.headers),{...e.headers,exclude:[...e.headers?.exclude||[],"cookie"]}),ck:m(s,e.cookies)};return null!==c&&(f.b=c),i("sha256").update(JSON.stringify(f)).digest("hex")}import{Readable as d}from"stream";import{pipeline as x}from"stream/promises";import b from"http-cache-semantics";function R(t,e){const n=204===t.status||304===t.status||t.status<200?null:function(t){return t instanceof Buffer?new Uint8Array(t):t&&"function"==typeof t.pipe?d.toWeb(t):t}(t.body);return new Response(n,{status:t.status,headers:{...t.headers,"x-proxy-cache":e}})}async function T(t,e){let n,r;const s=new Promise((e,s)=>{n=()=>{t.activeCacheWrites.delete(t.cacheKey),e()},r=e=>{t.activeCacheWrites.delete(t.cacheKey),s(e)}});s.catch(()=>{}),t.activeCacheWrites.set(t.cacheKey,s);try{const e=await t.fetcher(t.request.clone()),s=new b({url:t.request.url,method:t.request.method,headers:Object.fromEntries(t.request.headers)},{status:e.status,headers:Object.fromEntries(e.headers)}),i=new Headers(e.headers);if(i.set("x-proxy-cache","MISS"),!s.storable()&&!t.config.forceCache)return n(),new Response(e.body,{status:e.status,statusText:e.statusText,headers:i});const o={status:e.status,headers:Object.fromEntries(e.headers),policy:s.toObject(),url:t.request.url,method:t.request.method,timestamp:Date.now()};if(!e.body)return await t.cache.set(t.cacheKey,Buffer.alloc(0),o),n(),new Response(null,{status:e.status,statusText:e.statusText,headers:i});const[a,c]=e.body.tee();return x(d.fromWeb(c),t.cache.setStream(t.cacheKey,o)).then(n).catch(r),new Response(a,{status:e.status,statusText:e.statusText,headers:i})}catch(n){if(r(n),e&&t.config.staleIfError)return R(e,"STALE_IF_ERROR");throw n}}async function O(t,e,n){if(!await async function(t,e){const n=t.method.toUpperCase();if(!(e.methods||["GET","HEAD"]).includes(n))return!1;if(e.cacheRules&&e.cacheRules.length>0){const r=new URL(t.url),s=r.searchParams;let i=null,o=!1;for(const c of e.cacheRules)if(await a(c,n,r,s,t))return!0;return!1;async function a(t,n,r,s,a){if(t.method&&t.method.toUpperCase()!==n)return!1;if(t.path&&!l(t.path,r.pathname,!0))return!1;if(t.query)for(const[e,n]of Object.entries(t.query)){const t=s.has(e),r=s.get(e)||"";if("boolean"==typeof n){if(n&&!t)return!1;if(!n&&t)return!1}else if(!l(n,r))return!1}if(t.bodyType||t.body){const n=a.headers.get("content-type")||"",r=n.includes("application/json")?"json":n.includes("text/")||n.includes("application/xml")||n.includes("x-www-form-urlencoded")?"text":"binary";if(t.bodyType&&t.bodyType!==r)return!1;if(t.body){if("binary"===r)return!1;if(!o){try{const t=e.body?.maxLength||1024,n=await a.clone().text();i=n.slice(0,t)}catch(t){i=""}o=!0}if(!i||!l(t.body,i))return!1}}return!0}}return!0}(t,n.config))return e(t);const r=await async function(t,e,n){const r=n.generateKey||w,s=await r(t,n.config);return{...n,request:t,fetcher:e,cacheKey:s,activeCacheWrites:n.activeCacheWrites||new Map}}(t,e,n),s=await r.cache.get(r.cacheKey);if(s){const t=function(t,e){const n=b.fromObject(e.policy),r={url:e.url,method:t.request.method,headers:Object.fromEntries(t.request.headers)};return n.satisfiesWithoutRevalidation(r)?"HIT":"STALE"}(r,s);if("HIT"===t)return R(s,"HIT");if("STALE"===t&&!1!==r.backgroundUpdate)return function(t,e){if(t.activeCacheWrites.has(t.cacheKey))return;const n=T(t,e).catch(n=>(console.error(`[SWR Error] Background update failed for ${t.cacheKey}:`,n),R(e,"STALE_IF_ERROR")));t.onBackgroundUpdate?.(n)}(r,s),R(s,"STALE")}if(r.activeCacheWrites.has(r.cacheKey)){const t=await async function(t){const e=t.activeCacheWrites.get(t.cacheKey);if(!e)return null;await e;const n=await t.cache.get(t.cacheKey);return n?R(n,"HIT"):null}(r);if(t)return t}return T(r,s)}function E(t){return t||(t=new Map),async function(e,n,r){return O(e,n,{...r,activeCacheWrites:t})}}function S(t){const e=E(t.activeCacheWrites);return async function(n,r,s){return e(n,r,{...t,...s})}}export{s as SmartCache,S as createCachedFetch,E as createFetchWithCache,m as extractData,O as fetchWithCache,w as generateCacheKey,y as getSiteConfig,p as isAllowed,h as isGlob,l as isMatch};
|