recker 1.0.26 → 1.0.27

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 (171) hide show
  1. package/dist/browser/browser/cache.d.ts +40 -0
  2. package/dist/browser/browser/cache.js +199 -0
  3. package/dist/browser/browser/crypto.d.ts +24 -0
  4. package/dist/browser/browser/crypto.js +80 -0
  5. package/dist/browser/browser/index.d.ts +31 -0
  6. package/dist/browser/browser/index.js +31 -0
  7. package/dist/browser/browser/recker.d.ts +26 -0
  8. package/dist/browser/browser/recker.js +61 -0
  9. package/dist/browser/cache/basic-file-storage.d.ts +12 -0
  10. package/dist/browser/cache/basic-file-storage.js +50 -0
  11. package/dist/browser/cache/memory-limits.d.ts +20 -0
  12. package/dist/browser/cache/memory-limits.js +96 -0
  13. package/dist/browser/cache/memory-storage.d.ts +132 -0
  14. package/dist/browser/cache/memory-storage.js +454 -0
  15. package/dist/browser/cache.d.ts +40 -0
  16. package/dist/browser/cache.js +199 -0
  17. package/dist/browser/constants/http-status.d.ts +73 -0
  18. package/dist/browser/constants/http-status.js +156 -0
  19. package/dist/browser/cookies/memory-cookie-jar.d.ts +30 -0
  20. package/dist/browser/cookies/memory-cookie-jar.js +210 -0
  21. package/dist/browser/core/client.d.ts +118 -0
  22. package/dist/browser/core/client.js +667 -0
  23. package/dist/browser/core/errors.d.ts +142 -0
  24. package/dist/browser/core/errors.js +308 -0
  25. package/dist/browser/core/index.d.ts +5 -0
  26. package/dist/browser/core/index.js +5 -0
  27. package/dist/browser/core/request-promise.d.ts +23 -0
  28. package/dist/browser/core/request-promise.js +82 -0
  29. package/dist/browser/core/request.d.ts +20 -0
  30. package/dist/browser/core/request.js +76 -0
  31. package/dist/browser/core/response.d.ts +34 -0
  32. package/dist/browser/core/response.js +178 -0
  33. package/dist/browser/crypto.d.ts +24 -0
  34. package/dist/browser/crypto.js +80 -0
  35. package/dist/browser/index.d.ts +31 -0
  36. package/dist/browser/index.js +31 -0
  37. package/dist/browser/plugins/auth/api-key.d.ts +8 -0
  38. package/dist/browser/plugins/auth/api-key.js +27 -0
  39. package/dist/browser/plugins/auth/auth0.d.ts +33 -0
  40. package/dist/browser/plugins/auth/auth0.js +94 -0
  41. package/dist/browser/plugins/auth/aws-sigv4.d.ts +10 -0
  42. package/dist/browser/plugins/auth/aws-sigv4.js +88 -0
  43. package/dist/browser/plugins/auth/azure-ad.d.ts +48 -0
  44. package/dist/browser/plugins/auth/azure-ad.js +152 -0
  45. package/dist/browser/plugins/auth/basic.d.ts +7 -0
  46. package/dist/browser/plugins/auth/basic.js +13 -0
  47. package/dist/browser/plugins/auth/bearer.d.ts +8 -0
  48. package/dist/browser/plugins/auth/bearer.js +17 -0
  49. package/dist/browser/plugins/auth/cognito.d.ts +45 -0
  50. package/dist/browser/plugins/auth/cognito.js +208 -0
  51. package/dist/browser/plugins/auth/digest.d.ts +8 -0
  52. package/dist/browser/plugins/auth/digest.js +100 -0
  53. package/dist/browser/plugins/auth/firebase.d.ts +32 -0
  54. package/dist/browser/plugins/auth/firebase.js +195 -0
  55. package/dist/browser/plugins/auth/github-app.d.ts +36 -0
  56. package/dist/browser/plugins/auth/github-app.js +170 -0
  57. package/dist/browser/plugins/auth/google-service-account.d.ts +49 -0
  58. package/dist/browser/plugins/auth/google-service-account.js +172 -0
  59. package/dist/browser/plugins/auth/index.d.ts +15 -0
  60. package/dist/browser/plugins/auth/index.js +15 -0
  61. package/dist/browser/plugins/auth/mtls.d.ts +37 -0
  62. package/dist/browser/plugins/auth/mtls.js +140 -0
  63. package/dist/browser/plugins/auth/oauth2.d.ts +8 -0
  64. package/dist/browser/plugins/auth/oauth2.js +26 -0
  65. package/dist/browser/plugins/auth/oidc.d.ts +55 -0
  66. package/dist/browser/plugins/auth/oidc.js +222 -0
  67. package/dist/browser/plugins/auth/okta.d.ts +47 -0
  68. package/dist/browser/plugins/auth/okta.js +157 -0
  69. package/dist/browser/plugins/auth.d.ts +1 -0
  70. package/dist/browser/plugins/auth.js +1 -0
  71. package/dist/browser/plugins/cache.d.ts +15 -0
  72. package/dist/browser/plugins/cache.js +486 -0
  73. package/dist/browser/plugins/circuit-breaker.d.ts +13 -0
  74. package/dist/browser/plugins/circuit-breaker.js +100 -0
  75. package/dist/browser/plugins/compression.d.ts +4 -0
  76. package/dist/browser/plugins/compression.js +130 -0
  77. package/dist/browser/plugins/cookie-jar.d.ts +5 -0
  78. package/dist/browser/plugins/cookie-jar.js +72 -0
  79. package/dist/browser/plugins/dedup.d.ts +5 -0
  80. package/dist/browser/plugins/dedup.js +35 -0
  81. package/dist/browser/plugins/graphql.d.ts +13 -0
  82. package/dist/browser/plugins/graphql.js +58 -0
  83. package/dist/browser/plugins/grpc-web.d.ts +79 -0
  84. package/dist/browser/plugins/grpc-web.js +261 -0
  85. package/dist/browser/plugins/hls.d.ts +105 -0
  86. package/dist/browser/plugins/hls.js +395 -0
  87. package/dist/browser/plugins/jsonrpc.d.ts +75 -0
  88. package/dist/browser/plugins/jsonrpc.js +143 -0
  89. package/dist/browser/plugins/logger.d.ts +13 -0
  90. package/dist/browser/plugins/logger.js +108 -0
  91. package/dist/browser/plugins/odata.d.ts +181 -0
  92. package/dist/browser/plugins/odata.js +564 -0
  93. package/dist/browser/plugins/pagination.d.ts +16 -0
  94. package/dist/browser/plugins/pagination.js +105 -0
  95. package/dist/browser/plugins/rate-limit.d.ts +15 -0
  96. package/dist/browser/plugins/rate-limit.js +162 -0
  97. package/dist/browser/plugins/retry.d.ts +14 -0
  98. package/dist/browser/plugins/retry.js +116 -0
  99. package/dist/browser/plugins/scrape.d.ts +21 -0
  100. package/dist/browser/plugins/scrape.js +82 -0
  101. package/dist/browser/plugins/server-timing.d.ts +7 -0
  102. package/dist/browser/plugins/server-timing.js +24 -0
  103. package/dist/browser/plugins/soap.d.ts +72 -0
  104. package/dist/browser/plugins/soap.js +347 -0
  105. package/dist/browser/plugins/xml.d.ts +9 -0
  106. package/dist/browser/plugins/xml.js +194 -0
  107. package/dist/browser/plugins/xsrf.d.ts +9 -0
  108. package/dist/browser/plugins/xsrf.js +48 -0
  109. package/dist/browser/recker.d.ts +26 -0
  110. package/dist/browser/recker.js +61 -0
  111. package/dist/browser/runner/request-runner.d.ts +46 -0
  112. package/dist/browser/runner/request-runner.js +89 -0
  113. package/dist/browser/scrape/document.d.ts +44 -0
  114. package/dist/browser/scrape/document.js +210 -0
  115. package/dist/browser/scrape/element.d.ts +49 -0
  116. package/dist/browser/scrape/element.js +176 -0
  117. package/dist/browser/scrape/extractors.d.ts +16 -0
  118. package/dist/browser/scrape/extractors.js +356 -0
  119. package/dist/browser/scrape/types.d.ts +107 -0
  120. package/dist/browser/scrape/types.js +1 -0
  121. package/dist/browser/transport/fetch.d.ts +11 -0
  122. package/dist/browser/transport/fetch.js +143 -0
  123. package/dist/browser/transport/undici.d.ts +38 -0
  124. package/dist/browser/transport/undici.js +897 -0
  125. package/dist/browser/types/ai.d.ts +267 -0
  126. package/dist/browser/types/ai.js +1 -0
  127. package/dist/browser/types/index.d.ts +351 -0
  128. package/dist/browser/types/index.js +1 -0
  129. package/dist/browser/types/logger.d.ts +16 -0
  130. package/dist/browser/types/logger.js +66 -0
  131. package/dist/browser/types/udp.d.ts +138 -0
  132. package/dist/browser/types/udp.js +1 -0
  133. package/dist/browser/utils/agent-manager.d.ts +29 -0
  134. package/dist/browser/utils/agent-manager.js +160 -0
  135. package/dist/browser/utils/body.d.ts +10 -0
  136. package/dist/browser/utils/body.js +148 -0
  137. package/dist/browser/utils/charset.d.ts +15 -0
  138. package/dist/browser/utils/charset.js +169 -0
  139. package/dist/browser/utils/concurrency.d.ts +20 -0
  140. package/dist/browser/utils/concurrency.js +120 -0
  141. package/dist/browser/utils/dns.d.ts +6 -0
  142. package/dist/browser/utils/dns.js +26 -0
  143. package/dist/browser/utils/header-parser.d.ts +94 -0
  144. package/dist/browser/utils/header-parser.js +617 -0
  145. package/dist/browser/utils/html-cleaner.d.ts +1 -0
  146. package/dist/browser/utils/html-cleaner.js +21 -0
  147. package/dist/browser/utils/link-header.d.ts +69 -0
  148. package/dist/browser/utils/link-header.js +190 -0
  149. package/dist/browser/utils/optional-require.d.ts +19 -0
  150. package/dist/browser/utils/optional-require.js +105 -0
  151. package/dist/browser/utils/progress.d.ts +8 -0
  152. package/dist/browser/utils/progress.js +82 -0
  153. package/dist/browser/utils/request-pool.d.ts +22 -0
  154. package/dist/browser/utils/request-pool.js +101 -0
  155. package/dist/browser/utils/sse.d.ts +7 -0
  156. package/dist/browser/utils/sse.js +67 -0
  157. package/dist/browser/utils/streaming.d.ts +17 -0
  158. package/dist/browser/utils/streaming.js +84 -0
  159. package/dist/browser/utils/try-fn.d.ts +3 -0
  160. package/dist/browser/utils/try-fn.js +59 -0
  161. package/dist/browser/utils/user-agent.d.ts +44 -0
  162. package/dist/browser/utils/user-agent.js +100 -0
  163. package/dist/browser/utils/whois.d.ts +32 -0
  164. package/dist/browser/utils/whois.js +246 -0
  165. package/dist/browser/websocket/client.d.ts +65 -0
  166. package/dist/browser/websocket/client.js +313 -0
  167. package/dist/cli/index.d.ts +1 -0
  168. package/dist/cli/index.js +1 -0
  169. package/dist/transport/fetch.d.ts +7 -1
  170. package/dist/transport/fetch.js +58 -76
  171. package/package.json +34 -2
