Документация
База данных
Сохранение SQLite с ограниченным владением соединением, ошибками типизированных запросов, резервными копиями миграции и локальным хранилищем документов.
Автоматически переведено с английского. Смотреть оригинал на английском
Модель персистентности
@cadcamfun/db открывает SQLite через bun:sqlite и предоставляет базу данных Drizzle внутри Effect Database услуга. Сайдкар хранит свою базу данных приложений по адресу <dataDir>/cadcamfun.db. Соединение позволяет вести журнал с упреждающей записью и применять внешний ключ перед применением миграции.
База данных содержит рабочие копии проектов и связанных с ними ресурсов. Объекты контроля версий проекта, запросы на включение и запуски рабочих процессов также хранятся локально. Учетные данные поставщика ИИ используют отдельный файл JSON, читаемый владельцем; токен носителя приложения предоставляется при запуске, а не сохраняется в качестве предпочтения.
Схема и точки входа
packages/db/src/schema.ts определяет таблицы, индексы, отношения, значения по умолчанию и предполагаемые типы выбора/вставки. src/index.ts экспорт Database, DatabaseError, дождь Db тип и пространство имен схемы. src/upgrade.ts владеет приложением планирования миграции, резервного копирования и транзакций; src/migrate.ts это точка входа в командную строку. Сгенерированный SQL и журнал миграции живут в packages/db/drizzle.
Каждое соединение, открытое packages/db/src/index.ts переключает SQLite на journal_mode = WAL и включается foreign_keys. Закрытие базы данных выполняется wal_checkpoint(TRUNCATE) поэтому основной файл будет завершен после завершения процесса. Если контрольная точка занята или выходит из строя, в журнал записывается предупреждение и завершение работы продолжается.
Сохраненные значения
Идентификаторы домена по умолчанию равны UUID, если они настроены в таблице. Drizzle сопоставляет столбцы меток времени с датами JavaScript, а столбцы JSON — с объявленными типами записей. Репозитории HTTP преобразуют даты в их проводное представление и проверяют JSON на границе домена. Аннотация типа TypeScript в сохраненном столбце JSON сама по себе не доказывает, что импортированные или более старые данные соответствуют текущей схеме домена.
Проекты содержат чертежи, компоненты и траектории движения инструмента. Траектории инструмента могут ссылаться на чертеж, материал, фрезу и конфигурацию станка; их data поле может нести портативный производственный документ. В отдельных таблицах версий чертежей, компонентов и траекторий сохраняются версии ресурсов в дополнение к истории Git всего проекта.
Запрос через сервис
Database.layer принимает путь к файлу или :memory:, необязательная папка миграции и необязательный каталог резервных копий. Он получает дескриптор SQLite для своей области действия. Вызывающие абоненты используют query(operation, run) выполнять синхронную или асинхронную работу «Дождь»; иностранные исключения становятся DatabaseError с названием операции и исходной причиной.
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())
}) Репозитории имеют собственные правила доступа и интерпретацию строк; помощник общего запроса обеспечивает атрибуцию сбоя. Он не авторизует запрос автоматически и не создает транзакцию вокруг произвольной последовательности вызовов служб.
Поведение обновления
При открытии слоя с папкой миграции файлы миграции считываются и сравниваются их имена с __drizzle_migrations. Журнал старого формата заполняется соответствующими временными метками и хэшами миграции. Несопоставимый журнал не выполняет обновление вместо того, чтобы угадать идентификатор миграции.
Когда существующая база данных на диске нуждается в обновлении, служба создает резервную копию SQLite перед миграцией, используя VACUUM INTO. Он записывает частичный файл, переименовывает готовый снимок и сохраняет пять последних совпадающих резервных копий перед миграцией. Каталог резервных копий по умолчанию: backups рядом с базой данных. Новым базам данных и базам данных, находящимся в памяти, эта резервная копия перед обновлением не требуется.
Изменения журнала и ожидающие запросы SQL выполняются в одной немедленной транзакции. Неудачная миграция откатывает транзакцию и не позволяет построить слой. Приложение не должно объявлять о готовности в случае сбоя инициализации базы данных.
Команды миграции
bun run db:generate
bun run db:migrate Генерировать миграции после изменения schema.tsи сохраните сгенерированный SQL и журнал вместе. Интерфейс командной строки миграции использует DATABASE_URL при поставке удаляет начальный file: префикс, а в противном случае открывается ./data/cadcamfun.db. Корневая оболочка запускает этот скрипт с помощью packages/db в качестве рабочего каталога, поэтому его целью по умолчанию является packages/db/data/cadcamfun.db. Веб-студия использует data/cadcamfun.db в корне репозитория. Упакованная сайдкар использует CADCAMFUN_DATA_DIR. При выполнении обслуживания выберите явный путь к базе данных, чтобы эти точки входа не работали непреднамеренно с разными файлами.
Без папки миграции, Database.layer открывает базу данных, но не создает таблицы приложения. Тесты, которым требуется полная схема, предоставляют каталог миграции с базой данных в памяти.
Домены данных
- Айдентика и сотрудничество: пользователи, учетные записи, сеансы, аутентификаторы, токены проверки, подписки, организации, членство, приглашения, беседы, сообщения, уведомления и принудительные подписки.
- Проектирование и производство: проекты, чертежи, компоненты, их версии и комментарии, материалы, инструменты, конфигурации станков, траектории движения инструмента и записи производственных затрат.
- Сервисы приложений: файлы, элементы библиотеки, реестр/конфигурация плагинов, ключи API, записи аналитики и активности, данные рынка, конфигурация MCP и локальные записи значений ключа.
- История проекта: Репозитории Git, объекты и ссылки, запросы на включение и обзоры, а также рабочие процессы.
Наличие таблицы устанавливает модель персистентности. Сетевая интеграция и функции приложений зависят от их реальных обработчиков и служб, а не только от существования таблиц.
Локальное хранилище ключей и значений
LocalKeyValue сохраняет состояние JSON-совместимого приложения. Сайдкар KvStore поддерживает получение, обновление, удаление и фильтрацию по префиксам. Ключи должны содержать текст без пробелов и содержать не более 512 символов. Значения допускают нулевые значения, строки, логические значения, конечные числа, массивы и простые объекты; циклы, функции, неконечные числа и непростые объекты отклоняются.
Записи включают метку времени обновления. Upserts заменяет значение того же ключа. Список префиксов сортируется по ключу и фильтруется в памяти. Используйте это хранилище для предпочтений и контекста помощника; учетные данные принадлежат их специальной службе учетных данных.
Выключение и восстановление
Запросы финализатора области базы данных PRAGMA wal_checkpoint(TRUNCATE) прежде чем закрыть ручку. Занятая контрольная точка или сбой закрытия регистрируются как предупреждение, поэтому оставшиеся финализаторы могут работать. Владелец соединения, а не отдельные обработчики HTTP, закрывает дескриптор.
Для последовательного резервного копирования вручную остановите приложение перед копированием его каталога данных или используйте средства резервного копирования SQLite во время его работы. Действующая база данных может иметь состояние в своем файле WAL. Чтобы проверить резервную копию перед миграцией, поработайте с копией и сохраните исходную базу данных и резервные копии до подтверждения восстановления.
Обязанности репозитория
Сохраните логику создания и миграции SQLite-соединений в этом пакете. Репозитории Sidecar используют предоставленную службу базы данных и сопоставляют сбои хранилища с ошибками HTTP своего ресурса. Храните вычисления геометрии в ядре, а секреты — в службах учетных данных или токенов. Резервные копии базы данных содержат данные проекта и учетной записи, и для них требуется тот же контроль доступа, что и для активного каталога данных.
Проверяйте базу данных, не изменяя ее
После полного завершения работы используйте SQLite CLI для явного пути к базе данных. Режим «только для чтения» позволяет избежать случайного создания нового пустого файла, если путь указан неправильно. Эти проверки проверяют структуру и согласованность, не раскрывая строки учетных записей или учетных данных.
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 обычно сообщает ok; foreign_key_check возвращает строки только для нарушений. Журнал без name/applied_at столбцы — это старый формат, который код обновления приложения должен согласовать перед использованием последнего запроса. Прежде чем пытаться восстановить данные, проверьте пути и идентификатор миграции.
Приложение устанавливает принудительное использование внешнего ключа для своего собственного соединения. Отдельно открытое техническое соединение должно быть явно разрешено перед выполнением авторизованной записи; PRAGMA зависит от соединения.
Источник: packages/db/src/index.ts, upgrade.ts, и database.test.ts.
Используйте одноразовую базу данных в коде
Полная область действия службы в памяти полезна для примеров репозитория и изолированной диагностики. Область владеет соединением и применяет возвращенные миграции до того, как эффект прочитает службу.
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)) // [] Сохраните этот скрипт как apps/sidecar/diagnostic.ts и бежать bun apps/sidecar/diagnostic.ts из корня репозитория. Этот пакет предоставляет зависимости базы данных и эффектов, а URL-адрес миграции разрешается из этого файла. Отсутствующий или неправильный каталог миграции не эквивалентен пустой схеме приложения. Дождь query обратный вызов может выполнить синхронный запрос или вернуть обещание; помощник помещает ошибки под предоставленную метку операции.
Источник: packages/db/src/database.test.ts и index.ts.
Диагностика сбоев хранилища
- Нет такой таблицы: подтвердите точный файл базы данных и папку миграции. Служба разрешает открытие без миграции, но запрос к таблицам приложения в этом случае завершается неудачей.
- Ограничение внешнего ключа не удалось: проверьте указанного владельца/проекта/ресурса. Ограничения на удаление зависят от схемы: удаление пользователя с принадлежащим ему проектом ограничено, а строки сеанса каскадируются при удалении пользователя.
- Ошибка резервного копирования во время обновления: проверьте свободное место и доступ на запись в базе данных и каталогах резервных копий. Сбой резервного копирования останавливает миграцию; не удаляйте исходную базу данных, чтобы обойти ее.
- Устаревший журнал миграции не соответствует локальным миграциям: сохранить базу данных и восстановить соответствующий набор миграции. Переименование или удаление строк журнала может скрыть историю несовместимых обновлений.
- Контрольная точка занята при выключении: проверьте другие открытые соединения, прежде чем копировать только файл базы данных. Сохраняйте одноуровневые элементы WAL/SHM до завершения очистки контрольной точки или согласованного резервного копирования SQLite.
Резервные копии миграции — это точки восстановления перед обновлением с политикой хранения пяти файлов, а не непрерывное резервное копирование последующих изменений. Создайте резервную копию всего каталога данных отдельно в соответствии с требованиями восстановления установки.
Источник: packages/db/src/index.ts, upgrade.ts, schema.ts, и database.test.ts.
История миграции
Зарегистрированные миграции применяются в порядке папок. В каждой папке хранится migration.sql и комплект для дождевания snapshot.json используется для вычисления следующего различия. В строке журнала хранится имя папки, ее хэш, временная метка папки в миллисекундах (created_at), а также время применения ISO (applied_at).
| Папка | Размер | Что он создает |
|---|---|---|
20261004223029_careful_monster_badoon | 771 строк | Базовая схема, преобразованная из устаревшей модели Prisma: идентичность, организации, проекты, чертежи, компоненты, траектории движения инструмента, библиотеки, затраты, рынок, плагины и конфигурация MCP. Имена таблиц и столбцов не изменились, поэтому устарели. dev.db данные остаются читаемыми. |
20261008030518_redundant_dagger | 73 строк | История проекта: GitRepo, GitObject, GitRef, PullRequest, PullRequestReview, WorkflowRun. |
20261009012351_moaning_loki | 28 строк | Записи ядра геометрии: MeshAsset и CadOperationLog. |
20261009171602_brown_mad_thinker | 5 строк | LocalKeyValue (key текстовый первичный ключ, value текст не нулевой, updatedAt целое число не равно нулю). |
Имена папок берутся из drizzle-kit: временная метка UTC, за которой следует случайное имя. Никогда не переименовывайте и не редактируйте папку после ее применения где-либо; планировщик обновлений идентифицирует примененные миграции по имени, а для старых журналов — по отметке времени и хешу.
Процесс принятия решения об обновлении
| Ситуация | Резервное копирование | Результат |
|---|---|---|
| Новый файл, указана папка миграции | Нет (файл не существовал до открытия) | Все миграции применяются за одну транзакцию |
| Существующий файл, журнал обновлен | Нет | Открыт как есть; needed ложно |
| Существующий файл, ожидающий миграции | Да, до каких-либо изменений | Применены ожидающие миграции; строки журнала, вставленные с помощью applied_at |
Существующий файл, журнал без name столбец | Да | Столбцы name и applied_at добавлено, строки заполнены обратно, затем применены ожидающие миграции |
| Старая строка не соответствует локальной миграции | Резервная копия не создается: планирование терпит неудачу в первую очередь | DatabaseError с операцией migrate: «Устаревший журнал миграции не соответствует локальным миграциям» |
:memory: | Никогда | Миграции применяются, если указана папка |
| Нет папки миграции | Никогда | Открыто без создания таблиц |
Сопоставление обратного заполнения работает следующим образом: устаревшая строка created_at усекается до целых секунд и сравнивается с меткой времени каждой папки миграции. Если совпадает ровно одна миграция, она используется. В противном случае хеш разрывает связь, и в крайнем случае используется любая миграция с тем же хешем.
Структура файла резервной копии
backups/cadcamfun-20261015-034512.123-6f1c2d4e-8a90-4b7c-9e21-3f5a6b7c8d9e-pre-migrate.db
└ prefix ┘ └ UTC timestamp ┘ └ random UUID ┘ - Префикс — это имя файла базы данных без
.db, за которым следует тире. - Временная метка — UTC с точностью до миллисекунды. Если самая новая существующая резервная копия имеет такую же или более позднюю временную метку, новая резервная копия устанавливается на 1 мс позже нее, поэтому лексический порядок всегда равен порядку создания.
- Снимок записывается в
<name>.partialсVACUUM INTO, затем переименован. Авария оставляет не более.partialфайл, который удаляется вfinallyблокировать. - После успешного резервного копирования сохраняются только пять новейших файлов, соответствующих точному шаблону. Файлы, которые вы копируете в каталог под другими именами, никогда не удаляются.
VACUUM INTOсоздает компактную, автономную базу данных без родственного WAL, поэтому резервную копию можно открыть напрямую.
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 Восстановление откатывает схему и данные
Восстановленная резервная копия — это состояние непосредственно перед обновлением. Правок, внесенных после этого обновления, в нем нет, и при следующем запуске будет повторена та же миграция. Устраните причину сбоя перед перезапуском.
Основные формы таблиц
Это таблицы, которые настольный редактор, история и ядро читают чаще всего. Имена столбцов — это имена SQL; Дождь выставляет их под одни и те же ключи.
| Стол | Столбцы |
|---|---|
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 |
При удалении проекта удаляются его чертежи, компоненты, траектории движения инструмента, исторические объекты и ссылки, запросы на включение, запуски рабочих процессов, ресурсы сетки и журналы операций. Строки версий и комментариев каскадируются из родительского ресурса. А CadOperationLog строка сохраняется после удаления своего ресурса сетки с meshAssetId установлен на ноль. Ссылки траектории инструмента на материалы, инструменты и конфигурации станков не имеют каскада, поэтому материал, на который ссылаются, не может быть удален, пока на него указывает траектория инструмента.
Полный каталог столов
Схема определяет таблицы 60. Приведенный ниже список был создан на основе определений таблицы Drizzle: имя SQL, экспортированная константа, количество столбцов, количество объявленных индексов и внешние ключи с их правилом удаления. «Нет действия» — значение SQLite по умолчанию: удаление не выполняется, пока существуют ссылки на строки (внешние ключи применяются при подключении к приложению).
| SQL-таблица | Экспорт | Колс | Идкс | Внешние ключи |
|---|---|---|---|---|
Account | accountTable | 13 | 1 | userId → Пользователь (каскад) |
ActivityLog | activityLogTable | 7 | 0 | userId → Пользователь (без действий) |
AIAnalysisLog | aIAnalysisLogTable | 7 | 0 | userId → Пользователь (без действий) |
ApiKey | apiKeyTable | 7 | 1 | userId → Пользователь (каскад) |
authenticators | authenticatorTable | 8 | 0 | userId → Пользователь (каскад) |
CadOperationLog | cadOperationLogTable | 11 | 1 | projectId → Проект (каскад)meshAssetId → MeshAsset (установить ноль) |
Component | componentTable | 10 | 0 | projectId → Проект (каскад) |
ComponentComment | componentCommentTable | 6 | 0 | componentId → Компонент (каскад)userId → Пользователь (без действий) |
ComponentVersion | componentVersionTable | 6 | 0 | componentId → Компонент (каскад)userId → Пользователь (без действий) |
Conversation | conversationTable | 6 | 0 | organizationId → Организация (каскад) |
ConversationParticipant | conversationParticipantTable | 5 | 1 | userId → Пользователь (каскад)conversationId → Разговор (каскад) |
CostSettings | costSettingsTable | 11 | 0 | ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
Drawing | drawingTable | 8 | 0 | projectId → Проект (каскад) |
DrawingVersion | drawingVersionTable | 5 | 0 | drawingId → Рисунок (каскад) |
FileUpload | fileUploadTable | 12 | 0 | — |
GitObject | gitObjectTable | 4 | 1 | projectId → Проект (каскад) |
GitRef | gitRefTable | 3 | 1 | projectId → Проект (каскад) |
GitRepo | gitRepoTable | 4 | 0 | projectId → Проект (каскад) |
LibraryItem | libraryItemTable | 14 | 0 | ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
LocalKeyValue | localKeyValueTable | 3 | 0 | — |
MachineConfig | machineConfigTable | 9 | 0 | ownerId → Пользователь (без действий) |
MarketplaceCategory | marketplaceCategoryTable | 10 | 1 | parentId → MarketplaceCategory (нет действий) |
MarketplaceCollection | marketplaceCollectionTable | 10 | 1 | sellerId → Торговая площадкаПродавец (без действий) |
MarketplaceCollectionItem | marketplaceCollectionItemTable | 5 | 1 | collectionId → MarketplaceCollection (каскад)itemId → MarketplaceItem (каскад) |
MarketplaceItem | marketplaceItemTable | 30 | 5 | sellerId → Торговая площадкаПродавец (без действий) |
MarketplaceOrder | marketplaceOrderTable | 23 | 4 | buyerId → Пользователь (без действий)sellerId → Торговая площадкаПродавец (без действий)itemId → MarketplaceItem (нет действий) |
MarketplacePayout | marketplacePayoutTable | 12 | 1 | sellerId → Торговая площадкаПродавец (без действий) |
MarketplaceReview | marketplaceReviewTable | 12 | 2 | itemId → MarketplaceItem (нет действий)buyerId → Пользователь (без действий)orderId → MarketplaceOrder (без действий) |
MarketplaceSeller | marketplaceSellerTable | 19 | 0 | userId → Пользователь (без действий) |
MarketplaceWishlist | marketplaceWishlistTable | 4 | 1 | userId → Пользователь (каскад)itemId → MarketplaceItem (каскад) |
Material | materialTable | 9 | 0 | ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
MaterialCost | materialCostTable | 10 | 0 | materialId → Материал (каскад)ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
MCPServerConfig | mCPServerConfigTable | 11 | 1 | — |
MeshAsset | meshAssetTable | 7 | 1 | projectId → Проект (каскад) |
Message | messageTable | 8 | 1 | senderId → Пользователь (каскад)conversationId → Разговор (каскад)fileId → FileUpload (установить ноль) |
Notification | notificationTable | 12 | 0 | userId → Пользователь (каскад)organizationId → Организация (каскад)projectId → Проект (каскад)componentId → Компонент (каскад)messageId → Сообщение (каскад) |
OperationCost | operationCostTable | 10 | 0 | ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
Organization | organizationTable | 5 | 0 | — |
OrganizationInvitation | organizationInvitationTable | 7 | 0 | organizationId → Организация (каскад) |
passkey | passkeyTable | 11 | 0 | userId → Пользователь (каскад) |
PluginConfiguration | pluginConfigurationTable | 2 | 0 | pluginId → PluginRegistryEntry (каскад) |
PluginRegistryEntry | pluginRegistryEntryTable | 9 | 0 | — |
ProductionCostEstimate | productionCostEstimateTable | 17 | 0 | toolpathId → Траектория инструмента (каскад)operationCostId → Стоимость операции (без действий)ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
Project | projectTable | 8 | 0 | ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
PullRequest | pullRequestTable | 14 | 1 | projectId → Проект (каскад)authorId → Пользователь (без действий) |
PullRequestReview | pullRequestReviewTable | 8 | 1 | pullRequestId → PullRequest (каскад)reviewerId → Пользователь (без действий) |
PushSubscription | pushSubscriptionTable | 6 | 0 | userId → Пользователь (каскад) |
Session | sessionTable | 8 | 0 | userId → Пользователь (каскад) |
Subscription | subscriptionTable | 24 | 3 | referenceId → Пользователь (каскад) |
Token | tokenTable | 8 | 2 | userId → Пользователь (каскад) |
Tool | toolTable | 17 | 0 | ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
Toolpath | toolpathTable | 17 | 0 | projectId → Проект (каскад)createdBy → Пользователь (без действий)drawingId → Рисование (без действий)materialId → Материал (без действий)toolId → Инструмент (без действия)machineConfigId → MachineConfig (без действий) |
ToolpathComment | toolpathCommentTable | 6 | 0 | toolpathId → Траектория инструмента (каскад)userId → Пользователь (без действий) |
ToolpathVersion | toolpathVersionTable | 7 | 0 | toolpathId → Траектория инструмента (каскад)userId → Пользователь (без действий) |
ToolWearCost | toolWearCostTable | 10 | 0 | toolId → Инструмент (каскад)ownerId → Пользователь (без действий)organizationId → Организация (без действий) |
twoFactor | twoFactorTable | 4 | 0 | userId → Пользователь (каскад) |
User | userTable | 17 | 0 | — |
UserOrganization | userOrganizationTable | 5 | 1 | userId → Пользователь (каскад)organizationId → Организация (каскад) |
verification_tokens | verificationTokenTable | 3 | 1 | — |
WorkflowRun | workflowRunTable | 10 | 1 | projectId → Проект (каскад) |
Пишем безопасно
- Оберните изменения с несколькими операторами в транзакции Drizzle внутри одного
queryвызов:database.query("op", (db) => db.transaction((tx) => ...)). Отдельныйqueryвызовы - это отдельные транзакции. - Дайте каждому запросу стабильное имя операции, разделенное точками (
projects.list,toolpaths.update). Это единственный контекст,DatabaseErrorнесёт помимо причины, и она появляется в логах. - Генерировать идентификаторы с помощью
newId()(uUID) или пусть это сделает столбец по умолчанию; не создавайте идентификаторы из имен. - Проверяйте столбцы JSON при их чтении. Объявленный тип TypeScript не проверяется SQLite.
- Использовать
LocalKeyValueчерез службу дополнительных ключей и значений, а не записывать строки напрямую, поэтому применяется проверка ключа и значения.
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]
}))
}) Чтобы создать строки с явными идентификаторами, используйте schema.newId(); @cadcamfun/db повторно экспортирует модуль схемы как schema пространство имен, а не как имена верхнего уровня.