Keep/docs/data-models.md
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

11 KiB

Data Models - Memento Project

Overview

Memento uses SQLite as its database with Prisma ORM. The database schema is shared between the web application (keep-notes) and the MCP server.

Database: SQLite (prisma/dev.db) ORM: Prisma 5.22.0 Adapters:

  • @prisma/adapter-better-sqlite3 (primary)
  • @prisma/adapter-libsql (alternative for Turso)

Database Schema

User

Represents a user account with authentication and profile information.

model User {
  id               String     @id @default(cuid())
  name             String?
  email            String     @unique
  emailVerified    DateTime?
  password         String?    // Hashed password (bcrypt)
  role             String     @default("USER")  // "USER" or "ADMIN"
  image            String?    // Profile picture URL
  theme            String     @default("light") // UI theme preference
  resetToken       String?    @unique           // Password reset token
  resetTokenExpiry DateTime?                    // Reset token expiration
  accounts         Account[]
  sessions         Session[]
  notes            Note[]
  labels           Label[]
  createdAt        DateTime   @default(now())
  updatedAt        DateTime   @updatedAt
}

Relationships:

  • One-to-many with Account (OAuth providers)
  • One-to-many with Session (active sessions)
  • One-to-many with Note (user's notes)
  • One-to-many with Label (user's labels)

Fields:

  • id: CUID (Collision-resistant Unique Identifier)
  • email: Unique email address
  • password: Optional (can be OAuth-only users)
  • role: RBAC - "USER" or "ADMIN"
  • theme: UI theme preference ("light" or "dark")
  • resetToken: Password recovery token (unique)
  • resetTokenExpiry: Token validity period

Account

Stores OAuth provider account information (NextAuth.js).

model Account {
  userId                String
  type                  String
  provider              String           // google, github, etc.
  providerAccountId     String
  refresh_token         String?
  access_token          String?
  expires_at            Int?
  token_type            String?
  scope                 String?
  id_token              String?
  session_state         String?
  createdAt             DateTime         @default(now())
  updatedAt             DateTime         @updatedAt
  user                  User             @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@id([provider, providerAccountId])
}

Purpose: Links OAuth provider accounts to local User accounts

Relationships:

  • Many-to-one with User (via userId)

Constraints:

  • Composite unique key on provider + providerAccountId

Session

Stores active user sessions (NextAuth.js).

model Session {
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Purpose: Manages active user sessions for authentication

Relationships:

  • Many-to-one with User (via userId)

Fields:

  • sessionToken: Unique session identifier
  • expires: Session expiration timestamp

VerificationToken

Stores email verification tokens (NextAuth.js).

model VerificationToken {
  identifier String
  token      String
  expires    DateTime

  @@id([identifier, token])
}

Purpose: Email verification flow

Constraints:

  • Composite unique key on identifier + token

Label

User-defined labels/tags for organizing notes.

model Label {
  id        String   @id @default(cuid())
  name      String
  color     String   @default("gray")
  userId    String?  // Made optional for migration
  user      User?    @relation(fields: [userId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@unique([name, userId])  // Labels unique per user
  @@index([userId])
}

Purpose: Categorization and organization of notes

Relationships:

  • Many-to-one with User (via userId)

Fields:

  • name: Label display name
  • color: Visual color (red, orange, yellow, green, teal, blue, purple, pink, gray)
  • userId: Optional (migration artifact, logic enforces ownership)

Constraints:

  • Unique label name per user
  • Indexed on userId for fast lookup

Usage:

  • Stored as JSON array in Note.labels
  • User can have multiple labels with the same name? No, unique constraint

Note

Core data model - represents a note in the system.

model Note {
  id                  String    @id @default(cuid())
  title               String?              // Optional title
  content             String
  color               String    @default("default")
  isPinned            Boolean   @default(false)
  isArchived          Boolean   @default(false)
  type                String    @default("text")         // "text" or "checklist"
  checkItems          String?                         // JSON array
  labels              String?                         // JSON array
  images              String?                         // JSON array
  links               String?                         // JSON array
  reminder            DateTime?                       // Reminder timestamp
  isReminderDone      Boolean   @default(false)
  reminderRecurrence  String?                         // "none", "daily", "weekly", "monthly", "custom"
  reminderLocation    String?                         // Location-based reminders
  isMarkdown          Boolean   @default(false)       // Markdown rendering
  size                String    @default("small")      // "small", "medium", "large"
  embedding           String?                         // Vector embeddings (JSON)
  userId              String?                         // Owner
  user                User?     @relation(fields: [userId], references: [id], onDelete: Cascade)
  order               Int       @default(0)
  createdAt           DateTime  @default(now())
  updatedAt           DateTime  @updatedAt

  @@index([isPinned])
  @@index([isArchived])
  @@index([order])
  @@index([reminder])
  @@index([userId])
}

Purpose: Core note-taking entity

Relationships:

  • Many-to-one with User (via userId)

Fields:

Basic:

  • id: CUID
  • title: Optional heading/title
  • content: Note content (text or markdown)
  • color: Background color (default, red, orange, yellow, green, teal, blue, purple, pink, gray)
  • type: Note type - "text" or "checklist"

State Flags:

  • isPinned: Shows at top of list
  • `isArchived**: Removed from main view
  • isReminderDone: Reminder completed

Rich Features:

  • checkItems: JSON array of checklist items (if type=checklist)
    [
      {"id": "string", "text": "string", "checked": boolean}
    ]
    
  • labels: JSON array of label names
    ["work", "ideas", "urgent"]
    
  • images: JSON array of image URLs or base64
    ["data:image/png;base64,..."]
    
  • links: JSON array of link metadata
  • reminder: ISO8601 datetime for reminder
  • reminderRecurrence: Recurrence pattern
  • reminderLocation: Location-based reminders (future)
  • isMarkdown: Enable markdown rendering
  • size: Visual size (small, medium, large)
  • embedding: Vector embeddings for semantic search (JSON)

Ordering:

  • order: Manual sort order (drag-and-drop)

Indexes:

  • isPinned: Fast lookup for pinned notes
  • isArchived: Filter archived notes
  • order: Sort by manual ordering
  • reminder: Reminder queries (cron jobs)
  • userId: Filter by user

SystemConfig

Key-value storage for system-wide configuration.

model SystemConfig {
  key   String @id
  value String
}

Purpose: System configuration and feature flags

Examples:

  • Feature toggles
  • System-wide settings
  • Admin configuration

Relationships Diagram

User (1) ----< (N) Account
  |
  | (1)
  |
  +----< (N) Session
  |
  | (1)
  |
  +----< (N) Note
  |
  | (1)
  |
  +----< (N) Label

VerificationToken (standalone)

SystemConfig (standalone)

JSON Field Structures

checkItems (Note)

Array of checklist items:

[
  {
    "id": "unique-id",
    "text": "Task description",
    "checked": false
  }
]

labels (Note)

Array of label names (strings):

[
  "work",
  "ideas",
  "urgent"
]

images (Note)

Array of image URLs or base64 strings:

[
  "data:image/png;base64,iVBORw0KGgo...",
  "/uploads/images/note-image.png"
]

Array of link metadata (structure TBD):

[
  {
    "url": "https://example.com",
    "title": "Example Site",
    "description": "..."
  }
]

embedding (Note)

Vector embeddings for semantic search (JSON string):

[0.123, -0.456, 0.789, ...]

Database Operations

Common Queries

Get active user's notes:

const notes = await prisma.note.findMany({
  where: {
    userId: session.user.id,
    isArchived: false
  },
  orderBy: [
    { isPinned: 'desc' },
    { order: 'asc' },
    { updatedAt: 'desc' }
  ]
})

Search notes:

const notes = await prisma.note.findMany({
  where: {
    userId: session.user.id,
    OR: [
      { title: { contains: query, mode: 'insensitive' } },
      { content: { contains: query, mode: 'insensitive' } }
    ]
  }
})

Get user's labels:

const labels = await prisma.label.findMany({
  where: { userId: session.user.id },
  orderBy: { name: 'asc' }
})

Data Integrity

Cascade Deletes:

  • When User is deleted: delete their Accounts, Sessions, Notes, and Labels
  • Maintains referential integrity

Unique Constraints:

  • User.email
  • Account.provider + Account.providerAccountId
  • VerificationToken.identifier + VerificationToken.token
  • Label.name + Label.userId (per user)

Indexes:

  • All foreign keys indexed
  • Frequently queried fields indexed (isPinned, isArchived, order, reminder, userId)

Migration Notes

Current State:

  • SQLite database for local development
  • Better-sqlite3 adapter for embedded usage
  • Optional LibSQL adapter for cloud deployment (Turso)

Schema Evolution:

  • Label.userId made optional for migration (logic enforces ownership)
  • JSON fields stored as strings (parsed in application layer)
  • Future: Consider PostgreSQL for production with proper JSONB support

Database Size Considerations

SQLite Limits:

  • Max database size: 281 TB (theoretical)
  • Max row size: 1 GB
  • Max string/blob size: 1 GB

Practical Considerations:

  • Base64 images in Note.images could bloat database
  • Consider storing large files in filesystem and storing paths
  • Vector embeddings (Note.embedding) will grow with data

Recommendations:

  • Use CDN or object storage for images in production
  • Implement image compression before storage
  • Monitor database size with large note collections