Ir al contenido

SDK tipado — Primeros pasos

El paquete @rebasepro/client proporciona un SDK de JavaScript con seguridad de tipos para interactuar con tu backend de Rebase. Gestiona:

  • Operaciones de datos — CRUD con filtrado, ordenación y paginación
  • Obtención de relaciones — Incluye entidades relacionadas con .include()
  • Suscripciones en tiempo real — Actualizaciones en vivo basadas en WebSocket
  • Sincronización local-first y offline — Base de datos local de filas opcional, escrituras instantáneas offline, consultas en vivo
  • Autenticación — Gestión de tokens, inicio de sesión, registro, OAuth
  • Almacenamiento — Subida, descarga y gestión de archivos
  • Funciones personalizadas — Llama a endpoints personalizados del servidor
pnpm add @rebasepro/client

rebase dev deriva un puerto libre a partir de la ruta del proyecto en lugar de usar uno fijo, por lo que debes leer baseUrl de la URL que imprimió; no hay un puerto compartido por todos los proyectos. En un frontend con Vite, ese es el VITE_API_URL que el scaffold escribe en .env; en un script, una variable de entorno propia.

import { createRebaseClient } from "@rebasepro/client";
const client = createRebaseClient({
baseUrl: import.meta.env.VITE_API_URL,
});

La websocketUrl se deriva automáticamente de baseUrl (http → ws, https → wss). Puedes sobrescribirla explícitamente si es necesario:

const client = createRebaseClient({
baseUrl: import.meta.env.VITE_API_URL,
websocketUrl: import.meta.env.VITE_WS_URL,
});
Opción Tipo Descripción
baseUrl string URL del backend. Léela de lo que imprimió rebase dev, o de tu despliegue
websocketUrl string URL de WebSocket — derivada automáticamente de baseUrl si se omite
token string Token JWT estático para llamadas de servidor a servidor
apiPath string Prefijo de la API (por defecto: "/api")
fetch typeof fetch Implementación personalizada de fetch (p. ej. para SSR)
onUnauthorized () => Promise<boolean> Controlador personalizado para 401 — devuelve true para reintentar
realtime boolean Abre el WebSocket (por defecto true) — establece false en scripts de ejecución única
collections Record<string, string> Mapea nombres de accesores a slugs de colecciones
offline boolean | OfflineConfig Sincronización local-first — desactivada por defecto
impersonate string Ejecuta cada petición como este usuario (por uid), para comprobar qué ve un usuario. Solo con la sesión de un administrador: la API de datos, las funciones y el tiempo real se ejecutan como ese usuario, y cualquier otra ruta la rechaza

Genera un cliente totalmente tipado a partir de las definiciones de tus colecciones:

rebase generate-sdk

Mientras rebase dev está en ejecución no hace falta: regenera los tipos en generated/sdk/ al arrancar y en cada guardado bajo config/collections/. Ejecuta el comando tú mismo en CI, en un repositorio de frontend que no tiene colecciones (rebase generate-sdk --from link), o en cualquier lugar donde rebase dev no se esté ejecutando. En 0.23, rebase dev deja los tipos del SDK como están, así que ejecuta el comando después de cada cambio de colección.

Luego pasa el parámetro de tipo Database a createRebaseClient para obtener autocompletado completo:

import { createRebaseClient } from "@rebasepro/client";
import { collectionsDictionary, type Database } from "./generated/sdk/database.types";
const client = createRebaseClient<Database>({
baseUrl: import.meta.env.VITE_API_URL,
collections: collectionsDictionary,
});
// Full autocomplete on collection names and field types
const { data } = await client.data.products.find();

Cuando se proporciona Database, createRebaseClient devuelve una instancia de CreateRebaseClientResult<DB>. Esto mapea los accesores de colecciones en camelCase directamente en client.data a sus tipos correspondientes, brindándote autocompletado completo en las operaciones y tipos de colecciones (p. ej., client.data.products.find()).

collectionsDictionary mapea cada accesor de vuelta al slug que se usa en la transmisión de datos. Pásalo siempre que un slug no sea ya un nombre de propiedad válido — my-notes es accesible como client.data.myNotes solo porque el diccionario así lo indica.

Algunos nombres de client.data están ocupados: collection es la función dinámica collection(slug), y then, toJSON y $$typeof se dejan intactos para las comprobaciones de promesas y de serialización. Una colección cuyo accesor fuera uno de ellos se genera como <name>Collection — el slug collection es client.data.collectionCollection — y generate-sdk imprime una advertencia que nombra ambos. client.data.collection("collection") también llega a ella.

El nombre de un campo en la transmisión es su clave de propiedad, y la API utiliza camelCase en su totalidad. Una propiedad createdAt almacenada en una columna created_at es row.createdAt, y la clave foránea de una relación es authorId aunque la columna se mantenga como author_id. where y orderBy se basan en el mismo tipo Row, por lo que lo que compila es a lo que el backend responde.

Una clave de propiedad que tú escribiste es tu clave, cualquiera que sea su formato — nada renombra un nombre que hayas elegido. Las dos claves que se derivan en lugar de declararse, la clave foránea de una relación y una columna leída mediante introspección, están en camelCase.

Row describe una lectura, Insert un create() y Update un update() — no tienen la misma forma. Una columna que no es obligatoria es T | null en las tres, de modo que una escritura pueda vaciarla; la clave primaria siempre está presente en una lectura y nunca se puede establecer en una actualización; un destino belongsTo se puede escribir tanto como la relación ({ author: 5 }) o como su clave foránea ({ authorId: 5 }); y una pertenencia hasMany o manyToMany se escribe como una lista de ids de destino u objetos { id } ({ tags: ["t-1"] }), cada uno con un _pivot cuando la tabla de unión declara columnas de payload. Un campo que nadie puede escribir (access: { write: [] }) no está ni en Insert ni en Update. Una columna date se lee como una cadena ISO, y una escritura acepta esa cadena o un Date.

// Create
const product = await client.data.products.create({
name: "Camera",
price: 299,
});
// Query with filters
const { data } = await client.data.products
.where("price", ">=", 100)
.orderBy("createdAt", "desc")
.limit(10)
.find();
// Real-time subscription
const unsubscribe = client.data.products.listen(
{ where: { active: ["==", true] } },
(response) => console.log("Updated:", response.data)
);

En un frontend de Rebase, el cliente se crea una vez y se comparte a través del contexto:

import { createRebaseClient } from "@rebasepro/client";
const client = createRebaseClient({ baseUrl: API_URL });
<Rebase client={client} ...>

Accede a él desde cualquier componente:

import { useRebaseClient } from "@rebasepro/app";
function MyComponent() {
const client = useRebaseClient();
// client.data, client.auth, client.storage, client.functions
}