useRequestState
Stores a value in server request state, creating it on first access, to share it among middleware, loaders and handlers for that request.
import { useRequestState } from "@meekojs/meeko/server";A request passes through several server pieces: route middleware, <script server> code or the page's load, and the api.* and rpc.* calls made by SSR, which dispatch locally with the same context. useRequestState(context, key, factory) stores a value in context.state, that request's Map: the first piece to request key creates it with factory(), and the others receive the same value. Use it for a per-request cache (avoiding querying the same user twice), a batch loader or a counter.
When the request ends, the value disappears with it: it is never shared between users. This is server code: import it from @meekojs/meeko/server.
Usage#
// 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;
},
});If a page requests the same user in two components during SSR, the database receives only one query.
Type#
function useRequestState<T>(context: RequestContext, key: string, factory: () => T): Tinterface 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
}Parameters#
| Parameter | Type | Default | Description |
|---|---|---|---|
context |
RequestContext |
required | The request context: context from a handler, load, middleware or usePage(). |
key |
string |
required | Key for the value within the request. Use your own prefix ("users:byId") to avoid colliding with other pieces. |
factory |
() => T |
required | Creates the value if the key does not yet exist in this request. |
Return value#
The value T for that key in the request. It is not a signal and is not transferred to the browser.
Examples#
Sharing middleware calculations with the page#
// 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);
});<!-- 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 ?? "My store";
</script>
<template><h1>{brand}</h1></template>A typed helper#
Repeating the key and type on every call is fragile. A helper fixes them in one place:
// 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>());
}Counting queries in development#
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;
}Because factory returns an object, every call receives the same one and can modify it.
Common mistakes#
Using it in the component's <script>#
<script lang="ts">
import { useRequestState } from "@meekojs/meeko/server";
</script>meeko check reports 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. For component state transferred to the browser, use useState; for server data, export it from <script server>:
<script server lang="ts">
import { useRequestState } from "@meekojs/meeko/server";
const { context } = usePage();
export const tenantName = useRequestState(context, "tenant", () => ({ name: "My store" })).name;
</script>A module variable as a cache#
// server/utils/users.ts
const cache = new Map<string, User>();
export const findUser = (id: string) => cache.get(id) ?? loadUser(id);There is no error, but the Map lives in the process: all requests and users share it, it grows without limit and may return one user's data to another. Store the cache in the request:
export const findUser = (context: RequestContext, id: string) =>
useRequestState(context, "users", () => new Map<string, Promise<User>>()).get(id) ?? loadUser(id);The same key with different types#
useRequestState(context, "user", () => session.userId); // string
const user = useRequestState(context, "user", () => loadUser()); // expects a UseruseRequestState does not know the stored type: the second call receives the text stored by the first, typed as User, and fails later. Use different keys or a helper with a fixed key and type like the one above.
useRequestState(context, "user:id", () => session.userId);Notes#
- One request. State lives in
context.state, a newMapper request. Localapi.*andrpc.*calls during that page's SSR use the same context, so they share state; a later browser HTTP request has its own. - Not transferred. Nothing you store reaches the browser. For page state the browser needs, export data from
<script server>or useuseState. - Server only. Import it from
@meekojs/meeko/server; in a component's<script>and template, it isMEEK108. - Promises. Store the promise, not the result, if two pieces may request the same value simultaneously: the second waits for the first instead of starting another query.