@mrjasonroy/cache-components-cache-handler 16.0.0-alpha.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.
Files changed (50) hide show
  1. package/README.md +437 -0
  2. package/dist/data-cache/memory.d.ts +47 -0
  3. package/dist/data-cache/memory.d.ts.map +1 -0
  4. package/dist/data-cache/memory.js +310 -0
  5. package/dist/data-cache/memory.js.map +1 -0
  6. package/dist/data-cache/redis.d.ts +83 -0
  7. package/dist/data-cache/redis.d.ts.map +1 -0
  8. package/dist/data-cache/redis.js +240 -0
  9. package/dist/data-cache/redis.js.map +1 -0
  10. package/dist/data-cache/types.d.ts +85 -0
  11. package/dist/data-cache/types.d.ts.map +1 -0
  12. package/dist/data-cache/types.js +7 -0
  13. package/dist/data-cache/types.js.map +1 -0
  14. package/dist/handlers/composite.d.ts +70 -0
  15. package/dist/handlers/composite.d.ts.map +1 -0
  16. package/dist/handlers/composite.js +123 -0
  17. package/dist/handlers/composite.js.map +1 -0
  18. package/dist/handlers/memory.d.ts +77 -0
  19. package/dist/handlers/memory.d.ts.map +1 -0
  20. package/dist/handlers/memory.js +145 -0
  21. package/dist/handlers/memory.js.map +1 -0
  22. package/dist/handlers/redis.d.ts +80 -0
  23. package/dist/handlers/redis.d.ts.map +1 -0
  24. package/dist/handlers/redis.js +210 -0
  25. package/dist/handlers/redis.js.map +1 -0
  26. package/dist/helpers/buffer.d.ts +25 -0
  27. package/dist/helpers/buffer.d.ts.map +1 -0
  28. package/dist/helpers/buffer.js +45 -0
  29. package/dist/helpers/buffer.js.map +1 -0
  30. package/dist/helpers/is-implicit-tag.d.ts +9 -0
  31. package/dist/helpers/is-implicit-tag.d.ts.map +1 -0
  32. package/dist/helpers/is-implicit-tag.js +16 -0
  33. package/dist/helpers/is-implicit-tag.js.map +1 -0
  34. package/dist/helpers/lifespan.d.ts +18 -0
  35. package/dist/helpers/lifespan.d.ts.map +1 -0
  36. package/dist/helpers/lifespan.js +43 -0
  37. package/dist/helpers/lifespan.js.map +1 -0
  38. package/dist/helpers/next-config.d.ts +97 -0
  39. package/dist/helpers/next-config.d.ts.map +1 -0
  40. package/dist/helpers/next-config.js +96 -0
  41. package/dist/helpers/next-config.js.map +1 -0
  42. package/dist/index.d.ts +17 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +23 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/types.d.ts +163 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +9 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +67 -0
