@lolyjs/core 0.2.0-alpha.9 → 0.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1593 -762
- package/dist/{bootstrap-DgvWWDim.d.mts → bootstrap-BfGTMUkj.d.mts} +12 -0
- package/dist/{bootstrap-DgvWWDim.d.ts → bootstrap-BfGTMUkj.d.ts} +12 -0
- package/dist/cli.cjs +16397 -2601
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +19096 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +17419 -3204
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +323 -56
- package/dist/index.d.ts +323 -56
- package/dist/index.mjs +20236 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.types-Duhjyfit.d.mts +280 -0
- package/dist/index.types-Duhjyfit.d.ts +280 -0
- package/dist/react/cache.cjs +82 -32
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +29 -21
- package/dist/react/cache.d.ts +29 -21
- package/dist/react/{cache.js → cache.mjs} +84 -34
- package/dist/react/cache.mjs.map +1 -0
- package/dist/react/components.cjs +11 -12
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/{components.js → components.mjs} +12 -13
- package/dist/react/components.mjs.map +1 -0
- package/dist/react/hooks.cjs +16 -6
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/{hooks.js → hooks.mjs} +24 -14
- package/dist/react/hooks.mjs.map +1 -0
- package/dist/react/sockets.cjs +5 -6
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.mjs +21 -0
- package/dist/react/sockets.mjs.map +1 -0
- package/dist/react/themes.cjs +61 -18
- package/dist/react/themes.cjs.map +1 -1
- package/dist/react/{themes.js → themes.mjs} +64 -21
- package/dist/react/themes.mjs.map +1 -0
- package/dist/runtime.cjs +465 -117
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/{runtime.js → runtime.mjs} +466 -118
- package/dist/runtime.mjs.map +1 -0
- package/package.json +26 -26
- package/dist/cli.js +0 -5291
- package/dist/cli.js.map +0 -1
- package/dist/index.js +0 -6015
- package/dist/index.js.map +0 -1
- package/dist/react/cache.js.map +0 -1
- package/dist/react/components.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/react/sockets.js +0 -22
- package/dist/react/sockets.js.map +0 -1
- package/dist/react/themes.js.map +0 -1
- package/dist/runtime.js.map +0 -1
package/dist/react/cache.cjs
CHANGED
|
@@ -113,14 +113,16 @@ function deleteCacheEntry(key) {
|
|
|
113
113
|
function buildDataUrl(url) {
|
|
114
114
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
115
115
|
}
|
|
116
|
-
async function fetchRouteDataOnce(url) {
|
|
116
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
117
117
|
const dataUrl = buildDataUrl(url);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
const headers = {
|
|
119
|
+
"x-fw-data": "1",
|
|
120
|
+
Accept: "application/json"
|
|
121
|
+
};
|
|
122
|
+
if (skipLayoutHooks) {
|
|
123
|
+
headers["x-skip-layout-hooks"] = "true";
|
|
124
|
+
}
|
|
125
|
+
const res = await fetch(dataUrl, { headers });
|
|
124
126
|
let json = {};
|
|
125
127
|
try {
|
|
126
128
|
const text = await res.text();
|
|
@@ -140,7 +142,7 @@ async function fetchRouteDataOnce(url) {
|
|
|
140
142
|
};
|
|
141
143
|
return result;
|
|
142
144
|
}
|
|
143
|
-
function revalidatePath(path) {
|
|
145
|
+
function revalidatePath(path, skipAutoRevalidate = false) {
|
|
144
146
|
const normalizedPath = path.split("?")[0];
|
|
145
147
|
const hasQueryParams = path.includes("?");
|
|
146
148
|
const keysForPath = pathIndex.get(normalizedPath);
|
|
@@ -167,7 +169,7 @@ function revalidatePath(path) {
|
|
|
167
169
|
keysToDelete.forEach((key) => {
|
|
168
170
|
deleteCacheEntry(key);
|
|
169
171
|
});
|
|
170
|
-
if (typeof window !== "undefined") {
|
|
172
|
+
if (!skipAutoRevalidate && typeof window !== "undefined") {
|
|
171
173
|
const currentPathname = window.location.pathname;
|
|
172
174
|
const currentSearch = window.location.search;
|
|
173
175
|
const matchesCurrentPath = normalizedPath === currentPathname;
|
|
@@ -193,29 +195,60 @@ function revalidatePath(path) {
|
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
197
|
}
|
|
198
|
+
var isRevalidating = false;
|
|
196
199
|
async function revalidate() {
|
|
197
200
|
if (typeof window === "undefined") {
|
|
198
201
|
throw new Error("revalidate() can only be called on the client");
|
|
199
202
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
203
|
+
if (isRevalidating) {
|
|
204
|
+
const key = buildDataUrl(window.location.pathname + window.location.search);
|
|
205
|
+
const entry = dataCache.get(key);
|
|
206
|
+
if (entry && entry.status === "pending") {
|
|
207
|
+
return entry.promise;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
isRevalidating = true;
|
|
211
|
+
try {
|
|
212
|
+
const pathname = window.location.pathname + window.location.search;
|
|
213
|
+
revalidatePath(pathname, true);
|
|
214
|
+
const freshData = await getRouteData(pathname, { revalidate: true });
|
|
215
|
+
if (window.__FW_DATA__ && freshData.ok && freshData.json) {
|
|
216
|
+
const currentData = window.__FW_DATA__;
|
|
217
|
+
if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
|
|
218
|
+
window.__FW_LAYOUT_PROPS__ = freshData.json.layoutProps;
|
|
219
|
+
}
|
|
220
|
+
let combinedProps = currentData.props || {};
|
|
221
|
+
if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
|
|
222
|
+
combinedProps = {
|
|
223
|
+
...freshData.json.layoutProps,
|
|
224
|
+
...freshData.json.pageProps ?? freshData.json.props ?? {}
|
|
225
|
+
};
|
|
226
|
+
} else if (freshData.json.pageProps !== void 0) {
|
|
227
|
+
const preservedLayoutProps = window.__FW_LAYOUT_PROPS__ || {};
|
|
228
|
+
combinedProps = {
|
|
229
|
+
...preservedLayoutProps,
|
|
230
|
+
...freshData.json.pageProps
|
|
231
|
+
};
|
|
232
|
+
} else if (freshData.json.props) {
|
|
233
|
+
combinedProps = freshData.json.props;
|
|
234
|
+
}
|
|
235
|
+
window.__FW_DATA__ = {
|
|
236
|
+
...currentData,
|
|
237
|
+
pathname: pathname.split("?")[0],
|
|
238
|
+
params: freshData.json.params || currentData.params || {},
|
|
239
|
+
props: combinedProps,
|
|
240
|
+
metadata: freshData.json.metadata ?? currentData.metadata ?? null,
|
|
241
|
+
notFound: freshData.json.notFound ?? false,
|
|
242
|
+
error: freshData.json.error ?? false
|
|
243
|
+
};
|
|
244
|
+
window.dispatchEvent(new CustomEvent("fw-data-refresh", {
|
|
245
|
+
detail: { data: freshData }
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
return freshData;
|
|
249
|
+
} finally {
|
|
250
|
+
isRevalidating = false;
|
|
217
251
|
}
|
|
218
|
-
return freshData;
|
|
219
252
|
}
|
|
220
253
|
function revalidateRouteData(url) {
|
|
221
254
|
revalidatePath(url);
|
|
@@ -229,7 +262,7 @@ function prefetchRouteData(url) {
|
|
|
229
262
|
}
|
|
230
263
|
return;
|
|
231
264
|
}
|
|
232
|
-
const promise = fetchRouteDataOnce(url).then((value) => {
|
|
265
|
+
const promise = fetchRouteDataOnce(url, true).then((value) => {
|
|
233
266
|
setCacheEntry(key, { status: "fulfilled", value });
|
|
234
267
|
return value;
|
|
235
268
|
}).catch((error) => {
|
|
@@ -245,7 +278,7 @@ async function getRouteData(url, options) {
|
|
|
245
278
|
deleteCacheEntry(key);
|
|
246
279
|
}
|
|
247
280
|
const entry = dataCache.get(key);
|
|
248
|
-
if (entry) {
|
|
281
|
+
if (entry && !options?.revalidate) {
|
|
249
282
|
if (entry.status === "fulfilled") {
|
|
250
283
|
updateLRU(key);
|
|
251
284
|
return entry.value;
|
|
@@ -254,12 +287,29 @@ async function getRouteData(url, options) {
|
|
|
254
287
|
return entry.promise;
|
|
255
288
|
}
|
|
256
289
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
290
|
+
const skipLayoutHooks = !options?.revalidate;
|
|
291
|
+
const currentEntry = dataCache.get(key);
|
|
292
|
+
if (currentEntry && !options?.revalidate) {
|
|
293
|
+
if (currentEntry.status === "fulfilled") {
|
|
294
|
+
updateLRU(key);
|
|
295
|
+
return currentEntry.value;
|
|
296
|
+
}
|
|
297
|
+
if (currentEntry.status === "pending") {
|
|
298
|
+
return currentEntry.promise;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
|
|
302
|
+
const entryAfterFetch = dataCache.get(key);
|
|
303
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
304
|
+
setCacheEntry(key, { status: "fulfilled", value });
|
|
305
|
+
}
|
|
259
306
|
return value;
|
|
260
307
|
}).catch((error) => {
|
|
261
308
|
console.error("[client][cache] Error fetching route data:", error);
|
|
262
|
-
dataCache.
|
|
309
|
+
const entryAfterFetch = dataCache.get(key);
|
|
310
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
311
|
+
dataCache.set(key, { status: "rejected", error });
|
|
312
|
+
}
|
|
263
313
|
throw error;
|
|
264
314
|
});
|
|
265
315
|
dataCache.set(key, { status: "pending", promise });
|
package/dist/react/cache.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../modules/react/cache/index.ts","../../modules/react/cache/client-data-cache/index.ts"],"sourcesContent":["export * from \"./client-data-cache\";","type RouteData = {\n ok: boolean;\n status: number;\n json: any;\n};\n\ntype CacheEntry =\n | { status: \"pending\"; promise: Promise<RouteData> }\n | { status: \"fulfilled\"; value: RouteData }\n | { status: \"rejected\"; error: any };\n\n// Use window to guarantee a single shared cache instance\n// across all bundles/modules\nconst CACHE_KEY = \"__FW_DATA_CACHE__\";\n\n// Maximum number of entries in the cache (LRU)\nconst MAX_CACHE_SIZE = 100;\n\ntype CacheStore = {\n data: Map<string, CacheEntry>;\n index: Map<string, Set<string>>; // pathBase -> Set of keys\n lru: string[]; // Ordered list: most recent at end, oldest at start\n};\n\nfunction getCacheStore(): CacheStore {\n if (typeof window !== \"undefined\") {\n if (!(window as any)[CACHE_KEY]) {\n (window as any)[CACHE_KEY] = {\n data: new Map<string, CacheEntry>(),\n index: new Map<string, Set<string>>(),\n lru: [],\n };\n }\n return (window as any)[CACHE_KEY];\n }\n // Fallback for SSR (though this shouldn't be used on the client)\n return {\n data: new Map<string, CacheEntry>(),\n index: new Map<string, Set<string>>(),\n lru: [],\n };\n}\n\nconst cacheStore = getCacheStore();\nconst dataCache = cacheStore.data;\nconst pathIndex = cacheStore.index;\nconst lru = cacheStore.lru;\n\n// Helper functions for cache management\n\n/**\n * Extract base path from a cache key (removes query params)\n */\nfunction extractPathBase(key: string): string {\n return key.split(\"?\")[0];\n}\n\n/**\n * Add key to path index\n */\nfunction addToIndex(key: string): void {\n const pathBase = extractPathBase(key);\n if (!pathIndex.has(pathBase)) {\n pathIndex.set(pathBase, new Set());\n }\n pathIndex.get(pathBase)!.add(key);\n}\n\n/**\n * Remove key from path index\n */\nfunction removeFromIndex(key: string): void {\n const pathBase = extractPathBase(key);\n const keys = pathIndex.get(pathBase);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) {\n pathIndex.delete(pathBase);\n }\n }\n}\n\n/**\n * Update LRU order - move key to end (most recent)\n */\nfunction updateLRU(key: string): void {\n const index = lru.indexOf(key);\n if (index !== -1) {\n lru.splice(index, 1);\n }\n lru.push(key);\n}\n\n/**\n * Remove oldest entries if cache exceeds MAX_CACHE_SIZE\n */\nfunction evictOldest(): void {\n while (lru.length >= MAX_CACHE_SIZE && lru.length > 0) {\n const oldestKey = lru.shift()!;\n dataCache.delete(oldestKey);\n removeFromIndex(oldestKey);\n }\n}\n\n/**\n * Set cache entry and maintain indexes\n */\nfunction setCacheEntry(key: string, entry: CacheEntry): void {\n const existingEntry = dataCache.get(key);\n const wasFulfilled = existingEntry?.status === \"fulfilled\";\n \n dataCache.set(key, entry);\n \n // Only track fulfilled entries in LRU and index (not pending/rejected)\n if (entry.status === \"fulfilled\") {\n // Add to index if it wasn't already fulfilled (new entry or transition from pending/rejected)\n if (!wasFulfilled) {\n addToIndex(key);\n }\n updateLRU(key);\n evictOldest();\n } else if (wasFulfilled) {\n // If entry was fulfilled and now isn't (transitioning to pending/rejected), remove from index\n removeFromIndex(key);\n }\n}\n\n/**\n * Delete cache entry and clean up indexes\n */\nfunction deleteCacheEntry(key: string): void {\n if (dataCache.has(key)) {\n dataCache.delete(key);\n removeFromIndex(key);\n const lruIndex = lru.indexOf(key);\n if (lruIndex !== -1) {\n lru.splice(lruIndex, 1);\n }\n }\n}\n\nfunction buildDataUrl(url: string): string {\n return url + (url.includes(\"?\") ? \"&\" : \"?\") + \"__fw_data=1\";\n}\n\nasync function fetchRouteDataOnce(url: string): Promise<RouteData> {\n const dataUrl = buildDataUrl(url);\n\n const res = await fetch(dataUrl, {\n headers: {\n \"x-fw-data\": \"1\",\n Accept: \"application/json\",\n },\n });\n\n let json: any = {};\n\n try {\n const text = await res.text();\n if (text) {\n json = JSON.parse(text);\n }\n } catch (parseError) {\n console.error(\n \"[client][cache] Failed to parse response as JSON:\",\n parseError\n );\n }\n\n const result: RouteData = {\n ok: res.ok,\n status: res.status,\n json,\n };\n\n return result;\n}\n\n/**\n * Revalidates route data by removing it from the cache.\n * The next time you navigate to this route, fresh data will be fetched from the server.\n * This is a client-side function and does not require a server-side revalidation.\n *\n * @param path - The route path to revalidate (e.g., '/posts/1' or '/posts/1?page=2')\n * If query params are not included, revalidates all variants of that route.\n *\n * @example\n * ```ts\n * // After saving something to the DB, revalidate the route\n * await saveToDatabase(data);\n * revalidatePath('/posts');\n * \n * // Revalidate a specific route with query params\n * revalidatePath('/posts?page=2');\n * ```\n */\nexport function revalidatePath(path: string): void {\n // Normalize the base path (without query params)\n const normalizedPath = path.split(\"?\")[0];\n const hasQueryParams = path.includes(\"?\");\n \n // Get all keys for this path base from index (O(1) lookup)\n const keysForPath = pathIndex.get(normalizedPath);\n \n if (!keysForPath || keysForPath.size === 0) {\n return; // No entries to revalidate\n }\n \n // If the path includes specific query params, extract them\n let specificQueryParams: string | undefined;\n if (hasQueryParams) {\n const queryPart = path.split(\"?\")[1];\n // Sort query params for consistent comparison\n specificQueryParams = queryPart\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n }\n \n // Iterate only over keys for this path (much smaller set)\n const keysToDelete: string[] = [];\n for (const key of keysForPath) {\n // If specific query params were specified, check if they match\n if (hasQueryParams && specificQueryParams) {\n const [, keyQuery = \"\"] = key.split(\"?\");\n const keyQueryParams = keyQuery\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n \n if (keyQueryParams === specificQueryParams) {\n keysToDelete.push(key);\n }\n } else {\n // If no specific query params, revalidate all variants\n keysToDelete.push(key);\n }\n }\n \n // Delete matching entries\n keysToDelete.forEach((key) => {\n deleteCacheEntry(key);\n });\n \n // If the revalidated path matches the current route, automatically refresh data\n if (typeof window !== \"undefined\") {\n const currentPathname = window.location.pathname;\n const currentSearch = window.location.search;\n const matchesCurrentPath = normalizedPath === currentPathname;\n \n if (matchesCurrentPath) {\n if (hasQueryParams && specificQueryParams) {\n const currentQueryParams = currentSearch\n .replace(\"?\", \"\")\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n \n if (currentQueryParams === specificQueryParams) {\n revalidate().catch((err) => {\n console.error(\n \"[client][cache] Error revalidating current route:\",\n err\n );\n });\n }\n } else {\n revalidate().catch((err) => {\n console.error(\n \"[client][cache] Error revalidating current route:\",\n err\n );\n });\n }\n }\n }\n}\n\n/**\n * Revalidates and refreshes the current page data.\n * Similar to Next.js's `router.refresh()`.\n * \n * This function:\n * 1. Removes the current route from cache\n * 2. Fetches fresh data from the server\n * 3. Updates window.__FW_DATA__ with the new data\n * 4. Dispatches a 'fw-data-refresh' event for components to listen to\n * \n * @returns Promise that resolves with the fresh route data\n * \n * @example\n * ```ts\n * // Refresh current page data after a mutation\n * await revalidate();\n * ```\n */\nexport async function revalidate(): Promise<RouteData> {\n if (typeof window === \"undefined\") {\n throw new Error(\"revalidate() can only be called on the client\");\n }\n\n const pathname = window.location.pathname + window.location.search;\n \n // Revalidate the path (remove from cache)\n revalidatePath(pathname);\n \n // Fetch fresh data\n const freshData = await getRouteData(pathname, { revalidate: true });\n \n // Update window.__FW_DATA__ if it exists\n if ((window as any).__FW_DATA__ && freshData.ok && freshData.json) {\n const currentData = (window as any).__FW_DATA__;\n (window as any).__FW_DATA__ = {\n ...currentData,\n pathname: pathname.split(\"?\")[0],\n params: freshData.json.params || currentData.params || {},\n props: freshData.json.props || currentData.props || {},\n metadata: freshData.json.metadata ?? currentData.metadata ?? null,\n notFound: freshData.json.notFound ?? false,\n error: freshData.json.error ?? false,\n };\n \n // Dispatch event for components to listen to\n window.dispatchEvent(new CustomEvent(\"fw-data-refresh\", {\n detail: { data: freshData },\n }));\n }\n \n return freshData;\n}\n\n/**\n * @deprecated Use `revalidatePath()` instead. This function is kept for backwards compatibility.\n */\nexport function revalidateRouteData(url: string): void {\n revalidatePath(url);\n}\n\nexport function prefetchRouteData(url: string): void {\n const key = buildDataUrl(url);\n\n const cached = dataCache.get(key);\n\n if (cached && cached.status !== \"rejected\") {\n // Update LRU if it exists and is fulfilled\n if (cached.status === \"fulfilled\") {\n updateLRU(key);\n }\n return;\n }\n\n const promise = fetchRouteDataOnce(url)\n .then((value) => {\n setCacheEntry(key, { status: \"fulfilled\", value });\n return value;\n })\n .catch((error) => {\n console.error(\"[client][cache] Error prefetching route data:\", error);\n dataCache.set(key, { status: \"rejected\", error });\n throw error;\n });\n\n dataCache.set(key, { status: \"pending\", promise });\n}\n\nexport type GetRouteDataOptions = {\n /**\n * If true, forces revalidation of route data,\n * ignoring the cache and fetching fresh data from the server.\n * Similar to Next.js's `router.refresh()` behavior.\n */\n revalidate?: boolean;\n};\n\nexport async function getRouteData(\n url: string,\n options?: GetRouteDataOptions\n): Promise<RouteData> {\n const key = buildDataUrl(url);\n\n // If revalidation is requested, remove the entry from cache\n if (options?.revalidate) {\n deleteCacheEntry(key);\n }\n\n const entry = dataCache.get(key);\n\n if (entry) {\n if (entry.status === \"fulfilled\") {\n // Update LRU: mark as recently used\n updateLRU(key);\n return entry.value;\n }\n if (entry.status === \"pending\") {\n return entry.promise;\n }\n }\n\n // No entry in cache, fetch it\n const promise = fetchRouteDataOnce(url)\n .then((value) => {\n setCacheEntry(key, { status: \"fulfilled\", value });\n return value;\n })\n .catch((error) => {\n console.error(\"[client][cache] Error fetching route data:\", error);\n dataCache.set(key, { status: \"rejected\", error });\n throw error;\n });\n\n dataCache.set(key, { status: \"pending\", promise });\n return promise;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACaA,IAAM,YAAY;AAGlB,IAAM,iBAAiB;AAQvB,SAAS,gBAA4B;AACnC,MAAI,OAAO,WAAW,aAAa;AACjC,QAAI,CAAE,OAAe,SAAS,GAAG;AAC/B,MAAC,OAAe,SAAS,IAAI;AAAA,QAC3B,MAAM,oBAAI,IAAwB;AAAA,QAClC,OAAO,oBAAI,IAAyB;AAAA,QACpC,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AACA,WAAQ,OAAe,SAAS;AAAA,EAClC;AAEA,SAAO;AAAA,IACL,MAAM,oBAAI,IAAwB;AAAA,IAClC,OAAO,oBAAI,IAAyB;AAAA,IACpC,KAAK,CAAC;AAAA,EACR;AACF;AAEA,IAAM,aAAa,cAAc;AACjC,IAAM,YAAY,WAAW;AAC7B,IAAM,YAAY,WAAW;AAC7B,IAAM,MAAM,WAAW;AAOvB,SAAS,gBAAgB,KAAqB;AAC5C,SAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AACzB;AAKA,SAAS,WAAW,KAAmB;AACrC,QAAM,WAAW,gBAAgB,GAAG;AACpC,MAAI,CAAC,UAAU,IAAI,QAAQ,GAAG;AAC5B,cAAU,IAAI,UAAU,oBAAI,IAAI,CAAC;AAAA,EACnC;AACA,YAAU,IAAI,QAAQ,EAAG,IAAI,GAAG;AAClC;AAKA,SAAS,gBAAgB,KAAmB;AAC1C,QAAM,WAAW,gBAAgB,GAAG;AACpC,QAAM,OAAO,UAAU,IAAI,QAAQ;AACnC,MAAI,MAAM;AACR,SAAK,OAAO,GAAG;AACf,QAAI,KAAK,SAAS,GAAG;AACnB,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;AAKA,SAAS,UAAU,KAAmB;AACpC,QAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,MAAI,UAAU,IAAI;AAChB,QAAI,OAAO,OAAO,CAAC;AAAA,EACrB;AACA,MAAI,KAAK,GAAG;AACd;AAKA,SAAS,cAAoB;AAC3B,SAAO,IAAI,UAAU,kBAAkB,IAAI,SAAS,GAAG;AACrD,UAAM,YAAY,IAAI,MAAM;AAC5B,cAAU,OAAO,SAAS;AAC1B,oBAAgB,SAAS;AAAA,EAC3B;AACF;AAKA,SAAS,cAAc,KAAa,OAAyB;AAC3D,QAAM,gBAAgB,UAAU,IAAI,GAAG;AACvC,QAAM,eAAe,eAAe,WAAW;AAE/C,YAAU,IAAI,KAAK,KAAK;AAGxB,MAAI,MAAM,WAAW,aAAa;AAEhC,QAAI,CAAC,cAAc;AACjB,iBAAW,GAAG;AAAA,IAChB;AACA,cAAU,GAAG;AACb,gBAAY;AAAA,EACd,WAAW,cAAc;AAEvB,oBAAgB,GAAG;AAAA,EACrB;AACF;AAKA,SAAS,iBAAiB,KAAmB;AAC3C,MAAI,UAAU,IAAI,GAAG,GAAG;AACtB,cAAU,OAAO,GAAG;AACpB,oBAAgB,GAAG;AACnB,UAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,QAAI,aAAa,IAAI;AACnB,UAAI,OAAO,UAAU,CAAC;AAAA,IACxB;AAAA,EACF;AACF;AAEA,SAAS,aAAa,KAAqB;AACzC,SAAO,OAAO,IAAI,SAAS,GAAG,IAAI,MAAM,OAAO;AACjD;AAEA,eAAe,mBAAmB,KAAiC;AACjE,QAAM,UAAU,aAAa,GAAG;AAEhC,QAAM,MAAM,MAAM,MAAM,SAAS;AAAA,IAC/B,SAAS;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,EACF,CAAC;AAED,MAAI,OAAY,CAAC;AAEjB,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,MAAM;AACR,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB;AAAA,EACF,SAAS,YAAY;AACnB,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAoB;AAAA,IACxB,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ;AAAA,EACF;AAEA,SAAO;AACT;AAoBO,SAAS,eAAe,MAAoB;AAEjD,QAAM,iBAAiB,KAAK,MAAM,GAAG,EAAE,CAAC;AACxC,QAAM,iBAAiB,KAAK,SAAS,GAAG;AAGxC,QAAM,cAAc,UAAU,IAAI,cAAc;AAEhD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,gBAAgB;AAClB,UAAM,YAAY,KAAK,MAAM,GAAG,EAAE,CAAC;AAEnC,0BAAsB,UACnB,MAAM,GAAG,EACT,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,YAAY,CAAC,EACzC,KAAK,EACL,KAAK,GAAG;AAAA,EACb;AAGA,QAAM,eAAyB,CAAC;AAChC,aAAW,OAAO,aAAa;AAE7B,QAAI,kBAAkB,qBAAqB;AACzC,YAAM,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,MAAM,GAAG;AACvC,YAAM,iBAAiB,SACpB,MAAM,GAAG,EACT,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,YAAY,CAAC,EACzC,KAAK,EACL,KAAK,GAAG;AAEX,UAAI,mBAAmB,qBAAqB;AAC1C,qBAAa,KAAK,GAAG;AAAA,MACvB;AAAA,IACF,OAAO;AAEL,mBAAa,KAAK,GAAG;AAAA,IACvB;AAAA,EACF;AAGA,eAAa,QAAQ,CAAC,QAAQ;AAC5B,qBAAiB,GAAG;AAAA,EACtB,CAAC;AAGD,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,kBAAkB,OAAO,SAAS;AACxC,UAAM,gBAAgB,OAAO,SAAS;AACtC,UAAM,qBAAqB,mBAAmB;AAE9C,QAAI,oBAAoB;AACtB,UAAI,kBAAkB,qBAAqB;AACzC,cAAM,qBAAqB,cACxB,QAAQ,KAAK,EAAE,EACf,MAAM,GAAG,EACT,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,YAAY,CAAC,EACzC,KAAK,EACL,KAAK,GAAG;AAEX,YAAI,uBAAuB,qBAAqB;AAC9C,qBAAW,EAAE,MAAM,CAAC,QAAQ;AAC1B,oBAAQ;AAAA,cACN;AAAA,cACA;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AACL,mBAAW,EAAE,MAAM,CAAC,QAAQ;AAC1B,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAoBA,eAAsB,aAAiC;AACrD,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,QAAM,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS;AAG5D,iBAAe,QAAQ;AAGvB,QAAM,YAAY,MAAM,aAAa,UAAU,EAAE,YAAY,KAAK,CAAC;AAGnE,MAAK,OAAe,eAAe,UAAU,MAAM,UAAU,MAAM;AACjE,UAAM,cAAe,OAAe;AACpC,IAAC,OAAe,cAAc;AAAA,MAC5B,GAAG;AAAA,MACH,UAAU,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,MAC/B,QAAQ,UAAU,KAAK,UAAU,YAAY,UAAU,CAAC;AAAA,MACxD,OAAO,UAAU,KAAK,SAAS,YAAY,SAAS,CAAC;AAAA,MACrD,UAAU,UAAU,KAAK,YAAY,YAAY,YAAY;AAAA,MAC7D,UAAU,UAAU,KAAK,YAAY;AAAA,MACrC,OAAO,UAAU,KAAK,SAAS;AAAA,IACjC;AAGA,WAAO,cAAc,IAAI,YAAY,mBAAmB;AAAA,MACtD,QAAQ,EAAE,MAAM,UAAU;AAAA,IAC5B,CAAC,CAAC;AAAA,EACJ;AAEA,SAAO;AACT;AAKO,SAAS,oBAAoB,KAAmB;AACrD,iBAAe,GAAG;AACpB;AAEO,SAAS,kBAAkB,KAAmB;AACnD,QAAM,MAAM,aAAa,GAAG;AAE5B,QAAM,SAAS,UAAU,IAAI,GAAG;AAEhC,MAAI,UAAU,OAAO,WAAW,YAAY;AAE1C,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,GAAG;AAAA,IACf;AACA;AAAA,EACF;AAEA,QAAM,UAAU,mBAAmB,GAAG,EACnC,KAAK,CAAC,UAAU;AACf,kBAAc,KAAK,EAAE,QAAQ,aAAa,MAAM,CAAC;AACjD,WAAO;AAAA,EACT,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,YAAQ,MAAM,iDAAiD,KAAK;AACpE,cAAU,IAAI,KAAK,EAAE,QAAQ,YAAY,MAAM,CAAC;AAChD,UAAM;AAAA,EACR,CAAC;AAEH,YAAU,IAAI,KAAK,EAAE,QAAQ,WAAW,QAAQ,CAAC;AACnD;AAWA,eAAsB,aACpB,KACA,SACoB;AACpB,QAAM,MAAM,aAAa,GAAG;AAG5B,MAAI,SAAS,YAAY;AACvB,qBAAiB,GAAG;AAAA,EACtB;AAEA,QAAM,QAAQ,UAAU,IAAI,GAAG;AAE/B,MAAI,OAAO;AACT,QAAI,MAAM,WAAW,aAAa;AAEhC,gBAAU,GAAG;AACb,aAAO,MAAM;AAAA,IACf;AACA,QAAI,MAAM,WAAW,WAAW;AAC9B,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AAGA,QAAM,UAAU,mBAAmB,GAAG,EACnC,KAAK,CAAC,UAAU;AACf,kBAAc,KAAK,EAAE,QAAQ,aAAa,MAAM,CAAC;AACjD,WAAO;AAAA,EACT,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,YAAQ,MAAM,8CAA8C,KAAK;AACjE,cAAU,IAAI,KAAK,EAAE,QAAQ,YAAY,MAAM,CAAC;AAChD,UAAM;AAAA,EACR,CAAC;AAEH,YAAU,IAAI,KAAK,EAAE,QAAQ,WAAW,QAAQ,CAAC;AACjD,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../modules/react/cache/index.ts","../../modules/react/cache/client-data-cache/index.ts"],"sourcesContent":["export * from \"./client-data-cache\";","import type { PageMetadata } from \"@router/index\";\n\n/**\n * Response data structure from server for route data requests\n */\nexport type RouteDataResponse = {\n /** Combined props (layout + page) - kept for backward compatibility */\n props?: Record<string, unknown>;\n /** Layout props (from layout.server.hook.ts) - only present when layout hooks were executed */\n layoutProps?: Record<string, unknown>;\n /** Page props (from page.server.hook.ts) - always present in data requests */\n pageProps?: Record<string, unknown>;\n metadata?: PageMetadata | null;\n theme?: string;\n redirect?: { destination: string; permanent?: boolean };\n notFound?: boolean;\n error?: boolean;\n message?: string;\n params?: Record<string, string>;\n /** Pathname after rewrite (for client-side route matching) */\n pathname?: string;\n};\n\ntype RouteData = {\n ok: boolean;\n status: number;\n json: RouteDataResponse;\n};\n\ntype CacheEntry =\n | { status: \"pending\"; promise: Promise<RouteData> }\n | { status: \"fulfilled\"; value: RouteData }\n | { status: \"rejected\"; error: any };\n\n// Use window to guarantee a single shared cache instance\n// across all bundles/modules\nconst CACHE_KEY = \"__FW_DATA_CACHE__\";\n\n// Maximum number of entries in the cache (LRU)\nconst MAX_CACHE_SIZE = 100;\n\ntype CacheStore = {\n data: Map<string, CacheEntry>;\n index: Map<string, Set<string>>; // pathBase -> Set of keys\n lru: string[]; // Ordered list: most recent at end, oldest at start\n};\n\nfunction getCacheStore(): CacheStore {\n if (typeof window !== \"undefined\") {\n if (!(window as any)[CACHE_KEY]) {\n (window as any)[CACHE_KEY] = {\n data: new Map<string, CacheEntry>(),\n index: new Map<string, Set<string>>(),\n lru: [],\n };\n }\n return (window as any)[CACHE_KEY];\n }\n // Fallback for SSR (though this shouldn't be used on the client)\n return {\n data: new Map<string, CacheEntry>(),\n index: new Map<string, Set<string>>(),\n lru: [],\n };\n}\n\nconst cacheStore = getCacheStore();\nconst dataCache = cacheStore.data;\nconst pathIndex = cacheStore.index;\nconst lru = cacheStore.lru;\n\n// Helper functions for cache management\n\n/**\n * Extract base path from a cache key (removes query params)\n */\nfunction extractPathBase(key: string): string {\n return key.split(\"?\")[0];\n}\n\n/**\n * Add key to path index\n */\nfunction addToIndex(key: string): void {\n const pathBase = extractPathBase(key);\n if (!pathIndex.has(pathBase)) {\n pathIndex.set(pathBase, new Set());\n }\n pathIndex.get(pathBase)!.add(key);\n}\n\n/**\n * Remove key from path index\n */\nfunction removeFromIndex(key: string): void {\n const pathBase = extractPathBase(key);\n const keys = pathIndex.get(pathBase);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) {\n pathIndex.delete(pathBase);\n }\n }\n}\n\n/**\n * Update LRU order - move key to end (most recent)\n */\nfunction updateLRU(key: string): void {\n const index = lru.indexOf(key);\n if (index !== -1) {\n lru.splice(index, 1);\n }\n lru.push(key);\n}\n\n/**\n * Remove oldest entries if cache exceeds MAX_CACHE_SIZE\n */\nfunction evictOldest(): void {\n while (lru.length >= MAX_CACHE_SIZE && lru.length > 0) {\n const oldestKey = lru.shift()!;\n dataCache.delete(oldestKey);\n removeFromIndex(oldestKey);\n }\n}\n\n/**\n * Set cache entry and maintain indexes\n */\nfunction setCacheEntry(key: string, entry: CacheEntry): void {\n const existingEntry = dataCache.get(key);\n const wasFulfilled = existingEntry?.status === \"fulfilled\";\n \n dataCache.set(key, entry);\n \n // Only track fulfilled entries in LRU and index (not pending/rejected)\n if (entry.status === \"fulfilled\") {\n // Add to index if it wasn't already fulfilled (new entry or transition from pending/rejected)\n if (!wasFulfilled) {\n addToIndex(key);\n }\n updateLRU(key);\n evictOldest();\n } else if (wasFulfilled) {\n // If entry was fulfilled and now isn't (transitioning to pending/rejected), remove from index\n removeFromIndex(key);\n }\n}\n\n/**\n * Delete cache entry and clean up indexes\n */\nfunction deleteCacheEntry(key: string): void {\n if (dataCache.has(key)) {\n dataCache.delete(key);\n removeFromIndex(key);\n const lruIndex = lru.indexOf(key);\n if (lruIndex !== -1) {\n lru.splice(lruIndex, 1);\n }\n }\n}\n\nfunction buildDataUrl(url: string): string {\n return url + (url.includes(\"?\") ? \"&\" : \"?\") + \"__fw_data=1\";\n}\n\nasync function fetchRouteDataOnce(\n url: string,\n skipLayoutHooks: boolean = true\n): Promise<RouteData> {\n const dataUrl = buildDataUrl(url);\n\n const headers: Record<string, string> = {\n \"x-fw-data\": \"1\",\n Accept: \"application/json\",\n };\n\n // Send header to skip layout hooks execution in SPA navigation\n // Only skip if skipLayoutHooks is true (normal SPA navigation)\n // If false (revalidate), don't send header to force execution of all hooks\n if (skipLayoutHooks) {\n headers[\"x-skip-layout-hooks\"] = \"true\";\n }\n\n const res = await fetch(dataUrl, { headers });\n\n let json: any = {};\n\n try {\n const text = await res.text();\n if (text) {\n json = JSON.parse(text);\n }\n } catch (parseError) {\n console.error(\n \"[client][cache] Failed to parse response as JSON:\",\n parseError\n );\n }\n\n const result: RouteData = {\n ok: res.ok,\n status: res.status,\n json,\n };\n\n return result;\n}\n\n/**\n * Revalidates route data by removing it from the cache.\n * The next time you navigate to this route, fresh data will be fetched from the server.\n * This is a client-side function and does not require a server-side revalidation.\n *\n * @param path - The route path to revalidate (e.g., '/posts/1' or '/posts/1?page=2')\n * If query params are not included, revalidates all variants of that route.\n *\n * @example\n * ```ts\n * // After saving something to the DB, revalidate the route\n * await saveToDatabase(data);\n * revalidatePath('/posts');\n * \n * // Revalidate a specific route with query params\n * revalidatePath('/posts?page=2');\n * ```\n */\nexport function revalidatePath(path: string, skipAutoRevalidate: boolean = false): void {\n // Normalize the base path (without query params)\n const normalizedPath = path.split(\"?\")[0];\n const hasQueryParams = path.includes(\"?\");\n \n // Get all keys for this path base from index (O(1) lookup)\n const keysForPath = pathIndex.get(normalizedPath);\n \n if (!keysForPath || keysForPath.size === 0) {\n return; // No entries to revalidate\n }\n \n // If the path includes specific query params, extract them\n let specificQueryParams: string | undefined;\n if (hasQueryParams) {\n const queryPart = path.split(\"?\")[1];\n // Sort query params for consistent comparison\n specificQueryParams = queryPart\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n }\n \n // Iterate only over keys for this path (much smaller set)\n const keysToDelete: string[] = [];\n for (const key of keysForPath) {\n // If specific query params were specified, check if they match\n if (hasQueryParams && specificQueryParams) {\n const [, keyQuery = \"\"] = key.split(\"?\");\n const keyQueryParams = keyQuery\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n \n if (keyQueryParams === specificQueryParams) {\n keysToDelete.push(key);\n }\n } else {\n // If no specific query params, revalidate all variants\n keysToDelete.push(key);\n }\n }\n \n // Delete matching entries\n keysToDelete.forEach((key) => {\n deleteCacheEntry(key);\n });\n \n // If the revalidated path matches the current route, automatically refresh data\n // UNLESS skipAutoRevalidate is true (to prevent recursive calls from revalidate())\n if (!skipAutoRevalidate && typeof window !== \"undefined\") {\n const currentPathname = window.location.pathname;\n const currentSearch = window.location.search;\n const matchesCurrentPath = normalizedPath === currentPathname;\n \n if (matchesCurrentPath) {\n if (hasQueryParams && specificQueryParams) {\n const currentQueryParams = currentSearch\n .replace(\"?\", \"\")\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n \n if (currentQueryParams === specificQueryParams) {\n revalidate().catch((err) => {\n console.error(\n \"[client][cache] Error revalidating current route:\",\n err\n );\n });\n }\n } else {\n revalidate().catch((err) => {\n console.error(\n \"[client][cache] Error revalidating current route:\",\n err\n );\n });\n }\n }\n }\n}\n\n/**\n * Revalidates and refreshes the current page data.\n * Similar to Next.js's `router.refresh()`.\n * \n * This function:\n * 1. Removes the current route from cache\n * 2. Fetches fresh data from the server\n * 3. Updates window.__FW_DATA__ with the new data\n * 4. Dispatches a 'fw-data-refresh' event for components to listen to\n * \n * @returns Promise that resolves with the fresh route data\n * \n * @example\n * ```ts\n * // Refresh current page data after a mutation\n * await revalidate();\n * ```\n */\n// Flag to prevent recursive calls to revalidate()\nlet isRevalidating = false;\n\nexport async function revalidate(): Promise<RouteData> {\n if (typeof window === \"undefined\") {\n throw new Error(\"revalidate() can only be called on the client\");\n }\n\n // Prevent multiple simultaneous revalidations\n if (isRevalidating) {\n // Wait for the current revalidation to complete\n const key = buildDataUrl(window.location.pathname + window.location.search);\n const entry = dataCache.get(key);\n if (entry && entry.status === \"pending\") {\n return entry.promise;\n }\n // If no pending entry, something went wrong, allow the call\n }\n\n isRevalidating = true;\n try {\n const pathname = window.location.pathname + window.location.search;\n \n // Revalidate the path (remove from cache)\n // Pass a flag to prevent revalidatePath from calling revalidate() again (recursive call)\n revalidatePath(pathname, true); // true = skip auto-revalidate\n \n // Fetch fresh data\n const freshData = await getRouteData(pathname, { revalidate: true });\n \n // Update window.__FW_DATA__ if it exists\n if ((window as any).__FW_DATA__ && freshData.ok && freshData.json) {\n const currentData = (window as any).__FW_DATA__;\n \n // Update preserved layout props if new ones were returned\n if (freshData.json.layoutProps !== undefined && freshData.json.layoutProps !== null) {\n (window as any).__FW_LAYOUT_PROPS__ = freshData.json.layoutProps;\n }\n \n // Combine layout props (new or preserved) + page props\n let combinedProps = currentData.props || {};\n if (freshData.json.layoutProps !== undefined && freshData.json.layoutProps !== null) {\n // Use new layout props\n combinedProps = {\n ...freshData.json.layoutProps,\n ...(freshData.json.pageProps ?? freshData.json.props ?? {}),\n };\n } else if (freshData.json.pageProps !== undefined) {\n // Use preserved layout props + new page props\n const preservedLayoutProps = (window as any).__FW_LAYOUT_PROPS__ || {};\n combinedProps = {\n ...preservedLayoutProps,\n ...freshData.json.pageProps,\n };\n } else if (freshData.json.props) {\n // Fallback to combined props\n combinedProps = freshData.json.props;\n }\n \n (window as any).__FW_DATA__ = {\n ...currentData,\n pathname: pathname.split(\"?\")[0],\n params: freshData.json.params || currentData.params || {},\n props: combinedProps,\n metadata: freshData.json.metadata ?? currentData.metadata ?? null,\n notFound: freshData.json.notFound ?? false,\n error: freshData.json.error ?? false,\n };\n \n // Dispatch event for components to listen to\n window.dispatchEvent(new CustomEvent(\"fw-data-refresh\", {\n detail: { data: freshData },\n }));\n }\n \n return freshData;\n } finally {\n isRevalidating = false;\n }\n}\n\n/**\n * @deprecated Use `revalidatePath()` instead. This function is kept for backwards compatibility.\n */\nexport function revalidateRouteData(url: string): void {\n revalidatePath(url);\n}\n\nexport function prefetchRouteData(url: string): void {\n const key = buildDataUrl(url);\n\n const cached = dataCache.get(key);\n\n if (cached && cached.status !== \"rejected\") {\n // Update LRU if it exists and is fulfilled\n if (cached.status === \"fulfilled\") {\n updateLRU(key);\n }\n return;\n }\n\n // Prefetch uses skipLayoutHooks: true (normal navigation behavior)\n const promise = fetchRouteDataOnce(url, true)\n .then((value) => {\n setCacheEntry(key, { status: \"fulfilled\", value });\n return value;\n })\n .catch((error) => {\n console.error(\"[client][cache] Error prefetching route data:\", error);\n dataCache.set(key, { status: \"rejected\", error });\n throw error;\n });\n\n dataCache.set(key, { status: \"pending\", promise });\n}\n\nexport type GetRouteDataOptions = {\n /**\n * If true, forces revalidation of route data,\n * ignoring the cache and fetching fresh data from the server.\n * Similar to Next.js's `router.refresh()` behavior.\n */\n revalidate?: boolean;\n};\n\nexport async function getRouteData(\n url: string,\n options?: GetRouteDataOptions\n): Promise<RouteData> {\n const key = buildDataUrl(url);\n\n // If revalidation is requested, remove the entry from cache\n // This ensures we don't reuse pending or fulfilled entries\n if (options?.revalidate) {\n deleteCacheEntry(key);\n }\n\n const entry = dataCache.get(key);\n\n if (entry && !options?.revalidate) {\n // Only use cached entry if not revalidating\n if (entry.status === \"fulfilled\") {\n // Update LRU: mark as recently used\n updateLRU(key);\n return entry.value;\n }\n if (entry.status === \"pending\") {\n // Return existing pending promise to avoid duplicate requests\n return entry.promise;\n }\n }\n\n // No entry in cache (or revalidating), fetch it\n // skipLayoutHooks: true for normal SPA navigation, false when revalidating\n const skipLayoutHooks = !options?.revalidate;\n \n // Check again if an entry was added while we were processing (race condition)\n const currentEntry = dataCache.get(key);\n if (currentEntry && !options?.revalidate) {\n if (currentEntry.status === \"fulfilled\") {\n updateLRU(key);\n return currentEntry.value;\n }\n if (currentEntry.status === \"pending\") {\n return currentEntry.promise;\n }\n }\n \n // Create a new promise for this fetch\n const promise = fetchRouteDataOnce(url, skipLayoutHooks)\n .then((value) => {\n // Only set cache entry if this is still the current fetch for this key\n // This prevents race conditions where multiple revalidations happen simultaneously\n const entryAfterFetch = dataCache.get(key);\n if (!entryAfterFetch || entryAfterFetch.status === \"pending\") {\n setCacheEntry(key, { status: \"fulfilled\", value });\n }\n return value;\n })\n .catch((error) => {\n console.error(\"[client][cache] Error fetching route data:\", error);\n const entryAfterFetch = dataCache.get(key);\n if (!entryAfterFetch || entryAfterFetch.status === \"pending\") {\n dataCache.set(key, { status: \"rejected\", error });\n }\n throw error;\n });\n\n // Set pending entry - if revalidating, we already deleted it, so this is safe\n dataCache.set(key, { status: \"pending\", promise });\n \n return promise;\n}\n"],"mappings":"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","names":[]}
|
package/dist/react/cache.d.mts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
|
+
import { P as PageMetadata } from '../index.types-Duhjyfit.mjs';
|
|
2
|
+
import 'express';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Response data structure from server for route data requests
|
|
6
|
+
*/
|
|
7
|
+
type RouteDataResponse = {
|
|
8
|
+
/** Combined props (layout + page) - kept for backward compatibility */
|
|
9
|
+
props?: Record<string, unknown>;
|
|
10
|
+
/** Layout props (from layout.server.hook.ts) - only present when layout hooks were executed */
|
|
11
|
+
layoutProps?: Record<string, unknown>;
|
|
12
|
+
/** Page props (from page.server.hook.ts) - always present in data requests */
|
|
13
|
+
pageProps?: Record<string, unknown>;
|
|
14
|
+
metadata?: PageMetadata | null;
|
|
15
|
+
theme?: string;
|
|
16
|
+
redirect?: {
|
|
17
|
+
destination: string;
|
|
18
|
+
permanent?: boolean;
|
|
19
|
+
};
|
|
20
|
+
notFound?: boolean;
|
|
21
|
+
error?: boolean;
|
|
22
|
+
message?: string;
|
|
23
|
+
params?: Record<string, string>;
|
|
24
|
+
/** Pathname after rewrite (for client-side route matching) */
|
|
25
|
+
pathname?: string;
|
|
26
|
+
};
|
|
1
27
|
type RouteData = {
|
|
2
28
|
ok: boolean;
|
|
3
29
|
status: number;
|
|
4
|
-
json:
|
|
30
|
+
json: RouteDataResponse;
|
|
5
31
|
};
|
|
6
32
|
/**
|
|
7
33
|
* Revalidates route data by removing it from the cache.
|
|
@@ -21,25 +47,7 @@ type RouteData = {
|
|
|
21
47
|
* revalidatePath('/posts?page=2');
|
|
22
48
|
* ```
|
|
23
49
|
*/
|
|
24
|
-
declare function revalidatePath(path: string): void;
|
|
25
|
-
/**
|
|
26
|
-
* Revalidates and refreshes the current page data.
|
|
27
|
-
* Similar to Next.js's `router.refresh()`.
|
|
28
|
-
*
|
|
29
|
-
* This function:
|
|
30
|
-
* 1. Removes the current route from cache
|
|
31
|
-
* 2. Fetches fresh data from the server
|
|
32
|
-
* 3. Updates window.__FW_DATA__ with the new data
|
|
33
|
-
* 4. Dispatches a 'fw-data-refresh' event for components to listen to
|
|
34
|
-
*
|
|
35
|
-
* @returns Promise that resolves with the fresh route data
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* // Refresh current page data after a mutation
|
|
40
|
-
* await revalidate();
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
50
|
+
declare function revalidatePath(path: string, skipAutoRevalidate?: boolean): void;
|
|
43
51
|
declare function revalidate(): Promise<RouteData>;
|
|
44
52
|
/**
|
|
45
53
|
* @deprecated Use `revalidatePath()` instead. This function is kept for backwards compatibility.
|
|
@@ -56,4 +64,4 @@ type GetRouteDataOptions = {
|
|
|
56
64
|
};
|
|
57
65
|
declare function getRouteData(url: string, options?: GetRouteDataOptions): Promise<RouteData>;
|
|
58
66
|
|
|
59
|
-
export { type GetRouteDataOptions, getRouteData, prefetchRouteData, revalidate, revalidatePath, revalidateRouteData };
|
|
67
|
+
export { type GetRouteDataOptions, type RouteDataResponse, getRouteData, prefetchRouteData, revalidate, revalidatePath, revalidateRouteData };
|
package/dist/react/cache.d.ts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
|
+
import { P as PageMetadata } from '../index.types-Duhjyfit.js';
|
|
2
|
+
import 'express';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Response data structure from server for route data requests
|
|
6
|
+
*/
|
|
7
|
+
type RouteDataResponse = {
|
|
8
|
+
/** Combined props (layout + page) - kept for backward compatibility */
|
|
9
|
+
props?: Record<string, unknown>;
|
|
10
|
+
/** Layout props (from layout.server.hook.ts) - only present when layout hooks were executed */
|
|
11
|
+
layoutProps?: Record<string, unknown>;
|
|
12
|
+
/** Page props (from page.server.hook.ts) - always present in data requests */
|
|
13
|
+
pageProps?: Record<string, unknown>;
|
|
14
|
+
metadata?: PageMetadata | null;
|
|
15
|
+
theme?: string;
|
|
16
|
+
redirect?: {
|
|
17
|
+
destination: string;
|
|
18
|
+
permanent?: boolean;
|
|
19
|
+
};
|
|
20
|
+
notFound?: boolean;
|
|
21
|
+
error?: boolean;
|
|
22
|
+
message?: string;
|
|
23
|
+
params?: Record<string, string>;
|
|
24
|
+
/** Pathname after rewrite (for client-side route matching) */
|
|
25
|
+
pathname?: string;
|
|
26
|
+
};
|
|
1
27
|
type RouteData = {
|
|
2
28
|
ok: boolean;
|
|
3
29
|
status: number;
|
|
4
|
-
json:
|
|
30
|
+
json: RouteDataResponse;
|
|
5
31
|
};
|
|
6
32
|
/**
|
|
7
33
|
* Revalidates route data by removing it from the cache.
|
|
@@ -21,25 +47,7 @@ type RouteData = {
|
|
|
21
47
|
* revalidatePath('/posts?page=2');
|
|
22
48
|
* ```
|
|
23
49
|
*/
|
|
24
|
-
declare function revalidatePath(path: string): void;
|
|
25
|
-
/**
|
|
26
|
-
* Revalidates and refreshes the current page data.
|
|
27
|
-
* Similar to Next.js's `router.refresh()`.
|
|
28
|
-
*
|
|
29
|
-
* This function:
|
|
30
|
-
* 1. Removes the current route from cache
|
|
31
|
-
* 2. Fetches fresh data from the server
|
|
32
|
-
* 3. Updates window.__FW_DATA__ with the new data
|
|
33
|
-
* 4. Dispatches a 'fw-data-refresh' event for components to listen to
|
|
34
|
-
*
|
|
35
|
-
* @returns Promise that resolves with the fresh route data
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* // Refresh current page data after a mutation
|
|
40
|
-
* await revalidate();
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
50
|
+
declare function revalidatePath(path: string, skipAutoRevalidate?: boolean): void;
|
|
43
51
|
declare function revalidate(): Promise<RouteData>;
|
|
44
52
|
/**
|
|
45
53
|
* @deprecated Use `revalidatePath()` instead. This function is kept for backwards compatibility.
|
|
@@ -56,4 +64,4 @@ type GetRouteDataOptions = {
|
|
|
56
64
|
};
|
|
57
65
|
declare function getRouteData(url: string, options?: GetRouteDataOptions): Promise<RouteData>;
|
|
58
66
|
|
|
59
|
-
export { type GetRouteDataOptions, getRouteData, prefetchRouteData, revalidate, revalidatePath, revalidateRouteData };
|
|
67
|
+
export { type GetRouteDataOptions, type RouteDataResponse, getRouteData, prefetchRouteData, revalidate, revalidatePath, revalidateRouteData };
|
|
@@ -83,14 +83,16 @@ function deleteCacheEntry(key) {
|
|
|
83
83
|
function buildDataUrl(url) {
|
|
84
84
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
85
85
|
}
|
|
86
|
-
async function fetchRouteDataOnce(url) {
|
|
86
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
87
87
|
const dataUrl = buildDataUrl(url);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
const headers = {
|
|
89
|
+
"x-fw-data": "1",
|
|
90
|
+
Accept: "application/json"
|
|
91
|
+
};
|
|
92
|
+
if (skipLayoutHooks) {
|
|
93
|
+
headers["x-skip-layout-hooks"] = "true";
|
|
94
|
+
}
|
|
95
|
+
const res = await fetch(dataUrl, { headers });
|
|
94
96
|
let json = {};
|
|
95
97
|
try {
|
|
96
98
|
const text = await res.text();
|
|
@@ -110,7 +112,7 @@ async function fetchRouteDataOnce(url) {
|
|
|
110
112
|
};
|
|
111
113
|
return result;
|
|
112
114
|
}
|
|
113
|
-
function revalidatePath(path) {
|
|
115
|
+
function revalidatePath(path, skipAutoRevalidate = false) {
|
|
114
116
|
const normalizedPath = path.split("?")[0];
|
|
115
117
|
const hasQueryParams = path.includes("?");
|
|
116
118
|
const keysForPath = pathIndex.get(normalizedPath);
|
|
@@ -137,7 +139,7 @@ function revalidatePath(path) {
|
|
|
137
139
|
keysToDelete.forEach((key) => {
|
|
138
140
|
deleteCacheEntry(key);
|
|
139
141
|
});
|
|
140
|
-
if (typeof window !== "undefined") {
|
|
142
|
+
if (!skipAutoRevalidate && typeof window !== "undefined") {
|
|
141
143
|
const currentPathname = window.location.pathname;
|
|
142
144
|
const currentSearch = window.location.search;
|
|
143
145
|
const matchesCurrentPath = normalizedPath === currentPathname;
|
|
@@ -163,29 +165,60 @@ function revalidatePath(path) {
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
}
|
|
168
|
+
var isRevalidating = false;
|
|
166
169
|
async function revalidate() {
|
|
167
170
|
if (typeof window === "undefined") {
|
|
168
171
|
throw new Error("revalidate() can only be called on the client");
|
|
169
172
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
173
|
+
if (isRevalidating) {
|
|
174
|
+
const key = buildDataUrl(window.location.pathname + window.location.search);
|
|
175
|
+
const entry = dataCache.get(key);
|
|
176
|
+
if (entry && entry.status === "pending") {
|
|
177
|
+
return entry.promise;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
isRevalidating = true;
|
|
181
|
+
try {
|
|
182
|
+
const pathname = window.location.pathname + window.location.search;
|
|
183
|
+
revalidatePath(pathname, true);
|
|
184
|
+
const freshData = await getRouteData(pathname, { revalidate: true });
|
|
185
|
+
if (window.__FW_DATA__ && freshData.ok && freshData.json) {
|
|
186
|
+
const currentData = window.__FW_DATA__;
|
|
187
|
+
if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
|
|
188
|
+
window.__FW_LAYOUT_PROPS__ = freshData.json.layoutProps;
|
|
189
|
+
}
|
|
190
|
+
let combinedProps = currentData.props || {};
|
|
191
|
+
if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
|
|
192
|
+
combinedProps = {
|
|
193
|
+
...freshData.json.layoutProps,
|
|
194
|
+
...freshData.json.pageProps ?? freshData.json.props ?? {}
|
|
195
|
+
};
|
|
196
|
+
} else if (freshData.json.pageProps !== void 0) {
|
|
197
|
+
const preservedLayoutProps = window.__FW_LAYOUT_PROPS__ || {};
|
|
198
|
+
combinedProps = {
|
|
199
|
+
...preservedLayoutProps,
|
|
200
|
+
...freshData.json.pageProps
|
|
201
|
+
};
|
|
202
|
+
} else if (freshData.json.props) {
|
|
203
|
+
combinedProps = freshData.json.props;
|
|
204
|
+
}
|
|
205
|
+
window.__FW_DATA__ = {
|
|
206
|
+
...currentData,
|
|
207
|
+
pathname: pathname.split("?")[0],
|
|
208
|
+
params: freshData.json.params || currentData.params || {},
|
|
209
|
+
props: combinedProps,
|
|
210
|
+
metadata: freshData.json.metadata ?? currentData.metadata ?? null,
|
|
211
|
+
notFound: freshData.json.notFound ?? false,
|
|
212
|
+
error: freshData.json.error ?? false
|
|
213
|
+
};
|
|
214
|
+
window.dispatchEvent(new CustomEvent("fw-data-refresh", {
|
|
215
|
+
detail: { data: freshData }
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
return freshData;
|
|
219
|
+
} finally {
|
|
220
|
+
isRevalidating = false;
|
|
221
|
+
}
|
|
189
222
|
}
|
|
190
223
|
function revalidateRouteData(url) {
|
|
191
224
|
revalidatePath(url);
|
|
@@ -199,7 +232,7 @@ function prefetchRouteData(url) {
|
|
|
199
232
|
}
|
|
200
233
|
return;
|
|
201
234
|
}
|
|
202
|
-
const promise = fetchRouteDataOnce(url).then((value) => {
|
|
235
|
+
const promise = fetchRouteDataOnce(url, true).then((value) => {
|
|
203
236
|
setCacheEntry(key, { status: "fulfilled", value });
|
|
204
237
|
return value;
|
|
205
238
|
}).catch((error) => {
|
|
@@ -215,7 +248,7 @@ async function getRouteData(url, options) {
|
|
|
215
248
|
deleteCacheEntry(key);
|
|
216
249
|
}
|
|
217
250
|
const entry = dataCache.get(key);
|
|
218
|
-
if (entry) {
|
|
251
|
+
if (entry && !options?.revalidate) {
|
|
219
252
|
if (entry.status === "fulfilled") {
|
|
220
253
|
updateLRU(key);
|
|
221
254
|
return entry.value;
|
|
@@ -224,12 +257,29 @@ async function getRouteData(url, options) {
|
|
|
224
257
|
return entry.promise;
|
|
225
258
|
}
|
|
226
259
|
}
|
|
227
|
-
const
|
|
228
|
-
|
|
260
|
+
const skipLayoutHooks = !options?.revalidate;
|
|
261
|
+
const currentEntry = dataCache.get(key);
|
|
262
|
+
if (currentEntry && !options?.revalidate) {
|
|
263
|
+
if (currentEntry.status === "fulfilled") {
|
|
264
|
+
updateLRU(key);
|
|
265
|
+
return currentEntry.value;
|
|
266
|
+
}
|
|
267
|
+
if (currentEntry.status === "pending") {
|
|
268
|
+
return currentEntry.promise;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
|
|
272
|
+
const entryAfterFetch = dataCache.get(key);
|
|
273
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
274
|
+
setCacheEntry(key, { status: "fulfilled", value });
|
|
275
|
+
}
|
|
229
276
|
return value;
|
|
230
277
|
}).catch((error) => {
|
|
231
278
|
console.error("[client][cache] Error fetching route data:", error);
|
|
232
|
-
dataCache.
|
|
279
|
+
const entryAfterFetch = dataCache.get(key);
|
|
280
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
281
|
+
dataCache.set(key, { status: "rejected", error });
|
|
282
|
+
}
|
|
233
283
|
throw error;
|
|
234
284
|
});
|
|
235
285
|
dataCache.set(key, { status: "pending", promise });
|
|
@@ -242,4 +292,4 @@ export {
|
|
|
242
292
|
revalidatePath,
|
|
243
293
|
revalidateRouteData
|
|
244
294
|
};
|
|
245
|
-
//# sourceMappingURL=cache.
|
|
295
|
+
//# sourceMappingURL=cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../modules/react/cache/client-data-cache/index.ts"],"sourcesContent":["import type { PageMetadata } from \"@router/index\";\n\n/**\n * Response data structure from server for route data requests\n */\nexport type RouteDataResponse = {\n /** Combined props (layout + page) - kept for backward compatibility */\n props?: Record<string, unknown>;\n /** Layout props (from layout.server.hook.ts) - only present when layout hooks were executed */\n layoutProps?: Record<string, unknown>;\n /** Page props (from page.server.hook.ts) - always present in data requests */\n pageProps?: Record<string, unknown>;\n metadata?: PageMetadata | null;\n theme?: string;\n redirect?: { destination: string; permanent?: boolean };\n notFound?: boolean;\n error?: boolean;\n message?: string;\n params?: Record<string, string>;\n /** Pathname after rewrite (for client-side route matching) */\n pathname?: string;\n};\n\ntype RouteData = {\n ok: boolean;\n status: number;\n json: RouteDataResponse;\n};\n\ntype CacheEntry =\n | { status: \"pending\"; promise: Promise<RouteData> }\n | { status: \"fulfilled\"; value: RouteData }\n | { status: \"rejected\"; error: any };\n\n// Use window to guarantee a single shared cache instance\n// across all bundles/modules\nconst CACHE_KEY = \"__FW_DATA_CACHE__\";\n\n// Maximum number of entries in the cache (LRU)\nconst MAX_CACHE_SIZE = 100;\n\ntype CacheStore = {\n data: Map<string, CacheEntry>;\n index: Map<string, Set<string>>; // pathBase -> Set of keys\n lru: string[]; // Ordered list: most recent at end, oldest at start\n};\n\nfunction getCacheStore(): CacheStore {\n if (typeof window !== \"undefined\") {\n if (!(window as any)[CACHE_KEY]) {\n (window as any)[CACHE_KEY] = {\n data: new Map<string, CacheEntry>(),\n index: new Map<string, Set<string>>(),\n lru: [],\n };\n }\n return (window as any)[CACHE_KEY];\n }\n // Fallback for SSR (though this shouldn't be used on the client)\n return {\n data: new Map<string, CacheEntry>(),\n index: new Map<string, Set<string>>(),\n lru: [],\n };\n}\n\nconst cacheStore = getCacheStore();\nconst dataCache = cacheStore.data;\nconst pathIndex = cacheStore.index;\nconst lru = cacheStore.lru;\n\n// Helper functions for cache management\n\n/**\n * Extract base path from a cache key (removes query params)\n */\nfunction extractPathBase(key: string): string {\n return key.split(\"?\")[0];\n}\n\n/**\n * Add key to path index\n */\nfunction addToIndex(key: string): void {\n const pathBase = extractPathBase(key);\n if (!pathIndex.has(pathBase)) {\n pathIndex.set(pathBase, new Set());\n }\n pathIndex.get(pathBase)!.add(key);\n}\n\n/**\n * Remove key from path index\n */\nfunction removeFromIndex(key: string): void {\n const pathBase = extractPathBase(key);\n const keys = pathIndex.get(pathBase);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) {\n pathIndex.delete(pathBase);\n }\n }\n}\n\n/**\n * Update LRU order - move key to end (most recent)\n */\nfunction updateLRU(key: string): void {\n const index = lru.indexOf(key);\n if (index !== -1) {\n lru.splice(index, 1);\n }\n lru.push(key);\n}\n\n/**\n * Remove oldest entries if cache exceeds MAX_CACHE_SIZE\n */\nfunction evictOldest(): void {\n while (lru.length >= MAX_CACHE_SIZE && lru.length > 0) {\n const oldestKey = lru.shift()!;\n dataCache.delete(oldestKey);\n removeFromIndex(oldestKey);\n }\n}\n\n/**\n * Set cache entry and maintain indexes\n */\nfunction setCacheEntry(key: string, entry: CacheEntry): void {\n const existingEntry = dataCache.get(key);\n const wasFulfilled = existingEntry?.status === \"fulfilled\";\n \n dataCache.set(key, entry);\n \n // Only track fulfilled entries in LRU and index (not pending/rejected)\n if (entry.status === \"fulfilled\") {\n // Add to index if it wasn't already fulfilled (new entry or transition from pending/rejected)\n if (!wasFulfilled) {\n addToIndex(key);\n }\n updateLRU(key);\n evictOldest();\n } else if (wasFulfilled) {\n // If entry was fulfilled and now isn't (transitioning to pending/rejected), remove from index\n removeFromIndex(key);\n }\n}\n\n/**\n * Delete cache entry and clean up indexes\n */\nfunction deleteCacheEntry(key: string): void {\n if (dataCache.has(key)) {\n dataCache.delete(key);\n removeFromIndex(key);\n const lruIndex = lru.indexOf(key);\n if (lruIndex !== -1) {\n lru.splice(lruIndex, 1);\n }\n }\n}\n\nfunction buildDataUrl(url: string): string {\n return url + (url.includes(\"?\") ? \"&\" : \"?\") + \"__fw_data=1\";\n}\n\nasync function fetchRouteDataOnce(\n url: string,\n skipLayoutHooks: boolean = true\n): Promise<RouteData> {\n const dataUrl = buildDataUrl(url);\n\n const headers: Record<string, string> = {\n \"x-fw-data\": \"1\",\n Accept: \"application/json\",\n };\n\n // Send header to skip layout hooks execution in SPA navigation\n // Only skip if skipLayoutHooks is true (normal SPA navigation)\n // If false (revalidate), don't send header to force execution of all hooks\n if (skipLayoutHooks) {\n headers[\"x-skip-layout-hooks\"] = \"true\";\n }\n\n const res = await fetch(dataUrl, { headers });\n\n let json: any = {};\n\n try {\n const text = await res.text();\n if (text) {\n json = JSON.parse(text);\n }\n } catch (parseError) {\n console.error(\n \"[client][cache] Failed to parse response as JSON:\",\n parseError\n );\n }\n\n const result: RouteData = {\n ok: res.ok,\n status: res.status,\n json,\n };\n\n return result;\n}\n\n/**\n * Revalidates route data by removing it from the cache.\n * The next time you navigate to this route, fresh data will be fetched from the server.\n * This is a client-side function and does not require a server-side revalidation.\n *\n * @param path - The route path to revalidate (e.g., '/posts/1' or '/posts/1?page=2')\n * If query params are not included, revalidates all variants of that route.\n *\n * @example\n * ```ts\n * // After saving something to the DB, revalidate the route\n * await saveToDatabase(data);\n * revalidatePath('/posts');\n * \n * // Revalidate a specific route with query params\n * revalidatePath('/posts?page=2');\n * ```\n */\nexport function revalidatePath(path: string, skipAutoRevalidate: boolean = false): void {\n // Normalize the base path (without query params)\n const normalizedPath = path.split(\"?\")[0];\n const hasQueryParams = path.includes(\"?\");\n \n // Get all keys for this path base from index (O(1) lookup)\n const keysForPath = pathIndex.get(normalizedPath);\n \n if (!keysForPath || keysForPath.size === 0) {\n return; // No entries to revalidate\n }\n \n // If the path includes specific query params, extract them\n let specificQueryParams: string | undefined;\n if (hasQueryParams) {\n const queryPart = path.split(\"?\")[1];\n // Sort query params for consistent comparison\n specificQueryParams = queryPart\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n }\n \n // Iterate only over keys for this path (much smaller set)\n const keysToDelete: string[] = [];\n for (const key of keysForPath) {\n // If specific query params were specified, check if they match\n if (hasQueryParams && specificQueryParams) {\n const [, keyQuery = \"\"] = key.split(\"?\");\n const keyQueryParams = keyQuery\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n \n if (keyQueryParams === specificQueryParams) {\n keysToDelete.push(key);\n }\n } else {\n // If no specific query params, revalidate all variants\n keysToDelete.push(key);\n }\n }\n \n // Delete matching entries\n keysToDelete.forEach((key) => {\n deleteCacheEntry(key);\n });\n \n // If the revalidated path matches the current route, automatically refresh data\n // UNLESS skipAutoRevalidate is true (to prevent recursive calls from revalidate())\n if (!skipAutoRevalidate && typeof window !== \"undefined\") {\n const currentPathname = window.location.pathname;\n const currentSearch = window.location.search;\n const matchesCurrentPath = normalizedPath === currentPathname;\n \n if (matchesCurrentPath) {\n if (hasQueryParams && specificQueryParams) {\n const currentQueryParams = currentSearch\n .replace(\"?\", \"\")\n .split(\"&\")\n .filter((p) => !p.startsWith(\"__fw_data=\"))\n .sort()\n .join(\"&\");\n \n if (currentQueryParams === specificQueryParams) {\n revalidate().catch((err) => {\n console.error(\n \"[client][cache] Error revalidating current route:\",\n err\n );\n });\n }\n } else {\n revalidate().catch((err) => {\n console.error(\n \"[client][cache] Error revalidating current route:\",\n err\n );\n });\n }\n }\n }\n}\n\n/**\n * Revalidates and refreshes the current page data.\n * Similar to Next.js's `router.refresh()`.\n * \n * This function:\n * 1. Removes the current route from cache\n * 2. Fetches fresh data from the server\n * 3. Updates window.__FW_DATA__ with the new data\n * 4. Dispatches a 'fw-data-refresh' event for components to listen to\n * \n * @returns Promise that resolves with the fresh route data\n * \n * @example\n * ```ts\n * // Refresh current page data after a mutation\n * await revalidate();\n * ```\n */\n// Flag to prevent recursive calls to revalidate()\nlet isRevalidating = false;\n\nexport async function revalidate(): Promise<RouteData> {\n if (typeof window === \"undefined\") {\n throw new Error(\"revalidate() can only be called on the client\");\n }\n\n // Prevent multiple simultaneous revalidations\n if (isRevalidating) {\n // Wait for the current revalidation to complete\n const key = buildDataUrl(window.location.pathname + window.location.search);\n const entry = dataCache.get(key);\n if (entry && entry.status === \"pending\") {\n return entry.promise;\n }\n // If no pending entry, something went wrong, allow the call\n }\n\n isRevalidating = true;\n try {\n const pathname = window.location.pathname + window.location.search;\n \n // Revalidate the path (remove from cache)\n // Pass a flag to prevent revalidatePath from calling revalidate() again (recursive call)\n revalidatePath(pathname, true); // true = skip auto-revalidate\n \n // Fetch fresh data\n const freshData = await getRouteData(pathname, { revalidate: true });\n \n // Update window.__FW_DATA__ if it exists\n if ((window as any).__FW_DATA__ && freshData.ok && freshData.json) {\n const currentData = (window as any).__FW_DATA__;\n \n // Update preserved layout props if new ones were returned\n if (freshData.json.layoutProps !== undefined && freshData.json.layoutProps !== null) {\n (window as any).__FW_LAYOUT_PROPS__ = freshData.json.layoutProps;\n }\n \n // Combine layout props (new or preserved) + page props\n let combinedProps = currentData.props || {};\n if (freshData.json.layoutProps !== undefined && freshData.json.layoutProps !== null) {\n // Use new layout props\n combinedProps = {\n ...freshData.json.layoutProps,\n ...(freshData.json.pageProps ?? freshData.json.props ?? {}),\n };\n } else if (freshData.json.pageProps !== undefined) {\n // Use preserved layout props + new page props\n const preservedLayoutProps = (window as any).__FW_LAYOUT_PROPS__ || {};\n combinedProps = {\n ...preservedLayoutProps,\n ...freshData.json.pageProps,\n };\n } else if (freshData.json.props) {\n // Fallback to combined props\n combinedProps = freshData.json.props;\n }\n \n (window as any).__FW_DATA__ = {\n ...currentData,\n pathname: pathname.split(\"?\")[0],\n params: freshData.json.params || currentData.params || {},\n props: combinedProps,\n metadata: freshData.json.metadata ?? currentData.metadata ?? null,\n notFound: freshData.json.notFound ?? false,\n error: freshData.json.error ?? false,\n };\n \n // Dispatch event for components to listen to\n window.dispatchEvent(new CustomEvent(\"fw-data-refresh\", {\n detail: { data: freshData },\n }));\n }\n \n return freshData;\n } finally {\n isRevalidating = false;\n }\n}\n\n/**\n * @deprecated Use `revalidatePath()` instead. This function is kept for backwards compatibility.\n */\nexport function revalidateRouteData(url: string): void {\n revalidatePath(url);\n}\n\nexport function prefetchRouteData(url: string): void {\n const key = buildDataUrl(url);\n\n const cached = dataCache.get(key);\n\n if (cached && cached.status !== \"rejected\") {\n // Update LRU if it exists and is fulfilled\n if (cached.status === \"fulfilled\") {\n updateLRU(key);\n }\n return;\n }\n\n // Prefetch uses skipLayoutHooks: true (normal navigation behavior)\n const promise = fetchRouteDataOnce(url, true)\n .then((value) => {\n setCacheEntry(key, { status: \"fulfilled\", value });\n return value;\n })\n .catch((error) => {\n console.error(\"[client][cache] Error prefetching route data:\", error);\n dataCache.set(key, { status: \"rejected\", error });\n throw error;\n });\n\n dataCache.set(key, { status: \"pending\", promise });\n}\n\nexport type GetRouteDataOptions = {\n /**\n * If true, forces revalidation of route data,\n * ignoring the cache and fetching fresh data from the server.\n * Similar to Next.js's `router.refresh()` behavior.\n */\n revalidate?: boolean;\n};\n\nexport async function getRouteData(\n url: string,\n options?: GetRouteDataOptions\n): Promise<RouteData> {\n const key = buildDataUrl(url);\n\n // If revalidation is requested, remove the entry from cache\n // This ensures we don't reuse pending or fulfilled entries\n if (options?.revalidate) {\n deleteCacheEntry(key);\n }\n\n const entry = dataCache.get(key);\n\n if (entry && !options?.revalidate) {\n // Only use cached entry if not revalidating\n if (entry.status === \"fulfilled\") {\n // Update LRU: mark as recently used\n updateLRU(key);\n return entry.value;\n }\n if (entry.status === \"pending\") {\n // Return existing pending promise to avoid duplicate requests\n return entry.promise;\n }\n }\n\n // No entry in cache (or revalidating), fetch it\n // skipLayoutHooks: true for normal SPA navigation, false when revalidating\n const skipLayoutHooks = !options?.revalidate;\n \n // Check again if an entry was added while we were processing (race condition)\n const currentEntry = dataCache.get(key);\n if (currentEntry && !options?.revalidate) {\n if (currentEntry.status === \"fulfilled\") {\n updateLRU(key);\n return currentEntry.value;\n }\n if (currentEntry.status === \"pending\") {\n return currentEntry.promise;\n }\n }\n \n // Create a new promise for this fetch\n const promise = fetchRouteDataOnce(url, skipLayoutHooks)\n .then((value) => {\n // Only set cache entry if this is still the current fetch for this key\n // This prevents race conditions where multiple revalidations happen simultaneously\n const entryAfterFetch = dataCache.get(key);\n if (!entryAfterFetch || entryAfterFetch.status === \"pending\") {\n setCacheEntry(key, { status: \"fulfilled\", value });\n }\n return value;\n })\n .catch((error) => {\n console.error(\"[client][cache] Error fetching route data:\", error);\n const entryAfterFetch = dataCache.get(key);\n if (!entryAfterFetch || entryAfterFetch.status === \"pending\") {\n dataCache.set(key, { status: \"rejected\", error });\n }\n throw error;\n });\n\n // Set pending entry - if revalidating, we already deleted it, so this is safe\n dataCache.set(key, { status: \"pending\", promise });\n \n return promise;\n}\n"],"mappings":"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","names":[]}
|