Adaptadores de base de datos

Comprende el contrato de persistencia colaborativa y elige entre Memory, SQLite y Database Adapters personalizados.

Un Database Adapter persiste los datos colaborativos principales que utiliza Collaboration Service. Puedes implementar IDatabaseAdapter para conectar cualquier base de datos que satisfaga su contrato de comportamiento completo. Service gestiona la semántica de Unit, OT y revisiones; Adapter la convierte en operaciones de persistencia atómicas y seguras ante reintentos.

Verificar la persistencia

Detén Quick Start y ejecuta pnpm example:database-adapter desde la raíz. Abre http://127.0.0.1:3010/?unit=persistent-sheet, modifica una celda y compruébala en otro navegador. Detén el servidor, repite el comando y abre la misma URL: el valor debe permanecer. El archivo es examples/database-adapter/.data/collaboration.sqlite; no lo borres entre ejecuciones. El servidor lee el Unit existente y llama a createUnitFromData() solo ante UNIT_NOT_FOUND. Consulta server/main.ts.

Qué almacena un Adapter

El Adapter principal almacena:

  • el registro Unit y la revisión actual de cabecera;
  • instantáneas con revisión;
  • conjuntos de cambios confirmados;
  • bloques referenciados por las instantáneas Sheet;
  • registros de idempotencia1 de envío con clave (unitID, sid, reqId);
  • estado de eliminación lógica, recuperación y eliminación permanente de Units.

No almacena por defecto usuarios, roles, ACL, directorios, archivos ni metadatos del producto. History, Thread Comment y Worktree tienen contratos Adapter independientes.

Semántica obligatoria

IDatabaseAdapter es más que una interfaz CRUD. La implementación debe mantener las garantías de corrección que espera Collaboration Service:

ContratoSignificado
Creación atómicaLa instantánea inicial y los bloques Sheet se hacen visibles juntos o no se hace visible ninguno
CAS2 de revisiónEl siguiente envío solo se confirma cuando la revisión de cabecera coincide con la esperada
Idempotencia de envíoReintentar el mismo (unitID, sid, reqId) devuelve el mismo resultado determinado
Cambios contiguosLos conjuntos de cambios confirmados y la revisión de la Unit mantienen continuidad
Visibilidad de instantáneasUna instantánea solo se puede leer tras almacenar todas sus dependencias
Ciclo de vida atómicoTodas las Units del lote de eliminación o recuperación tienen éxito, o ninguna
Marca de eliminación permanenteNo se puede volver a crear un ID de Unit eliminado permanentemente

Convertir cada método en una lectura o escritura normal sin implementar esta concurrencia y atomicidad rompe OT y la recuperación.

Memory Adapter

@univerjs-pro/collaboration-database-memory es una implementación dentro del proceso apropiada para:

  • pruebas unitarias y de integración;
  • ejemplos oficiales;
  • entornos temporales de desarrollo;
  • validación rápida de Middleware personalizados o del montaje del cliente.
TypeScript
import { MemoryDatabaseAdapter } from "@univerjs-pro/collaboration-database-memory";import { UniverCollabService } from "@univerjs-pro/collaboration-service";const database = new MemoryDatabaseAdapter();const service = new UniverCollabService({ dbAdapter: database });

Memory Adapter sigue el mismo contrato de revisión, CAS e idempotencia que las implementaciones persistentes, pero los datos viven en el proceso Node.js actual y se pierden cuando termina.

SQLite Adapter

@univerjs-pro/collaboration-database-sqlite es una implementación persistente SQLite para desarrollo, pruebas, aplicaciones locales y uso a pequeña escala.

TypeScript
import { mkdir } from "node:fs/promises";import { SQLiteDatabaseAdapter } from "@univerjs-pro/collaboration-database-sqlite";import { UniverCollabService } from "@univerjs-pro/collaboration-service";await mkdir("./data", { recursive: true });const database = new SQLiteDatabaseAdapter({  filename: "./data/collaboration.sqlite",  busyTimeoutMs: 5_000,});const service = new UniverCollabService({ dbAdapter: database });

