@cacheable/net 1.0.1 → 1.0.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/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,13 +32,19 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  CacheableNet: () => CacheableNet,
24
34
  Net: () => Net,
25
- fetch: () => fetch
35
+ del: () => del,
36
+ fetch: () => fetch,
37
+ get: () => get,
38
+ head: () => head,
39
+ patch: () => patch,
40
+ post: () => post
26
41
  });
27
42
  module.exports = __toCommonJS(index_exports);
28
43
  var import_cacheable = require("cacheable");
29
44
  var import_hookified = require("hookified");
30
45
 
31
46
  // src/fetch.ts
47
+ var import_http_cache_semantics = __toESM(require("http-cache-semantics"), 1);
32
48
  var import_undici = require("undici");
33
49
  async function fetch(url, options) {
34
50
  if (!options.cache) {
@@ -38,23 +54,301 @@ async function fetch(url, options) {
38
54
  ...options,
39
55
  cache: "no-cache"
40
56
  };
41
- return options.cache.getOrSet(url, async () => {
42
- const response = await (0, import_undici.fetch)(url, fetchOptions);
43
- if (!response.ok) {
44
- throw new Error(`Fetch failed with status ${response.status}`);
57
+ if (options.method === "POST" || options.method === "PATCH" || options.method === "DELETE" || options.method === "HEAD") {
58
+ const response2 = await (0, import_undici.fetch)(url, fetchOptions);
59
+ if (!response2.ok) {
60
+ throw new Error(`Fetch failed with status ${response2.status}`);
45
61
  }
46
- return response;
62
+ return response2;
63
+ }
64
+ const useHttpCache = options.useHttpCache !== false;
65
+ const method = options.method || "GET";
66
+ const cacheKey = `${method}:${url}`;
67
+ if (!useHttpCache) {
68
+ const cachedData = await options.cache.getOrSet(cacheKey, async () => {
69
+ const response2 = await (0, import_undici.fetch)(url, fetchOptions);
70
+ if (!response2.ok) {
71
+ throw new Error(`Fetch failed with status ${response2.status}`);
72
+ }
73
+ const body2 = await response2.text();
74
+ return {
75
+ body: body2,
76
+ status: response2.status,
77
+ statusText: response2.statusText,
78
+ headers: Object.fromEntries(response2.headers.entries())
79
+ };
80
+ });
81
+ if (!cachedData) {
82
+ throw new Error("Failed to get or set cache data");
83
+ }
84
+ return new Response(cachedData.body, {
85
+ status: cachedData.status,
86
+ statusText: cachedData.statusText,
87
+ headers: cachedData.headers
88
+ });
89
+ }
90
+ const policyKey = `${cacheKey}:policy`;
91
+ const [cachedResponse, cachedPolicyData] = await Promise.all([
92
+ options.cache.get(cacheKey),
93
+ options.cache.get(policyKey)
94
+ ]);
95
+ let policy;
96
+ let cachedBody;
97
+ let cachedStatus;
98
+ let cachedStatusText;
99
+ let cachedHeaders;
100
+ if (cachedPolicyData && cachedResponse) {
101
+ policy = import_http_cache_semantics.default.fromObject(
102
+ cachedPolicyData
103
+ );
104
+ cachedBody = cachedResponse.body;
105
+ cachedStatus = cachedResponse.status;
106
+ cachedStatusText = cachedResponse.statusText;
107
+ cachedHeaders = cachedResponse.headers;
108
+ }
109
+ const requestHeaders = fetchOptions.headers || {};
110
+ const request = {
111
+ url,
112
+ method,
113
+ headers: requestHeaders
114
+ };
115
+ if (policy?.satisfiesWithoutRevalidation(request)) {
116
+ const headers = policy.responseHeaders();
117
+ return new Response(cachedBody, {
118
+ status: cachedStatus,
119
+ statusText: cachedStatusText,
120
+ headers
121
+ });
122
+ }
123
+ let revalidationHeaders = {};
124
+ if (policy?.revalidationHeaders(request)) {
125
+ revalidationHeaders = policy.revalidationHeaders(request);
126
+ }
127
+ const response = await (0, import_undici.fetch)(url, {
128
+ ...fetchOptions,
129
+ headers: {
130
+ ...fetchOptions.headers,
131
+ ...revalidationHeaders
132
+ }
133
+ });
134
+ if (response.status === 304 && policy) {
135
+ const { policy: updatedPolicy, modified } = policy.revalidatedPolicy(
136
+ request,
137
+ {
138
+ status: response.status,
139
+ headers: Object.fromEntries(response.headers.entries())
140
+ }
141
+ );
142
+ if (!modified) {
143
+ const ttl = updatedPolicy.timeToLive();
144
+ await options.cache.set(policyKey, updatedPolicy.toObject(), ttl);
145
+ await options.cache.set(
146
+ cacheKey,
147
+ {
148
+ body: cachedBody,
149
+ status: cachedStatus,
150
+ statusText: cachedStatusText,
151
+ headers: cachedHeaders
152
+ },
153
+ ttl
154
+ );
155
+ const headers = updatedPolicy.responseHeaders();
156
+ return new Response(cachedBody, {
157
+ status: cachedStatus,
158
+ statusText: cachedStatusText,
159
+ headers
160
+ });
161
+ }
162
+ }
163
+ if (!response.ok && response.status !== 304) {
164
+ throw new Error(`Fetch failed with status ${response.status}`);
165
+ }
166
+ const body = await response.text();
167
+ const responseForPolicy = {
168
+ status: response.status,
169
+ statusText: response.statusText,
170
+ headers: Object.fromEntries(response.headers.entries())
171
+ };
172
+ const newPolicy = new import_http_cache_semantics.default(request, responseForPolicy);
173
+ if (newPolicy.storable()) {
174
+ const ttl = newPolicy.timeToLive();
175
+ await Promise.all([
176
+ options.cache.set(
177
+ cacheKey,
178
+ {
179
+ body,
180
+ status: response.status,
181
+ statusText: response.statusText,
182
+ headers: responseForPolicy.headers
183
+ },
184
+ ttl
185
+ ),
186
+ options.cache.set(policyKey, newPolicy.toObject(), ttl)
187
+ ]);
188
+ }
189
+ return new Response(body, {
190
+ status: response.status,
191
+ statusText: response.statusText,
192
+ headers: response.headers
193
+ });
194
+ }
195
+ async function get(url, options) {
196
+ const response = await fetch(url, { ...options, method: "GET" });
197
+ const text = await response.text();
198
+ let data;
199
+ try {
200
+ data = JSON.parse(text);
201
+ } catch {
202
+ data = text;
203
+ }
204
+ const newResponse = new Response(text, {
205
+ status: response.status,
206
+ statusText: response.statusText,
207
+ headers: response.headers
208
+ });
209
+ return {
210
+ data,
211
+ response: newResponse
212
+ };
213
+ }
214
+ async function post(url, data, options) {
215
+ let body;
216
+ const headers = { ...options.headers };
217
+ if (typeof data === "string") {
218
+ body = data;
219
+ } else if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob) {
220
+ body = data;
221
+ } else {
222
+ body = JSON.stringify(data);
223
+ if (!headers["Content-Type"] && !headers["content-type"]) {
224
+ headers["Content-Type"] = "application/json";
225
+ }
226
+ }
227
+ const response = await fetch(url, {
228
+ ...options,
229
+ headers,
230
+ body,
231
+ method: "POST"
232
+ });
233
+ const text = await response.text();
234
+ let responseData;
235
+ try {
236
+ responseData = JSON.parse(text);
237
+ } catch {
238
+ responseData = text;
239
+ }
240
+ const newResponse = new Response(text, {
241
+ status: response.status,
242
+ statusText: response.statusText,
243
+ headers: response.headers
244
+ });
245
+ return {
246
+ data: responseData,
247
+ response: newResponse
248
+ };
249
+ }
250
+ async function patch(url, data, options) {
251
+ let body;
252
+ const headers = { ...options.headers };
253
+ if (typeof data === "string") {
254
+ body = data;
255
+ } else if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob) {
256
+ body = data;
257
+ } else {
258
+ body = JSON.stringify(data);
259
+ if (!headers["Content-Type"] && !headers["content-type"]) {
260
+ headers["Content-Type"] = "application/json";
261
+ }
262
+ }
263
+ const response = await fetch(url, {
264
+ ...options,
265
+ headers,
266
+ body,
267
+ method: "PATCH"
268
+ });
269
+ const text = await response.text();
270
+ let responseData;
271
+ try {
272
+ responseData = JSON.parse(text);
273
+ } catch {
274
+ responseData = text;
275
+ }
276
+ const newResponse = new Response(text, {
277
+ status: response.status,
278
+ statusText: response.statusText,
279
+ headers: response.headers
47
280
  });
281
+ return {
282
+ data: responseData,
283
+ response: newResponse
284
+ };
285
+ }
286
+ async function del(url, data, options) {
287
+ let actualData;
288
+ let actualOptions;
289
+ if (data !== void 0 && typeof data === "object" && data !== null && "cache" in data) {
290
+ actualData = void 0;
291
+ actualOptions = data;
292
+ } else if (options) {
293
+ actualData = data;
294
+ actualOptions = options;
295
+ } else {
296
+ throw new Error("Fetch options must include a cache instance or options.");
297
+ }
298
+ let body;
299
+ const headers = { ...actualOptions.headers };
300
+ if (actualData !== void 0) {
301
+ if (typeof actualData === "string") {
302
+ body = actualData;
303
+ } else if (actualData instanceof FormData || actualData instanceof URLSearchParams || actualData instanceof Blob) {
304
+ body = actualData;
305
+ } else {
306
+ body = JSON.stringify(actualData);
307
+ if (!headers["Content-Type"] && !headers["content-type"]) {
308
+ headers["Content-Type"] = "application/json";
309
+ }
310
+ }
311
+ }
312
+ const response = await fetch(url, {
313
+ ...actualOptions,
314
+ headers,
315
+ body,
316
+ method: "DELETE"
317
+ });
318
+ const text = await response.text();
319
+ let responseData;
320
+ try {
321
+ responseData = JSON.parse(text);
322
+ } catch {
323
+ responseData = text;
324
+ }
325
+ const newResponse = new Response(text, {
326
+ status: response.status,
327
+ statusText: response.statusText,
328
+ headers: response.headers
329
+ });
330
+ return {
331
+ data: responseData,
332
+ response: newResponse
333
+ };
334
+ }
335
+ async function head(url, options) {
336
+ const response = await fetch(url, { ...options, method: "HEAD" });
337
+ return response;
48
338
  }
49
339
 
50
340
  // src/index.ts
51
341
  var CacheableNet = class extends import_hookified.Hookified {
52
342
  _cache = new import_cacheable.Cacheable();
343
+ _useHttpCache = true;
53
344
  constructor(options) {
54
345
  super(options);
55
346
  if (options?.cache) {
56
347
  this._cache = options.cache instanceof import_cacheable.Cacheable ? options.cache : new import_cacheable.Cacheable(options.cache);
57
348
  }
349
+ if (options?.useHttpCache !== void 0) {
350
+ this._useHttpCache = options.useHttpCache;
351
+ }
58
352
  }
59
353
  get cache() {
60
354
  return this._cache;
@@ -62,8 +356,27 @@ var CacheableNet = class extends import_hookified.Hookified {
62
356
  set cache(value) {
63
357
  this._cache = value;
64
358
  }
359
+ /**
360
+ * Get the current HTTP cache setting.
361
+ * @returns {boolean} Whether HTTP cache semantics are enabled
362
+ */
363
+ get useHttpCache() {
364
+ return this._useHttpCache;
365
+ }
366
+ /**
367
+ * Set whether to use HTTP cache semantics.
368
+ * @param {boolean} value - Enable or disable HTTP cache semantics
369
+ */
370
+ set useHttpCache(value) {
371
+ this._useHttpCache = value;
372
+ }
65
373
  /**
66
374
  * Fetch data from a URL with optional request options. Will use the cache that is already set in the instance.
375
+ *
376
+ * When `useHttpCache` is enabled (default), cache entries will have their TTL
377
+ * set based on HTTP cache headers (e.g., Cache-Control: max-age). When disabled,
378
+ * the default TTL from the Cacheable instance is used.
379
+ *
67
380
  * @param {string} url The URL to fetch.
68
381
  * @param {FetchRequestInit} options Optional request options.
69
382
  * @returns {Promise<FetchResponse>} The response from the fetch.
@@ -71,15 +384,187 @@ var CacheableNet = class extends import_hookified.Hookified {
71
384
  async fetch(url, options) {
72
385
  const fetchOptions = {
73
386
  ...options,
74
- cache: this._cache
387
+ cache: this._cache,
388
+ useHttpCache: this._useHttpCache
75
389
  };
76
390
  return fetch(url, fetchOptions);
77
391
  }
392
+ /**
393
+ * Perform a GET request to a URL with optional request options. Will use the cache that is already set in the instance.
394
+ * @param {string} url The URL to fetch.
395
+ * @param {Omit<FetchRequestInit, 'method'>} options Optional request options (method will be set to GET).
396
+ * @returns {Promise<DataResponse<T>>} The typed data and response from the fetch.
397
+ */
398
+ async get(url, options) {
399
+ const response = await this.fetch(url, { ...options, method: "GET" });
400
+ const text = await response.text();
401
+ let data;
402
+ try {
403
+ data = JSON.parse(text);
404
+ } catch {
405
+ data = text;
406
+ }
407
+ const newResponse = new Response(text, {
408
+ status: response.status,
409
+ statusText: response.statusText,
410
+ headers: response.headers
411
+ });
412
+ return {
413
+ data,
414
+ response: newResponse
415
+ };
416
+ }
417
+ /**
418
+ * Perform a POST request to a URL with data and optional request options. Will use the cache that is already set in the instance.
419
+ * @param {string} url The URL to fetch.
420
+ * @param {unknown} data The data to send in the request body.
421
+ * @param {Omit<FetchRequestInit, 'method' | 'body'>} options Optional request options (method and body will be set).
422
+ * @returns {Promise<DataResponse<T>>} The typed data and response from the fetch.
423
+ */
424
+ async post(url, data, options) {
425
+ let body;
426
+ const headers = { ...options?.headers };
427
+ if (typeof data === "string") {
428
+ body = data;
429
+ } else if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob) {
430
+ body = data;
431
+ } else {
432
+ body = JSON.stringify(data);
433
+ if (!headers["Content-Type"] && !headers["content-type"]) {
434
+ headers["Content-Type"] = "application/json";
435
+ }
436
+ }
437
+ const response = await this.fetch(url, {
438
+ ...options,
439
+ headers,
440
+ body,
441
+ method: "POST"
442
+ });
443
+ const text = await response.text();
444
+ let responseData;
445
+ try {
446
+ responseData = JSON.parse(text);
447
+ } catch {
448
+ responseData = text;
449
+ }
450
+ const newResponse = new Response(text, {
451
+ status: response.status,
452
+ statusText: response.statusText,
453
+ headers: response.headers
454
+ });
455
+ return {
456
+ data: responseData,
457
+ response: newResponse
458
+ };
459
+ }
460
+ /**
461
+ * Perform a HEAD request to a URL with optional request options. Will use the cache that is already set in the instance.
462
+ * @param {string} url The URL to fetch.
463
+ * @param {Omit<FetchRequestInit, 'method'>} options Optional request options (method will be set to HEAD).
464
+ * @returns {Promise<FetchResponse>} The response from the fetch (no body).
465
+ */
466
+ async head(url, options) {
467
+ const response = await this.fetch(url, { ...options, method: "HEAD" });
468
+ return response;
469
+ }
470
+ /**
471
+ * Perform a PATCH request to a URL with data and optional request options. Will use the cache that is already set in the instance.
472
+ * @param {string} url The URL to fetch.
473
+ * @param {unknown} data The data to send in the request body.
474
+ * @param {Omit<FetchRequestInit, 'method' | 'body'>} options Optional request options (method and body will be set).
475
+ * @returns {Promise<DataResponse<T>>} The typed data and response from the fetch.
476
+ */
477
+ async patch(url, data, options) {
478
+ let body;
479
+ const headers = { ...options?.headers };
480
+ if (typeof data === "string") {
481
+ body = data;
482
+ } else if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob) {
483
+ body = data;
484
+ } else {
485
+ body = JSON.stringify(data);
486
+ if (!headers["Content-Type"] && !headers["content-type"]) {
487
+ headers["Content-Type"] = "application/json";
488
+ }
489
+ }
490
+ const response = await this.fetch(url, {
491
+ ...options,
492
+ headers,
493
+ body,
494
+ method: "PATCH"
495
+ });
496
+ const text = await response.text();
497
+ let responseData;
498
+ try {
499
+ responseData = JSON.parse(text);
500
+ } catch {
501
+ responseData = text;
502
+ }
503
+ const newResponse = new Response(text, {
504
+ status: response.status,
505
+ statusText: response.statusText,
506
+ headers: response.headers
507
+ });
508
+ return {
509
+ data: responseData,
510
+ response: newResponse
511
+ };
512
+ }
513
+ /**
514
+ * Perform a DELETE request to a URL with optional data and request options. Will use the cache that is already set in the instance.
515
+ * @param {string} url The URL to fetch.
516
+ * @param {unknown} data Optional data to send in the request body.
517
+ * @param {Omit<FetchRequestInit, 'method' | 'body'>} options Optional request options (method and body will be set).
518
+ * @returns {Promise<DataResponse<T>>} The typed data and response from the fetch.
519
+ */
520
+ async delete(url, data, options) {
521
+ let body;
522
+ const headers = { ...options?.headers };
523
+ if (data !== void 0) {
524
+ if (typeof data === "string") {
525
+ body = data;
526
+ } else if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob) {
527
+ body = data;
528
+ } else {
529
+ body = JSON.stringify(data);
530
+ if (!headers["Content-Type"] && !headers["content-type"]) {
531
+ headers["Content-Type"] = "application/json";
532
+ }
533
+ }
534
+ }
535
+ const response = await this.fetch(url, {
536
+ ...options,
537
+ headers,
538
+ body,
539
+ method: "DELETE"
540
+ });
541
+ const text = await response.text();
542
+ let responseData;
543
+ try {
544
+ responseData = JSON.parse(text);
545
+ } catch {
546
+ responseData = text;
547
+ }
548
+ const newResponse = new Response(text, {
549
+ status: response.status,
550
+ statusText: response.statusText,
551
+ headers: response.headers
552
+ });
553
+ return {
554
+ data: responseData,
555
+ response: newResponse
556
+ };
557
+ }
78
558
  };
79
559
  var Net = CacheableNet;
80
560
  // Annotate the CommonJS export names for ESM import in node:
81
561
  0 && (module.exports = {
82
562
  CacheableNet,
83
563
  Net,
84
- fetch
564
+ del,
565
+ fetch,
566
+ get,
567
+ head,
568
+ patch,
569
+ post
85
570
  });