@ivogt/rsc-router 0.0.0-experimental.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 +19 -0
- package/package.json +131 -0
- package/src/__mocks__/version.ts +6 -0
- package/src/__tests__/route-definition.test.ts +63 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +891 -0
- package/src/browser/navigation-client.ts +155 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +545 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +228 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +178 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-navigation.ts +150 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +149 -0
- package/src/browser/rsc-router.tsx +310 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/server-action-bridge.ts +747 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +443 -0
- package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
- package/src/cache/__tests__/memory-store.test.ts +484 -0
- package/src/cache/cache-scope.ts +565 -0
- package/src/cache/cf/__tests__/cf-cache-store.test.ts +361 -0
- package/src/cache/cf/cf-cache-store.ts +274 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/index.ts +52 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +366 -0
- package/src/client.rsc.tsx +88 -0
- package/src/client.tsx +609 -0
- package/src/components/DefaultDocument.tsx +20 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +259 -0
- package/src/handle.ts +120 -0
- package/src/handles/MetaTags.tsx +178 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/href-client.ts +128 -0
- package/src/href.ts +139 -0
- package/src/index.rsc.ts +69 -0
- package/src/index.ts +84 -0
- package/src/loader.rsc.ts +204 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +198 -0
- package/src/route-definition.ts +1333 -0
- package/src/route-map-builder.ts +140 -0
- package/src/route-types.ts +148 -0
- package/src/route-utils.ts +89 -0
- package/src/router/__tests__/match-context.test.ts +104 -0
- package/src/router/__tests__/match-pipelines.test.ts +537 -0
- package/src/router/__tests__/match-result.test.ts +566 -0
- package/src/router/__tests__/on-error.test.ts +935 -0
- package/src/router/__tests__/pattern-matching.test.ts +577 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +60 -0
- package/src/router/loader-resolution.ts +326 -0
- package/src/router/manifest.ts +116 -0
- package/src/router/match-context.ts +261 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +261 -0
- package/src/router/match-middleware/cache-store.ts +250 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +268 -0
- package/src/router/match-middleware/segment-resolution.ts +174 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +212 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.test.ts +1355 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +271 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +299 -0
- package/src/router/types.ts +96 -0
- package/src/router.ts +3484 -0
- package/src/rsc/__tests__/helpers.test.ts +175 -0
- package/src/rsc/handler.ts +942 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +225 -0
- package/src/segment-system.tsx +405 -0
- package/src/server/__tests__/request-context.test.ts +171 -0
- package/src/server/context.ts +340 -0
- package/src/server/handle-store.ts +230 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +470 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +126 -0
- package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
- package/src/ssr/index.tsx +215 -0
- package/src/types.ts +1473 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +357 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/index.ts +608 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +109 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
2
|
+
import { MemorySegmentCacheStore } from "../memory-segment-store";
|
|
3
|
+
import type { CachedEntryData } from "../types";
|
|
4
|
+
|
|
5
|
+
describe("MemorySegmentCacheStore", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Clear the global cache between tests
|
|
8
|
+
MemorySegmentCacheStore.resetGlobalCache();
|
|
9
|
+
vi.useFakeTimers();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const createTestData = (id: string = "test-segment"): CachedEntryData => ({
|
|
17
|
+
segments: [
|
|
18
|
+
{
|
|
19
|
+
encoded: "test-component-" + id,
|
|
20
|
+
metadata: {
|
|
21
|
+
id,
|
|
22
|
+
type: "route",
|
|
23
|
+
namespace: "test",
|
|
24
|
+
index: 0,
|
|
25
|
+
params: {},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
handles: {},
|
|
30
|
+
expiresAt: Date.now() + 60000,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("constructor", () => {
|
|
34
|
+
it("should create store without options", () => {
|
|
35
|
+
const store = new MemorySegmentCacheStore();
|
|
36
|
+
expect(store).toBeDefined();
|
|
37
|
+
expect(store.defaults).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should accept default options", () => {
|
|
41
|
+
const store = new MemorySegmentCacheStore({
|
|
42
|
+
defaults: { ttl: 120, swr: 300 },
|
|
43
|
+
});
|
|
44
|
+
expect(store.defaults).toEqual({ ttl: 120, swr: 300 });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should use globalThis for HMR survival", () => {
|
|
48
|
+
const store1 = new MemorySegmentCacheStore();
|
|
49
|
+
store1.set("key", createTestData(), 60);
|
|
50
|
+
|
|
51
|
+
// Create new instance - should share the same cache
|
|
52
|
+
const store2 = new MemorySegmentCacheStore();
|
|
53
|
+
const stats = store2.getStats();
|
|
54
|
+
|
|
55
|
+
expect(stats.size).toBe(1);
|
|
56
|
+
expect(stats.keys).toContain("key");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("resetGlobalCache", () => {
|
|
61
|
+
it("should clear global cache state", async () => {
|
|
62
|
+
const store1 = new MemorySegmentCacheStore();
|
|
63
|
+
await store1.set("key", createTestData(), 60);
|
|
64
|
+
expect(store1.getStats().size).toBe(1);
|
|
65
|
+
|
|
66
|
+
// Reset global cache
|
|
67
|
+
MemorySegmentCacheStore.resetGlobalCache();
|
|
68
|
+
|
|
69
|
+
// New store should have empty cache
|
|
70
|
+
const store2 = new MemorySegmentCacheStore();
|
|
71
|
+
expect(store2.getStats().size).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should not affect existing store instance cache reference", async () => {
|
|
75
|
+
const store = new MemorySegmentCacheStore();
|
|
76
|
+
await store.set("key", createTestData(), 60);
|
|
77
|
+
|
|
78
|
+
// Reset creates new global Map, but existing store still has old reference
|
|
79
|
+
MemorySegmentCacheStore.resetGlobalCache();
|
|
80
|
+
|
|
81
|
+
// Old store still sees its data (stale reference)
|
|
82
|
+
expect(store.getStats().size).toBe(1);
|
|
83
|
+
|
|
84
|
+
// But new store has fresh empty cache
|
|
85
|
+
const newStore = new MemorySegmentCacheStore();
|
|
86
|
+
expect(newStore.getStats().size).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("get/set", () => {
|
|
91
|
+
it("should return null for missing key", async () => {
|
|
92
|
+
const store = new MemorySegmentCacheStore();
|
|
93
|
+
const result = await store.get("missing-key");
|
|
94
|
+
expect(result).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should store and retrieve data", async () => {
|
|
98
|
+
const store = new MemorySegmentCacheStore();
|
|
99
|
+
const data = createTestData();
|
|
100
|
+
|
|
101
|
+
await store.set("test-key", data, 60);
|
|
102
|
+
const result = await store.get("test-key");
|
|
103
|
+
|
|
104
|
+
expect(result).not.toBeNull();
|
|
105
|
+
expect(result!.data.segments).toEqual(data.segments);
|
|
106
|
+
expect(result!.data.handles).toEqual(data.handles);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should always return shouldRevalidate=false (no SWR support)", async () => {
|
|
110
|
+
const store = new MemorySegmentCacheStore();
|
|
111
|
+
const data = createTestData();
|
|
112
|
+
|
|
113
|
+
await store.set("test-key", data, 60, 300); // SWR param is ignored
|
|
114
|
+
const result = await store.get("test-key");
|
|
115
|
+
|
|
116
|
+
expect(result!.shouldRevalidate).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle multiple entries", async () => {
|
|
120
|
+
const store = new MemorySegmentCacheStore();
|
|
121
|
+
|
|
122
|
+
await store.set("key1", createTestData("seg1"), 60);
|
|
123
|
+
await store.set("key2", createTestData("seg2"), 60);
|
|
124
|
+
await store.set("key3", createTestData("seg3"), 60);
|
|
125
|
+
|
|
126
|
+
const result1 = await store.get("key1");
|
|
127
|
+
const result2 = await store.get("key2");
|
|
128
|
+
const result3 = await store.get("key3");
|
|
129
|
+
|
|
130
|
+
expect(result1!.data.segments[0].metadata.id).toBe("seg1");
|
|
131
|
+
expect(result2!.data.segments[0].metadata.id).toBe("seg2");
|
|
132
|
+
expect(result3!.data.segments[0].metadata.id).toBe("seg3");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("TTL expiration", () => {
|
|
137
|
+
it("should calculate expiresAt from TTL", async () => {
|
|
138
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
139
|
+
|
|
140
|
+
const store = new MemorySegmentCacheStore();
|
|
141
|
+
const data = createTestData();
|
|
142
|
+
|
|
143
|
+
await store.set("key", data, 60);
|
|
144
|
+
|
|
145
|
+
// Should be available before TTL
|
|
146
|
+
vi.advanceTimersByTime(59 * 1000);
|
|
147
|
+
let result = await store.get("key");
|
|
148
|
+
expect(result).not.toBeNull();
|
|
149
|
+
|
|
150
|
+
// Should expire after TTL
|
|
151
|
+
vi.advanceTimersByTime(2 * 1000);
|
|
152
|
+
result = await store.get("key");
|
|
153
|
+
expect(result).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should delete expired entries on get", async () => {
|
|
157
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
158
|
+
|
|
159
|
+
const store = new MemorySegmentCacheStore();
|
|
160
|
+
await store.set("key", createTestData(), 5);
|
|
161
|
+
|
|
162
|
+
const stats1 = store.getStats();
|
|
163
|
+
expect(stats1.size).toBe(1);
|
|
164
|
+
|
|
165
|
+
vi.advanceTimersByTime(6 * 1000);
|
|
166
|
+
await store.get("key");
|
|
167
|
+
|
|
168
|
+
const stats2 = store.getStats();
|
|
169
|
+
expect(stats2.size).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should handle different TTLs for different entries", async () => {
|
|
173
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
174
|
+
|
|
175
|
+
const store = new MemorySegmentCacheStore();
|
|
176
|
+
await store.set("short", createTestData("short"), 10);
|
|
177
|
+
await store.set("long", createTestData("long"), 100);
|
|
178
|
+
|
|
179
|
+
vi.advanceTimersByTime(50 * 1000);
|
|
180
|
+
|
|
181
|
+
const shortResult = await store.get("short");
|
|
182
|
+
const longResult = await store.get("long");
|
|
183
|
+
|
|
184
|
+
expect(shortResult).toBeNull();
|
|
185
|
+
expect(longResult).not.toBeNull();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("delete", () => {
|
|
190
|
+
it("should delete existing entry", async () => {
|
|
191
|
+
const store = new MemorySegmentCacheStore();
|
|
192
|
+
await store.set("key", createTestData(), 60);
|
|
193
|
+
|
|
194
|
+
const deleted = await store.delete("key");
|
|
195
|
+
expect(deleted).toBe(true);
|
|
196
|
+
|
|
197
|
+
const result = await store.get("key");
|
|
198
|
+
expect(result).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should return false for non-existent key", async () => {
|
|
202
|
+
const store = new MemorySegmentCacheStore();
|
|
203
|
+
const deleted = await store.delete("missing");
|
|
204
|
+
expect(deleted).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("clear", () => {
|
|
209
|
+
it("should clear all entries", async () => {
|
|
210
|
+
const store = new MemorySegmentCacheStore();
|
|
211
|
+
await store.set("key1", createTestData("seg1"), 60);
|
|
212
|
+
await store.set("key2", createTestData("seg2"), 60);
|
|
213
|
+
|
|
214
|
+
await store.clear();
|
|
215
|
+
|
|
216
|
+
const stats = store.getStats();
|
|
217
|
+
expect(stats.size).toBe(0);
|
|
218
|
+
expect(stats.keys).toEqual([]);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("getStats", () => {
|
|
223
|
+
it("should return empty stats for new store", () => {
|
|
224
|
+
const store = new MemorySegmentCacheStore();
|
|
225
|
+
const stats = store.getStats();
|
|
226
|
+
|
|
227
|
+
expect(stats.size).toBe(0);
|
|
228
|
+
expect(stats.keys).toEqual([]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should return correct stats after operations", async () => {
|
|
232
|
+
const store = new MemorySegmentCacheStore();
|
|
233
|
+
await store.set("key1", createTestData(), 60);
|
|
234
|
+
await store.set("key2", createTestData(), 60);
|
|
235
|
+
|
|
236
|
+
const stats = store.getStats();
|
|
237
|
+
|
|
238
|
+
expect(stats.size).toBe(2);
|
|
239
|
+
expect(stats.keys).toContain("key1");
|
|
240
|
+
expect(stats.keys).toContain("key2");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("edge cases", () => {
|
|
245
|
+
it("should handle special characters in keys", async () => {
|
|
246
|
+
const store = new MemorySegmentCacheStore();
|
|
247
|
+
const key = "route:/products/cat=electronics&page=1";
|
|
248
|
+
const data = createTestData();
|
|
249
|
+
|
|
250
|
+
await store.set(key, data, 60);
|
|
251
|
+
const result = await store.get(key);
|
|
252
|
+
|
|
253
|
+
expect(result).not.toBeNull();
|
|
254
|
+
expect(result!.data).toBeDefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should overwrite existing entries with same key", async () => {
|
|
258
|
+
const store = new MemorySegmentCacheStore();
|
|
259
|
+
|
|
260
|
+
await store.set("key", createTestData("first"), 60);
|
|
261
|
+
await store.set("key", createTestData("second"), 60);
|
|
262
|
+
|
|
263
|
+
const result = await store.get("key");
|
|
264
|
+
expect(result!.data.segments[0].metadata.id).toBe("second");
|
|
265
|
+
|
|
266
|
+
const stats = store.getStats();
|
|
267
|
+
expect(stats.size).toBe(1);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should handle empty handles object", async () => {
|
|
271
|
+
const store = new MemorySegmentCacheStore();
|
|
272
|
+
const data: CachedEntryData = {
|
|
273
|
+
segments: [],
|
|
274
|
+
handles: {},
|
|
275
|
+
expiresAt: Date.now() + 60000,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await store.set("key", data, 60);
|
|
279
|
+
const result = await store.get("key");
|
|
280
|
+
|
|
281
|
+
expect(result!.data.handles).toEqual({});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should handle complex segment data", async () => {
|
|
285
|
+
const store = new MemorySegmentCacheStore();
|
|
286
|
+
const data: CachedEntryData = {
|
|
287
|
+
segments: [
|
|
288
|
+
{
|
|
289
|
+
encoded: "layout-component",
|
|
290
|
+
metadata: {
|
|
291
|
+
id: "layout",
|
|
292
|
+
type: "layout",
|
|
293
|
+
namespace: "root",
|
|
294
|
+
index: 0,
|
|
295
|
+
params: {},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
encoded: "page-component",
|
|
300
|
+
metadata: {
|
|
301
|
+
id: "page",
|
|
302
|
+
type: "route",
|
|
303
|
+
namespace: "products",
|
|
304
|
+
index: 1,
|
|
305
|
+
params: { id: "123" },
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
handles: {
|
|
310
|
+
layout: { title: "My App" },
|
|
311
|
+
page: { meta: { description: "Product page" } },
|
|
312
|
+
},
|
|
313
|
+
expiresAt: Date.now() + 60000,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
await store.set("key", data, 60);
|
|
317
|
+
const result = await store.get("key");
|
|
318
|
+
|
|
319
|
+
expect(result!.data.segments).toHaveLength(2);
|
|
320
|
+
expect(result!.data.handles).toEqual(data.handles);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("should handle TTL of 0 (immediate expiration)", async () => {
|
|
324
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
325
|
+
|
|
326
|
+
const store = new MemorySegmentCacheStore();
|
|
327
|
+
await store.set("key", createTestData(), 0);
|
|
328
|
+
|
|
329
|
+
vi.advanceTimersByTime(1);
|
|
330
|
+
const result = await store.get("key");
|
|
331
|
+
expect(result).toBeNull();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should handle very large TTL", async () => {
|
|
335
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
336
|
+
|
|
337
|
+
const store = new MemorySegmentCacheStore();
|
|
338
|
+
await store.set("key", createTestData(), 365 * 24 * 60 * 60); // 1 year
|
|
339
|
+
|
|
340
|
+
// Advance 6 months
|
|
341
|
+
vi.advanceTimersByTime(180 * 24 * 60 * 60 * 1000);
|
|
342
|
+
const result = await store.get("key");
|
|
343
|
+
expect(result).not.toBeNull();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("should handle many segments in one entry", async () => {
|
|
347
|
+
const store = new MemorySegmentCacheStore();
|
|
348
|
+
const data: CachedEntryData = {
|
|
349
|
+
segments: Array.from({ length: 50 }, (_, i) => ({
|
|
350
|
+
encoded: `component-${i}`,
|
|
351
|
+
metadata: {
|
|
352
|
+
id: `segment-${i}`,
|
|
353
|
+
type: "route" as const,
|
|
354
|
+
namespace: `ns-${i}`,
|
|
355
|
+
index: i,
|
|
356
|
+
params: { index: String(i) },
|
|
357
|
+
},
|
|
358
|
+
})),
|
|
359
|
+
handles: {},
|
|
360
|
+
expiresAt: Date.now() + 60000,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
await store.set("key", data, 60);
|
|
364
|
+
const result = await store.get("key");
|
|
365
|
+
|
|
366
|
+
expect(result!.data.segments).toHaveLength(50);
|
|
367
|
+
expect(result!.data.segments[49].metadata.id).toBe("segment-49");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should handle unicode in segment data", async () => {
|
|
371
|
+
const store = new MemorySegmentCacheStore();
|
|
372
|
+
const data: CachedEntryData = {
|
|
373
|
+
segments: [
|
|
374
|
+
{
|
|
375
|
+
encoded: "コンポーネント",
|
|
376
|
+
metadata: {
|
|
377
|
+
id: "日本語-segment",
|
|
378
|
+
type: "route",
|
|
379
|
+
namespace: "路由",
|
|
380
|
+
index: 0,
|
|
381
|
+
params: { name: "世界" },
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
handles: {
|
|
386
|
+
"日本語-segment": { title: "こんにちは" },
|
|
387
|
+
},
|
|
388
|
+
expiresAt: Date.now() + 60000,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
await store.set("key", data, 60);
|
|
392
|
+
const result = await store.get("key");
|
|
393
|
+
|
|
394
|
+
expect(result!.data.segments[0].metadata.id).toBe("日本語-segment");
|
|
395
|
+
expect(result!.data.handles["日本語-segment"]).toEqual({
|
|
396
|
+
title: "こんにちは",
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("should handle segments with all metadata fields", async () => {
|
|
401
|
+
const store = new MemorySegmentCacheStore();
|
|
402
|
+
const data: CachedEntryData = {
|
|
403
|
+
segments: [
|
|
404
|
+
{
|
|
405
|
+
encoded: "full-component",
|
|
406
|
+
encodedLayout: "layout-data",
|
|
407
|
+
encodedLoading: "loading-data",
|
|
408
|
+
encodedLoaderData: "loader-data",
|
|
409
|
+
encodedLoaderDataPromise: "promise-data",
|
|
410
|
+
metadata: {
|
|
411
|
+
id: "full-segment",
|
|
412
|
+
type: "route",
|
|
413
|
+
namespace: "test",
|
|
414
|
+
index: 0,
|
|
415
|
+
params: { id: "123" },
|
|
416
|
+
slot: "@modal",
|
|
417
|
+
belongsToRoute: "parent-route",
|
|
418
|
+
layoutName: "MainLayout",
|
|
419
|
+
parallelName: "sidebar",
|
|
420
|
+
loaderId: "loader-1",
|
|
421
|
+
loaderIds: ["loader-1", "loader-2"],
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
handles: {},
|
|
426
|
+
expiresAt: Date.now() + 60000,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
await store.set("key", data, 60);
|
|
430
|
+
const result = await store.get("key");
|
|
431
|
+
|
|
432
|
+
const seg = result!.data.segments[0];
|
|
433
|
+
expect(seg.metadata.slot).toBe("@modal");
|
|
434
|
+
expect(seg.metadata.belongsToRoute).toBe("parent-route");
|
|
435
|
+
expect(seg.metadata.layoutName).toBe("MainLayout");
|
|
436
|
+
expect(seg.metadata.loaderIds).toEqual(["loader-1", "loader-2"]);
|
|
437
|
+
expect(seg.encodedLayout).toBe("layout-data");
|
|
438
|
+
expect(seg.encodedLoading).toBe("loading-data");
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("should handle concurrent access from multiple stores", async () => {
|
|
442
|
+
const store1 = new MemorySegmentCacheStore();
|
|
443
|
+
const store2 = new MemorySegmentCacheStore();
|
|
444
|
+
|
|
445
|
+
await store1.set("key1", createTestData("seg1"), 60);
|
|
446
|
+
await store2.set("key2", createTestData("seg2"), 60);
|
|
447
|
+
|
|
448
|
+
// Both stores share the same underlying Map via globalThis
|
|
449
|
+
const result1 = await store1.get("key2");
|
|
450
|
+
const result2 = await store2.get("key1");
|
|
451
|
+
|
|
452
|
+
expect(result1).not.toBeNull();
|
|
453
|
+
expect(result2).not.toBeNull();
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("should handle rapid successive operations", async () => {
|
|
457
|
+
const store = new MemorySegmentCacheStore();
|
|
458
|
+
|
|
459
|
+
// Rapid set/get/delete operations
|
|
460
|
+
for (let i = 0; i < 100; i++) {
|
|
461
|
+
await store.set(`key${i}`, createTestData(`seg${i}`), 60);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
expect(store.getStats().size).toBe(100);
|
|
465
|
+
|
|
466
|
+
for (let i = 0; i < 50; i++) {
|
|
467
|
+
await store.delete(`key${i}`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
expect(store.getStats().size).toBe(50);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("should handle entries with empty segments array", async () => {
|
|
474
|
+
const store = new MemorySegmentCacheStore();
|
|
475
|
+
const data: CachedEntryData = {
|
|
476
|
+
segments: [],
|
|
477
|
+
handles: {},
|
|
478
|
+
expiresAt: Date.now() + 60000,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
await store.set("key", data, 60);
|
|
482
|
+
const result = await store.get("key");
|
|
483
|
+
|
|
484
|
+
expect(result!.data.segments).toEqual([]);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
});
|