sqlitype es un mini ORM para trabajar con bases de datos SQLite, que combina:
- Validación de tipos en tiempo de compilación (TypeScript)
- Validación de datos en tiempo de ejecución (TypeBox)
- Operaciones CRUD type-safe
- Soporte para relaciones entre modelos
Los modelos representan tus tablas de base de datos. Cada modelo necesita:
- Un esquema TypeBox que define la estructura
- Configuración básica (nombre de tabla y conexión a DB)
import { Type } from '@sinclair/typebox';
import sqlitype from 'sqlitype';
import Database from 'bun:sqlite';
// Definición del esquema
const User = Type.Object({
id : Type.Number(),
name : Type.String(),
email : Type.String(),
age : Type.Optional(Type.Number())
},
{
$id : "Users" // nombre de la tabla
});
// Inferir el tipo TypeScript
type User = Static<typeof User>;
// Creación del modelo
const Users = new sqlitype.Model(User);
sqlitype.useConnection(new Database('mydb.sqlite'));
// También puedes usar fromTypebox
const Users = sqlitype.fromTypebox(UserSchema);sqlitype se encarga de crear o actualizar la tabla para mantenerla sincronizada con el esquema. (dentro de lo que SQLite permite)
Puedes cambiar la conexión en cualquier momento con useClient:
sqlitype.useClient(new Database('otra.db'));
// Todos los modelos existentes usarán la nueva conexiónSe utiliza la validación de TypeBox en tiempo de ejecución antes de insertar nuevos datos. En tiempo de compilación utilizará la de Typescript 😍
Para los errores de validación se utiliza el formato de TypeBox.
const newUser = await Users.insert({
name: "María García",
email: "maria@ejemplo.com",
age: 28
});
console.log(newUser.id); // ID auto-generadoSi los datos no pasan la validación se lanza un error:
try {
await Users.insert({ name: "Pepe", email: 123 }); // Error! email debe ser string
} catch (e) {
console.log(e.message); // "Validation error"
console.log(e.errors); // Array de ValueError de TypeBox
}Métodos disponibles:
find({...})- Con filtrosfindById(id)- Por ID único
// Todos los usuarios
const allUsers = await Users.find();
// Usuarios de 28 años
const adultos = await Users.find({
age: 28
});
const antonios = await Users.find({
name : "%Antonio%"
})
// usuarios con menos de 18 años
const jovenes = await Users.find({
age : { $lt : 18 }
})
// Usuario específico
const user = await Users.findById(1);Operadores disponibles para los filtros:
| Operador | Ejemplo | SQL |
|---|---|---|
| (valor directo) | { age: 28 } |
"age" = ? |
$gt |
{ age: { $gt: 18 } } |
"age" > ? |
$gte |
{ age: { $gte: 18 } } |
"age" >= ? |
$lt |
{ age: { $lt: 18 } } |
"age" < ? |
$lte |
{ age: { $lte: 18 } } |
"age" <= ? |
$in |
{ age: { $in: [18, 21] } } |
"age" IN (?,?) |
$nin |
{ age: { $nin: [18] } } |
"age" NOT IN (?) |
% wildcard |
{ name: "%Ana%" } |
"name" LIKE ? |
$ne + % |
{ name: { $ne: "%Ana%" } } |
"name" NOT LIKE ? |
$ne |
{ name: { $ne: "Pepa" } } |
"name" <> ? |
$ne: null |
{ name: { $ne: null } } |
"name" IS NOT NULL |
null |
{ name: null } |
"name" IS NULL |
Todos los métodos find y findAndJoin aceptan FindOptions como segundo parámetro:
const resultados = await Users.find(
{ age: { $gt: 18 } },
{
order: { name: "asc" },
limit: 10,
offset: 20
}
);El order soporta paths anidados para ordenar por campos de relaciones:
const libros = await Books.findAndJoin(
{},
{
order: { title: "asc", "author.name": "desc" },
limit: 5
}
);TypeScript autocompleta los paths válidos según el esquema del modelo.
const updated = await Users.update(1, {
age: 29 // Nuevo valor
});Se pueden definir relaciones con otro modelo utilizando ModelReference
Las relaciones pueden ser obligatorias u opcionales:
const Book = Type.Object({
id: Type.Number(),
title: Type.String(),
author: sqlitype.ModelReference(Authors), // ⬅️ Obligatoria
editor: Type.Optional(sqlitype.ModelReference(Authors)) // ⬅️ Opcional
}, { $id : "Book" })Para las opcionales puedes filtrar por si tienen o no referencia:
// Libros sin editor
const sinEditor = await Books.find({ editor: null })
// Libros con editor
const conEditor = await Books.find({ editor: { $ne: null } })// Modelo Autor
const Book = Type.Object({
id: Type.Number(),
name: Type.String()
}, { $id : "Author" });
const Authors = new sqlitype.Model(Author);
// Modelo Libro (relacionado con Autor)
const Book = Type.Object({
id: Type.Number(),
title: Type.String(),
author: sqlitype.ModelReference(Authors) // ⬅️ Relación
}, { $id : "Book" })
const Books = new sqlitype.Model(Book);
// Uso
const author = await Authors.insert({ name: "Gabriel García Márquez" });
const book = await Books.insert({
title: "Cien años de soledad",
author: author // Asignamos la relación
});Luego se podrá filtrar por ese campo haciendo findAndJoin de varias maneras
const [bookWithAuthor] = await Books.findAndJoin({
id : 1,
author : {} // esto populará el autor del libro
})
const booksByAuthor = await Books.findAndJoin({
"author": {
name : "%Gabriel%"
} // esto filtrará todos los libros de un autor
});
const books = await Books.findAndJoin({
title : "%soledad%",
author : {
name : "%Gabriel%"
}
}); // todos los libros con soledad en el titulo escritos por alguien que se llame Gabriel 😳También puedes combinar operadores en los campos anidados:
// Libros cuyo autor NO se llame Gabriel, o libros sin autor
const libros = await Books.findAndJoin({
author: {
name: { $ne: "%Gabriel%" } // NOT LIKE
}
});
// Libros de varios autores específicos
const libros = await Books.findAndJoin({
author: {
id: { $in: [1, 2, 3] }
}
});Con relaciones opcionales puedes popular aunque no tengan referencia:
// Popula el author, aunque el editor sea null
const [book] = await Books.findAndJoin({
editor: null,
author: {}
});
// book.author está populado, book.editor es undefinedY con FindOptions puedes ordenar por campos anidados con type-safe:
const libros = await Books.findAndJoin(
{ author: { name: "%Gabriel%" } },
{ order: { title: "asc", "author.name": "desc" }, limit: 5 }
);const total = await Users.count(); // 5
const adultos = await Users.count({ age: { $gt: 18 } }); // 3
// También con relaciones
const libros = await Books.count({ author: { name: "%Gabriel%" } });| TypeBox | SQLite | Descripción |
|---|---|---|
| Type.String() | TEXT | Strings en general |
| Type.Number() | REAL | Números |
| Type.Boolean() | INTEGER | flags, booleanos.. |
| Type.Date() | INTEGER | fechas (almacenadas como timestamp) |
| Type.Object() | TEXT | datos JSON (almacenados como texto) |
| Type.Any() | TEXT | datos JSON (almacenados como texto) |
| Type.Array() | TEXT | listas (almacenadas como JSON) |