SQLite Adapter usa claves externas y transacciones de escritura BEGIN IMMEDIATE para confirmar atómicamente. Crea el esquema actual en una base vacía. Rechaza esquemas incompletos o no compatibles en lugar de aplicar migraciones desconocidas automáticamente.

No cambia journal_mode ni decide la ubicación del archivo, la política de copias de seguridad u otros ajustes SQLite. SQLite está orientado a desarrollo, aplicaciones locales y pequeña escala.

Database Adapters personalizados

Un Adapter personalizado suele ser adecuado cuando:

  • la aplicación ya utiliza PostgreSQL, MySQL u otra base de negocio;
  • los envíos colaborativos deben integrarse con mecanismos de base específicos de la aplicación;
  • las necesidades operativas o de cumplimiento exceden el alcance previsto de SQLite.

Implementa IDatabaseAdapter e inyéctalo en Service igual que un Adapter integrado:

TypeScript
const database = new ApplicationCollaborationDatabase({ pool });const service = new UniverCollabService({ dbAdapter: database });

Service no depende de una base concreta, pero tampoco puede añadir transacciones, CAS o deduplicación a una implementación personalizada. Adapter debe implementar el contrato completo con las capacidades de su base de datos.

Context y límite de la aplicación

Cada llamada a Service pasa su customData a Middleware, Database Adapter y Events relacionados, pero el SDK no lo persiste automáticamente. Las decisiones de autorización suelen mantenerse en el Middleware de Service en vez de repetirse en los métodos del Adapter:

text
Middleware de Service  → permite o rechaza la operación de negocio  → Database Adapter  → almacena atómicamente el estado colaborativo autoritativo

Así, Memory, SQLite y los Adapters personalizados comparten la misma política de aplicación.

Si los datos de negocio requieren consistencia fuerte con los colaborativos, un Database Adapter personalizado puede leer valores de customData y escribir ambos conjuntos dentro de su transacción de base de datos.

Si no necesitan compartir la transacción colaborativa, Middleware o Events pueden escribirlos en otras tablas o sistemas externos. No comparten por defecto la transacción interna del Adapter. Los Events son notificaciones locales al proceso y de mejor esfuerzo emitidas tras un commit colaborativo correcto. La aplicación es responsable de la idempotencia, los reintentos y las compensaciones necesarios.

Almacenamiento de extensiones

History, Thread Comment y Worktree definen interfaces Database Adapter independientes. Sus Adapters pueden compartir una base o usar almacenamiento separado. Una implementación personalizada también puede proporcionar varias interfaces Adapter e inyectarse en los Services correspondientes.

Como son interfaces independientes, el SDK no proporciona transacciones entre módulos por defecto. Aunque compartan base de datos, la aplicación define la estrategia de consistencia entre módulos.

Elegir un Adapter

AdapterUso más adecuadoRestricción principal
MemoryPruebas, ejemplos, desarrollo temporalLos datos desaparecen al terminar el proceso
SQLiteDesarrollo, pruebas, aplicaciones locales, pequeña escalaBase de un archivo con ajustes a cargo de la aplicación
PersonalizadoBases existentes o integración de datos de negocioLa aplicación debe implementar y verificar el contrato completo

La aplicación es propietaria de cada Adapter inyectado. Libera Service antes de Adapter:

TypeScript
await service.dispose();await database.dispose();

Consulta el ejemplo de Database Adapter, ejecutable y con implementaciones para comparar.

Footnotes

  1. La idempotencia permite reintentar un envío sin aplicar sus cambios dos veces. Conserve el identificador original; el mismo contenido con otro identificador constituye un nuevo envío. ↩

  2. CAS actualiza un valor solo si aún coincide con el esperado. La comprobación de la revisión y la escritura siguiente deben ser atómicas para impedir que dos envíos concurrentes confirmen la misma revisión. ↩

¿Cómo es esta guía?

© 2026 DreamNum Co., Ltd.