@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 CHANGED
@@ -13,6 +13,8 @@
13
13
  ## 核心特性
14
14
 
15
15
  - **🚀 混合多级缓存**: L1 (LRU 内存) 提供极速响应,L2 (内容寻址磁盘 `cacache`) 提供持久化存储。
16
+ - **📥 HTTP POST & 多方法支持**: 完整支持 POST、PUT 等非 GET 方法的缓存,内置智能请求体指纹计算机制。
17
+ - **🎯 精细化规则拦截**: 支持通过 `cacheRules` 对特定路径或 Query 参数进行外科手术式的精确缓存控制。
16
18
  - **🌊 原生流式分发**: 内部完全基于 Stream 管道化构建,在代理大文件时天然防 OOM 内存溢出。
17
19
  - **🧠 智能元数据驻留**: 无论文件多大,元数据 (Headers, Status, Policy) 始终驻留在内存中,确保纳秒级的缓存策略判定。
18
20
  - **🔄 过期后异步更新 (SWR)**: 立即返回过期数据,同时在后台静默更新缓存,实现“零等待”响应。
@@ -31,6 +33,8 @@ pnpm add @isdk/proxy
31
33
 
32
34
  使用 `@isdk/proxy` 的主要方式是通过 `fetchWithCache` 函数,它可以包装任何 HTTP 请求逻辑。
33
35
 
36
+ ### 基础用法 (GET 请求)
37
+
34
38
  ```typescript
35
39
  import { SmartCache, createCachedFetch } from '@isdk/proxy';
36
40
 
@@ -40,31 +44,100 @@ const cache = new SmartCache({
40
44
  maxMemorySize: 1024 * 1024 // 内存阈值 1MB
41
45
  });
42
46
 
43
- // 2. 创建一个预配置的缓存 Fetcher (内部会自动防缓存击穿)
47
+ // 2. 创建一个预配置的缓存 Fetcher
44
48
  const myFetch = createCachedFetch({
45
49
  cache,
46
50
  config: {
47
51
  staleIfError: true,
48
- forceCache: false // 设置为 true 可无视 no-store 强制缓存一切,适用于离线应用
49
52
  },
50
53
  backgroundUpdate: true // 开启 SWR (过期后后台静默更新)
51
54
  });
52
55
 
53
- // 3. 在应用的任何地方愉快地使用它!
54
- const request = new Request('https://api.example.com/data');
55
- const response = await myFetch(request, (req) => fetch(req)); // 传入任何返回 Promise<Response> 的获取函数
56
+ // 3. 愉快地使用它!
57
+ const response = await myFetch(new Request('https://api.example.com/data'), (req) => fetch(req));
58
+ console.log(response.headers.get('x-proxy-cache'));
59
+ ```
60
+
61
+ ### 进阶用法:缓存 POST 请求
56
62
 
