@privaty/db
v0.1.0
Published
Downloads
37
Readme
@privaty/db
Reusable database building blocks on drizzle:
column kits, schema bundles, and thin data helpers — for postgres
and the sqlite family (Cloudflare D1, Turso, native libsql), with the
schemas also shipped as plain .sql DDL for consumers that don't use
drizzle (or aren't written in TypeScript at all).
pnpm add drizzle-orm @privaty/db
drizzle-ormis a peerDependency — you bring your own instance and driver.
Two entries, never one
import { timestamps } from "@privaty/db/pg";
// or
import { timestamps } from "@privaty/db/sqlite";There is deliberately no root export: the dialects are separate entry points so an edge/D1 bundle never touches pg code. Every export exists on both sides under the same name — the twins are maintained explicitly (drizzle has no single-source multi-dialect schema) and a parity spec keeps them identical in shape.
Bundles
The unit of this library is the bundle: one or more tables designed to be used together (a single-table schema is a bundle of one). Foreign keys exist only within a bundle — bundles reference each other by plain id columns, never by constraint, so each one composes into your app independently.
calendar — dim_dates, dim_holidays, dim_calendar_days
A date dimension, a multi-calendar holiday table, and a per-calendar business-day table. All ship empty; pure generators fill them, in this order (the foreign keys insist):
import {
danishCalendar,
dimCalendarDayRows,
dimCalendarDays,
dimDateRows,
dimDates,
dimHolidayRows,
dimHolidays,
} from "@privaty/db/sqlite"; // or "@privaty/db/pg" — same names, same rows
const range = {
from: "2020-01-01",
to: "2035-12-31",
calendars: [danishCalendar],
};
await db.insert(dimDates).values(dimDateRows(range));
await db.insert(dimHolidays).values(dimHolidayRows(range));
await db.insert(dimCalendarDays).values(dimCalendarDayRows(range));(Chunk the inserts on D1 — it caps bound parameters per statement.)
dim_dates holds one universal row per day: an integer date_key
(YYYYMMDD), the ISO date, calendar and ISO-week parts, sortable
labels, weekend and month-end flags — and no names, since labels belong
to the frontend. dim_holidays keys on (date_key, calendar, code):
calendar is an ISO 3166 code (DK, DE-BY), code a global
snake_case meaning such as easter_monday, kind one of public,
bank, observance. dim_calendar_days holds one row per day per
calendar: a flag per holiday kind, is_business_day, the running
business_day_of_month with business_days_in_month (so "the last
business day of the month" is business_day_of_month =
business_days_in_month), and previous_business_day_key /
next_business_day_key for rolling a date with one key lookup. What
counts as a business day is decided when you seed — a weekday with no
public holiday by default; pass businessDayExcludes: ["public",
"bank"] for a bank's calendar — so each database carries its owner's
definition and every lookup rides the key. Define more calendars with
defineCalendar;
CalendarCode<typeof calendar> is the union of a calendar's codes, so
an i18n table keyed on it is exhaustive. Each rule carries its
native-language name for consumers that want the label as-is.
Performance lives in your fact tables: index every date_key column
that references the dimension (Postgres does not index referencing
columns on its own), and write date filters as key or date ranges
(date_key BETWEEN 20260101 AND 20261231) rather than
year = 2026 — the range rides the primary key, the column filter scans
the table, and on D1 every scanned row is billed. The bundle itself ships
only its primary keys and unique constraints; add an index on a label
column if your queries filter by one.
The bundle also exports its relations part. Spread it into your own relations object — cross-bundle relations belong to your app, on the plain id columns:
import { defineRelations } from "drizzle-orm";
import { calendarRelations, dimDates } from "@privaty/db/pg";
const relations = {
...defineRelations({ orders, dimDates }, (r) => ({
orders: {
day: r.one.dimDates({ from: r.orders.dateKey, to: r.dimDates.dateKey }),
},
})),
...calendarRelations,
};
const db = drizzle(client, { relations });
await db.query.orders.findMany({ with: { day: { with: { holidays: true } } } });Raw SQL
Every bundle ships as one dependency-ordered DDL file per dialect under
sql/<dialect>/<bundle>.sql — apply it with any tool in any language.
The files are generated from the drizzle schemas (drizzle-kit export)
and CI fails if they ever drift from the TypeScript source. On the
sqlite family, remember that foreign keys are enforced only with
PRAGMA foreign_keys = ON (D1 and libsql default it on).
Status
0.1.0 ships the calendar bundle — three tables in both dialects, their
.sql artifacts, generators, and a Danish holiday calendar — proven
against PGlite and libsql in the test suite. See the repository's
CLAUDE.md for the full design and roadmap.
