@isdk/proxy 0.1.1 โ†’ 0.1.3

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.
Files changed (36) hide show
  1. package/README.cn.md +249 -9
  2. package/README.md +249 -7
  3. package/dist/index.d.mts +374 -41
  4. package/dist/index.d.ts +374 -41
  5. package/dist/index.js +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/docs/README.md +249 -7
  8. package/docs/classes/OfflineCacheMissError.md +426 -0
  9. package/docs/classes/SmartCache.md +81 -13
  10. package/docs/functions/createCachedFetch.md +1 -1
  11. package/docs/functions/createFetchWithCache.md +1 -1
  12. package/docs/functions/extractData.md +34 -5
  13. package/docs/functions/fetchWithCache.md +18 -9
  14. package/docs/functions/generateCacheKey.md +34 -4
  15. package/docs/functions/getSiteConfig.md +39 -0
  16. package/docs/functions/isAllowed.md +35 -8
  17. package/docs/functions/isCacheable.md +27 -0
  18. package/docs/functions/isGlob.md +23 -0
  19. package/docs/functions/isMatch.md +44 -0
  20. package/docs/functions/prefetch.md +33 -0
  21. package/docs/globals.md +15 -0
  22. package/docs/interfaces/BodyFilterConfig.md +77 -0
  23. package/docs/interfaces/CacheEntry.md +9 -9
  24. package/docs/interfaces/CacheMetadata.md +8 -8
  25. package/docs/interfaces/CacheRule.md +80 -0
  26. package/docs/interfaces/FetchWithCacheContext.md +44 -16
  27. package/docs/interfaces/FetchWithCacheOptions.md +40 -12
  28. package/docs/interfaces/KeyFilterConfig.md +11 -7
  29. package/docs/interfaces/PrefetchOptions.md +107 -0
  30. package/docs/interfaces/PrefetchRequest.md +31 -0
  31. package/docs/interfaces/PrefetchResult.md +47 -0
  32. package/docs/interfaces/ProxyConfig.md +4 -4
  33. package/docs/interfaces/SiteCacheConfig.md +56 -11
  34. package/docs/interfaces/SmartCacheOptions.md +32 -6
  35. package/docs/variables/OfflineCacheMissErrorCode.md +18 -0
  36. package/package.json +5 -3
package/docs/README.md CHANGED
@@ -19,6 +19,8 @@ In high-concurrency environmentsโ€”like **API Proxies**, **Web Scrapers**, or **
19
19
  ## Key Features
20
20
 
21
21
  - **๐Ÿš€ Hybrid Multi-tier Cache**: Extreme speed with L1 (LRU Memory) and persistence with L2 (Content Addressable Disk via `cacache`).
22
+ - **๐Ÿ“ฅ HTTP POST & Method Support**: Full support for caching POST, PUT, and other methods with intelligent request body fingerprinting.
23
+ - **๐ŸŽฏ Precision Filtering**: Fine-grained `cacheRules` to intercept specific paths or query parameters.
22
24
  - **๐ŸŒŠ Streaming Native**: Fully stream-based internal pipeline natively prevents Out-Of-Memory (OOM) issues when proxying large files.
23
25
  - **๐Ÿง  Intelligent Meta-Residency**: Metadata (Headers, Status, Policy) stays in memory regardless of body size, ensuring nanosecond cache policy evaluations.
24
26
  - **๐Ÿ”„ Stale-While-Revalidate (SWR)**: Serve stale content instantly while updating the cache silently in the background.
@@ -37,6 +39,8 @@ pnpm add @isdk/proxy
37
39
 
38
40
  The primary way to use `@isdk/proxy` is via the `fetchWithCache` function, which can wrap any HTTP request logic.
39
41
 
