Documentation
Base de données
Persistance SQLite avec propriété de connexion étendue, échecs de requêtes typées, sauvegardes de migration et stockage de documents local.
Traduit automatiquement de l'anglais. Voir l'original en anglais
Modèle de persistance
@cadcamfun/db ouvre SQLite via bun:sqlite et expose une base de données Drizzle dans l'effet Database service. Le sidecar stocke sa base de données d'application dans <dataDir>/cadcamfun.db. La connexion permet la journalisation en écriture anticipée et l'application des clés étrangères avant d'appliquer les migrations.
La base de données contient la copie de travail des projets et leurs ressources associées. Les objets de contrôle de version du projet, les demandes d'extraction et les exécutions de flux de travail sont également stockés localement. Les informations d'identification du fournisseur d'IA utilisent un fichier JSON distinct, lisible par le propriétaire ; le jeton du porteur d'application est fourni au lancement plutôt que conservé comme préférence.
Schéma et points d'entrée
packages/db/src/schema.ts définit les tables, les index, les relations, les valeurs par défaut et les types de sélection/insertion déduits. src/index.ts exportations Database, DatabaseError, la bruine Db type et l’espace de noms du schéma. src/upgrade.ts possède la planification de la migration, les sauvegardes et les applications transactionnelles ; src/migrate.ts est le point d'entrée de la ligne de commande. Le SQL généré et le journal de migration sont présents packages/db/drizzle.
Chaque connexion ouverte par packages/db/src/index.ts bascule SQLite vers journal_mode = WAL et s'allume foreign_keys. La fermeture de la base de données s'exécute wal_checkpoint(TRUNCATE) ainsi, le fichier principal est terminé à la fin du processus. Si le point de contrôle est occupé ou échoue, un avertissement est enregistré et l'arrêt se poursuit.
Valeurs stockées
Les identifiants de domaine sont par défaut les UUID lorsqu'ils sont configurés par la table. Drizzle mappe les colonnes d'horodatage aux dates JavaScript et les colonnes JSON aux types d'enregistrement déclarés. Les référentiels HTTP convertissent les dates en leur représentation filaire et valident JSON à la limite du domaine. Une annotation de type TypeScript sur une colonne JSON stockée ne prouve pas en elle-même que les données importées ou plus anciennes correspondent au schéma de domaine actuel.
Les projets possèdent leurs propres dessins, composants et parcours d'outils. Les parcours d'outils peuvent faire référence à un dessin, un matériau, une fraise et une configuration de machine ; leur data champ peut transporter un document de fabrication portable. Des tables distinctes de versions de dessin, de composant et de parcours d'outil conservent les versions de ressources en plus de l'historique Git de l'ensemble du projet.
Requête via le service
Database.layer accepte un chemin de fichier ou :memory:, un dossier de migrations facultatif et un répertoire de sauvegarde facultatif. Il acquiert le handle SQLite pour sa portée Effect. Les appelants utilisent query(operation, run) pour exécuter des travaux Drizzle synchrones ou asynchrones ; les exceptions étrangères deviennent DatabaseError avec le nom de l’opération et la cause originale.
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())
}) Les référentiels possèdent leurs propres règles d'accès et interprétation des lignes ; l'assistant de requête partagé fournit une attribution d'échec. Il n'autorise pas automatiquement une requête ni ne crée de transaction autour d'une séquence arbitraire d'appels de service.
Comportement de mise à niveau
L'ouverture d'une couche avec un dossier migrations lit les fichiers de migration et compare leurs noms avec __drizzle_migrations. Un journal de l’ancien format est rempli en faisant correspondre les horodatages et les hachages de migration. Un journal incomparable fait échouer la mise à niveau au lieu de deviner une identité de migration.
Lorsqu'une base de données sur disque existante nécessite une mise à niveau, le service crée une sauvegarde SQLite de pré-migration à l'aide de VACUUM INTO. Il écrit dans un fichier partiel, renomme l'instantané terminé et conserve les cinq dernières sauvegardes correspondantes avant la migration. Le répertoire de sauvegarde par défaut est backups à côté de la base de données. Les bases de données nouvelles et en mémoire n'ont pas besoin de cette sauvegarde préalable à la mise à niveau.
Les modifications du journal et les instructions SQL en attente s'exécutent en une seule transaction immédiate. Un échec de migration annule la transaction et fait échouer la construction de la couche. L'application ne doit pas annoncer qu'elle est prête lorsque l'initialisation de la base de données échoue.
Commandes de migration
bun run db:generate
bun run db:migrate Générer des migrations après modification schema.ts, et conservez le SQL généré et le journal ensemble. La CLI de migration utilise DATABASE_URL lorsqu'il est fourni, supprime une initiale file: préfixe, et sinon ouvre ./data/cadcamfun.db. Le wrapper racine exécute ce script avec packages/db comme répertoire de travail, sa cible par défaut est donc packages/db/data/cadcamfun.db. Utilisations de Web Studio data/cadcamfun.db à la racine du référentiel. Le sidecar emballé utilise CADCAMFUN_DATA_DIR. Sélectionnez un chemin de base de données explicite lors de l'exécution de la maintenance afin que ces points d'entrée n'agissent pas involontairement sur différents fichiers.
Sans dossier de migrations, Database.layer ouvre la base de données mais ne crée pas de tables d'application. Les tests qui nécessitent le schéma complet fournissent au répertoire de migration une base de données en mémoire.
Domaines de données
- Identité et collaboration : utilisateurs, comptes, sessions, authentificateurs, jetons de vérification, abonnements, organisations, adhésions, invitations, conversations, messages, notifications et abonnements push.
- Conception et fabrication : projets, dessins, composants, leurs versions et commentaires, matériaux, outils, configurations de machines, parcours d'outils et enregistrements des coûts de production.
- Services applicatifs : fichiers, éléments de bibliothèque, registre/configuration du plugin, clés API, enregistrements d'analyse et d'activité, données de marché, configuration MCP et entrées de valeur-clé locales.
- Historique du projet : Dépôts Git, objets et références, demandes d'extraction et révisions, et exécutions de flux de travail.
La présence d'une table établit un modèle de persistance. Les intégrations réseau et les fonctionnalités des applications dépendent de leurs gestionnaires et services réels, plutôt que de la seule existence des tables.
Stockage local de valeurs-clés
LocalKeyValue conserve l’état de l’application compatible JSON. Le sidecar KvStore prend en charge les listes get, upsert, delete et filtrées par préfixe. Les clés doivent contenir du texte autre que des espaces et comporter au maximum 512 caractères. Les valeurs autorisent les valeurs nulles, les chaînes, les booléens, les nombres finis, les tableaux et les objets simples ; les cycles, les fonctions, les nombres non finis et les objets non simples sont rejetés.
Les entrées incluent un horodatage de mise à jour. Les upserts remplacent la valeur de la même clé. La liste des préfixes trie par clé et filtre en mémoire. Utilisez cette boutique pour les préférences et le contexte de l'assistant ; les informations d’identification appartiennent à leur service d’informations d’identification dédié.
Arrêt et récupération
Les requêtes du finaliseur de portée de base de données PRAGMA wal_checkpoint(TRUNCATE) avant de fermer la poignée. Un point de contrôle occupé ou un échec de fermeture est enregistré sous forme d'avertissement afin que les finaliseurs restants puissent s'exécuter. Le propriétaire de la connexion, plutôt que les gestionnaires HTTP individuels, ferme le handle.
Pour une sauvegarde manuelle cohérente, arrêtez proprement l'application avant de copier son répertoire de données, ou utilisez les fonctionnalités de sauvegarde de SQLite pendant son exécution. Une base de données active peut avoir un état dans son fichier WAL. Pour inspecter une sauvegarde de pré-migration, travaillez sur une copie et conservez la base de données et les sauvegardes d'origine jusqu'à ce que la récupération soit confirmée.
Responsabilités du référentiel
Conservez la logique de création de connexion et de migration SQLite dans ce package. Les référentiels Sidecar utilisent le service de base de données fourni et mappent les échecs de stockage aux erreurs HTTP de leur ressource. Conservez le calcul de la géométrie dans le noyau et les secrets dans les services d'informations d'identification ou de jetons. Les sauvegardes de base de données contiennent des données de projet et de compte et nécessitent les mêmes contrôles d'accès que le répertoire de données actif.
Inspecter une base de données sans la modifier
Après un arrêt propre, utilisez la CLI SQLite sur un chemin de base de données explicite. Le mode lecture seule évite de créer accidentellement un nouveau fichier vide lorsqu'un chemin est erroné. Ces contrôles inspectent la structure et la cohérence sans exposer les lignes de comptes ou d'informations d'identification.
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 rapporte normalement ok; foreign_key_check renvoie les lignes uniquement pour les violations. Un journal sans le name/applied_at columns est un format plus ancien, que le code de mise à niveau de l'application doit concilier avant d'utiliser la dernière requête. Vérifiez les chemins et l’identité de la migration avant de tenter des réparations.
L'application définit l'application des clés étrangères sur sa propre connexion. Une connexion de maintenance ouverte séparément doit l'activer explicitement avant d'effectuer des écritures autorisées ; le PRAGMA est spécifique à la connexion.
Source: packages/db/src/index.ts, upgrade.ts, et database.test.ts.
Utiliser une base de données jetable dans le code
Une portée complète du service en mémoire est utile pour les exemples de référentiel et les diagnostics isolés. L’étendue est propriétaire de la connexion et applique les migrations enregistrées avant que l’effet ne lise le service.
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)) // [] Enregistrez ce script sous apps/sidecar/diagnostic.ts et courir bun apps/sidecar/diagnostic.ts à partir de la racine du référentiel. Ce package fournit la base de données et les dépendances d'effet, et l'URL de migration est résolue à partir de ce fichier. Un répertoire de migrations manquant ou erroné n'équivaut pas à un schéma d'application vide. La bruine query le rappel peut exécuter une requête synchrone ou renvoyer une promesse ; l'assistant enveloppe les échecs sous l'étiquette d'opération fournie.
Source: packages/db/src/database.test.ts et index.ts.
Diagnostiquer les pannes de stockage
- Aucun tableau de ce type : confirmez le fichier de base de données exact et le dossier de migrations. L'ouverture sans migrations est autorisée par le service, mais l'interrogation des tables d'application échoue alors.
- La contrainte de clé étrangère a échoué : inspectez le propriétaire/projet/ressource référencé. Les restrictions de suppression sont spécifiques au schéma : la suppression d'un utilisateur possédant un projet détenu est restreinte, tandis que les lignes de session sont en cascade lorsque leur utilisateur est supprimé.
- La sauvegarde a échoué lors de la mise à niveau : vérifiez l'espace libre et l'accès en écriture dans la base de données et les répertoires de sauvegarde. L'échec de la sauvegarde arrête la migration ; ne supprimez pas la base de données d'origine pour la contourner.
- Le journal de migration hérité ne correspond pas aux migrations locales : préserver la base de données et récupérer l’ensemble de migration correspondant. Renommer ou supprimer des lignes de journal masquerait l’historique de mise à niveau incompatible.
- Point de contrôle occupé à l'arrêt : inspectez les autres connexions ouvertes avant de copier uniquement le fichier de base de données. Conservez les frères et sœurs WAL/SHM jusqu'à ce qu'un point de contrôle propre ou une sauvegarde SQLite cohérente soit terminée.
Les sauvegardes de migration sont des points de récupération préalables à la mise à niveau avec une politique de rétention de cinq fichiers, et non une sauvegarde continue des modifications ultérieures. Sauvegardez le répertoire de données complet séparément selon les exigences de récupération de l'installation.
Source: packages/db/src/index.ts, upgrade.ts, schema.ts, et database.test.ts.
Histoire des migrations
Les migrations enregistrées sont appliquées dans l'ordre des dossiers. Chaque dossier contient un migration.sql et le kit bruine snapshot.json utilisé pour calculer le prochain diff. La ligne du journal stocke le nom du dossier, son hachage, l'horodatage du dossier en millisecondes (created_at), et l'heure ISO à laquelle elle a été appliquée (applied_at).
| Dossier | Taille | Ce que cela crée |
|---|---|---|
20261004223029_careful_monster_badoon | 771 lignes | Le schéma de base converti à partir du modèle Prisma existant : identité, organisations, projets, dessins, composants, parcours d'outils, bibliothèques, coûts, place de marché, plugins et configuration MCP. Les noms des tables et des colonnes sont inchangés, donc plus anciens dev.db les données restent lisibles. |
20261008030518_redundant_dagger | 73 lignes | Historique du projet : GitRepo, GitObject, GitRef, PullRequest, PullRequestReview, WorkflowRun. |
20261009012351_moaning_loki | 28 lignes | Enregistrements du noyau de géométrie : MeshAsset et CadOperationLog. |
20261009171602_brown_mad_thinker | 5 lignes | LocalKeyValue (key clé primaire de texte, value texte non nul, updatedAt entier non nul). |
Les noms de dossiers proviennent de drizzle-kit : un horodatage UTC suivi d'un nom aléatoire. Ne renommez ou ne modifiez jamais un dossier une fois qu'il a été appliqué quelque part ; le planificateur de mise à niveau identifie les migrations appliquées par nom et, pour les journaux plus anciens, par horodatage et hachage.
Flux de décision de mise à niveau
| Situation | Sauvegarde | Résultat |
|---|---|---|
| Nouveau fichier, dossier de migrations donné | Non (le fichier n'existait pas avant l'ouverture) | Toutes les migrations appliquées en une seule transaction |
| Dossier existant, journal à jour | Non | Ouvert tel quel ; needed est faux |
| Fichier existant, migrations en attente | Oui, avant tout changement | Migrations en attente appliquées ; lignes de journal insérées avec applied_at |
Fichier existant, journal sans name colonne | Oui | Colonnes name et applied_at ajouté, lignes remplies, puis migrations en attente appliquées |
| La ligne héritée ne correspond à aucune migration locale | Aucune sauvegarde n'est effectuée : la planification échoue en premier | DatabaseError avec opération migrate: "Le journal de migration hérité ne correspond pas aux migrations locales" |
:memory: | Jamais | Migrations appliquées si un dossier est donné |
| Aucun dossier de migrations | Jamais | Ouvert sans créer de tables |
La correspondance de remplissage fonctionne comme ceci : une ligne héritée created_at est tronqué à des secondes entières et comparé à l'horodatage du dossier de chaque migration. Si exactement une migration correspond, elle est utilisée. Sinon, le hachage brise l'égalité et, en dernier recours, toute migration avec le même hachage est utilisée.
Anatomie du fichier de sauvegarde
backups/cadcamfun-20261015-034512.123-6f1c2d4e-8a90-4b7c-9e21-3f5a6b7c8d9e-pre-migrate.db
└ prefix ┘ └ UTC timestamp ┘ └ random UUID ┘ - Le préfixe est le nom du fichier de base de données sans
.db, suivi d'un tiret. - L'horodatage est UTC avec une précision à la milliseconde. Si la sauvegarde existante la plus récente a le même horodatage ou un horodatage ultérieur, la nouvelle est définie 1 ms après elle, donc l'ordre lexical est toujours égal à l'ordre de création.
- L'instantané est écrit dans
<name>.partialavecVACUUM INTO, puis renommé. Un crash laisse au plus une.partialfichier, qui est supprimé dans unfinallybloc. - Après une sauvegarde réussie, seuls les cinq fichiers les plus récents correspondant au modèle exact sont conservés. Les fichiers que vous copiez dans le répertoire sous d'autres noms ne sont jamais supprimés.
VACUUM INTOproduit une base de données compacte et autonome sans frère WAL, de sorte qu'une sauvegarde peut être ouverte directement.
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 Restauration du schéma et des données
Une sauvegarde restaurée est l'état juste avant la mise à niveau. Les modifications apportées après cette mise à niveau n'y figurent pas et le prochain lancement tentera à nouveau la même migration. Corrigez la cause de l’échec avant de redémarrer.
Formes de table de base
Ce sont les tableaux que l'éditeur de bureau, l'historique et le noyau lisent le plus souvent. Les noms de colonnes sont les noms SQL ; Drizzle les expose sous les mêmes clés.
| Tableau | Colonnes |
|---|---|
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 |
La suppression d'un projet supprime ses dessins, composants, parcours d'outils, objets et références d'historique, demandes d'extraction, exécutions de flux de travail, actifs de maillage et journaux d'opérations. Les lignes de version et de commentaires sont en cascade à partir de leur ressource parent. UN CadOperationLog la ligne survit à la suppression de son actif de maillage avec meshAssetId mis à zéro. Les liens de parcours d'outil vers les matériaux, les outils et les configurations de machine n'ont pas de cascade, donc un matériau référencé ne peut pas être supprimé tant qu'un parcours d'outil pointe dessus.
Catalogue de tables complet
Le schéma définit 60 tables. La liste ci-dessous a été réalisée à partir des définitions de la table Drizzle : nom SQL, constante exportée, nombre de colonnes, nombre d'index déclarés et clés étrangères avec leur règle de suppression. "aucune action" est la valeur par défaut de SQLite : la suppression échoue tant que les lignes de référence existent (les clés étrangères sont appliquées sur la connexion de l'application).
| Tableau SQL | Exporter | Cols | Idx | Clés étrangères |
|---|---|---|---|---|
Account | accountTable | 13 | 1 | userId → Utilisateur (cascade) |
ActivityLog | activityLogTable | 7 | 0 | userId → Utilisateur (aucune action) |
AIAnalysisLog | aIAnalysisLogTable | 7 | 0 | userId → Utilisateur (aucune action) |
ApiKey | apiKeyTable | 7 | 1 | userId → Utilisateur (cascade) |
authenticators | authenticatorTable | 8 | 0 | userId → Utilisateur (cascade) |
CadOperationLog | cadOperationLogTable | 11 | 1 | projectId → Projet (cascade)meshAssetId → MeshAsset (définir null) |
Component | componentTable | 10 | 0 | projectId → Projet (cascade) |
ComponentComment | componentCommentTable | 6 | 0 | componentId → Composant (cascade)userId → Utilisateur (aucune action) |
ComponentVersion | componentVersionTable | 6 | 0 | componentId → Composant (cascade)userId → Utilisateur (aucune action) |
Conversation | conversationTable | 6 | 0 | organizationId → Organisation (cascade) |
ConversationParticipant | conversationParticipantTable | 5 | 1 | userId → Utilisateur (cascade)conversationId → Conversation (cascade) |
CostSettings | costSettingsTable | 11 | 0 | ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
Drawing | drawingTable | 8 | 0 | projectId → Projet (cascade) |
DrawingVersion | drawingVersionTable | 5 | 0 | drawingId → Dessin (cascade) |
FileUpload | fileUploadTable | 12 | 0 | — |
GitObject | gitObjectTable | 4 | 1 | projectId → Projet (cascade) |
GitRef | gitRefTable | 3 | 1 | projectId → Projet (cascade) |
GitRepo | gitRepoTable | 4 | 0 | projectId → Projet (cascade) |
LibraryItem | libraryItemTable | 14 | 0 | ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
LocalKeyValue | localKeyValueTable | 3 | 0 | — |
MachineConfig | machineConfigTable | 9 | 0 | ownerId → Utilisateur (aucune action) |
MarketplaceCategory | marketplaceCategoryTable | 10 | 1 | parentId → Catégorie Marketplace (aucune action) |
MarketplaceCollection | marketplaceCollectionTable | 10 | 1 | sellerId → MarketplaceSeller (aucune action) |
MarketplaceCollectionItem | marketplaceCollectionItemTable | 5 | 1 | collectionId → MarketplaceCollection (cascade)itemId → MarketplaceItem (cascade) |
MarketplaceItem | marketplaceItemTable | 30 | 5 | sellerId → MarketplaceSeller (aucune action) |
MarketplaceOrder | marketplaceOrderTable | 23 | 4 | buyerId → Utilisateur (aucune action)sellerId → MarketplaceSeller (aucune action)itemId → MarketplaceItem (aucune action) |
MarketplacePayout | marketplacePayoutTable | 12 | 1 | sellerId → MarketplaceSeller (aucune action) |
MarketplaceReview | marketplaceReviewTable | 12 | 2 | itemId → MarketplaceItem (aucune action)buyerId → Utilisateur (aucune action)orderId → MarketplaceOrder (aucune action) |
MarketplaceSeller | marketplaceSellerTable | 19 | 0 | userId → Utilisateur (aucune action) |
MarketplaceWishlist | marketplaceWishlistTable | 4 | 1 | userId → Utilisateur (cascade)itemId → MarketplaceItem (cascade) |
Material | materialTable | 9 | 0 | ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
MaterialCost | materialCostTable | 10 | 0 | materialId → Matériel (cascade)ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
MCPServerConfig | mCPServerConfigTable | 11 | 1 | — |
MeshAsset | meshAssetTable | 7 | 1 | projectId → Projet (cascade) |
Message | messageTable | 8 | 1 | senderId → Utilisateur (cascade)conversationId → Conversation (cascade)fileId → FileUpload (définir null) |
Notification | notificationTable | 12 | 0 | userId → Utilisateur (cascade)organizationId → Organisation (cascade)projectId → Projet (cascade)componentId → Composant (cascade)messageId → Message (cascade) |
OperationCost | operationCostTable | 10 | 0 | ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
Organization | organizationTable | 5 | 0 | — |
OrganizationInvitation | organizationInvitationTable | 7 | 0 | organizationId → Organisation (cascade) |
passkey | passkeyTable | 11 | 0 | userId → Utilisateur (cascade) |
PluginConfiguration | pluginConfigurationTable | 2 | 0 | pluginId → PluginRegistryEntry (cascade) |
PluginRegistryEntry | pluginRegistryEntryTable | 9 | 0 | — |
ProductionCostEstimate | productionCostEstimateTable | 17 | 0 | toolpathId → Parcours d'outil (cascade)operationCostId → OperationCost (aucune action)ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
Project | projectTable | 8 | 0 | ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
PullRequest | pullRequestTable | 14 | 1 | projectId → Projet (cascade)authorId → Utilisateur (aucune action) |
PullRequestReview | pullRequestReviewTable | 8 | 1 | pullRequestId → PullRequest (cascade)reviewerId → Utilisateur (aucune action) |
PushSubscription | pushSubscriptionTable | 6 | 0 | userId → Utilisateur (cascade) |
Session | sessionTable | 8 | 0 | userId → Utilisateur (cascade) |
Subscription | subscriptionTable | 24 | 3 | referenceId → Utilisateur (cascade) |
Token | tokenTable | 8 | 2 | userId → Utilisateur (cascade) |
Tool | toolTable | 17 | 0 | ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
Toolpath | toolpathTable | 17 | 0 | projectId → Projet (cascade)createdBy → Utilisateur (aucune action)drawingId → Dessin (aucune action)materialId → Matériel (aucune action)toolId → Outil (aucune action)machineConfigId → MachineConfig (aucune action) |
ToolpathComment | toolpathCommentTable | 6 | 0 | toolpathId → Parcours d'outil (cascade)userId → Utilisateur (aucune action) |
ToolpathVersion | toolpathVersionTable | 7 | 0 | toolpathId → Parcours d'outil (cascade)userId → Utilisateur (aucune action) |
ToolWearCost | toolWearCostTable | 10 | 0 | toolId → Outil (cascade)ownerId → Utilisateur (aucune action)organizationId → Organisation (aucune action) |
twoFactor | twoFactorTable | 4 | 0 | userId → Utilisateur (cascade) |
User | userTable | 17 | 0 | — |
UserOrganization | userOrganizationTable | 5 | 1 | userId → Utilisateur (cascade)organizationId → Organisation (cascade) |
verification_tokens | verificationTokenTable | 3 | 1 | — |
WorkflowRun | workflowRunTable | 10 | 1 | projectId → Projet (cascade) |
Écrire en toute sécurité
- Encapsulez les modifications multi-instructions dans une transaction Drizzle dans une seule
queryappel:database.query("op", (db) => db.transaction((tx) => ...)). Séparéqueryles appels sont des transactions distinctes. - Donnez à chaque requête un nom d'opération stable en pointillés (
projects.list,toolpaths.update). C'est le seul contexte dans lequelDatabaseErrorporte en plus de la cause, et elle apparaît dans les journaux. - Générez des identifiants avec
newId()(un UUID) ou laissez la colonne par défaut le faire ; ne construisez pas d'identifiants à partir de noms. - Validez les colonnes JSON lors de leur lecture. Le type TypeScript déclaré n'est pas vérifié par SQLite.
- Utiliser
LocalKeyValuevia le service clé-valeur sidecar plutôt que d'écrire directement les lignes, de sorte que la validation de clé et de valeur est appliquée.
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]
}))
}) Pour créer des lignes avec des identifiants explicites, utilisez schema.newId(); @cadcamfun/db réexporte le module de schéma en tant que schema espace de noms, pas en tant que noms de niveau supérieur.