@mastra/pg 1.15.1 → 1.16.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/docs/SKILL.md +2 -2
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +36 -2
- package/dist/docs/references/docs-memory-working-memory.md +3 -3
- package/dist/docs/references/docs-rag-vector-databases.md +7 -2
- package/dist/docs/references/docs-storage-overview.md +214 -0
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/docs/references/reference-storage-composite.md +64 -6
- package/dist/docs/references/reference-storage-dynamodb.md +1 -1
- package/dist/docs/references/reference-storage-retention.md +74 -6
- package/dist/index.cjs +97 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +98 -65
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/datasets/index.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/docs/references/docs-memory-storage.md +0 -267
|
@@ -6,7 +6,7 @@ Storage grows without bound by default. Retention is an opt-in, age-based cleanu
|
|
|
6
6
|
|
|
7
7
|
`prune()` deletes rows. It caps growth and is safe to run against large tables (batched, bounded, resumable, cancellable). It never reclaims disk — on SQLite/libSQL the freed pages are reused by future writes so the file stops growing, but handing disk back to the OS (for example a `VACUUM`) is left to the underlying database and the operator to manage.
|
|
8
8
|
|
|
9
|
-
Retention covers **growth tables** only — tables that accumulate rows unbounded as a side effect of normal operation (conversation history, telemetry, job and run records, schedule fire history, event feeds). User-authored artifacts and config (agents, skills, workspaces, prompt blocks, datasets, schedule definitions, channel installations, and so on) grow with user intent and are edited or deleted explicitly, so they
|
|
9
|
+
Retention covers **growth tables** only — tables that accumulate rows unbounded as a side effect of normal operation (conversation history, telemetry, job and run records, schedule fire history, event feeds). User-authored artifacts and config (agents, skills, workspaces, prompt blocks, datasets, schedule definitions, channel installations, and so on) grow with user intent and are edited or deleted explicitly, so they're not valid retention keys.
|
|
10
10
|
|
|
11
11
|
The reference implementations are [libSQL](https://mastra.ai/reference/storage/libsql), [PostgreSQL](https://mastra.ai/reference/storage/postgresql), and [MongoDB](https://mastra.ai/reference/storage/mongodb). Other adapters keep rows forever until they implement retention.
|
|
12
12
|
|
|
@@ -89,8 +89,8 @@ Each domain declares which of its tables can be age-pruned and which timestamp c
|
|
|
89
89
|
> **Note:**
|
|
90
90
|
>
|
|
91
91
|
> - The memory `observational_memory` table has no timestamp anchor, so it can't be age-pruned and isn't a valid retention key.
|
|
92
|
-
> - Experiments prune as whole units: an aged experiment's result rows are deleted together with it (results cascade with their parent), so a run is never left partially deleted.
|
|
93
|
-
> - For `schedules`, the growth table is the fire history (`schedule_triggers`, one row per fire) — schedule definitions are config and
|
|
92
|
+
> - Experiments prune as whole units: an aged experiment's result rows are deleted together with it (results cascade with their parent), so a run is never left partially deleted. Retention doesn't have a separate `results` key.
|
|
93
|
+
> - For `schedules`, the growth table is the fire history (`schedule_triggers`, one row per fire) — schedule definitions are config and aren't pruned.
|
|
94
94
|
> - On PostgreSQL, timestamp anchors use the timezone-aware mirror columns (for example `createdAtZ`, `completedAtZ`).
|
|
95
95
|
> - LibSQL supports all domains above; PostgreSQL and MongoDB support all except `threadState` and `harness`, which they don't implement.
|
|
96
96
|
> - The v-next PostgreSQL observability domain stores signal events in day-partitioned tables (`spans`, `metrics`, `logs`, `scores`, `feedback`). For it, `prune()` drops whole day partitions (or TimescaleDB chunks) that are entirely older than the cutoff instead of deleting rows — effective granularity is one day, and a partition is only dropped once its entire day is past `maxAge`. `PruneResult.deleted` reports the number of rows in the dropped partitions.
|
|
@@ -105,6 +105,8 @@ Deletes rows older than their configured `maxAge` across every domain that has a
|
|
|
105
105
|
|
|
106
106
|
`prune()` is designed to be safe on tables with millions of rows. It deletes in bounded, batched chunks — each batch is its own transaction — so it never takes a long lock or bloats the transaction log. It never runs a `VACUUM`.
|
|
107
107
|
|
|
108
|
+
Pass `options.retention` to replace the configured policies for that call only — for example to skip a domain (keep chat history) or prune more aggressively than the standing config. The store's configured `retention` is unchanged.
|
|
109
|
+
|
|
108
110
|
Anchor-column indexes are created lazily on the first `prune()` call for each table with a policy — never at `init()` — so deployments that don't configure retention pay no extra index write or disk overhead. The first prune of an existing large table pays a one-time index build; subsequent prunes reuse the index.
|
|
109
111
|
|
|
110
112
|
```typescript
|
|
@@ -116,6 +118,13 @@ const results = await storage.prune({
|
|
|
116
118
|
for (const r of results) {
|
|
117
119
|
console.log(`${r.domain}.${r.table}: deleted ${r.deleted}, done=${r.done}`)
|
|
118
120
|
}
|
|
121
|
+
|
|
122
|
+
// One-off pass with different policies (configured retention untouched):
|
|
123
|
+
await storage.prune({
|
|
124
|
+
retention: {
|
|
125
|
+
observability: { spans: { maxAge: '1d' } },
|
|
126
|
+
},
|
|
127
|
+
})
|
|
119
128
|
```
|
|
120
129
|
|
|
121
130
|
Returns: `Promise<PruneResult[]>`
|
|
@@ -130,6 +139,8 @@ Returns: `Promise<PruneResult[]>`
|
|
|
130
139
|
|
|
131
140
|
**signal** (`AbortSignal`): Cooperative cancellation. The batch loop checks it between batches and stops cleanly, returning partial results with done: false.
|
|
132
141
|
|
|
142
|
+
**retention** (`RetentionConfig`): Replaces the store's configured retention policies for this call only — e.g. to skip a domain or prune more aggressively. The configured retention is unchanged.
|
|
143
|
+
|
|
133
144
|
##### PruneResult
|
|
134
145
|
|
|
135
146
|
Each result describes one table's progress:
|
|
@@ -145,7 +156,7 @@ interface PruneResult {
|
|
|
145
156
|
|
|
146
157
|
## Running prune on a schedule
|
|
147
158
|
|
|
148
|
-
`prune()` has no built-in scheduler — you decide when it runs. Because it
|
|
159
|
+
`prune()` has no built-in scheduler — you decide when it runs. Because it's bounded, a single call may not delete everything. When any result has `done: false`, eligible rows remain and you call again on the next tick. This keeps each invocation short and lets a large backlog drain over several runs.
|
|
149
160
|
|
|
150
161
|
```typescript
|
|
151
162
|
// Runs on your own cron (node-cron, a workflow schedule, an external job, etc.).
|
|
@@ -164,11 +175,68 @@ async function retentionTick() {
|
|
|
164
175
|
|
|
165
176
|
You can also cancel a long-running prune with an `AbortSignal` — the loop stops between batches and returns partial results with `done: false`, so the next run resumes cleanly.
|
|
166
177
|
|
|
178
|
+
## MongoDB TTL indexes (alternative to prune)
|
|
179
|
+
|
|
180
|
+
MongoDB offers native [TTL (Time-To-Live) indexes](https://www.mongodb.com/docs/manual/core/index-ttl/) that automatically delete expired documents without requiring manual `prune()` calls. This is a database-level feature that runs as a background thread.
|
|
181
|
+
|
|
182
|
+
> **When to use TTL vs prune():** **Use MongoDB TTL indexes when:**
|
|
183
|
+
>
|
|
184
|
+
> - You want automated, zero-maintenance deletion
|
|
185
|
+
> - Your retention periods are fixed (e.g., "always 30 days")
|
|
186
|
+
> - You prefer database-native solutions
|
|
187
|
+
>
|
|
188
|
+
> **Use `prune()` when:**
|
|
189
|
+
>
|
|
190
|
+
> - You need fine-grained control over deletion timing
|
|
191
|
+
> - You want to cap deletion rate during business hours
|
|
192
|
+
> - You need resumable, cancellable cleanup operations
|
|
193
|
+
> - You're using composite storage with multiple databases
|
|
194
|
+
>
|
|
195
|
+
> Both approaches are valid. TTL is simpler; `prune()` gives more control.
|
|
196
|
+
|
|
197
|
+
### Setting up TTL indexes on MongoDB
|
|
198
|
+
|
|
199
|
+
TTL indexes work on date fields. MongoDB checks the index every 60 seconds and deletes documents where the date field + TTL duration < current time.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { MongoDBStore } from '@mastra/mongodb'
|
|
203
|
+
|
|
204
|
+
const storage = new MongoDBStore({
|
|
205
|
+
id: 'mongodb-storage',
|
|
206
|
+
uri: process.env.MONGODB_URI!,
|
|
207
|
+
dbName: process.env.MONGODB_DB_NAME!,
|
|
208
|
+
indexes: [
|
|
209
|
+
// Messages expire after 30 days
|
|
210
|
+
{
|
|
211
|
+
collection: 'mastra_messages',
|
|
212
|
+
keys: { createdAt: 1 },
|
|
213
|
+
options: { expireAfterSeconds: 30 * 24 * 60 * 60 }, // 30 days
|
|
214
|
+
},
|
|
215
|
+
// Threads expire after 90 days
|
|
216
|
+
{
|
|
217
|
+
collection: 'mastra_threads',
|
|
218
|
+
keys: { createdAt: 1 },
|
|
219
|
+
options: { expireAfterSeconds: 90 * 24 * 60 * 60 }, // 90 days
|
|
220
|
+
},
|
|
221
|
+
// Spans expire after 7 days
|
|
222
|
+
{
|
|
223
|
+
collection: 'mastra_ai_spans',
|
|
224
|
+
keys: { startedAt: 1 },
|
|
225
|
+
options: { expireAfterSeconds: 7 * 24 * 60 * 60 }, // 7 days
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
> **Tip:** TTL indexes delete documents shortly after they expire (background thread runs every \~60 seconds), but the exact timing is not guaranteed. For precise, immediate cleanup, use `prune()` instead.
|
|
232
|
+
|
|
167
233
|
## Reclaiming disk
|
|
168
234
|
|
|
169
|
-
`prune()` deletes rows but
|
|
235
|
+
`prune()` deletes rows but doesn't shrink the database file. On SQLite/libSQL the freed pages go on a freelist and are reused by future writes, so the file stops growing — for most users this alone solves the unbounded-growth problem.
|
|
236
|
+
|
|
237
|
+
Handing that free space back to the OS is a separate concern that Mastra doesn't manage. If you specifically need to shrink the file, run the underlying database's compaction (for example `VACUUM` on self-hosted libSQL) yourself, in a maintenance window — a full `VACUUM` locks the file and needs roughly twice the file size in free disk. On PostgreSQL, autovacuum reclaims dead tuples for reuse automatically; a manual `VACUUM FULL` is only needed if you must return disk to the OS.
|
|
170
238
|
|
|
171
|
-
|
|
239
|
+
For MongoDB, deleted documents are reused by future insertions. To reclaim disk space, run [`db.runCommand({ compact: "collection_name" })`](https://www.mongodb.com/docs/manual/reference/command/compact/) during a maintenance window.
|
|
172
240
|
|
|
173
241
|
> **LibSQL and Turso:** [Turso Cloud](https://mastra.ai/reference/storage/libsql) manages storage compaction for you, so there's nothing to reclaim manually. This applies only to self-hosted libSQL files.
|
|
174
242
|
|
package/dist/index.cjs
CHANGED
|
@@ -5452,6 +5452,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5452
5452
|
await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "organizationId", "TEXT");
|
|
5453
5453
|
await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "projectId", "TEXT");
|
|
5454
5454
|
await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "toolMocks", "JSONB");
|
|
5455
|
+
await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "externalId", "TEXT");
|
|
5455
5456
|
await this.createDefaultIndexes();
|
|
5456
5457
|
await this.createCustomIndexes();
|
|
5457
5458
|
}
|
|
@@ -5470,6 +5471,11 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5470
5471
|
table: storage.TABLE_DATASET_ITEMS,
|
|
5471
5472
|
columns: ["datasetId", "datasetVersion"]
|
|
5472
5473
|
},
|
|
5474
|
+
{
|
|
5475
|
+
name: "idx_dataset_items_external_id_history",
|
|
5476
|
+
table: storage.TABLE_DATASET_ITEMS,
|
|
5477
|
+
columns: ["datasetId", "externalId", "datasetVersion"]
|
|
5478
|
+
},
|
|
5473
5479
|
{
|
|
5474
5480
|
name: "idx_dataset_items_dataset_validto_deleted",
|
|
5475
5481
|
table: storage.TABLE_DATASET_ITEMS,
|
|
@@ -5552,6 +5558,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5552
5558
|
id: row.id,
|
|
5553
5559
|
datasetId: row.datasetId,
|
|
5554
5560
|
datasetVersion: row.datasetVersion,
|
|
5561
|
+
externalId: row.externalId ?? null,
|
|
5555
5562
|
organizationId: row.organizationId ?? null,
|
|
5556
5563
|
projectId: row.projectId ?? null,
|
|
5557
5564
|
input: storage.safelyParseJSON(row.input),
|
|
@@ -5570,6 +5577,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5570
5577
|
id: row.id,
|
|
5571
5578
|
datasetId: row.datasetId,
|
|
5572
5579
|
datasetVersion: row.datasetVersion,
|
|
5580
|
+
externalId: row.externalId ?? null,
|
|
5573
5581
|
organizationId: row.organizationId ?? null,
|
|
5574
5582
|
projectId: row.projectId ?? null,
|
|
5575
5583
|
validTo: row.validTo,
|
|
@@ -5596,7 +5604,8 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5596
5604
|
// --- Dataset CRUD ---
|
|
5597
5605
|
async createDataset(input) {
|
|
5598
5606
|
try {
|
|
5599
|
-
const id = crypto.randomUUID();
|
|
5607
|
+
const id = input.id ?? crypto.randomUUID();
|
|
5608
|
+
if (input.id !== void 0) this.validateCallerDefinedDatasetId(input.id);
|
|
5600
5609
|
const now = /* @__PURE__ */ new Date();
|
|
5601
5610
|
const nowIso = now.toISOString();
|
|
5602
5611
|
await this.#db.insert({
|
|
@@ -5641,6 +5650,11 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5641
5650
|
updatedAt: now
|
|
5642
5651
|
};
|
|
5643
5652
|
} catch (error$1) {
|
|
5653
|
+
if (input.id !== void 0 && storage.hasErrorCode(error$1, /* @__PURE__ */ new Set(["23505"]))) {
|
|
5654
|
+
const existing = await this.getDatasetById({ id: input.id });
|
|
5655
|
+
if (existing) return this.resolveExistingDataset(existing, { ...input, id: input.id });
|
|
5656
|
+
}
|
|
5657
|
+
if (error$1 instanceof error.MastraError) throw error$1;
|
|
5644
5658
|
throw new error.MastraError(
|
|
5645
5659
|
{
|
|
5646
5660
|
id: storage.createStorageErrorId("PG", "CREATE_DATASET", "FAILED"),
|
|
@@ -5913,11 +5927,12 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5913
5927
|
parentOrganizationId = row.organizationId ?? null;
|
|
5914
5928
|
parentProjectId = row.projectId ?? null;
|
|
5915
5929
|
await t.none(
|
|
5916
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,NULL,false,$
|
|
5930
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","externalId","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,$6,NULL,false,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)`,
|
|
5917
5931
|
[
|
|
5918
5932
|
id,
|
|
5919
5933
|
args.datasetId,
|
|
5920
5934
|
newVersion,
|
|
5935
|
+
args.externalId ?? null,
|
|
5921
5936
|
parentOrganizationId,
|
|
5922
5937
|
parentProjectId,
|
|
5923
5938
|
JSON.stringify(args.input),
|
|
@@ -6017,11 +6032,12 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
6017
6032
|
[newVersion, args.id]
|
|
6018
6033
|
);
|
|
6019
6034
|
await t.none(
|
|
6020
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,NULL,false,$
|
|
6035
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","externalId","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,$6,NULL,false,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)`,
|
|
6021
6036
|
[
|
|
6022
6037
|
args.id,
|
|
6023
6038
|
args.datasetId,
|
|
6024
6039
|
newVersion,
|
|
6040
|
+
existing.externalId ?? null,
|
|
6025
6041
|
parentOrganizationId,
|
|
6026
6042
|
parentProjectId,
|
|
6027
6043
|
JSON.stringify(mergedInput),
|
|
@@ -6101,11 +6117,12 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
6101
6117
|
[newVersion, id]
|
|
6102
6118
|
);
|
|
6103
6119
|
await t.none(
|
|
6104
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,NULL,true,$
|
|
6120
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","externalId","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,$6,NULL,true,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)`,
|
|
6105
6121
|
[
|
|
6106
6122
|
id,
|
|
6107
6123
|
datasetId,
|
|
6108
6124
|
newVersion,
|
|
6125
|
+
existing.externalId ?? null,
|
|
6109
6126
|
parentOrganizationId,
|
|
6110
6127
|
parentProjectId,
|
|
6111
6128
|
JSON.stringify(existing.input),
|
|
@@ -6140,78 +6157,93 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
6140
6157
|
}
|
|
6141
6158
|
async _doBatchInsertItems(input) {
|
|
6142
6159
|
try {
|
|
6143
|
-
|
|
6144
|
-
if (!dataset) {
|
|
6145
|
-
throw new error.MastraError({
|
|
6146
|
-
id: storage.createStorageErrorId("PG", "BULK_ADD_ITEMS", "DATASET_NOT_FOUND"),
|
|
6147
|
-
domain: error.ErrorDomain.STORAGE,
|
|
6148
|
-
category: error.ErrorCategory.USER,
|
|
6149
|
-
details: { datasetId: input.datasetId }
|
|
6150
|
-
});
|
|
6151
|
-
}
|
|
6160
|
+
if (input.items.length === 0) return [];
|
|
6152
6161
|
const datasetsTable = getTableName2({ indexName: storage.TABLE_DATASETS, schemaName: getSchemaName2(this.#schema) });
|
|
6153
6162
|
const itemsTable = getTableName2({ indexName: storage.TABLE_DATASET_ITEMS, schemaName: getSchemaName2(this.#schema) });
|
|
6154
6163
|
const versionsTable = getTableName2({
|
|
6155
6164
|
indexName: storage.TABLE_DATASET_VERSIONS,
|
|
6156
6165
|
schemaName: getSchemaName2(this.#schema)
|
|
6157
6166
|
});
|
|
6158
|
-
|
|
6159
|
-
const nowIso = now.toISOString();
|
|
6160
|
-
const versionId = crypto.randomUUID();
|
|
6161
|
-
const itemsWithIds = input.items.map((itemInput) => ({ id: crypto.randomUUID(), input: itemInput }));
|
|
6162
|
-
const parentOrganizationId = dataset.organizationId ?? null;
|
|
6163
|
-
const parentProjectId = dataset.projectId ?? null;
|
|
6164
|
-
let newVersion;
|
|
6167
|
+
let results = [];
|
|
6165
6168
|
await this.#db.client.tx(async (t) => {
|
|
6166
|
-
const
|
|
6167
|
-
`
|
|
6169
|
+
const dataset = await t.oneOrNone(
|
|
6170
|
+
`SELECT "version", "organizationId", "projectId" FROM ${datasetsTable} WHERE "id" = $1 FOR UPDATE`,
|
|
6168
6171
|
[input.datasetId]
|
|
6169
6172
|
);
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6173
|
+
if (!dataset)
|
|
6174
|
+
throw new error.MastraError({
|
|
6175
|
+
id: storage.createStorageErrorId("PG", "BULK_ADD_ITEMS", "DATASET_NOT_FOUND"),
|
|
6176
|
+
domain: error.ErrorDomain.STORAGE,
|
|
6177
|
+
category: error.ErrorCategory.USER,
|
|
6178
|
+
details: { datasetId: input.datasetId }
|
|
6179
|
+
});
|
|
6180
|
+
const externalIds = [...new Set(input.items.flatMap((item) => item.externalId ? [item.externalId] : []))];
|
|
6181
|
+
const historyRows = externalIds.length ? await t.manyOrNone(
|
|
6182
|
+
`SELECT * FROM ${itemsTable} WHERE "datasetId" = $1 AND "externalId" = ANY($2::text[]) ORDER BY "datasetVersion"`,
|
|
6183
|
+
[input.datasetId, externalIds]
|
|
6184
|
+
) : [];
|
|
6185
|
+
const plan = this.planDatasetItemBatch(
|
|
6186
|
+
input.items,
|
|
6187
|
+
historyRows.map((row) => this.transformItemRowFull(row)),
|
|
6188
|
+
() => crypto.randomUUID()
|
|
6189
|
+
);
|
|
6190
|
+
const resolved = new Map(
|
|
6191
|
+
[...plan.existingCurrentItems].map(([id, row]) => [id, this.datasetItemFromRow(row)])
|
|
6192
|
+
);
|
|
6193
|
+
if (plan.inserts.length > 0) {
|
|
6194
|
+
const newVersion = Number(dataset.version) + 1;
|
|
6195
|
+
const now = /* @__PURE__ */ new Date();
|
|
6196
|
+
const nowIso = now.toISOString();
|
|
6197
|
+
await t.none(`UPDATE ${datasetsTable} SET "version" = $2 WHERE "id" = $1`, [input.datasetId, newVersion]);
|
|
6198
|
+
for (const { id, item } of plan.inserts) {
|
|
6199
|
+
await t.none(
|
|
6200
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","externalId","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,$6,NULL,false,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)`,
|
|
6201
|
+
[
|
|
6202
|
+
id,
|
|
6203
|
+
input.datasetId,
|
|
6204
|
+
newVersion,
|
|
6205
|
+
item.externalId ?? null,
|
|
6206
|
+
dataset.organizationId ?? null,
|
|
6207
|
+
dataset.projectId ?? null,
|
|
6208
|
+
JSON.stringify(item.input),
|
|
6209
|
+
jsonbArg(item.groundTruth),
|
|
6210
|
+
jsonbArg(item.expectedTrajectory),
|
|
6211
|
+
jsonbArg(item.toolMocks),
|
|
6212
|
+
jsonbArg(item.requestContext),
|
|
6213
|
+
jsonbArg(item.metadata),
|
|
6214
|
+
jsonbArg(item.source),
|
|
6215
|
+
nowIso,
|
|
6216
|
+
nowIso,
|
|
6217
|
+
nowIso,
|
|
6218
|
+
nowIso
|
|
6219
|
+
]
|
|
6220
|
+
);
|
|
6221
|
+
resolved.set(id, {
|
|
6175
6222
|
id,
|
|
6176
|
-
input.datasetId,
|
|
6177
|
-
newVersion,
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6223
|
+
datasetId: input.datasetId,
|
|
6224
|
+
datasetVersion: newVersion,
|
|
6225
|
+
externalId: item.externalId ?? null,
|
|
6226
|
+
organizationId: dataset.organizationId ?? null,
|
|
6227
|
+
projectId: dataset.projectId ?? null,
|
|
6228
|
+
input: item.input,
|
|
6229
|
+
groundTruth: item.groundTruth,
|
|
6230
|
+
expectedTrajectory: item.expectedTrajectory,
|
|
6231
|
+
toolMocks: item.toolMocks,
|
|
6232
|
+
requestContext: item.requestContext,
|
|
6233
|
+
metadata: item.metadata,
|
|
6234
|
+
source: item.source,
|
|
6235
|
+
createdAt: now,
|
|
6236
|
+
updatedAt: now
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
await t.none(
|
|
6240
|
+
`INSERT INTO ${versionsTable} ("id","datasetId","version","createdAt","createdAtZ") VALUES ($1,$2,$3,$4,$5)`,
|
|
6241
|
+
[crypto.randomUUID(), input.datasetId, newVersion, nowIso, nowIso]
|
|
6192
6242
|
);
|
|
6193
6243
|
}
|
|
6194
|
-
|
|
6195
|
-
`INSERT INTO ${versionsTable} ("id","datasetId","version","createdAt","createdAtZ") VALUES ($1,$2,$3,$4,$5)`,
|
|
6196
|
-
[versionId, input.datasetId, newVersion, nowIso, nowIso]
|
|
6197
|
-
);
|
|
6244
|
+
results = plan.resolvedIds.map((id) => resolved.get(id));
|
|
6198
6245
|
});
|
|
6199
|
-
return
|
|
6200
|
-
id,
|
|
6201
|
-
datasetId: input.datasetId,
|
|
6202
|
-
datasetVersion: newVersion,
|
|
6203
|
-
organizationId: parentOrganizationId,
|
|
6204
|
-
projectId: parentProjectId,
|
|
6205
|
-
input: itemInput.input,
|
|
6206
|
-
groundTruth: itemInput.groundTruth,
|
|
6207
|
-
expectedTrajectory: itemInput.expectedTrajectory,
|
|
6208
|
-
toolMocks: itemInput.toolMocks,
|
|
6209
|
-
requestContext: itemInput.requestContext,
|
|
6210
|
-
metadata: itemInput.metadata,
|
|
6211
|
-
source: itemInput.source,
|
|
6212
|
-
createdAt: now,
|
|
6213
|
-
updatedAt: now
|
|
6214
|
-
}));
|
|
6246
|
+
return results;
|
|
6215
6247
|
} catch (error$1) {
|
|
6216
6248
|
if (error$1 instanceof error.MastraError) throw error$1;
|
|
6217
6249
|
throw new error.MastraError(
|
|
@@ -6265,11 +6297,12 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
6265
6297
|
[newVersion, item.id]
|
|
6266
6298
|
);
|
|
6267
6299
|
await t.none(
|
|
6268
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,NULL,true,$
|
|
6300
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","externalId","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","requestContext","metadata","source","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,$4,$5,$6,NULL,true,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)`,
|
|
6269
6301
|
[
|
|
6270
6302
|
item.id,
|
|
6271
6303
|
input.datasetId,
|
|
6272
6304
|
newVersion,
|
|
6305
|
+
item.externalId ?? null,
|
|
6273
6306
|
parentOrganizationId,
|
|
6274
6307
|
parentProjectId,
|
|
6275
6308
|
JSON.stringify(item.input),
|