跳到主要内容
非官方简体中文翻译,内容版权归 Mastra 所有,遵循 Apache-2.0 许可。

Storage

Storage APIs have been standardized with consistent pagination and naming patterns across all methods.

Database migration
Database migration的直接链接

Run these SQL migrations through your normal migration process (e.g., Prisma Migrate, Drizzle Kit, or your DBA review process).

Scorers table column rename
Scorers table column rename的直接链接

The runtimeContext column in mastra_scorers was renamed to requestContext.

Who needs this migration

Only if you use @mastra/pg or @mastra/libsql with evals/scoring and have existing data in the runtimeContext column.

What breaks without it

Existing score records won't have their request context data accessible.

After deploying v1 (which adds the new requestContext column on init), copy the data and drop the old column:

UPDATE mastra_scorers SET "requestContext" = "runtimeContext" WHERE "runtimeContext" IS NOT NULL;
ALTER TABLE mastra_scorers DROP COLUMN "runtimeContext";

Duplicate spans migration
Duplicate spans migration的直接链接

If you're upgrading from an older version of Mastra, you may have duplicate (traceId, spanId) entries in your mastra_spans table. V1 adds a unique constraint on these columns to ensure data integrity, but this constraint can't be added if duplicates exist.

Who needs this migration

Only if you have existing spans data from Mastra versions prior to v1 and encounter errors about duplicate key violations or constraint creation failures.

What breaks without it

The storage initialization may fail when trying to add the unique constraint, or you may see errors like "duplicate key value violates unique constraint".

Run the migration command which automatically deduplicates spans and adds the constraint:

npx mastra migrate

The CLI bundles your project and connects to your configured storage before running the migration. It keeps the most complete record (based on endTime and attributes) when removing duplicates.

Option 2: Manual SQL (PostgreSQL)
Option 2: Manual SQL (PostgreSQL)的直接链接

If you prefer to run the migration manually:

-- Remove duplicates, keeping the most complete record
DELETE FROM mastra_spans a USING mastra_spans b
WHERE a.ctid < b.ctid
AND a."traceId" = b."traceId"
AND a."spanId" = b."spanId";

-- Add the unique constraint
ALTER TABLE mastra_spans ADD CONSTRAINT mastra_spans_trace_span_unique UNIQUE ("traceId", "spanId");

Option 3: Manual migration (Other databases)
Option 3: Manual migration (Other databases)的直接链接

For ClickHouse, LibSQL, MongoDB, or MSSQL, use the programmatic API:

const storage = mastra.getStorage()
const observabilityStore = await storage.getStore('observability')

// Check if migration is needed
const status = await observabilityStore?.checkSpansMigrationStatus()
console.log(status)

// Run the migration
const result = await observabilityStore?.migrateSpans()
console.log(result)

JSON columns (TEXT → JSONB)
JSON columns (TEXT → JSONB)的直接链接

PostgreSQL only. The metadata column in mastra_threads and snapshot column in mastra_workflow_snapshot changed from TEXT to JSONB.

Recommended

Migrating to JSONB enables native PostgreSQL JSON operators and GIN indexing for better query performance on JSON fields.

ALTER TABLE mastra_threads
ALTER COLUMN metadata TYPE jsonb
USING metadata::jsonb;

ALTER TABLE mastra_workflow_snapshot
ALTER COLUMN snapshot TYPE jsonb
USING snapshot::jsonb;

Added
Added的直接链接

Storage composition in MastraCompositeStore
storage-composition-in-mastracompositestore的直接链接

MastraCompositeStore can now compose storage domains from different adapters. Use it when different purposes require different databases, such as PostgreSQL for memory and workflows with a specialized database for observability.

import { MastraCompositeStore } from '@mastra/core/storage'
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg'
import { MemoryLibSQL } from '@mastra/libsql'
import { Mastra } from '@mastra/core'

// Compose domains from different stores
const mastra = new Mastra({
storage: new MastraCompositeStore({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
}),
})

See the Storage Composition reference for more details.

Changed
Changed的直接链接

MastraStorage renamed to MastraCompositeStore
mastrastorage-renamed-to-mastracompositestore的直接链接

The MastraStorage class has been renamed to MastraCompositeStore to better reflect its role as a composite storage implementation that routes different domains to different underlying stores. This avoids confusion with the general "Mastra Storage" concept (the storage property on the Mastra instance).

The old MastraStorage name remains available as a deprecated alias for backward compatibility, but will be removed in a future version.

To migrate, update your imports and instantiation:

- import { MastraStorage } from "@mastra/core/storage";
+ import { MastraCompositeStore } from "@mastra/core/storage";
import { MemoryLibSQL } from "@mastra/libsql";
import { WorkflowsPG } from "@mastra/pg";

