Documentación
Base de datos
Persistencia de SQLite con propiedad de conexión con alcance, errores de consultas escritas, copias de seguridad de migración y almacenamiento de documentos local.
Traducido automáticamente del inglés. Ver el original en inglés
Modelo de persistencia
@cadcamfun/db abre SQLite a través de bun:sqlite y expone una base de datos de Drizzle dentro del Efecto Database servicio. El sidecar almacena su base de datos de aplicaciones en <dataDir>/cadcamfun.db. La conexión permite el registro de escritura anticipada y la aplicación de claves externas antes de aplicar las migraciones.
La base de datos contiene la copia de trabajo de los proyectos y sus recursos relacionados. Los objetos de control de versiones del proyecto, las solicitudes de extracción y las ejecuciones de flujo de trabajo también se almacenan localmente. Las credenciales del proveedor de IA utilizan un archivo JSON independiente legible por el propietario; el token del portador de la aplicación se proporciona en el momento del lanzamiento en lugar de persistir como preferencia.
Esquema y puntos de entrada
packages/db/src/schema.ts define tablas, índices, relaciones, valores predeterminados y tipos de selección/inserción inferidos. src/index.ts exportaciones Database, DatabaseError, la llovizna Db tipo y el espacio de nombres del esquema. src/upgrade.ts posee planificación de migración, copias de seguridad y aplicaciones transaccionales; src/migrate.ts es el punto de entrada de la línea de comandos. SQL generado y el diario de migración viven en packages/db/drizzle.
Cada conexión abierta por packages/db/src/index.ts cambia SQLite a journal_mode = WAL y se enciende foreign_keys. Cerrar las ejecuciones de la base de datos wal_checkpoint(TRUNCATE) por lo que el archivo principal estará completo cuando finalice el proceso. Si el punto de control está ocupado o falla, se registra una advertencia y el apagado continúa.
Valores almacenados
Los identificadores de dominio tienen de forma predeterminada UUID cuando están configurados en la tabla. Drizzle asigna columnas de marca de tiempo a fechas de JavaScript y columnas JSON a los tipos de registros declarados. Los repositorios HTTP convierten fechas en su representación cableada y validan JSON en el límite del dominio. Una anotación de tipo TypeScript en una columna JSON almacenada no prueba por sí sola que los datos importados o antiguos coincidan con el esquema de dominio actual.
Proyecta sus propios dibujos, componentes y trayectorias. Las trayectorias de herramientas pueden hacer referencia a un dibujo, material, cortador y configuración de la máquina; su data el campo puede llevar un documento de fabricación portátil. Las tablas de versiones de dibujos, componentes y trayectorias de herramientas separadas conservan las versiones de recursos además del historial Git de todo el proyecto.
Consulta a través del servicio.
Database.layer acepta una ruta de archivo o :memory:, una carpeta de migraciones opcional y un directorio de copia de seguridad opcional. Adquiere el identificador SQLite para su alcance de Efecto. Las personas que llaman usan query(operation, run) ejecutar trabajos de Llovizna sincrónicos o asincrónicos; las excepciones extranjeras se convierten DatabaseError con el nombre de la operación y la causa original.
import { Database, schema } from "@cadcamfun/db"
import { Effect } from "effect"
const loadProjects = Effect.gen(function* () {
const database = yield* Database
return yield* database.query("projects.list", (db) =>
db.select().from(schema.projectTable).all())
}) Los repositorios poseen reglas de acceso e interpretación de filas; el asistente de consulta compartido proporciona la atribución de errores. No autoriza automáticamente una consulta ni crea una transacción en torno a una secuencia arbitraria de llamadas de servicio.
Comportamiento de actualización
Al abrir una capa con una carpeta de migraciones se leen los archivos de migración y se comparan sus nombres con __drizzle_migrations. Un diario del formato anterior se rellena haciendo coincidir las marcas de tiempo y los hashes de migración. Un diario que no se puede comparar falla en la actualización en lugar de adivinar una identidad de migración.
Cuando una base de datos existente en disco necesita una actualización, el servicio crea una copia de seguridad SQLite previa a la migración usando VACUUM INTO. Escribe en un archivo parcial, cambia el nombre de la instantánea completa y conserva las últimas cinco copias de seguridad previas a la migración coincidentes. El directorio de copia de seguridad predeterminado es backups al lado de la base de datos. Las bases de datos nuevas y en memoria no necesitan esta copia de seguridad previa a la actualización.
Los cambios de diario y las declaraciones SQL pendientes se ejecutan en una transacción inmediata. Una migración fallida revierte la transacción y falla la construcción de la capa. La aplicación no debe anunciar que está lista cuando falla la inicialización de la base de datos.
Comandos de migración
bun run db:generate
bun run db:migrate Generar migraciones después de cambiar schema.tsy mantenga juntos el SQL y el diario generados. La CLI de migración utiliza DATABASE_URL cuando se suministra, elimina una inicial file: prefijo, y de lo contrario se abre ./data/cadcamfun.db. El contenedor raíz ejecuta este script con packages/db como su directorio de trabajo, por lo que su destino predeterminado es packages/db/data/cadcamfun.db. Usos de Web Studio data/cadcamfun.db en la raíz del repositorio. El sidecar empaquetado utiliza CADCAMFUN_DATA_DIR. Seleccione una ruta de base de datos explícita cuando ejecute el mantenimiento para que estos puntos de entrada no operen en diferentes archivos sin querer.
Sin una carpeta de migraciones, Database.layer abre la base de datos pero no crea tablas de aplicación. Las pruebas que necesitan el esquema completo proporcionan al directorio de migración una base de datos en memoria.
Dominios de datos
- Identidad y colaboración: usuarios, cuentas, sesiones, autenticadores, tokens de verificación, suscripciones, organizaciones, membresías, invitaciones, conversaciones, mensajes, notificaciones y suscripciones push.
- Diseño y fabricación: proyectos, dibujos, componentes, sus versiones y comentarios, materiales, herramientas, configuraciones de máquinas, trayectorias de herramientas y registros de costos de producción.
- Servicios de aplicación: archivos, elementos de biblioteca, registro/configuración de complementos, claves API, registros de actividad y análisis, datos del mercado, configuración de MCP y entradas de valores-clave locales.
- Historia del proyecto: Repositorios, objetos y referencias de Git, solicitudes de extracción y revisiones, y ejecuciones de flujos de trabajo.
La presencia de una tabla establece un modelo de persistencia. Las integraciones de red y las características de las aplicaciones dependen de sus controladores y servicios reales, más que de la existencia de la tabla únicamente.
Almacenamiento local de valores-clave
LocalKeyValue persiste el estado de la aplicación compatible con JSON. el sidecar KvStore admite obtener, insertar, eliminar y obtener listados filtrados por prefijos. Las claves deben contener texto sin espacios en blanco y tener como máximo 512 caracteres. Los valores permiten nulos, cadenas, booleanos, números finitos, matrices y objetos simples; Se rechazan ciclos, funciones, números no finitos y objetos no simples.
Las entradas incluyen una marca de tiempo de actualización. Las actualizaciones reemplazan el valor de la misma clave. El listado de prefijos ordena por clave y filtra en la memoria. Utilice esta tienda para conocer las preferencias y el contexto del asistente; Las credenciales pertenecen a su servicio de credenciales dedicado.
Apagado y recuperación
Las solicitudes del finalizador del alcance de la base de datos PRAGMA wal_checkpoint(TRUNCATE) antes de cerrar la manija. Un punto de control ocupado o un error de cierre se registra como una advertencia para que se puedan ejecutar los finalizadores restantes. El propietario de la conexión, en lugar de los controladores HTTP individuales, cierra el identificador.
Para realizar una copia de seguridad manual consistente, detenga la aplicación limpiamente antes de copiar su directorio de datos, o use las funciones de copia de seguridad de SQLite mientras se está ejecutando. Una base de datos activa puede tener estado en su archivo WAL. Para inspeccionar una copia de seguridad previa a la migración, trabaje en una copia y conserve la base de datos original y las copias de seguridad hasta que se confirme la recuperación.
Responsabilidades del repositorio
Mantenga la lógica de migración y creación de conexiones SQLite en este paquete. Los repositorios Sidecar utilizan el servicio de base de datos proporcionado y asignan fallas de almacenamiento a los errores HTTP de su recurso. Mantenga el cálculo de la geometría en el núcleo y los secretos en los servicios de credenciales o tokens. Las copias de seguridad de la base de datos contienen datos de proyectos y cuentas y necesitan los mismos controles de acceso que el directorio de datos activo.
Inspeccionar una base de datos sin cambiarla
Después de un apagado limpio, utilice la CLI de SQLite en una ruta de base de datos explícita. El modo de solo lectura evita la creación accidental de un nuevo archivo vacío cuando una ruta es incorrecta. Estas comprobaciones inspeccionan la estructura y la coherencia sin exponer filas de cuentas o credenciales.
sqlite3 -readonly /absolute/path/cadcamfun.db 'PRAGMA quick_check;'
sqlite3 -readonly /absolute/path/cadcamfun.db 'PRAGMA foreign_key_check;'
sqlite3 -readonly /absolute/path/cadcamfun.db "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;"
sqlite3 -readonly /absolute/path/cadcamfun.db 'SELECT name, applied_at FROM __drizzle_migrations ORDER BY id;' quick_check normalmente informa ok; foreign_key_check devuelve filas solo para violaciones. Un diario sin name/applied_at columns es un formato más antiguo, que el código de actualización de la aplicación debe conciliar antes de utilizar la última consulta. Verifique las rutas y la identidad de la migración antes de intentar realizar reparaciones.
La aplicación establece la aplicación de claves externas en su propia conexión. Una conexión de mantenimiento abierta por separado debe habilitarla explícitamente antes de realizar escrituras autorizadas; PRAGMA es específico de la conexión.
Fuente: packages/db/src/index.ts, upgrade.ts, y database.test.ts.
Utilice una base de datos desechable en el código
Un alcance completo del servicio en memoria es útil para ejemplos de repositorio y diagnósticos aislados. El alcance es propietario de la conexión y aplica las migraciones registradas antes de que el efecto lea el servicio.
import { Database, schema } from "@cadcamfun/db"
import { Effect } from "effect"
const migrationsFolder = new URL("../../packages/db/drizzle", import.meta.url).pathname
const program = Effect.gen(function* () {
const database = yield* Database
return yield* database.query("diagnostic.projects", (db) =>
db.select({ id: schema.projectTable.id }).from(schema.projectTable).all())
}).pipe(Effect.provide(Database.layer({ path: ":memory:", migrationsFolder })))
console.log(await Effect.runPromise(program)) // [] Guarde este script como apps/sidecar/diagnostic.ts y correr bun apps/sidecar/diagnostic.ts desde la raíz del repositorio. Este paquete proporciona la base de datos y las dependencias de Effect, y la URL de migración se resuelve a partir de ese archivo. Un directorio de migraciones faltante o incorrecto no equivale a un esquema de aplicación vacío. la llovizna query la devolución de llamada puede ejecutar una consulta sincrónica o devolver una promesa; el ayudante envuelve las fallas debajo de la etiqueta de operación suministrada.
Fuente: packages/db/src/database.test.ts y index.ts.
Diagnosticar fallas de almacenamiento
- No existe tal tabla: confirme el archivo de base de datos exacto y la carpeta de migraciones. El servicio permite la apertura sin migraciones, pero la consulta de las tablas de la aplicación falla.
- La restricción de clave externa falló: inspeccione el propietario/proyecto/recurso al que se hace referencia. Las restricciones de eliminación son específicas del esquema: la eliminación de un usuario con un proyecto de su propiedad está restringida, mientras que las filas de la sesión caen en cascada cuando se elimina su usuario.
- La copia de seguridad falló durante la actualización: verifique el espacio libre y el acceso de escritura en la base de datos y en los directorios de respaldo. La falla de la copia de seguridad detiene la migración; no elimine la base de datos original para evitarla.
- El diario de migración heredado no coincide con las migraciones locales: preservar la base de datos y recuperar el conjunto de migración coincidente. Cambiar el nombre o eliminar filas del diario ocultaría el historial de actualizaciones incompatibles.
- Punto de control ocupado al apagar: inspeccione otras conexiones abiertas antes de copiar solo el archivo de base de datos. Conserve los hermanos WAL/SHM hasta que se complete un punto de control limpio o una copia de seguridad SQLite consistente.
Las copias de seguridad de la migración son puntos de recuperación previos a la actualización con una política de retención de cinco archivos, no copias de seguridad continuas de ediciones posteriores. Haga una copia de seguridad del directorio de datos completo por separado de acuerdo con los requisitos de recuperación de la instalación.
Fuente: packages/db/src/index.ts, upgrade.ts, schema.ts, y database.test.ts.
Historia de migración
Las migraciones registradas se aplican en orden de carpeta. Cada carpeta contiene un migration.sql y el kit de llovizna snapshot.json utilizado para calcular la siguiente diferencia. La fila del diario almacena el nombre de la carpeta, su hash y la marca de tiempo de la carpeta en milisegundos (created_at) y la hora ISO en la que se aplicó (applied_at).
| Carpeta | Tamaño | lo que crea |
|---|---|---|
20261004223029_careful_monster_badoon | 771 líneas | El esquema de referencia convertido del modelo Prisma heredado: identidad, organizaciones, proyectos, dibujos, componentes, rutas de herramientas, bibliotecas, costos, mercado, complementos y configuración de MCP. Los nombres de tablas y columnas no han cambiado, por lo que son más antiguos. dev.db los datos permanecen legibles. |
20261008030518_redundant_dagger | 73 líneas | Historia del proyecto: GitRepo, GitObject, GitRef, PullRequest, PullRequestReview, WorkflowRun. |
20261009012351_moaning_loki | 28 líneas | Registros del núcleo de geometría: MeshAsset y CadOperationLog. |
20261009171602_brown_mad_thinker | 5 líneas | LocalKeyValue (key clave primaria de texto, value texto no nulo, updatedAt entero no nulo). |
Los nombres de las carpetas provienen de drizzle-kit: una marca de tiempo UTC seguida de un nombre aleatorio. Nunca cambie el nombre ni edite una carpeta una vez que se haya aplicado en cualquier lugar; el planificador de actualizaciones identifica las migraciones aplicadas por nombre y, para revistas más antiguas, por marca de tiempo y hash.
Flujo de decisión de actualización
| Situación | Respaldo | Resultado |
|---|---|---|
| Nuevo archivo, carpeta de migraciones proporcionada | No (el archivo no existía antes de abrirlo) | Todas las migraciones aplicadas en una sola transacción. |
| Archivo existente, diario actualizado. | No | Abierto tal cual; needed es falso |
| Archivo existente, migraciones pendientes | Sí, antes de cualquier cambio. | Se aplicaron migraciones pendientes; filas del diario insertadas con applied_at |
Archivo existente, diario sin name columna | Sí | columnas name y applied_at agregado, filas rellenadas, luego se aplican migraciones pendientes |
| La fila heredada no coincide con ninguna migración local | No se realiza ninguna copia de seguridad: la planificación falla primero | DatabaseError con operación migrate: "El diario de migración heredado no coincide con las migraciones locales" |
:memory: | Nunca | Migraciones aplicadas si se proporciona una carpeta. |
| Sin carpeta de migraciones | Nunca | Abierto sin crear tablas. |
La coincidencia de reabastecimiento funciona así: una fila heredada created_at se trunca a segundos completos y se compara con la marca de tiempo de la carpeta de cada migración. Si exactamente una migración coincide, se utiliza. De lo contrario, el hash rompe el empate y, como último recurso, se utiliza cualquier migración con el mismo hash.
Anatomía del archivo de respaldo
backups/cadcamfun-20261015-034512.123-6f1c2d4e-8a90-4b7c-9e21-3f5a6b7c8d9e-pre-migrate.db
└ prefix ┘ └ UTC timestamp ┘ └ random UUID ┘ - El prefijo es el nombre del archivo de la base de datos sin
.db, seguido de un guión. - La marca de tiempo es UTC con precisión de milisegundos. Si la copia de seguridad existente más reciente tiene la misma marca de tiempo o una posterior, la nueva se establece 1 ms después, por lo que el orden léxico siempre es igual al orden de creación.
- La instantánea se escribe en
<name>.partialconVACUUM INTO, luego renombrado. Un accidente deja como máximo un.partialarchivo, que se elimina en unfinallybloquear. - Después de una copia de seguridad exitosa, solo se conservan los cinco archivos más nuevos que coincidan con el patrón exacto. Los archivos que copia en el directorio con otros nombres nunca se eliminan.
VACUUM INTOproduce una base de datos compacta y autónoma sin ningún hermano WAL, por lo que se puede abrir una copia de seguridad directamente.
DATA="/absolute/path/to/data"
cp "$DATA/cadcamfun.db" "$DATA/cadcamfun.db.broken" # keep the failed state
rm -f "$DATA/cadcamfun.db-wal" "$DATA/cadcamfun.db-shm"
cp "$DATA/backups/cadcamfun-<timestamp>-<uuid>-pre-migrate.db" "$DATA/cadcamfun.db"
sqlite3 -readonly "$DATA/cadcamfun.db" 'PRAGMA quick_check;' # expect: ok La restauración revierte el esquema y los datos
Una copia de seguridad restaurada es el estado justo antes de la actualización. Las ediciones realizadas después de esa actualización no están incluidas y el próximo lanzamiento intentará la misma migración nuevamente. Corrija la causa del fallo antes de reiniciar.
Formas de mesa central
Estas son las tablas que el editor de escritorio, el historial y el kernel leen con más frecuencia. Los nombres de las columnas son los nombres SQL; Drizzle los expone bajo las mismas teclas.
| Mesa | columnas |
|---|---|
Project | id, name, description, createdAt, updatedAt, ownerId, organizationId, isPublic |
Drawing | id, name, description, data, thumbnail, createdAt, updatedAt, projectId |
Component | id, name, description, data, thumbnail, type, isPublic, createdAt, updatedAt, projectId |
Toolpath | id, name, description, data, type, operationType, gcode, thumbnail, isPublic, projectId, createdAt, updatedAt, createdBy, drawingId, materialId, toolId, machineConfigId |
GitRepo | projectId, head, remote, createdAt |
GitObject | projectId, oid, type, body |
GitRef | projectId, name, oid |
PullRequest | id, projectId, number, title, body, head, base, state, authorId, authorKind, mergeCommit, remoteNumber, createdAt, updatedAt |
WorkflowRun | id, projectId, workflow, event, ref, sha, status, events, startedAt, finishedAt |
MeshAsset | id, projectId, kind, inputs, stats, mesh, createdAt |
CadOperationLog | id, projectId, operation, meshAssetId, outcome, durationMs, inputs, error, traceId, spanId, createdAt |
LocalKeyValue | key, value, updatedAt |
Al eliminar un proyecto, se eliminan sus dibujos, componentes, rutas de herramientas, objetos históricos y referencias, solicitudes de extracción, ejecuciones de flujo de trabajo, activos de malla y registros de operaciones. Las filas de versión y comentarios caen en cascada desde su recurso principal. A CadOperationLog la fila sobrevive a la eliminación de su activo de malla con meshAssetId establecido en nulo. Los enlaces de rutas de herramienta a materiales, herramientas y configuraciones de máquinas no tienen cascada, por lo que un material al que se hace referencia no se puede eliminar mientras una ruta de herramienta apunte a él.
Catálogo de mesa completo
El esquema define 60 tablas. La siguiente lista se produjo a partir de las definiciones de la tabla Drizzle: nombre SQL, constante exportada, recuento de columnas, número de índices declarados y claves externas con su regla de eliminación. "sin acción" es el valor predeterminado de SQLite: la eliminación falla mientras existen filas de referencia (las claves externas se aplican en la conexión de la aplicación).
| tabla SQL | Exportar | columnas | identificación | claves foráneas |
|---|---|---|---|---|
Account | accountTable | 13 | 1 | userId → Usuario (cascada) |
ActivityLog | activityLogTable | 7 | 0 | userId → Usuario (sin acción) |
AIAnalysisLog | aIAnalysisLogTable | 7 | 0 | userId → Usuario (sin acción) |
ApiKey | apiKeyTable | 7 | 1 | userId → Usuario (cascada) |
authenticators | authenticatorTable | 8 | 0 | userId → Usuario (cascada) |
CadOperationLog | cadOperationLogTable | 11 | 1 | projectId → Proyecto (cascada)meshAssetId → MeshAsset (establecer nulo) |
Component | componentTable | 10 | 0 | projectId → Proyecto (cascada) |
ComponentComment | componentCommentTable | 6 | 0 | componentId → Componente (cascada)userId → Usuario (sin acción) |
ComponentVersion | componentVersionTable | 6 | 0 | componentId → Componente (cascada)userId → Usuario (sin acción) |
Conversation | conversationTable | 6 | 0 | organizationId → Organización (cascada) |
ConversationParticipant | conversationParticipantTable | 5 | 1 | userId → Usuario (cascada)conversationId → Conversación (cascada) |
CostSettings | costSettingsTable | 11 | 0 | ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
Drawing | drawingTable | 8 | 0 | projectId → Proyecto (cascada) |
DrawingVersion | drawingVersionTable | 5 | 0 | drawingId → Dibujo (cascada) |
FileUpload | fileUploadTable | 12 | 0 | — |
GitObject | gitObjectTable | 4 | 1 | projectId → Proyecto (cascada) |
GitRef | gitRefTable | 3 | 1 | projectId → Proyecto (cascada) |
GitRepo | gitRepoTable | 4 | 0 | projectId → Proyecto (cascada) |
LibraryItem | libraryItemTable | 14 | 0 | ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
LocalKeyValue | localKeyValueTable | 3 | 0 | — |
MachineConfig | machineConfigTable | 9 | 0 | ownerId → Usuario (sin acción) |
MarketplaceCategory | marketplaceCategoryTable | 10 | 1 | parentId → Categoría de mercado (sin acción) |
MarketplaceCollection | marketplaceCollectionTable | 10 | 1 | sellerId → MarketplaceSeller (sin acción) |
MarketplaceCollectionItem | marketplaceCollectionItemTable | 5 | 1 | collectionId → MarketplaceCollection (cascada)itemId → MarketplaceItem (cascada) |
MarketplaceItem | marketplaceItemTable | 30 | 5 | sellerId → MarketplaceSeller (sin acción) |
MarketplaceOrder | marketplaceOrderTable | 23 | 4 | buyerId → Usuario (sin acción)sellerId → MarketplaceSeller (sin acción)itemId → MarketplaceItem (sin acción) |
MarketplacePayout | marketplacePayoutTable | 12 | 1 | sellerId → MarketplaceSeller (sin acción) |
MarketplaceReview | marketplaceReviewTable | 12 | 2 | itemId → MarketplaceItem (sin acción)buyerId → Usuario (sin acción)orderId → Orden de Marketplace (sin acción) |
MarketplaceSeller | marketplaceSellerTable | 19 | 0 | userId → Usuario (sin acción) |
MarketplaceWishlist | marketplaceWishlistTable | 4 | 1 | userId → Usuario (cascada)itemId → MarketplaceItem (cascada) |
Material | materialTable | 9 | 0 | ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
MaterialCost | materialCostTable | 10 | 0 | materialId → Material (cascada)ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
MCPServerConfig | mCPServerConfigTable | 11 | 1 | — |
MeshAsset | meshAssetTable | 7 | 1 | projectId → Proyecto (cascada) |
Message | messageTable | 8 | 1 | senderId → Usuario (cascada)conversationId → Conversación (cascada)fileId → FileUpload (establecer nulo) |
Notification | notificationTable | 12 | 0 | userId → Usuario (cascada)organizationId → Organización (cascada)projectId → Proyecto (cascada)componentId → Componente (cascada)messageId → Mensaje (cascada) |
OperationCost | operationCostTable | 10 | 0 | ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
Organization | organizationTable | 5 | 0 | — |
OrganizationInvitation | organizationInvitationTable | 7 | 0 | organizationId → Organización (cascada) |
passkey | passkeyTable | 11 | 0 | userId → Usuario (cascada) |
PluginConfiguration | pluginConfigurationTable | 2 | 0 | pluginId → PluginRegistryEntry (cascada) |
PluginRegistryEntry | pluginRegistryEntryTable | 9 | 0 | — |
ProductionCostEstimate | productionCostEstimateTable | 17 | 0 | toolpathId → Ruta de herramienta (cascada)operationCostId → Costo de operación (sin acción)ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
Project | projectTable | 8 | 0 | ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
PullRequest | pullRequestTable | 14 | 1 | projectId → Proyecto (cascada)authorId → Usuario (sin acción) |
PullRequestReview | pullRequestReviewTable | 8 | 1 | pullRequestId → PullRequest (cascada)reviewerId → Usuario (sin acción) |
PushSubscription | pushSubscriptionTable | 6 | 0 | userId → Usuario (cascada) |
Session | sessionTable | 8 | 0 | userId → Usuario (cascada) |
Subscription | subscriptionTable | 24 | 3 | referenceId → Usuario (cascada) |
Token | tokenTable | 8 | 2 | userId → Usuario (cascada) |
Tool | toolTable | 17 | 0 | ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
Toolpath | toolpathTable | 17 | 0 | projectId → Proyecto (cascada)createdBy → Usuario (sin acción)drawingId → Dibujo (sin acción)materialId → Material (sin acción)toolId → Herramienta (sin acción)machineConfigId → MachineConfig (sin acción) |
ToolpathComment | toolpathCommentTable | 6 | 0 | toolpathId → Ruta de herramienta (cascada)userId → Usuario (sin acción) |
ToolpathVersion | toolpathVersionTable | 7 | 0 | toolpathId → Ruta de herramienta (cascada)userId → Usuario (sin acción) |
ToolWearCost | toolWearCostTable | 10 | 0 | toolId → Herramienta (cascada)ownerId → Usuario (sin acción)organizationId → Organización (sin acción) |
twoFactor | twoFactorTable | 4 | 0 | userId → Usuario (cascada) |
User | userTable | 17 | 0 | — |
UserOrganization | userOrganizationTable | 5 | 1 | userId → Usuario (cascada)organizationId → Organización (cascada) |
verification_tokens | verificationTokenTable | 3 | 1 | — |
WorkflowRun | workflowRunTable | 10 | 1 | projectId → Proyecto (cascada) |
Escribir de forma segura
- Envuelva los cambios de varias declaraciones en una transacción de Drizzle dentro de una
queryllamar:database.query("op", (db) => db.transaction((tx) => ...)). Separadoquerylas llamadas son transacciones separadas. - Asigne a cada consulta un nombre de operación estable con puntos (
projects.list,toolpaths.update). Es el único contexto en el queDatabaseErrorlleva además de la causa y aparece en los registros. - Generar identificaciones con
newId()(un UUID) o dejar que la columna lo haga por defecto; no cree identificadores a partir de nombres. - Valide las columnas JSON al leerlas. SQLite no verifica el tipo TypeScript declarado.
- Usar
LocalKeyValuea través del servicio de valor-clave sidecar en lugar de escribir filas directamente, por lo que se aplica la validación de clave y valor.
import { Database, schema } from "@cadcamfun/db"
import { eq } from "drizzle-orm"
import { Effect } from "effect"
const renameProject = (id: string, name: string) => Effect.gen(function* () {
const database = yield* Database
return yield* database.query("projects.rename", (db) =>
db.transaction((tx) => {
const updated = tx.update(schema.projectTable)
.set({ name, updatedAt: new Date() })
.where(eq(schema.projectTable.id, id))
.returning().all()
if (updated.length === 0) throw new Error(`project ${id} not found`)
return updated[0]
}))
}) Para crear filas con identificadores explícitos, utilice schema.newId(); @cadcamfun/db reexporta el módulo de esquema como el schema espacio de nombres, no como nombres de nivel superior.