Support caching for streamed responses

Aperta
#7 3 commenti 1 reazione 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
30/100
Tipo di issue
Bug
Chiarezza
Da chiarire
Stato di attività
Ferma
Stack tecnologico
react, typescript

Direzione di ricerca

Inizia con src/index.tsx, righe 116-140, e riproduci il percorso di una Promise completata dall’esempio collegato StackBlitz/routes/index.tsx. Traccia il confronto tra deferredServerData, serverData, freshData e adapter; completato significa che la cache distingue i dati memorizzati nella cache con Promise completata dai dati che richiedono la rivalidazione, con un comportamento verificato.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Stackblitz

When using defer to stream a response from a loader that returned a promise which later used in a boundary, we cannot properly serialize the promise to store say in localStorage or IndexedDB (JSON.stringify will result in an empty object {}). The only way to store this type of data is directly in memory, so on the next navigation, clientLoader will return fulfilled promise with the data, rather than calling the server `loader'.

let cache: SerializeFrom<typeof loader>;
export const clientLoader = defineClientLoader(async ({ serverLoader }) => {
    if (!cache) {
        cache = await serverLoader<typeof loader>();
    }

    return cache;
});

clientLoader.hydrate = true;

By using remix-client-cache, we can create an adapter for when we need to cache streaming responses, rather than relying on a global setting using, for example, localStorage.

import { lru } from 'tiny-lru';
import { type CacheAdapter, createCacheAdapter } from 'remix-client-cache';

const cache = lru(100);

class LRUAdapter implements CacheAdapter {
	async getItem(key: string) {
		return cache.get(key);
	}

	async setItem(key: string, value: any) {
		return cache.set(key, value);
	}

	async removeItem(key: string) {
		return cache.delete(key);
	}
}

export const { adapter: lruAdapter } = createCacheAdapter(() => new LRUAdapter());

routes/index.tsx:

import { defer } from '@remix-run/node';
import { Await, ClientLoaderFunctionArgs, Link } from '@remix-run/react';
import { Suspense } from 'react';
import { cacheClientLoader, useCachedLoaderData } from 'remix-client-cache';
import { lruAdapter } from '~/client-cache-adapter';

async function getQueryData() {
	await new Promise((resolve) => setTimeout(resolve, 3000));
	return { data: [{ id: 1 }] };
}

export async function loader() {
	const query = getQueryData();

	return defer({
		query,
	});
}

export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args, { adapter: lruAdapter });
clientLoader.hydrate = true;

export default function Page() {
	const { query } = useCachedLoaderData<typeof loader>();

	return (
		<>
			<Link to="/">Home</Link>

			<br />
			<br />

			<Suspense fallback="loading...">
				<Await resolve={query}>
					{({ data }) => (
						<ul>
							{data?.map((entry) => <li key={entry.id}>{entry.id}</li>)}
						</ul>
					)}
				</Await>
			</Suspense>
		</>
	);
}

This opens up a question whether it's a good idea to store data in server memory, it would be interesting if we could transform fulfilled promises using a library like turbo-stream to store on the client-side or use a web worker and decode to the original form for consumption by .


There is currently a bug where a returned promise from the cache has already been fulfilled, the internal logic of remix-client-cache cannot understand when a revalidation should occur, or fulfilled data is currently present, since we must store promises directly in memory and not as a string.
https://github.com/forge42dev/remix-client-cache/blob/main/src/index.tsx#L116-L140

// Unpack deferred data from the server
useEffect(() => {
	let isMounted = true;
	if (loaderData.deferredServerData) {
		loaderData.deferredServerData.then((newData: any) => {
			if (isMounted) {
				adapter.setItem(loaderData.key, newData);
				setFreshData(newData);
			}
		});
	}
	return () => {
		isMounted = false;
	};
}, [loaderData, adapter]);

// Update the cache if the data changes
useEffect(() => {
	if (
		loaderData.serverData &&
		JSON.stringify(loaderData.serverData) !== JSON.stringify(freshData)
	) {
		setFreshData(loaderData.serverData);
	}
}, [loaderData?.serverData, freshData]);
Lingua principale
TypeScript
Stelle
247
Fork
3
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

  • Nessun Dockerfile né file Docker Compose
  • Ha un modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di code-forge-io/remix-client-cache

Tutte le issue di code-forge-io/remix-client-cache

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.