feat(04-01): add tags schema with case-insensitive unique index

- Add tags table with nanoid PK and case-insensitive unique index on name
- Add entry_tags junction table with composite PK and cascade deletes
- Export lower() helper function for case-insensitive queries
- Export Tag, NewTag, EntryTag types for TypeScript inference
This commit is contained in:
Thomas Richter
2026-01-31 13:03:17 +01:00
parent a232a95ced
commit 7dc63e625d

View File

@@ -1,4 +1,11 @@
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { sqliteTable, text, integer, uniqueIndex, primaryKey } from 'drizzle-orm/sqlite-core';
import { sql, type SQL } from 'drizzle-orm';
import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core';
// Helper function for case-insensitive indexing
export function lower(column: AnySQLiteColumn): SQL {
return sql`lower(${column})`;
}
// Entry types: 'task' (actionable) or 'thought' (reference)
export const entries = sqliteTable('entries', {
@@ -36,3 +43,35 @@ export const images = sqliteTable('images', {
export type Image = typeof images.$inferSelect;
export type NewImage = typeof images.$inferInsert;
// Tags table: reusable tags for organizing entries
export const tags = sqliteTable(
'tags',
{
id: text('id').primaryKey(), // nanoid
name: text('name').notNull(),
createdAt: text('created_at')
.notNull()
.$defaultFn(() => new Date().toISOString())
},
(table) => [uniqueIndex('tagNameUniqueIndex').on(lower(table.name))]
);
export type Tag = typeof tags.$inferSelect;
export type NewTag = typeof tags.$inferInsert;
// Junction table for many-to-many entry-tag relationship
export const entryTags = sqliteTable(
'entry_tags',
{
entryId: text('entry_id')
.notNull()
.references(() => entries.id, { onDelete: 'cascade' }),
tagId: text('tag_id')
.notNull()
.references(() => tags.id, { onDelete: 'cascade' })
},
(t) => [primaryKey({ columns: [t.entryId, t.tagId] })]
);
export type EntryTag = typeof entryTags.$inferSelect;