Documentação
Banco de dados
Persistência SQLite com propriedade de conexão com escopo definido, falhas de consulta digitada, backups de migração e armazenamento local de documentos.
Traduzido automaticamente do inglês. Ver o original em inglês
Modelo de persistência
@cadcamfun/db abre SQLite através bun:sqlite e expõe um banco de dados Drizzle dentro do Effect Database serviço. O sidecar armazena seu banco de dados de aplicativos em <dataDir>/cadcamfun.db. A conexão permite o registro em log write-ahead e a aplicação de chave estrangeira antes de aplicar migrações.
O banco de dados contém a cópia de trabalho dos projetos e seus recursos relacionados. Objetos de controle de versão do projeto, solicitações pull e execuções de fluxo de trabalho também são armazenados localmente. As credenciais do provedor de IA usam um arquivo JSON separado, legível pelo proprietário; o token de portador do aplicativo é fornecido na inicialização, em vez de persistido como preferência.
Esquema e pontos de entrada
packages/db/src/schema.ts define tabelas, índices, relacionamentos, padrões e tipos de seleção/inserção inferidos. src/index.ts exportações Database, DatabaseError, a garoa Db tipo e o namespace do esquema. src/upgrade.ts possui planejamento de migração, backups e aplicativos transacionais; src/migrate.ts é o ponto de entrada da linha de comando. O SQL gerado e o diário de migração residem em packages/db/drizzle.
Cada conexão aberta por packages/db/src/index.ts muda SQLite para journal_mode = WAL e liga foreign_keys. Fechando as execuções do banco de dados wal_checkpoint(TRUNCATE) portanto, o arquivo principal estará completo quando o processo for encerrado. Se o ponto de verificação estiver ocupado ou falhar, um aviso será registrado e o encerramento continuará.
Valores armazenados
Os identificadores de domínio são UUIDs padrão quando configurados pela tabela. O Drizzle mapeia colunas de carimbo de data/hora para datas JavaScript e colunas JSON para os tipos de registro declarados. Os repositórios HTTP convertem datas em sua representação de transferência e validam JSON no limite do domínio. Uma anotação de tipo TypeScript em uma coluna JSON armazenada não prova por si só que os dados importados ou mais antigos correspondem ao esquema de domínio atual.
Projeta seus próprios desenhos, componentes e percursos da ferramenta. Os percursos da ferramenta podem fazer referência a um desenho, material, cortador e configuração de máquina; deles data campo pode conter um documento de fabricação portátil. Tabelas separadas de versão de desenho, componente e percurso retêm versões de recursos, além do histórico Git de todo o projeto.
Consulta através do serviço
Database.layer aceita um caminho de arquivo ou :memory:, uma pasta de migração opcional e um diretório de backup opcional. Ele adquire o identificador SQLite para seu escopo Effect. Os chamadores usam query(operation, run) para executar trabalho Drizzle síncrono ou assíncrono; exceções estrangeiras tornam-se DatabaseError com o nome da operação e a 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())
}) Os repositórios possuem regras de acesso e interpretação de linhas; o auxiliar de consulta compartilhada fornece atribuição de falha. Ele não autoriza automaticamente uma consulta nem cria uma transação em torno de uma sequência arbitrária de chamadas de serviço.
Comportamento de atualização
Abrir uma camada com uma pasta de migrações lê os arquivos de migração e compara seus nomes com __drizzle_migrations. Um diário do formato mais antigo é preenchido com carimbos de data/hora e hashes de migração correspondentes. Um diário incomparável falha na atualização em vez de adivinhar uma identidade de migração.
Quando um banco de dados existente em disco precisa de atualização, o serviço cria um backup SQLite de pré-migração usando VACUUM INTO. Ele grava em um arquivo parcial, renomeia o instantâneo concluído e retém os últimos cinco backups de pré-migração correspondentes. O diretório de backup padrão é backups ao lado do banco de dados. Os bancos de dados novos e na memória não precisam desse backup pré-atualização.
As alterações do diário e as instruções SQL pendentes são executadas em uma transação imediata. Uma migração com falha reverte a transação e falha na construção da camada. O aplicativo não deve anunciar a prontidão quando a inicialização do banco de dados falhar.
Comandos de migração
bun run db:generate
bun run db:migrate Gerar migrações após alterar schema.tse mantenha o SQL gerado e o diário juntos. A CLI de migração usa DATABASE_URL quando fornecido, remove uma inicial file: prefixo e, caso contrário, abre ./data/cadcamfun.db. O wrapper raiz executa este script com packages/db como seu diretório de trabalho, então seu destino padrão é packages/db/data/cadcamfun.db. O Web Studio usa data/cadcamfun.db na raiz do repositório. O sidecar embalado usa CADCAMFUN_DATA_DIR. Selecione um caminho de banco de dados explícito ao executar a manutenção para que esses pontos de entrada não operem involuntariamente em arquivos diferentes.
Sem uma pasta de migrações, Database.layer abre o banco de dados, mas não cria tabelas de aplicativos. Os testes que precisam do esquema completo fornecem ao diretório de migração um banco de dados na memória.
Domínios de dados
- Identidade e colaboração: usuários, contas, sessões, autenticadores, tokens de verificação, assinaturas, organizações, associações, convites, conversas, mensagens, notificações e assinaturas push.
- Projeto e fabricação: projetos, desenhos, componentes, suas versões e comentários, materiais, ferramentas, configurações de máquinas, percursos da ferramenta e registros de custos de produção.
- Serviços de aplicativos: arquivos, itens de biblioteca, registro/configuração de plug-ins, chaves de API, análises e registros de atividades, dados de mercado, configuração de MCP e entradas de valores-chave locais.
- Histórico do projeto: Repositórios Git, objetos e referências, pull requests e revisões e execuções de fluxo de trabalho.
A presença de uma tabela estabelece um modelo de persistência. As integrações de rede e os recursos de aplicativos dependem de seus manipuladores e serviços reais, e não apenas da existência de tabelas.
Armazenamento local de valores-chave
LocalKeyValue persiste o estado do aplicativo compatível com JSON. O sidecar KvStore suporta listagem get, upsert, delete e filtrada por prefixo. As chaves devem conter texto sem espaços em branco e ter no máximo 512 caracteres. Os valores permitem nulos, strings, booleanos, números finitos, matrizes e objetos simples; ciclos, funções, números não finitos e objetos não simples são rejeitados.
As entradas incluem um carimbo de data/hora de atualização. Upserts substituem o valor da mesma chave. A listagem de prefixos classifica por chave e filtra na memória. Use esta loja para preferências e contexto do assistente; as credenciais pertencem ao seu serviço de credenciais dedicado.
Desligamento e recuperação
As solicitações do finalizador de escopo do banco de dados PRAGMA wal_checkpoint(TRUNCATE) antes de fechar a alça. Um ponto de verificação ocupado ou falha de fechamento é registrado como um aviso para que os finalizadores restantes possam ser executados. O proprietário da conexão, em vez de manipuladores HTTP individuais, fecha o identificador.
Para um backup manual consistente, pare o aplicativo antes de copiar seu diretório de dados ou use os recursos de backup do SQLite enquanto ele estiver em execução. Um banco de dados ativo pode ter estado em seu arquivo WAL. Para inspecionar um backup pré-migração, trabalhe em uma cópia e preserve o banco de dados e os backups originais até que a recuperação seja confirmada.
Responsabilidades do repositório
Mantenha a lógica de criação e migração da conexão SQLite neste pacote. Os repositórios secundários usam o serviço de banco de dados fornecido e mapeiam falhas de armazenamento para os erros HTTP de seus recursos. Mantenha o cálculo da geometria no núcleo e os segredos nos serviços de credenciais ou tokens. Os backups de banco de dados contêm dados de projetos e contas e precisam dos mesmos controles de acesso que o diretório de dados ativo.
Inspecione um banco de dados sem alterá-lo
Após um desligamento limpo, use a CLI do SQLite em um caminho de banco de dados explícito. O modo somente leitura evita a criação acidental de um novo arquivo vazio quando um caminho está errado. Essas verificações inspecionam a estrutura e a consistência sem expor linhas de contas ou credenciais.
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 relata ok; foreign_key_check retorna linhas apenas para violações. Um diário sem name/applied_at colunas é um formato mais antigo, que o código de atualização do aplicativo deve reconciliar antes de usar a última consulta. Verifique os caminhos e a identidade da migração antes de tentar reparos.
O aplicativo define a imposição de chave estrangeira em sua própria conexão. Uma conexão de manutenção aberta separadamente deve habilitá-la explicitamente antes de fazer gravações autorizadas; o PRAGMA é específico da conexão.
Fonte: packages/db/src/index.ts, upgrade.ts, e database.test.ts.
Use um banco de dados descartável no código
Um escopo completo de serviço na memória é útil para exemplos de repositório e diagnósticos isolados. O escopo possui a conexão e aplica as migrações com check-in antes que o efeito leia o serviço.
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)) // [] Salve este script como apps/sidecar/diagnostic.ts e correr bun apps/sidecar/diagnostic.ts da raiz do repositório. Este pacote fornece o banco de dados e as dependências do Effect, e a URL de migração é resolvida a partir desse arquivo. Um diretório de migração ausente ou incorreto não é equivalente a um esquema de aplicativo vazio. A garoa query o retorno de chamada pode executar uma consulta síncrona ou retornar uma promessa; o auxiliar agrupa as falhas no rótulo de operação fornecido.
Fonte: packages/db/src/database.test.ts e index.ts.
Diagnosticar falhas de armazenamento
- Não existe tal tabela: confirme o arquivo de banco de dados exato e a pasta de migrações. A abertura sem migrações é permitida pelo serviço, mas a consulta às tabelas do aplicativo falha.
- Falha na restrição de chave estrangeira: inspecione o proprietário/projeto/recurso referenciado. As restrições de exclusão são específicas do esquema: a exclusão de um usuário com um projeto próprio é restrita, enquanto as linhas da sessão ficam em cascata quando o usuário é excluído.
- Falha no backup durante a atualização: verifique o espaço livre e o acesso de gravação no banco de dados e nos diretórios de backup. Falha no backup interrompe a migração; não exclua o banco de dados original para ignorá-lo.
- O diário de migração legado não corresponde às migrações locais: preserve o banco de dados e recupere o conjunto de migração correspondente. Renomear ou remover linhas do diário ocultaria o histórico de atualizações incompatíveis.
- Ponto de verificação ocupado no desligamento: inspecione outras conexões abertas antes de copiar apenas o arquivo de banco de dados. Retenha os irmãos WAL/SHM até que um ponto de verificação limpo ou um backup SQLite consistente seja concluído.
Os backups de migração são pontos de recuperação pré-atualização com uma política de retenção de cinco arquivos, e não backup contínuo de edições subsequentes. Faça backup do diretório de dados completo separadamente de acordo com os requisitos de recuperação da instalação.
Fonte: packages/db/src/index.ts, upgrade.ts, schema.ts, e database.test.ts.
Histórico de migração
As migrações com check-in são aplicadas na ordem das pastas. Cada pasta contém um migration.sql e o kit de garoa snapshot.json usado para calcular a próxima diferença. A linha do diário armazena o nome da pasta, seu hash, o carimbo de data/hora da pasta em milissegundos (created_at) e a hora ISO em que foi aplicado (applied_at).
| Pasta | Tamanho | O que isso cria |
|---|---|---|
20261004223029_careful_monster_badoon | 771 linhas | O esquema de linha de base convertido do modelo Prisma legado: identidade, organizações, projetos, desenhos, componentes, percursos da ferramentas, bibliotecas, custos, mercado, plug-ins e configuração de MCP. Os nomes das tabelas e colunas permanecem inalterados, portanto são mais antigos dev.db os dados permanecem legíveis. |
20261008030518_redundant_dagger | 73 linhas | Histórico do projeto: GitRepo, GitObject, GitRef, PullRequest, PullRequestReview, WorkflowRun. |
20261009012351_moaning_loki | 28 linhas | Registros do kernel de geometria: MeshAsset e CadOperationLog. |
20261009171602_brown_mad_thinker | 5 linhas | LocalKeyValue (key chave primária de texto, value texto não nulo, updatedAt inteiro não nulo). |
Os nomes das pastas vêm do drizzle-kit: um carimbo de data/hora UTC seguido por um nome aleatório. Nunca renomeie ou edite uma pasta depois de aplicada em qualquer lugar; o planejador de atualização identifica as migrações aplicadas por nome e, para diários mais antigos, por carimbo de data/hora e hash.
Fluxo de decisão de atualização
| Situação | Backup | Resultado |
|---|---|---|
| Novo arquivo, pasta de migrações fornecida | Não (o arquivo não existia antes de ser aberto) | Todas as migrações aplicadas em uma transação |
| Arquivo existente, diário atualizado | Não | Aberto como está; needed é falso |
| Arquivo existente, migrações pendentes | Sim, antes de qualquer alteração | Migrações pendentes aplicadas; linhas do diário inseridas com applied_at |
Arquivo existente, diário sem name coluna | Sim | Colunas name e applied_at adicionadas, linhas preenchidas e migrações pendentes aplicadas |
| A linha herdada não corresponde a nenhuma migração local | Nenhum backup é feito: o planejamento falha primeiro | DatabaseError com operação migrate: "O diário de migração legado não corresponde às migrações locais" |
:memory: | Nunca | Migrações aplicadas se uma pasta for fornecida |
| Nenhuma pasta de migrações | Nunca | Aberto sem criar tabelas |
A correspondência de preenchimento funciona assim: uma linha herdada created_at é truncado para segundos inteiros e comparado com o carimbo de data/hora da pasta de cada migração. Se exatamente uma migração corresponder, ela será usada. Caso contrário, o hash desfaz o empate e, como último recurso, qualquer migração com o mesmo hash é usada.
Anatomia do arquivo de backup
backups/cadcamfun-20261015-034512.123-6f1c2d4e-8a90-4b7c-9e21-3f5a6b7c8d9e-pre-migrate.db
└ prefix ┘ └ UTC timestamp ┘ └ random UUID ┘ - O prefixo é o nome do arquivo do banco de dados sem
.db, seguido por um travessão. - O carimbo de data/hora é UTC com precisão de milissegundos. Se o backup existente mais recente tiver o mesmo carimbo de data/hora ou posterior, o novo será definido 1 ms depois dele, portanto, a ordem lexical sempre será igual à ordem de criação.
- O instantâneo é gravado em
<name>.partialcomVACUUM INTO, então renomeado. Um acidente deixa no máximo um.partialarquivo, que é removido em umfinallybloquear. - Após um backup bem-sucedido, apenas os cinco arquivos mais recentes que correspondam ao padrão exato serão mantidos. Os arquivos copiados para o diretório com outros nomes nunca são excluídos.
VACUUM INTOproduz um banco de dados compacto e independente sem nenhum irmão WAL, para que um backup possa ser aberto diretamente.
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 A restauração reverte esquema e dados
Um backup restaurado é o estado imediatamente anterior à atualização. As edições feitas após essa atualização não estão nela e a próxima inicialização tentará a mesma migração novamente. Corrija a causa da falha antes de reiniciar.
Formas centrais da mesa
Estas são as tabelas que o editor de desktop, o histórico e o kernel leem com mais frequência. Os nomes das colunas são os nomes SQL; Drizzle os expõe sob as mesmas chaves.
| Mesa | Colunas |
|---|---|
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 |
A exclusão de um projeto remove seus desenhos, componentes, percursos da ferramenta, objetos históricos e referências, pull requests, execuções de fluxo de trabalho, ativos de malha e logs de operação. As linhas de versão e comentários são dispostas em cascata a partir de seu recurso pai. UM CadOperationLog linha sobrevive à exclusão de seu ativo de malha com meshAssetId definido como nulo. Os links de percurso para materiais, ferramentas e configurações de máquina não possuem cascata, portanto um material referenciado não pode ser excluído enquanto um percurso aponta para ele.
Catálogo de mesas completo
O esquema define 60 tabelas. A lista abaixo foi produzida a partir das definições da tabela Drizzle: nome SQL, constante exportada, contagem de colunas, número de índices declarados e chaves estrangeiras com sua regra de exclusão. "nenhuma ação" é o padrão do SQLite: a exclusão falha enquanto existem linhas de referência (chaves estrangeiras são aplicadas na conexão do aplicativo).
| Tabela SQL | Exportar | Colunas | Idx | Chaves estrangeiras |
|---|---|---|---|---|
Account | accountTable | 13 | 1 | userId → Usuário (cascata) |
ActivityLog | activityLogTable | 7 | 0 | userId → Usuário (sem ação) |
AIAnalysisLog | aIAnalysisLogTable | 7 | 0 | userId → Usuário (sem ação) |
ApiKey | apiKeyTable | 7 | 1 | userId → Usuário (cascata) |
authenticators | authenticatorTable | 8 | 0 | userId → Usuário (cascata) |
CadOperationLog | cadOperationLogTable | 11 | 1 | projectId → Projeto (cascata)meshAssetId → MeshAsset (definir nulo) |
Component | componentTable | 10 | 0 | projectId → Projeto (cascata) |
ComponentComment | componentCommentTable | 6 | 0 | componentId → Componente (cascata)userId → Usuário (sem ação) |
ComponentVersion | componentVersionTable | 6 | 0 | componentId → Componente (cascata)userId → Usuário (sem ação) |
Conversation | conversationTable | 6 | 0 | organizationId → Organização (cascata) |
ConversationParticipant | conversationParticipantTable | 5 | 1 | userId → Usuário (cascata)conversationId → Conversa (cascata) |
CostSettings | costSettingsTable | 11 | 0 | ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
Drawing | drawingTable | 8 | 0 | projectId → Projeto (cascata) |
DrawingVersion | drawingVersionTable | 5 | 0 | drawingId → Desenho (cascata) |
FileUpload | fileUploadTable | 12 | 0 | — |
GitObject | gitObjectTable | 4 | 1 | projectId → Projeto (cascata) |
GitRef | gitRefTable | 3 | 1 | projectId → Projeto (cascata) |
GitRepo | gitRepoTable | 4 | 0 | projectId → Projeto (cascata) |
LibraryItem | libraryItemTable | 14 | 0 | ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
LocalKeyValue | localKeyValueTable | 3 | 0 | — |
MachineConfig | machineConfigTable | 9 | 0 | ownerId → Usuário (sem ação) |
MarketplaceCategory | marketplaceCategoryTable | 10 | 1 | parentId → MarketplaceCategory (sem ação) |
MarketplaceCollection | marketplaceCollectionTable | 10 | 1 | sellerId → MarketplaceSeller (sem ação) |
MarketplaceCollectionItem | marketplaceCollectionItemTable | 5 | 1 | collectionId → MarketplaceCollection (cascata)itemId → MarketplaceItem (cascata) |
MarketplaceItem | marketplaceItemTable | 30 | 5 | sellerId → MarketplaceSeller (sem ação) |
MarketplaceOrder | marketplaceOrderTable | 23 | 4 | buyerId → Usuário (sem ação)sellerId → MarketplaceSeller (sem ação)itemId → MarketplaceItem (sem ação) |
MarketplacePayout | marketplacePayoutTable | 12 | 1 | sellerId → MarketplaceSeller (sem ação) |
MarketplaceReview | marketplaceReviewTable | 12 | 2 | itemId → MarketplaceItem (sem ação)buyerId → Usuário (sem ação)orderId → MarketplaceOrder (sem ação) |
MarketplaceSeller | marketplaceSellerTable | 19 | 0 | userId → Usuário (sem ação) |
MarketplaceWishlist | marketplaceWishlistTable | 4 | 1 | userId → Usuário (cascata)itemId → MarketplaceItem (cascata) |
Material | materialTable | 9 | 0 | ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
MaterialCost | materialCostTable | 10 | 0 | materialId → Material (cascata)ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
MCPServerConfig | mCPServerConfigTable | 11 | 1 | — |
MeshAsset | meshAssetTable | 7 | 1 | projectId → Projeto (cascata) |
Message | messageTable | 8 | 1 | senderId → Usuário (cascata)conversationId → Conversa (cascata)fileId → FileUpload (definir nulo) |
Notification | notificationTable | 12 | 0 | userId → Usuário (cascata)organizationId → Organização (cascata)projectId → Projeto (cascata)componentId → Componente (cascata)messageId → Mensagem (cascata) |
OperationCost | operationCostTable | 10 | 0 | ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
Organization | organizationTable | 5 | 0 | — |
OrganizationInvitation | organizationInvitationTable | 7 | 0 | organizationId → Organização (cascata) |
passkey | passkeyTable | 11 | 0 | userId → Usuário (cascata) |
PluginConfiguration | pluginConfigurationTable | 2 | 0 | pluginId → PluginRegistryEntry (cascata) |
PluginRegistryEntry | pluginRegistryEntryTable | 9 | 0 | — |
ProductionCostEstimate | productionCostEstimateTable | 17 | 0 | toolpathId → Percurso (cascata)operationCostId → OperationCost (sem ação)ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
Project | projectTable | 8 | 0 | ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
PullRequest | pullRequestTable | 14 | 1 | projectId → Projeto (cascata)authorId → Usuário (sem ação) |
PullRequestReview | pullRequestReviewTable | 8 | 1 | pullRequestId → PullRequest (cascata)reviewerId → Usuário (sem ação) |
PushSubscription | pushSubscriptionTable | 6 | 0 | userId → Usuário (cascata) |
Session | sessionTable | 8 | 0 | userId → Usuário (cascata) |
Subscription | subscriptionTable | 24 | 3 | referenceId → Usuário (cascata) |
Token | tokenTable | 8 | 2 | userId → Usuário (cascata) |
Tool | toolTable | 17 | 0 | ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
Toolpath | toolpathTable | 17 | 0 | projectId → Projeto (cascata)createdBy → Usuário (sem ação)drawingId → Desenhar (sem ação)materialId → Material (sem ação)toolId → Ferramenta (sem ação)machineConfigId → MachineConfig (sem ação) |
ToolpathComment | toolpathCommentTable | 6 | 0 | toolpathId → Percurso (cascata)userId → Usuário (sem ação) |
ToolpathVersion | toolpathVersionTable | 7 | 0 | toolpathId → Percurso (cascata)userId → Usuário (sem ação) |
ToolWearCost | toolWearCostTable | 10 | 0 | toolId → Ferramenta (cascata)ownerId → Usuário (sem ação)organizationId → Organização (sem ação) |
twoFactor | twoFactorTable | 4 | 0 | userId → Usuário (cascata) |
User | userTable | 17 | 0 | — |
UserOrganization | userOrganizationTable | 5 | 1 | userId → Usuário (cascata)organizationId → Organização (cascata) |
verification_tokens | verificationTokenTable | 3 | 1 | — |
WorkflowRun | workflowRunTable | 10 | 1 | projectId → Projeto (cascata) |
Escrevendo com segurança
- Envolva alterações de múltiplas instruções em uma transação Drizzle dentro de uma
querychamar:database.query("op", (db) => db.transaction((tx) => ...)). Separarquerychamadas são transações separadas. - Dê a cada consulta um nome de operação estável e pontilhado (
projects.list,toolpaths.update). É o único contextoDatabaseErrorcarrega além da causa e aparece nos logs. - Gerar IDs com
newId()(um UUID) ou deixe o padrão da coluna fazer isso; não crie IDs a partir de nomes. - Valide colunas JSON ao lê-las. O tipo TypeScript declarado não é verificado pelo SQLite.
- Usar
LocalKeyValuepor meio do serviço de valor-chave secundário, em vez de escrever linhas diretamente, para que a validação de chave e valor seja aplicada.
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 criar linhas com IDs explícitos, use schema.newId(); @cadcamfun/db reexporta o módulo de esquema como o schema namespace, não como nomes de nível superior.