import 'package:drift/drift.dart'; part 'chat_database.g.dart'; /// A 1:1 chat message, held locally so a conversation survives leaving the /// screen and going offline. /// /// This is deliberately NOT a [SyncColumns] inventory row: chat history is an /// ephemeral, per-device cache of what the relays delivered — not the user's /// CRDT-synced inventory. It never enters an inventory snapshot, backup, or the /// device-to-device sync, so it needs no HLC / tombstone / author metadata. It /// lives in its own encrypted database ([ChatDatabase]) to keep it fully /// isolated from the inventory schema, its migrations, and its sync stream. /// /// [accountScope] namespaces rows per social identity (empty = the original /// identity), replacing the old per-key keystore prefix. The unique key makes /// de-duplication a DB invariant: a relay re-delivers the same gift wrap on /// every (re)subscribe, and an identical (scope, peer, sender, timestamp, text) /// row is simply not inserted twice. @TableIndex( name: 'messages_by_conversation', columns: {#accountScope, #peerPubkey, #sentAt}, ) class Messages extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get accountScope => text()(); TextColumn get peerPubkey => text()(); TextColumn get fromPubkey => text()(); TextColumn get body => text()(); /// Milliseconds since epoch — the message's own timestamp (NIP-17), used for /// ordering and for the read/unread mark. IntColumn get sentAt => integer()(); @override List> get uniqueKeys => [ {accountScope, peerPubkey, fromPubkey, sentAt, body}, ]; } /// The encrypted local chat cache (SQLCipher via an injected executor, exactly /// like [AppDatabase]). Separate database on purpose — see [Messages]. There is /// no historical schema to migrate: version 1, created fresh. @DriftDatabase(tables: [Messages]) class ChatDatabase extends _$ChatDatabase { ChatDatabase(super.e); @override int get schemaVersion => 1; @override MigrationStrategy get migration => MigrationStrategy(onCreate: (m) async => m.createAll()); }