Initial Commit
This commit is contained in:
120
packages/communication/messaging/imessage-adapter/README.md
Normal file
120
packages/communication/messaging/imessage-adapter/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# @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.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [adapter](./src/adapter/imessage-adapter.md) — `MessageSourceAdapter`-shaped wrapper around `IMessageWatcher` that projects each `ParsedIMessage` into the channel-agn…
|
||||
- [imessage-adapter](./src/adapter/imessage-adapter.md) — `MessageSourceAdapter`-shaped wrapper around `IMessageWatcher` that projects each `ParsedIMessage` into the channel-agn…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable, fully-qualified error-code registry for `@suaveplan/imessage-adapter`. Every error class in this mo…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable, fully-qualified error-code registry for `@suaveplan/imessage-adapter`. Every error class in this mo…
|
||||
- [fixtures](./src/fixtures/chat-db-fixture.md) — Builds a real `chat.db`-shaped SQLite database on a temp path using the structural `SqliteReader` seam. This is the fou…
|
||||
- [chat-db-fixture](./src/fixtures/chat-db-fixture.md) — Builds a real `chat.db`-shaped SQLite database on a temp path using the structural `SqliteReader` seam. This is the fou…
|
||||
- [otel](./src/otel/metrics.md) — Defines the four OpenTelemetry instruments emitted by `@suaveplan/imessage-adapter` and the helpers that record onto th…
|
||||
- [metrics](./src/otel/metrics.md) — Defines the four OpenTelemetry instruments emitted by `@suaveplan/imessage-adapter` and the helpers that record onto th…
|
||||
- [parser](./src/parser/imessage-parser.md) — Pure-function parser that turns Apple's chat.db row shapes into the `ParsedIMessage` projection consumed by every downs…
|
||||
- [imessage-parser](./src/parser/imessage-parser.md) — Pure-function parser that turns Apple's chat.db row shapes into the `ParsedIMessage` projection consumed by every downs…
|
||||
- [schemas](./src/schemas/schemas.md) — Provides Zod boundary schemas for `IMessageWatcherConfig` and `IMessageAdapterConfig`, plus the `parseWatcherConfig` /…
|
||||
- [schemas](./src/schemas/schemas.md) — Provides Zod boundary schemas for `IMessageWatcherConfig` and `IMessageAdapterConfig`, plus the `parseWatcherConfig` /…
|
||||
- [sqlite](./src/sqlite/bun-sqlite-reader.md) — Production-grade `SqliteReader` backed by Bun's built-in `bun:sqlite` driver. The reader is the single seam through whi…
|
||||
- [bun-sqlite-reader](./src/sqlite/bun-sqlite-reader.md) — Production-grade `SqliteReader` backed by Bun's built-in `bun:sqlite` driver. The reader is the single seam through whi…
|
||||
- [watcher](./src/watcher/imessage-watcher.md) — Polls Apple's `~/Library/Messages/chat.db` SQLite database on a configurable interval, projects new rows into `ParsedIM…
|
||||
- [imessage-watcher](./src/watcher/imessage-watcher.md) — Polls Apple's `~/Library/Messages/chat.db` SQLite database on a configurable interval, projects new rows into `ParsedIM…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/imessage-adapter
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user