@sdxc/cache 2026.10.12 → 2026.10.16

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
@@ -2,241 +2,305 @@
2
2
 
3
3
  Cache contract with adapters for memory and Cloudflare KV.
4
4
 
5
- One interface covers read, write, fetch and delete, and an adapter binds it to a store — an
6
- in-process map for tests, Workers KV in production. Nothing throws: every call answers with a
7
- `Result`, and each method takes the value's type on its own, so one instance serves a whole
8
- namespace whatever mix of types goes into it.
5
+ One `Cache` interface covers read, write, fetch and delete, and an adapter binds it to a store:
6
+ an in-process map for tests, Workers KV in production. Every call answers with a `Result`, and
7
+ each method takes the value's type on its own, so one instance serves a whole namespace.
9
8
 
10
9
  ## Installation
11
10
 
12
- ```bash
11
+ ```sh
13
12
  npm add @sdxc/cache
14
13
  ```
15
14
 
16
- Every call answers with a `Result` from
17
- [`@sdxc/result`](https://www.npmjs.com/package/@sdxc/result), which is where `isFailure` and
18
- `isSuccess` come from. It installs alongside this package.
15
+ Calls answer with a `Result` from [`@sdxc/result`](https://www.npmjs.com/package/@sdxc/result),
16
+ where `isFailure` and `isSuccess` come from; it installs alongside this package.
19
17
 
20
18
  ## Usage
21
19
 
22
- An instance is a store, not a type. Each method takes the value's type on its own, so one
23
- cache serves a whole namespace whatever mix of types goes into it. Nothing throws — every
24
- call answers with a `Result`:
20
+ ### Compute once, read many times
25
21
 
26
22
  ```typescript
27
- import { WorkerKVCache } from "@sdxc/cache/worker-kv";
23
+ import { MemoryCache } from "@sdxc/cache/memory";
28
24
  import { isFailure } from "@sdxc/result";
29
25
 
30
- let cache = new WorkerKVCache(env.CACHE, { waitUntil: ctx.waitUntil.bind(ctx) });
31
-
32
- let post = await cache.fetch(
33
- `post:${id}`,
34
- async () => db.query.posts.findFirst({ where: eq(posts.id, id) }),
35
- { ttl: "5 minutes" },
36
- );
26
+ let cache = new MemoryCache();
37
27
 
38
- if (isFailure(post)) return notFound();
39
- render(post.data);
28
+ let post = await cache.fetch(`post:${id}`, async () => loadPost(id), { ttl: "5 minutes" });
29
+ if (isFailure(post)) return new Response(null, { status: 500 });
30
+ return Response.json(post.data);
40
31
  ```
41
32
 
42
- `fetch` takes the type from the loader, so nothing is annotated and nothing is cast. What
43
- comes back is what reads back out of the store, which is not always what went in:
33
+ `fetch` returns the stored entry, or runs the loader on a miss and stores what it returns. The
34
+ type comes from the loader, so nothing is annotated.
35
+
36
+ ### Read, write and delete entries
44
37
 
45
38
  ```typescript
46
- let session = await cache.fetch("session", async () => ({ at: new Date() }));
47
- // Result<{ at: string }, CacheError> — a Date is written as the string it serializes to
48
- ```
39
+ import { isSuccess } from "@sdxc/result";
49
40
 
50
- Swap the adapter to cache in a test without a KV namespace or a Worker:
41
+ await cache.write("post:1", { id: 1, title: "Hello" }, { ttl: "1 hour" });
51
42
 
52
- ```typescript
53
- import { MemoryCache } from "@sdxc/cache/memory";
43
+ let hit = await cache.read<{ id: number; title: string }>("post:1");
44
+ if (isSuccess(hit) && hit.data !== null) console.info(hit.data.title);
54
45
 
