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

createRpcClient

Crea un cliente tipado de los procedimientos RPC a partir de su manifiesto público, donde cada llamada es un POST JSON con el token CSRF.

API avanzada Desde 1.0.0 @meekojs/meeko/framework Código fuente
ts
import { createRpcClient } from "@meekojs/meeko/framework";

createRpcClient construye un cliente como el autoimport rpc: un método por procedimiento, anidado según su ID ("users.get" → rpc.users.get(input)). Cada llamada es un POST a su ruta (/_meeko/rpc/users/get) con la entrada en JSON y el token CSRF en x-meeko-csrf, y devuelve la salida parseada o rechaza con ClientError. Sólo necesita la lista pública de procedimientos; nunca importa código del servidor.

Uso#

ts
import { createRpcClient } from "@meekojs/meeko/framework";
import type { RpcOperation } from "@meekojs/meeko/framework";

type Contract = {
  "math.sum": RpcOperation<{ a: number; b: number }, { total: number }>;
  "users.get": RpcOperation<{ id: string }, { id: string; name: string }>;
};

const rpc = createRpcClient<Contract>(
  {
    rpc: [
      { id: "math.sum", pathname: "/_meeko/rpc/math/sum" },
      { id: "users.get", pathname: "/_meeko/rpc/users/get" },
    ],
  },
  { baseUrl: "https://example.com", csrfToken: () => process.env.CSRF_TOKEN },
);

const { total } = await rpc.math.sum({ a: 1, b: 2 }); // POST https://example.com/_meeko/rpc/math/sum

Tipo#

ts
function createRpcClient<Contract extends Record<string, RpcOperation>>(
  manifest: Pick<PublicServerManifest, "rpc">,
  options?: ClientOptions,
): RpcClient<Contract>

ClientOptions

ts
interface ClientOptions {
	readonly baseUrl?: string | (() => string)
	readonly fetch?: typeof fetch
	readonly credentials?: RequestCredentials
	readonly csrfToken?: string | (() => string | undefined)
	readonly headers?: HeadersInit | (() => HeadersInit)
}

RpcOperation

ts
interface RpcOperation<Input = unknown, Output = unknown> {
	readonly input: Input
	readonly output: Output
}

Parámetros#

Parámetro Tipo Por defecto Descripción
manifest { rpc: { id, pathname }[] } obligatorio Los procedimientos públicos. El id con puntos es el camino del método; pathname, su ruta, normalmente /_meeko/rpc/<carpetas>/<archivo>.
options ClientOptions {} baseUrl, fetch, credentials, csrfToken y headers, como en createApiClient.

Contract describe cada procedimiento con RpcOperation<Input, Output>; en una aplicación sale de .meeko/types/server-contracts.d.ts.

Valor devuelto#

Un objeto anidado por ID. Cada método recibe la entrada y, opcionalmente, { signal, idempotencyKey }, y devuelve una promesa de la salida:

Respuesta Resultado
2xx con JSON La salida parseada.
Un error del servidor { code, status, requestId, issues? } Rechaza con ClientError.
Un cuerpo que no es JSON Rechaza con ClientError INVALID_RESPONSE.

Lanza TypeError al crearse si dos procedimientos colisionan (Client key collision: users.get, por ejemplo con users y users.get, porque rpc.users no puede ser a la vez una función y un objeto) o un ID no es válido.

Ejemplos#

Cancelar una llamada lenta#

ts
import { createRpcClient } from "@meekojs/meeko/framework";
import type { RpcOperation } from "@meekojs/meeko/framework";

type Contract = { "reports.build": RpcOperation<{ month: string }, { url: string }> };
const rpc = createRpcClient<Contract>({ rpc: [{ id: "reports.build", pathname: "/_meeko/rpc/reports/build" }] }, { baseUrl: "https://example.com" });

const report = await rpc.reports.build({ month: "2026-10" }, { signal: AbortSignal.timeout(5_000) });
console.log(report.url);

Reintentos seguros con una clave de idempotencia#

ts
const key = crypto.randomUUID();
await retry(() => rpc.orders.create({ items }, { idempotencyKey: key }));

El servidor recibe la misma Idempotency-Key en cada intento (context.idempotencyKey) y puede devolver el resultado guardado en lugar de crear otro pedido. Un valor que no es un UUID rechaza con TypeError sin enviar nada.

En una prueba, contra la aplicación sin red#

ts
const rpc = createRpcClient<Contract>(manifest, {
  baseUrl: "http://localhost",
  fetch: (input, init) => app.fetch(new Request(input, init)),
});

Con app de createServerApp, el cliente prueba el contrato completo (CSRF, validación, errores) sin abrir un puerto.

Errores comunes#

Dos IDs que no pueden convivir#

Incorrectots
createRpcClient({ rpc: [
  { id: "users", pathname: "/_meeko/rpc/users" },
  { id: "users.get", pathname: "/_meeko/rpc/users/get" },
] });

TypeError: Client key collision: users.get. En una aplicación, el descubrimiento de servidor ya lo rechaza al generar el manifiesto, con los dos archivos.

Correctots
createRpcClient({ rpc: [
  { id: "users.list", pathname: "/_meeko/rpc/users/list" },
  { id: "users.get", pathname: "/_meeko/rpc/users/get" },
] });

Usarlo en Node sin baseUrl#

Incorrectots
await createRpcClient<Contract>(manifest).math.sum({ a: 1, b: 2 });

TypeError: Failed to parse URL from /_meeko/rpc/math/sum: en Node, fetch no resuelve rutas relativas.

Correctots
await createRpcClient<Contract>(manifest, { baseUrl: "http://127.0.0.1:4173" }).math.sum({ a: 1, b: 2 });

Una mutación con sesión sin token CSRF#

Incorrectots
const rpc = createRpcClient<Contract>(manifest, { baseUrl, headers: { cookie } });
await rpc.cart.add({ sku: "A1" });

Con una cookie de sesión, el servidor exige x-meeko-csrf en cada POST: sin él, ClientError con estado 403. El token llega en la cabecera x-meeko-csrf-token de las respuestas y en la meta meeko-csrf-token del documento.

Correctots
const rpc = createRpcClient<Contract>(manifest, { baseUrl, headers: { cookie }, csrfToken: () => token });

Notas#

  • Siempre POST. Un procedimiento RPC no se puede pedir con GET: el servidor responde 405 con Allow: POST. Para lecturas cacheables o enlazables, usa un endpoint de API.
  • Servidor y navegador. Funciona en cualquier runtime con fetch. En el navegador, createBrowserClientOptions() da csrfToken y fetch ya conectados al documento.
  • Seguridad. El manifiesto público no contiene rutas de archivos del servidor. La entrada se valida en el servidor con el schema de defineRpc; los tipos del cliente no sustituyen esa validación.

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/advanced/createrpcclient.md