57
- console.log(response.headers.get('x-proxy-cache')); // 输出: "MISS", "HIT", "STALE" 或 "STALE_IF_ERROR"
58
- const data = await response.json();
63
+ 你可以通过配置 `methods` 开启 POST/PUT 缓存,并使用 `body` 过滤器排除请求体中的动态字段(如时间戳、随机数),从而确保缓存键的稳定性。
64
+
65
+ ```typescript
66
+ const myPostFetch = createCachedFetch({
67
+ cache,
68
+ config: {
69
+ methods: ['GET', 'POST'], // 允许缓存 POST
70
+ body: {
71
+ exclude: ['timestamp', 'nonce'] // 生成缓存键时忽略这些动态字段
72
+ },
73
+ cacheRules: [
74
+ { method: 'POST', path: '/api/v1/query' } // 仅对特定的 POST 接口生效
75
+ ],
76
+ forceCache: true // 对于 POST 请求,后端通常不发 Cache-Control,建议开启强制缓存
77
+ }
78
+ });
79
+ ```
80
+
81
+ ## 配置详解:`SiteCacheConfig`
82
+
83
+ | 配置项 | 类型 | 说明 |
84
+ | :--- | :--- | :--- |
85
+ | `methods` | `string[]` | 允许缓存的 HTTP 方法列表。默认仅为 `['GET', 'HEAD']`。 |
86
+ | `cacheRules` | `CacheRule[]` | 精细化拦截规则。如果配置,请求必须匹配其中至少一条规则才会被缓存。 |
87
+ | `query` | `KeyFilterConfig` | URL 查询参数过滤(`include` 白名单 / `exclude` 黑名单)。 |
88
+ | `headers` | `KeyFilterConfig` | 请求头过滤。 |
89
+ | `cookies` | `KeyFilterConfig` | Cookie 字段过滤。 |
90
+ | `body` | `KeyFilterConfig` | **仅限 JSON** 的请求体字段过滤。 |
91
+ | `staleIfError`| `boolean` | 网络请求失败时,是否强制返回本地过期的旧缓存。 |
92
+ | `forceCache` | `boolean` | 是否无视源站指令强制执行缓存,常用于离线应用。 |
93
+
94
+ ### `CacheRule` 规则对象
95
+
96
+ - `method`: 匹配的 HTTP 方法。
97
+ - `path`: 路径匹配(支持**正则表达式**、**Glob 通配符**、**数组格式**或**前缀匹配**)。
98
+ - `query`: 键值对匹配。值可以是 `string`(全等/Glob匹配)、`true`(参数必须存在)、`false`(参数必须不存在)、或 `RegExp`(正则匹配)。
99
+ - `body`: Body 内容匹配(支持**正则表达式**、**Glob 通配符**或**数组格式**)。
100
+
101
+ ### 模式匹配说明
102
+
103
+ `@isdk/proxy` 为所有可配置字段提供强大的模式匹配能力:
104
+
105
+ | 模式类型 | 示例 | 说明 |
106
+ | :--- | :--- | :--- |
107
+ | **正则表达式** | `/api/v[12]/.*/i` | JavaScript 正则(JSON 中用字符串表示,如 `"/api/v[12]/.*/i"`) |
108
+ | **Glob 通配符** | `/**/*.json` | 文件路径风格通配符匹配 |
109
+ | **否定模式** | `['!/api/private/**', '/api/**']` | 排除匹配(以 `!` 开头) |
110
+ | **数组格式** | `['/api/v1/*', '/api/v2/*']` | 多模式组合(OR 逻辑,负向优先) |
111
+ | **布尔值** | `true` / `false` | 用于 query 参数:必须存在/不存在 |
112
+
113
+ **高级模式匹配示例:**
114
+
115
+ ```typescript
116
+ const myFetch = createCachedFetch({
117
+ cache,
118
+ config: {
119
+ cacheRules: [
120
+ {
121
+ path: ['/api/v1/items/*', '!/api/v1/items/private/*'], // v1 items,排除 private
122
+ query: {
123
+ format: '/^(json|xml)$/', // 正则匹配 format 参数
124
+ 'page*': true // Glob:任何以 page 开头的参数必须存在
125
+ },
126
+ body: /\"action\"\s*:\s*\"query\"/ // 正则匹配 Body 内容
127
+ }
128
+ ]
129
+ }
130
+ });
59
131
  ```
60
132
 
61
133
  ## 适配器 (Adapters)
62
134
 
63
135
  `@isdk/proxy` 旨在成为环境无关的纯净核心。虽然核心库保持纯粹,但你可以轻松集成或找到针对特定环境的适配器:
64
136
 
