feat(WHE-27): add drizzle db package and typed env config

This commit is contained in:
2026-03-05 03:05:02 +04:00
parent 18168a8dab
commit 8086044938
21 changed files with 631 additions and 28 deletions

View File

@@ -0,0 +1,14 @@
import { defineConfig } from 'drizzle-kit'
const dbCredentials = process.env.DATABASE_URL
? {
url: process.env.DATABASE_URL
}
: undefined
export default defineConfig({
dialect: 'postgresql',
schema: './packages/db/src/schema.ts',
out: './packages/db/drizzle',
dbCredentials
})

View File

@@ -0,0 +1,18 @@
CREATE TABLE "households" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "members" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"household_id" uuid NOT NULL,
"telegram_user_id" text NOT NULL,
"display_name" text NOT NULL,
"is_admin" integer DEFAULT 0 NOT NULL,
"joined_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "members" ADD CONSTRAINT "members_household_id_households_id_fk" FOREIGN KEY ("household_id") REFERENCES "public"."households"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "members_household_idx" ON "members" USING btree ("household_id");--> statement-breakpoint
CREATE UNIQUE INDEX "members_household_tg_user_unique" ON "members" USING btree ("household_id","telegram_user_id");

View File

@@ -0,0 +1,151 @@
{
"id": "41ded009-68a0-4c58-87ba-e4ff51d87211",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.households": {
"name": "households",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.members": {
"name": "members",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"household_id": {
"name": "household_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"telegram_user_id": {
"name": "telegram_user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"is_admin": {
"name": "is_admin",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"joined_at": {
"name": "joined_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"members_household_idx": {
"name": "members_household_idx",
"columns": [
{
"expression": "household_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"members_household_tg_user_unique": {
"name": "members_household_tg_user_unique",
"columns": [
{
"expression": "household_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "telegram_user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"members_household_id_households_id_fk": {
"name": "members_household_id_households_id_fk",
"tableFrom": "members",
"tableTo": "households",
"columnsFrom": ["household_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1772663250726,
"tag": "0000_modern_centennial",
"breakpoints": true
}
]
}

16
packages/db/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "@household/db",
"private": true,
"type": "module",
"scripts": {
"build": "bun build src/index.ts --outdir dist --target bun",
"typecheck": "tsgo --project tsconfig.json --noEmit",
"test": "bun test --pass-with-no-tests",
"lint": "oxlint \"src\""
},
"dependencies": {
"@household/config": "workspace:*",
"drizzle-orm": "^0.44.5",
"postgres": "^3.4.7"
}
}

12
packages/db/src/client.ts Normal file
View File

@@ -0,0 +1,12 @@
import postgres from 'postgres'
import { drizzle } from 'drizzle-orm/postgres-js'
import { env } from '@household/config'
const queryClient = postgres(env.DATABASE_URL, {
prepare: false,
max: 5
})
export const db = drizzle(queryClient)
export { queryClient }

2
packages/db/src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { db, queryClient } from './client'
export * as schema from './schema'

28
packages/db/src/schema.ts Normal file
View File

@@ -0,0 +1,28 @@
import { index, integer, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core'
export const households = pgTable('households', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
})
export const members = pgTable(
'members',
{
id: uuid('id').defaultRandom().primaryKey(),
householdId: uuid('household_id')
.notNull()
.references(() => households.id, { onDelete: 'cascade' }),
telegramUserId: text('telegram_user_id').notNull(),
displayName: text('display_name').notNull(),
isAdmin: integer('is_admin').default(0).notNull(),
joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
householdIdx: index('members_household_idx').on(table.householdId),
householdTgUserUnique: uniqueIndex('members_household_tg_user_unique').on(
table.householdId,
table.telegramUserId
)
})
)

View File

@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true
},
"include": ["src/**/*.ts", "drizzle.config.ts"]
}