# `createChatDbFixture` _(no submodules)_ ## Purpose Builds a real `chat.db`-shaped SQLite database on a temp path using the structural `SqliteReader` seam. This is the foundation of the test strategy: every watcher / adapter test runs against a real `bun:sqlite` database whose schema mirrors Apple's Messages app, populated via the helpers returned by this factory. There are no mocks anywhere in the call chain — the SQL the watcher executes in production runs unchanged against the fixture. ## Features - DDL covers the six tables the watcher reads: `message`, `handle`, `chat`, `chat_message_join`, `attachment`, `message_attachment_join` — with the exact column names + types Apple's chat.db uses. - Helpers return the inserted ROWID via a `SELECT ROWID ORDER BY ROWID DESC LIMIT 1` so tests can wire foreign-key joins deterministically without depending on `last_insert_rowid()` semantics across reader implementations. - `insertMessage(row, chatId)` writes both the message and its `chat_message_join` row so the watcher's `JOIN cmj` query finds the chat without extra wiring. - `insertAttachment(row, messageId)` writes both the attachment and its `message_attachment_join` row. - `close()` closes the handle so the temp file can be deleted by the caller's `afterEach`. ## Usage: Basic ```ts import { tmpdir } from "node:os"; import { join } from "node:path"; import { createBunSqliteReader, createChatDbFixture, } from "@suaveplan/imessage-adapter"; const reader = createBunSqliteReader(); const fixture = createChatDbFixture(reader, join(tmpdir(), "chat.db")); const chatId = fixture.insertChat({ guid: "iMessage;+;+15551234567", chat_identifier: "+15551234567", display_name: null, group_id: null, service_name: "iMessage", is_archived: 0, }); fixture.close(); ``` ## Usage: Advanced ```ts const handleId = fixture.insertHandle({ id: "+15551234567", country: "us", service: "iMessage", uncanonicalized_id: null, }); const messageId = fixture.insertMessage({ guid: "m-1", text: "Hello", handle_id: handleId, service: "iMessage", date: 700_000_000, date_read: 0, date_delivered: 0, is_from_me: 0, is_read: 1, is_delivered: 1, is_sent: 0, cache_has_attachments: 1, associated_message_guid: null, associated_message_type: 0, balloon_bundle_id: null, group_title: null, }, chatId); fixture.insertAttachment({ guid: "a-1", filename: "/tmp/photo.jpg", mime_type: "image/jpeg", total_bytes: 1024, transfer_name: "photo.jpg", }, messageId); ``` ## API Reference - `createChatDbFixture(reader, path): ChatDbFixture` — DDLs the schema and returns the helpers. - `ChatDbFixture.handle: SqliteHandle` — exposed for advanced custom inserts. - `insertHandle(row): number` — returns the new ROWID. - `insertChat(row): number` — returns the new ROWID. - `insertMessage(row, chatId?): number` — wires the `chat_message_join` row when `chatId` is supplied. - `insertAttachment(row, messageId): number` — wires the `message_attachment_join` row. - `close(): void` — closes the handle. ## Implementation Notes The factory is server-tier-only because it requires a real SQLite implementation, but it accepts any `SqliteReader` so a future in-process SQLite-WASM reader could also drive the fixture. The schema uses `AUTOINCREMENT` so insertion order maps to monotonically increasing ROWIDs, which the watcher's `ROWID > lastRowId` cursor relies on for incremental polling.