Saltar al contenido
Meeko
Escribe para buscar en las guías y en la referencia de la API. Ábrela desde cualquier página con / o Ctrl K; muévete con las flechas y entra con Intro.
Empezar

Contenidos

En esta página

useRequestState

Guarda un valor en el estado de una petición del servidor y lo crea la primera vez, para compartirlo entre middleware, loaders y handlers de esa petición.

composable Desde 1.0.0 @meekojs/meeko/server Código fuente
ts
import { useRequestState } from "@meekojs/meeko/server";

Una petición pasa por varias piezas de servidor: el middleware de ruta, el código de <script server> o el load de la página, y las llamadas a api.* y rpc.* que hace el SSR, que se despachan localmente con el mismo contexto. useRequestState(context, key, factory) guarda un valor en context.state, el Map de esa petición: la primera pieza que pide key lo crea con factory() y las demás reciben el mismo valor. Sirve para una caché por petición (no consultar dos veces el mismo usuario), un cargador por lotes o un contador.

Al terminar la petición, el valor desaparece con ella: nunca se comparte entre usuarios. Es código de servidor: se importa de @meekojs/meeko/server.

Uso#

ts
// server/rpc/users/get.ts
import { defineRpc, useRequestState } from "@meekojs/meeko/server";
import { userParams, userSchema } from "../../../shared/schemas/users.js";

export default defineRpc({
  input: userParams,
  output: userSchema,
  handler: async ({ input, context }) => {
    const cache = useRequestState(context, "users:byId", () => new Map<string, Promise<{ id: string; name: string }>>());
    let user = cache.get(input.id);
    if (!user) {
      user = context.services.db.users.findById(input.id);
      cache.set(input.id, user);
    }
    return user;
  },
});

Si una página pide el mismo usuario en dos componentes durante el SSR, la base de datos sólo recibe una consulta.

Tipo#

ts
function useRequestState<T>(context: RequestContext, key: string, factory: () => T): T

RequestContext

ts
interface RequestContext<out TServices = unknown, TEnv = unknown> {
	readonly request: Request
	readonly requestId: string
	readonly signal: AbortSignal
	readonly services: ContextServices<TServices>
	readonly session: SessionHandle<SessionData>
	readonly state: Map<string, unknown>
	readonly platform: ServerPlatform<TEnv>
	readonly idempotencyKey?: string | undefined
	readonly clientAddress?: string | undefined
}

Parámetros#

Parámetro Tipo Por defecto Descripción
context RequestContext obligatorio El contexto de la petición: el context de un handler, de load, del middleware o de usePage().
key string obligatorio Clave del valor dentro de la petición. Usa un prefijo propio ("users:byId") para no chocar con otras piezas.
factory () => T obligatorio Crea el valor si la clave no existe todavía en esta petición.

Valor devuelto#

El valor T de esa clave en la petición. No es una señal ni viaja al navegador.

Ejemplos#

Compartir con la página lo que calculó el middleware#

ts
// app/middleware/01-tenant.server.ts
import { useRequestState } from "@meekojs/meeko/server";

export default defineServerRouteMiddleware(async ({ context, url }) => {
  const host = url.hostname;
  const tenant = await context.services.db.tenants.byHost(host);
  useRequestState(context, "tenant", () => tenant);
});
meeko
<!-- app/pages/index.meeko -->
<script server lang="ts">
import { useRequestState } from "@meekojs/meeko/server";
const { context } = usePage();
const tenant = useRequestState(context, "tenant", () => null as { id: string; name: string } | null);
export const brand = tenant?.name ?? "Mi tienda";
</script>

<template><h1>{brand}</h1></template>

Un helper tipado#

Repetir la clave y el tipo en cada llamada es frágil. Un helper los fija en un solo sitio:

ts
// server/utils/request-cache.ts
import { useRequestState } from "@meekojs/meeko/server";
import type { RequestContext } from "@meekojs/meeko/server";

export function requestCache<T>(context: RequestContext, name: string): Map<string, T> {
  return useRequestState(context, `cache:${name}`, () => new Map<string, T>());
}

Contar consultas en desarrollo#

ts
import { useRequestState } from "@meekojs/meeko/server";
import type { RequestContext } from "@meekojs/meeko/server";

export function countQuery(context: RequestContext): number {
  const counter = useRequestState(context, "debug:queries", () => ({ value: 0 }));
  return ++counter.value;
}

Como factory devuelve un objeto, cada llamada recibe el mismo y puede modificarlo.

Errores comunes#

Usarlo en el <script> del componente#

Incorrectomeeko
<script lang="ts">
import { useRequestState } from "@meekojs/meeko/server";
</script>

meeko check da MEEK108: @meekojs/meeko/server sólo existe en el servidor y no se puede cargar en el navegador: el <script> y la plantilla de un componente también se ejecutan en el navegador. Mueve ese código a <script server> o a server/; para usar sus tipos, impórtalos con import type. Para estado del componente que viaja al navegador, usa useState; para datos del servidor, expórtalos desde <script server>:

Correctomeeko
<script server lang="ts">
import { useRequestState } from "@meekojs/meeko/server";
const { context } = usePage();
export const tenantName = useRequestState(context, "tenant", () => ({ name: "Mi tienda" })).name;
</script>

Una variable de módulo como caché#

Incorrectots
// server/utils/users.ts
const cache = new Map<string, User>();
export const findUser = (id: string) => cache.get(id) ?? loadUser(id);

No hay error, pero el Map vive en el proceso: lo comparten todas las peticiones y todos los usuarios, crece sin límite y puede devolver a un usuario datos de otro. Guarda la caché en la petición:

Correctots
export const findUser = (context: RequestContext, id: string) =>
  useRequestState(context, "users", () => new Map<string, Promise<User>>()).get(id) ?? loadUser(id);

La misma clave con tipos distintos#

Incorrectots
useRequestState(context, "user", () => session.userId);         // string
const user = useRequestState(context, "user", () => loadUser()); // espera un User

useRequestState no conoce el tipo guardado: la segunda llamada recibe el texto que guardó la primera, con el tipo User, y falla más adelante. Usa claves distintas, o un helper con la clave y el tipo fijos como el de arriba.

Correctots
useRequestState(context, "user:id", () => session.userId);

Notas#

  • Una petición. El estado vive en context.state, un Map nuevo por petición. Las llamadas locales de api.* y rpc.* durante el SSR de esa página usan el mismo contexto, así que comparten el estado; una petición HTTP posterior del navegador tiene el suyo.
  • No viaja. Nada de lo que guardas llega al navegador. Para estado de la página que el navegador necesita, exporta un dato desde <script server> o usa useState.
  • Sólo servidor. Se importa de @meekojs/meeko/server; en el <script> y la plantilla de un componente es MEEK108.
  • Promesas. Guarda la promesa, no el resultado, si dos piezas pueden pedir el mismo valor a la vez: la segunda espera a la primera en lugar de lanzar otra consulta.

Relacionado#

¿Te sirvió esta página?

Añadir un comentario

Se envía al elegir «Sí» o «No». Solo se guardan la página, tu respuesta y el comentario: ni cookies ni datos tuyos.

0 de 2000 caracteres

Editar en GitHub: docs/reference/composables/userequeststate.md