Overview
This library provides a set of shared logic, documentation, and workflows for using a combination of drizzle and better-sqlite3 to include SQLite instances in the app.
These docs explain how consumers of @saflib/drizzle-sqlite3
should use this library within SAF applications.
Package Structure
Each package which depends on @saflib/drizzle-sqlite3
should have the following structure:
{service-name}-db/
├── data/
│ └── .gitkeep
├── drizzle.config.ts
├── errors.ts
├── index.ts
├── instances.ts
├── migrations/
├── package.json
├── queries/
│ └── <domain>/
│ ├── index.ts
│ ├── get-by-id.ts
│ ├── get-by-id.test.ts
│ └── ...
├── schema.ts
└── types.ts
Files and Directories Explained
data/
Where the SQLite files are stored. Follows convention.
drizzle.config.ts
Should export the result of defineConfig
from drizzle-kit
as default. At minimum this configuration should contain the following:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./migrations",
schema: "./schema.ts",
dialect: "sqlite",
});
See Drizzle Kit configuration file for more info.
errors.ts
Normally, this file only contains:
- One subclass of
HandledDatabaseError
. - Multiple subclasses of that subclass.
This enumerates all the errors which this database will return, and provides a superclass which can be used for convenient typing and instanceof
checking.
The main reason errors are stored in a file at the root rather than adjacent to where they're thrown, is mainly so it's easy to export them since consumers will need to reference them.
Errors may define a constructor and custom values, however that is often not required.
See Queries for more information on error handling.
See also example Identity DB Errors
index.ts
The interface of the database package, and what will be received when importing the package with no subpath.
It should return a spread of the public interface provided by instances.ts
, as well as the queries for each domain. It should also re-export types.ts
and errors.ts
.
export type * from "./types.ts";
export * from "./errors.ts";
import { mainDbManager } from "./instances.ts";
import * as users from "./queries/users/index.ts";
import * as todos from "./queries/todos/index.ts";
export const mainDb = {
...mainDbManager.publicInterface(),
users,
todos,
};
See instances.ts for details on the public interface but in a nutshell, consumers can "connect" to the file (or in-memory) db instance and receive a DbKey
which they can pass to queries. This keeps queries simple (in that they don't need to be associated our bound to a specific instance) and prevents direct access to the database by consumers.
instances.ts
The glue between the schema, config, and this library's logic. It will likely be exactly this:
import { DbManager } from "@saflib/drizzle-sqlite3";
import * as schema from "./schema.ts";
import config from "./drizzle.config.ts";
export const mainDbManager = new DbManager(schema, config, import.meta.url);
The DbManager
constructor needs the url of the current file to resolve the relative paths to the migration folder and the data folder.
migrations/
Where Drizzle's migration files are stored. See Migrations with Drizzle Kit for more information about migrations.
Drizzle also provides this helpful guide on different database migration approaches. I have only done "Option 4" (apply during runtime) so far as that's the simplest (not necessarily the safest for preventing downtime) but there's nothing in this library preventing the others.
package.json
Aside from including "@saflib/drizzle-sqlite3": "*"
in the dependencies, you can include some drizzle-kit
commands in package scripts. The most useful would be "generate": "drizzle-kit generate"
, which will create migration files if the schema has changed. You can also use the kit through npx
directly.
See Migrations with Drizzle Kit for more info.
queries/
The meat of the database package. Queries should be organized into sub-folders by domain, and each of those folders should include an index.ts
file which exports all queries in that folder in a single bundle which index.ts
will import.
See Queries for more information.
schema.ts
Where the Drizzle schema will live. See Drizzle schema and "Manage Schema" topics on the Drizzle site for more info, and the schema doc here for SAF-specific recommendations.
I haven't organized my schema into multiple files yet but it should be fine to put them into a schema
directory instead.
types.ts
Like errors.ts
, it's helpful to have public interface types in a central location where they can all be exported along with everything else.
By and large these are re-exports of Drizzle's $inferInsert
or $inferSelect
, or altered versions of those, usually with Pick
or Omit
. See for example the Identity Database's types.