@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.
@@ -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 are not valid retention keys.
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. There is no separate `results` key.
93
- > - For `schedules`, the growth table is the fire history (`schedule_triggers`, one row per fire) — schedule definitions are config and are not pruned.
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 is 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.
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 does not 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.
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
- Handing that free space back to the OS is a separate concern that Mastra does not 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.
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,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
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,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
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,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
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
- const dataset = await this.getDatasetById({ id: input.datasetId });
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
- const now = /* @__PURE__ */ new Date();
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 row = await t.one(
6167
- `UPDATE ${datasetsTable} SET "version" = "version" + 1 WHERE "id" = $1 RETURNING "version"`,
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
- newVersion = row.version;
6171
- for (const { id, input: itemInput } of itemsWithIds) {
6172
- await t.none(
6173
- `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,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
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
- parentOrganizationId,
6179
- parentProjectId,
6180
- JSON.stringify(itemInput.input),
6181
- jsonbArg(itemInput.groundTruth),
6182
- jsonbArg(itemInput.expectedTrajectory),
6183
- jsonbArg(itemInput.toolMocks),
6184
- jsonbArg(itemInput.requestContext),
6185
- jsonbArg(itemInput.metadata),
6186
- jsonbArg(itemInput.source),
6187
- nowIso,
6188
- nowIso,
6189
- nowIso,
6190
- nowIso
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
- await t.none(
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 itemsWithIds.map(({ id, input: itemInput }) => ({
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,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
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),