55
- let cache = new MemoryCache();
46
+ await cache.delete("post:1");
56
47
  ```
57
48
 
58
- ## Nothing throws
49
+ `read` succeeds with `null` for a missing or expired entry. A `ttl` is whole seconds or a
50
+ duration string such as `"1 hour"`; without one the entry never expires.
59
51
 
60
- Every method answers with a `Result<_, CacheError>` from
61
- [`@sdxc/result`](https://www.npmjs.com/package/@sdxc/result), and `CacheError.code` says
62
- which of three things happened:
52
+ ### Values come back as JSON
63
53
 
64
- | Code | Means | Reached by |
65
- | --------------- | -------------------------------------------------------- | ------------------------- |
66
- | `unavailable` | The store could not be reached, or refused the operation | `read`, `write`, `delete` |
67
- | `invalid_value` | JSON could not write the value, or read the entry stored | every method |
68
- | `load_failed` | The loader a `fetch` was given failed | `fetch` |
54
+ Entries are stored as JSON, and every method answers with the value as it reads back, so a hit
55
+ and a miss have the same type:
69
56
 
70
- `unavailable` is the one a caller can ignore: the value is simply not cached, and computing
71
- it is the same recovery a miss asks for. A store failure is also recorded on the
72
- invocation's log through [`@sdxc/logger`](https://www.npmjs.com/package/@sdxc/logger), so
73
- discarding one still leaves a trace — store health is the package's own to report. A
74
- `load_failed` is not logged, because it is the caller's and travels in the `Result`.
57
+ ```typescript
58
+ let session = await cache.fetch("session", async () => ({ at: new Date() }));
59
+ // Result<{ at: string }, CacheError>: the Date reads back as its ISO string
60
+ ```
75
61
 
76
- Every failure carries the `key` it happened on, and the original error as `cause`. So a
77
- caller that would rather throw rethrows the cause and keeps its own error type:
62
+ ### Store entries in Workers KV
78
63
 
79
64
  ```typescript
