@mastra/pg 1.36.0-alpha.1 → 1.36.0-alpha.3
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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +30 -30
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +7 -3
- package/dist/index.cjs +221 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +221 -79
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +5 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/schema-snapshot.d.ts +8 -0
- package/dist/storage/db/schema-snapshot.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +12 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/feedback.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/feedback.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +2 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/sql.d.ts +2 -0
- package/dist/storage/domains/observability/v-next/sql.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2719,7 +2719,8 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2719
2719
|
notNullColumns,
|
|
2720
2720
|
indexes,
|
|
2721
2721
|
replicaIdentityIndexes,
|
|
2722
|
-
primaryKeyIndexes
|
|
2722
|
+
primaryKeyIndexes,
|
|
2723
|
+
createdTables: /* @__PURE__ */ new Set()
|
|
2723
2724
|
};
|
|
2724
2725
|
}
|
|
2725
2726
|
// src/storage/db/index.ts
|
|
@@ -2879,26 +2880,30 @@ function generateTableSQL({ tableName, schema, schemaName, compositePrimaryKey,
|
|
|
2879
2880
|
` : ""}
|
|
2880
2881
|
`;
|
|
2881
2882
|
}
|
|
2883
|
+
function indexColumnsSQL(columns, opclass) {
|
|
2884
|
+
return columns.map((col) => {
|
|
2885
|
+
const [colName, ...modifiers] = col.includes(" DESC") || col.includes(" ASC") ? col.split(" ") : [col];
|
|
2886
|
+
if (!colName) throw new Error(`Invalid column specification: ${col}`);
|
|
2887
|
+
return [
|
|
2888
|
+
`"${(0, _mastra_core_utils.parseSqlIdentifier)(colName, "column name")}"`,
|
|
2889
|
+
opclass,
|
|
2890
|
+
...modifiers
|
|
2891
|
+
].filter(Boolean).join(" ");
|
|
2892
|
+
}).join(", ");
|
|
2893
|
+
}
|
|
2882
2894
|
/**
|
|
2883
2895
|
* Generates a CREATE INDEX SQL statement from index options.
|
|
2884
2896
|
* Used by exportSchemas to produce index DDL without a database connection.
|
|
2885
2897
|
*/
|
|
2886
2898
|
function generateIndexSQL(options, schemaName) {
|
|
2887
|
-
const { name, table, columns, unique = false, where, method = "btree" } = options;
|
|
2899
|
+
const { name, table, columns, unique = false, where, method = "btree", opclass } = options;
|
|
2888
2900
|
const fullTableName = getTableName$6({
|
|
2889
2901
|
indexName: table,
|
|
2890
2902
|
schemaName: getSchemaName$6(schemaName)
|
|
2891
2903
|
});
|
|
2892
2904
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
2893
2905
|
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
2894
|
-
const columnsStr = columns
|
|
2895
|
-
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
2896
|
-
const [colName, ...modifiers] = col.split(" ");
|
|
2897
|
-
if (!colName) throw new Error(`Invalid column specification: ${col}`);
|
|
2898
|
-
return `"${(0, _mastra_core_utils.parseSqlIdentifier)(colName, "column name")}" ${modifiers.join(" ")}`;
|
|
2899
|
-
}
|
|
2900
|
-
return `"${(0, _mastra_core_utils.parseSqlIdentifier)(col, "column name")}"`;
|
|
2901
|
-
}).join(", ");
|
|
2906
|
+
const columnsStr = indexColumnsSQL(columns, opclass);
|
|
2902
2907
|
const whereStr = where ? ` WHERE ${where}` : "";
|
|
2903
2908
|
return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${`"${(0, _mastra_core_utils.parseSqlIdentifier)(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${whereStr};`;
|
|
2904
2909
|
}
|
|
@@ -3409,6 +3414,7 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
3409
3414
|
if (this.schemaName) await this.setupSchema();
|
|
3410
3415
|
const snapshot = this.schemaSnapshot;
|
|
3411
3416
|
if (!snapshot || !this.snapshotShowsTableConverged(snapshot, tableName)) {
|
|
3417
|
+
const isNewTable = snapshot ? !snapshot.tables.has(tableName) : false;
|
|
3412
3418
|
const sql = generateTableSQL({
|
|
3413
3419
|
tableName,
|
|
3414
3420
|
schema,
|
|
@@ -3422,6 +3428,7 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
3422
3428
|
}
|
|
3423
3429
|
if (snapshot) {
|
|
3424
3430
|
snapshot.tables.add(tableName);
|
|
3431
|
+
if (isNewTable) snapshot.createdTables.add(tableName);
|
|
3425
3432
|
const created = this.snapshotColumns(snapshot, tableName);
|
|
3426
3433
|
for (const [name, def] of Object.entries(schema)) {
|
|
3427
3434
|
const parsedName = (0, _mastra_core_utils.parseSqlIdentifier)(name, "column name");
|
|
@@ -3890,18 +3897,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3890
3897
|
const snapshot = this.schemaSnapshot;
|
|
3891
3898
|
if (snapshot?.indexes.has(name)) return;
|
|
3892
3899
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
3893
|
-
const concurrentStr = concurrent ? "CONCURRENTLY " : "";
|
|
3900
|
+
const concurrentStr = concurrent && !this.isTableCreatedInThisInit(table) ? "CONCURRENTLY " : "";
|
|
3894
3901
|
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
3895
|
-
const columnsStr = columns
|
|
3896
|
-
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
3897
|
-
const [colName, ...modifiers] = col.split(" ");
|
|
3898
|
-
if (!colName) throw new Error(`Invalid column specification: ${col}`);
|
|
3899
|
-
const quotedCol = `"${(0, _mastra_core_utils.parseSqlIdentifier)(colName, "column name")}" ${modifiers.join(" ")}`;
|
|
3900
|
-
return opclass ? `${quotedCol} ${opclass}` : quotedCol;
|
|
3901
|
-
}
|
|
3902
|
-
const quotedCol = `"${(0, _mastra_core_utils.parseSqlIdentifier)(col, "column name")}"`;
|
|
3903
|
-
return opclass ? `${quotedCol} ${opclass}` : quotedCol;
|
|
3904
|
-
}).join(", ");
|
|
3902
|
+
const columnsStr = indexColumnsSQL(columns, opclass);
|
|
3905
3903
|
const whereStr = where ? ` WHERE ${where}` : "";
|
|
3906
3904
|
const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
|
|
3907
3905
|
let withStr = "";
|
|
@@ -3934,6 +3932,13 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3934
3932
|
if (snapshot?.indexes.has(indexName)) return;
|
|
3935
3933
|
if (await buildIndex(this.client, this.schemaName || "public", indexName, () => this.timedIndexBuild(indexName, sql), this.logger)) snapshot?.indexes.add(indexName);
|
|
3936
3934
|
}
|
|
3935
|
+
/**
|
|
3936
|
+
* Whether `tableName` was absent when this init's snapshot was read and this
|
|
3937
|
+
* init created it, so its indexes can be built without CONCURRENTLY.
|
|
3938
|
+
*/
|
|
3939
|
+
isTableCreatedInThisInit(tableName) {
|
|
3940
|
+
return this.schemaSnapshot?.createdTables.has(tableName) ?? false;
|
|
3941
|
+
}
|
|
3937
3942
|
async timedIndexBuild(indexName, sql) {
|
|
3938
3943
|
const startedAt = Date.now();
|
|
3939
3944
|
this.logger.info(`Creating index ${indexName}`);
|
|
@@ -11673,6 +11678,13 @@ function dedupeMessagesForSave(messages) {
|
|
|
11673
11678
|
}
|
|
11674
11679
|
return Array.from(deduped.values());
|
|
11675
11680
|
}
|
|
11681
|
+
/** Shallow merge; keys patched to `undefined` are dropped when serialized. Non-object metadata is replaced. */
|
|
11682
|
+
function mergeMetadata(existing, patch) {
|
|
11683
|
+
return {
|
|
11684
|
+
...existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {},
|
|
11685
|
+
...patch
|
|
11686
|
+
};
|
|
11687
|
+
}
|
|
11676
11688
|
var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
11677
11689
|
supportsPartialThreadUpdate = true;
|
|
11678
11690
|
supportsObservationalMemory = true;
|
|
@@ -12239,7 +12251,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
12239
12251
|
const createdAt = toUtcISOString(thread.createdAt);
|
|
12240
12252
|
const updatedAt = toUtcISOString(thread.updatedAt);
|
|
12241
12253
|
const metadataJson = thread.metadata ? toPgJson(thread.metadata) : null;
|
|
12242
|
-
await this.#fenced(fence, "saveThread", (q) => q.
|
|
12254
|
+
const saved = await this.#fenced(fence, "saveThread", (q) => q.oneOrNone(`INSERT INTO ${tableName} AS t (
|
|
12243
12255
|
id,
|
|
12244
12256
|
"resourceId",
|
|
12245
12257
|
title,
|
|
@@ -12250,13 +12262,12 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
12250
12262
|
"updatedAtZ"
|
|
12251
12263
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
12252
12264
|
ON CONFLICT (id) DO UPDATE SET
|
|
12253
|
-
"resourceId" = EXCLUDED."resourceId",
|
|
12254
12265
|
title = EXCLUDED.title,
|
|
12255
12266
|
metadata = EXCLUDED.metadata,
|
|
12256
|
-
"createdAt" = EXCLUDED."createdAt",
|
|
12257
|
-
"createdAtZ" = EXCLUDED."createdAtZ",
|
|
12258
12267
|
"updatedAt" = EXCLUDED."updatedAt",
|
|
12259
|
-
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12268
|
+
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12269
|
+
WHERE t."resourceId" = EXCLUDED."resourceId"
|
|
12270
|
+
RETURNING *`, [
|
|
12260
12271
|
thread.id,
|
|
12261
12272
|
thread.resourceId,
|
|
12262
12273
|
thread.title,
|
|
@@ -12266,12 +12277,26 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
12266
12277
|
updatedAt,
|
|
12267
12278
|
updatedAt
|
|
12268
12279
|
]));
|
|
12280
|
+
if (!saved) throw new _mastra_core_error.MastraError({
|
|
12281
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SAVE_THREAD", "RESOURCE_ID_MISMATCH"),
|
|
12282
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
12283
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
12284
|
+
text: `Thread ${thread.id} already exists and belongs to a different resource. Use updateThreadResourceId to move a thread and its messages to another resource.`,
|
|
12285
|
+
details: {
|
|
12286
|
+
threadId: thread.id,
|
|
12287
|
+
resourceId: thread.resourceId
|
|
12288
|
+
}
|
|
12289
|
+
});
|
|
12269
12290
|
return {
|
|
12270
|
-
|
|
12271
|
-
|
|
12291
|
+
id: saved.id,
|
|
12292
|
+
resourceId: saved.resourceId,
|
|
12293
|
+
title: saved.title,
|
|
12294
|
+
metadata: saved.metadata == null ? void 0 : typeof saved.metadata === "string" ? JSON.parse(saved.metadata) : saved.metadata,
|
|
12295
|
+
createdAt: saved.createdAtZ || saved.createdAt,
|
|
12296
|
+
updatedAt: saved.updatedAtZ || saved.updatedAt
|
|
12272
12297
|
};
|
|
12273
12298
|
} catch (error) {
|
|
12274
|
-
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
12299
|
+
if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
12275
12300
|
throw new _mastra_core_error.MastraError({
|
|
12276
12301
|
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SAVE_THREAD", "FAILED"),
|
|
12277
12302
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -12280,52 +12305,125 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
12280
12305
|
}, error);
|
|
12281
12306
|
}
|
|
12282
12307
|
}
|
|
12283
|
-
|
|
12308
|
+
#toThread(row) {
|
|
12309
|
+
return {
|
|
12310
|
+
id: row.id,
|
|
12311
|
+
resourceId: row.resourceId,
|
|
12312
|
+
title: row.title,
|
|
12313
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
|
|
12314
|
+
createdAt: row.createdAtZ || row.createdAt,
|
|
12315
|
+
updatedAt: row.updatedAtZ || row.updatedAt
|
|
12316
|
+
};
|
|
12317
|
+
}
|
|
12318
|
+
async #lockThread(t, tableName, { id, resourceId }) {
|
|
12319
|
+
const params = [id];
|
|
12320
|
+
let query = `SELECT * FROM ${tableName} WHERE id = $1`;
|
|
12321
|
+
if (resourceId !== void 0) {
|
|
12322
|
+
query += ` AND "resourceId" = $2`;
|
|
12323
|
+
params.push(resourceId);
|
|
12324
|
+
}
|
|
12325
|
+
const row = await t.oneOrNone(`${query} FOR UPDATE`, params);
|
|
12326
|
+
return row ? this.#toThread(row) : null;
|
|
12327
|
+
}
|
|
12328
|
+
/**
|
|
12329
|
+
* Writes `title` (when given) and the full `metadata` value (when given).
|
|
12330
|
+
* Callers that merge metadata must hold the row lock from `#lockThread`.
|
|
12331
|
+
*/
|
|
12332
|
+
async #writeThread(q, tableName, { id, title, metadata }) {
|
|
12333
|
+
const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
|
|
12334
|
+
const params = [
|
|
12335
|
+
title ?? null,
|
|
12336
|
+
nowStr,
|
|
12337
|
+
nowStr,
|
|
12338
|
+
id
|
|
12339
|
+
];
|
|
12340
|
+
let metadataSet = "";
|
|
12341
|
+
if (metadata !== void 0) {
|
|
12342
|
+
params.push(toPgJson(metadata));
|
|
12343
|
+
metadataSet = "metadata = $5,";
|
|
12344
|
+
}
|
|
12345
|
+
const row = await q.oneOrNone(`UPDATE ${tableName}
|
|
12346
|
+
SET
|
|
12347
|
+
title = COALESCE($1, title),
|
|
12348
|
+
${metadataSet}
|
|
12349
|
+
"updatedAt" = $2,
|
|
12350
|
+
"updatedAtZ" = $3
|
|
12351
|
+
WHERE id = $4
|
|
12352
|
+
RETURNING *`, params);
|
|
12353
|
+
return row ? this.#toThread(row) : null;
|
|
12354
|
+
}
|
|
12355
|
+
/**
|
|
12356
|
+
* Cross-process atomic metadata read-modify-write: the thread row stays locked
|
|
12357
|
+
* (`SELECT ... FOR UPDATE`) from the read through the write, so concurrent
|
|
12358
|
+
* writers from any process serialize instead of overwriting each other.
|
|
12359
|
+
*/
|
|
12360
|
+
async updateThreadMetadata({ id, resourceId, update, fence: explicitFence }) {
|
|
12361
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
12362
|
+
const threadsTable = getTableName$3({
|
|
12363
|
+
indexName: _mastra_core_storage.TABLE_THREADS,
|
|
12364
|
+
schemaName: getSchemaName$3(this.#schema)
|
|
12365
|
+
});
|
|
12366
|
+
try {
|
|
12367
|
+
return await this.#db.client.tx(async (t) => {
|
|
12368
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateThreadMetadata");
|
|
12369
|
+
const thread = await this.#lockThread(t, threadsTable, {
|
|
12370
|
+
id,
|
|
12371
|
+
resourceId
|
|
12372
|
+
});
|
|
12373
|
+
if (!thread) return null;
|
|
12374
|
+
const metadata = update(thread);
|
|
12375
|
+
if (!metadata) return thread;
|
|
12376
|
+
return this.#writeThread(t, threadsTable, {
|
|
12377
|
+
id,
|
|
12378
|
+
metadata: mergeMetadata(thread.metadata, metadata)
|
|
12379
|
+
});
|
|
12380
|
+
});
|
|
12381
|
+
} catch (error) {
|
|
12382
|
+
if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
12383
|
+
throw new _mastra_core_error.MastraError({
|
|
12384
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_THREAD_METADATA", "FAILED"),
|
|
12385
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
12386
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
12387
|
+
details: { threadId: id }
|
|
12388
|
+
}, error);
|
|
12389
|
+
}
|
|
12390
|
+
}
|
|
12391
|
+
async updateThread({ id, title, metadata, fence: explicitFence }) {
|
|
12284
12392
|
const threadTableName = getTableName$3({
|
|
12285
12393
|
indexName: _mastra_core_storage.TABLE_THREADS,
|
|
12286
12394
|
schemaName: getSchemaName$3(this.#schema)
|
|
12287
12395
|
});
|
|
12396
|
+
const notFound = () => new _mastra_core_error.MastraError({
|
|
12397
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_THREAD", "FAILED"),
|
|
12398
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
12399
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
12400
|
+
text: `Thread ${id} not found`,
|
|
12401
|
+
details: {
|
|
12402
|
+
threadId: id,
|
|
12403
|
+
title: title ?? null
|
|
12404
|
+
}
|
|
12405
|
+
});
|
|
12288
12406
|
try {
|
|
12289
|
-
return await this.#fenced(
|
|
12290
|
-
const
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
12294
|
-
category: _mastra_core_error.ErrorCategory.USER,
|
|
12295
|
-
text: `Thread ${id} not found`,
|
|
12296
|
-
details: {
|
|
12297
|
-
threadId: id,
|
|
12298
|
-
title: title ?? null
|
|
12299
|
-
}
|
|
12407
|
+
if (metadata === void 0) return await this.#fenced(explicitFence, "updateThread", async (q) => {
|
|
12408
|
+
const thread = await this.#writeThread(q, threadTableName, {
|
|
12409
|
+
id,
|
|
12410
|
+
title
|
|
12300
12411
|
});
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
mergedMetadata,
|
|
12317
|
-
nowStr,
|
|
12318
|
-
nowStr,
|
|
12319
|
-
id
|
|
12320
|
-
]);
|
|
12321
|
-
return {
|
|
12322
|
-
id: thread.id,
|
|
12323
|
-
resourceId: thread.resourceId,
|
|
12324
|
-
title: thread.title,
|
|
12325
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
12326
|
-
createdAt: thread.createdAtZ || thread.createdAt,
|
|
12327
|
-
updatedAt: thread.updatedAtZ || thread.updatedAt
|
|
12328
|
-
};
|
|
12412
|
+
if (!thread) throw notFound();
|
|
12413
|
+
return thread;
|
|
12414
|
+
});
|
|
12415
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
12416
|
+
return await this.#db.client.tx(async (t) => {
|
|
12417
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateThread");
|
|
12418
|
+
const existing = await this.#lockThread(t, threadTableName, { id });
|
|
12419
|
+
if (!existing) throw notFound();
|
|
12420
|
+
const thread = await this.#writeThread(t, threadTableName, {
|
|
12421
|
+
id,
|
|
12422
|
+
title,
|
|
12423
|
+
metadata: mergeMetadata(existing.metadata, metadata)
|
|
12424
|
+
});
|
|
12425
|
+
if (!thread) throw notFound();
|
|
12426
|
+
return thread;
|
|
12329
12427
|
});
|
|
12330
12428
|
} catch (error) {
|
|
12331
12429
|
if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
@@ -17381,8 +17479,12 @@ ${onConflict}`,
|
|
|
17381
17479
|
async function insertRows(client, schema, table, records, partitioning, onConflict) {
|
|
17382
17480
|
const insert = buildInsert(schema, table, records, onConflict);
|
|
17383
17481
|
if (!insert) return;
|
|
17482
|
+
await withPartitionRetry(client, schema, table, records, partitioning, () => client.query(insert.text, insert.values));
|
|
17483
|
+
}
|
|
17484
|
+
/** Retry a complete write after creating a missing native partition, outside any failed transaction. */
|
|
17485
|
+
async function withPartitionRetry(client, schema, table, records, partitioning, write) {
|
|
17384
17486
|
try {
|
|
17385
|
-
await
|
|
17487
|
+
return await write();
|
|
17386
17488
|
} catch (error) {
|
|
17387
17489
|
if (!isMissingPartitionError(error) || await resolveMode(client, partitioning) !== "native") throw error;
|
|
17388
17490
|
const days = withinPartitionWindow(recordDays(records, SIGNAL_TIME_COLUMN[table]), partitioning);
|
|
@@ -17395,7 +17497,7 @@ async function insertRows(client, schema, table, records, partitioning, onConfli
|
|
|
17395
17497
|
cause
|
|
17396
17498
|
}), { cause });
|
|
17397
17499
|
});
|
|
17398
|
-
await
|
|
17500
|
+
return await write();
|
|
17399
17501
|
}
|
|
17400
17502
|
}
|
|
17401
17503
|
/** `42501`: the role may not create the child, or does not own the parent it must attach to. */
|
|
@@ -18329,13 +18431,47 @@ function pushFeedbackIdentity(acc, feedbackType, feedbackSource) {
|
|
|
18329
18431
|
acc.conditions.push(`"valueNumber" IS NOT NULL`);
|
|
18330
18432
|
}
|
|
18331
18433
|
async function createFeedback(client, schema, args, partitioning) {
|
|
18332
|
-
|
|
18333
|
-
|
|
18434
|
+
await writeFeedback(client, schema, [args.feedback], args.onConflict, partitioning);
|
|
18435
|
+
}
|
|
18436
|
+
async function replaceFeedback(client, schema, args, partitioning) {
|
|
18437
|
+
await writeFeedback(client, schema, [args.feedback], "replace", partitioning, true);
|
|
18334
18438
|
}
|
|
18335
18439
|
async function batchCreateFeedback(client, schema, args, partitioning) {
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18440
|
+
await writeFeedback(client, schema, args.feedbacks, args.onConflict, partitioning);
|
|
18441
|
+
}
|
|
18442
|
+
/** Serialize feedback writes by ID, including absent IDs and retries with a different timestamp. */
|
|
18443
|
+
async function writeFeedback(client, schema, feedbacks, onConflict, partitioning, preserveIdentity = false) {
|
|
18444
|
+
if (!feedbacks.length) return;
|
|
18445
|
+
const currentFeedback = new Map((onConflict === "ignore" ? [...feedbacks].reverse() : feedbacks).map((feedback) => [feedback.feedbackId, feedback]));
|
|
18446
|
+
const rows = [...currentFeedback.values()].map(feedbackRecordToRow);
|
|
18447
|
+
const ids = [...currentFeedback.keys()].sort();
|
|
18448
|
+
await withPartitionRetry(client, schema, TABLE_FEEDBACK_EVENTS, rows, partitioning, () => client.tx(async (tx) => {
|
|
18449
|
+
await tx.query(`SELECT pg_advisory_xact_lock(hashtextextended(lock_key, 0))
|
|
18450
|
+
FROM unnest($1::text[]) WITH ORDINALITY AS locks(lock_key, position)
|
|
18451
|
+
ORDER BY position`, [ids.map((id) => JSON.stringify([
|
|
18452
|
+
schema,
|
|
18453
|
+
TABLE_FEEDBACK_EVENTS,
|
|
18454
|
+
id
|
|
18455
|
+
]))]);
|
|
18456
|
+
const existing = await tx.any(`SELECT DISTINCT ON ("feedbackId") "feedbackId", "traceId", "timestamp", "organizationId", "resourceId"
|
|
18457
|
+
FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
18458
|
+
WHERE "feedbackId" = ANY($1::text[]) ORDER BY "feedbackId", "cursorId" DESC`, [ids]);
|
|
18459
|
+
const byId = new Map(existing.map((row) => [row.feedbackId, row]));
|
|
18460
|
+
const accepted = rows.filter((row) => {
|
|
18461
|
+
const previous = byId.get(row.feedbackId);
|
|
18462
|
+
if (!previous) return true;
|
|
18463
|
+
if (onConflict === "ignore") return false;
|
|
18464
|
+
if (preserveIdentity && (previous.traceId !== row.traceId || new Date(previous.timestamp).getTime() !== new Date(row.timestamp).getTime() || (previous.organizationId ?? null) !== (row.organizationId ?? null) || (previous.resourceId ?? null) !== (row.resourceId ?? null))) throw new _mastra_core_error.MastraError({
|
|
18465
|
+
id: "OBSERVABILITY_FEEDBACK_IDENTITY_CONFLICT",
|
|
18466
|
+
domain: _mastra_core_error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
18467
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
18468
|
+
text: "Feedback replacement must preserve its trace, timestamp, and tenant"
|
|
18469
|
+
});
|
|
18470
|
+
return true;
|
|
18471
|
+
});
|
|
18472
|
+
const insert = buildInsert(schema, TABLE_FEEDBACK_EVENTS, accepted, FEEDBACK_UPSERT_CLAUSE);
|
|
18473
|
+
if (insert) await tx.query(insert.text, insert.values);
|
|
18474
|
+
}));
|
|
18339
18475
|
}
|
|
18340
18476
|
async function getFeedbackById(client, schema, feedbackId) {
|
|
18341
18477
|
const row = await client.oneOrNone(`SELECT ${FEEDBACK_SELECT_COLUMNS} FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
@@ -22026,6 +22162,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22026
22162
|
"thread-query",
|
|
22027
22163
|
"trace-query-tenant-scope",
|
|
22028
22164
|
"feedback",
|
|
22165
|
+
"feedback-replacement",
|
|
22029
22166
|
"trace-query-context-ids",
|
|
22030
22167
|
...contextFieldFeatures,
|
|
22031
22168
|
...timingFeatures,
|
|
@@ -22054,6 +22191,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22054
22191
|
"thread-query",
|
|
22055
22192
|
"trace-query-tenant-scope",
|
|
22056
22193
|
"feedback",
|
|
22194
|
+
"feedback-replacement",
|
|
22057
22195
|
"trace-query-context-ids",
|
|
22058
22196
|
...contextFieldFeatures,
|
|
22059
22197
|
...timingFeatures,
|
|
@@ -22153,6 +22291,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22153
22291
|
async batchCreateScores(args) {
|
|
22154
22292
|
await this.#run("BATCH_CREATE_SCORES", () => batchCreateScores(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.scores.length });
|
|
22155
22293
|
}
|
|
22294
|
+
async writeFeedbackReplacement(args) {
|
|
22295
|
+
await this.#run("REPLACE_FEEDBACK", () => replaceFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22296
|
+
}
|
|
22156
22297
|
async createFeedback(args) {
|
|
22157
22298
|
await this.#run("CREATE_FEEDBACK", () => createFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22158
22299
|
}
|
|
@@ -22178,7 +22319,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22178
22319
|
return this.#run("GET_SCORE_BY_ID", () => getScoreById(this.#readClient, this.#schema, scoreId), { scoreId });
|
|
22179
22320
|
}
|
|
22180
22321
|
async getFeedbackById(feedbackId) {
|
|
22181
|
-
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#
|
|
22322
|
+
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#client, this.#schema, feedbackId), { feedbackId });
|
|
22182
22323
|
}
|
|
22183
22324
|
async listFeedback(args) {
|
|
22184
22325
|
return this.#run("LIST_FEEDBACK", () => listFeedback(this.#readClient, this.#schema, args));
|
|
@@ -25964,15 +26105,16 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25964
26105
|
this.logger?.warn?.(`Failed to create index ${stateBaseIndexName}:`, error);
|
|
25965
26106
|
}
|
|
25966
26107
|
if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
|
|
26108
|
+
const concurrently = !this.#db.isTableCreatedInThisInit(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
|
|
25967
26109
|
const indexName = workflowSnapshotStatusIndexName(this.#schema);
|
|
25968
26110
|
try {
|
|
25969
|
-
await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema,
|
|
26111
|
+
await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema, concurrently));
|
|
25970
26112
|
} catch (error) {
|
|
25971
26113
|
this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
|
|
25972
26114
|
}
|
|
25973
26115
|
const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
|
|
25974
26116
|
try {
|
|
25975
|
-
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema,
|
|
26117
|
+
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema, concurrently));
|
|
25976
26118
|
} catch (error) {
|
|
25977
26119
|
this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
|
|
25978
26120
|
}
|