Sign inStart creating

Database

Schema: prisma/schema.prisma (~50 models, covering Part 8.3 in full).

Portability

Dev runs SQLite so the app boots with no infrastructure. Three concessions keep it Postgres-compatible, each isolated to one file:

ConcessionWhyPostgres migration
JSON in String columns (*Json)SQLite has no JSON scalar in this connectorChange the type to Json; all access is via server/lib/json.ts
Embeddings as JSON float arraysNo pgvectorUnsupported("vector(768)") + ivfflat; similarity moves into SQL. Access is via server/lib/vector.ts
Enums as StringSQLite connector has no enumsPromote to Postgres enums; allowed values live in src/lib/enums.ts

Client generation

This machine is Windows on ARM64, for which Prisma ships no native query engine — the legacy generator fails with "not a valid Win32 application".

The schema therefore uses:

generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"
  runtime      = "nodejs"
  moduleFormat = "cjs"
  engineType   = "client"     // WASM compiler + JS driver adapter, no Rust engine
}

with PrismaBetterSQLite3 in server/db.ts. This is also the better production posture: one fewer platform-specific binary. Moving to Postgres swaps the adapter for @prisma/adapter-pg and nothing else changes.

Path gotcha

The Prisma CLI resolves a relative file: URL against prisma/; a driver adapter resolves it against process.cwd(). Left alone, prisma db push and the app use two different database files — and the symptom is a confusing TableDoesNotExist. resolveSqliteUrl in server/db.ts normalizes to an absolute path so one DATABASE_URL works for both.

Model groups

GroupModels
IdentityUser, Session, Organization, OrgMembership, Subscription, CreditLedger
ProjectProject, ProjectBranch, Universe, BrandBible, LoreEntry
CharacterCharacter, CharacterVersion, CharacterRefAsset, CharacterState, Wardrobe, Voice, ConsentRecord
WorldWorld, Location, Prop
ScriptScript, ScriptVersion, ToneMap, Scene, DirectionNote, MusicCue, SoundNote
ShotShot, ShotCharacter, ShotProp, DialogueLine
StyleStylePack, StyleModifier
ModelsModelRegistryEntry, ModelBenchmark, Generation
AssetsAsset, AssetFolder, GraphEdge, CascadePlan
TimelineTimeline, TimelineTrack, TimelineClip, EditDecisionProposal
ContinuityContinuityFlag, Contradiction, FilmAudit, BudgetSnapshot
DirectorDirectorMessage
TheaterPublication, CastCard, ModerationCase, ReviewDecision, Report, Strike, WatchHistory, MyListEntry, Rating, Comment, Follow, FestivalEdition, FestivalEntry, FestivalVote
PublishingSocialConnection, SocialPost, Campaign

Notes on specific models

GraphEdge is the heart. Type-agnostic by design — adding a node type needs no migration. Indexed on (projectId, fromType, fromId) and (projectId, toType, toId) for both traversal directions, plus (projectId, stale) for the badge.

CreditLedger is append-only. Never update a row.

Generation.requestJson stores the full capability request, so any generation can be replayed exactly. provenanceJson stores what actually happened, including failovers.

CharacterVersion.validFromSceneOrder / validToSceneOrder implement canon pinning — a redesign in scene 30 cannot corrupt scene 4.

Publication.unlistedSlug exists because quality rejection is not removal.

Commands

npm run db:push      # sync schema (dev)
npm run db:seed      # styles, models, demo project, catalog
npm run db:reset     # force-reset + reseed
npm run db:studio    # Prisma Studio