42
+ ### Basic Usage (GET)
43
+
40
44
  ```typescript
41
45
  import { SmartCache, createCachedFetch } from '@isdk/proxy';
42
46
 
@@ -46,28 +50,112 @@ const cache = new SmartCache({
46
50
  maxMemorySize: 1024 * 1024 // 1MB threshold
47
51
  });
48
52
 
49
- // 2. Create a pre-configured cached fetcher (automatically tracks concurrent requests)
53
+ // 2. Create a pre-configured cached fetcher
50
54
  const myFetch = createCachedFetch({
51
55
  cache,
52
56
  config: {
53
57
  staleIfError: true,
54
- forceCache: false // Set to true to cache everything (ignore no-store) for offline-first apps
55
58
  },
56
59
  backgroundUpdate: true // Enable SWR
57
60
  });
58
61
 
59
- // 3. Use it anywhere in your app!
60
- const request = new Request('https://api.example.com/data');
61
- const response = await myFetch(request, (req) => fetch(req));
62
+ // 3. Use it!
63
+ const response = await myFetch(new Request('https://api.example.com/data'), (req) => fetch(req));
64
+ console.log(response.headers.get('x-proxy-cache'));
65
+ ```
66
+
67
+ ### Advanced Usage: Caching POST Requests
68
+
69
+ You can cache POST/PUT requests by enabling methods and defining body filters to ignore dynamic fields (like timestamps) in the request body.
70
+
71
+ ```typescript
72
+ const myPostFetch = createCachedFetch({
73
+ cache,
74
+ config: {
75
+ methods: ['GET', 'POST'], // Enable POST caching
76
+ body: {
77
+ exclude: ['timestamp', 'nonce'] // Ignore these fields when generating cache keys
78
+ },
79
+ cacheRules: [
80
+ { method: 'POST', path: '/api/v1/query' } // Only cache specific POST endpoints
81
+ ],
82
+ forceCache: true // Often needed for POST if backend doesn't send Cache-Control
83
+ }
84
+ });
85
+ ```
86
+
87
+ ## Configuration: `SiteCacheConfig`
88
+
89
+ | Field | Type | Description |
90
+ | :--- | :--- | :--- |
91
+ | `methods` | `string[]` | List of allowed HTTP methods. Default: `['GET', 'HEAD']`. |
92
+ | `cacheRules` | `CacheRule[]` | Fine-grained rules. If set, a request must match at least one rule to be cached. |
93
+ | `query` | `KeyFilterConfig` | Filters for URL search parameters (`include`/`exclude`). |
94
+ | `headers` | `KeyFilterConfig` | Filters for request headers. |
95
+ | `cookies` | `KeyFilterConfig` | Filters for cookies. |
96
+ | `body` | `KeyFilterConfig` | Filters for request body fields. For JSON, supports field-level filtering; also supports extracting key data via `extract` regex. |
97
+ | `staleIfError`| `boolean` | Serve stale cache on network failure. |
98
+ | `forceCache` | `boolean` | Ignore `no-store` and force caching (useful for offline support). |
99
+ | `offline` | `boolean` | Offline mode. Only reads from cache; throws `OfflineCacheMissError` if no cache exists. |
100
+
101
+ ### `CacheRule` Object
102
+
103
+ - `method`: HTTP method to match.
104
+ - `path`: URL pathname matching (supports **RegExp**, **Glob**, **Array**, or **prefix match**).
105
+ - `query`: Key-value pairs. Values can be `string` (exact/Glob match), `true` (must exist), `false` (must not exist), or `RegExp`.
106
+ - `bodyType`: Match body type. Supports `'json'`, `'text'`, `'binary'`.
107
+ - `body`: Body content matching (supports **RegExp**, **Glob**, or **Array**).
108
+
109
+ ---
110
+
111
+ ### `fetchWithCache` Advanced Options
112
+
113
+ In addition to `SiteCacheConfig`, `fetchWithCache` supports the following control options:
114
+
115
+ | Option | Type | Description |
116
+ | :--- | :--- | :--- |
117
+ | `backgroundUpdate` | `boolean` | Whether to enable background async update (SWR). Default is `true`. |
118
+ | `onBackgroundUpdate`| `function` | Callback that receives the update Promise when a background update is triggered. Useful for task tracking. |
119
+ | `generateKey` | `function` | Custom cache key generation function. |
120
+
121
+ ### Pattern Matching
122
+
123
+ `@isdk/proxy` provides powerful pattern matching for all configurable fields:
124
+
125
+ | Pattern Type | Example | Description |
126
+ | :--- | :--- | :--- |
127
+ | **RegExp** | `/api/v[12]/.*/i` | JavaScript RegExp (in JSON, use string like `"/api/v[12]/.*/i"`) |
128
+ | **Glob** | `/**/*.json` | File path style wildcard matching |
129
+ | **Negation** | `['!/api/private/**', '/api/**']` | Exclude patterns (prefixed with `!`, checked first) |
130
+ | **Array** | `['/api/v1/*', '/api/v2/*']` | Multiple patterns (OR logic, negative takes precedence) |
131
+ | **Boolean** | `true` / `false` | For query params: must/must not exist |
62
132
 