export const mastra = new Mastra({
- storage: new MastraStorage({
+ storage: new MastraCompositeStore({
id: "composite",
domains: {
memory: new MemoryLibSQL({ url: "file:./memory.db" }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
},
}),
});
备注

If you're using a single store implementation (like PostgresStore or LibSQLStore) directly, keep the existing configuration. This only affects code that explicitly uses MastraStorage for composite storage.

Required id property for storage instances
required-id-property-for-storage-instances的直接链接

Storage instances now require an id property. This unique identifier is used for tracking and managing storage instances within Mastra. The id should be a descriptive, unique string for each storage instance in your application.

To migrate, add an id field to your storage constructor.

const storage = new PostgresStore({
+ id: 'main-postgres-store',
connectionString: process.env.POSTGRES_CONNECTION_STRING,
schemaName: 'public',
});

const upstashStore = new UpstashStore({
+ id: 'upstash-cache-store',
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

Pagination from offset/limit to page/perPage
pagination-from-offsetlimit-to-pageperpage的直接链接

All pagination APIs now use page and perPage instead of offset and limit, matching page-based web pagination.

To migrate, update all pagination parameters from offset/limit to page/perPage. Note that page is 0-indexed.

memoryStore.listMessages({
threadId: 'thread-123',
- offset: 0,
- limit: 20,
+ page: 0,
+ perPage: 20,
});

getMessagesPaginated to listMessages
getmessagespaginated-to-listmessages的直接链接

The getMessagesPaginated() method has been replaced with listMessages(). The new method supports perPage: false to fetch all records without pagination. This change aligns with the list* naming convention and adds flexibility for fetching all records.

To migrate, rename the method and update pagination parameters. You can now use perPage: false to fetch all records.

+ const memoryStore = await storage.getStore('memory');
+
// Paginated
- const result = await storage.getMessagesPaginated({
+ const result = await memoryStore?.listMessages({
threadId: 'thread-123',
- offset: 0,
- limit: 20,
+ page: 0,
+ perPage: 20,
});

// Fetch all records (no pagination limit)
+ const allMessages = await memoryStore?.listMessages({
+ threadId: 'thread-123',
+ page: 0,
+ perPage: false,
+ });
Codemod

You can use Mastra's codemod CLI to update your code automatically:

npx @mastra/codemod@latest v1/storage-get-messages-paginated .

Domain-specific storage access via getStore()
domain-specific-storage-access-via-getstore的直接链接

Storage operations are now accessed through domain-specific stores instead of directly on the storage instance.

Domains include:

  • memory - Threads, messages, and resources
  • workflows - Workflow snapshots
  • scores - Evaluation scores
  • observability - Traces and spans
  • agents - Stored agent data

To migrate, call getStore() with the domain name, then call methods on the returned store.

const storage = mastra.getStorage();

// Memory operations (threads, messages, resources)
- const thread = await storage.getThread({ threadId: '123' });
- await storage.saveThread({ thread });
+ const memoryStore = await storage.getStore('memory');
+ const thread = await memoryStore?.getThreadById({ threadId: '123' });
+ await memoryStore?.saveThread({ thread });

// Workflow operations (snapshots)
- const snapshot = await storage.loadWorkflowSnapshot({ runId, workflowName });
- await storage.persistWorkflowSnapshot({ runId, workflowName, snapshot });
+ const workflowStore = await storage.getStore('workflows');
+ const snapshot = await workflowStore?.loadWorkflowSnapshot({ runId, workflowName });
+ await workflowStore?.persistWorkflowSnapshot({ runId, workflowName, snapshot });

// Observability operations (traces, spans)
- const traces = await storage.listTraces({ page: 0, perPage: 20 });
+ const observabilityStore = await storage.getStore('observability');
+ const traces = await observabilityStore?.listTraces({ page: 0, perPage: 20 });

// Score operations (evaluations)
- const scores = await storage.listScoresByScorerId({ scorerId: 'helpfulness' });
+ const scoresStore = await storage.getStore('scores');
+ const scores = await scoresStore?.listScoresByScorerId({ scorerId: 'helpfulness' });

getThreadsByResourceId to listThreads
getthreadsbyresourceid-to-listthreads的直接链接

The getThreadsByResourceId() method has been replaced with listThreads(). The new method adds pagination support and filtering by resourceId, metadata, or both.

important

The old getThreadsByResourceId() returned all matching threads without pagination. The new listThreads() requires pagination parameters. To preserve the old behavior of fetching all threads, use perPage: false.

To migrate, use the memory store and the new listThreads() method with pagination and an optional filter object.

- const threads = await storage.getThreadsByResourceId({
- resourceId: 'res-123',
- });
+ const memoryStore = await storage.getStore('memory');
+
+ // Paginated (recommended for large datasets)
+ const result = await memoryStore?.listThreads({
+ filter: { resourceId: 'res-123' },
+ page: 0,
+ perPage: 20,
+ });
+ const threads = result?.threads;
+
+ // Or fetch all threads like before (use perPage: false)
+ const allResult = await memoryStore?.listThreads({
+ filter: { resourceId: 'res-123' },
+ perPage: false,
+ });
+ const allThreads = allResult?.threads;

The new method also supports:

  • Listing all threads (omit filter)
  • Filtering by metadata only
  • Combined resourceId + metadata filters
// List all threads
await memoryStore?.listThreads({ page: 0, perPage: 20 })

// Filter by metadata only
await memoryStore?.listThreads({
filter: { metadata: { status: 'active' } },
page: 0,
perPage: 20,
})

// Combined filter
await memoryStore?.listThreads({
filter: {
resourceId: 'user-123',
metadata: { category: 'support' },
},
page: 0,
perPage: 20,
})
Codemod

You can use Mastra's codemod CLI to update your code automatically:

npx @mastra/codemod@latest v1/storage-list-threads-by-resource-to-list-threads .

getWorkflowRuns to listWorkflowRuns
getworkflowruns-to-listworkflowruns的直接链接

The getWorkflowRuns() method has been renamed to listWorkflowRuns(). This change aligns with the convention that list* methods return collections.

To migrate, use the workflows stores, rename the method call and update pagination parameters.

- const runs = await storage.getWorkflowRuns({
+ const workflowStore = await storage.getStore('workflows');
+ const runs = await workflowStore?.listWorkflowRuns({
fromDate,
toDate,
+ page: 0,
+ perPage: 20,
});
Codemod

You can use Mastra's codemod CLI to update your code automatically:

npx @mastra/codemod@latest v1/storage-list-workflow-runs .

getMessagesById to listMessagesById
getmessagesbyid-to-listmessagesbyid的直接链接

The getMessagesById() method has been renamed to listMessagesById(). This change aligns with the convention that list* methods return collections.

To migrate, use the memory store and rename the method call.

+ const memoryStore = await storage.getStore('memory');
- const result = await storage.getMessagesById({
+ const result = await memoryStore?.listMessagesById({
messageIds: ['msg-1', 'msg-2'],
});
Codemod

You can use Mastra's codemod CLI to update your code automatically:

npx @mastra/codemod@latest v1/storage-list-messages-by-id .

Storage getMessages and saveMessages signatures
storage-getmessages-and-savemessages-signatures的直接链接

The getMessages() and saveMessages() methods have changed signatures and return types. Format overloads have been removed, and the methods now always work with MastraDBMessage. This change simplifies the API by removing format variations.

To migrate, use the memory store, remove format parameters, and update code to work with the consistent return type.

+ const memoryStore = await storage.getStore('memory');
+
// Always returns { messages: MastraDBMessage[] }
- const v1Messages = await storage.getMessages({ threadId, format: 'v1' });
- const v2Messages = await storage.getMessages({ threadId, format: 'v2' });
+ const result = await memoryStore?.getMessages({ threadId });
+ const messages = result?.messages; // MastraDBMessage[]

// SaveMessages always uses MastraDBMessage
- await storage.saveMessages({ messages: v1Messages, format: 'v1' });
- await storage.saveMessages({ messages: v2Messages, format: 'v2' });
+ const saveResult = await memoryStore?.saveMessages({ messages: mastraDBMessages });
+ const saved = saveResult?.messages; // MastraDBMessage[]

Vector store API from positional arguments to arguments by name
Vector store API from positional arguments to arguments by name的直接链接

All vector store methods now use an arguments object instead of positional arguments. This makes each value's purpose visible at the call site and lets method signatures evolve without relying on argument order.

To migrate, update all vector store method calls to use an arguments object.

- await vectorDB.createIndex(indexName, 3, 'cosine');
+ await vectorDB.createIndex({
+ indexName: indexName,
+ dimension: 3,
+ metric: 'cosine',
+ });

- await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
+ await vectorDB.upsert({
+ indexName: indexName,
+ vectors: [[1, 2, 3]],
+ metadata: [{ test: 'data' }],
+ });

- await vectorDB.query(indexName, [1, 2, 3], 5);
+ await vectorDB.query({
+ indexName: indexName,
+ queryVector: [1, 2, 3],
+ topK: 5,
+ });

Vector store method renames
Vector store method renames的直接链接

The updateIndexById and deleteIndexById methods have been renamed to updateVector and deleteVector respectively. The new names identify that these methods operate on vectors.

To migrate, rename the methods and pass an arguments object.

- await vectorDB.updateIndexById(indexName, id, update);
- await vectorDB.deleteIndexById(indexName, id);
+ await vectorDB.updateVector({ indexName, id, update });
+ await vectorDB.deleteVector({ indexName, id });

PGVector constructor from connection string to object
PGVector constructor from connection string to object的直接链接

The PGVector constructor now requires object parameters instead of a connection string. Object parameters align the API across storage adapters.

To migrate, pass the connection string as an object property.

- const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!);
+ const pgVector = new PgVector({
+ connectionString: process.env.POSTGRES_CONNECTION_STRING,
+ });
Codemod

You can use Mastra's codemod CLI to update your code automatically:

npx @mastra/codemod@latest v1/vector-pg-constructor .

PGVector defineIndex to buildIndex
pgvector-defineindex-to-buildindex的直接链接

The defineIndex() method has been removed in favor of buildIndex(), which identifies that the method builds an index.

To migrate, rename the method and pass an arguments object.

- await vectorDB.defineIndex(indexName, 'cosine', { type: 'flat' });
+ await vectorDB.buildIndex({
+ indexName: indexName,
+ metric: 'cosine',
+ indexConfig: { type: 'flat' },
+ });

PostgresStore: schema to schemaName
postgresstore-schema-to-schemaname的直接链接

The schema parameter has been renamed to schemaName in the PostgresStore constructor. The new name identifies the value as the database schema's name.

To migrate, rename the parameter.

const pgStore = new PostgresStore({
connectionString: process.env.POSTGRES_CONNECTION_STRING,
- schema: customSchema,
+ schemaName: customSchema,
});
Codemod

You can use Mastra's codemod CLI to update your code automatically:

npx @mastra/codemod@latest v1/storage-postgres-schema-name .

Score storage methods to listScoresBy* pattern
score-storage-methods-to-listscoresby-pattern的直接链接

Score storage APIs have been renamed to follow the listScoresBy* pattern. The new names follow the broader API naming conventions.

To migrate, update method names from getScores to listScoresByScorerId and related variants.

- const scores = await storage.getScores({ scorerName: 'helpfulness-scorer' });
+ const scores = await storage.listScoresByScorerId({
+ scorerId: 'helpfulness-scorer',
+ });
+ // Also available: listScoresByRunId, listScoresByEntityId, listScoresBySpan

Removed
Removed的直接链接

Non-paginated storage functions
Non-paginated storage functions的直接链接

Non-paginated storage functions have been removed in favor of paginated versions. All list operations now use pagination, though you can fetch all records with perPage: false. Pagination aligns list operations across the API and prevents accidental loading of large datasets.

To migrate, use paginated methods via domain stores. For fetching all records, use perPage: false.

- // Non-paginated direct access
- const messages = await storage.getMessages({ threadId });

+ // Use paginated methods via domain stores
+ const memoryStore = await storage.getStore('memory');
+ const result = await memoryStore?.listMessages({ threadId, page: 0, perPage: 20 });
+ // Or fetch all
+ const allMessages = await memoryStore?.listMessages({
+ threadId,
+ page: 0,
+ perPage: false,
+ });

getTraces and getTracesPaginated
gettraces-and-gettracespaginated的直接链接

The getTraces() and getTracesPaginated() methods have been removed from storage. Use the observability package to access traces instead of core storage.

To migrate, use observability storage methods instead.

- const traces = await storage.getTraces({ traceId: 'trace-123' });
- const paginated = await storage.getTracesPaginated({ page: 0, perPage: 20 });

+ // Use observability API for traces
+ import { initObservability } from '@mastra/observability';
+ const observability = initObservability({ config: { ... } });
+ // Access traces through observability API

Evals test utilities
Evals test utilities的直接链接

Evals domain test utilities have been removed from @internal/test-utils. This change reflects the removal of legacy evals functionality.

To migrate, use storage APIs directly for testing instead of specialized evals test utilities.

- import { createEvalsTests } from '@internal/test-utils/domains/evals';
- createEvalsTests({ storage });

+ // Use storage APIs directly for testing

TABLE_EVALS from MSSQL storage
TABLE_EVALS from MSSQL storage的直接链接

The TABLE_EVALS table has been removed from MSSQL storage implementations. This change reflects the removal of legacy evals functionality.

If you were using MSSQL storage with evals, migrate to a different storage adapter or remove evals functionality.