matecitodb
v4.0.2
Published
SDK oficial de matecito.dev — Auth, queries, realtime, storage, batch, functions, AI, analytics y más para Next.js, React y Node.js
Maintainers
Readme
matecitodb 🧉
El SDK oficial de matecito.dev — Backend-as-a-Service con TypeScript nativo, consultas fluidas, tiempo real, autenticación completa y mucho más.
npm install matecitodbInicio rápido
import { createClient } from 'matecitodb'
const db = createClient({
url: 'https://tu-proyecto.matecito.dev',
apiKey: 'mk_anon_...',
})
// Consultar registros
const { data: posts } = await db.from('posts')
.select('id, title, created_at')
.eq('status', 'publicado')
.latest()
.limit(10)
// Insertar
const { data: post } = await db.from('posts').insert({
title: 'Mi primer post',
content: 'Hola desde MatecitoDB!',
})
// Escuchar cambios en tiempo real
const unsub = db.from('posts').subscribe((event) => {
console.log(event.action, event.record)
})Tabla de contenidos
- Instalación y configuración
- Records & Query Builder
- Autenticación
- Realtime
- Storage
- Batch
- SQL Runner
- Permisos
- Colecciones y Schema
- Stats y Logs
- SMTP y Email Templates
- Webhooks
- CLI
- Manejo de errores
- Tipado con Schema
Instalación y configuración
npm install matecitodbimport { createClient } from 'matecitodb'
const db = createClient({
url: 'https://tu-proyecto.matecito.dev',
apiKey: 'mk_anon_...', // clave pública (cliente)
// serviceKey: 'mk_service_...', // clave de servicio (servidor/admin)
autoRefresh: true, // refresca el token JWT automáticamente
timeout: 30_000,
retry: { attempts: 3, baseDelay: 500 },
debug: false, // loguea requests (headers sensibles son redactados)
})Variables de entorno (Next.js / Vite)
NEXT_PUBLIC_MATECITODB_API_KEY=mk_anon_...
MATECITODB_API_KEY=mk_service_...
MATECITODB_URL=https://tu-proyecto.matecito.devRecords & Query Builder
Accedé a cualquier colección con db.from('nombre'). El builder es thenable — podés usar await directamente.
Consultar
// Traer todos
const { data: posts } = await db.from('posts').find()
// Con filtros
const { data } = await db.from('posts')
.select('id, title, author')
.eq('status', 'publicado')
.gte('views', 100)
.ilike('title', '%matecito%')
.latest()
.limit(20)
.page(2)
// Traer uno por ID
const { data: post } = await db.from('posts').getOne('abc-123')
// Traer el primero que coincide
const { data: post } = await db.from('posts')
.eq('slug', 'mi-post')
.getFirst()
// findOne: acepta ID string o filtros
const post = await db.from('posts').findOne('abc-123')
const post = await db.from('posts').findOne({ slug: 'mi-post' })
// Contar
const count = await db.from('posts').eq('status', 'publicado').count()Filtros disponibles
| Método | Operador SQL |
|--------|-------------|
| .eq(col, val) | = |
| .neq(col, val) | != |
| .gt(col, val) | > |
| .gte(col, val) | >= |
| .lt(col, val) | < |
| .lte(col, val) | <= |
| .like(col, pattern) | LIKE |
| .ilike(col, pattern) | ILIKE |
| .inValues(col, [v1, v2]) | IN (...) |
| .notInValues(col, [v1, v2]) | NOT IN (...) |
| .or('price.gte.100,name.ilike.%cafe%') | OR (...) |
| .search('texto') | ILIKE en todos los campos |
Fechas como filtro
// Acepta objetos Date — se envía como ISO 8601 completo
await db.from('events')
.gte('date', new Date('2025-01-01'))
.lt('date', new Date('2026-01-01'))
.find()Ordenar y paginar
db.from('posts').latest() // ORDER BY created_at DESC
db.from('posts').oldest() // ORDER BY created_at ASC
db.from('posts').order('title', { ascending: true })
db.from('posts').limit(50).page(3)Insertar
const { data, error } = await db.from('posts').insert({
title: 'Nuevo post',
content: 'Contenido...',
})Actualizar
// Por ID
await db.from('posts').eq('id', 'abc-123').update({ title: 'Título nuevo' })
// Bulk update — actualiza todos los que coinciden con los filtros
await db.from('posts').eq('status', 'borrador').update({ archived: true })
// Merge — actualiza solo los campos enviados, preserva el resto
await db.from('posts').eq('id', 'abc-123').merge({ views: 42 })
// Con fecha de expiración
await db.from('sessions').eq('id', 'xyz').update(
{ active: true },
{ expiresAt: new Date(Date.now() + 3600_000) }
)Upsert
const { data, upserted } = await db.from('profiles').upsert(
{ user_id: 'abc', bio: 'Dev apasionado' },
'user_id' // campo de conflicto
)
console.log(upserted) // true = insertado, false = actualizadoEliminar
// Soft-delete (recuperable)
await db.from('posts').delete('abc-123')
// Restaurar
await db.from('posts').restore('abc-123')
// Borrado permanente
await db.from('posts').hardDelete('abc-123')
// Bulk soft-delete
await db.from('posts').eq('status', 'spam').delete()Registros eliminados y expirados
// Incluir registros soft-deleted en los resultados
const { data } = await db.from('posts').includeDeleted().find()
// Incluir registros expirados
const { data } = await db.from('sessions').includeExpired().find()Insertar múltiples (batch)
const records = await db.from('products').insertMany([
{ name: 'Yerba', price: 500 },
{ name: 'Mate', price: 1200 },
])Paginar como stream
for await (const batch of db.from('posts').paginate({ batchSize: 100 })) {
console.log('Lote de', batch.length, 'posts')
}Exportar
// Devuelve un Blob con todos los registros (hasta 10k)
const blob = await db.from('posts').export({ format: 'csv' })
const blob = await db.from('posts').export({ format: 'json' })
// Descargar en el browser
const url = URL.createObjectURL(blob)Autenticación
Registro e inicio de sesión
// Registrar
const { data, error } = await db.auth.signUp({
email: '[email protected]',
password: 'contraseña123',
username: 'miusuario', // campo extra opcional
})
// Iniciar sesión
const { data, error } = await db.auth.signIn({
email: '[email protected]',
password: 'contraseña123',
})
// Cerrar sesión
await db.auth.signOut()
// Estado actual
const user = db.auth.user
const token = db.auth.token
const loggedIn = db.auth.isLoggedInEscuchar cambios de sesión
const unsubscribe = db.auth.onAuthChange((user) => {
if (user) {
console.log('Sesión activa:', user.email)
} else {
console.log('Sesión cerrada')
}
})
// Dejar de escuchar
unsubscribe()Perfil y contraseña
// Actualizar perfil
await db.auth.updateProfile({ name: 'Juan', avatar_seed: 'abc' })
// Solicitar reset de contraseña
await db.auth.requestPasswordReset('[email protected]', {
resetUrlBase: 'https://miapp.com/reset',
})
// Confirmar nuevo password con el token del email
await db.auth.resetPassword(token, 'nueva-contraseña')Verificación de email
// Verificar email con el token del link
await db.auth.verifyEmail(token)
// Reenviar email de verificación
await db.auth.resendVerification({ email: '[email protected]' })
await db.auth.resendVerification({ userId: 'user-uuid' })OAuth
// 1. Generar URL de redirección (incluye state para CSRF)
const state = crypto.randomUUID()
const url = db.auth.getOAuthUrl('google', 'https://miapp.com/callback', { state })
// 2. Redirigir al usuario a `url`
// 3. En el callback, completar el flujo con los params de la URL
const { data, error } = await db.auth.handleOAuthCallback({
access_token: searchParams.get('access_token'),
refresh_token: searchParams.get('refresh_token'),
})Inyectar sesión manualmente (SSO / deep links)
db.auth.setSession({
accessToken: 'jwt...',
refreshToken: 'refresh...',
})Administración de usuarios (requiere serviceKey)
// Listar usuarios
const { data } = await db.auth.admin.listUsers({ limit: 20, search: 'juan' })
// Eliminar usuario
await db.auth.admin.deleteUser('user-uuid')Configurar proveedores OAuth (requiere serviceKey)
// Configurar Google
await db.auth.oauth.configure('google', {
clientId: 'TU_CLIENT_ID',
clientSecret: 'TU_CLIENT_SECRET',
redirectUri: 'https://miapp.com/callback',
})
// Listar proveedores activos
const { data: providers } = await db.auth.oauth.listProviders()
// Eliminar proveedor
await db.auth.oauth.remove('github')Realtime
// Suscribirse a todos los cambios de una colección
const unsub = db.from('messages').subscribe((event) => {
console.log(event.action) // 'created' | 'updated' | 'deleted'
console.log(event.collection) // 'messages'
console.log(event.record) // el registro como objeto plano
console.log(event.recordId) // ID del registro
})
// Dejar de escuchar
unsub()
// Escuchar estado de conexión
const unsub = db.realtime.onStatusChange((status) => {
// 'connected' | 'disconnected'
console.log('Realtime:', status)
})
// Desconectar manualmente
db.realtime.disconnect()El SDK mantiene la conexión automáticamente con reconexión exponencial y un ping/pong cada 30 segundos para detectar conexiones zombi.
Storage
// Subir un archivo (browser File)
const { data, error } = await db.storage.upload(file, {
path: 'avatars/user-1.png',
public: true,
})
// Subir desde URL (server-side)
await db.storage.uploadFromUrl('https://ejemplo.com/img.jpg', {
path: 'imports/img.jpg',
})
// Listar archivos
const { data: files } = await db.storage.list('avatars/')
// Eliminar
await db.storage.delete('avatars/user-1.png')
// URL pública
const url = db.storage.getPublicUrl('avatars/user-1.png')Batch
Ejecuta múltiples operaciones en una sola transacción del servidor.
const result = await db.batch()
.insert('posts', { title: 'Post A' })
.insert('posts', { title: 'Post B' })
.update('settings', { key: 'theme', value: 'dark' }, { collection: 'settings' })
.delete('drafts', 'draft-uuid')
.execute()
for (const item of result.results) {
if (item.ok) {
console.log('OK:', item.record)
} else {
console.error('Error:', item.error)
}
}SQL Runner
Ejecuta SQL raw directamente contra tu proyecto. Requiere serviceKey.
const { data, error } = await db.sql.run(
'SELECT id, title FROM posts WHERE views > $1 LIMIT $2',
[100, 10]
)
console.log(data.rows) // array de filas
console.log(data.rowCount) // cantidad de filas
console.log(data.durationMs) // tiempo de ejecución
console.log(data.truncated) // true si se cortaron resultadosPermisos
Gestioná los permisos de cada colección. Requiere serviceKey.
// Ver todos los permisos
const { data } = await db.permissions.getAll()
// Ver permisos de una colección
const { data } = await db.permissions.get('posts')
// Actualizar permisos específicos
await db.permissions.set('posts', {
list: 'public', // sin autenticación
get: 'public',
create: 'auth', // requiere JWT
update: 'auth',
delete: 'service', // solo serviceKey
})
// Aplicar el mismo nivel a todas las operaciones
await db.permissions.setAll('private_data', 'service')Niveles disponibles: public | auth | service | nobody
Colecciones y Schema
Requiere serviceKey.
// Listar colecciones
const { data: cols } = await db.collections.list()
// Crear colección con campos
await db.collections.create('products', {
fields: [
{ name: 'name', type: 'text', required: true },
{ name: 'price', type: 'number', required: true },
{ name: 'image', type: 'file' },
{ name: 'category', type: 'relation', options: { collection: 'categories' } },
]
})
// Renombrar
await db.collections.rename('old_name', 'new_name')
// Eliminar (irreversible)
await db.collections.delete('temp_data')
// ─── Campos ────────────────────────────────────────────
// Listar campos
const { data: fields } = await db.collections.fields('products').list()
// Agregar campo
await db.collections.fields('products').create({
name: 'discount',
type: 'number',
required: false,
})
// Actualizar campo
await db.collections.fields('products').update(fieldId, { required: true })
// Eliminar campo
await db.collections.fields('products').delete(fieldId)Tipos de campo disponibles: text | number | boolean | email | date | file | json | relation | select
Stats y Logs
Requiere serviceKey.
// Estadísticas del proyecto
const { data: stats } = await db.stats.get()
console.log(stats.total_records, stats.storage_used_mb, stats.requests_today)
// Logs de requests recientes
const { data: logs } = await db.logs.list({ limit: 50 })
// Filtrar logs
const { data } = await db.logs.list({ status: 500 }) // solo errores
const { data } = await db.logs.list({ method: 'POST' }) // solo POSTs
const { data } = await db.logs.list({ path: '/records' }) // por pathSMTP y Email Templates
Requiere serviceKey.
// Configurar SMTP
await db.smtp.set({
host: 'smtp.gmail.com',
port: 587,
user: '[email protected]',
pass: 'app-password',
from: 'Mi App <[email protected]>',
})
// Enviar email de prueba
await db.smtp.test('[email protected]')
// ─── Templates ─────────────────────────────────────────
// Seed de templates del sistema (welcome, reset-password, etc.)
await db.emailTemplates.seed()
// Crear template personalizado (soporta {{variable}})
const { data } = await db.emailTemplates.create({
name: 'pedido_confirmado',
subject: 'Tu pedido {{order_id}} fue confirmado',
body: '<h1>Hola {{user_name}}</h1><p>¡Tu pedido está en camino!</p>',
})
// Listar templates
const { data: list } = await db.emailTemplates.list()
// Actualizar template
await db.emailTemplates.update('template-uuid', { subject: 'Nuevo asunto' })
// Eliminar template
await db.emailTemplates.delete('template-uuid')Webhooks
Requiere serviceKey.
// Crear webhook
const { data: hook } = await db.webhooks.create({
url: 'https://miapp.com/hooks/matecito',
events: ['record.created', 'record.deleted'],
secret: 'mi-secreto-de-firma',
})
// Listar webhooks
const { data: hooks } = await db.webhooks.list()
// Actualizar (ej. deshabilitar)
await db.webhooks.update('hook-uuid', { enabled: false })
// Eliminar
await db.webhooks.delete('hook-uuid')Eventos disponibles: record.created | record.updated | record.deleted | auth.signup | auth.login | storage.upload
CLI
# Inicializar proyecto (genera .env.local con tus credenciales)
npx matecitodb init
# Generar interfaces TypeScript desde tu schema actual
npx matecitodb generate types
# Scaffold de autenticación para Next.js
npx matecitodb generate auth
# Generar un React hook para una colección
npx matecitodb generate hook postsTipado automático
Después de correr generate types, podés usar el schema como genérico:
import type { Database } from './matecito.types'
const db = createClient<Database>({ url, apiKey })
// Ahora db.from('posts') está completamente tipado
const { data } = await db.from('posts').eq('status', 'publicado').find()
// data es Post[] con autocompletado completoManejo de errores
Todos los métodos devuelven { data, error }. Nunca tiran excepciones salvo find() / findOne() / count().
const { data, error } = await db.from('posts').insert({ title: 'Test' })
if (error) {
console.error(error.message) // mensaje legible
console.error(error.status) // código HTTP (0 = error de red)
console.error(error.raw) // respuesta raw del servidor
}Reintentos y auto-refresh
El SDK maneja automáticamente:
- Auto-retry: reintentos con backoff exponencial ante errores de red
- Auto-refresh: si recibe un 401, refresca el JWT y reintenta la request original una vez
Configurable al crear el cliente:
const db = createClient({
url,
apiKey,
autoRefresh: true,
retry: { attempts: 3, baseDelay: 500 },
})Tipado con Schema
// matecito.types.ts (generado por `npx matecitodb generate types`)
export interface Database {
posts: {
id: string
title: string
content: string
status: 'borrador' | 'publicado'
views: number
created_at: string
}
users: {
id: string
email: string
name: string
}
}
// Uso con tipado completo
import { createClient } from 'matecitodb'
import type { Database } from './matecito.types'
const db = createClient<Database>({ url, apiKey })
const { data: posts } = await db.from('posts').find()
// posts es Database['posts'][] — completamente tipadoDocumentación por módulo
- Inicio rápido
- Autenticación
- Records & CRUD
- Realtime
- Storage
- Colecciones
- Stats y Logs
- SMTP y Email
- Webhooks
- Manejo de errores
Hecho con ❤️ en Argentina 🧉 · matecito.dev
