@cool-ai/beach-missives-mastra-adapter
v0.1.0
Published
Mastra storage adapter for Beach's MissiveStore — wraps any @mastra/<store> package so its persistence backs the missive log.
Readme
@cool-ai/beach-missives-mastra-adapter
Mastra storage adapter for Beach's MissiveStore. Wraps any @mastra/<store> package — @mastra/pg, @mastra/libsql, @mastra/redis, @mastra/mongodb, @mastra/dynamodb, @mastra/clickhouse, @mastra/duckdb, @mastra/cloudflare, @mastra/upstash — so its persistence backs the missive log.
Home: cool-ai.org · Documentation: cool-ai.org/docs
Why this exists
Mastra ships nine maintained Apache-2.0 storage backends. A small Beach team cannot match that maintenance budget; per Beach's adopt-vs-build gate, Beach wraps once and lets consumers pick which backend to mount. This is week 2 of the locked Mastra-adapter quarter (the LLM adapter was week 1).
Use this adapter when you want the missive store backed by one of the Mastra storage packages — typically because your application is already using Mastra for other purposes, or because you want the operational profile (Postgres durability, Redis throughput, etc.) of a specific backend without writing a per-backend Beach package.
Use the reference InMemoryMissiveStore or JSONMissiveStore for development and tests. Use this adapter for production persistence.
Install
npm install @cool-ai/beach-missives-mastra-adapter @mastra/core @mastra/<store>@mastra/core is a peer dependency. Pick whichever @mastra/<store> package matches your durability needs and pin it directly. The consumer instantiates the storage and injects it — Beach's interior never imports @mastra/core.
Usage
import { MastraStorage } from '@mastra/libsql';
import { MastraMissiveStore } from '@cool-ai/beach-missives-mastra-adapter';
const mastraStorage = new MastraStorage({ url: 'file:./missives.db' });
const missiveStore = new MastraMissiveStore(mastraStorage);
// Use as the missive store anywhere @cool-ai/beach-missives expects one.
await missiveStore.write({
id: 'missive-abc',
sessionId: 'session-123',
turnId: 'turn-456',
channelId: 'email',
origin: { address: '[email protected]' },
parts: [{ partType: 'response', text: 'Reply text' }],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const sessionMissives = await missiveStore.listBySession('session-123');
const turnMissives = await missiveStore.listByTurn('turn-456');MastraMissiveStore implements the full MissiveStore contract (write, get, listBySession, listByTurn, delete).
Options
new MastraMissiveStore(mastraStorage, {
// Stamp every Mastra message with this resourceId. Mastra uses
// resourceId for memory-scoping (typically userId / tenantId);
// pass it for per-tenant isolation in the underlying store.
resourceId: 'tenant-42',
// Default limit applied to listBySession when the caller does not
// pass options.limit. Defaults to 100 (matching InMemoryMissiveStore).
defaultListLimit: 200,
});Connection-injection convention
Beach does not open the underlying database connection. The consumer constructs the Mastra storage instance — with whatever connection pool, retry policy, TLS configuration, or knex instance reuse the consumer's deployment requires — and passes the constructed storage to new MastraMissiveStore(). This is the standard pattern across the Mastra-adapter quarter and lets consumers retain operational control (e.g. Legal's existing knex MariaDB pool can be reused via @mastra/libsql's appropriate constructor; Beach does not duplicate the pool).
Storage layout
Each Missive is stored as one or two Mastra messages depending on whether it carries a turnId:
| Mastra thread | When | Mastra message id |
|---|---|---|
| beach:session:<sessionId> | always | <missive.id> |
| beach:turn:<turnId> | only when missive.turnId is set | <missive.id>::turn |
The full Missive payload travels in the Mastra message's content as a JSON string. listBySession reads from the session thread; listByTurn reads from the turn thread. Dual-write trades a small write amplification for clean read-time semantics on both correlation axes — Mastra's storage is thread-scoped and cannot natively answer "find every message with this turnId across all threads".
Architectural commitments
- Beach's interior never imports
@mastra/core.@cool-ai/beach-core,@cool-ai/beach-session,@cool-ai/beach-missives, and the rest of Beach's runtime stay Mastra-independent. The dependency lives at the adapter boundary. @mastra/coreis a peer dependency of@cool-ai/beach-missives-mastra-adapteronly — not of@cool-ai/beach-missives. Consumers using the in-memory or JSON reference stores never install Mastra.- No wrapping under
@mastra/core/eepaths. Adapter targets only Apache-2.0 surfaces.
Related
@cool-ai/beach-missives— the missive store interface this adapter implements.@cool-ai/beach-llm-mastra— the LLM-side Mastra adapter (week 1 of the Mastra-adapter quarter).- The Mastra-adapter quarter: this package is week 2 of the locked Option B ordering (LLM → missives → channel → durable → evals → voice).
