My App
LoopKitBackend

Prisma Adapter

Use Prisma as the storage backend for LoopKit

Setup

import { PrismaLoopKitAdapter } from '@loopkit/nestjs';

const adapter = new PrismaLoopKitAdapter(prismaClient);

The adapter expects a Prisma client with the following models:

Prisma ModelEntity
loopKitCardCards
loopKitNoteNotes
loopKitNoteTypeNote Types
loopKitDeckDecks
loopKitDeckPresetDeck Presets
loopKitReviewLogReview Logs

Required Prisma Schema

Add these models to your schema.prisma:

model LoopKitCard {
    id          String   @id @default(uuid())
    noteId      String
    templateId  String
    deckId      String
    state       String   @default("new")
    easeFactor  Float    @default(2.5)
    interval    Float    @default(0)
    dueDate     DateTime @default(now())
    currentStep Int      @default(0)
    lapseCount  Int      @default(0)
    reviewCount Int      @default(0)
    createdAt   DateTime @default(now())
    updatedAt   DateTime @updatedAt

    note LoopKitNote @relation(fields: [noteId], references: [id], onDelete: Cascade)
    deck LoopKitDeck @relation(fields: [deckId], references: [id])

    @@unique([noteId, templateId])
    @@index([deckId, state])
    @@index([deckId, dueDate])
}

model LoopKitNote {
    id         String   @id @default(uuid())
    noteTypeId String
    fields     Json
    tags       Json     @default("[]")
    createdAt  DateTime @default(now())
    updatedAt  DateTime @updatedAt

    noteType LoopKitNoteType @relation(fields: [noteTypeId], references: [id])
    cards    LoopKitCard[]
}

model LoopKitNoteType {
    id        String   @id @default(uuid())
    name      String   @unique
    fields    Json
    templates Json
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt

    notes LoopKitNote[]
}

model LoopKitDeck {
    id              String   @id @default(uuid())
    name            String
    description     String?
    parentDeckId    String?
    presetId        String?
    configOverrides Json?
    createdAt       DateTime @default(now())
    updatedAt       DateTime @updatedAt

    parent   LoopKitDeck?       @relation("DeckHierarchy", fields: [parentDeckId], references: [id])
    children LoopKitDeck[]      @relation("DeckHierarchy")
    preset   LoopKitDeckPreset? @relation(fields: [presetId], references: [id])
    cards    LoopKitCard[]
    logs     LoopKitReviewLog[]
}

model LoopKitDeckPreset {
    id        String   @id @default(uuid())
    name      String
    config    Json
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt

    decks LoopKitDeck[]
}

model LoopKitReviewLog {
    id          String   @id @default(uuid())
    cardId      String
    deckId      String
    grade       String
    prevState   Json
    newState    Json
    reviewedAt  DateTime @default(now())
    timeTakenMs Int

    deck LoopKitDeck @relation(fields: [deckId], references: [id])

    @@index([cardId])
    @@index([deckId, reviewedAt])
}

Then run prisma migrate dev to apply the schema.

Error Mapping

The adapter maps Prisma errors to LoopKit errors:

Prisma CodeLoopKit ErrorDescription
P2002DuplicateEntityErrorUnique constraint violation
P2025EntityNotFoundErrorRecord not found
P2034ConcurrencyConflictErrorTransaction conflict
Connection errorsStorageConnectionErrorDatabase unreachable

JSON Fields

The adapter automatically serializes/deserializes JSON fields:

  • fields, tags on Notes
  • fields, templates on NoteTypes
  • configOverrides on Decks
  • config on Presets
  • prevState, newState on ReviewLogs

Transaction Support

await adapter.transaction(async (txStorage) => {
    const note = await txStorage.createNote({ ... });
    await txStorage.createCard({ noteId: note.id, ... });
});

The adapter creates a new PrismaLoopKitAdapter wrapping the Prisma transaction client.

On this page