@studiocms/cfetch 0.1.6 → 0.2.0
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.md +268 -25
- package/dist/cache.d.ts +78 -0
- package/dist/cache.js +89 -0
- package/dist/consts.d.ts +4 -8
- package/dist/consts.js +7 -2
- package/dist/index.d.ts +47 -20
- package/dist/index.js +7 -1
- package/dist/stub.js +145 -68
- package/dist/types.d.ts +26 -41
- package/dist/wrappers.d.ts +241 -11
- package/dist/wrappers.js +79 -39
- package/package.json +8 -6
- package/dist/utils/isOlderThan.d.ts +0 -13
- package/dist/utils/isOlderThan.js +0 -13
package/dist/wrappers.js
CHANGED
|
@@ -1,47 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Data, Effect, Layer } from "effect";
|
|
2
|
+
import { CacheMaps, CacheService } from "./cache.js";
|
|
3
|
+
import { Duration } from "effect";
|
|
4
|
+
const store = /* @__PURE__ */ new Map();
|
|
5
|
+
const tagIndex = /* @__PURE__ */ new Map();
|
|
6
|
+
const CacheMapLayer = Layer.succeed(CacheMaps, { store, tagIndex });
|
|
7
|
+
const CacheLive = CacheService.Default.pipe(Layer.provide(CacheMapLayer));
|
|
8
|
+
class FetchError extends Data.TaggedError("FetchError") {
|
|
9
|
+
}
|
|
10
|
+
const runEffect = (effect) => Effect.runPromise(effect);
|
|
11
|
+
const cacheableMethods = ["GET", "HEAD"];
|
|
12
|
+
const noOpParser = (_) => Promise.resolve(void 0);
|
|
13
|
+
const fetchAndParse = (url, parser, options) => Effect.gen(function* () {
|
|
14
|
+
const response = yield* Effect.tryPromise({
|
|
15
|
+
try: () => fetch(url, options),
|
|
16
|
+
catch: (cause) => new FetchError({ message: "Failed to fetch", cause })
|
|
17
|
+
});
|
|
18
|
+
const data = yield* Effect.tryPromise({
|
|
19
|
+
try: () => parser(response),
|
|
20
|
+
catch: (cause) => new FetchError({ message: "Failed to parse response", cause })
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
data,
|
|
24
|
+
ok: response.ok,
|
|
25
|
+
status: response.status,
|
|
26
|
+
statusText: response.statusText,
|
|
27
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
28
|
+
};
|
|
7
29
|
});
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
const cFetchEffect = (url, parser, options, cacheConfig) => Effect.gen(function* () {
|
|
31
|
+
const cache = yield* CacheService;
|
|
32
|
+
const { key, verbose = false, ...cacheOpts } = cacheConfig || {};
|
|
33
|
+
const method = options?.method?.toUpperCase() || "GET";
|
|
34
|
+
if (!cacheableMethods.includes(method)) {
|
|
35
|
+
if (verbose) console.log(`[c:fetch] Bypassing cache for non-cacheable method: ${method}`);
|
|
36
|
+
return yield* fetchAndParse(url, parser, options);
|
|
37
|
+
}
|
|
38
|
+
const urlString = typeof url === "string" ? url : url.href;
|
|
39
|
+
const cacheRelevantOptions = options ? {
|
|
40
|
+
method: options.method,
|
|
41
|
+
headers: options.headers instanceof Headers ? Object.fromEntries(
|
|
42
|
+
[...options.headers.entries()].sort(([a], [b]) => a.localeCompare(b))
|
|
43
|
+
) : options.headers ? Object.fromEntries(
|
|
44
|
+
Object.entries(options.headers).sort(([a], [b]) => a.localeCompare(b))
|
|
45
|
+
) : void 0,
|
|
46
|
+
body: typeof options.body === "string" ? options.body : void 0
|
|
47
|
+
} : {};
|
|
48
|
+
if (options?.body && typeof options.body !== "string" && !key && verbose) {
|
|
11
49
|
console.warn(
|
|
12
|
-
|
|
50
|
+
`[c:fetch] Non-serializable request body detected for ${urlString}. Consider providing an explicit cache key via cacheConfig.key to avoid collisions.`
|
|
13
51
|
);
|
|
14
|
-
const response = await fetch(input, init);
|
|
15
|
-
const data = type === "json" ? await response.clone().json() : await response.clone().text();
|
|
16
|
-
const result = new Response(type === "json" ? JSON.stringify(data) : data, response);
|
|
17
|
-
return metadata ? { lastCheck: /* @__PURE__ */ new Date(), data: result } : result;
|
|
18
52
|
}
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!response.ok) {
|
|
25
|
-
if (!storedData) {
|
|
26
|
-
throw new Error("Failed to retrieve cached data, and failed to fetch new data");
|
|
27
|
-
}
|
|
28
|
-
const fallback = new Response(
|
|
29
|
-
type === "json" ? JSON.stringify(storedData.data) : storedData.data
|
|
30
|
-
);
|
|
31
|
-
return metadata ? { ...storedData, data: fallback } : fallback;
|
|
32
|
-
}
|
|
33
|
-
const data = type === "json" ? await response.clone().json() : await response.clone().text();
|
|
34
|
-
const newCachedData = { lastCheck: /* @__PURE__ */ new Date(), data };
|
|
35
|
-
cachedData.set(key, newCachedData);
|
|
36
|
-
const result = new Response(type === "json" ? JSON.stringify(data) : data, response);
|
|
37
|
-
return metadata ? { ...newCachedData, data: result } : result;
|
|
53
|
+
const cacheKey = key ?? `${urlString}-${JSON.stringify(cacheRelevantOptions)}`;
|
|
54
|
+
const cached = yield* cache.get(cacheKey);
|
|
55
|
+
if (cached) {
|
|
56
|
+
if (verbose) console.log(`[c:fetch] Cache hit for: ${cacheKey}, returning cached response.`);
|
|
57
|
+
return cached;
|
|
38
58
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
if (verbose) console.log(`[c:fetch] Cache miss for: ${cacheKey}, fetching from network.`);
|
|
60
|
+
const effectiveParser = method === "HEAD" ? noOpParser : parser;
|
|
61
|
+
const cachedResponse = yield* fetchAndParse(url, effectiveParser, options);
|
|
62
|
+
if (cachedResponse.ok) {
|
|
63
|
+
yield* cache.set(cacheKey, cachedResponse, cacheOpts);
|
|
64
|
+
if (verbose) console.log(`[c:fetch] Cached response for: ${cacheKey}`);
|
|
65
|
+
}
|
|
66
|
+
return cachedResponse;
|
|
67
|
+
}).pipe(Effect.provide(CacheLive));
|
|
68
|
+
const cFetchEffectJson = (url, options, cacheConfig) => cFetchEffect(url, (res) => res.json(), options, cacheConfig);
|
|
69
|
+
const cFetchEffectText = (url, options, cacheConfig) => cFetchEffect(url, (res) => res.text(), options, cacheConfig);
|
|
70
|
+
const cFetchEffectBlob = (url, options, cacheConfig) => cFetchEffect(url, (res) => res.blob(), options, cacheConfig);
|
|
71
|
+
const cFetch = (url, parser, options, cacheConfig) => runEffect(cFetchEffect(url, parser, options, cacheConfig));
|
|
72
|
+
const cFetchJson = (url, options, cacheConfig) => runEffect(cFetchEffectJson(url, options, cacheConfig));
|
|
73
|
+
const cFetchText = (url, options, cacheConfig) => runEffect(cFetchEffectText(url, options, cacheConfig));
|
|
74
|
+
const cFetchBlob = (url, options, cacheConfig) => runEffect(cFetchEffectBlob(url, options, cacheConfig));
|
|
44
75
|
export {
|
|
76
|
+
Duration,
|
|
77
|
+
FetchError,
|
|
45
78
|
cFetch,
|
|
46
|
-
|
|
79
|
+
cFetchBlob,
|
|
80
|
+
cFetchEffect,
|
|
81
|
+
cFetchEffectBlob,
|
|
82
|
+
cFetchEffectJson,
|
|
83
|
+
cFetchEffectText,
|
|
84
|
+
cFetchJson,
|
|
85
|
+
cFetchText,
|
|
86
|
+
noOpParser
|
|
47
87
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@studiocms/cfetch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Astro integration that allows you to have a cached fetch function in your Astro SSR project.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://studiocms.dev",
|
|
@@ -50,14 +50,16 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@inox-tools/astro-tests": "^0.
|
|
53
|
+
"@inox-tools/astro-tests": "^0.8.0",
|
|
54
54
|
"@types/node": "^22.0.0",
|
|
55
|
-
"jest-extended": "^
|
|
56
|
-
"astro": "^5.
|
|
55
|
+
"jest-extended": "^7.0.0",
|
|
56
|
+
"astro": "^5.16.6",
|
|
57
|
+
"effect": "^3.19.14"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
|
-
"astro": "^5.
|
|
60
|
-
"
|
|
60
|
+
"astro": "^5.16.6",
|
|
61
|
+
"effect": "^3.19.13",
|
|
62
|
+
"vite": "^6.4.1"
|
|
61
63
|
},
|
|
62
64
|
"scripts": {
|
|
63
65
|
"build": "buildkit build 'src/**/*.{ts,astro,css,js}'",
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This module contains helper functions for cFetch
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Determines whether a given date is older than a specified lifetime.
|
|
7
|
-
*
|
|
8
|
-
* @param date - The date to compare against the current time.
|
|
9
|
-
* @param lifetime - The lifetime duration in the format of `${number}m` for minutes
|
|
10
|
-
* or `${number}h` for hours (e.g., "30m" or "2h").
|
|
11
|
-
* @returns `true` if the given date is older than the specified lifetime, otherwise `false`.
|
|
12
|
-
*/
|
|
13
|
-
export default function isOlderThan(date: Date, lifetime: `${number}m` | `${number}h`): boolean;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
function isOlderThan(date, lifetime) {
|
|
2
|
-
const now = /* @__PURE__ */ new Date();
|
|
3
|
-
let milliseconds = 0;
|
|
4
|
-
if (lifetime.endsWith("m")) {
|
|
5
|
-
milliseconds = Number.parseInt(lifetime) * 60 * 1e3;
|
|
6
|
-
} else if (lifetime.endsWith("h")) {
|
|
7
|
-
milliseconds = Number.parseInt(lifetime) * 60 * 60 * 1e3;
|
|
8
|
-
}
|
|
9
|
-
return date < new Date(now.getTime() - milliseconds);
|
|
10
|
-
}
|
|
11
|
-
export {
|
|
12
|
-
isOlderThan as default
|
|
13
|
-
};
|