Files
skills/docs/communication/messaging/imessage-adapter
2026-05-29 08:20:56 +01:00
..
2026-05-29 08:20:56 +01:00
2026-05-29 08:20:56 +01:00

@suaveplan/imessage-adapter - macOS iMessage SQLite chat.db watcher. Polls Apple's Messages database, decodes Apple-epoch / nanosecond timestamps, resolves handle + chat + attachment joins, and emits ParsedIMessage + InboundMessage envelopes. Structured IMESSAGE.* error registry, Zod-validated config boundary, AbortSignal cancellation, injectable SqliteReader seam (production: bun:sqlite, tests: real bun:sqlite chat.db fixture — no mocks), EventBus bridge atop @suaveplan/events, four OTel instruments.

macOS iMessage SQLite chat.db watcher and MessageSourceAdapter implementation. Polls Apple's ~/Library/Messages/chat.db, decodes Apple-epoch / nanosecond timestamps, resolves handle + chat + attachment joins, and emits ParsedIMessage and InboundMessage envelopes. Server-tier (Bun); the bun:sqlite driver is loaded lazily inside the production reader so the package type-checks + lints on every host.

Documentation tree

  • adapter — MessageSourceAdapter-shaped wrapper around IMessageWatcher that projects each ParsedIMessage into the channel-agn…
    • imessage-adapter — MessageSourceAdapter-shaped wrapper around IMessageWatcher that projects each ParsedIMessage into the channel-agn…
  • error-codes — Defines the stable, fully-qualified error-code registry for @suaveplan/imessage-adapter. Every error class in this mo…
    • error-codes — Defines the stable, fully-qualified error-code registry for @suaveplan/imessage-adapter. Every error class in this mo…
  • fixtures — Builds a real chat.db-shaped SQLite database on a temp path using the structural SqliteReader seam. This is the fou…
    • chat-db-fixture — Builds a real chat.db-shaped SQLite database on a temp path using the structural SqliteReader seam. This is the fou…
  • otel — Defines the four OpenTelemetry instruments emitted by @suaveplan/imessage-adapter and the helpers that record onto th…
    • metrics — Defines the four OpenTelemetry instruments emitted by @suaveplan/imessage-adapter and the helpers that record onto th…
  • parser — Pure-function parser that turns Apple's chat.db row shapes into the ParsedIMessage projection consumed by every downs…
    • imessage-parser — Pure-function parser that turns Apple's chat.db row shapes into the ParsedIMessage projection consumed by every downs…
  • schemas — Provides Zod boundary schemas for IMessageWatcherConfig and IMessageAdapterConfig, plus the parseWatcherConfig /…
    • schemas — Provides Zod boundary schemas for IMessageWatcherConfig and IMessageAdapterConfig, plus the parseWatcherConfig /…
  • sqlite — Production-grade SqliteReader backed by Bun's built-in bun:sqlite driver. The reader is the single seam through whi…
    • bun-sqlite-reader — Production-grade SqliteReader backed by Bun's built-in bun:sqlite driver. The reader is the single seam through whi…
  • watcher — Polls Apple's ~/Library/Messages/chat.db SQLite database on a configurable interval, projects new rows into `ParsedIM…
    • imessage-watcher — Polls Apple's ~/Library/Messages/chat.db SQLite database on a configurable interval, projects new rows into `ParsedIM…

Highlights

  • IMessageWatcher — Zod-validated config boundary, AbortSignal propagation, injectable SqliteReader, typed EventBus surface alongside the upstream on/off listener API, monotonic lastRowId cursor (settable + readable for resume), filters for includeFromMe and chatFilter.
  • IMessageAdapter — wraps the watcher and projects each ParsedIMessage into a channel-agnostic InboundMessage with normalised phone numbers and chat metadata.
  • IMessageParser — pure-function parser for chat.db row shapes; Apple-epoch decoding, attachment fallbacks, phone normalisation.
  • createChatDbFixture — real chat.db-shaped SQLite database builder used by tests so every test path runs against the same SQL the production watcher executes — no mocks.
  • bunSqliteReader / createBunSqliteReader — production SqliteReader backed by Bun's bun:sqlite. Errors are wrapped in IMessageDbOpenFailedError.

Install

bun add @suaveplan/imessage-adapter

Usage

import { IMessageAdapter } from "@suaveplan/imessage-adapter";

const adapter = new IMessageAdapter();
await adapter.initialize({
    dbPath: "~/Library/Messages/chat.db",
    pollIntervalMs: 5000,
    includeFromMe: false,
});

for (const inbound of await adapter.poll()) {
    console.log(inbound.sourceAddress, inbound.content);
    adapter.markProcessed(inbound.id);
}

await adapter.close();

Error registry (IMESSAGE.*)

Code Class When
IMESSAGE.DB_OPEN_FAILED IMessageDbOpenFailedError bun:sqlite cannot open the chat.db path
IMESSAGE.DB_QUERY_FAILED IMessageDbQueryFailedError A poll-time SQL query throws
IMESSAGE.WATCHER_NOT_RUNNING IMessageWatcherNotRunningError stop() called while not running
IMESSAGE.WATCHER_ALREADY_RUNNING IMessageWatcherAlreadyRunningError start() called twice
IMESSAGE.ADAPTER_NOT_INITIALIZED IMessageAdapterNotInitializedError poll() before initialize()
IMESSAGE.INVALID_CONFIG IMessageInvalidConfigError Zod boundary rejected config; context.issues carries the path + reason
IMESSAGE.POLL_ABORTED IMessagePollAbortedError AbortSignal fired
IMESSAGE.POLL_FAILED IMessagePollFailedError Reserved for explicit poll() wrapping
IMESSAGE.ATTACHMENT_PARSE_FAILED IMessageAttachmentParseError Attachment row could not be parsed
IMESSAGE.APPLE_DATE_PARSE_FAILED IMessageAppleDateParseError Timestamp is NaN / non-finite

OpenTelemetry instruments

Instrument Kind Attributes
imessage.messages_emitted_total Counter chat_type, from_me
imessage.attachments_emitted_total Counter mime_family
imessage.poll_errors_total Counter code
imessage.poll_duration_ms Histogram outcome (ok, error, aborted)

Call refreshIMessageMeters() after metrics.setGlobalMeterProvider to bind subsequent records to the chosen provider.

Subpath exports

  • @suaveplan/imessage-adapter/error-codes
  • @suaveplan/imessage-adapter/schemas
  • @suaveplan/imessage-adapter/parser
  • @suaveplan/imessage-adapter/watcher
  • @suaveplan/imessage-adapter/adapter
  • @suaveplan/imessage-adapter/sqlite/bun-sqlite-reader
  • @suaveplan/imessage-adapter/otel
  • @suaveplan/imessage-adapter/fixtures

Tier

Server-tier (Bun on macOS). The bun:sqlite import is reached via createRequire(import.meta.url) so the static module graph never attempts to resolve the specifier on a non-Bun runner.