80
- let stored = await cache.fetch(key, load, { ttl: TTL });
81
- if (isFailure(stored)) throw stored.error.cause ?? stored.error;
82
- return stored.data;
65
+ import { WorkerKVCache } from "@sdxc/cache/worker-kv";
66
+
67
+ let cache = new WorkerKVCache(env.CACHE, { waitUntil: ctx.waitUntil.bind(ctx) });
83
68
  ```
84
69
 
85
- **`fetch` absorbs everything the store does wrong**, because it can compute a value
86
- instead: an unreachable store, an entry that is not JSON, and a refused write all still
87
- answer with the value. So a `fetch` failure means the value could not be produced at all.
70
+ With `waitUntil`, a write returns at once and the put finishes after the response; the instance
71
+ answers reads of that key from memory until KV holds it. Create one instance per request.
88
72
 
89
73
  ## API
90
74
 
91
75
  ### `Cache`
92
76
 
93
- The contract both adapters implement.
77
+ The contract every adapter implements, exported from `@sdxc/cache`.
94
78
 
95
- #### `read<T>(key): Promise<Result<JSONSerialized<T> | null, CacheError>>`
79
+ #### `read<T = JSONValue>(key: string): Promise<Result<JSONSerialized<T> | null, CacheError>>`
96
80
 
97
- Reads an entry, succeeding with `null` when it is missing or expired.
81
+ Reads an entry, succeeding with `null` when it is missing or expired. `T` is an assertion about
82
+ what the store holds. A stored `null` also reads as `null`; use `fetch` where a cached `null` must
83
+ count as a hit.
98
84
 
99
- `T` is an assertion. What a store holds under a key is a fact about the past, and no
100
- signature can check it, so the parameter states a claim rather than verifying one. It
101
- defaults to `JSONValue` for a caller who would rather narrow the result.
85
+ #### `write<T>(key: string, value: T, options?: CacheWriteOptions): Promise<Result<void, CacheError>>`
102
86
 
103
- A stored `null` succeeds with `null` too, indistinguishable from a miss. Reach for `fetch`
104
- where a cached `null` has to count as a hit.
87
+ Stores an entry, replacing any current value for the key. The entry is readable once the call
88
+ succeeds.
105
89
 
106
- ```typescript
107
- let hit = await cache.read<{ id: number }>("post:1");
108
- if (isSuccess(hit) && hit.data !== null) render(hit.data);
109
- ```
90
+ #### `fetch<T>(key: string, load: () => Promise<T>, options?: CacheWriteOptions): Promise<Result<JSONSerialized<T>, CacheError>>`
91
+
92
+ Returns the stored entry, or computes, stores and returns it on a miss. A stored `null` is a
93
+ hit. Store problems (unreachable store, an entry that is not JSON, a refused write) still answer
94
+ with the computed value, so a failure means the value could not be produced: `load_failed` or
95
+ `invalid_value`.
110
96
 
111
- #### `write<T>(key, value, options?): Promise<Result<void, CacheError>>`
97
+ #### `delete(key: string): Promise<Result<void, CacheError>>`
112
98
 
113
- Writes an entry, replacing any current value for the key. Succeeds once the value is
114
- readable.
99
+ Removes an entry, succeeding whether or not it exists.
115
100
 
116
- - `options.ttl` — how long the entry stays readable, as whole seconds or a duration string
117
- such as `"1 hour"`. Omitted, the entry never expires.
101
+ ### `CacheError`
118
102
 
119
- ```typescript
120
- await cache.write("post:1", { id: 1, publishedAt: new Date() }, { ttl: "1 hour" });
121
- ```
103
+ The error inside every failed `Result`: `code`, the `key` the call was made on, and the original
104
+ error as `cause`.
122
105
 
123
- #### `fetch<T>(key, load, options?): Promise<Result<JSONSerialized<T>, CacheError>>`
106
+ | `code` | Means | Reached by |
107
+ | --------------- | ----------------------------------------------------------- | ------------------------- |
108
+ | `unavailable` | The store could not be reached, or refused the operation | `read`, `write`, `delete` |
109
+ | `invalid_value` | JSON could not write the value, or could not read the entry | every method but `delete` |
110
+ | `load_failed` | The loader given to `fetch` threw or rejected | `fetch` |
124
111
 
125
- Returns the stored entry, computing and storing it on a miss. The type comes from `load`,
126
- and the value is returned as it reads back, so a hit and a miss answer with the same type.
112
+ An `unavailable` failure is also logged as a warning on the current invocation's log from
113
+ [`@sdxc/logger`](https://www.npmjs.com/package/@sdxc/logger), so a caller can treat it as a miss
114
+ and still leave a trace.
127
115
 
128
- A stored `null` is a hit here, unlike in `read`.
116
+ ### `new MemoryCache(options?: MemoryCacheOptions)`
129
117
 
130
- ```typescript
131
- let feed = await cache.fetch("feed", async () => buildFeed(), { ttl: "5 minutes" });
132
- ```
118
+ From `@sdxc/cache/memory`. Holds entries in a map for the life of the instance, serializing on
119
+ write and parsing on read exactly as a remote store does; a value read back is a copy.
133
120
 
134
- #### `delete(key): Promise<Result<void, CacheError>>`
121
+ - `options.now`: returns the current time in milliseconds, for expiring entries in a test.
122
+ Defaults to `Date.now`.
135
123
 
136
- Removes an entry, succeeding whether or not it exists.
124
+ ### `new WorkerKVCache(kv: KVNamespace, options?: WorkerKVCacheOptions)`
137
125
 
138
- ### `CacheError`
126
+ From `@sdxc/cache/worker-kv`. Stores entries in a Cloudflare KV namespace.
139
127
 
140
- The failure inside every `Result`. Carries `code`, the `key` it happened on, and the
141
- original error as `cause`.
128
+ - `options.waitUntil`: extends the invocation past the response. Given one, writes are deferred
129
+ to it, the instance answers reads from those writes immediately, writes to one key land in
130
+ order, and a `delete` waits for a pending put.
142
131
 
143
- ### `MemoryCache`
132
+ KV rejects a TTL under 60 seconds: the entry is not written and the put reports `unavailable`.
144
133
 
145
- `@sdxc/cache/memory` — holds entries in a map for the life of the instance.
134
+ ### `conformance(options: ConformanceOptions): void`
146
135
 
147
- It is not a mock. It serializes on write and parses on read, so a `Date` comes back as a
148
- string here exactly as it would from a remote store, and it passes the same conformance
149
- suite every other adapter does. A value read back is a copy, so mutating it changes
150
- nothing that is stored.
136
+ From `@sdxc/cache/conformance`. Registers Vitest tests asserting the `Cache` contract against an
137
+ adapter you construct.
151
138
 
152
- ```typescript
153
- new MemoryCache();
154
- new MemoryCache({ now: () => clock }); // expire an entry without waiting for one
155
- ```
139
+ - `options.name`: labels the registered suite.
140
+ - `options.create`: builds the cache under test; called once per test.
141
+ - `options.expire`: moves the adapter's clock forward by a number of seconds. Leave it out and
142
+ the expiry tests are skipped.
156
143
 
157
- - `options.now` — reads the current time in milliseconds. Defaults to `Date.now`.
144
+ ### Types
158
145
 
159
- ### `WorkerKVCache`
146
+ #### `CacheWriteOptions`
160
147
 
161
- `@sdxc/cache/worker-kv` — reads and writes entries in a KV namespace.
148
+ `{ ttl?: DurationInput }`, where `DurationInput` from
149
+ [`@sdxc/duration`](https://www.npmjs.com/package/@sdxc/duration) is whole seconds or a duration
150
+ string.
151
+
152
+ #### `CacheErrorCode`
153
+
154
+ `"unavailable" | "invalid_value" | "load_failed"`.
155
+
156
+ #### `CacheErrorOptions`
157
+
158
+ What `new CacheError(message, options)` takes: `code`, `key`, and the standard `cause`. An
159
+ adapter of your own builds its failures with it.
160
+
161
+ #### `MemoryCacheOptions`, `WorkerKVCacheOptions`, `ConformanceOptions`
162
+
163
+ The options objects described above.
164
+
165
+ ## Pattern: Accept Any Cache, Test With Memory
166
+
167
+ Type a dependency as `Cache` so production passes the KV adapter and a test passes a memory one.
162
168
 
163
169
  ```typescript
