Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Aking16/timify/llms.txt

Use this file to discover all available pages before exploring further.

Timify uses Drizzle Kit to manage schema evolution. The source of truth is src/db/schema.ts; Drizzle Kit compares that file against the live database (or generates SQL migration files from it) so your SQLite schema always stays in sync with your TypeScript definitions. All generated migration files are stored in the ./drizzle/ directory and tracked in version control.

Configuration

Drizzle Kit is configured in drizzle.config.ts at the project root:
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  out: "./drizzle",         // migration output directory
  schema: "./src/db/schema.ts",
  dialect: "sqlite",
  dbCredentials: {
    url: process.env.DB_FILE_NAME || "file:./src/db/local.db",
  },
});
OptionValueNotes
out./drizzleWhere migration SQL files and the meta journal are written
schema./src/db/schema.tsSingle schema file containing all table definitions
dialectsqliteTargets SQLite via @libsql/client
dbCredentials.urlDB_FILE_NAME env varFalls back to file:./src/db/local.db if the variable is unset
Set the DB_FILE_NAME environment variable in your .env.local file, for example DB_FILE_NAME=file:./src/db/local.db. The file: prefix is required by @libsql/client.

Migration History

The journal file at drizzle/meta/_journal.json records every applied migration in order. Timify currently has three migrations:
IndexTagTimestamp
00000_typical_wolfpack2026-05-14
10001_perpetual_spitfire2026-05-19
20002_pink_darkhawk2026-05-20

Migration 0 — 0000_typical_wolfpack.sql

This is the initial schema migration. It creates all eight tables from scratch and establishes every foreign-key relationship and index. Tables created: user, session, account, verification, projects, time_entries, tags, time_entry_tags Foreign keys added:
  • account.user_iduser.id (cascade delete)
  • projects.user_iduser.id (cascade delete)
  • session.user_iduser.id (cascade delete)
  • tags.user_iduser.id (cascade delete)
  • time_entries.user_iduser.id (cascade delete)
  • time_entries.project_idprojects.id (set null on delete)
  • time_entry_tags.time_entry_idtime_entries.id (cascade delete)
  • time_entry_tags.tag_idtags.id (cascade delete)
Indexes created: account_userId_idx, session_userId_idx, verification_identifier_idx
CREATE TABLE "user" (
  "id" text PRIMARY KEY NOT NULL,
  "name" text NOT NULL,
  "email" text NOT NULL,
  "email_verified" boolean DEFAULT false NOT NULL,
  "image" text,
  "created_at" timestamp DEFAULT now() NOT NULL,
  "updated_at" timestamp DEFAULT now() NOT NULL,
  CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "projects" (
  "id" text PRIMARY KEY NOT NULL,
  "user_id" text NOT NULL,
  "name" text NOT NULL,
  "description" text,
  "color" text DEFAULT '#3b82f6',
  "is_active" boolean DEFAULT true,
  "created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "time_entries" (
  "id" text PRIMARY KEY NOT NULL,
  "user_id" text NOT NULL,
  "project_id" text,
  "title" text,
  "description" text,
  "start_time" timestamp DEFAULT now(),
  "end_time" timestamp,
  "duration" integer,
  "is_running" boolean DEFAULT true,
  "billable" boolean DEFAULT false,
  "hourly_rate" real,
  "created_at" timestamp DEFAULT now(),
  "updated_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "tags" (
  "id" text PRIMARY KEY NOT NULL,
  "user_id" text NOT NULL,
  "name" text NOT NULL,
  "color" text DEFAULT '#9ca3af'
);
--> statement-breakpoint
CREATE TABLE "time_entry_tags" (
  "id" text PRIMARY KEY NOT NULL,
  "time_entry_id" text NOT NULL,
  "tag_id" text NOT NULL
);

Migration 1 — 0001_perpetual_spitfire.sql

This migration hardens nullable columns that were missing NOT NULL constraints in the initial schema, and adds missing columns to projects and tags. Changes:
  • projects.created_at — made NOT NULL
  • time_entries.created_at — made NOT NULL
  • time_entries.updated_at — given a DEFAULT now() and made NOT NULL
  • projects — new column hourly_rate real DEFAULT 0
  • projects — new column updated_at timestamp DEFAULT now() NOT NULL
  • tags — new column created_at timestamp DEFAULT now() NOT NULL
  • tags — new column updated_at timestamp DEFAULT now() NOT NULL
ALTER TABLE "projects" ALTER COLUMN "created_at" SET NOT NULL;
--> statement-breakpoint
ALTER TABLE "time_entries" ALTER COLUMN "created_at" SET NOT NULL;
--> statement-breakpoint
ALTER TABLE "time_entries" ALTER COLUMN "updated_at" SET DEFAULT now();
--> statement-breakpoint
ALTER TABLE "time_entries" ALTER COLUMN "updated_at" SET NOT NULL;
--> statement-breakpoint
ALTER TABLE "projects" ADD COLUMN "hourly_rate" real DEFAULT 0;
--> statement-breakpoint
ALTER TABLE "projects" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;
--> statement-breakpoint
ALTER TABLE "tags" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;
--> statement-breakpoint
ALTER TABLE "tags" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;

Migration 2 — 0002_pink_darkhawk.sql

This migration adds the composite unique constraint to the time_entry_tags join table, preventing a tag from being applied to the same time entry more than once. Change: time_entry_tags — new constraint time_entry_tags_time_entry_id_tag_id_unique on (time_entry_id, tag_id)
ALTER TABLE "time_entry_tags"
  ADD CONSTRAINT "time_entry_tags_time_entry_id_tag_id_unique"
  UNIQUE("time_entry_id","tag_id");

Drizzle Kit Commands

The project’s AGENTS.md lists npx drizzle-kit push and npx drizzle-kit studio as the primary day-to-day commands. The generate and migrate commands are available for production-grade deployments where an auditable migration trail is required.

drizzle-kit push — Sync schema directly to the database

npx drizzle-kit push
Reads src/db/schema.ts, introspects the live database, and applies the diff without generating any migration files. Changes are immediate. When to use: Local development, single-developer instances, or rapid prototyping where you want schema changes reflected instantly without maintaining a migration history.

drizzle-kit generate — Generate a migration file from schema changes

npx drizzle-kit generate
Compares src/db/schema.ts against the last snapshot in drizzle/meta/ and writes a new numbered .sql file to ./drizzle/. Nothing is applied to the database yet. When to use: Before deploying to staging or production. Review the generated SQL before running migrate.

drizzle-kit migrate — Apply pending migration files

npx drizzle-kit migrate
Reads the journal at drizzle/meta/_journal.json, determines which migrations have not yet been applied to the target database, and runs them in order. When to use: Production deployments and any environment where you need a controlled, auditable rollout of schema changes.

drizzle-kit studio — Visual database browser

npx drizzle-kit studio
Launches Drizzle Studio, a web-based GUI for browsing and editing your SQLite data, at http://localhost:4983. When to use: Inspecting data during development, debugging query results, or manually correcting records without writing raw SQL.
push vs migrate — which should you use?Use drizzle-kit push during local development and for single-instance self-hosted deployments where you don’t need an auditable migration trail. It is faster and requires no intermediate file generation.Use drizzle-kit generate + drizzle-kit migrate for production or any multi-environment setup (staging → production). Migration files can be reviewed in pull requests, rolled back by reverting commits, and re-run reliably on any fresh database — giving you full control over schema changes.
Never run drizzle-kit push against a production database that contains live data. It can drop and recreate columns or tables to resolve schema diffs, which may cause irreversible data loss. Always use migrate in production.

Build docs developers (and LLMs) love