Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Check architecture boundaries
run: |
pnpm check:contracts
pnpm check:architecture

- name: Lint
run: pnpm lint

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"scripts": {
"format": "prettier --write .",
"lint": "eslint --cache .",
"pretypecheck": "pnpm --dir ../.. check:contract-boundaries && pnpm ipc:check",
"pretypecheck": "pnpm --dir ../.. check:contracts && pnpm --dir ../.. check:architecture && pnpm ipc:check",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "pnpm typecheck:node && pnpm typecheck:web",
Expand Down
26 changes: 14 additions & 12 deletions apps/desktop/src/main/database/client.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { drizzle, BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { existsSync } from 'fs'
import * as schema from '@memry/db-schema/schema'
import * as dataSchema from '@memry/db-schema/data-schema'
import * as indexSchema from '@memry/db-schema/index-schema'
import * as sqliteVec from 'sqlite-vec'
import { EMBEDDING_DIMENSION } from '../lib/embeddings-constants'
import type { DataDb, IndexDb, RawIndexDb, DrizzleDb } from './types'

export type DrizzleDb = BetterSQLite3Database<typeof schema>
export type { DataDb, IndexDb, RawIndexDb, DrizzleDb } from './types'

let dataDb: DrizzleDb | null = null
let indexDb: DrizzleDb | null = null
let dataDb: DataDb | null = null
let indexDb: IndexDb | null = null
let sqliteDataDb: Database.Database | null = null
let sqliteIndexDb: Database.Database | null = null

export function initDatabase(dbPath: string): DrizzleDb {
export function initDatabase(dbPath: string): DataDb {
sqliteDataDb = new Database(dbPath)

// WAL mode for better concurrency and crash recovery
Expand All @@ -33,11 +35,11 @@ export function initDatabase(dbPath: string): DrizzleDb {
// Store temp tables in memory
sqliteDataDb.pragma('temp_store = MEMORY')

dataDb = drizzle(sqliteDataDb, { schema })
dataDb = drizzle(sqliteDataDb, { schema: dataSchema })
return dataDb
}

export function initIndexDatabase(dbPath: string): DrizzleDb {
export function initIndexDatabase(dbPath: string): IndexDb {
sqliteIndexDb = new Database(dbPath)

// WAL mode for better concurrency
Expand Down Expand Up @@ -69,13 +71,13 @@ export function initIndexDatabase(dbPath: string): DrizzleDb {
)
`)

indexDb = drizzle(sqliteIndexDb, { schema })
indexDb = drizzle(sqliteIndexDb, { schema: indexSchema })
return indexDb
}

export function getDatabase(): DrizzleDb {
if (!dataDb) throw new Error('Database not initialized')
return dataDb
return dataDb as DrizzleDb
}

export function isDatabaseInitialized(): boolean {
Expand All @@ -84,14 +86,14 @@ export function isDatabaseInitialized(): boolean {

export function getIndexDatabase(): DrizzleDb {
if (!indexDb) throw new Error('Index database not initialized')
return indexDb
return indexDb as DrizzleDb
}

/**
* Get the raw better-sqlite3 connection for the index database.
* Used for direct sqlite-vec queries on vec_notes virtual table.
*/
export function getRawIndexDatabase(): Database.Database {
export function getRawIndexDatabase(): RawIndexDb {
if (!sqliteIndexDb) throw new Error('Index database not initialized')
return sqliteIndexDb
}
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/main/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ export {
closeAllDatabases,
checkIndexHealth,
withTimeout,
type DrizzleDb,
type IndexHealth
} from './client'

export type { DataDb, IndexDb, RawIndexDb, DrizzleDb } from './types'

export { runMigrations, runIndexMigrations } from './migrate'

export {
Expand Down
9 changes: 6 additions & 3 deletions apps/desktop/src/main/database/queries/notes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ export {
renameTag,
deleteTag,
removeTagFromNote,
type NoteWithTagInfo
} from './tag-queries'

export {
getOrCreateTag,
getAllTagDefinitions,
updateTagColor,
renameTagDefinition,
deleteTagDefinition,
ensureTagDefinitions,
type NoteWithTagInfo
} from './tag-queries'
ensureTagDefinitions
} from '../tag-definitions'

export {
setNoteLinks,
Expand Down
145 changes: 1 addition & 144 deletions apps/desktop/src/main/database/queries/notes/tag-queries.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { eq, and, or, inArray, like, count, desc, sql } from 'drizzle-orm'
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'
import {
noteCache,
noteTags,
type NoteCache,
type NewNoteTag
} from '@memry/db-schema/schema/notes-cache'
import { tagDefinitions } from '@memry/db-schema/schema/tag-definitions'
import * as schema from '@memry/db-schema/schema'

type DrizzleDb = BetterSQLite3Database<typeof schema>
import type { DrizzleDb } from '../../types'

// ============================================================================
// Tag Operations
Expand Down Expand Up @@ -261,142 +257,3 @@ export function removeTagFromNote(db: DrizzleDb, noteId: string, tag: string): v
.where(and(eq(noteTags.noteId, noteId), eq(noteTags.tag, normalizedTag)))
.run()
}

// ============================================================================
// Tag Definition Operations (vault-wide tag registry with colors)
// ============================================================================

const TAG_COLOR_PALETTE = [
'rose',
'pink',
'fuchsia',
'purple',
'violet',
'indigo',
'blue',
'sky',
'cyan',
'teal',
'emerald',
'green',
'lime',
'yellow',
'amber',
'orange',
'stone',
'slate',
'gray',
'zinc',
'neutral',
'warm',
'red',
'coral'
]

export function getOrCreateTag(db: DrizzleDb, name: string): { name: string; color: string } {
const normalizedName = name.toLowerCase().trim()

const existing = db
.select()
.from(tagDefinitions)
.where(eq(tagDefinitions.name, normalizedName))
.get()

if (existing) {
return { name: existing.name, color: existing.color }
}

const tagCount = db.select({ count: count() }).from(tagDefinitions).get()?.count ?? 0

const color = TAG_COLOR_PALETTE[tagCount % TAG_COLOR_PALETTE.length]

db.insert(tagDefinitions).values({ name: normalizedName, color }).run()

return { name: normalizedName, color }
}

export function getAllTagDefinitions(db: DrizzleDb): { name: string; color: string }[] {
return db
.select({
name: tagDefinitions.name,
color: tagDefinitions.color
})
.from(tagDefinitions)
.all()
}

export function updateTagColor(db: DrizzleDb, name: string, color: string): void {
const normalizedName = name.toLowerCase().trim()
db.update(tagDefinitions).set({ color }).where(eq(tagDefinitions.name, normalizedName)).run()
}

export function renameTagDefinition(db: DrizzleDb, oldName: string, newName: string): void {
const normalizedOld = oldName.toLowerCase().trim()
const normalizedNew = newName.toLowerCase().trim()

if (normalizedOld === normalizedNew) return

const existingNew = db
.select()
.from(tagDefinitions)
.where(eq(tagDefinitions.name, normalizedNew))
.get()

if (existingNew) {
db.delete(tagDefinitions).where(eq(tagDefinitions.name, normalizedOld)).run()
} else {
db.update(tagDefinitions)
.set({ name: normalizedNew })
.where(eq(tagDefinitions.name, normalizedOld))
.run()
}

const children = db
.select({ name: tagDefinitions.name })
.from(tagDefinitions)
.where(like(tagDefinitions.name, `${normalizedOld}/%`))
.all()

for (const child of children) {
const newChildName = normalizedNew + child.name.slice(normalizedOld.length)
const existingChild = db
.select()
.from(tagDefinitions)
.where(eq(tagDefinitions.name, newChildName))
.get()

if (existingChild) {
db.delete(tagDefinitions).where(eq(tagDefinitions.name, child.name)).run()
} else {
db.update(tagDefinitions)
.set({ name: newChildName })
.where(eq(tagDefinitions.name, child.name))
.run()
}
}
}

export function deleteTagDefinition(
db: DrizzleDb,
name: string,
options: { cascade?: boolean } = {}
): void {
const normalizedName = name.toLowerCase().trim()
db.delete(tagDefinitions).where(eq(tagDefinitions.name, normalizedName)).run()

if (options.cascade) {
db.delete(tagDefinitions)
.where(like(tagDefinitions.name, `${normalizedName}/%`))
.run()
}
}

export function ensureTagDefinitions(
db: DrizzleDb,
tags: string[]
): { name: string; color: string }[] {
const normalized = Array.from(
new Set(tags.map((tag) => tag.toLowerCase().trim()).filter(Boolean))
)
return normalized.map((tag) => getOrCreateTag(db, tag))
}
Loading
Loading