164
- new WorkerKVCache(env.CACHE);
165
- new WorkerKVCache(env.CACHE, { waitUntil: ctx.waitUntil.bind(ctx) });
166
- ```
170
+ import type { Cache } from "@sdxc/cache";
171
+ import { MemoryCache } from "@sdxc/cache/memory";
172
+ import { isFailure } from "@sdxc/result";
173
+ import { expect, test, vi } from "vitest";
174
+
175
+ interface Post {
176
+ id: string;
177
+ title: string;
178
+ }
179
+
180
+ async function publishedPosts(cache: Cache, load: () => Promise<Post[]>) {
181
+ let posts = await cache.fetch("posts:published", load, { ttl: "5 minutes" });
182
+ return isFailure(posts) ? [] : posts.data;
183
+ }
167
184
 
168
- - `options.waitUntil` — extends the invocation past the response. Given one, a write is
169
- handed over and the caller is not made to wait for KV.
185
+ test("loads the posts once", async () => {
186
+ let cache = new MemoryCache();
187
+ let load = vi.fn(async () => [{ id: "1", title: "Hello" }]);
170
188
 
171
- A deferred write is still readable the moment it succeeds: the value is held on the
172
- instance until the put lands, and an instance is per request, which is the scope over
173
- which reading your own writes is both achievable and worth anything. Two writes to one key
174
- land in the order they were made, and a `delete` waits for a put still in flight.
189
+ await publishedPosts(cache, load);
190
+ await publishedPosts(cache, load);
175
191
 
176
- KV refuses a TTL under 60 seconds, so a shorter one leaves the entry unwritten and reports
177
- `unavailable`. A caller wanting a shorter lifetime than that wants something other than a
178
- cache.
192
+ expect(load).toHaveBeenCalledTimes(1);
193
+ });
194
+ ```
179
195
 