63
- console.log(response.headers.get('x-proxy-cache')); // "MISS", "HIT", "STALE", or "STALE_IF_ERROR"
64
- const data = await response.json();
133
+ **Example with advanced pattern matching:**
134
+
135
+ ```typescript
136
+ const myFetch = createCachedFetch({
137
+ cache,
138
+ config: {
139
+ cacheRules: [
140
+ {
141
+ path: ['/api/v1/items/*', '!/api/v1/items/private/*'], // v1 items, exclude private
142
+ query: {
143
+ format: '/^(json|xml)$/', // Regex for format param
144
+ 'page*': true // Glob: any param starting with 'page' must exist
145
+ },
146
+ body: /\"action\"\s*:\s*\"query\"/ // Regex body match
147
+ }
148
+ ]
149
+ }
150
+ });
65
151
  ```
66
152
 
67
153
  ## Adapters
68
154
 
69
155
  `@isdk/proxy` is designed to be framework-agnostic. While the core library is pure, you can find (or build) adapters for specific environments:
70
156
 
157
+ - **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.
158
+ - **Crawlee Adapter**: See [@isdk/proxy-crawlee](https://www.npmjs.com/package/@isdk/proxy-crawlee) (separate package) for integrating with Crawlee web scraping lifecycle.
71
159
  - **MSW Adapter**: See `@isdk/proxy-msw` (separate package) to use this caching engine as an MSW interceptor.
72
160
  - **Axios Adapter**: Easily implemented by converting Axios config to Web `Request`.
73
161
 
@@ -120,9 +208,163 @@ The hybrid multi-tier storage engine.
120
208
  - **`options.maxMemorySize`**: Threshold (in bytes) for offloading bodies to disk (default `1048576`, i.e., 1MB).
121
209
  - **`options.storagePath`**: Disk storage path for the `cacache` engine (defaults to a system temp folder).
122
210
 
211
+ ### Utility Functions
212
+
213
+ Exported from `@isdk/proxy` for advanced usage:
214
+
215
+ #### `isMatch(pattern, value, usePrefix?)`
216
+
217
+ Universal pattern matching function. Supports RegExp, Glob, array patterns (with negation), and string prefix/exact matching.
218
+
219
+ - **`pattern`**: `string | RegExp | (string | RegExp)[]`
220
+ - **`value`**: The string to test against
221
+ - **`usePrefix`**: For plain strings, use prefix match instead of exact match (default: `false`)
222
+ - **Returns**: `boolean`
223
+
224
+ ```typescript
225
+ import { isMatch } from '@isdk/proxy';
226
+
227
+ isMatch('/api/v[12]/.*', '/api/v1/users'); // RegExp
228
+ isMatch('/api/**/*.json', '/api/v1/data.json'); // Glob
229
+ isMatch(['!/private/**', '/api/**'], '/api/data'); // Negation
230
+ ```
231
+
232
+ #### `isGlob(pattern)`
233
+
234
+ Check if a pattern is Glob syntax.
235
+
236
+ - **`pattern`**: `string`
237
+ - **Returns**: `boolean`
238
+
239
+ #### `getSiteConfig(urlString, proxyConfig)`
240
+
241
+ Get the site-specific cache configuration for a given URL.
242
+
243
+ - **`urlString`**: Full URL to match
244
+ - **`proxyConfig`**: `ProxyConfig` object with `sites` and `default` config
245
+ - **Returns**: `SiteCacheConfig`
246
+
247
+ ```typescript
248
+ import { getSiteConfig } from '@isdk/proxy';
249
+
250
+ const config = getSiteConfig('https://api.example.com/data', {
251
+ default: { methods: ['GET'] },
252
+ sites: {
253
+ 'api.example.com': { methods: ['GET', 'POST'], forceCache: true },
254
+ '/internal/': { staleIfError: true } // prefix match
255
+ }
256
+ });
257
+ ```
258
+
259
+ #### `isAllowed(key, config, defaultAllowed?)`
260
+
261
+ Check if a key is allowed to participate in cache key fingerprinting.
262
+
263
+ - **`key`**: The key name to check
264
+ - **`config`**: `KeyFilterConfig` with `include` (whitelist) or `exclude` (blacklist)
265
+ - **`defaultAllowed`**: Optional. Default value when no config or no match
266
+ - **Returns**: `boolean | undefined`
267
+
268
+ **Priority Logic**:
269
+
270
+ 1. `exclude` hit โ†’ returns `false` (highest priority)
271
+ 2. `include` exists and hits โ†’ returns `true`
272
+ 3. `include` exists but no hit โ†’ returns `false`
273
+ 4. No config โ†’ uses `defaultAllowed` (returns `undefined` if not provided)
274
+
275
+ ```typescript
276
+ import { isAllowed } from '@isdk/proxy';
277
+
278
+ // No config
279
+ isAllowed('key'); // undefined (falsy)
280
+
281
+ // Whitelist
282
+ isAllowed('id', { include: ['id', 'name'] }); // true
283
+ isAllowed('email', { include: ['id', 'name'] }); // false
284
+
285
+ // Blacklist
286
+ isAllowed('password', { exclude: ['password'] }); // false
287
+ isAllowed('name', { exclude: ['password'] }); // undefined (falsy)
288
+
289
+ // Need defaultAllowed to set default
290
+ isAllowed('name', { exclude: ['password'] }, true); // true
291
+ ```
292
+
293
+ #### `extractData(source, config, defaultAllowed?)`
294
+
295
+ Extract and normalize data from a source object based on filter config. Used for generating cache fingerprints.
296
+
297
+ - **`source`**: Original data object (Query, Headers, Cookies, etc.)
298
+ - **`config`**: `KeyFilterConfig` object
299
+ - **`defaultAllowed`**: Optional. Whether to allow extraction when no config or no match (default `false`)
300
+ - **Returns**: `Record<string, string[]>` normalized data with lowercase keys and sorted array values
301
+
302
+ ```typescript
303
+ import { extractData } from '@isdk/proxy';
304
+
305
+ const headers = { 'Content-Type': 'application/json', 'X-Request-Id': '123' };
306
+
307
+ // No extraction by default
308
+ extractData(headers); // {}
309
+
310
+ // Extract all keys
311
+ extractData(headers, undefined, true); // { 'content-type': ['application/json'], 'x-request-id': ['123'] }
312
+
313
+ // Whitelist
314
+ extractData(headers, { include: ['content-type'] }); // { 'content-type': ['application/json'] }
315
+
316
+ // Blacklist
317
+ extractData(headers, { include: ['*'], exclude: ['x-request-id'] }, true); // { 'content-type': ['application/json'] }
318
+ ```
319
+
320
+ ### `prefetch(options)`
321
+
322
+ Pre-cache function that fetches and stores a list of URLs into cache ahead of time.
323
+
324
+ - **`urls`**: `PrefetchRequest[]`. Each object contains `url` and optional `request` config.
325
+ - **`config`**: `ProxyConfig` full configuration.
326
+ - **`cache`**: `SmartCache` instance.
327
+ - **`concurrency`**: Concurrency limit (default `3`).
328
+ - **`onProgress`**: Progress callback `(completed, total, url) => void`.
329
+
330
+ ```typescript
331
+ import { prefetch } from '@isdk/proxy';
332
+
333
+ const result = await prefetch({
334
+ urls: [
335
+ { url: 'https://api.example.com/page1' },
336
+ { url: 'https://api.example.com/api2', request: { method: 'POST', body: '...' } }
337
+ ],
338
+ config,
339
+ cache,
340
+ onProgress: (c, t, url) => console.log(`Progress: ${c}/${t} - ${url}`)
341
+ });
342
+ console.log(`Succeeded: ${result.succeeded}, Failed: ${result.failed}`);
343
+ ```
344
+
345
+ ### Error Handling: `OfflineCacheMissError`
346
+
347
+ When `offline: true` mode is enabled and a request does not hit the cache, this error is thrown.
348
+
349
+ - **`name`**: `OfflineCacheMissError`
350
+ - **`code`**: `ERR_OFFLINE_CACHE_MISS` (can be imported as `OfflineCacheMissErrorCode`)
351
+
352
+ ```typescript
353
+ import { OfflineCacheMissError } from '@isdk/proxy';
354
+
355
+ try {
356
+ await myFetch(request);
357
+ } catch (e) {
358
+ if (e instanceof OfflineCacheMissError) {
359
+ // Handle offline cache miss
360
+ }
361
+ }
362
+ ```
363
+
123
364
  ### Cache Status Headers
124
365
 
125
366
  Every response processed by `@isdk/proxy` will include an `x-proxy-cache` header indicating its lifecycle:
367
+
126
368
  - `HIT`: Served entirely from L1 or L2 cache.
127
369
  - `MISS`: Bypassed cache and fetched from the origin server.
128
370
  - `STALE`: Served from stale cache while a background update was initiated (SWR).