@studiocms/cfetch 0.2.0 → 0.3.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 CHANGED
@@ -103,10 +103,19 @@ interface CachedResponse<T> {
103
103
 
104
104
  ```ts
105
105
  interface CFetchConfig {
106
- ttl?: Duration.DurationInput;
107
- tags?: string[];
108
- key?: string;
109
- verbose?: boolean;
106
+ ttl?: Duration.DurationInput;
107
+ tags?: string[];
108
+ key?: string;
109
+ verbose?: boolean;
110
+ }
111
+ ```
112
+
113
+ ##### `InvalidateCacheOptions` type
114
+
115
+ ```ts
116
+ interface InvalidateCacheOptions {
117
+ keys?: string[];
118
+ tags?: string[];
110
119
  }
111
120
  ```
112
121
 
@@ -140,6 +149,27 @@ Return type:
140
149
  */
141
150
  ```
142
151
 
152
+ ##### `invalidateCacheEffect`
153
+
154
+ ###### Interface
155
+
156
+ ```ts
157
+ const invalidateCacheEffect: (opts: InvalidateCacheOptions) => Effect.Effect<void, never, never>
158
+ ```
159
+
160
+ ###### Example Usage
161
+
162
+ ```ts
163
+ const effect = invalidateCacheEffect({
164
+ tags: ['user'],
165
+ keys: ['user:123', 'user:456']
166
+ })
167
+ /*
168
+ Return type:
169
+ Effect.Effect<void, never, never>
170
+ */
171
+ ```
172
+
143
173
  ##### `cFetchEffectJson`
144
174
 
145
175
  ###### Interface
@@ -247,7 +277,7 @@ const cFetch: <T>(
247
277
  ```ts
248
278
  import { cFetch } from "c:fetch"
249
279
 
250
- const effect = await cFetch<{ foo: string; bar: number; }>(
280
+ const response = await cFetch<{ foo: string; bar: number; }>(
251
281
  'https://api.example.com/data',
252
282
  (res) => res.json(),
253
283
  { method: "GET" }
@@ -275,7 +305,7 @@ const cFetchJson: <T>(
275
305
  ```ts
276
306
  import { cFetchJson } from "c:fetch"
277
307
 
278
- const effect = await cFetchJson<{ foo: string; bar: number; }>(
308
+ const response = await cFetchJson<{ foo: string; bar: number; }>(
279
309
  'https://api.example.com/data',
280
310
  { method: "GET" }
281
311
  );
@@ -302,7 +332,7 @@ const cFetchText: (
302
332
  ```ts
303
333
  import { cFetchText } from "c:fetch"
304
334
 
305
- const effect = await cFetchText(
335
+ const response = await cFetchText(
306
336
  'https://example.com',
307
337
  { method: "GET" }
308
338
  );
@@ -329,7 +359,7 @@ const cFetchBlob: (
329
359
  ```ts
330
360
  import { cFetchBlob } from "c:fetch"
331
361
 
332
- const effect = await cFetchBlob(
362
+ const response = await cFetchBlob(
333
363
  'https://example.com/image.png',
334
364
  { method: "GET" }
335
365
  );
@@ -339,6 +369,27 @@ Return type:
339
369
  */
340
370
  ```
341
371
 
372
+ ##### `invalidateCache`
373
+
374
+ ###### Interface
375
+
376
+ ```ts
377
+ const invalidateCache: (opts: InvalidateCacheOptions) => Promise<void>
378
+ ```
379
+
380
+ ###### Example Usage
381
+
382
+ ```ts
383
+ const res = await invalidateCache({
384
+ tags: ['user'],
385
+ keys: ['user:123', 'user:456']
386
+ })
387
+ /*
388
+ Return type:
389
+ void
390
+ */
391
+ ```
392
+
342
393
  ## Licensing
343
394
 
344
395
  [MIT Licensed](https://github.com/withstudiocms/cfetch/blob/main/LICENSE).
@@ -1,47 +1,44 @@
1
- /**
2
- * @module cfetch/cache
3
- * Cache service implementation using Effect-TS.
4
- */
5
- import { Context, Duration, Effect } from 'effect';
1
+ import { Context, Duration, Effect } from "effect";
2
+
3
+ //#region src/cache.d.ts
6
4
  /**
7
5
  * Represents a cache entry with its value, expiration time, last updated time, and tags.
8
6
  */
9
- export interface CacheEntry<A> {
10
- value: A;
11
- expiresAt: number;
12
- lastUpdatedAt: number;
13
- tags: Set<string>;
7
+ interface CacheEntry<A> {
8
+ value: A;
9
+ expiresAt: number;
10
+ lastUpdatedAt: number;
11
+ tags: Set<string>;
14
12
  }
15
13
  /**
16
14
  * Represents the status of a cache entry, including expiration and tags.
17
15
  */
18
- export interface CacheEntryStatus {
19
- expiresAt: Date;
20
- lastUpdatedAt: Date;
21
- tags: Set<string>;
16
+ interface CacheEntryStatus {
17
+ expiresAt: Date;
18
+ lastUpdatedAt: Date;
19
+ tags: Set<string>;
22
20
  }
23
21
  declare const CacheMaps_base: Context.TagClass<CacheMaps, "@studiocms/cfetch/CacheMaps", {
24
- store: Map<string, CacheEntry<unknown>>;
25
- tagIndex: Map<string, Set<string>>;
22
+ store: Map<string, CacheEntry<unknown>>;
23
+ tagIndex: Map<string, Set<string>>;
26
24
  }>;
27
25
  /**
28
26
  * Tag to hold the in-memory cache maps.
29
27
  *
30
28
  * This tag provides access to the main cache store and the tag index for invalidation.
31
29
  */
32
- export declare class CacheMaps extends CacheMaps_base {
33
- }
30
+ declare class CacheMaps extends CacheMaps_base {}
34
31
  declare const CacheService_base: Effect.Service.Class<CacheService, "@studiocms/cfetch/CacheService", {
35
- readonly effect: Effect.Effect<{
36
- get: <A>(key: string) => Effect.Effect<A | null, never, never>;
37
- set: <A>(key: string, value: A, options?: {
38
- ttl?: Duration.DurationInput;
39
- tags?: string[];
40
- }) => Effect.Effect<void, never, never>;
41
- delete: (key: string) => Effect.Effect<void, never, never>;
42
- invalidateTags: (tags: string[]) => Effect.Effect<void, never, never>;
43
- clear: () => Effect.Effect<void, never, never>;
44
- }, never, CacheMaps>;
32
+ readonly effect: Effect.Effect<{
33
+ get: <A>(key: string) => Effect.Effect<A | null, never, never>;
34
+ set: <A>(key: string, value: A, options?: {
35
+ ttl?: Duration.DurationInput;
36
+ tags?: string[];
37
+ }) => Effect.Effect<void, never, never>;
38
+ delete: (key: string) => Effect.Effect<void, never, never>;
39
+ invalidateTags: (tags: string[]) => Effect.Effect<void, never, never>;
40
+ clear: () => Effect.Effect<void, never, never>;
41
+ }, never, CacheMaps>;
45
42
  }>;
46
43
  /**
47
44
  * A service for managing cached data with TTL (time-to-live) and tag-based invalidation.
@@ -73,6 +70,6 @@ declare const CacheService_base: Effect.Service.Class<CacheService, "@studiocms/
73
70
  *
74
71
  * @public
75
72
  */
76
- export declare class CacheService extends CacheService_base {
77
- }
78
- export {};
73
+ declare class CacheService extends CacheService_base {}
74
+ //#endregion
75
+ export { CacheEntry, CacheEntryStatus, CacheMaps, CacheService };
package/dist/cache.mjs ADDED
@@ -0,0 +1,176 @@
1
+ import { defaultConfigLive } from "./consts.mjs";
2
+ import { Clock, Context, Duration, Effect } from "effect";
3
+
4
+ //#region src/cache.ts
5
+ /**
6
+ * @module cfetch/cache
7
+ * Cache service implementation using Effect-TS.
8
+ */
9
+ /**
10
+ * Tag to hold the in-memory cache maps.
11
+ *
12
+ * This tag provides access to the main cache store and the tag index for invalidation.
13
+ */
14
+ var CacheMaps = class extends Context.Tag("@studiocms/cfetch/CacheMaps")() {};
15
+ /**
16
+ * Error thrown when there is an issue fetching the cache configuration.
17
+ */
18
+ var ConfigFetchError = class {
19
+ _tag = "ConfigFetchError";
20
+ };
21
+ /**
22
+ * Fetches the cache configuration from the virtual module.
23
+ * Falls back to default configuration if not available.
24
+ */
25
+ const getConfig = async () => {
26
+ try {
27
+ return (await import("virtual:cfetch/config")).default;
28
+ } catch (error) {
29
+ console.warn("Could not load virtual:cfetch/config, using default config.");
30
+ return defaultConfigLive;
31
+ }
32
+ };
33
+ /**
34
+ * A service for managing cached data with TTL (time-to-live) and tag-based invalidation.
35
+ *
36
+ * @remarks
37
+ * This service provides an in-memory cache with the following features:
38
+ * - Automatic expiration based on TTL
39
+ * - Tag-based organization for batch invalidation
40
+ * - Effect-based API for safe side-effect management
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const program = Effect.gen(function* () {
45
+ * const cache = yield* CacheService;
46
+ *
47
+ * // Set a value with custom TTL and tags
48
+ * yield* cache.set('user:123', userData, {
49
+ * ttl: Duration.minutes(5),
50
+ * tags: ['user', 'profile']
51
+ * });
52
+ *
53
+ * // Get a value
54
+ * const result = yield* cache.get('user:123');
55
+ *
56
+ * // Invalidate all entries with specific tags
57
+ * yield* cache.invalidateTags(['user']);
58
+ * });
59
+ * ```
60
+ *
61
+ * @public
62
+ */
63
+ var CacheService = class extends Effect.Service()("@studiocms/cfetch/CacheService", { effect: Effect.gen(function* () {
64
+ const { store, tagIndex } = yield* CacheMaps;
65
+ /**
66
+ * Get the Cache Configuration from the virtual module, with a fallback to default configuration.
67
+ *
68
+ * This function attempts to load the cache configuration from the virtual module `virtual:cfetch/config`. If the module is not available or fails to load, it catches the error and returns a default configuration. This ensures that the cache service always has a valid configuration to work with, even in environments where the virtual module cannot be resolved.
69
+ *
70
+ * @returns {import('virtual:cfetch/config').CacheConfigLive} The cache configuration object
71
+ * @throws {ConfigFetchError} If there is an error fetching the configuration
72
+ */
73
+ const config = yield* Effect.tryPromise({
74
+ try: () => getConfig(),
75
+ catch: () => new ConfigFetchError()
76
+ }).pipe(Effect.catchTag("ConfigFetchError", () => Effect.succeed(defaultConfigLive)));
77
+ /**
78
+ * Retrieves a value from the cache by its key, checking for expiration and returning null if the entry is not found or has expired.
79
+ *
80
+ * @template A - The type of the cached value
81
+ * @param key - The key associated with the cached entry
82
+ * @returns An Effect that yields the cached value of type A, or null if not found or expired
83
+ */
84
+ const get = (key) => Effect.gen(function* () {
85
+ const now = yield* Clock.currentTimeMillis;
86
+ const entry = store.get(key);
87
+ if (!entry) return null;
88
+ if (entry.expiresAt < now) {
89
+ yield* deleteKey(key);
90
+ return null;
91
+ }
92
+ return entry.value;
93
+ });
94
+ /**
95
+ * Sets a value in the cache with an optional TTL and tags for invalidation.
96
+ *
97
+ * This function adds a new entry to the cache with the specified key and value. It calculates the expiration time based on the provided TTL (or the default lifetime from the configuration) and associates any provided tags with the entry for later invalidation. If tags are provided, it also updates the tag index to allow for efficient invalidation of entries by tag.
98
+ *
99
+ * @template A - The type of the value being cached
100
+ * @param key - The key to associate with the cached entry
101
+ * @param value - The value to cache
102
+ * @param options - Optional configuration for the cache entry, including TTL and tags
103
+ */
104
+ const set = (key, value, options) => Effect.gen(function* () {
105
+ const now = yield* Clock.currentTimeMillis;
106
+ const ttl = options?.ttl ?? Duration.millis(config.lifetime);
107
+ const tags = new Set(options?.tags ?? []);
108
+ const expiresAt = now + Duration.toMillis(ttl);
109
+ store.set(key, {
110
+ value,
111
+ expiresAt,
112
+ lastUpdatedAt: now,
113
+ tags
114
+ });
115
+ for (const tag of tags) {
116
+ if (!tagIndex.has(tag)) tagIndex.set(tag, /* @__PURE__ */ new Set());
117
+ tagIndex.get(tag)?.add(key);
118
+ }
119
+ });
120
+ /**
121
+ * Deletes a cache entry by its key, removing it from the cache and updating the tag index accordingly.
122
+ *
123
+ * This function removes a cache entry identified by the given key from the main cache store. If the entry exists, it also iterates through the associated tags and updates the tag index to remove references to the deleted key. If a tag no longer has any keys associated with it after deletion, the tag is removed from the index entirely.
124
+ *
125
+ * @param key - The key of the cache entry to delete
126
+ * @returns An Effect that performs the deletion when executed
127
+ */
128
+ const deleteKey = (key) => Effect.sync(() => {
129
+ const entry = store.get(key);
130
+ if (entry) {
131
+ for (const tag of entry.tags) {
132
+ const keys = tagIndex.get(tag);
133
+ if (keys) {
134
+ keys.delete(key);
135
+ if (keys.size === 0) tagIndex.delete(tag);
136
+ }
137
+ }
138
+ store.delete(key);
139
+ }
140
+ });
141
+ /**
142
+ * Invalidates cache entries based on specified tags, removing all entries associated with those tags from the cache.
143
+ *
144
+ * This function takes an array of tags and iterates through each tag to find all associated cache keys from the tag index. It then deletes each of those keys from the cache using the `deleteKey` function. This allows for efficient batch invalidation of cache entries that are grouped by common tags.
145
+ *
146
+ * @param tags - An array of tags for which to invalidate associated cache entries
147
+ * @returns An Effect that performs the invalidation when executed
148
+ */
149
+ const invalidateTags = (tags) => Effect.gen(function* () {
150
+ for (const tag of tags) {
151
+ const keys = tagIndex.get(tag);
152
+ if (keys) for (const key of [...keys]) yield* deleteKey(key);
153
+ }
154
+ });
155
+ /**
156
+ * Clears the entire cache, removing all entries and resetting the tag index.
157
+ *
158
+ * This function completely empties the cache store and the tag index, effectively resetting the cache to an empty state. It is useful for scenarios where a full cache reset is needed, such as during development or when significant changes occur that invalidate all cached data.
159
+ *
160
+ * @returns An Effect that performs the cache clearing when executed
161
+ */
162
+ const clear = () => Effect.sync(() => {
163
+ store.clear();
164
+ tagIndex.clear();
165
+ });
166
+ return {
167
+ get,
168
+ set,
169
+ delete: deleteKey,
170
+ invalidateTags,
171
+ clear
172
+ };
173
+ }) }) {};
174
+
175
+ //#endregion
176
+ export { CacheMaps, CacheService };
@@ -0,0 +1,17 @@
1
+ import { CacheConfig, CacheConfigLive } from "./types.mjs";
2
+
3
+ //#region src/consts.d.ts
4
+ /**
5
+ * Default cache configuration for cfetch.
6
+ *
7
+ * This configuration sets the default lifetime of cached entries to 1 hour.
8
+ */
9
+ declare const defaultConfig: CacheConfig;
10
+ /**
11
+ * Default cache configuration with live values for internal use.
12
+ *
13
+ * This configuration converts the lifetime to milliseconds for use in caching logic.
14
+ */
15
+ declare const defaultConfigLive: CacheConfigLive;
16
+ //#endregion
17
+ export { defaultConfig, defaultConfigLive };
@@ -0,0 +1,23 @@
1
+ import { Duration } from "effect";
2
+
3
+ //#region src/consts.ts
4
+ /**
5
+ * @module cfetch/consts
6
+ *
7
+ * Constant values used throughout the cfetch package.
8
+ */
9
+ /**
10
+ * Default cache configuration for cfetch.
11
+ *
12
+ * This configuration sets the default lifetime of cached entries to 1 hour.
13
+ */
14
+ const defaultConfig = { lifetime: Duration.hours(1) };
15
+ /**
16
+ * Default cache configuration with live values for internal use.
17
+ *
18
+ * This configuration converts the lifetime to milliseconds for use in caching logic.
19
+ */
20
+ const defaultConfigLive = { lifetime: Duration.toMillis(Duration.hours(1)) };
21
+
22
+ //#endregion
23
+ export { defaultConfig, defaultConfigLive };
@@ -0,0 +1,32 @@
1
+ import { CacheConfig } from "./types.mjs";
2
+ import { Duration } from "effect";
3
+ import { AstroIntegration } from "astro";
4
+
5
+ //#region src/index.d.ts
6
+ /**
7
+ * Creates a caching fetch integration for Astro.
8
+ *
9
+ * This integration provides a cached fetch implementation that can be configured
10
+ * with custom cache lifetime and other options. It sets up virtual module imports
11
+ * and injects TypeScript type definitions for the cached fetch functionality.
12
+ *
13
+ * @param opts - Optional cache configuration options to customize the caching behavior
14
+ * @returns An Astro integration object with hooks for configuration setup and completion
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // astro.config.mjs
19
+ * import cFetch, { Duration } from '@studiocms/cfetch';
20
+ *
21
+ * export default defineConfig({
22
+ * integrations: [
23
+ * cFetch({
24
+ * lifetime: Duration.minutes(10), // Cache entries live for 10 minutes (default is 1 hour)
25
+ * })
26
+ * ]
27
+ * });
28
+ * ```
29
+ */
30
+ declare function cFetch(opts?: CacheConfig): AstroIntegration;
31
+ //#endregion
32
+ export { Duration, cFetch, cFetch as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,62 @@
1
+ import { defaultConfig } from "./consts.mjs";
2
+ import { addVirtualImports, createResolver } from "./utils/integration.mjs";
3
+ import stub_default from "./stub.mjs";
4
+ import { Duration, Duration as Duration$1 } from "effect";
5
+
6
+ //#region src/index.ts
7
+ /**
8
+ * Creates a caching fetch integration for Astro.
9
+ *
10
+ * This integration provides a cached fetch implementation that can be configured
11
+ * with custom cache lifetime and other options. It sets up virtual module imports
12
+ * and injects TypeScript type definitions for the cached fetch functionality.
13
+ *
14
+ * @param opts - Optional cache configuration options to customize the caching behavior
15
+ * @returns An Astro integration object with hooks for configuration setup and completion
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // astro.config.mjs
20
+ * import cFetch, { Duration } from '@studiocms/cfetch';
21
+ *
22
+ * export default defineConfig({
23
+ * integrations: [
24
+ * cFetch({
25
+ * lifetime: Duration.minutes(10), // Cache entries live for 10 minutes (default is 1 hour)
26
+ * })
27
+ * ]
28
+ * });
29
+ * ```
30
+ */
31
+ function cFetch(opts) {
32
+ const name = "@studiocms/cfetch";
33
+ const { resolve } = createResolver(import.meta.url);
34
+ const options = {
35
+ ...defaultConfig,
36
+ ...opts
37
+ };
38
+ return {
39
+ name,
40
+ hooks: {
41
+ "astro:config:setup": (params) => {
42
+ addVirtualImports(params, {
43
+ name,
44
+ imports: {
45
+ "virtual:cfetch/config": `export default ${JSON.stringify({ lifetime: Duration$1.toMillis(options.lifetime) })}`,
46
+ "c:fetch": `export * from '${resolve("./wrappers.mjs")}';`
47
+ }
48
+ });
49
+ },
50
+ "astro:config:done": ({ injectTypes }) => {
51
+ injectTypes({
52
+ filename: "cfetch.d.ts",
53
+ content: stub_default
54
+ });
55
+ }
56
+ }
57
+ };
58
+ }
59
+ var src_default = cFetch;
60
+
61
+ //#endregion
62
+ export { Duration, cFetch, src_default as default };
@@ -0,0 +1,22 @@
1
+ //#region src/stub.d.ts
2
+ /**
3
+ * This file serves as a stub for TypeScript type definitions related to the cFetch integration.
4
+ * It defines the structure of virtual modules and the types that will be injected into the consuming project.
5
+ * The actual implementation of these types is provided in the corresponding .mjs files, but this stub allows
6
+ * for proper type checking and IntelliSense in development environments.
7
+ *
8
+ * The stub includes module declarations for 'virtual:cfetch/config' and 'c:fetch', defining the expected types
9
+ * and exports that will be available when using the cFetch integration in an Astro project.
10
+ *
11
+ * @remarks
12
+ * - The 'virtual:cfetch/config' module provides access to the cache configuration, which can be customized by users.
13
+ * - The 'c:fetch' module exports various types and functions related to the cached fetch functionality, including error types, parsers, and the main cFetch function.
14
+ *
15
+ * @module
16
+ */
17
+ /**
18
+ * Stub content for TypeScript type definitions related to the cFetch integration.
19
+ */
20
+ declare const stub: string;
21
+ //#endregion
22
+ export { stub as default };