180
- ### `conformance(options)`
196
+ ## Pattern: Expire Entries On A Test Clock
181
197
 
182
- `@sdxc/cache/conformance` — registers the suite that says what a cache is, as Vitest tests
183
- against whatever you construct. Run it against a new adapter and it is a cache, or it is
184
- not.
198
+ Pass `now` to control time, and an entry expires without waiting for it.
185
199
 
186
200
  ```typescript
187
- import { conformance } from "@sdxc/cache/conformance";
201
+ import { MemoryCache } from "@sdxc/cache/memory";
202
+ import { unwrap } from "@sdxc/result";
203
+ import { expect, test } from "vitest";
188
204
 
189
- conformance({
190
- name: "MyCache",
191
- create: () => new MyCache(),
192
- expire: (seconds) => clock.advance(seconds),
205
+ test("an entry expires after its ttl", async () => {
206
+ let clock = 0;
207
+ let cache = new MemoryCache({ now: () => clock });
208
+
209
+ unwrap(await cache.write("token", "abc", { ttl: "1 minute" }));
210
+ clock += 59_000;
211
+ expect(unwrap(await cache.read("token"))).toBe("abc");
212
+
213
+ clock += 1_000;
214
+ expect(unwrap(await cache.read("token"))).toBeNull();
193
215
  });
194
216
  ```
195
217
 
196
- - `name` — labels the registered suite.
197
- - `create` — builds the cache under test, called for every test so a mutable adapter starts
198
- clean.
199
- - `expire` — moves the adapter's clock forward. An adapter that can only expire in real
200
- time leaves it out, and the expiry tests are registered only for the adapters that can.
218
+ ## Pattern: Cache Per Request In A Worker
219
+
220
+ Build the KV adapter inside the handler so read-your-own-writes stays scoped to one request, and
221
+ key entries by what they hold so a shared namespace stays readable.
201
222
 
202
- ## Pattern: keying by prefix
223
+ ```typescript
224
+ import { WorkerKVCache } from "@sdxc/cache/worker-kv";
225
+ import { isFailure } from "@sdxc/result";
226
+
227
+ interface Env {
228
+ CACHE: KVNamespace;
229
+ }
203
230
 
204
- Prefix a key by what it holds, so entries are recognizable in a namespace shared with other
205
- caches and a related group can be reasoned about together:
231
+ export default {
232
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
233
+ let cache = new WorkerKVCache(env.CACHE, { waitUntil: ctx.waitUntil.bind(ctx) });
234
+ let locale = new URL(request.url).searchParams.get("locale") ?? "en";
235
+
236
+ let feed = await cache.fetch(`feed:${locale}`, async () => buildFeed(locale), {
237
+ ttl: "10 minutes",
238
+ });
239
+ if (isFailure(feed)) return new Response("Feed unavailable", { status: 503 });
240
+
241
+ return Response.json(feed.data);
242
+ },
243
+ };
244
+ ```
245
+
246
+ ## Pattern: Invalidate After A Write
247
+
248
+ Delete the cached entry when the source changes, and the next `fetch` recomputes it.
206
249
 
207
250
  ```typescript
208
- await cache.write(`post:${id}`, post, { ttl: "1 hour" });
209
- await cache.write(`feed:${locale}`, feed, { ttl: "5 minutes" });
251
+ import type { Cache } from "@sdxc/cache";
252
+
253
+ async function renamePost(cache: Cache, id: string, title: string) {
254
+ await savePostTitle(id, title);
255
+ await cache.delete(`post:${id}`);
256
+ }
210
257
  ```
