Timify’s database is a single SQLite file managed by Drizzle ORM. The schema lives inDocumentation 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.
src/db/schema.ts and is split into two groups: four auth tables owned by better-auth, and four application tables that hold your projects, time entries, and tags. All primary keys use text UUIDs; timestamps are stored as Unix epoch integers and surfaced as JavaScript Date objects by Drizzle’s { mode: "timestamp" } option.
Auth Tables
These four tables are created and maintained by better-auth. You should not write to them directly; interact with them through the better-auth client/server APIs.user
Stores each registered user’s core identity. The email column carries a unique constraint enforced at the database level.
Primary key. A text UUID assigned by better-auth at registration.
Display name of the user.
Unique email address. Used as the login identifier.
Whether the user has confirmed their email address. Defaults to
false.Optional URL to the user’s avatar image.
Timestamp of account creation. Defaults to the current Unix epoch via
unixepoch().Timestamp of the last update. Automatically refreshed on every write via
$onUpdateFn.session
Holds active authentication sessions. Each user may have at most one session at a time, enforced by a unique index on userId.
Primary key. A text UUID.
The point in time after which this session is invalid and must be re-authenticated.
Unique opaque session token sent to the client. Carries a database-level
UNIQUE constraint.Optional IP address captured at session creation.
Optional
User-Agent header captured at session creation.Foreign key →
user.id. Cascade-deletes this session when the parent user is removed. Covered by unique index session_userId_idx.Session creation timestamp. Defaults to the current Unix epoch.
Last-modified timestamp. Automatically refreshed on every write.
account
Links a user to an OAuth provider account or a local credential. A unique index on userId (account_userId_idx) ensures one account record per user.
Primary key. A text UUID.
The user’s identifier within the external provider (e.g. a GitHub user ID).
Identifier for the auth provider, e.g.
"github" or "credential".Foreign key →
user.id. Cascade-deletes this account record when the parent user is removed.OAuth access token, if issued by the provider.
OAuth refresh token, if issued by the provider.
OIDC identity token, if issued by the provider.
Expiry timestamp for the access token.
Expiry timestamp for the refresh token.
Space-separated OAuth scopes that were granted.
Hashed password for credential-based (email/password) accounts.
null for OAuth accounts.Record creation timestamp. Defaults to the current Unix epoch.
Last-modified timestamp. Automatically refreshed on every write.
verification
Stores short-lived tokens used for email verification and password-reset flows. A unique index on identifier (verification_identifier_idx) prevents duplicate pending verifications for the same target.
Primary key. A text UUID.
The target being verified — typically an email address or phone number. Unique per row.
The secret token or OTP value to be checked against user input.
Expiry timestamp after which the verification record should be considered invalid.
Record creation timestamp. Defaults to the current Unix epoch.
Last-modified timestamp. Automatically refreshed on every write.
Application Tables
These tables form the core of Timify’s time-tracking domain. All IDs are auto-generated viacrypto.randomUUID() through Drizzle’s $defaultFn.
projects
Represents a named billable or non-billable project that groups time entries together.
Primary key. UUID generated at insert time via
crypto.randomUUID().Foreign key →
user.id. Cascade-deletes all projects when the parent user is removed.Human-readable project name shown throughout the UI.
Optional free-text description of the project’s purpose.
Hex color code used to visually distinguish the project in the UI. Defaults to
#3b82f6 (blue).Default billing rate in the user’s chosen currency. Defaults to
0. Individual time entries can override this value.Whether the project is currently active. Inactive projects are hidden from timers but retained for reporting. Defaults to
true.Record creation timestamp. Defaults to the current Unix epoch.
Last-modified timestamp. Automatically refreshed on every write.
time_entries
Each row represents a single tracked interval of work. A running entry has isRunning = true and a null endTime; a stopped entry has both endTime and duration populated.
Primary key. UUID generated at insert time via
crypto.randomUUID().Foreign key →
user.id. Cascade-deletes all time entries when the parent user is removed.Optional foreign key →
projects.id. When the referenced project is deleted, this column is set to null (the entry is preserved but un-linked).Short label for what was worked on during this interval.
Optional longer description or notes about the work.
When the timer was started. Defaults to the current Unix epoch at insert time.
When the timer was stopped.
null for currently running entries.Pre-calculated duration in seconds. Populated when the entry is stopped.
null for running entries.true while the timer is actively counting. Defaults to true at insert.Whether this entry should count toward billable hours. Defaults to
false.Per-entry rate override. When set, this takes precedence over the parent project’s
hourlyRate. Optional.Record creation timestamp. Defaults to the current Unix epoch.
Last-modified timestamp. Automatically refreshed on every write.
duration is a cached computed field. It equals endTime - startTime in seconds and is written when the entry is stopped. Always treat it as a derived value; for running entries, compute elapsed time from startTime and the current clock.tags
User-defined labels used to categorize time entries. Each tag belongs to one user and can be applied to many time entries.
Primary key. UUID generated at insert time via
crypto.randomUUID().Foreign key →
user.id. Cascade-deletes all tags when the parent user is removed.Display name of the tag, e.g.
"deep-work" or "client-abc".Hex color code for the tag badge. Defaults to
#9ca3af (gray).Record creation timestamp. Defaults to the current Unix epoch.
Last-modified timestamp. Automatically refreshed on every write.
time_entry_tags
Join table implementing the many-to-many relationship between time_entries and tags. A composite unique index on (timeEntryId, tagId) prevents the same tag from being applied to the same entry twice.
Primary key. UUID generated at insert time via
crypto.randomUUID().Foreign key →
time_entries.id. Cascade-deletes this join row when the parent time entry is removed.Foreign key →
tags.id. Cascade-deletes this join row when the referenced tag is removed.Relations
Drizzle relations provide TypeScript type safety for eager-loading queries. They do not create extra SQL constraints — all referential integrity is handled by the foreign keys described above.Cascade Delete Summary
Understanding delete propagation is critical when removing records.| Deleted record | Affected child records | Behavior |
|---|---|---|
user | session, account, projects, time_entries, tags | Cascade delete — all owned rows are removed |
projects row | time_entries.projectId | Set null — entries are kept, project link cleared |
time_entries row | time_entry_tags | Cascade delete — join rows are removed |
tags row | time_entry_tags | Cascade delete — join rows are removed |
Unique Constraints Summary
| Table | Constraint | Columns |
|---|---|---|
user | user_email_unique | email |
session | session_token_unique | token |
session | session_userId_idx | user_id |
account | account_userId_idx | user_id |
verification | verification_identifier_idx | identifier |
time_entry_tags | unique_time_entry_tag | time_entry_id, tag_id |
Example: Querying with Drizzle
Thedb singleton exported from src/db/index.ts connects via @libsql/client and is the entry point for all queries.
