@pixel-pulse/next-cache-brain-core 0.2.0 → 0.3.1
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 +149 -0
- package/dist/index.d.mts +33 -9
- package/dist/index.d.ts +33 -9
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
feat: transition to beta with new smartFetch engine and renamed DevTools
|
|
2
|
+
|
|
3
|
+
- Rename `CacheDevtools` to `CacheBrainDevtools` for brand consistency.
|
|
4
|
+
- Implement `smartFetch` with support for `swr`, `cache-first`, and `network-first` strategies.
|
|
5
|
+
- Add real-time synchronization between core and UI via global event bridge.
|
|
6
|
+
- Update documentation and README for NPM launch.# 🧠 Next Cache Brain (Beta)
|
|
7
|
+
|
|
8
|
+
> **The intelligent caching layer for Next.js and React.** > Monitor, debug, and optimize your data fetching with a beautiful real-time DevTools interface.
|
|
9
|
+
|
|
10
|
+
`next-cache-brain` provides a suite of high-level fetching strategies (SWR, Cache-First, Network-First) paired with a visual debugger to help you understand exactly what’s happening in your application's cache.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ⚠️ Beta Version Notice
|
|
15
|
+
|
|
16
|
+
This project is currently in **Beta**. We are actively refining the API and performance.
|
|
17
|
+
|
|
18
|
+
- Please report any bugs via GitHub Issues.
|
|
19
|
+
- Expect breaking changes until version **1.0.0**.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 📦 Installation
|
|
24
|
+
|
|
25
|
+
Install both the core engine and the DevTools UI:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @pixel-pulse/next-cache-brain-core @pixel-pulse/next-cache-brain-devtools
|
|
29
|
+
# or
|
|
30
|
+
pnpm add @pixel-pulse/next-cache-brain-core @pixel-pulse/next-cache-brain-devtools
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### 1. Setup the Provider and UI
|
|
38
|
+
|
|
39
|
+
In your Next.js `app/layout.tsx`, wrap your application in the `CacheBrainProvider` and add the `CacheBrainDevtools` component.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// app/layout.tsx
|
|
43
|
+
import {
|
|
44
|
+
CacheBrainProvider,
|
|
45
|
+
CacheBrainDevtools,
|
|
46
|
+
} from "@pixel-pulse/next-cache-brain-devtools";
|
|
47
|
+
|
|
48
|
+
export default function RootLayout({
|
|
49
|
+
children,
|
|
50
|
+
}: {
|
|
51
|
+
children: React.ReactNode;
|
|
52
|
+
}) {
|
|
53
|
+
return (
|
|
54
|
+
<html lang="en">
|
|
55
|
+
<body>
|
|
56
|
+
<CacheBrainProvider>
|
|
57
|
+
{children}
|
|
58
|
+
|
|
59
|
+
{/* enabled={true} shows the Brain Icon in development mode */}
|
|
60
|
+
<CacheBrainDevtools
|
|
61
|
+
enabled={process.env.NODE_ENV === "development"}
|
|
62
|
+
/>
|
|
63
|
+
</CacheBrainProvider>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### 2. Implement Caching Strategies
|
|
73
|
+
|
|
74
|
+
Import the `smartFetch` engine from the `core` package. All strategies automatically synchronize with the DevTools UI via the internal event bridge.
|
|
75
|
+
|
|
76
|
+
Option 1: `cache-first`
|
|
77
|
+
|
|
78
|
+
Best for static or rarely changing data (e.g., product details, settings). It checks the cache first and only hits the network if the data is expired or missing.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
const data = await smartFetch(url, {
|
|
82
|
+
strategy: "cache-first",
|
|
83
|
+
ttl: 60 * 1000, // 1 minute
|
|
84
|
+
dedupe: true,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Option 2: `swr` (Stale-While-Revalidate)
|
|
89
|
+
|
|
90
|
+
Best for dynamic feeds (e.g., social posts, dashboards). It returns the cached data immediately while fetching fresh data in the background to update the cache for the next request.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
const data = await smartFetch(url, {
|
|
94
|
+
strategy: "swr",
|
|
95
|
+
ttl: 30 * 1000, // 30 seconds
|
|
96
|
+
dedupe: true,
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Option 3: `network-first`
|
|
101
|
+
|
|
102
|
+
Best for critical real-time data (e.g., stock prices, chat). It always tries the network first but falls back to the cache if the user is offline or the request fails.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const data = await smartFetch(url, {
|
|
106
|
+
strategy: "network-first",
|
|
107
|
+
ttl: 10 * 1000,
|
|
108
|
+
dedupe: false,
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Option 3: `no-cache`
|
|
113
|
+
|
|
114
|
+
Bypasses the caching logic entirely. This is useful for testing or for data that should never be stored, while still allowing the request to be tracked in the Cache Brain DevTools.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
const data = await smartFetch(url, {
|
|
118
|
+
strategy: "no-cache",
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 3. Full Implementation Example
|
|
125
|
+
|
|
126
|
+
Here is how you would use it inside a Next.js Server Component:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { smartFetch } from "@pixel-pulse/next-cache-brain-core";
|
|
130
|
+
|
|
131
|
+
async function ProductList() {
|
|
132
|
+
const url = "https://api.example.com/products";
|
|
133
|
+
|
|
134
|
+
// Using cache-first for optimized performance
|
|
135
|
+
const products = await smartFetch(url, {
|
|
136
|
+
ttl: 60000,
|
|
137
|
+
strategy: "cache-first",
|
|
138
|
+
dedupe: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<ul>
|
|
143
|
+
{products.map((p: any) => (
|
|
144
|
+
<li key={p.id}>{p.name}</li>
|
|
145
|
+
))}
|
|
146
|
+
</ul>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
interface CacheEntry<T> {
|
|
2
|
+
key: string;
|
|
3
|
+
data: T;
|
|
4
|
+
expiry: number;
|
|
5
|
+
ttl: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Listener = (data: any[]) => void;
|
|
9
|
+
declare const cacheEngine: {
|
|
10
|
+
subscribe(fn: Listener): () => boolean;
|
|
11
|
+
getValues(): CacheEntry<any>[];
|
|
12
|
+
notify(data?: any[]): void;
|
|
13
|
+
};
|
|
14
|
+
|
|
1
15
|
type Strategy = "cache-first" | "network-first" | "swr" | "no-cache";
|
|
2
16
|
type SmartFetchOptions = {
|
|
3
17
|
strategy?: Strategy;
|
|
@@ -5,17 +19,27 @@ type SmartFetchOptions = {
|
|
|
5
19
|
dedupe?: boolean;
|
|
6
20
|
};
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
/**
|
|
23
|
+
* smartFetch is the main entry point for the library.
|
|
24
|
+
* It handles deduplication, strategy selection, and DevTools notification.
|
|
25
|
+
*/
|
|
26
|
+
declare function smartFetch<T = any>(url: string, options?: SmartFetchOptions): Promise<T>;
|
|
27
|
+
|
|
28
|
+
declare function setCache<T>(key: string, data: T, ttl: number): CacheEntry<T>;
|
|
9
29
|
|
|
10
30
|
declare function clearCache(url?: string): void;
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
/**
|
|
33
|
+
* GLOBAL TYPE DEFINITION
|
|
34
|
+
* Informs TypeScript about our DevTools bridge on the window object.
|
|
35
|
+
*/
|
|
36
|
+
declare global {
|
|
37
|
+
interface Window {
|
|
38
|
+
__NCB_DEVTOOLS__?: {
|
|
39
|
+
getCache: () => any[];
|
|
40
|
+
subscribe: (cb: () => void) => () => void;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
17
43
|
}
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export { type CacheEntry, type SmartFetchOptions, clearCache, setCache, smartFetch };
|
|
45
|
+
export { type CacheEntry, cacheEngine, clearCache, setCache, smartFetch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
interface CacheEntry<T> {
|
|
2
|
+
key: string;
|
|
3
|
+
data: T;
|
|
4
|
+
expiry: number;
|
|
5
|
+
ttl: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Listener = (data: any[]) => void;
|
|
9
|
+
declare const cacheEngine: {
|
|
10
|
+
subscribe(fn: Listener): () => boolean;
|
|
11
|
+
getValues(): CacheEntry<any>[];
|
|
12
|
+
notify(data?: any[]): void;
|
|
13
|
+
};
|
|
14
|
+
|
|
1
15
|
type Strategy = "cache-first" | "network-first" | "swr" | "no-cache";
|
|
2
16
|
type SmartFetchOptions = {
|
|
3
17
|
strategy?: Strategy;
|
|
@@ -5,17 +19,27 @@ type SmartFetchOptions = {
|
|
|
5
19
|
dedupe?: boolean;
|
|
6
20
|
};
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
/**
|
|
23
|
+
* smartFetch is the main entry point for the library.
|
|
24
|
+
* It handles deduplication, strategy selection, and DevTools notification.
|
|
25
|
+
*/
|
|
26
|
+
declare function smartFetch<T = any>(url: string, options?: SmartFetchOptions): Promise<T>;
|
|
27
|
+
|
|
28
|
+
declare function setCache<T>(key: string, data: T, ttl: number): CacheEntry<T>;
|
|
9
29
|
|
|
10
30
|
declare function clearCache(url?: string): void;
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
/**
|
|
33
|
+
* GLOBAL TYPE DEFINITION
|
|
34
|
+
* Informs TypeScript about our DevTools bridge on the window object.
|
|
35
|
+
*/
|
|
36
|
+
declare global {
|
|
37
|
+
interface Window {
|
|
38
|
+
__NCB_DEVTOOLS__?: {
|
|
39
|
+
getCache: () => any[];
|
|
40
|
+
subscribe: (cb: () => void) => () => void;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
17
43
|
}
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export { type CacheEntry, type SmartFetchOptions, clearCache, setCache, smartFetch };
|
|
45
|
+
export { type CacheEntry, cacheEngine, clearCache, setCache, smartFetch };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var d=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)d(e,n,{get:t[n],enumerable:!0})},I=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of N(t))!F.call(e,r)&&r!==n&&d(e,r,{get:()=>t[r],enumerable:!(o=O(t,r))||o.enumerable});return e};var B=e=>I(d({},"__esModule",{value:!0}),e);var P={};D(P,{cacheEngine:()=>c,clearCache:()=>T,setCache:()=>i,smartFetch:()=>x});module.exports=B(P);var g=new Set,c={subscribe(e){return g.add(e),e(this.getValues()),()=>g.delete(e)},getValues(){return Array.from(s.values())},notify(e){let t=e||this.getValues();g.forEach(n=>n(t))}};var y=Symbol.for("ncb.cache"),l=Symbol.for("ncb.pending"),w=Symbol.for("ncb.inflight");globalThis[y]||(globalThis[y]=new Map);globalThis[l]||(globalThis[l]=new Map);globalThis[w]||(globalThis[w]=new Map);var s=globalThis[y],m=globalThis[l],p=globalThis[w];var A="ncb_cache_snapshot",L="ncb-devtools-update";var h=()=>{if(typeof window>"u")return;let e=c.getValues();try{localStorage.setItem(A,JSON.stringify(e))}catch(t){console.warn("NCB: Persist failed",t)}c.notify(e),window.dispatchEvent(new CustomEvent(L,{detail:e}))};var E="ncb_persistent_storage";function i(e,t,n){let o={key:e,data:t,expiry:Date.now()+n,ttl:n};if(s.set(e,o),typeof window<"u")try{let r=localStorage.getItem(E),a=r?JSON.parse(r):{};a[e]=o,localStorage.setItem(E,JSON.stringify(a))}catch(r){console.warn("NCB: Persistence failed",r)}return h(),o}async function b(e,t){let n=Date.now(),o=s.get(e);if(o&&o.expiry>n)return o.data;if(m.has(e))return m.get(e);let r=fetch(e).then(a=>a.json()).then(a=>(i(e,a,t),m.delete(e),a)).catch(a=>{throw m.delete(e),a});return m.set(e,r),r}async function C(e,t){try{let o=await(await fetch(e)).json();return i(e,o,t),o}catch(n){let o=s.get(e);if(o)return o.data;throw n}}async function S(e){return(await fetch(e)).json()}async function _(e,t){let n=s.get(e);if(n)return fetch(e).then(a=>a.json()).then(a=>i(e,a,t)).catch(a=>console.error("SWR Background Fetch Failed:",a)),n.data;let r=await(await fetch(e)).json();return i(e,r,t),r}async function x(e,t){let n=t?.ttl??1e4,o=t?.strategy??"cache-first",r=t?.dedupe??!0;if(r&&p.has(e))return p.get(e);let u=(async()=>{try{let f;switch(o){case"network-first":f=await C(e,n);break;case"swr":f=await _(e,n);break;case"no-cache":f=await S(e);break;default:f=await b(e,n);break}return h(),f}catch(f){throw console.error(`[NextCacheBrain] Fetch failed for ${e}:`,f),f}finally{r&&p.delete(e)}})();return r&&p.set(e,u),u}var v="ncb_persistent_storage";function T(e){if(e){if(s.delete(e),typeof window<"u"){let t=Array.from(s.values());localStorage.setItem(v,JSON.stringify(t))}}else s.clear(),typeof window<"u"&&localStorage.removeItem(v);h()}if(typeof window<"u"){try{let e=localStorage.getItem("ncb_persistent_storage");if(e){let t=JSON.parse(e);Object.values(t).forEach(n=>{n.expiry>Date.now()&&s.set(n.key,n)}),c.notify()}}catch(e){console.error("NCB Core: Hydration failed",e)}window.__NCB_DEVTOOLS__={getCache:()=>c.getValues(),subscribe:e=>c.subscribe(e)}}0&&(module.exports={cacheEngine,clearCache,setCache,smartFetch});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var d=new Set,c={subscribe(e){return d.add(e),e(this.getValues()),()=>d.delete(e)},getValues(){return Array.from(a.values())},notify(e){let t=e||this.getValues();d.forEach(n=>n(t))}};var g=Symbol.for("ncb.cache"),y=Symbol.for("ncb.pending"),l=Symbol.for("ncb.inflight");globalThis[g]||(globalThis[g]=new Map);globalThis[y]||(globalThis[y]=new Map);globalThis[l]||(globalThis[l]=new Map);var a=globalThis[g],m=globalThis[y],p=globalThis[l];var x="ncb_cache_snapshot",v="ncb-devtools-update";var h=()=>{if(typeof window>"u")return;let e=c.getValues();try{localStorage.setItem(x,JSON.stringify(e))}catch(t){console.warn("NCB: Persist failed",t)}c.notify(e),window.dispatchEvent(new CustomEvent(v,{detail:e}))};var u="ncb_persistent_storage";function f(e,t,n){let r={key:e,data:t,expiry:Date.now()+n,ttl:n};if(a.set(e,r),typeof window<"u")try{let s=localStorage.getItem(u),o=s?JSON.parse(s):{};o[e]=r,localStorage.setItem(u,JSON.stringify(o))}catch(s){console.warn("NCB: Persistence failed",s)}return h(),r}async function E(e,t){let n=Date.now(),r=a.get(e);if(r&&r.expiry>n)return r.data;if(m.has(e))return m.get(e);let s=fetch(e).then(o=>o.json()).then(o=>(f(e,o,t),m.delete(e),o)).catch(o=>{throw m.delete(e),o});return m.set(e,s),s}async function b(e,t){try{let r=await(await fetch(e)).json();return f(e,r,t),r}catch(n){let r=a.get(e);if(r)return r.data;throw n}}async function C(e){return(await fetch(e)).json()}async function S(e,t){let n=a.get(e);if(n)return fetch(e).then(o=>o.json()).then(o=>f(e,o,t)).catch(o=>console.error("SWR Background Fetch Failed:",o)),n.data;let s=await(await fetch(e)).json();return f(e,s,t),s}async function T(e,t){let n=t?.ttl??1e4,r=t?.strategy??"cache-first",s=t?.dedupe??!0;if(s&&p.has(e))return p.get(e);let w=(async()=>{try{let i;switch(r){case"network-first":i=await b(e,n);break;case"swr":i=await S(e,n);break;case"no-cache":i=await C(e);break;default:i=await E(e,n);break}return h(),i}catch(i){throw console.error(`[NextCacheBrain] Fetch failed for ${e}:`,i),i}finally{s&&p.delete(e)}})();return s&&p.set(e,w),w}var _="ncb_persistent_storage";function O(e){if(e){if(a.delete(e),typeof window<"u"){let t=Array.from(a.values());localStorage.setItem(_,JSON.stringify(t))}}else a.clear(),typeof window<"u"&&localStorage.removeItem(_);h()}if(typeof window<"u"){try{let e=localStorage.getItem("ncb_persistent_storage");if(e){let t=JSON.parse(e);Object.values(t).forEach(n=>{n.expiry>Date.now()&&a.set(n.key,n)}),c.notify()}}catch(e){console.error("NCB Core: Hydration failed",e)}window.__NCB_DEVTOOLS__={getCache:()=>c.getValues(),subscribe:e=>c.subscribe(e)}}export{c as cacheEngine,O as clearCache,f as setCache,T as smartFetch};
|