211
258
 
212
- ## Pattern: a cache in a test
259
+ ## Pattern: Rethrow Failures
213
260
 
214
- Take `Cache` where a cache is used, and the test supplies a memory one:
261
+ A caller that prefers exceptions rethrows the original error, keeping its own error type.
215
262
 
216
263
  ```typescript
217
- async function publishedPosts(cache: Cache): Promise<Post[]> {
218
- let posts = await cache.fetch("posts", async () => db.query.posts.findMany(), {
219
- ttl: "5 minutes",
220
- });
264
+ import type { Cache } from "@sdxc/cache";
265
+ import { isFailure } from "@sdxc/result";
221
266
 
222
- return isFailure(posts) ? [] : posts.data;
267
+ async function cachedReport(cache: Cache, month: string) {
268
+ let report = await cache.fetch(`report:${month}`, async () => buildReport(month), {
269
+ ttl: "1 day",
270
+ });
271
+ if (isFailure(report)) throw report.error.cause ?? report.error;
272
+ return report.data;
223
273
  }
274
+ ```
224
275
 
225
- test("reads the posts once", async () => {
226
- let cache = new MemoryCache();
276
+ ## Pattern: Verify A Custom Adapter
227
277
 
228
- await publishedPosts(cache);
229
- await publishedPosts(cache);
278
+ Implement `Cache` over another store, answer its failures with a `CacheError`, and run the
279
+ conformance suite against it in a test file.
230
280
 
231
- expect(queries).toHaveLength(1);
281
+ ```typescript
282
+ import type { Cache } from "@sdxc/cache";
283
+ import { CacheError } from "@sdxc/cache";
284
+ import { conformance } from "@sdxc/cache/conformance";
285
+ import { failure } from "@sdxc/result";
286
+
287
+ export function unavailable(key: string, cause: unknown) {
288
+ return failure(
289
+ new CacheError(`The store could not reach ${key}`, { code: "unavailable", key, cause }),
290
+ );
291
+ }
292
+
293
+ conformance({
294
+ name: "RedisCache",
295
+ create: (): Cache => new RedisCache(redis),
232
296
  });
233
297
  ```
234
298
 
235
299
  ## Versioning
236
300
 
237
- Releases are dated rather than semantic. A version is the UTC date it was published,
238
- written `YYYY.M.D`, so `2026.9.4` is the release from 4 September 2026. At most one
239
- release goes out per day.
301
+ Releases are dated rather than semantic. A version is the UTC date it was published, written
302
+ `YYYY.M.D`, so `2026.9.4` is the release from 4 September 2026. At most one release goes out per
303
+ day.
240
304
 
241
305
  Those numbers say when, not what: a later date means a later release and carries no
242
306
  compatibility promise. Any release may change or remove an export.
@@ -251,8 +315,8 @@ Depend on one exact date, and move it when you are ready to take the change:
251
315
  }
252
316
  ```
253
317
 
254
- A caret or tilde range reads the date as major, minor and patch, so it accepts every
255
- later release in the same year. An exact version keeps the upgrade yours to schedule.
318
+ A caret or tilde range reads the date as major, minor and patch, so it accepts every later
319
+ release in the same year. An exact version keeps the upgrade yours to schedule.
256
320
 
257
321
  ## License
258
322
 
@@ -9,6 +9,17 @@
9
9
  import type { Result } from "@sdxc/result";
10
10
  import type { JSONSerialized, JSONValue } from "@sdxc/types";
11
11
  import type { Cache, CacheError, CacheWriteOptions } from "../index.js";
12
+ /**
13
+ * The calls this cache makes on a Workers KV binding. Declared here so a binding typed by any
14
+ * source fits it: a Worker's generated types, `@cloudflare/workers-types`, or a test's own.
15
+ */
16
+ export interface WorkerKVNamespace {
17
+ get(key: string, type: "text"): Promise<string | null>;
18
+ put(key: string, value: string, options?: {
19
+ expirationTtl?: number;
20
+ }): Promise<void>;
21
+ delete(key: string): Promise<void>;
22
+ }
12
23
  /** How a Worker KV cache defers its writes. */
13
24
  export interface WorkerKVCacheOptions {
14
25
  /**
@@ -30,7 +41,7 @@ export declare class WorkerKVCache implements Cache {
30
41
  * @param kv The namespace entries are stored in.
31
42
  * @param options How writes are deferred.
32
43
  */
33
- constructor(kv: KVNamespace, { waitUntil }?: WorkerKVCacheOptions);
44
+ constructor(kv: WorkerKVNamespace, { waitUntil }?: WorkerKVCacheOptions);
34
45
  read<T = JSONValue>(key: string): Promise<Result<JSONSerialized<T> | null, CacheError>>;
35
46
  write<T>(key: string, value: T, options?: CacheWriteOptions): Promise<Result<void, CacheError>>;
36
47
  fetch<T>(key: string, load: () => Promise<T>, options?: CacheWriteOptions): Promise<Result<JSONSerialized<T>, CacheError>>;
@@ -7,6 +7,7 @@
7
7
  * @copyright Sergio Xalambrí 2026
8
8
  */
9
9
  import { isSuccess, unwrap } from "@sdxc/result";
10
+ import { generateUUID } from "@sdxc/uuid/v4";
10
11
  import { describe, expect, test, vi } from "vitest";
11
12
  /**
12
13
  * The shortest TTL the suite asks for. KV refuses anything under 60 seconds, so
@@ -20,7 +21,7 @@ const MIN_TTL_SECONDS = 60;
20
21
  */
21
22
  export function conformance({ name, create, expire }) {
22
23
  /** A key no other test has used, so adapters sharing a store cannot collide. */
23
- let key = () => `conformance:${crypto.randomUUID()}`;
24
+ let key = () => `conformance:${generateUUID()}`;
24
25
  /** The error a call answered with, asserting that it answered with one. */
25
26
  let errorOf = (result) => {
26
27
  if (isSuccess(result))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdxc/cache",
3
- "version": "2026.10.12",
3
+ "version": "2026.10.16",
4
4
  "description": "Cache contract with adapters for memory and Cloudflare KV",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,12 +11,13 @@
11
11
  "./worker-kv": "./dist/adapters/worker-kv.js"
12
12
  },
13
13
  "dependencies": {
14
- "@sdxc/duration": "2026.10.6",
15
- "@sdxc/logger": "2026.10.12",
16
- "@sdxc/result": "2026.10.6",
17
- "@sdxc/types": "2026.10.2"
14
+ "@sdxc/duration": "2026.10.13",
15
+ "@sdxc/logger": "2026.10.16",
16
+ "@sdxc/result": "2026.10.13",
17
+ "@sdxc/types": "2026.10.13",
18
+ "@sdxc/uuid": "2026.10.13"
18
19
  },
19
- "gitHead": "857ef87ddb0b2484e28b46431d0740fcc80e5f33",
20
+ "gitHead": "1e547d8431568f5ed9bdb079ca3046c936240fde",
20
21
  "publishConfig": {
21
22
  "access": "public"
22
23
  },
@@ -25,7 +26,7 @@
25
26
  "url": "git+https://github.com/sergiodxa/monorepo.git",
26
27
  "directory": "packages/cache"
27
28
  },
28
- "homepage": "https://sdxc.sergiodxa.com/api/cache",
29
+ "homepage": "https://sdxc.dev/api/cache",
29
30
  "author": {
30
31
  "name": "Sergio Xalambrí",
31
32
  "url": "https://sergiodxa.com"