@@ -0,0 +1 @@
1
+ export * from './auth/index.js';
@@ -0,0 +1 @@
1
+ export * from './auth/index.js';
@@ -0,0 +1,15 @@
1
+ import { CacheStorage, CacheStrategy, Plugin, ReckerRequest, CacheEntry } from '../types/index.js';
2
+ export interface CacheOptions {
3
+ storage?: CacheStorage;
4
+ strategy?: CacheStrategy;
5
+ ttl?: number;
6
+ methods?: string[];
7
+ keyGenerator?: (req: ReckerRequest) => string;
8
+ respectCacheControl?: boolean;
9
+ respectVary?: boolean;
10
+ maxStale?: number;
11
+ forceRevalidate?: boolean;
12
+ }
13
+ declare function parseCacheControl(header: string | null): Partial<CacheEntry>;
14
+ export declare function cachePlugin(options?: CacheOptions): Plugin;
15
+ export { parseCacheControl };
@@ -0,0 +1,486 @@
1
+ import { HttpResponse } from '../core/response.js';
2
+ import { HttpRequest } from '../core/request.js';
3
+ import { MemoryStorage } from '../cache/memory-storage.js';
4
+ import { createHash } from 'node:crypto';
5
+ function parseRequestCacheControl(header) {
6
+ if (!header)
7
+ return {};
8
+ const result = {};
9
+ const directives = header.toLowerCase().split(',').map(d => d.trim());
10
+ for (const directive of directives) {
11
+ if (directive.startsWith('max-age=')) {
12
+ result.maxAge = parseInt(directive.slice(8), 10);
13
+ }
14
+ else if (directive.startsWith('min-fresh=')) {
15
+ result.minFresh = parseInt(directive.slice(10), 10);
16
+ }
17
+ else if (directive.startsWith('max-stale')) {
18
+ const equals = directive.indexOf('=');
19
+ if (equals !== -1) {
20
+ result.maxStale = parseInt(directive.slice(equals + 1), 10);
21
+ }
22
+ else {
23
+ result.maxStale = Infinity;
24
+ }
25
+ }
26
+ else if (directive === 'only-if-cached') {
27
+ result.onlyIfCached = true;
28
+ }
29
+ else if (directive === 'no-cache') {
30
+ result.noCache = true;
31
+ }
32
+ else if (directive === 'no-store') {
33
+ result.noStore = true;
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ function parseCacheControl(header) {
39
+ if (!header)
40
+ return {};
41
+ const result = {};
42
+ const directives = header.toLowerCase().split(',').map(d => d.trim());
43
+ for (const directive of directives) {
44
+ if (directive.startsWith('max-age=')) {
45
+ result.maxAge = parseInt(directive.slice(8), 10);
46
+ }
47
+ else if (directive.startsWith('s-maxage=')) {
48
+ result.sMaxAge = parseInt(directive.slice(9), 10);
49
+ }
50
+ else if (directive.startsWith('stale-while-revalidate=')) {
51
+ result.staleWhileRevalidate = parseInt(directive.slice(23), 10);
52
+ }
53
+ else if (directive.startsWith('stale-if-error=')) {
54
+ result.staleIfError = parseInt(directive.slice(15), 10);
55
+ }
56
+ else if (directive === 'no-cache') {
57
+ result.noCache = true;
58
+ }
59
+ else if (directive === 'no-store') {
60
+ result.noStore = true;
61
+ }
62
+ else if (directive === 'must-revalidate') {
63
+ result.mustRevalidate = true;
64
+ }
65
+ else if (directive === 'private') {
66
+ result.isPrivate = true;
67
+ }
68
+ else if (directive === 'public') {
69
+ result.isPublic = true;
70
+ }
71
+ }
72
+ return result;
73
+ }
74
+ function isFresh(entry, now) {
75
+ const age = (now - entry.timestamp) / 1000;
76
+ if (entry.sMaxAge !== undefined) {
77
+ return age < entry.sMaxAge;
78
+ }
79
+ if (entry.maxAge !== undefined) {
80
+ return age < entry.maxAge;
81
+ }
82
+ if (entry.expires !== undefined) {
83
+ return now < entry.expires;
84
+ }
85
+ if (entry.lastModified) {
86
+ const lastModifiedTime = new Date(entry.lastModified).getTime();
87
+ if (!isNaN(lastModifiedTime)) {
88
+ const dateTime = entry.headers['date']
89
+ ? new Date(entry.headers['date']).getTime()
90
+ : entry.timestamp;
91
+ if (!isNaN(dateTime) && dateTime > lastModifiedTime) {
92
+ const timeSinceModified = (dateTime - lastModifiedTime) / 1000;
93
+ const heuristicFreshness = timeSinceModified * 0.1;
94
+ return age < heuristicFreshness;
95
+ }
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ function satisfiesRequestDirectives(entry, now, reqDirectives) {
101
+ const age = (now - entry.timestamp) / 1000;
102
+ const responseMaxAge = entry.sMaxAge ?? entry.maxAge ?? Infinity;
103
+ const currentFreshness = responseMaxAge - age;
104
+ const fresh = isFresh(entry, now);
105
+ if (reqDirectives.maxAge !== undefined && age > reqDirectives.maxAge) {
106
+ return { allowed: false, reason: 'exceeds-request-max-age' };
107
+ }
108
+ if (reqDirectives.minFresh !== undefined) {
109
+ if (!fresh || currentFreshness < reqDirectives.minFresh) {
110
+ return { allowed: false, reason: 'insufficient-freshness' };
111
+ }
112
+ }
113
+ if (reqDirectives.onlyIfCached) {
114
+ return { allowed: true };
115
+ }
116
+ if (!fresh && reqDirectives.maxStale === undefined) {
117
+ return { allowed: false, reason: 'stale-not-acceptable' };
118
+ }
119
+ if (!fresh && reqDirectives.maxStale !== undefined) {
120
+ const staleness = age - responseMaxAge;
121
+ if (reqDirectives.maxStale !== Infinity && staleness > reqDirectives.maxStale) {
122
+ return { allowed: false, reason: 'exceeds-max-stale' };
123
+ }
124
+ }
125
+ return { allowed: true };
126
+ }
127
+ function canServeStale(entry, now) {
128
+ if (!entry.staleWhileRevalidate)
129
+ return false;
130
+ const age = (now - entry.timestamp) / 1000;
131
+ const maxAge = entry.sMaxAge ?? entry.maxAge ?? 0;
132
+ const staleTime = age - maxAge;
133
+ return staleTime < entry.staleWhileRevalidate;
134
+ }
135
+ function createConditionalRequest(req, entry) {
136
+ const conditionalHeaders = new Headers(req.headers);
137
+ if (entry.etag) {
138
+ conditionalHeaders.set('If-None-Match', entry.etag);
139
+ }
140
+ if (entry.lastModified) {
141
+ conditionalHeaders.set('If-Modified-Since', entry.lastModified);
142
+ }
143
+ return new HttpRequest(req.url, {
144
+ method: req.method,
145
+ headers: conditionalHeaders,
146
+ body: req.body,
147
+ signal: req.signal,
148
+ throwHttpErrors: false,
149
+ timeout: req.timeout,
150
+ onUploadProgress: req.onUploadProgress,
151
+ onDownloadProgress: req.onDownloadProgress,
152
+ maxResponseSize: req.maxResponseSize
153
+ });
154
+ }
155
+ async function storeCacheEntry(storage, baseKey, req, entry, ttl, keyGenerator) {
156
+ if (entry.vary) {
157
+ const varyKey = keyGenerator(req, entry.vary);
158
+ await storage.set(varyKey, entry, ttl);
159
+ const varyMarker = {
160
+ ...entry,
161
+ body: '',
162
+ };
163
+ await storage.set(baseKey, varyMarker, ttl);
164
+ }
165
+ else {
166
+ await storage.set(baseKey, entry, ttl);
167
+ }
168
+ }
169
+ async function createCacheEntry(response, body, now) {
170
+ const headers = {};
171
+ response.headers.forEach((v, k) => { headers[k] = v; });
172
+ const cacheControl = parseCacheControl(response.headers.get('Cache-Control'));
173
+ let expires;
174
+ const expiresHeader = response.headers.get('Expires');
175
+ if (expiresHeader) {
176
+ const expiresDate = new Date(expiresHeader);
177
+ if (!isNaN(expiresDate.getTime())) {
178
+ expires = expiresDate.getTime();
179
+ }
180
+ }
181
+ return {
182
+ status: response.status,
183
+ statusText: response.statusText,
184
+ headers,
185
+ body,
186
+ timestamp: now,
187
+ etag: response.headers.get('ETag') || undefined,
188
+ lastModified: response.headers.get('Last-Modified') || undefined,
189
+ vary: response.headers.get('Vary') || undefined,
190
+ expires,
191
+ ...cacheControl
192
+ };
193
+ }
194
+ function createCachedResponse(entry, cacheStatus) {
195
+ const headers = new Headers(entry.headers);
196
+ headers.set('X-Cache', cacheStatus);
197
+ headers.set('X-Cache-Age', String(Math.floor((Date.now() - entry.timestamp) / 1000)));
198
+ if (cacheStatus === 'stale' || cacheStatus === 'stale-error') {
199
+ const warningCode = cacheStatus === 'stale' ? 110 : 111;
200
+ const warningText = cacheStatus === 'stale' ? 'Response is Stale' : 'Revalidation Failed';
201
+ const existingWarning = headers.get('Warning');
202
+ if (existingWarning) {
203
+ headers.set('Warning', `${existingWarning}, ${warningCode} - "${warningText}"`);
204
+ }
205
+ else {
206
+ headers.set('Warning', `${warningCode} - "${warningText}"`);
207
+ }
208
+ }
209
+ const response = new Response(entry.body, {
210
+ status: entry.status,
211
+ statusText: entry.statusText,
212
+ headers
213
+ });
214
+ const httpResponse = new HttpResponse(response);
215
+ return httpResponse;
216
+ }
217
+ export function cachePlugin(options = {}) {
218
+ const storage = options.storage || new MemoryStorage();
219
+ const strategy = options.strategy || 'cache-first';
220
+ const ttl = options.ttl || 60 * 1000;
221
+ const methods = options.methods || ['GET'];
222
+ const respectCacheControl = options.respectCacheControl !== false;
223
+ const forceRevalidate = options.forceRevalidate === true;
224
+ const generateKey = options.keyGenerator || ((req, varyHeaders) => {
225
+ let key = `${req.method}:${req.url}`;
226
+ if (req.method !== 'GET' && req.method !== 'HEAD' && req.body) {
227
+ try {
228
+ const bodyStr = typeof req.body === 'string'
229
+ ? req.body
230
+ : (req.body instanceof Buffer ? req.body.toString() : JSON.stringify(req.body));
231
+ if (bodyStr) {
232
+ const hash = createHash('sha256').update(bodyStr).digest('hex').substring(0, 16);
233
+ key += `:${hash}`;
234
+ }
235
+ }
236
+ catch {
237
+ }
238
+ }
239
+ if (varyHeaders && options.respectVary !== false) {
240
+ const varyHeaderNames = varyHeaders.split(',').map(h => h.trim().toLowerCase());
241
+ const varyParts = [];
242
+ for (const headerName of varyHeaderNames) {
243
+ if (headerName === '*') {
244
+ return `${key}:vary-*:${Date.now()}`;
245
+ }
246
+ const headerValue = req.headers.get(headerName);
247
+ if (headerValue) {
248
+ varyParts.push(`${headerName}=${headerValue}`);
249
+ }
250
+ }
251
+ if (varyParts.length > 0) {
252
+ key += `:vary:${varyParts.join('|')}`;
253
+ }
254
+ }
255
+ return key;
256
+ });
257
+ const cacheMiddleware = async (req, next) => {
258
+ const unsafeMethods = [
259
+ 'POST', 'PUT', 'PATCH', 'DELETE',
260
+ 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK',
261
+ 'LINK', 'UNLINK', 'PURGE'
262
+ ];
263
+ if (unsafeMethods.includes(req.method)) {
264
+ const response = await next(req);
265
+ if (response.ok) {
266
+ await storage.delete(`GET:${req.url}`);
267
+ await storage.delete(`HEAD:${req.url}`);
268
+ }
269
+ return response;
270
+ }
271
+ if (!methods.includes(req.method)) {
272
+ return next(req);
273
+ }
274
+ const reqCacheControl = respectCacheControl
275
+ ? parseRequestCacheControl(req.headers.get('Cache-Control'))
276
+ : {};
277
+ if (respectCacheControl && reqCacheControl.noStore) {
278
+ return next(req);
279
+ }
280
+ if (respectCacheControl) {
281
+ const reqPragma = req.headers.get('Pragma');
282
+ if (reqPragma?.includes('no-cache')) {
283
+ return next(req);
284
+ }
285
+ }
286
+ const baseKey = generateKey(req);
287
+ let key = baseKey;
288
+ const now = Date.now();
289
+ let cachedEntry;
290
+ if (options.respectVary !== false) {
291
+ const baseEntry = await storage.get(baseKey);
292
+ if (baseEntry?.vary) {
293
+ key = generateKey(req, baseEntry.vary);
294
+ cachedEntry = await storage.get(key);
295
+ }
296
+ else if (baseEntry?.body) {
297
+ cachedEntry = baseEntry;
298
+ }
299
+ else {
300
+ cachedEntry = undefined;
301
+ }
302
+ }
303
+ else {
304
+ cachedEntry = await storage.get(key);
305
+ }
306
+ if (reqCacheControl.onlyIfCached) {
307
+ if (!cachedEntry) {
308
+ const response = new Response(null, {
309
+ status: 504,
310
+ statusText: 'Gateway Timeout'
311
+ });
312
+ return new HttpResponse(response);
313
+ }
314
+ const satisfaction = satisfiesRequestDirectives(cachedEntry, now, reqCacheControl);
315
+ if (!satisfaction.allowed) {
316
+ const response = new Response(null, {
317
+ status: 504,
318
+ statusText: 'Gateway Timeout'
319
+ });
320
+ return new HttpResponse(response);
321
+ }
322
+ return createCachedResponse(cachedEntry, isFresh(cachedEntry, now) ? 'hit' : 'stale');
323
+ }
324
+ const hasRequestDirectives = Object.keys(reqCacheControl).length > 0;
325
+ if (cachedEntry && respectCacheControl && hasRequestDirectives && strategy === 'network-first') {
326
+ const satisfaction = satisfiesRequestDirectives(cachedEntry, now, reqCacheControl);
327
+ if (!satisfaction.allowed) {
328
+ cachedEntry = undefined;
329
+ }
330
+ }
331
+ if (strategy === 'network-only') {
332
+ const response = await next(req);
333
+ if (response.ok && respectCacheControl) {
334
+ const cacheControl = parseCacheControl(response.headers.get('Cache-Control'));
335
+ if (!cacheControl.noStore) {
336
+ const clonedResponse = response.raw.clone();
337
+ const body = await clonedResponse.text();
338
+ const entry = await createCacheEntry(response, body, now);
339
+ const entryTtl = (entry.sMaxAge ?? entry.maxAge ?? ttl / 1000) * 1000;
340
+ await storeCacheEntry(storage, baseKey, req, entry, entryTtl, generateKey);
341
+ }
342
+ }
343
+ return response;
344
+ }
345
+ if (strategy === 'rfc-compliant' || strategy === 'revalidate') {
346
+ if (cachedEntry) {
347
+ const fresh = isFresh(cachedEntry, now);
348
+ const satisfaction = satisfiesRequestDirectives(cachedEntry, now, reqCacheControl);
349
+ const hasValidators = cachedEntry.etag || cachedEntry.lastModified;
350
+ if (!satisfaction.allowed && !hasValidators) {
351
+ cachedEntry = undefined;
352
+ }
353
+ else if (fresh && !forceRevalidate && !cachedEntry.noCache && !reqCacheControl.noCache) {
354
+ return createCachedResponse(cachedEntry, 'hit');
355
+ }
356
+ else if (!fresh && reqCacheControl.maxStale !== undefined && !forceRevalidate && !reqCacheControl.noCache) {
357
+ return createCachedResponse(cachedEntry, 'stale');
358
+ }
359
+ }
360
+ if (cachedEntry) {
361
+ const conditionalReq = createConditionalRequest(req, cachedEntry);
362
+ try {
363
+ const response = await next(conditionalReq);
364
+ if (response.status === 304) {
365
+ const updatedEntry = {
366
+ ...cachedEntry,
367
+ timestamp: now,
368
+ etag: response.headers.get('ETag') || cachedEntry.etag,
369
+ lastModified: response.headers.get('Last-Modified') || cachedEntry.lastModified,
370
+ };
371
+ const freshnessTtl = (updatedEntry.sMaxAge ?? updatedEntry.maxAge ?? ttl / 1000) * 1000;
372
+ const storageTtl = Math.max(freshnessTtl, ttl);
373
+ await storage.set(key, updatedEntry, storageTtl);
374
+ return createCachedResponse(updatedEntry, 'revalidated');
375
+ }
376
+ if (response.ok) {
377
+ const cacheControl = parseCacheControl(response.headers.get('Cache-Control'));
378
+ if (!cacheControl.noStore) {
379
+ const clonedResponse = response.raw.clone();
380
+ const body = await clonedResponse.text();
381
+ const entry = await createCacheEntry(response, body, now);
382
+ const freshnessTtl = (entry.sMaxAge ?? entry.maxAge ?? ttl / 1000) * 1000;
383
+ const storageTtl = Math.max(freshnessTtl, ttl);
384
+ await storeCacheEntry(storage, baseKey, req, entry, storageTtl, generateKey);
385
+ }
386
+ }
387
+ return response;
388
+ }
389
+ catch (error) {
390
+ if (cachedEntry.staleIfError && canServeStale(cachedEntry, now)) {
391
+ return createCachedResponse(cachedEntry, 'stale-error');
392
+ }
393
+ throw error;
394
+ }
395
+ }
396
+ const response = await next(req);
397
+ if (response.ok) {
398
+ const cacheControl = parseCacheControl(response.headers.get('Cache-Control'));
399
+ if (!cacheControl.noStore) {
400
+ const clonedResponse = response.raw.clone();
401
+ const body = await clonedResponse.text();
402
+ const entry = await createCacheEntry(response, body, now);
403
+ const freshnessTtl = (entry.sMaxAge ?? entry.maxAge ?? ttl / 1000) * 1000;
404
+ const storageTtl = Math.max(freshnessTtl, ttl);
405
+ await storeCacheEntry(storage, baseKey, req, entry, storageTtl, generateKey);
406
+ }
407
+ }
408
+ return response;
409
+ }
410
+ if (strategy === 'cache-first') {
411
+ if (cachedEntry) {
412
+ return createCachedResponse(cachedEntry, 'hit');
413
+ }
414
+ const response = await next(req);
415
+ if (response.ok) {
416
+ const clonedResponse = response.raw.clone();
417
+ const body = await clonedResponse.text();
418
+ const entry = await createCacheEntry(response, body, now);
419
+ await storeCacheEntry(storage, baseKey, req, entry, ttl, generateKey);
420
+ }
421
+ return response;
422
+ }
423
+ if (strategy === 'stale-while-revalidate') {
424
+ if (cachedEntry) {
425
+ const cachedResponse = createCachedResponse(cachedEntry, 'stale');
426
+ (async () => {
427
+ try {
428
+ const conditionalReq = createConditionalRequest(req, cachedEntry);
429
+ const freshResponse = await next(conditionalReq);
430
+ if (freshResponse.status === 304) {
431
+ const updatedEntry = {
432
+ ...cachedEntry,
433
+ timestamp: Date.now()
434
+ };
435
+ await storage.set(key, updatedEntry, ttl);
436
+ }
437
+ else if (freshResponse.ok) {
438
+ const clonedResponse = freshResponse.raw.clone();
439
+ const body = await clonedResponse.text();
440
+ const entry = await createCacheEntry(freshResponse, body, Date.now());
441
+ const freshnessTtl = (entry.sMaxAge ?? entry.maxAge ?? ttl / 1000) * 1000;
442
+ const storageTtl = Math.max(freshnessTtl, ttl);
443
+ await storeCacheEntry(storage, baseKey, req, entry, storageTtl, generateKey);
444
+ }
445
+ }
446
+ catch {
447
+ }
448
+ })();
449
+ return cachedResponse;
450
+ }
451
+ const response = await next(req);
452
+ if (response.ok) {
453
+ const clonedResponse = response.raw.clone();
454
+ const body = await clonedResponse.text();
455
+ const entry = await createCacheEntry(response, body, now);
456
+ await storeCacheEntry(storage, baseKey, req, entry, ttl, generateKey);
457
+ }
458
+ return response;
459
+ }
460
+ if (strategy === 'network-first') {
461
+ try {
462
+ const response = await next(req);
463
+ if (response.ok) {
464
+ const clonedResponse = response.raw.clone();
465
+ const body = await clonedResponse.text();
466
+ const entry = await createCacheEntry(response, body, now);
467
+ const freshnessTtl = (entry.sMaxAge ?? entry.maxAge ?? ttl / 1000) * 1000;
468
+ const storageTtl = Math.max(freshnessTtl, ttl);
469
+ await storeCacheEntry(storage, baseKey, req, entry, storageTtl, generateKey);
470
+ }
471
+ return response;
472
+ }
473
+ catch (error) {
474
+ if (cachedEntry) {
475
+ return createCachedResponse(cachedEntry, 'stale-error');
476
+ }
477
+ throw error;
478
+ }
479
+ }
480
+ return next(req);
481
+ };
482
+ return (client) => {
483
+ client.use(cacheMiddleware);
484
+ };
485
+ }
486
+ export { parseCacheControl };
@@ -0,0 +1,13 @@
1
+ import { Plugin, ReckerResponse } from '../types/index.js';
2
+ export interface CircuitBreakerOptions {
3
+ threshold?: number;
4
+ resetTimeout?: number;
5
+ shouldTrip?: (error: any, response?: ReckerResponse) => boolean;
6
+ onStateChange?: (state: CircuitState, service: string) => void;
7
+ }
8
+ export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
9
+ export declare class CircuitBreakerError extends Error {
10
+ service: string;
11
+ constructor(service: string);
12
+ }
13
+ export declare function circuitBreakerPlugin(options?: CircuitBreakerOptions): Plugin;
@@ -0,0 +1,100 @@
1
+ export class CircuitBreakerError extends Error {
2
+ service;
3
+ constructor(service) {
4
+ super(`Circuit breaker is OPEN for ${service}`);
5
+ this.service = service;
6
+ this.name = 'CircuitBreakerError';
7
+ }
8
+ }
9
+ export function circuitBreakerPlugin(options = {}) {
10
+ const threshold = options.threshold || 5;
11
+ const resetTimeout = options.resetTimeout || 30 * 1000;
12
+ const circuits = new Map();
13
+ const getCircuitKey = (req) => {
14
+ try {
15
+ return new URL(req.url).hostname;
16
+ }
17
+ catch {
18
+ return 'unknown';
19
+ }
20
+ };
21
+ const shouldTrip = options.shouldTrip || ((err, res) => {
22
+ if (res)
23
+ return res.status >= 500;
24
+ return true;
25
+ });
26
+ return (client) => {
27
+ client.beforeRequest((req) => {
28
+ const key = getCircuitKey(req);
29
+ let stats = circuits.get(key);
30
+ if (!stats) {
31
+ stats = { failures: 0, lastFailureTime: 0, state: 'CLOSED' };
32
+ circuits.set(key, stats);
33
+ }
34
+ if (stats.state === 'OPEN') {
35
+ const now = Date.now();
36
+ if (now - stats.lastFailureTime > resetTimeout) {
37
+ stats.state = 'HALF_OPEN';
38
+ if (options.onStateChange)
39
+ options.onStateChange('HALF_OPEN', key);
40
+ }
41
+ else {
42
+ throw new CircuitBreakerError(key);
43
+ }
44
+ }
45
+ });
46
+ client.afterResponse((req, res) => {
47
+ const key = getCircuitKey(req);
48
+ const stats = circuits.get(key);
49
+ if (!stats)
50
+ return;
51
+ const isFailure = shouldTrip(null, res);
52
+ if (isFailure) {
53
+ handleFailure(stats, key);
54
+ }
55
+ else {
56
+ handleSuccess(stats, key);
57
+ }
58
+ });
59
+ client.onError((err, req) => {
60
+ if (err instanceof CircuitBreakerError) {
61
+ throw err;
62
+ }
63
+ const key = getCircuitKey(req);
64
+ const stats = circuits.get(key);
65
+ if (!stats)
66
+ return;
67
+ const isFailure = shouldTrip(err, undefined);
68
+ if (isFailure) {
69
+ handleFailure(stats, key);
70
+ }
71
+ });
72
+ function handleSuccess(stats, key) {
73
+ if (stats.state === 'HALF_OPEN') {
74
+ stats.state = 'CLOSED';
75
+ stats.failures = 0;
76
+ if (options.onStateChange)
77
+ options.onStateChange('CLOSED', key);
78
+ }
79
+ else if (stats.state === 'CLOSED') {
80
+ stats.failures = 0;
81
+ }
82
+ }
83
+ function handleFailure(stats, key) {
84
+ stats.failures++;
85
+ stats.lastFailureTime = Date.now();
86
+ if (stats.state === 'HALF_OPEN') {
87
+ stats.state = 'OPEN';
88
+ if (options.onStateChange)
89
+ options.onStateChange('OPEN', key);
90
+ }
91
+ else if (stats.state === 'CLOSED') {
92
+ if (stats.failures >= threshold) {
93
+ stats.state = 'OPEN';
94
+ if (options.onStateChange)
95
+ options.onStateChange('OPEN', key);
96
+ }
97
+ }
98
+ }
99
+ };
100
+ }
@@ -0,0 +1,4 @@
1
+ import { Middleware, CompressionOptions } from '../types/index.js';
2
+ export type CompressionAlgorithm = 'gzip' | 'deflate' | 'br';
3
+ export declare function compression(options?: CompressionOptions): Middleware;
4
+ export declare function createCompressionMiddleware(config: boolean | CompressionOptions): Middleware | null;