package/README.md ADDED
@@ -0,0 +1,437 @@
1
+ # @mrjasonroy/cache-components-cache-handler
2
+
3
+ Modern cache handler for Next.js 16+ with support for the `"use cache"` directive and cache components.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Next.js 16+ only** - Built specifically for the new caching system
8
+ - ✅ **TypeScript** - Full type safety with strict mode
9
+ - ✅ **In-memory LRU** - Fast, production-ready memory caching
10
+ - ✅ **Composite handler** - Multi-tier caching strategies
11
+ - ✅ **Tag-based revalidation** - Both explicit and implicit tags
12
+ - ✅ **TTL support** - Time-based expiration
13
+ - ✅ **Zero dependencies** - Lightweight and fast
14
+ - ✅ **Comprehensive tests** - 31 tests, 100% coverage
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @mrjasonroy/cache-components-cache-handler
20
+ # or
21
+ pnpm add @mrjasonroy/cache-components-cache-handler
22
+ # or
23
+ yarn add @mrjasonroy/cache-components-cache-handler
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Setup for Next.js 16
29
+
30
+ Next.js 16 has **two separate cache systems**:
31
+ 1. **ISR Cache** (`cacheHandler`) - For Incremental Static Regeneration
32
+ 2. **Data Cache** (`cacheHandlers`) - For `"use cache"` and `"use cache: remote"`
33
+
34
+ Use the helper function to configure both easily:
35
+
36
+ ```typescript
37
+ // next.config.mjs
38
+ import { createCacheConfig } from "@mrjasonroy/cache-components-cache-handler";
39
+ import { resolve } from "path";
40
+
41
+ const cacheConfig = createCacheConfig({
42
+ isrHandlerPath: resolve(process.cwd(), "./cache-handler.mjs"),
43
+ dataCacheHandlerPath: resolve(process.cwd(), "./data-cache-handler.mjs"),
44
+ });
45
+
46
+ export default {
47
+ cacheComponents: true, // Enable cache components
48
+ ...cacheConfig,
49
+ // Rest of your Next.js config
50
+ };
51
+ ```
52
+
53
+ Then create your cache handler files:
54
+
55
+ ```javascript
56
+ // cache-handler.mjs (ISR Cache)
57
+ import { createMemoryCacheHandler } from "@mrjasonroy/cache-components-cache-handler";
58
+
59
+ export default createMemoryCacheHandler({
60
+ maxItemsNumber: 1000,
61
+ maxItemSizeBytes: 100 * 1024 * 1024, // 100MB
62
+ });
63
+ ```
64
+
65
+ ```javascript
66
+ // data-cache-handler.mjs (Data Cache for "use cache")
67
+ import { createMemoryDataCacheHandler } from "@mrjasonroy/cache-components-cache-handler";
68
+
69
+ export default createMemoryDataCacheHandler({
70
+ ttl: 3600, // 1 hour default
71
+ });
72
+ ```
73
+
74
+ ### Multi-Tier Caching
75
+
76
+ ```typescript
77
+ // next.config.js
78
+ import {
79
+ createMemoryCacheHandler,
80
+ createCompositeHandler,
81
+ } from "@mrjasonroy/cache-components-cache-handler";
82
+
83
+ const memoryHandler = createMemoryCacheHandler({
84
+ maxItemsNumber: 100, // Small L1 cache
85
+ });
86
+
87
+ const redisHandler = createRedisHandler({
88
+ // Your Redis handler implementation
89
+ });
90
+
91
+ export default {
92
+ cacheHandler: createCompositeHandler({
93
+ handlers: [memoryHandler, redisHandler],
94
+ // Optional: route based on tags
95
+ setStrategy: (data) => {
96
+ if (data.tags.includes("memory-only")) {
97
+ return 0; // Use memory handler
98
+ }
99
+ return 1; // Use Redis handler
100
+ },
101
+ }),
102
+ };
103
+ ```
104
+
105
+ ## API Reference
106
+
107
+ ### `createCacheConfig(options)`
108
+
109
+ Helper function to generate Next.js 16 cache configuration for both ISR and Data Cache.
110
+
111
+ **Options:**
112
+
113
+ | Option | Type | Required | Description |
114
+ |--------|------|----------|-------------|
115
+ | `isrHandlerPath` | `string` | Yes | Path to ISR cache handler file |
116
+ | `dataCacheHandlerPath` | `string` | Yes | Path to data cache handler file |
117
+ | `disableDefaultMemoryCache` | `boolean` | No | Disable Next.js default memory cache (default: `true`) |
118
+
119
+ **Returns:**
120
+
121
+ ```typescript
122
+ {
123
+ cacheHandler: string; // ISR cache handler path
124
+ cacheHandlers: {
125
+ default: string; // "use cache" handler path
126
+ remote: string; // "use cache: remote" handler path
127
+ };
128
+ cacheMaxMemorySize?: number; // 0 if disableDefaultMemoryCache is true
129
+ }
130
+ ```
131
+
132
+ **Example:**
133
+
134
+ ```typescript
135
+ import { createCacheConfig } from "@mrjasonroy/cache-components-cache-handler";
136
+ import { resolve } from "path";
137
+
138
+ const cacheConfig = createCacheConfig({
139
+ isrHandlerPath: resolve(process.cwd(), "./cache-handler.mjs"),
140
+ dataCacheHandlerPath: resolve(process.cwd(), "./data-cache-handler.mjs"),
141
+ });
142
+
143
+ export default {
144
+ cacheComponents: true,
145
+ ...cacheConfig,
146
+ };
147
+ ```
148
+
149
+ ### `createCacheConfigWithProfiles(options)`
150
+
151
+ Advanced helper for using different cache handlers for `default` and `remote` profiles.
152
+
153
+ **Options:**
154
+
155
+ | Option | Type | Required | Description |
156
+ |--------|------|----------|-------------|
157
+ | `isrHandlerPath` | `string` | Yes | Path to ISR cache handler file |
158
+ | `defaultDataCacheHandlerPath` | `string` | Yes | Path for "use cache" (fast, local) |
159
+ | `remoteDataCacheHandlerPath` | `string` | Yes | Path for "use cache: remote" (DB, API) |
160
+ | `disableDefaultMemoryCache` | `boolean` | No | Disable Next.js default memory cache (default: `true`) |
161
+
162
+ **Example:**
163
+
164
+ ```typescript
165
+ const cacheConfig = createCacheConfigWithProfiles({
166
+ isrHandlerPath: resolve(process.cwd(), "./cache-handler.mjs"),
167
+ defaultDataCacheHandlerPath: resolve(process.cwd(), "./data-cache-memory.mjs"),
168
+ remoteDataCacheHandlerPath: resolve(process.cwd(), "./data-cache-redis.mjs"),
169
+ });
170
+ ```
171
+
172
+ ### `createMemoryCacheHandler(options?)`
173
+
174
+ Creates an in-memory LRU cache handler.
175
+
176
+ **Options:**
177
+
178
+ | Option | Type | Default | Description |
179
+ |--------|------|---------|-------------|
180
+ | `maxItemsNumber` | `number` | `1000` | Maximum number of items to store |
181
+ | `maxItemSizeBytes` | `number` | `104857600` (100MB) | Maximum size per item |
182
+ | `defaultTTL` | `number` | `undefined` | Default TTL in seconds |
183
+
184
+ **Features:**
185
+
186
+ - LRU eviction when max size exceeded
187
+ - Automatic expiration based on TTL
188
+ - Tag-based revalidation (explicit and implicit)
189
+ - Size-based rejection of oversized entries
190
+
191
+ **Example:**
192
+
193
+ ```typescript
194
+ const handler = createMemoryCacheHandler({
195
+ maxItemsNumber: 500,
196
+ maxItemSizeBytes: 50 * 1024 * 1024, // 50MB
197
+ defaultTTL: 1800, // 30 minutes
198
+ });
199
+ ```
200
+
201
+ ### `createCompositeHandler(options)`
202
+
203
+ Creates a composite handler that orchestrates multiple cache handlers.
204
+
205
+ **Options:**
206
+
207
+ | Option | Type | Required | Description |
208
+ |--------|------|----------|-------------|
209
+ | `handlers` | `CacheHandler[]` | Yes | Array of handlers (ordered by priority) |
210
+ | `setStrategy` | `(data) => number` | No | Function to choose which handler to use for writes |
211
+
212
+ **Features:**
213
+
214
+ - First-match read strategy (tries handlers in order)
215
+ - Configurable write strategy
216
+ - Fault tolerance with `Promise.allSettled()`
217
+ - Parallel revalidation across all handlers
218
+
219
+ **Example:**
220
+
221
+ ```typescript
222
+ const composite = createCompositeHandler({
223
+ handlers: [memoryHandler, redisHandler],
224
+ setStrategy: (data) => {
225
+ // Cache small items in memory, large items in Redis
226
+ const size = JSON.stringify(data).length;
227
+ return size < 10000 ? 0 : 1;
228
+ },
229
+ });
230
+ ```
231
+
232
+ ## Advanced Usage
233
+
234
+ ### Tag-Based Revalidation
235
+
236
+ ```typescript
237
+ // app/page.tsx
238
+ export default async function Page() {
239
+ "use cache";
240
+ // This component will be cached with implicit tags
241
+ return <div>Cached content</div>;
242
+ }
243
+
244
+ // app/api/revalidate/route.ts
245
+ import { revalidateTag } from "next/cache";
246
+
247
+ export async function POST(request: Request) {
248
+ const { tag } = await request.json();
249
+ revalidateTag(tag);
250
+ return Response.json({ revalidated: true });
251
+ }
252
+ ```
253
+
254
+ ### Time-Based Revalidation
255
+
256
+ ```typescript
257
+ // app/page.tsx
258
+ export default async function Page() {
259
+ "use cache";
260
+ export const revalidate = 3600; // Revalidate after 1 hour
261
+
262
+ return <div>Cached for 1 hour</div>;
263
+ }
264
+ ```
265
+
266
+ ### Custom Cache Tags
267
+
268
+ ```typescript
269
+ // app/actions.ts
270
+ "use server";
271
+
272
+ import { unstable_cache } from "next/cache";
273
+
274
+ export const getData = unstable_cache(
275
+ async () => {
276
+ return fetch("https://api.example.com/data").then((res) => res.json());
277
+ },
278
+ ["api-data"],
279
+ {
280
+ tags: ["api-data", "user-data"],
281
+ revalidate: 3600,
282
+ },
283
+ );
284
+ ```
285
+
286
+ ## How It Works
287
+
288
+ ### Implicit vs Explicit Tags
289
+
290
+ Next.js 16 uses two types of tags:
291
+
292
+ 1. **Explicit tags** - User-defined tags (e.g., `["user-123", "posts"]`)
293
+ 2. **Implicit tags** - System-generated tags with `_N_T_` prefix for ISR
294
+
295
+ The handler tracks both types separately for efficient revalidation.
296
+
297
+ ### LRU Eviction
298
+
299
+ When the cache reaches `maxItemsNumber`:
300
+
301
+ 1. The oldest (least recently used) entry is evicted
302
+ 2. Entries are moved to the end on each access
303
+ 3. New entries are always added at the end
304
+
305
+ ### TTL Expiration
306
+
307
+ Entries can expire based on:
308
+
309
+ 1. `revalidate` value in cache context (seconds)
310
+ 2. `defaultTTL` if no revalidate specified
311
+ 3. Never expire if `revalidate: false`
312
+
313
+ Expiration is checked on every `get()` call (lazy expiration).
314
+
315
+ ## Type Definitions
316
+
317
+ ```typescript
318
+ interface CacheHandler {
319
+ name: string;
320
+ get(key: string, meta?: CacheHandlerGetMeta): Promise<CacheValue | null>;
321
+ set(key: string, value: CacheValue, context?: CacheHandlerContext): Promise<void>;
322
+ revalidateTag(tag: string): Promise<void>;
323
+ delete?(key: string): Promise<void>;
324
+ }
325
+
326
+ interface CacheHandlerContext {
327
+ tags?: string[];
328
+ revalidate?: number | false;
329
+ softTags?: string[];
330
+ }
331
+
332
+ interface CacheHandlerGetMeta {
333
+ implicitTags: string[];
334
+ }
335
+ ```
336
+
337
+ ## Testing
338
+
339
+ ```bash
340
+ # Run tests
341
+ pnpm test
342
+
343
+ # Run tests in watch mode
344
+ pnpm test:watch
345
+
346
+ # Run with coverage
347
+ pnpm test --coverage
348
+ ```
349
+
350
+ ## Performance
351
+
352
+ The memory handler is optimized for production use:
353
+
354
+ - Native `Map` for O(1) lookups
355
+ - Minimal overhead per cache entry
356
+ - Zero dependencies
357
+ - Fast expiration checks
358
+ - Efficient LRU implementation
359
+
360
+ Benchmark (1000 items, 1KB each):
361
+
362
+ - Set: ~0.01ms per item
363
+ - Get: ~0.005ms per item
364
+ - Memory usage: ~1MB + cache data
365
+
366
+ ## Migration from Other Handlers
367
+
368
+ ### From Next.js Built-in Cache
369
+
370
+ ```diff
371
+ // next.config.js
372
+ + import { createMemoryCacheHandler } from "@mrjasonroy/cache-components-cache-handler";
373
+
374
+ export default {
375
+ + cacheHandler: createMemoryCacheHandler(),
376
+ };
377
+ ```
378
+
379
+ ### From Custom Redis Handler
380
+
381
+ ```diff
382
+ // next.config.js
383
+ import {
384
+ createMemoryCacheHandler,
385
+ + createCompositeHandler,
386
+ } from "@mrjasonroy/cache-components-cache-handler";
387
+ import { createRedisHandler } from "./redis-handler";
388
+
389
+ export default {
390
+ - cacheHandler: createRedisHandler(),
391
+ + cacheHandler: createCompositeHandler({
392
+ + handlers: [
393
+ + createMemoryCacheHandler({ maxItemsNumber: 100 }), // L1 cache
394
+ + createRedisHandler(), // L2 cache
395
+ + ],
396
+ + }),
397
+ };
398
+ ```
399
+
400
+ ## Troubleshooting
401
+
402
+ ### Cache not working
403
+
404
+ 1. Check Next.js version (requires 16.0.0+)
405
+ 2. Verify `"use cache"` directive is at top of component/function
406
+ 3. Check browser/server console for errors
407
+
408
+ ### Memory usage too high
409
+
410
+ 1. Reduce `maxItemsNumber`
411
+ 2. Reduce `maxItemSizeBytes`
412
+ 3. Add TTL with `defaultTTL`
413
+ 4. Use composite handler with Redis for large caches
414
+
415
+ ### Revalidation not working
416
+
417
+ 1. Verify tags are correctly defined
418
+ 2. Check `revalidateTag()` is called on server
419
+ 3. Ensure implicit tags are passed to `get()`
420
+
421
+ ## Contributing
422
+
423
+ This project is part of a monorepo. See the main [README](../../README.md) for contribution guidelines.
424
+
425
+ ## License
426
+
427
+ MIT © Jason Roy
428
+
429
+ ## Support
430
+
431
+ - [GitHub Issues](https://github.com/mrjasonroy/cache-components-cache-handler/issues)
432
+ - [Documentation](https://github.com/mrjasonroy/cache-components-cache-handler)
433
+
434
+ ## Related
435
+
436
+ - [Next.js 16 Cache Documentation](https://nextjs.org/docs/app/getting-started/cache-components)
437
+ - [@mrjasonroy/cache-components-cache-handler-redis](../cache-handler-redis) - Redis implementation
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Memory-based Data Cache Handler for "use cache" directive
3
+ * Based on Next.js default handler but with customizable options
4
+ *
5
+ * In-memory caches are fragile and should not use stale-while-revalidate
6
+ * semantics because entries may be evicted before reuse. Stale entries
7
+ * are considered expired/missing.
8
+ */
9
+ import type { DataCacheHandler } from "./types.js";
10
+ export interface MemoryDataCacheHandlerOptions {
11
+ /**
12
+ * Maximum cache size in bytes
13
+ * @default 50MB (50 * 1024 * 1024)
14
+ */
15
+ maxSize?: number;
16
+ /**
17
+ * Enable debug logging
18
+ * @default false
19
+ */
20
+ debug?: boolean;
21
+ }
22
+ /**
23
+ * Create a memory-based data cache handler for "use cache" directive
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // cache-handler.mjs
28
+ * import { createMemoryDataCacheHandler } from '@mrjasonroy/better-nextjs-cache-handler/data-cache';
29
+ *
30
+ * export default createMemoryDataCacheHandler({
31
+ * maxSize: 100 * 1024 * 1024, // 100MB
32
+ * debug: process.env.NODE_ENV === 'development'
33
+ * });
34
+ * ```
35
+ *
36
+ * Then in next.config.js:
37
+ * ```javascript
38
+ * module.exports = {
39
+ * cacheComponents: true,
40
+ * cacheHandlers: {
41
+ * default: require.resolve('./cache-handler.mjs')
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ export declare function createMemoryDataCacheHandler(options?: MemoryDataCacheHandlerOptions): DataCacheHandler;
47
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/data-cache/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAkB,gBAAgB,EAAa,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA+LD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,6BAAkC,GAC1C,gBAAgB,CAiKlB"}