@cool-ai/beach-missives-mastra-adapter
v0.1.8
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
Limitation:
MastraMissiveStoredoes not supportdelete(). Mastra's storage API has no per-message deletion method; there is no correct implementation possible. If your application requires missive deletion, useInMemoryMissiveStore,JSONMissiveStore, orSQLiteMissiveStorefrom@cool-ai/beach-missivesinstead. See Deletion not supported for the full explanation.
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.
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 { LibSQLStore } from '@mastra/libsql';
import { MastraMissiveStore } from '@cool-ai/beach-missives-mastra-adapter';
const mastraStorage = new LibSQLStore({ 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',
triggerEventId: 'event-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 eventMissives = await missiveStore.listByEvent('event-456');MastraMissiveStore implements write, get, listBySession, and listByEvent.
Deletion not supported
MastraMissiveStore does not implement MissiveStore.delete(). This is not an oversight.
Mastra's storage API has no per-record deletion method. It provides deleteThread, which removes an entire thread. Because each missive is written to two threads (beach:session:<sessionId> and beach:event:<triggerEventId>), and delete(missiveId) receives only the missive ID, there is no way to remove a specific message from a thread using the Mastra API.
MissiveStore.delete is an optional method — Beach's interface models what storage backends can actually provide. Any code that calls store.delete() should guard for its absence:
if (missiveStore.delete !== undefined) {
await missiveStore.delete(id);
}If your application requires missive deletion, use InMemoryMissiveStore, JSONMissiveStore, or SQLiteMissiveStore from @cool-ai/beach-missives — all of which implement delete fully. If you need Mastra-backed persistence and deletion, file a request against the Mastra project for per-message deletion support.
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, or TLS configuration the deployment requires — and passes the constructed storage to new MastraMissiveStore(). Beach does not duplicate the connection; the consumer retains full operational control over it.
Storage layout
Each Missive is stored as one or two Mastra messages depending on whether it carries a triggerEventId:
| Mastra thread | When | Mastra message id |
|---|---|---|
| beach:session:<sessionId> | always | <missive.id> |
| beach:event:<triggerEventId> | only when missive.triggerEventId is set | <missive.id>::event |
The full Missive payload travels in the Mastra message's content as a JSON string. listBySession reads from the session thread; listByEvent reads from the event 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 triggerEventId across all threads".
beach: is a reserved namespace
The beach: thread-name prefix is reserved on any Mastra storage backing a MastraMissiveStore. Consumers must not write to keys / threads matching ^beach: in any Mastra store backing this adapter. Sharing a Mastra store between Beach and consumer-owned data is supported, but the consumer's data lives outside the reserved namespace.
The reservation lets operators inspect the underlying store directly (Postgres queries, Redis CLI) and identify Beach-owned data unambiguously. It also makes the prefix the contract that any future cross-store interoperability (a migration to a non-Mastra store, an out-of-band reader, a backup-and-restore) needs to honour. See documentation/decisions/missive-store-prefix-scheme.md for the full reservation contract and what it does — and does not — constrain.
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.
