@braine/quantum-query 1.2.0 → 1.2.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 +97 -88
- package/dist/index.cjs +28 -28
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +28 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
# @braine/quantum-query
|
|
2
2
|
|
|
3
3
|
**State Management at the Speed of Light.**
|
|
4
|
-
> A unified architecture that merges Store, Actions, and API Logic into single, high-performance
|
|
4
|
+
> A unified, signal-based architecture that merges Store, Actions, and API Logic into a single, high-performance ecosystem.
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/@braine/quantum-query)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-
## Why "Quantum"?
|
|
10
|
-
Existing libraries behave like "Buses". They stop at every station (component) to check if someone needs to get off (re-render).
|
|
9
|
+
## ⚡️ Why "Quantum"?
|
|
10
|
+
Existing libraries (Redux, RTK Query) behave like "Buses". They stop at every station (component) to check if someone needs to get off (re-render via O(n) selectors).
|
|
11
|
+
|
|
11
12
|
**Quantum-Query** behaves like a teleporter. It updates *only* the specific component listening to a specific property, instantly.
|
|
12
13
|
|
|
13
|
-
* **O(1) Reactivity**:
|
|
14
|
-
* **Zero Boilerplate**: No
|
|
15
|
-
* **Enterprise
|
|
14
|
+
* **O(1) Reactivity**: Powered by Atomic Signals. Zero selectors. No "Top-Down" re-renders.
|
|
15
|
+
* **Zero Boilerplate**: No reducers, no providers, no slices, no thunks.
|
|
16
|
+
* **Enterprise Ecosystem**: Persistence, Plugins, Deduplication, and Validation included.
|
|
16
17
|
|
|
17
18
|
---
|
|
18
19
|
|
|
19
|
-
## Installation
|
|
20
|
+
## 📦 Installation
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
23
|
npm install @braine/quantum-query
|
|
@@ -24,13 +25,32 @@ npm install @braine/quantum-query
|
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
27
|
-
##
|
|
28
|
+
## 🚀 Quick Start (React Hooks)
|
|
29
|
+
|
|
30
|
+
If you just want to fetch data, it works exactly like you expect.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { useQuery } from '@braine/quantum-query';
|
|
34
|
+
|
|
35
|
+
function UserProfile({ id }) {
|
|
36
|
+
const { data, isLoading } = useQuery({
|
|
37
|
+
queryKey: ['user', id],
|
|
38
|
+
queryFn: () => fetch(`/api/user/${id}`).then(r => r.json()),
|
|
39
|
+
staleTime: 5000 // Auto-cache for 5s
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (isLoading) return <div>Loading...</div>;
|
|
43
|
+
return <div>Hello, {data.name}!</div>;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
28
48
|
|
|
29
|
-
|
|
49
|
+
## 🧠 The "Smart Model" Pattern (Advanced)
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
`defineModel` wraps your state, computed properties, and actions into one reactive entity.
|
|
51
|
+
Stop splitting your logic between Redux (Client State) and React Query (Server State). **Smart Models** combine state, computed properties, and actions into one reactive entity.
|
|
33
52
|
|
|
53
|
+
### 1. Define Model
|
|
34
54
|
```typescript
|
|
35
55
|
import { defineModel } from '@braine/quantum-query';
|
|
36
56
|
|
|
@@ -45,48 +65,47 @@ export const TodoModel = defineModel({
|
|
|
45
65
|
computed: {
|
|
46
66
|
activeCount() {
|
|
47
67
|
return this.items.length;
|
|
68
|
+
},
|
|
69
|
+
isEmpty() {
|
|
70
|
+
return this.items.length === 0;
|
|
48
71
|
}
|
|
49
72
|
},
|
|
50
73
|
|
|
51
74
|
// 3. Actions (Sync + Async + Optimistic)
|
|
52
75
|
actions: {
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
await api.post('/todos', { text });
|
|
59
|
-
} catch (err) {
|
|
60
|
-
this.items.pop(); // Auto-Rollback
|
|
61
|
-
}
|
|
76
|
+
add(text: string) {
|
|
77
|
+
this.items.push(text); // Direct mutation (proxied)
|
|
78
|
+
},
|
|
79
|
+
async save() {
|
|
80
|
+
await api.post('/todos', { items: this.items });
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
});
|
|
65
84
|
```
|
|
66
85
|
|
|
67
|
-
### 2. Use
|
|
68
|
-
Just use it.
|
|
69
|
-
|
|
86
|
+
### 2. Use Model
|
|
70
87
|
```tsx
|
|
71
88
|
import { useStore } from '@braine/quantum-query';
|
|
72
89
|
import { TodoModel } from './models/TodoModel';
|
|
73
90
|
|
|
74
91
|
function TodoApp() {
|
|
75
|
-
|
|
92
|
+
// auto-subscribes ONLY to properties accessed in this component
|
|
93
|
+
const model = useStore(TodoModel);
|
|
76
94
|
|
|
77
95
|
return (
|
|
78
|
-
<
|
|
79
|
-
Active: {model.activeCount}
|
|
80
|
-
|
|
96
|
+
<div>
|
|
97
|
+
<h1>Active: {model.activeCount}</h1>
|
|
98
|
+
<button onClick={() => model.add("Ship it")}>Add</button>
|
|
99
|
+
</div>
|
|
81
100
|
);
|
|
82
101
|
}
|
|
83
102
|
```
|
|
84
103
|
|
|
85
104
|
---
|
|
86
105
|
|
|
87
|
-
## Enterprise HTTP Client
|
|
106
|
+
## 🌐 Enterprise HTTP Client
|
|
88
107
|
|
|
89
|
-
We built a fetch wrapper that matches **RTK Query** in power but keeps **Axios** simplicity.
|
|
108
|
+
We built a fetch wrapper that matches **RTK Query** in power but keeps **Axios** simplicity. It includes **Automatic Deduplication** and **Retries**.
|
|
90
109
|
|
|
91
110
|
```typescript
|
|
92
111
|
import { createHttpClient } from '@braine/quantum-query';
|
|
@@ -94,9 +113,9 @@ import { createHttpClient } from '@braine/quantum-query';
|
|
|
94
113
|
export const api = createHttpClient({
|
|
95
114
|
baseURL: 'https://api.myapp.com',
|
|
96
115
|
timeout: 5000,
|
|
97
|
-
retry: { retries: 3 }, // Exponential backoff for
|
|
116
|
+
retry: { retries: 3 }, // Exponential backoff for Network errors
|
|
98
117
|
|
|
99
|
-
// Auth Handling
|
|
118
|
+
// Auth Handling (Auto-Refresh)
|
|
100
119
|
auth: {
|
|
101
120
|
getToken: () => localStorage.getItem('token'),
|
|
102
121
|
onTokenExpired: async () => {
|
|
@@ -107,97 +126,87 @@ export const api = createHttpClient({
|
|
|
107
126
|
}
|
|
108
127
|
});
|
|
109
128
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
const users = await api.get('/users');
|
|
129
|
+
// data is strictly typed!
|
|
130
|
+
const user = await api.get<User>('/me');
|
|
113
131
|
```
|
|
114
132
|
|
|
115
133
|
---
|
|
116
134
|
|
|
117
|
-
## Data Integrity (Runtime Safety)
|
|
135
|
+
## 🛡️ Data Integrity (Runtime Safety)
|
|
118
136
|
|
|
119
|
-
Don't trust the backend. Validate it. We support **Zod**, **Valibot**, or **Yup** directly.
|
|
137
|
+
Don't trust the backend. Validate it. We support **Zod**, **Valibot**, or **Yup** schemas directly in the hook.
|
|
120
138
|
|
|
121
139
|
```typescript
|
|
122
140
|
import { z } from 'zod';
|
|
123
141
|
|
|
124
142
|
const UserSchema = z.object({
|
|
125
143
|
id: z.string(),
|
|
126
|
-
name: z.string()
|
|
144
|
+
name: z.string(),
|
|
145
|
+
role: z.enum(['admin', 'user'])
|
|
127
146
|
});
|
|
128
147
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
const { data } = useQuery({
|
|
149
|
+
queryKey: ['user'],
|
|
150
|
+
queryFn: fetchUser,
|
|
151
|
+
schema: UserSchema // Throws descriptive error if API returns garbage
|
|
133
152
|
});
|
|
134
153
|
```
|
|
135
154
|
|
|
136
155
|
---
|
|
137
156
|
|
|
138
|
-
##
|
|
157
|
+
## 🔌 Plugin System (Middleware) 🆕
|
|
139
158
|
|
|
140
|
-
|
|
159
|
+
Inject logic into every request lifecycle (Logging, Analytics, Performance Monitoring).
|
|
141
160
|
|
|
142
161
|
```typescript
|
|
143
|
-
import {
|
|
144
|
-
|
|
145
|
-
// Background refetch (stale-while-revalidate)
|
|
146
|
-
const { data, isStale } = useQuery({
|
|
147
|
-
queryKey: ['user'],
|
|
148
|
-
queryFn: () => api.get('/me'),
|
|
149
|
-
staleTime: 30000,
|
|
150
|
-
refetchOnWindowFocus: true // Auto-refresh on tab return
|
|
151
|
-
});
|
|
162
|
+
import { queryCache } from '@braine/quantum-query';
|
|
152
163
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
164
|
+
queryCache.use({
|
|
165
|
+
name: 'logger',
|
|
166
|
+
onFetchStart: (key) => console.log('Fetching', key),
|
|
167
|
+
onFetchError: (key, error) => console.error(`Fetch failed for ${key}:`, error)
|
|
157
168
|
});
|
|
169
|
+
```
|
|
158
170
|
|
|
159
|
-
|
|
160
|
-
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
|
161
|
-
queryKey: ['feed'],
|
|
162
|
-
queryFn: ({ pageParam }) => api.get(`/feed?cursor=${pageParam}`),
|
|
163
|
-
getNextPageParam: (last) => last.nextCursor
|
|
164
|
-
});
|
|
171
|
+
---
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
optimisticHelpers.setQueryData(['todos'], ctx.prev);
|
|
178
|
-
}
|
|
173
|
+
## 💾 Persistence Adapter 🆕
|
|
174
|
+
|
|
175
|
+
Persist your cache to `localStorage` (or IndexedDB/AsyncStorage) automatically. Works offline.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { persistQueryClient, createLocalStoragePersister } from '@braine/quantum-query/persist';
|
|
179
|
+
|
|
180
|
+
persistQueryClient({
|
|
181
|
+
queryClient: queryCache,
|
|
182
|
+
persister: createLocalStoragePersister(),
|
|
183
|
+
maxAge: 1000 * 60 * 60 * 24 // 24 hours
|
|
179
184
|
});
|
|
180
185
|
```
|
|
181
186
|
|
|
182
187
|
---
|
|
183
188
|
|
|
184
|
-
##
|
|
189
|
+
## 📚 Documentation
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
| **Boilerplate** | High | Medium | **Minimal** ✅ |
|
|
190
|
-
| **Performance** | Good | Good | **O(1) Reactivity** ✅ |
|
|
191
|
-
| **Pagination** | Yes | Yes | **Yes** ✅ |
|
|
192
|
-
| **Infinite Scroll** | Yes | Yes | **Yes** ✅ |
|
|
193
|
-
| **Optimistic Updates** | Manual | Yes | **Yes** ✅ |
|
|
194
|
-
| **Bundle Size** | ~40kb | ~13kb | **~8kb** ✅ |
|
|
195
|
-
| **Learning Curve** | Steep | Medium | **Gentle** ✅ |
|
|
191
|
+
* **[API Reference](docs/api.md)**: Full method signatures and options.
|
|
192
|
+
* **[Recipes](docs/recipes.md)**: Common patterns (Auth, Infinite Scroll, Optimistic UI).
|
|
193
|
+
* **[Migration Guide](docs/migration.md)**: Step-by-step guide from RTK Query / Redux.
|
|
196
194
|
|
|
197
|
-
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🆚 Comparison
|
|
198
|
+
|
|
199
|
+
| Feature | RTK Query | TanStack Query | **Quantum-Query** |
|
|
200
|
+
| :--- | :--- | :--- | :--- |
|
|
201
|
+
| **Architecture** | Redux (Store + Slices) | Observers | **Atomic Signals** ✅ |
|
|
202
|
+
| **Boilerplate** | High (Provider + Store) | Medium | **Zero** ✅ |
|
|
203
|
+
| **Re-Renders** | Selector-based (O(n)) | Observer-based | **Signal-based (O(1))** ✅ |
|
|
204
|
+
| **Smart Models** | ❌ (Requires Redux) | ❌ | **Built-in** ✅ |
|
|
205
|
+
| **Bundle Size** | ~17kb | ~13kb | **~3kb** ✅ |
|
|
206
|
+
| **Deduplication** | Yes | Yes | **Yes** ✅ |
|
|
207
|
+
| **Persistence** | `redux-persist` | Experimental | **Built-in First Class** ✅ |
|
|
198
208
|
|
|
199
209
|
---
|
|
200
210
|
|
|
201
211
|
## License
|
|
202
212
|
MIT
|
|
203
|
-
# -braine-quantum-query
|
package/dist/index.cjs
CHANGED
|
@@ -686,16 +686,16 @@ var QueryCache = class {
|
|
|
686
686
|
/**
|
|
687
687
|
* Generate cache key from query key array
|
|
688
688
|
*/
|
|
689
|
-
generateKey(queryKey) {
|
|
689
|
+
generateKey = (queryKey) => {
|
|
690
690
|
if (Array.isArray(queryKey)) {
|
|
691
691
|
return stableHash(queryKey);
|
|
692
692
|
}
|
|
693
693
|
return stableHash([queryKey.key, queryKey.params]);
|
|
694
|
-
}
|
|
694
|
+
};
|
|
695
695
|
/**
|
|
696
696
|
* Get data (wrapper around signal.get)
|
|
697
697
|
*/
|
|
698
|
-
get(queryKey) {
|
|
698
|
+
get = (queryKey) => {
|
|
699
699
|
const key = this.generateKey(queryKey);
|
|
700
700
|
const signal = this.signals.get(key);
|
|
701
701
|
if (!signal) return void 0;
|
|
@@ -708,12 +708,12 @@ var QueryCache = class {
|
|
|
708
708
|
return void 0;
|
|
709
709
|
}
|
|
710
710
|
return entry.data;
|
|
711
|
-
}
|
|
711
|
+
};
|
|
712
712
|
/**
|
|
713
713
|
* Get Signal for a key (Low level API for hooks)
|
|
714
714
|
* Automatically creates a signal if one doesn't exist
|
|
715
715
|
*/
|
|
716
|
-
getSignal(queryKey) {
|
|
716
|
+
getSignal = (queryKey) => {
|
|
717
717
|
const key = this.generateKey(queryKey);
|
|
718
718
|
let signal = this.signals.get(key);
|
|
719
719
|
if (!signal) {
|
|
@@ -721,11 +721,11 @@ var QueryCache = class {
|
|
|
721
721
|
this.signals.set(key, signal);
|
|
722
722
|
}
|
|
723
723
|
return signal;
|
|
724
|
-
}
|
|
724
|
+
};
|
|
725
725
|
/**
|
|
726
726
|
* Check if data is stale
|
|
727
727
|
*/
|
|
728
|
-
isStale(queryKey) {
|
|
728
|
+
isStale = (queryKey) => {
|
|
729
729
|
const key = this.generateKey(queryKey);
|
|
730
730
|
const signal = this.signals.get(key);
|
|
731
731
|
if (!signal) return true;
|
|
@@ -734,11 +734,11 @@ var QueryCache = class {
|
|
|
734
734
|
const now = Date.now();
|
|
735
735
|
const age = now - entry.timestamp;
|
|
736
736
|
return age > entry.staleTime;
|
|
737
|
-
}
|
|
737
|
+
};
|
|
738
738
|
/**
|
|
739
739
|
* Set cached data (updates signal)
|
|
740
740
|
*/
|
|
741
|
-
set(queryKey, data, options) {
|
|
741
|
+
set = (queryKey, data, options) => {
|
|
742
742
|
const key = this.generateKey(queryKey);
|
|
743
743
|
const entry = {
|
|
744
744
|
data,
|
|
@@ -755,7 +755,7 @@ var QueryCache = class {
|
|
|
755
755
|
}
|
|
756
756
|
const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
|
|
757
757
|
this.plugins.forEach((p) => p.onQueryUpdated?.(normalizedKey, data));
|
|
758
|
-
}
|
|
758
|
+
};
|
|
759
759
|
// --- DEDUPLICATION ---
|
|
760
760
|
deduplicationCache = /* @__PURE__ */ new Map();
|
|
761
761
|
// --- MIDDLEWARE / PLUGINS ---
|
|
@@ -763,15 +763,15 @@ var QueryCache = class {
|
|
|
763
763
|
/**
|
|
764
764
|
* Register a middleware plugin
|
|
765
765
|
*/
|
|
766
|
-
use(plugin) {
|
|
766
|
+
use = (plugin) => {
|
|
767
767
|
this.plugins.push(plugin);
|
|
768
768
|
return this;
|
|
769
|
-
}
|
|
769
|
+
};
|
|
770
770
|
/**
|
|
771
771
|
* Fetch data with deduplication.
|
|
772
772
|
* If a request for the same key is already in flight, returns the existing promise.
|
|
773
773
|
*/
|
|
774
|
-
async
|
|
774
|
+
fetch = async (queryKey, fn) => {
|
|
775
775
|
const key = this.generateKey(queryKey);
|
|
776
776
|
const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
|
|
777
777
|
if (this.deduplicationCache.has(key)) {
|
|
@@ -792,12 +792,12 @@ var QueryCache = class {
|
|
|
792
792
|
);
|
|
793
793
|
this.deduplicationCache.set(key, promise);
|
|
794
794
|
return promise;
|
|
795
|
-
}
|
|
795
|
+
};
|
|
796
796
|
/**
|
|
797
797
|
* Invalidate queries matching the key prefix
|
|
798
798
|
* Marks them as undefined to trigger refetches without breaking subscriptions
|
|
799
799
|
*/
|
|
800
|
-
invalidate(queryKey) {
|
|
800
|
+
invalidate = (queryKey) => {
|
|
801
801
|
const prefix = this.generateKey(queryKey);
|
|
802
802
|
const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
|
|
803
803
|
this.plugins.forEach((p) => p.onInvalidate?.(normalizedKey));
|
|
@@ -813,23 +813,23 @@ var QueryCache = class {
|
|
|
813
813
|
invalidateKey(key);
|
|
814
814
|
}
|
|
815
815
|
}
|
|
816
|
-
}
|
|
816
|
+
};
|
|
817
817
|
/**
|
|
818
818
|
* Remove all cache entries
|
|
819
819
|
*/
|
|
820
|
-
clear() {
|
|
820
|
+
clear = () => {
|
|
821
821
|
this.signals.clear();
|
|
822
|
-
}
|
|
822
|
+
};
|
|
823
823
|
/**
|
|
824
824
|
* Prefetch data (same as set but explicit intent)
|
|
825
825
|
*/
|
|
826
|
-
prefetch(queryKey, data, options) {
|
|
826
|
+
prefetch = (queryKey, data, options) => {
|
|
827
827
|
this.set(queryKey, data, options);
|
|
828
|
-
}
|
|
828
|
+
};
|
|
829
829
|
/**
|
|
830
830
|
* Garbage collection - remove expired entries
|
|
831
831
|
*/
|
|
832
|
-
startGarbageCollection() {
|
|
832
|
+
startGarbageCollection = () => {
|
|
833
833
|
this.gcInterval = setInterval(() => {
|
|
834
834
|
const now = Date.now();
|
|
835
835
|
for (const [key, signal] of this.signals.entries()) {
|
|
@@ -841,37 +841,37 @@ var QueryCache = class {
|
|
|
841
841
|
}
|
|
842
842
|
}
|
|
843
843
|
}, 60 * 1e3);
|
|
844
|
-
}
|
|
844
|
+
};
|
|
845
845
|
/**
|
|
846
846
|
* Stop garbage collection
|
|
847
847
|
*/
|
|
848
|
-
destroy() {
|
|
848
|
+
destroy = () => {
|
|
849
849
|
if (this.gcInterval) {
|
|
850
850
|
clearInterval(this.gcInterval);
|
|
851
851
|
this.gcInterval = null;
|
|
852
852
|
}
|
|
853
853
|
this.clear();
|
|
854
|
-
}
|
|
854
|
+
};
|
|
855
855
|
/**
|
|
856
856
|
* Get cache stats (for debugging)
|
|
857
857
|
*/
|
|
858
|
-
getStats() {
|
|
858
|
+
getStats = () => {
|
|
859
859
|
return {
|
|
860
860
|
size: this.signals.size,
|
|
861
861
|
keys: Array.from(this.signals.keys())
|
|
862
862
|
};
|
|
863
|
-
}
|
|
863
|
+
};
|
|
864
864
|
/**
|
|
865
865
|
* Get all entries (wrapper for DevTools)
|
|
866
866
|
*/
|
|
867
|
-
getAll() {
|
|
867
|
+
getAll = () => {
|
|
868
868
|
const map = /* @__PURE__ */ new Map();
|
|
869
869
|
for (const [key, signal] of this.signals.entries()) {
|
|
870
870
|
const val = signal.get();
|
|
871
871
|
if (val) map.set(key, val);
|
|
872
872
|
}
|
|
873
873
|
return map;
|
|
874
|
-
}
|
|
874
|
+
};
|
|
875
875
|
};
|
|
876
876
|
var queryCache = new QueryCache();
|
|
877
877
|
|
package/dist/index.d.cts
CHANGED
|
@@ -155,50 +155,50 @@ declare class QueryCache {
|
|
|
155
155
|
/**
|
|
156
156
|
* Get data (wrapper around signal.get)
|
|
157
157
|
*/
|
|
158
|
-
get<T>(queryKey: QueryKeyInput)
|
|
158
|
+
get: <T>(queryKey: QueryKeyInput) => T | undefined;
|
|
159
159
|
/**
|
|
160
160
|
* Get Signal for a key (Low level API for hooks)
|
|
161
161
|
* Automatically creates a signal if one doesn't exist
|
|
162
162
|
*/
|
|
163
|
-
getSignal<T>(queryKey: QueryKeyInput)
|
|
163
|
+
getSignal: <T>(queryKey: QueryKeyInput) => Signal<CacheEntry<T> | undefined>;
|
|
164
164
|
/**
|
|
165
165
|
* Check if data is stale
|
|
166
166
|
*/
|
|
167
|
-
isStale(queryKey: QueryKeyInput)
|
|
167
|
+
isStale: (queryKey: QueryKeyInput) => boolean;
|
|
168
168
|
/**
|
|
169
169
|
* Set cached data (updates signal)
|
|
170
170
|
*/
|
|
171
|
-
set<T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
171
|
+
set: <T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
172
172
|
staleTime?: number;
|
|
173
173
|
cacheTime?: number;
|
|
174
|
-
})
|
|
174
|
+
}) => void;
|
|
175
175
|
private deduplicationCache;
|
|
176
176
|
private plugins;
|
|
177
177
|
/**
|
|
178
178
|
* Register a middleware plugin
|
|
179
179
|
*/
|
|
180
|
-
use(plugin: QueryPlugin)
|
|
180
|
+
use: (plugin: QueryPlugin) => this;
|
|
181
181
|
/**
|
|
182
182
|
* Fetch data with deduplication.
|
|
183
183
|
* If a request for the same key is already in flight, returns the existing promise.
|
|
184
184
|
*/
|
|
185
|
-
fetch<T>(queryKey: QueryKeyInput, fn: () => Promise<T>)
|
|
185
|
+
fetch: <T>(queryKey: QueryKeyInput, fn: () => Promise<T>) => Promise<T>;
|
|
186
186
|
/**
|
|
187
187
|
* Invalidate queries matching the key prefix
|
|
188
188
|
* Marks them as undefined to trigger refetches without breaking subscriptions
|
|
189
189
|
*/
|
|
190
|
-
invalidate(queryKey: QueryKeyInput)
|
|
190
|
+
invalidate: (queryKey: QueryKeyInput) => void;
|
|
191
191
|
/**
|
|
192
192
|
* Remove all cache entries
|
|
193
193
|
*/
|
|
194
|
-
clear()
|
|
194
|
+
clear: () => void;
|
|
195
195
|
/**
|
|
196
196
|
* Prefetch data (same as set but explicit intent)
|
|
197
197
|
*/
|
|
198
|
-
prefetch<T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
198
|
+
prefetch: <T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
199
199
|
staleTime?: number;
|
|
200
200
|
cacheTime?: number;
|
|
201
|
-
})
|
|
201
|
+
}) => void;
|
|
202
202
|
/**
|
|
203
203
|
* Garbage collection - remove expired entries
|
|
204
204
|
*/
|
|
@@ -206,18 +206,18 @@ declare class QueryCache {
|
|
|
206
206
|
/**
|
|
207
207
|
* Stop garbage collection
|
|
208
208
|
*/
|
|
209
|
-
destroy()
|
|
209
|
+
destroy: () => void;
|
|
210
210
|
/**
|
|
211
211
|
* Get cache stats (for debugging)
|
|
212
212
|
*/
|
|
213
|
-
getStats()
|
|
213
|
+
getStats: () => {
|
|
214
214
|
size: number;
|
|
215
215
|
keys: string[];
|
|
216
216
|
};
|
|
217
217
|
/**
|
|
218
218
|
* Get all entries (wrapper for DevTools)
|
|
219
219
|
*/
|
|
220
|
-
getAll()
|
|
220
|
+
getAll: () => Map<string, CacheEntry>;
|
|
221
221
|
}
|
|
222
222
|
declare const queryCache: QueryCache;
|
|
223
223
|
|
package/dist/index.d.ts
CHANGED
|
@@ -155,50 +155,50 @@ declare class QueryCache {
|
|
|
155
155
|
/**
|
|
156
156
|
* Get data (wrapper around signal.get)
|
|
157
157
|
*/
|
|
158
|
-
get<T>(queryKey: QueryKeyInput)
|
|
158
|
+
get: <T>(queryKey: QueryKeyInput) => T | undefined;
|
|
159
159
|
/**
|
|
160
160
|
* Get Signal for a key (Low level API for hooks)
|
|
161
161
|
* Automatically creates a signal if one doesn't exist
|
|
162
162
|
*/
|
|
163
|
-
getSignal<T>(queryKey: QueryKeyInput)
|
|
163
|
+
getSignal: <T>(queryKey: QueryKeyInput) => Signal<CacheEntry<T> | undefined>;
|
|
164
164
|
/**
|
|
165
165
|
* Check if data is stale
|
|
166
166
|
*/
|
|
167
|
-
isStale(queryKey: QueryKeyInput)
|
|
167
|
+
isStale: (queryKey: QueryKeyInput) => boolean;
|
|
168
168
|
/**
|
|
169
169
|
* Set cached data (updates signal)
|
|
170
170
|
*/
|
|
171
|
-
set<T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
171
|
+
set: <T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
172
172
|
staleTime?: number;
|
|
173
173
|
cacheTime?: number;
|
|
174
|
-
})
|
|
174
|
+
}) => void;
|
|
175
175
|
private deduplicationCache;
|
|
176
176
|
private plugins;
|
|
177
177
|
/**
|
|
178
178
|
* Register a middleware plugin
|
|
179
179
|
*/
|
|
180
|
-
use(plugin: QueryPlugin)
|
|
180
|
+
use: (plugin: QueryPlugin) => this;
|
|
181
181
|
/**
|
|
182
182
|
* Fetch data with deduplication.
|
|
183
183
|
* If a request for the same key is already in flight, returns the existing promise.
|
|
184
184
|
*/
|
|
185
|
-
fetch<T>(queryKey: QueryKeyInput, fn: () => Promise<T>)
|
|
185
|
+
fetch: <T>(queryKey: QueryKeyInput, fn: () => Promise<T>) => Promise<T>;
|
|
186
186
|
/**
|
|
187
187
|
* Invalidate queries matching the key prefix
|
|
188
188
|
* Marks them as undefined to trigger refetches without breaking subscriptions
|
|
189
189
|
*/
|
|
190
|
-
invalidate(queryKey: QueryKeyInput)
|
|
190
|
+
invalidate: (queryKey: QueryKeyInput) => void;
|
|
191
191
|
/**
|
|
192
192
|
* Remove all cache entries
|
|
193
193
|
*/
|
|
194
|
-
clear()
|
|
194
|
+
clear: () => void;
|
|
195
195
|
/**
|
|
196
196
|
* Prefetch data (same as set but explicit intent)
|
|
197
197
|
*/
|
|
198
|
-
prefetch<T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
198
|
+
prefetch: <T>(queryKey: QueryKeyInput, data: T, options?: {
|
|
199
199
|
staleTime?: number;
|
|
200
200
|
cacheTime?: number;
|
|
201
|
-
})
|
|
201
|
+
}) => void;
|
|
202
202
|
/**
|
|
203
203
|
* Garbage collection - remove expired entries
|
|
204
204
|
*/
|
|
@@ -206,18 +206,18 @@ declare class QueryCache {
|
|
|
206
206
|
/**
|
|
207
207
|
* Stop garbage collection
|
|
208
208
|
*/
|
|
209
|
-
destroy()
|
|
209
|
+
destroy: () => void;
|
|
210
210
|
/**
|
|
211
211
|
* Get cache stats (for debugging)
|
|
212
212
|
*/
|
|
213
|
-
getStats()
|
|
213
|
+
getStats: () => {
|
|
214
214
|
size: number;
|
|
215
215
|
keys: string[];
|
|
216
216
|
};
|
|
217
217
|
/**
|
|
218
218
|
* Get all entries (wrapper for DevTools)
|
|
219
219
|
*/
|
|
220
|
-
getAll()
|
|
220
|
+
getAll: () => Map<string, CacheEntry>;
|
|
221
221
|
}
|
|
222
222
|
declare const queryCache: QueryCache;
|
|
223
223
|
|
package/dist/index.js
CHANGED
|
@@ -638,16 +638,16 @@ var QueryCache = class {
|
|
|
638
638
|
/**
|
|
639
639
|
* Generate cache key from query key array
|
|
640
640
|
*/
|
|
641
|
-
generateKey(queryKey) {
|
|
641
|
+
generateKey = (queryKey) => {
|
|
642
642
|
if (Array.isArray(queryKey)) {
|
|
643
643
|
return stableHash(queryKey);
|
|
644
644
|
}
|
|
645
645
|
return stableHash([queryKey.key, queryKey.params]);
|
|
646
|
-
}
|
|
646
|
+
};
|
|
647
647
|
/**
|
|
648
648
|
* Get data (wrapper around signal.get)
|
|
649
649
|
*/
|
|
650
|
-
get(queryKey) {
|
|
650
|
+
get = (queryKey) => {
|
|
651
651
|
const key = this.generateKey(queryKey);
|
|
652
652
|
const signal = this.signals.get(key);
|
|
653
653
|
if (!signal) return void 0;
|
|
@@ -660,12 +660,12 @@ var QueryCache = class {
|
|
|
660
660
|
return void 0;
|
|
661
661
|
}
|
|
662
662
|
return entry.data;
|
|
663
|
-
}
|
|
663
|
+
};
|
|
664
664
|
/**
|
|
665
665
|
* Get Signal for a key (Low level API for hooks)
|
|
666
666
|
* Automatically creates a signal if one doesn't exist
|
|
667
667
|
*/
|
|
668
|
-
getSignal(queryKey) {
|
|
668
|
+
getSignal = (queryKey) => {
|
|
669
669
|
const key = this.generateKey(queryKey);
|
|
670
670
|
let signal = this.signals.get(key);
|
|
671
671
|
if (!signal) {
|
|
@@ -673,11 +673,11 @@ var QueryCache = class {
|
|
|
673
673
|
this.signals.set(key, signal);
|
|
674
674
|
}
|
|
675
675
|
return signal;
|
|
676
|
-
}
|
|
676
|
+
};
|
|
677
677
|
/**
|
|
678
678
|
* Check if data is stale
|
|
679
679
|
*/
|
|
680
|
-
isStale(queryKey) {
|
|
680
|
+
isStale = (queryKey) => {
|
|
681
681
|
const key = this.generateKey(queryKey);
|
|
682
682
|
const signal = this.signals.get(key);
|
|
683
683
|
if (!signal) return true;
|
|
@@ -686,11 +686,11 @@ var QueryCache = class {
|
|
|
686
686
|
const now = Date.now();
|
|
687
687
|
const age = now - entry.timestamp;
|
|
688
688
|
return age > entry.staleTime;
|
|
689
|
-
}
|
|
689
|
+
};
|
|
690
690
|
/**
|
|
691
691
|
* Set cached data (updates signal)
|
|
692
692
|
*/
|
|
693
|
-
set(queryKey, data, options) {
|
|
693
|
+
set = (queryKey, data, options) => {
|
|
694
694
|
const key = this.generateKey(queryKey);
|
|
695
695
|
const entry = {
|
|
696
696
|
data,
|
|
@@ -707,7 +707,7 @@ var QueryCache = class {
|
|
|
707
707
|
}
|
|
708
708
|
const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
|
|
709
709
|
this.plugins.forEach((p) => p.onQueryUpdated?.(normalizedKey, data));
|
|
710
|
-
}
|
|
710
|
+
};
|
|
711
711
|
// --- DEDUPLICATION ---
|
|
712
712
|
deduplicationCache = /* @__PURE__ */ new Map();
|
|
713
713
|
// --- MIDDLEWARE / PLUGINS ---
|
|
@@ -715,15 +715,15 @@ var QueryCache = class {
|
|
|
715
715
|
/**
|
|
716
716
|
* Register a middleware plugin
|
|
717
717
|
*/
|
|
718
|
-
use(plugin) {
|
|
718
|
+
use = (plugin) => {
|
|
719
719
|
this.plugins.push(plugin);
|
|
720
720
|
return this;
|
|
721
|
-
}
|
|
721
|
+
};
|
|
722
722
|
/**
|
|
723
723
|
* Fetch data with deduplication.
|
|
724
724
|
* If a request for the same key is already in flight, returns the existing promise.
|
|
725
725
|
*/
|
|
726
|
-
async
|
|
726
|
+
fetch = async (queryKey, fn) => {
|
|
727
727
|
const key = this.generateKey(queryKey);
|
|
728
728
|
const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
|
|
729
729
|
if (this.deduplicationCache.has(key)) {
|
|
@@ -744,12 +744,12 @@ var QueryCache = class {
|
|
|
744
744
|
);
|
|
745
745
|
this.deduplicationCache.set(key, promise);
|
|
746
746
|
return promise;
|
|
747
|
-
}
|
|
747
|
+
};
|
|
748
748
|
/**
|
|
749
749
|
* Invalidate queries matching the key prefix
|
|
750
750
|
* Marks them as undefined to trigger refetches without breaking subscriptions
|
|
751
751
|
*/
|
|
752
|
-
invalidate(queryKey) {
|
|
752
|
+
invalidate = (queryKey) => {
|
|
753
753
|
const prefix = this.generateKey(queryKey);
|
|
754
754
|
const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
|
|
755
755
|
this.plugins.forEach((p) => p.onInvalidate?.(normalizedKey));
|
|
@@ -765,23 +765,23 @@ var QueryCache = class {
|
|
|
765
765
|
invalidateKey(key);
|
|
766
766
|
}
|
|
767
767
|
}
|
|
768
|
-
}
|
|
768
|
+
};
|
|
769
769
|
/**
|
|
770
770
|
* Remove all cache entries
|
|
771
771
|
*/
|
|
772
|
-
clear() {
|
|
772
|
+
clear = () => {
|
|
773
773
|
this.signals.clear();
|
|
774
|
-
}
|
|
774
|
+
};
|
|
775
775
|
/**
|
|
776
776
|
* Prefetch data (same as set but explicit intent)
|
|
777
777
|
*/
|
|
778
|
-
prefetch(queryKey, data, options) {
|
|
778
|
+
prefetch = (queryKey, data, options) => {
|
|
779
779
|
this.set(queryKey, data, options);
|
|
780
|
-
}
|
|
780
|
+
};
|
|
781
781
|
/**
|
|
782
782
|
* Garbage collection - remove expired entries
|
|
783
783
|
*/
|
|
784
|
-
startGarbageCollection() {
|
|
784
|
+
startGarbageCollection = () => {
|
|
785
785
|
this.gcInterval = setInterval(() => {
|
|
786
786
|
const now = Date.now();
|
|
787
787
|
for (const [key, signal] of this.signals.entries()) {
|
|
@@ -793,37 +793,37 @@ var QueryCache = class {
|
|
|
793
793
|
}
|
|
794
794
|
}
|
|
795
795
|
}, 60 * 1e3);
|
|
796
|
-
}
|
|
796
|
+
};
|
|
797
797
|
/**
|
|
798
798
|
* Stop garbage collection
|
|
799
799
|
*/
|
|
800
|
-
destroy() {
|
|
800
|
+
destroy = () => {
|
|
801
801
|
if (this.gcInterval) {
|
|
802
802
|
clearInterval(this.gcInterval);
|
|
803
803
|
this.gcInterval = null;
|
|
804
804
|
}
|
|
805
805
|
this.clear();
|
|
806
|
-
}
|
|
806
|
+
};
|
|
807
807
|
/**
|
|
808
808
|
* Get cache stats (for debugging)
|
|
809
809
|
*/
|
|
810
|
-
getStats() {
|
|
810
|
+
getStats = () => {
|
|
811
811
|
return {
|
|
812
812
|
size: this.signals.size,
|
|
813
813
|
keys: Array.from(this.signals.keys())
|
|
814
814
|
};
|
|
815
|
-
}
|
|
815
|
+
};
|
|
816
816
|
/**
|
|
817
817
|
* Get all entries (wrapper for DevTools)
|
|
818
818
|
*/
|
|
819
|
-
getAll() {
|
|
819
|
+
getAll = () => {
|
|
820
820
|
const map = /* @__PURE__ */ new Map();
|
|
821
821
|
for (const [key, signal] of this.signals.entries()) {
|
|
822
822
|
const val = signal.get();
|
|
823
823
|
if (val) map.set(key, val);
|
|
824
824
|
}
|
|
825
825
|
return map;
|
|
826
|
-
}
|
|
826
|
+
};
|
|
827
827
|
};
|
|
828
828
|
var queryCache = new QueryCache();
|
|
829
829
|
|