@solidjs/router 0.16.0 → 0.17.0-next.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 +85 -79
- package/dist/components.jsx +22 -16
- package/dist/data/action.d.ts +12 -10
- package/dist/data/action.js +98 -78
- package/dist/data/events.d.ts +8 -1
- package/dist/data/events.js +3 -3
- package/dist/data/index.d.ts +2 -3
- package/dist/data/index.js +2 -3
- package/dist/data/query.d.ts +1 -3
- package/dist/data/query.js +10 -16
- package/dist/index.d.ts +2 -2
- package/dist/index.js +212 -261
- package/dist/index.jsx +1 -1
- package/dist/lifecycle.js +1 -1
- package/dist/routers/HashRouter.js +1 -1
- package/dist/routers/MemoryRouter.js +1 -1
- package/dist/routers/Router.js +2 -2
- package/dist/routers/StaticRouter.js +1 -1
- package/dist/routers/components.d.ts +0 -4
- package/dist/routers/components.jsx +30 -19
- package/dist/routing.d.ts +4 -3
- package/dist/routing.js +71 -49
- package/dist/types.d.ts +3 -18
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +8 -0
- package/package.json +8 -6
- package/dist/data/createAsync.d.ts +0 -32
- package/dist/data/createAsync.js +0 -93
- package/dist/src/components.d.ts +0 -31
- package/dist/src/components.jsx +0 -39
- package/dist/src/data/action.d.ts +0 -17
- package/dist/src/data/action.js +0 -163
- package/dist/src/data/action.spec.d.ts +0 -1
- package/dist/src/data/action.spec.js +0 -297
- package/dist/src/data/createAsync.d.ts +0 -32
- package/dist/src/data/createAsync.js +0 -96
- package/dist/src/data/createAsync.spec.d.ts +0 -1
- package/dist/src/data/createAsync.spec.js +0 -196
- package/dist/src/data/events.d.ts +0 -9
- package/dist/src/data/events.js +0 -123
- package/dist/src/data/events.spec.d.ts +0 -1
- package/dist/src/data/events.spec.js +0 -567
- package/dist/src/data/index.d.ts +0 -4
- package/dist/src/data/index.js +0 -4
- package/dist/src/data/query.d.ts +0 -23
- package/dist/src/data/query.js +0 -232
- package/dist/src/data/query.spec.d.ts +0 -1
- package/dist/src/data/query.spec.js +0 -354
- package/dist/src/data/response.d.ts +0 -4
- package/dist/src/data/response.js +0 -42
- package/dist/src/data/response.spec.d.ts +0 -1
- package/dist/src/data/response.spec.js +0 -165
- package/dist/src/index.d.ts +0 -7
- package/dist/src/index.jsx +0 -6
- package/dist/src/lifecycle.d.ts +0 -5
- package/dist/src/lifecycle.js +0 -69
- package/dist/src/routers/HashRouter.d.ts +0 -9
- package/dist/src/routers/HashRouter.js +0 -41
- package/dist/src/routers/MemoryRouter.d.ts +0 -24
- package/dist/src/routers/MemoryRouter.js +0 -57
- package/dist/src/routers/Router.d.ts +0 -9
- package/dist/src/routers/Router.js +0 -45
- package/dist/src/routers/StaticRouter.d.ts +0 -6
- package/dist/src/routers/StaticRouter.js +0 -15
- package/dist/src/routers/components.d.ts +0 -27
- package/dist/src/routers/components.jsx +0 -118
- package/dist/src/routers/createRouter.d.ts +0 -10
- package/dist/src/routers/createRouter.js +0 -41
- package/dist/src/routers/index.d.ts +0 -11
- package/dist/src/routers/index.js +0 -6
- package/dist/src/routing.d.ts +0 -175
- package/dist/src/routing.js +0 -560
- package/dist/src/types.d.ts +0 -200
- package/dist/src/types.js +0 -1
- package/dist/src/utils.d.ts +0 -13
- package/dist/src/utils.js +0 -185
- package/dist/test/helpers.d.ts +0 -6
- package/dist/test/helpers.js +0 -50
package/dist/src/data/query.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
|
|
2
|
-
import { getRequestEvent, isServer } from "solid-js/web";
|
|
3
|
-
import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
|
|
4
|
-
const LocationHeader = "Location";
|
|
5
|
-
const PRELOAD_TIMEOUT = 5000;
|
|
6
|
-
const CACHE_TIMEOUT = 180000;
|
|
7
|
-
let cacheMap = new Map();
|
|
8
|
-
// cleanup forward/back cache
|
|
9
|
-
if (!isServer) {
|
|
10
|
-
setInterval(() => {
|
|
11
|
-
const now = Date.now();
|
|
12
|
-
for (let [k, v] of cacheMap.entries()) {
|
|
13
|
-
if (!v[4].count && now - v[0] > CACHE_TIMEOUT) {
|
|
14
|
-
cacheMap.delete(k);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}, 300000);
|
|
18
|
-
}
|
|
19
|
-
function getCache() {
|
|
20
|
-
if (!isServer)
|
|
21
|
-
return cacheMap;
|
|
22
|
-
const req = getRequestEvent();
|
|
23
|
-
if (!req)
|
|
24
|
-
throw new Error("Cannot find cache context");
|
|
25
|
-
return (req.router || (req.router = {})).cache || (req.router.cache = new Map());
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Revalidates the given cache entry/entries.
|
|
29
|
-
*/
|
|
30
|
-
export function revalidate(key, force = true) {
|
|
31
|
-
return startTransition(() => {
|
|
32
|
-
const now = Date.now();
|
|
33
|
-
cacheKeyOp(key, entry => {
|
|
34
|
-
force && (entry[0] = 0); //force cache miss
|
|
35
|
-
entry[4][1](now); // retrigger live signals
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
export function cacheKeyOp(key, fn) {
|
|
40
|
-
key && !Array.isArray(key) && (key = [key]);
|
|
41
|
-
for (let k of cacheMap.keys()) {
|
|
42
|
-
if (key === undefined || matchKey(k, key))
|
|
43
|
-
fn(cacheMap.get(k));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
export function query(fn, name) {
|
|
47
|
-
// prioritize GET for server functions
|
|
48
|
-
if (fn.GET)
|
|
49
|
-
fn = fn.GET;
|
|
50
|
-
const cachedFn = ((...args) => {
|
|
51
|
-
const cache = getCache();
|
|
52
|
-
const intent = getIntent();
|
|
53
|
-
const inPreloadFn = getInPreloadFn();
|
|
54
|
-
const owner = getOwner();
|
|
55
|
-
const navigate = owner ? useNavigate() : undefined;
|
|
56
|
-
const now = Date.now();
|
|
57
|
-
const key = name + hashKey(args);
|
|
58
|
-
let cached = cache.get(key);
|
|
59
|
-
let tracking;
|
|
60
|
-
if (isServer) {
|
|
61
|
-
const e = getRequestEvent();
|
|
62
|
-
if (e) {
|
|
63
|
-
const dataOnly = (e.router || (e.router = {})).dataOnly;
|
|
64
|
-
if (dataOnly) {
|
|
65
|
-
const data = e && (e.router.data || (e.router.data = {}));
|
|
66
|
-
if (data && key in data)
|
|
67
|
-
return data[key];
|
|
68
|
-
if (Array.isArray(dataOnly) && !matchKey(key, dataOnly)) {
|
|
69
|
-
data[key] = undefined;
|
|
70
|
-
return Promise.resolve();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (getListener() && !isServer) {
|
|
76
|
-
tracking = true;
|
|
77
|
-
onCleanup(() => cached[4].count--);
|
|
78
|
-
}
|
|
79
|
-
if (cached &&
|
|
80
|
-
cached[0] &&
|
|
81
|
-
(isServer ||
|
|
82
|
-
intent === "native" ||
|
|
83
|
-
cached[4].count ||
|
|
84
|
-
Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
|
|
85
|
-
if (tracking) {
|
|
86
|
-
cached[4].count++;
|
|
87
|
-
cached[4][0](); // track
|
|
88
|
-
}
|
|
89
|
-
if (cached[3] === "preload" && intent !== "preload") {
|
|
90
|
-
cached[0] = now;
|
|
91
|
-
}
|
|
92
|
-
let res = cached[1];
|
|
93
|
-
if (intent !== "preload") {
|
|
94
|
-
res =
|
|
95
|
-
"then" in cached[1]
|
|
96
|
-
? cached[1].then(handleResponse(false), handleResponse(true))
|
|
97
|
-
: handleResponse(false)(cached[1]);
|
|
98
|
-
!isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
|
|
99
|
-
}
|
|
100
|
-
inPreloadFn && "then" in res && res.catch(() => { });
|
|
101
|
-
return res;
|
|
102
|
-
}
|
|
103
|
-
let res;
|
|
104
|
-
if (!isServer && sharedConfig.has && sharedConfig.has(key)) {
|
|
105
|
-
res = sharedConfig.load(key); // hydrating
|
|
106
|
-
// @ts-ignore at least until we add a delete method to sharedConfig
|
|
107
|
-
delete globalThis._$HY.r[key];
|
|
108
|
-
}
|
|
109
|
-
else
|
|
110
|
-
res = fn(...args);
|
|
111
|
-
if (cached) {
|
|
112
|
-
cached[0] = now;
|
|
113
|
-
cached[1] = res;
|
|
114
|
-
cached[3] = intent;
|
|
115
|
-
!isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
cache.set(key, (cached = [now, res, , intent, createSignal(now)]));
|
|
119
|
-
cached[4].count = 0;
|
|
120
|
-
}
|
|
121
|
-
if (tracking) {
|
|
122
|
-
cached[4].count++;
|
|
123
|
-
cached[4][0](); // track
|
|
124
|
-
}
|
|
125
|
-
if (isServer) {
|
|
126
|
-
const e = getRequestEvent();
|
|
127
|
-
if (e && e.router.dataOnly)
|
|
128
|
-
return (e.router.data[key] = res);
|
|
129
|
-
}
|
|
130
|
-
if (intent !== "preload") {
|
|
131
|
-
res =
|
|
132
|
-
"then" in res
|
|
133
|
-
? res.then(handleResponse(false), handleResponse(true))
|
|
134
|
-
: handleResponse(false)(res);
|
|
135
|
-
}
|
|
136
|
-
inPreloadFn && "then" in res && res.catch(() => { });
|
|
137
|
-
// serialize on server
|
|
138
|
-
if (isServer &&
|
|
139
|
-
sharedConfig.context &&
|
|
140
|
-
sharedConfig.context.async &&
|
|
141
|
-
!sharedConfig.context.noHydrate) {
|
|
142
|
-
const e = getRequestEvent();
|
|
143
|
-
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
|
|
144
|
-
}
|
|
145
|
-
return res;
|
|
146
|
-
function handleResponse(error) {
|
|
147
|
-
return async (v) => {
|
|
148
|
-
if (v instanceof Response) {
|
|
149
|
-
const e = getRequestEvent();
|
|
150
|
-
if (e) {
|
|
151
|
-
for (const [key, value] of v.headers) {
|
|
152
|
-
if (key == "set-cookie")
|
|
153
|
-
e.response.headers.append("set-cookie", value);
|
|
154
|
-
else
|
|
155
|
-
e.response.headers.set(key, value);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const url = v.headers.get(LocationHeader);
|
|
159
|
-
if (url !== null) {
|
|
160
|
-
// client + server relative redirect
|
|
161
|
-
if (navigate && url.startsWith("/"))
|
|
162
|
-
startTransition(() => {
|
|
163
|
-
navigate(url, { replace: true });
|
|
164
|
-
});
|
|
165
|
-
else if (!isServer)
|
|
166
|
-
window.location.href = url;
|
|
167
|
-
else if (e)
|
|
168
|
-
e.response.status = 302;
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (v.customBody)
|
|
172
|
-
v = await v.customBody();
|
|
173
|
-
}
|
|
174
|
-
if (error)
|
|
175
|
-
throw v;
|
|
176
|
-
cached[2] = v;
|
|
177
|
-
return v;
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
cachedFn.keyFor = (...args) => name + hashKey(args);
|
|
182
|
-
cachedFn.key = name;
|
|
183
|
-
return cachedFn;
|
|
184
|
-
}
|
|
185
|
-
query.get = (key) => {
|
|
186
|
-
const cached = getCache().get(key);
|
|
187
|
-
return cached[2];
|
|
188
|
-
};
|
|
189
|
-
query.set = (key, value) => {
|
|
190
|
-
const cache = getCache();
|
|
191
|
-
const now = Date.now();
|
|
192
|
-
let cached = cache.get(key);
|
|
193
|
-
if (cached) {
|
|
194
|
-
cached[0] = now;
|
|
195
|
-
cached[1] = Promise.resolve(value);
|
|
196
|
-
cached[2] = value;
|
|
197
|
-
cached[3] = "preload";
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
cache.set(key, (cached = [now, Promise.resolve(value), value, "preload", createSignal(now)]));
|
|
201
|
-
cached[4].count = 0;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
query.delete = (key) => getCache().delete(key);
|
|
205
|
-
query.clear = () => getCache().clear();
|
|
206
|
-
/** @deprecated use query instead */
|
|
207
|
-
export const cache = query;
|
|
208
|
-
function matchKey(key, keys) {
|
|
209
|
-
for (let k of keys) {
|
|
210
|
-
if (k && key.startsWith(k))
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
// Modified from the amazing Tanstack Query library (MIT)
|
|
216
|
-
// https://github.com/TanStack/query/blob/main/packages/query-core/src/utils.ts#L168
|
|
217
|
-
export function hashKey(args) {
|
|
218
|
-
return JSON.stringify(args, (_, val) => isPlainObject(val)
|
|
219
|
-
? Object.keys(val)
|
|
220
|
-
.sort()
|
|
221
|
-
.reduce((result, key) => {
|
|
222
|
-
result[key] = val[key];
|
|
223
|
-
return result;
|
|
224
|
-
}, {})
|
|
225
|
-
: val);
|
|
226
|
-
}
|
|
227
|
-
function isPlainObject(obj) {
|
|
228
|
-
let proto;
|
|
229
|
-
return (obj != null &&
|
|
230
|
-
typeof obj === "object" &&
|
|
231
|
-
(!(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype));
|
|
232
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
import { createRoot } from "solid-js";
|
|
2
|
-
import { vi } from "vitest";
|
|
3
|
-
import { query, revalidate, cacheKeyOp, hashKey } from "./query.js";
|
|
4
|
-
import { createMockRouter } from "../../test/helpers.js";
|
|
5
|
-
const mockRouter = createMockRouter();
|
|
6
|
-
vi.mock("../routing.js", () => ({
|
|
7
|
-
useRouter: () => mockRouter,
|
|
8
|
-
useNavigate: () => vi.fn(),
|
|
9
|
-
getIntent: () => "navigate",
|
|
10
|
-
getInPreloadFn: () => false,
|
|
11
|
-
createRouterContext: () => mockRouter,
|
|
12
|
-
RouterContextObj: {},
|
|
13
|
-
RouteContextObj: {},
|
|
14
|
-
useRoute: () => mockRouter.base,
|
|
15
|
-
useResolvedPath: () => "/",
|
|
16
|
-
useHref: () => "/",
|
|
17
|
-
useLocation: () => mockRouter.location,
|
|
18
|
-
useRouteData: () => undefined,
|
|
19
|
-
useMatch: () => null,
|
|
20
|
-
useParams: () => ({}),
|
|
21
|
-
useSearchParams: () => [{}, vi.fn()],
|
|
22
|
-
useIsRouting: () => false,
|
|
23
|
-
usePreloadRoute: () => vi.fn(),
|
|
24
|
-
useBeforeLeave: () => vi.fn()
|
|
25
|
-
}));
|
|
26
|
-
describe("query", () => {
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
query.clear();
|
|
29
|
-
vi.clearAllTimers();
|
|
30
|
-
vi.useFakeTimers();
|
|
31
|
-
});
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
vi.runOnlyPendingTimers();
|
|
34
|
-
vi.useRealTimers();
|
|
35
|
-
});
|
|
36
|
-
test("should create cached function with correct properties", () => {
|
|
37
|
-
return createRoot(() => {
|
|
38
|
-
const testFn = async (id) => `data-${id}`;
|
|
39
|
-
const cachedFn = query(testFn, "testQuery");
|
|
40
|
-
expect(typeof cachedFn).toBe("function");
|
|
41
|
-
expect(cachedFn.key).toBe("testQuery");
|
|
42
|
-
expect(typeof cachedFn.keyFor).toBe("function");
|
|
43
|
-
expect(cachedFn.keyFor(123)).toBe("testQuery[123]");
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
test("should cache function results", async () => {
|
|
47
|
-
return createRoot(async () => {
|
|
48
|
-
let callCount = 0;
|
|
49
|
-
const testFn = async (id) => {
|
|
50
|
-
callCount++;
|
|
51
|
-
return `data-${id}`;
|
|
52
|
-
};
|
|
53
|
-
const cachedFn = query(testFn, "testQuery");
|
|
54
|
-
const result1 = await cachedFn(123);
|
|
55
|
-
const result2 = await cachedFn(123);
|
|
56
|
-
expect(result1).toBe("data-123");
|
|
57
|
-
expect(result2).toBe("data-123");
|
|
58
|
-
expect(callCount).toBe(1);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
test("should cache different arguments separately", async () => {
|
|
62
|
-
return createRoot(async () => {
|
|
63
|
-
let callCount = 0;
|
|
64
|
-
const testFn = async (id) => {
|
|
65
|
-
callCount++;
|
|
66
|
-
return `data-${id}`;
|
|
67
|
-
};
|
|
68
|
-
const cachedFn = query(testFn, "testQuery");
|
|
69
|
-
const result1 = await cachedFn(123);
|
|
70
|
-
const result2 = await cachedFn(456);
|
|
71
|
-
expect(result1).toBe("data-123");
|
|
72
|
-
expect(result2).toBe("data-456");
|
|
73
|
-
expect(callCount).toBe(2);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
test("should handle synchronous functions", async () => {
|
|
77
|
-
return createRoot(async () => {
|
|
78
|
-
const testFn = (id) => Promise.resolve(`data-${id}`);
|
|
79
|
-
const cachedFn = query(testFn, "testQuery");
|
|
80
|
-
const result1 = await cachedFn(123);
|
|
81
|
-
const result2 = await cachedFn(123);
|
|
82
|
-
expect(result1).toBe("data-123");
|
|
83
|
-
expect(result2).toBe("data-123");
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
test("should prioritize GET method for server functions", async () => {
|
|
87
|
-
return createRoot(async () => {
|
|
88
|
-
const postFn = () => Promise.resolve("POST result");
|
|
89
|
-
const getFn = () => Promise.resolve("GET result");
|
|
90
|
-
postFn.GET = getFn;
|
|
91
|
-
const cachedFn = query(postFn, "serverQuery");
|
|
92
|
-
const result = await cachedFn();
|
|
93
|
-
expect(result).toBe("GET result");
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
describe("query.get", () => {
|
|
98
|
-
beforeEach(() => {
|
|
99
|
-
query.clear();
|
|
100
|
-
});
|
|
101
|
-
test("should retrieve cached value", async () => {
|
|
102
|
-
return createRoot(async () => {
|
|
103
|
-
const testFn = async (id) => `data-${id}`;
|
|
104
|
-
const cachedFn = query(testFn, "testQuery");
|
|
105
|
-
await cachedFn(123);
|
|
106
|
-
const cached = query.get("testQuery[123]");
|
|
107
|
-
expect(cached).toBe("data-123");
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
test("handle non-existent key gracefully", () => {
|
|
111
|
-
expect(() => query.get("nonexistent")).toThrow();
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
describe("query.set", () => {
|
|
115
|
-
beforeEach(() => {
|
|
116
|
-
query.clear();
|
|
117
|
-
});
|
|
118
|
-
test("should set cached value", () => {
|
|
119
|
-
query.set("testKey", "test value");
|
|
120
|
-
const cached = query.get("testKey");
|
|
121
|
-
expect(cached).toBe("test value");
|
|
122
|
-
});
|
|
123
|
-
test("should update existing cached value", async () => {
|
|
124
|
-
return createRoot(async () => {
|
|
125
|
-
const testFn = async () => "original";
|
|
126
|
-
const cachedFn = query(testFn, "testQuery");
|
|
127
|
-
await cachedFn();
|
|
128
|
-
query.set("testQuery[]", "updated");
|
|
129
|
-
const cached = query.get("testQuery[]");
|
|
130
|
-
expect(cached).toBe("updated");
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
describe("query.delete", () => {
|
|
135
|
-
beforeEach(() => {
|
|
136
|
-
query.clear();
|
|
137
|
-
});
|
|
138
|
-
test("should delete cached entry", async () => {
|
|
139
|
-
return createRoot(async () => {
|
|
140
|
-
const testFn = async () => "data";
|
|
141
|
-
const cachedFn = query(testFn, "testQuery");
|
|
142
|
-
await cachedFn();
|
|
143
|
-
expect(query.get("testQuery[]")).toBe("data");
|
|
144
|
-
query.delete("testQuery[]");
|
|
145
|
-
expect(() => query.get("testQuery[]")).toThrow();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
describe("query.clear", () => {
|
|
150
|
-
beforeEach(() => {
|
|
151
|
-
query.clear();
|
|
152
|
-
});
|
|
153
|
-
test("should clear all cached entries", async () => {
|
|
154
|
-
return createRoot(async () => {
|
|
155
|
-
const testFn1 = async () => "data1";
|
|
156
|
-
const testFn2 = async () => "data2";
|
|
157
|
-
const cachedFn1 = query(testFn1, "query1");
|
|
158
|
-
const cachedFn2 = query(testFn2, "query2");
|
|
159
|
-
await cachedFn1();
|
|
160
|
-
await cachedFn2();
|
|
161
|
-
expect(query.get("query1[]")).toBe("data1");
|
|
162
|
-
expect(query.get("query2[]")).toBe("data2");
|
|
163
|
-
query.clear();
|
|
164
|
-
expect(() => query.get("query1[]")).toThrow();
|
|
165
|
-
expect(() => query.get("query2[]")).toThrow();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
describe("revalidate", () => {
|
|
170
|
-
beforeEach(() => {
|
|
171
|
-
query.clear();
|
|
172
|
-
vi.useFakeTimers();
|
|
173
|
-
});
|
|
174
|
-
afterEach(() => {
|
|
175
|
-
vi.useRealTimers();
|
|
176
|
-
});
|
|
177
|
-
test("should revalidate all cached entries when no key provided", async () => {
|
|
178
|
-
return createRoot(async () => {
|
|
179
|
-
let callCount = 0;
|
|
180
|
-
const testFn = async () => {
|
|
181
|
-
callCount++;
|
|
182
|
-
return `data-${callCount}`;
|
|
183
|
-
};
|
|
184
|
-
const cachedFn = query(testFn, "testQuery");
|
|
185
|
-
const result1 = await cachedFn();
|
|
186
|
-
expect(result1).toBe("data-1");
|
|
187
|
-
expect(callCount).toBe(1);
|
|
188
|
-
await revalidate(); // Force revalidation and wait for transition
|
|
189
|
-
vi.runAllTimers();
|
|
190
|
-
const result2 = await cachedFn();
|
|
191
|
-
expect(result2).toBe("data-2");
|
|
192
|
-
expect(callCount).toBe(2);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
test("revalidate specific key", async () => {
|
|
196
|
-
return createRoot(async () => {
|
|
197
|
-
let callCount1 = 0;
|
|
198
|
-
let callCount2 = 0;
|
|
199
|
-
const testFn1 = async () => {
|
|
200
|
-
callCount1++;
|
|
201
|
-
return `data1-${callCount1}`;
|
|
202
|
-
};
|
|
203
|
-
const testFn2 = async () => {
|
|
204
|
-
callCount2++;
|
|
205
|
-
return `data2-${callCount2}`;
|
|
206
|
-
};
|
|
207
|
-
const willRevalidateFn = query(testFn1, "query1");
|
|
208
|
-
const willNotRevalidateFn = query(testFn2, "query2");
|
|
209
|
-
await willRevalidateFn();
|
|
210
|
-
await willNotRevalidateFn();
|
|
211
|
-
expect(callCount1).toBe(1);
|
|
212
|
-
expect(callCount2).toBe(1);
|
|
213
|
-
await revalidate(willRevalidateFn.key);
|
|
214
|
-
vi.runAllTimers();
|
|
215
|
-
await willRevalidateFn();
|
|
216
|
-
await willNotRevalidateFn();
|
|
217
|
-
expect(callCount1).toBe(2);
|
|
218
|
-
expect(callCount2).toBe(1);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
test("should revalidate multiple keys", async () => {
|
|
222
|
-
return createRoot(async () => {
|
|
223
|
-
let callCount1 = 0;
|
|
224
|
-
let callCount2 = 0;
|
|
225
|
-
let callCount3 = 0;
|
|
226
|
-
const testFn1 = async () => {
|
|
227
|
-
callCount1++;
|
|
228
|
-
return `data1-${callCount1}`;
|
|
229
|
-
};
|
|
230
|
-
const testFn2 = async () => {
|
|
231
|
-
callCount2++;
|
|
232
|
-
return `data2-${callCount2}`;
|
|
233
|
-
};
|
|
234
|
-
const testFn3 = async () => {
|
|
235
|
-
callCount3++;
|
|
236
|
-
return `data3-${callCount3}`;
|
|
237
|
-
};
|
|
238
|
-
const cachedFn1 = query(testFn1, "query1");
|
|
239
|
-
const cachedFn2 = query(testFn2, "query2");
|
|
240
|
-
const cachedFn3 = query(testFn3, "query3");
|
|
241
|
-
await cachedFn1();
|
|
242
|
-
await cachedFn2();
|
|
243
|
-
await cachedFn3();
|
|
244
|
-
await revalidate([cachedFn1.key, cachedFn3.key]);
|
|
245
|
-
vi.runAllTimers();
|
|
246
|
-
await cachedFn1();
|
|
247
|
-
await cachedFn2();
|
|
248
|
-
await cachedFn3();
|
|
249
|
-
expect(callCount1).toBe(2);
|
|
250
|
-
expect(callCount2).toBe(1);
|
|
251
|
-
expect(callCount3).toBe(2);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
describe("cacheKeyOp should", () => {
|
|
256
|
-
beforeEach(() => {
|
|
257
|
-
query.clear();
|
|
258
|
-
});
|
|
259
|
-
test("operate on all entries when no key provided", async () => {
|
|
260
|
-
return createRoot(async () => {
|
|
261
|
-
const testFn1 = async () => "data1";
|
|
262
|
-
const testFn2 = async () => "data2";
|
|
263
|
-
const cachedFn1 = query(testFn1, "query1");
|
|
264
|
-
const cachedFn2 = query(testFn2, "query2");
|
|
265
|
-
await cachedFn1();
|
|
266
|
-
await cachedFn2();
|
|
267
|
-
let operationCount = 0;
|
|
268
|
-
cacheKeyOp(undefined, () => {
|
|
269
|
-
operationCount++;
|
|
270
|
-
});
|
|
271
|
-
expect(operationCount).toBe(2);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
test("operate on specific key", async () => {
|
|
275
|
-
return createRoot(async () => {
|
|
276
|
-
const testFn = async () => "data";
|
|
277
|
-
const cachedFn = query(testFn, "testQuery");
|
|
278
|
-
await cachedFn();
|
|
279
|
-
let operationCount = 0;
|
|
280
|
-
cacheKeyOp(cachedFn.key, () => {
|
|
281
|
-
operationCount++;
|
|
282
|
-
});
|
|
283
|
-
expect(operationCount).toBe(1);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
test("operate on multiple keys", async () => {
|
|
287
|
-
return createRoot(async () => {
|
|
288
|
-
const testFn1 = async () => "data1";
|
|
289
|
-
const testFn2 = async () => "data2";
|
|
290
|
-
const testFn3 = async () => "data3";
|
|
291
|
-
const cachedFn1 = query(testFn1, "query1");
|
|
292
|
-
const cachedFn2 = query(testFn2, "query2");
|
|
293
|
-
const cachedFn3 = query(testFn3, "other");
|
|
294
|
-
await cachedFn1();
|
|
295
|
-
await cachedFn2();
|
|
296
|
-
await cachedFn3();
|
|
297
|
-
let operationCount = 0;
|
|
298
|
-
cacheKeyOp([cachedFn1.key, cachedFn2.key], () => {
|
|
299
|
-
operationCount++;
|
|
300
|
-
});
|
|
301
|
-
expect(operationCount).toBe(2);
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
test("handle partial key matches", async () => {
|
|
305
|
-
return createRoot(async () => {
|
|
306
|
-
const testFn1 = async (id) => `data1-${id}`;
|
|
307
|
-
const testFn2 = async (id) => `data2-${id}`;
|
|
308
|
-
const cachedFn1 = query(testFn1, "query1");
|
|
309
|
-
const cachedFn2 = query(testFn2, "query2");
|
|
310
|
-
await cachedFn1(1);
|
|
311
|
-
await cachedFn1(2);
|
|
312
|
-
await cachedFn2(1);
|
|
313
|
-
let operationCount = 0;
|
|
314
|
-
cacheKeyOp([cachedFn1.key], () => {
|
|
315
|
-
operationCount++;
|
|
316
|
-
});
|
|
317
|
-
expect(operationCount).toBe(2); // Should match both query1[1] and query1[2]
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
describe("hashKey should", () => {
|
|
322
|
-
test("generate consistent hash for same input", () => {
|
|
323
|
-
const hash1 = hashKey([1, "test", { key: "value" }]);
|
|
324
|
-
const hash2 = hashKey([1, "test", { key: "value" }]);
|
|
325
|
-
expect(hash1).toBe(hash2);
|
|
326
|
-
});
|
|
327
|
-
test("generate different hash for different input", () => {
|
|
328
|
-
const hash1 = hashKey([1, "test"]);
|
|
329
|
-
const hash2 = hashKey([2, "test"]);
|
|
330
|
-
expect(hash1).not.toBe(hash2);
|
|
331
|
-
});
|
|
332
|
-
test("handle object key ordering consistently", () => {
|
|
333
|
-
const hash1 = hashKey([{ b: 2, a: 1 }]);
|
|
334
|
-
const hash2 = hashKey([{ a: 1, b: 2 }]);
|
|
335
|
-
expect(hash1).toBe(hash2);
|
|
336
|
-
});
|
|
337
|
-
test("handle nested objects", () => {
|
|
338
|
-
const hash1 = hashKey([{ outer: { b: 2, a: 1 } }]);
|
|
339
|
-
const hash2 = hashKey([{ outer: { a: 1, b: 2 } }]);
|
|
340
|
-
expect(hash1).toBe(hash2);
|
|
341
|
-
});
|
|
342
|
-
test("handle arrays", () => {
|
|
343
|
-
const hash1 = hashKey([[1, 2, 3]]);
|
|
344
|
-
const hash2 = hashKey([[1, 2, 3]]);
|
|
345
|
-
const hash3 = hashKey([[3, 2, 1]]);
|
|
346
|
-
expect(hash1).toBe(hash2);
|
|
347
|
-
expect(hash1).not.toBe(hash3);
|
|
348
|
-
});
|
|
349
|
-
test("handle empty arguments", () => {
|
|
350
|
-
const hash = hashKey([]);
|
|
351
|
-
expect(typeof hash).toBe("string");
|
|
352
|
-
expect(hash).toBe("[]");
|
|
353
|
-
});
|
|
354
|
-
});
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { RouterResponseInit, CustomResponse } from "../types.js";
|
|
2
|
-
export declare function redirect(url: string, init?: number | RouterResponseInit): CustomResponse<never>;
|
|
3
|
-
export declare function reload(init?: RouterResponseInit): CustomResponse<never>;
|
|
4
|
-
export declare function json<T>(data: T, init?: RouterResponseInit): CustomResponse<T>;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export function redirect(url, init = 302) {
|
|
2
|
-
let responseInit;
|
|
3
|
-
let revalidate;
|
|
4
|
-
if (typeof init === "number") {
|
|
5
|
-
responseInit = { status: init };
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
({ revalidate, ...responseInit } = init);
|
|
9
|
-
if (typeof responseInit.status === "undefined") {
|
|
10
|
-
responseInit.status = 302;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
const headers = new Headers(responseInit.headers);
|
|
14
|
-
headers.set("Location", url);
|
|
15
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
16
|
-
const response = new Response(null, {
|
|
17
|
-
...responseInit,
|
|
18
|
-
headers: headers
|
|
19
|
-
});
|
|
20
|
-
return response;
|
|
21
|
-
}
|
|
22
|
-
export function reload(init = {}) {
|
|
23
|
-
const { revalidate, ...responseInit } = init;
|
|
24
|
-
const headers = new Headers(responseInit.headers);
|
|
25
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
26
|
-
return new Response(null, {
|
|
27
|
-
...responseInit,
|
|
28
|
-
headers
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
export function json(data, init = {}) {
|
|
32
|
-
const { revalidate, ...responseInit } = init;
|
|
33
|
-
const headers = new Headers(responseInit.headers);
|
|
34
|
-
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
|
35
|
-
headers.set("Content-Type", "application/json");
|
|
36
|
-
const response = new Response(JSON.stringify(data), {
|
|
37
|
-
...responseInit,
|
|
38
|
-
headers
|
|
39
|
-
});
|
|
40
|
-
response.customBody = () => data;
|
|
41
|
-
return response;
|
|
42
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|