65
- - **MSW 适配器**: 参见 `@isdk/proxy-msw` (独立包),将此缓存引擎作为 MSW 拦截器使用。
137
+ - **HTTP 代理服务器 (Node.js)**: 参见 [@isdk/proxy-server](https://www.npmjs.com/package/@isdk/proxy-server)(独立包),用于启动独立的 HTTP 缓存代理服务器。
138
+ - **Crawlee 适配器**: 参见 [@isdk/proxy-crawlee](https://www.npmjs.com/package/@isdk/proxy-crawlee)(独立包),用于集成到 Crawlee 网页爬虫生命周期中。
139
+ - **MSW 适配器**: 参见 `@isdk/proxy-msw`(独立包),将此缓存引擎作为 MSW 拦截器使用。
66
140
  - **Axios 适配器**: 可以通过将 Axios 配置转换为 Web 标准 `Request` 轻松实现。
67
- - **Crawlee 适配器**: 能够集成到爬虫生命周期中,减少重复抓取。
68
141
 
69
142
  ## 架构设计详解
70
143
 
@@ -124,6 +197,115 @@ const data = await response.json();
124
197
  - **`options.maxMemorySize`**: 响应体进入内存 (L1) 的大小阈值(字节),超过此大小的文件将直接进入磁盘流传输(默认 `1048576` 即 1MB)。
125
198
  - **`options.storagePath`**: 磁盘 L2 缓存(cacache)的物理存储路径(默认为操作系统的临时目录)。
126
199
 
200
+ ### 工具函数
201
+
202
+ 导出以下工具函数供高级用法:
203
+
204
+ #### `isMatch(pattern, value, usePrefix?)`
205
+
206
+ 通用模式匹配函数。支持正则表达式、Glob、数组模式(含否定)和字符串前缀/精确匹配。
207
+
208
+ - **`pattern`**: `string | RegExp | (string | RegExp)[]`
209
+ - **`value`**: 要测试的字符串
210
+ - **`usePrefix`**: 对于普通字符串,是否使用前缀匹配而非精确匹配(默认:`false`)
211
+ - **返回值**: `boolean`
212
+
213
+ ```typescript
214
+ import { isMatch } from '@isdk/proxy';
215
+
216
+ isMatch('/api/v[12]/.*', '/api/v1/users'); // 正则表达式
217
+ isMatch('/api/**/*.json', '/api/v1/data.json'); // Glob 通配符
218
+ isMatch(['!/private/**', '/api/**'], '/api/data'); // 否定模式
219
+ ```
220
+
221
+ #### `isGlob(pattern)`
222
+
223
+ 判断字符串是否为 Glob 语法。
224
+
225
+ - **`pattern`**: `string`
226
+ - **返回值**: `boolean`
227
+
228
+ #### `getSiteConfig(urlString, proxyConfig)`
229
+
230
+ 根据 URL 获取对应的站点级缓存配置。
231
+
232
+ - **`urlString`**: 完整的请求 URL
233
+ - **`proxyConfig`**: 包含 `sites` 和 `default` 配置的 `ProxyConfig` 对象
234
+ - **返回值**: `SiteCacheConfig`
235
+
236
+ ```typescript
237
+ import { getSiteConfig } from '@isdk/proxy';
238
+
239
+ const config = getSiteConfig('https://api.example.com/data', {
240
+ default: { methods: ['GET'] },
241
+ sites: {
242
+ 'api.example.com': { methods: ['GET', 'POST'], forceCache: true },
243
+ '/internal/': { staleIfError: true } // 前缀匹配
244
+ }
245
+ });
246
+ ```
247
+
248
+ #### `isAllowed(key, config, defaultAllowed?)`
249
+
250
+ 判断指定的键是否允许参与缓存指纹计算。
251
+
252
+ - **`key`**: 要检查的键名
253
+ - **`config`**: `KeyFilterConfig` 对象,支持 `include`(白名单)或 `exclude`(黑名单)
254
+ - **`defaultAllowed`**: 可选参数。当没有配置或配置未命中时使用的默认值
255
+ - **返回值**: `boolean | undefined`
256
+
257
+ **优先级逻辑**:
258
+
259
+ 1. `exclude` 命中 → 直接返回 `false`(优先级最高)
260
+ 2. `include` 存在且命中 → 返回 `true`
261
+ 3. `include` 存在但不命中 → 返回 `false`
262
+ 4. 都没有配置 → 使用 `defaultAllowed`(未传则返回 `undefined`)
263
+
264
+ ```typescript
265
+ import { isAllowed } from '@isdk/proxy';
266
+
267
+ // 无配置
268
+ isAllowed('key'); // undefined (falsy)
269
+
270
+ // 白名单
271
+ isAllowed('id', { include: ['id', 'name'] }); // true
272
+ isAllowed('email', { include: ['id', 'name'] }); // false
273
+
274
+ // 黑名单
275
+ isAllowed('password', { exclude: ['password'] }); // false
276
+ isAllowed('name', { exclude: ['password'] }); // undefined (falsy)
277
+
278
+ // 需要 defaultAllowed 来设置默认值
279
+ isAllowed('name', { exclude: ['password'] }, true); // true
280
+ ```
281
+
282
+ #### `extractData(source, config, defaultAllowed?)`
283
+
284
+ 从源对象中根据过滤配置提取数据并标准化。用于生成缓存指纹。
285
+
286
+ - **`source`**: 原始数据对象(Query、Headers、Cookies 等)
287
+ - **`config`**: `KeyFilterConfig` 对象
288
+ - **`defaultAllowed`**: 可选参数。当没有配置或配置未命中时,是否允许提取(默认 `false`)
289
+ - **返回值**: `Record<string, string[]>` 标准化后的数据,键为小写,值为排序后的数组
290
+
291
+ ```typescript
292
+ import { extractData } from '@isdk/proxy';
293
+
294
+ const headers = { 'Content-Type': 'application/json', 'X-Request-Id': '123' };
295
+
296
+ // 默认不提取任何键
297
+ extractData(headers); // {}
298
+
299
+ // 提取所有键
300
+ extractData(headers, undefined, true); // { 'content-type': ['application/json'], 'x-request-id': ['123'] }
301
+
302
+ // 白名单
303
+ extractData(headers, { include: ['content-type'] }); // { 'content-type': ['application/json'] }
304
+
305
+ // 黑名单
306
+ extractData(headers, { include: ['*'], exclude: ['x-request-id'] }, true); // { 'content-type': ['application/json'] }
307
+ ```
308
+
127
309
  ### 缓存状态标头 (Cache Status Headers)
128
310
 
129
311
  由 `@isdk/proxy` 处理并返回的所有 `Response`,其 Headers 中都会注入 `x-proxy-cache` 字段以便观测生命周期,可能的值有:
package/README.md CHANGED
@@ -15,6 +15,8 @@ In high-concurrency environments—like **API Proxies**, **Web Scrapers**, or **
15
15
  ## Key Features
16
16
 
17
17
  - **🚀 Hybrid Multi-tier Cache**: Extreme speed with L1 (LRU Memory) and persistence with L2 (Content Addressable Disk via `cacache`).
18
+ - **📥 HTTP POST & Method Support**: Full support for caching POST, PUT, and other methods with intelligent request body fingerprinting.
19
+ - **🎯 Precision Filtering**: Fine-grained `cacheRules` to intercept specific paths or query parameters.
18
20
  - **🌊 Streaming Native**: Fully stream-based internal pipeline natively prevents Out-Of-Memory (OOM) issues when proxying large files.
19
21
  - **🧠 Intelligent Meta-Residency**: Metadata (Headers, Status, Policy) stays in memory regardless of body size, ensuring nanosecond cache policy evaluations.
20
22
  - **🔄 Stale-While-Revalidate (SWR)**: Serve stale content instantly while updating the cache silently in the background.
@@ -33,6 +35,8 @@ pnpm add @isdk/proxy
33
35
 
34
36
  The primary way to use `@isdk/proxy` is via the `fetchWithCache` function, which can wrap any HTTP request logic.
35
37
 
38
+ ### Basic Usage (GET)
39
+
36
40
  ```typescript
37
41
  import { SmartCache, createCachedFetch } from '@isdk/proxy';
38
42
 
@@ -42,28 +46,98 @@ const cache = new SmartCache({
42
46
  maxMemorySize: 1024 * 1024 // 1MB threshold
43
47
  });
44
48
 
45
- // 2. Create a pre-configured cached fetcher (automatically tracks concurrent requests)
49
+ // 2. Create a pre-configured cached fetcher
46
50
  const myFetch = createCachedFetch({
47
51
  cache,
48
52
  config: {
49
53
  staleIfError: true,
50
- forceCache: false // Set to true to cache everything (ignore no-store) for offline-first apps
51
54
  },
52
55
  backgroundUpdate: true // Enable SWR
53
56
  });
54
57
 
55
- // 3. Use it anywhere in your app!
56
- const request = new Request('https://api.example.com/data');
57
- const response = await myFetch(request, (req) => fetch(req));
58
+ // 3. Use it!
59
+ const response = await myFetch(new Request('https://api.example.com/data'), (req) => fetch(req));
60
+ console.log(response.headers.get('x-proxy-cache'));
61
+ ```
62
+
63
+ ### Advanced Usage: Caching POST Requests
58
64
 
59
- console.log(response.headers.get('x-proxy-cache')); // "MISS", "HIT", "STALE", or "STALE_IF_ERROR"
60
- const data = await response.json();
65
+ You can cache POST/PUT requests by enabling methods and defining body filters to ignore dynamic fields (like timestamps) in the request body.
66
+
67
+ ```typescript
68
+ const myPostFetch = createCachedFetch({
69
+ cache,
70
+ config: {
71
+ methods: ['GET', 'POST'], // Enable POST caching
72
+ body: {
73
+ exclude: ['timestamp', 'nonce'] // Ignore these fields when generating cache keys
74
+ },
75
+ cacheRules: [
76
+ { method: 'POST', path: '/api/v1/query' } // Only cache specific POST endpoints
77
+ ],
78
+ forceCache: true // Often needed for POST if backend doesn't send Cache-Control
79
+ }
80
+ });
81
+ ```
82
+
83
+ ## Configuration: `SiteCacheConfig`
84
+
85
+ | Field | Type | Description |
86
+ | :--- | :--- | :--- |
87
+ | `methods` | `string[]` | List of allowed HTTP methods. Default: `['GET', 'HEAD']`. |
88
+ | `cacheRules` | `CacheRule[]` | Fine-grained rules. If set, a request must match at least one rule to be cached. |
89
+ | `query` | `KeyFilterConfig` | Filters for URL search parameters (`include`/`exclude`). |
90
+ | `headers` | `KeyFilterConfig` | Filters for request headers. |
91
+ | `cookies` | `KeyFilterConfig` | Filters for cookies. |
92
+ | `body` | `KeyFilterConfig` | Filters for JSON request body fields. |
93
+ | `staleIfError`| `boolean` | Serve stale cache on network failure. |
94
+ | `forceCache` | `boolean` | Ignore `no-store` and force caching (useful for offline support). |
95
+
96
+ ### `CacheRule` Object
97
+
98
+ - `method`: HTTP method to match.
99
+ - `path`: URL pathname matching (supports **RegExp**, **Glob**, **Array**, or **prefix match**).
100
+ - `query`: Key-value pairs. Values can be `string` (exact/Glob match), `true` (must exist), `false` (must not exist), or `RegExp`.
101
+ - `body`: Body content matching (supports **RegExp**, **Glob**, or **Array**).
102
+
103
+ ### Pattern Matching
104
+
105
+ `@isdk/proxy` provides powerful pattern matching for all configurable fields:
106
+
107
+ | Pattern Type | Example | Description |
108
+ | :--- | :--- | :--- |
109
+ | **RegExp** | `/api/v[12]/.*/i` | JavaScript RegExp (in JSON, use string like `"/api/v[12]/.*/i"`) |
110
+ | **Glob** | `/**/*.json` | File path style wildcard matching |
111
+ | **Negation** | `['!/api/private/**', '/api/**']` | Exclude patterns (prefixed with `!`, checked first) |
112
+ | **Array** | `['/api/v1/*', '/api/v2/*']` | Multiple patterns (OR logic, negative takes precedence) |
113
+ | **Boolean** | `true` / `false` | For query params: must/must not exist |
114
+
115
+ **Example with advanced pattern matching:**
116
+
117
+ ```typescript
118
+ const myFetch = createCachedFetch({
119
+ cache,
120
+ config: {
121
+ cacheRules: [
122
+ {
123
+ path: ['/api/v1/items/*', '!/api/v1/items/private/*'], // v1 items, exclude private
124
+ query: {
125
+ format: '/^(json|xml)$/', // Regex for format param
126
+ 'page*': true // Glob: any param starting with 'page' must exist
127
+ },
128
+ body: /\"action\"\s*:\s*\"query\"/ // Regex body match
129
+ }
130
+ ]
131
+ }
132
+ });
61
133
  ```
62
134
 
63
135
  ## Adapters
64
136
 
65
137
  `@isdk/proxy` is designed to be framework-agnostic. While the core library is pure, you can find (or build) adapters for specific environments:
66
138
 
139
+ - **HTTP Caching Proxy Server (Node.js)**: See [@isdk/proxy-server](https://www.npmjs.com/package/@isdk/proxy-server) (separate package) for running a standalone HTTP forward proxy.
140
+ - **Crawlee Adapter**: See [@isdk/proxy-crawlee](https://www.npmjs.com/package/@isdk/proxy-crawlee) (separate package) for integrating with Crawlee web scraping lifecycle.
67
141
  - **MSW Adapter**: See `@isdk/proxy-msw` (separate package) to use this caching engine as an MSW interceptor.
68
142
  - **Axios Adapter**: Easily implemented by converting Axios config to Web `Request`.
69
143
 
@@ -116,9 +190,119 @@ The hybrid multi-tier storage engine.
116
190
  - **`options.maxMemorySize`**: Threshold (in bytes) for offloading bodies to disk (default `1048576`, i.e., 1MB).
117
191
  - **`options.storagePath`**: Disk storage path for the `cacache` engine (defaults to a system temp folder).
118
192
 
193
+ ### Utility Functions
194
+
195
+ Exported from `@isdk/proxy` for advanced usage:
196
+
197
+ #### `isMatch(pattern, value, usePrefix?)`
198
+
199
+ Universal pattern matching function. Supports RegExp, Glob, array patterns (with negation), and string prefix/exact matching.
200
+
201
+ - **`pattern`**: `string | RegExp | (string | RegExp)[]`
202
+ - **`value`**: The string to test against
203
+ - **`usePrefix`**: For plain strings, use prefix match instead of exact match (default: `false`)
204
+ - **Returns**: `boolean`
205
+
206
+ ```typescript
207
+ import { isMatch } from '@isdk/proxy';
208
+
209
+ isMatch('/api/v[12]/.*', '/api/v1/users'); // RegExp
210
+ isMatch('/api/**/*.json', '/api/v1/data.json'); // Glob
211
+ isMatch(['!/private/**', '/api/**'], '/api/data'); // Negation
212
+ ```
213
+
214
+ #### `isGlob(pattern)`
215
+
216
+ Check if a pattern is Glob syntax.
217
+
218
+ - **`pattern`**: `string`
219
+ - **Returns**: `boolean`
220
+
221
+ #### `getSiteConfig(urlString, proxyConfig)`
222
+
223
+ Get the site-specific cache configuration for a given URL.
224
+
225
+ - **`urlString`**: Full URL to match
226
+ - **`proxyConfig`**: `ProxyConfig` object with `sites` and `default` config
227
+ - **Returns**: `SiteCacheConfig`
228
+
229
+ ```typescript
230
+ import { getSiteConfig } from '@isdk/proxy';
231
+
232
+ const config = getSiteConfig('https://api.example.com/data', {
233
+ default: { methods: ['GET'] },
234
+ sites: {
235
+ 'api.example.com': { methods: ['GET', 'POST'], forceCache: true },
236
+ '/internal/': { staleIfError: true } // prefix match
237
+ }
238
+ });
239
+ ```
240
+
241
+ #### `isAllowed(key, config, defaultAllowed?)`
242
+
243
+ Check if a key is allowed to participate in cache key fingerprinting.
244
+
245
+ - **`key`**: The key name to check
246
+ - **`config`**: `KeyFilterConfig` with `include` (whitelist) or `exclude` (blacklist)
247
+ - **`defaultAllowed`**: Optional. Default value when no config or no match
248
+ - **Returns**: `boolean | undefined`
249
+
250
+ **Priority Logic**:
251
+
252
+ 1. `exclude` hit → returns `false` (highest priority)
253
+ 2. `include` exists and hits → returns `true`
254
+ 3. `include` exists but no hit → returns `false`
255
+ 4. No config → uses `defaultAllowed` (returns `undefined` if not provided)
256
+
257
+ ```typescript
258
+ import { isAllowed } from '@isdk/proxy';
259
+
260
+ // No config
261
+ isAllowed('key'); // undefined (falsy)
262
+
263
+ // Whitelist
264
+ isAllowed('id', { include: ['id', 'name'] }); // true
265
+ isAllowed('email', { include: ['id', 'name'] }); // false
266
+
267
+ // Blacklist
268
+ isAllowed('password', { exclude: ['password'] }); // false
269
+ isAllowed('name', { exclude: ['password'] }); // undefined (falsy)
270
+
271
+ // Need defaultAllowed to set default
272
+ isAllowed('name', { exclude: ['password'] }, true); // true
273
+ ```
274
+
275
+ #### `extractData(source, config, defaultAllowed?)`
276
+
277
+ Extract and normalize data from a source object based on filter config. Used for generating cache fingerprints.
278
+
279
+ - **`source`**: Original data object (Query, Headers, Cookies, etc.)
280
+ - **`config`**: `KeyFilterConfig` object
281
+ - **`defaultAllowed`**: Optional. Whether to allow extraction when no config or no match (default `false`)
282
+ - **Returns**: `Record<string, string[]>` normalized data with lowercase keys and sorted array values
283
+
284
+ ```typescript
285
+ import { extractData } from '@isdk/proxy';
286
+
287
+ const headers = { 'Content-Type': 'application/json', 'X-Request-Id': '123' };
288
+
289
+ // No extraction by default
290
+ extractData(headers); // {}
291
+
292
+ // Extract all keys
293
+ extractData(headers, undefined, true); // { 'content-type': ['application/json'], 'x-request-id': ['123'] }
294
+
295
+ // Whitelist
296
+ extractData(headers, { include: ['content-type'] }); // { 'content-type': ['application/json'] }
297
+
298
+ // Blacklist
299
+ extractData(headers, { include: ['*'], exclude: ['x-request-id'] }, true); // { 'content-type': ['application/json'] }
300
+ ```
301
+
119
302
  ### Cache Status Headers
120
303
 
121
304
  Every response processed by `@isdk/proxy` will include an `x-proxy-cache` header indicating its lifecycle:
305
+
122
306
  - `HIT`: Served entirely from L1 or L2 cache.
123
307
  - `MISS`: Bypassed cache and fetched from the origin server.
124
308
  - `STALE`: Served from stale cache while a background update was initiated (SWR).