@mastra/pg 1.24.0 → 1.25.0-alpha.1

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/dist/index.js CHANGED
@@ -6274,23 +6274,6 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6274
6274
  }
6275
6275
  async _doUpdateItem(args) {
6276
6276
  try {
6277
- const existing = await this.#getItemById(this.#db.client, { id: args.id });
6278
- if (!existing) throw new MastraError({
6279
- id: createStorageErrorId("PG", "UPDATE_ITEM", "NOT_FOUND"),
6280
- domain: ErrorDomain.STORAGE,
6281
- category: ErrorCategory.USER,
6282
- details: { itemId: args.id }
6283
- });
6284
- if (existing.datasetId !== args.datasetId) throw new MastraError({
6285
- id: createStorageErrorId("PG", "UPDATE_ITEM", "DATASET_MISMATCH"),
6286
- domain: ErrorDomain.STORAGE,
6287
- category: ErrorCategory.USER,
6288
- details: {
6289
- itemId: args.id,
6290
- expectedDatasetId: args.datasetId,
6291
- actualDatasetId: existing.datasetId
6292
- }
6293
- });
6294
6277
  const datasetsTable = getTableName$5({
6295
6278
  indexName: TABLE_DATASETS,
6296
6279
  schemaName: getSchemaName$5(this.#schema)
@@ -6306,23 +6289,49 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6306
6289
  const versionId = crypto.randomUUID();
6307
6290
  const now = /* @__PURE__ */ new Date();
6308
6291
  const nowIso = now.toISOString();
6309
- const mergedInput = args.input !== void 0 ? args.input : existing.input;
6310
- const mergedGroundTruth = args.groundTruth !== void 0 ? args.groundTruth : existing.groundTruth;
6311
- const mergedExpectedTrajectory = args.expectedTrajectory !== void 0 ? args.expectedTrajectory : existing.expectedTrajectory;
6312
- const mergedToolMocks = args.toolMocks !== void 0 ? args.toolMocks : existing.toolMocks;
6313
- const mergedUnmockedToolPolicy = args.unmockedToolPolicy !== void 0 ? args.unmockedToolPolicy : existing.unmockedToolPolicy;
6314
- const mergedScorerIds = args.scorerIds !== void 0 ? args.scorerIds ?? void 0 : existing.scorerIds;
6315
- const mergedRequestContext = args.requestContext !== void 0 ? args.requestContext : existing.requestContext;
6316
- const mergedMetadata = args.metadata !== void 0 ? args.metadata : existing.metadata;
6317
- const mergedSource = args.source !== void 0 ? args.source : existing.source;
6318
- let newVersion;
6319
- let parentOrganizationId = null;
6320
- let parentProjectId = null;
6292
+ let updated;
6321
6293
  await this.#db.client.tx(async (t) => {
6294
+ await t.oneOrNone(`SELECT "id" FROM ${datasetsTable} WHERE "id" = $1 FOR UPDATE`, [args.datasetId]);
6295
+ const existing = await this.#getItemById(t, { id: args.id });
6296
+ if (!existing) throw new MastraError({
6297
+ id: createStorageErrorId("PG", "UPDATE_ITEM", "NOT_FOUND"),
6298
+ domain: ErrorDomain.STORAGE,
6299
+ category: ErrorCategory.USER,
6300
+ details: { itemId: args.id }
6301
+ });
6302
+ if (existing.datasetId !== args.datasetId) throw new MastraError({
6303
+ id: createStorageErrorId("PG", "UPDATE_ITEM", "DATASET_MISMATCH"),
6304
+ domain: ErrorDomain.STORAGE,
6305
+ category: ErrorCategory.USER,
6306
+ details: {
6307
+ itemId: args.id,
6308
+ expectedDatasetId: args.datasetId,
6309
+ actualDatasetId: existing.datasetId
6310
+ }
6311
+ });
6312
+ if (existing.metadata?.__purged === true) throw new MastraError({
6313
+ id: "DATASET_ITEM_PURGED",
6314
+ domain: ErrorDomain.STORAGE,
6315
+ category: ErrorCategory.USER,
6316
+ details: {
6317
+ datasetId: args.datasetId,
6318
+ itemId: args.id
6319
+ },
6320
+ text: `Purged dataset item cannot be updated: ${args.id}`
6321
+ });
6322
+ const mergedInput = args.input !== void 0 ? args.input : existing.input;
6323
+ const mergedGroundTruth = args.groundTruth !== void 0 ? args.groundTruth : existing.groundTruth;
6324
+ const mergedExpectedTrajectory = args.expectedTrajectory !== void 0 ? args.expectedTrajectory : existing.expectedTrajectory;
6325
+ const mergedToolMocks = args.toolMocks !== void 0 ? args.toolMocks : existing.toolMocks;
6326
+ const mergedUnmockedToolPolicy = args.unmockedToolPolicy !== void 0 ? args.unmockedToolPolicy : existing.unmockedToolPolicy;
6327
+ const mergedScorerIds = args.scorerIds !== void 0 ? args.scorerIds ?? void 0 : existing.scorerIds;
6328
+ const mergedRequestContext = args.requestContext !== void 0 ? args.requestContext : existing.requestContext;
6329
+ const mergedMetadata = args.metadata !== void 0 ? args.metadata : existing.metadata;
6330
+ const mergedSource = args.source !== void 0 ? args.source : existing.source;
6322
6331
  const row = await t.one(`UPDATE ${datasetsTable} SET "version" = "version" + 1 WHERE "id" = $1 RETURNING "version", "organizationId", "projectId"`, [args.datasetId]);
6323
- newVersion = row.version;
6324
- parentOrganizationId = row.organizationId ?? null;
6325
- parentProjectId = row.projectId ?? null;
6332
+ const newVersion = row.version;
6333
+ const parentOrganizationId = row.organizationId ?? null;
6334
+ const parentProjectId = row.projectId ?? null;
6326
6335
  await t.none(`UPDATE ${itemsTable} SET "validTo" = $1 WHERE "id" = $2 AND "validTo" IS NULL AND "isDeleted" = false`, [newVersion, args.id]);
6327
6336
  await t.none(`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","externalId","organizationId","projectId","validTo","isDeleted","input","groundTruth","expectedTrajectory","toolMocks","unmockedToolPolicy","scorerIds","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,$18,$19)`, [
6328
6337
  args.id,
@@ -6352,23 +6361,24 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6352
6361
  nowIso,
6353
6362
  nowIso
6354
6363
  ]);
6364
+ updated = {
6365
+ ...existing,
6366
+ datasetVersion: newVersion,
6367
+ organizationId: parentOrganizationId,
6368
+ projectId: parentProjectId,
6369
+ input: mergedInput,
6370
+ groundTruth: mergedGroundTruth,
6371
+ expectedTrajectory: mergedExpectedTrajectory,
6372
+ toolMocks: mergedToolMocks,
6373
+ unmockedToolPolicy: mergedUnmockedToolPolicy,
6374
+ scorerIds: mergedScorerIds,
6375
+ requestContext: mergedRequestContext,
6376
+ metadata: mergedMetadata,
6377
+ source: mergedSource,
6378
+ updatedAt: now
6379
+ };
6355
6380
  });
6356
- return {
6357
- ...existing,
6358
- datasetVersion: newVersion,
6359
- organizationId: parentOrganizationId,
6360
- projectId: parentProjectId,
6361
- input: mergedInput,
6362
- groundTruth: mergedGroundTruth,
6363
- expectedTrajectory: mergedExpectedTrajectory,
6364
- toolMocks: mergedToolMocks,
6365
- unmockedToolPolicy: mergedUnmockedToolPolicy,
6366
- scorerIds: mergedScorerIds,
6367
- requestContext: mergedRequestContext,
6368
- metadata: mergedMetadata,
6369
- source: mergedSource,
6370
- updatedAt: now
6371
- };
6381
+ return updated;
6372
6382
  } catch (error) {
6373
6383
  if (error instanceof MastraError) throw error;
6374
6384
  throw new MastraError({
@@ -6380,18 +6390,6 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6380
6390
  }
6381
6391
  async _doDeleteItem({ id, datasetId }) {
6382
6392
  try {
6383
- const existing = await this.#getItemById(this.#db.client, { id });
6384
- if (!existing) return;
6385
- if (existing.datasetId !== datasetId) throw new MastraError({
6386
- id: createStorageErrorId("PG", "DELETE_ITEM", "DATASET_MISMATCH"),
6387
- domain: ErrorDomain.STORAGE,
6388
- category: ErrorCategory.USER,
6389
- details: {
6390
- itemId: id,
6391
- expectedDatasetId: datasetId,
6392
- actualDatasetId: existing.datasetId
6393
- }
6394
- });
6395
6393
  const datasetsTable = getTableName$5({
6396
6394
  indexName: TABLE_DATASETS,
6397
6395
  schemaName: getSchemaName$5(this.#schema)
@@ -6407,6 +6405,19 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6407
6405
  const versionId = crypto.randomUUID();
6408
6406
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
6409
6407
  await this.#db.client.tx(async (t) => {
6408
+ await t.oneOrNone(`SELECT "id" FROM ${datasetsTable} WHERE "id" = $1 FOR UPDATE`, [datasetId]);
6409
+ const existing = await this.#getItemById(t, { id });
6410
+ if (!existing) return;
6411
+ if (existing.datasetId !== datasetId) throw new MastraError({
6412
+ id: createStorageErrorId("PG", "DELETE_ITEM", "DATASET_MISMATCH"),
6413
+ domain: ErrorDomain.STORAGE,
6414
+ category: ErrorCategory.USER,
6415
+ details: {
6416
+ itemId: id,
6417
+ expectedDatasetId: datasetId,
6418
+ actualDatasetId: existing.datasetId
6419
+ }
6420
+ });
6410
6421
  const row = await t.one(`UPDATE ${datasetsTable} SET "version" = "version" + 1 WHERE "id" = $1 RETURNING "version", "organizationId", "projectId"`, [datasetId]);
6411
6422
  const newVersion = row.version;
6412
6423
  const parentOrganizationId = row.organizationId ?? null;
@@ -6593,19 +6604,6 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6593
6604
  }
6594
6605
  async _doBatchDeleteItems(input) {
6595
6606
  try {
6596
- const dataset = await this.#getDatasetById(this.#db.client, { id: input.datasetId });
6597
- if (!dataset) throw new MastraError({
6598
- id: createStorageErrorId("PG", "BULK_DELETE_ITEMS", "DATASET_NOT_FOUND"),
6599
- domain: ErrorDomain.STORAGE,
6600
- category: ErrorCategory.USER,
6601
- details: { datasetId: input.datasetId }
6602
- });
6603
- const currentItems = [];
6604
- for (const itemId of input.itemIds) {
6605
- const item = await this.#getItemById(this.#db.client, { id: itemId });
6606
- if (item && item.datasetId === input.datasetId) currentItems.push(item);
6607
- }
6608
- if (currentItems.length === 0) return;
6609
6607
  const datasetsTable = getTableName$5({
6610
6608
  indexName: TABLE_DATASETS,
6611
6609
  schemaName: getSchemaName$5(this.#schema)
@@ -6620,9 +6618,19 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6620
6618
  });
6621
6619
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
6622
6620
  const versionId = crypto.randomUUID();
6623
- const parentOrganizationId = dataset.organizationId ?? null;
6624
- const parentProjectId = dataset.projectId ?? null;
6625
6621
  await this.#db.client.tx(async (t) => {
6622
+ await t.oneOrNone(`SELECT "id" FROM ${datasetsTable} WHERE "id" = $1 FOR UPDATE`, [input.datasetId]);
6623
+ const dataset = await this.#getDatasetById(t, { id: input.datasetId });
6624
+ if (!dataset) throw new MastraError({
6625
+ id: createStorageErrorId("PG", "BULK_DELETE_ITEMS", "DATASET_NOT_FOUND"),
6626
+ domain: ErrorDomain.STORAGE,
6627
+ category: ErrorCategory.USER,
6628
+ details: { datasetId: input.datasetId }
6629
+ });
6630
+ const currentItems = (await t.manyOrNone(`SELECT * FROM ${itemsTable} WHERE "id" = ANY($1::text[]) AND "datasetId" = $2 AND "validTo" IS NULL AND "isDeleted" = false`, [input.itemIds, input.datasetId])).map((row) => this.transformItemRow(row));
6631
+ if (currentItems.length === 0) return;
6632
+ const parentOrganizationId = dataset.organizationId ?? null;
6633
+ const parentProjectId = dataset.projectId ?? null;
6626
6634
  const newVersion = (await t.one(`UPDATE ${datasetsTable} SET "version" = "version" + 1 WHERE "id" = $1 RETURNING "version"`, [input.datasetId])).version;
6627
6635
  for (const item of currentItems) {
6628
6636
  await t.none(`UPDATE ${itemsTable} SET "validTo" = $1 WHERE "id" = $2 AND "validTo" IS NULL AND "isDeleted" = false`, [newVersion, item.id]);
@@ -10660,6 +10668,57 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10660
10668
  }, error);
10661
10669
  }
10662
10670
  }
10671
+ /**
10672
+ * Atomically reassign a thread and all of its messages to a different resource.
10673
+ *
10674
+ * Runs inside a single transaction and takes a `SELECT ... FOR UPDATE` row lock on the
10675
+ * thread, so overlapping transfers of the same thread serialize and can never interleave
10676
+ * the thread update with the message update. Either both the thread and every message move
10677
+ * to the new resource, or neither does — there is no split-ownership window. The thread's
10678
+ * `createdAt` is preserved. Callers are responsible for authorizing the reassignment.
10679
+ */
10680
+ async updateThreadResourceId({ threadId, resourceId }) {
10681
+ const threadsTable = getTableName$3({
10682
+ indexName: TABLE_THREADS,
10683
+ schemaName: getSchemaName$3(this.#schema)
10684
+ });
10685
+ const messagesTable = getTableName$3({
10686
+ indexName: TABLE_MESSAGES,
10687
+ schemaName: getSchemaName$3(this.#schema)
10688
+ });
10689
+ try {
10690
+ return await this.#db.client.tx(async (t) => {
10691
+ const thread = await t.oneOrNone(`SELECT * FROM ${threadsTable} WHERE id = $1 FOR UPDATE`, [threadId]);
10692
+ if (!thread) throw new Error(`Thread "${threadId}" not found`);
10693
+ const normalized = {
10694
+ id: thread.id,
10695
+ resourceId: thread.resourceId,
10696
+ title: thread.title,
10697
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
10698
+ createdAt: thread.createdAtZ || thread.createdAt,
10699
+ updatedAt: thread.updatedAtZ || thread.updatedAt
10700
+ };
10701
+ if (thread.resourceId === resourceId) return normalized;
10702
+ await t.none(`UPDATE ${threadsTable} SET "resourceId" = $1, "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id = $2`, [resourceId, threadId]);
10703
+ await t.none(`UPDATE ${messagesTable} SET "resourceId" = $1 WHERE thread_id = $2`, [resourceId, threadId]);
10704
+ return {
10705
+ ...normalized,
10706
+ resourceId,
10707
+ updatedAt: /* @__PURE__ */ new Date()
10708
+ };
10709
+ });
10710
+ } catch (error) {
10711
+ throw new MastraError({
10712
+ id: createStorageErrorId("PG", "UPDATE_THREAD_RESOURCE_ID", "FAILED"),
10713
+ domain: ErrorDomain.STORAGE,
10714
+ category: ErrorCategory.THIRD_PARTY,
10715
+ details: {
10716
+ threadId,
10717
+ resourceId
10718
+ }
10719
+ }, error);
10720
+ }
10721
+ }
10663
10722
  async listThreads(args) {
10664
10723
  const { page = 0, perPage: perPageInput, orderBy, filter } = args;
10665
10724
  try {
@@ -11664,7 +11723,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11664
11723
  await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
11665
11724
  return updatedResource;
11666
11725
  }
11667
- async cloneThread(args) {
11726
+ async copyThread(args) {
11668
11727
  const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
11669
11728
  const sourceThread = await this.#getThreadById(this.#db.client, { threadId: sourceThreadId });
11670
11729
  if (!sourceThread) throw new MastraError({
@@ -11692,7 +11751,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11692
11751
  });
11693
11752
  try {
11694
11753
  return await this.#db.client.tx(async (t) => {
11695
- let messageQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"
11754
+ let messageQuery = `SELECT id, "createdAt"
11696
11755
  FROM ${messageTableName} WHERE thread_id = $1`;
11697
11756
  const messageParams = [sourceThreadId];
11698
11757
  let paramIndex = 2;
@@ -11753,42 +11812,23 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11753
11812
  nowStr,
11754
11813
  nowStr
11755
11814
  ]);
11756
- const clonedMessages = [];
11757
11815
  const messageIdMap = {};
11758
11816
  const targetResourceId = resourceId || sourceThread.resourceId;
11759
11817
  for (const sourceMsg of sourceMessages) {
11760
11818
  const newMessageId = crypto.randomUUID();
11761
11819
  messageIdMap[sourceMsg.id] = newMessageId;
11762
- const normalizedMsg = this.normalizeMessageRow(sourceMsg);
11763
- let parsedContent = normalizedMsg.content;
11764
- try {
11765
- parsedContent = JSON.parse(normalizedMsg.content);
11766
- } catch {}
11767
- const createdAt = toUtcISOString(new Date(normalizedMsg.createdAt));
11768
- await t.none(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11769
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
11820
+ const insertResult = await t.query(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11821
+ SELECT $1, $2, content, "createdAt", "createdAtZ", role, type, $3
11822
+ FROM ${messageTableName} WHERE id = $4`, [
11770
11823
  newMessageId,
11771
11824
  newThreadId,
11772
- typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
11773
- createdAt,
11774
- createdAt,
11775
- normalizedMsg.role,
11776
- normalizedMsg.type || "v2",
11777
- targetResourceId
11825
+ targetResourceId,
11826
+ sourceMsg.id
11778
11827
  ]);
11779
- clonedMessages.push({
11780
- id: newMessageId,
11781
- threadId: newThreadId,
11782
- content: parsedContent,
11783
- role: normalizedMsg.role,
11784
- type: normalizedMsg.type,
11785
- createdAt: new Date(normalizedMsg.createdAt),
11786
- resourceId: targetResourceId
11787
- });
11828
+ if (insertResult.rowCount !== 1) throw new Error(`Failed to copy message ${sourceMsg.id}: expected 1 row copied but got ${insertResult.rowCount}`);
11788
11829
  }
11789
11830
  return {
11790
11831
  thread: newThread,
11791
- clonedMessages,
11792
11832
  messageIdMap
11793
11833
  };
11794
11834
  });
@@ -15276,11 +15316,6 @@ function jsonField(value) {
15276
15316
  }
15277
15317
  function parsedJson(value) {
15278
15318
  if (value == null) return void 0;
15279
- if (typeof value === "string") try {
15280
- return JSON.parse(value);
15281
- } catch {
15282
- return;
15283
- }
15284
15319
  return value;
15285
15320
  }
15286
15321
  function rowToCommonContext(row) {