@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.js
CHANGED
|
@@ -2696,7 +2696,8 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2696
2696
|
notNullColumns,
|
|
2697
2697
|
indexes,
|
|
2698
2698
|
replicaIdentityIndexes,
|
|
2699
|
-
primaryKeyIndexes
|
|
2699
|
+
primaryKeyIndexes,
|
|
2700
|
+
createdTables: /* @__PURE__ */ new Set()
|
|
2700
2701
|
};
|
|
2701
2702
|
}
|
|
2702
2703
|
// src/storage/db/index.ts
|
|
@@ -2856,26 +2857,30 @@ function generateTableSQL({ tableName, schema, schemaName, compositePrimaryKey,
|
|
|
2856
2857
|
` : ""}
|
|
2857
2858
|
`;
|
|
2858
2859
|
}
|
|
2860
|
+
function indexColumnsSQL(columns, opclass) {
|
|
2861
|
+
return columns.map((col) => {
|
|
2862
|
+
const [colName, ...modifiers] = col.includes(" DESC") || col.includes(" ASC") ? col.split(" ") : [col];
|
|
2863
|
+
if (!colName) throw new Error(`Invalid column specification: ${col}`);
|
|
2864
|
+
return [
|
|
2865
|
+
`"${parseSqlIdentifier(colName, "column name")}"`,
|
|
2866
|
+
opclass,
|
|
2867
|
+
...modifiers
|
|
2868
|
+
].filter(Boolean).join(" ");
|
|
2869
|
+
}).join(", ");
|
|
2870
|
+
}
|
|
2859
2871
|
/**
|
|
2860
2872
|
* Generates a CREATE INDEX SQL statement from index options.
|
|
2861
2873
|
* Used by exportSchemas to produce index DDL without a database connection.
|
|
2862
2874
|
*/
|
|
2863
2875
|
function generateIndexSQL(options, schemaName) {
|
|
2864
|
-
const { name, table, columns, unique = false, where, method = "btree" } = options;
|
|
2876
|
+
const { name, table, columns, unique = false, where, method = "btree", opclass } = options;
|
|
2865
2877
|
const fullTableName = getTableName$6({
|
|
2866
2878
|
indexName: table,
|
|
2867
2879
|
schemaName: getSchemaName$6(schemaName)
|
|
2868
2880
|
});
|
|
2869
2881
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
2870
2882
|
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
2871
|
-
const columnsStr = columns
|
|
2872
|
-
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
2873
|
-
const [colName, ...modifiers] = col.split(" ");
|
|
2874
|
-
if (!colName) throw new Error(`Invalid column specification: ${col}`);
|
|
2875
|
-
return `"${parseSqlIdentifier(colName, "column name")}" ${modifiers.join(" ")}`;
|
|
2876
|
-
}
|
|
2877
|
-
return `"${parseSqlIdentifier(col, "column name")}"`;
|
|
2878
|
-
}).join(", ");
|
|
2883
|
+
const columnsStr = indexColumnsSQL(columns, opclass);
|
|
2879
2884
|
const whereStr = where ? ` WHERE ${where}` : "";
|
|
2880
2885
|
return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${`"${parseSqlIdentifier(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${whereStr};`;
|
|
2881
2886
|
}
|
|
@@ -3386,6 +3391,7 @@ var PgDB = class extends MastraBase {
|
|
|
3386
3391
|
if (this.schemaName) await this.setupSchema();
|
|
3387
3392
|
const snapshot = this.schemaSnapshot;
|
|
3388
3393
|
if (!snapshot || !this.snapshotShowsTableConverged(snapshot, tableName)) {
|
|
3394
|
+
const isNewTable = snapshot ? !snapshot.tables.has(tableName) : false;
|
|
3389
3395
|
const sql = generateTableSQL({
|
|
3390
3396
|
tableName,
|
|
3391
3397
|
schema,
|
|
@@ -3399,6 +3405,7 @@ var PgDB = class extends MastraBase {
|
|
|
3399
3405
|
}
|
|
3400
3406
|
if (snapshot) {
|
|
3401
3407
|
snapshot.tables.add(tableName);
|
|
3408
|
+
if (isNewTable) snapshot.createdTables.add(tableName);
|
|
3402
3409
|
const created = this.snapshotColumns(snapshot, tableName);
|
|
3403
3410
|
for (const [name, def] of Object.entries(schema)) {
|
|
3404
3411
|
const parsedName = parseSqlIdentifier(name, "column name");
|
|
@@ -3867,18 +3874,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3867
3874
|
const snapshot = this.schemaSnapshot;
|
|
3868
3875
|
if (snapshot?.indexes.has(name)) return;
|
|
3869
3876
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
3870
|
-
const concurrentStr = concurrent ? "CONCURRENTLY " : "";
|
|
3877
|
+
const concurrentStr = concurrent && !this.isTableCreatedInThisInit(table) ? "CONCURRENTLY " : "";
|
|
3871
3878
|
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
3872
|
-
const columnsStr = columns
|
|
3873
|
-
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
3874
|
-
const [colName, ...modifiers] = col.split(" ");
|
|
3875
|
-
if (!colName) throw new Error(`Invalid column specification: ${col}`);
|
|
3876
|
-
const quotedCol = `"${parseSqlIdentifier(colName, "column name")}" ${modifiers.join(" ")}`;
|
|
3877
|
-
return opclass ? `${quotedCol} ${opclass}` : quotedCol;
|
|
3878
|
-
}
|
|
3879
|
-
const quotedCol = `"${parseSqlIdentifier(col, "column name")}"`;
|
|
3880
|
-
return opclass ? `${quotedCol} ${opclass}` : quotedCol;
|
|
3881
|
-
}).join(", ");
|
|
3879
|
+
const columnsStr = indexColumnsSQL(columns, opclass);
|
|
3882
3880
|
const whereStr = where ? ` WHERE ${where}` : "";
|
|
3883
3881
|
const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
|
|
3884
3882
|
let withStr = "";
|
|
@@ -3911,6 +3909,13 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3911
3909
|
if (snapshot?.indexes.has(indexName)) return;
|
|
3912
3910
|
if (await buildIndex(this.client, this.schemaName || "public", indexName, () => this.timedIndexBuild(indexName, sql), this.logger)) snapshot?.indexes.add(indexName);
|
|
3913
3911
|
}
|
|
3912
|
+
/**
|
|
3913
|
+
* Whether `tableName` was absent when this init's snapshot was read and this
|
|
3914
|
+
* init created it, so its indexes can be built without CONCURRENTLY.
|
|
3915
|
+
*/
|
|
3916
|
+
isTableCreatedInThisInit(tableName) {
|
|
3917
|
+
return this.schemaSnapshot?.createdTables.has(tableName) ?? false;
|
|
3918
|
+
}
|
|
3914
3919
|
async timedIndexBuild(indexName, sql) {
|
|
3915
3920
|
const startedAt = Date.now();
|
|
3916
3921
|
this.logger.info(`Creating index ${indexName}`);
|
|
@@ -11650,6 +11655,13 @@ function dedupeMessagesForSave(messages) {
|
|
|
11650
11655
|
}
|
|
11651
11656
|
return Array.from(deduped.values());
|
|
11652
11657
|
}
|
|
11658
|
+
/** Shallow merge; keys patched to `undefined` are dropped when serialized. Non-object metadata is replaced. */
|
|
11659
|
+
function mergeMetadata(existing, patch) {
|
|
11660
|
+
return {
|
|
11661
|
+
...existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {},
|
|
11662
|
+
...patch
|
|
11663
|
+
};
|
|
11664
|
+
}
|
|
11653
11665
|
var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
11654
11666
|
supportsPartialThreadUpdate = true;
|
|
11655
11667
|
supportsObservationalMemory = true;
|
|
@@ -12216,7 +12228,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12216
12228
|
const createdAt = toUtcISOString(thread.createdAt);
|
|
12217
12229
|
const updatedAt = toUtcISOString(thread.updatedAt);
|
|
12218
12230
|
const metadataJson = thread.metadata ? toPgJson(thread.metadata) : null;
|
|
12219
|
-
await this.#fenced(fence, "saveThread", (q) => q.
|
|
12231
|
+
const saved = await this.#fenced(fence, "saveThread", (q) => q.oneOrNone(`INSERT INTO ${tableName} AS t (
|
|
12220
12232
|
id,
|
|
12221
12233
|
"resourceId",
|
|
12222
12234
|
title,
|
|
@@ -12227,13 +12239,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12227
12239
|
"updatedAtZ"
|
|
12228
12240
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
12229
12241
|
ON CONFLICT (id) DO UPDATE SET
|
|
12230
|
-
"resourceId" = EXCLUDED."resourceId",
|
|
12231
12242
|
title = EXCLUDED.title,
|
|
12232
12243
|
metadata = EXCLUDED.metadata,
|
|
12233
|
-
"createdAt" = EXCLUDED."createdAt",
|
|
12234
|
-
"createdAtZ" = EXCLUDED."createdAtZ",
|
|
12235
12244
|
"updatedAt" = EXCLUDED."updatedAt",
|
|
12236
|
-
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12245
|
+
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12246
|
+
WHERE t."resourceId" = EXCLUDED."resourceId"
|
|
12247
|
+
RETURNING *`, [
|
|
12237
12248
|
thread.id,
|
|
12238
12249
|
thread.resourceId,
|
|
12239
12250
|
thread.title,
|
|
@@ -12243,12 +12254,26 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12243
12254
|
updatedAt,
|
|
12244
12255
|
updatedAt
|
|
12245
12256
|
]));
|
|
12257
|
+
if (!saved) throw new MastraError({
|
|
12258
|
+
id: createStorageErrorId("PG", "SAVE_THREAD", "RESOURCE_ID_MISMATCH"),
|
|
12259
|
+
domain: ErrorDomain.STORAGE,
|
|
12260
|
+
category: ErrorCategory.USER,
|
|
12261
|
+
text: `Thread ${thread.id} already exists and belongs to a different resource. Use updateThreadResourceId to move a thread and its messages to another resource.`,
|
|
12262
|
+
details: {
|
|
12263
|
+
threadId: thread.id,
|
|
12264
|
+
resourceId: thread.resourceId
|
|
12265
|
+
}
|
|
12266
|
+
});
|
|
12246
12267
|
return {
|
|
12247
|
-
|
|
12248
|
-
|
|
12268
|
+
id: saved.id,
|
|
12269
|
+
resourceId: saved.resourceId,
|
|
12270
|
+
title: saved.title,
|
|
12271
|
+
metadata: saved.metadata == null ? void 0 : typeof saved.metadata === "string" ? JSON.parse(saved.metadata) : saved.metadata,
|
|
12272
|
+
createdAt: saved.createdAtZ || saved.createdAt,
|
|
12273
|
+
updatedAt: saved.updatedAtZ || saved.updatedAt
|
|
12249
12274
|
};
|
|
12250
12275
|
} catch (error) {
|
|
12251
|
-
if (isRunFenceConflictError(error)) throw error;
|
|
12276
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
12252
12277
|
throw new MastraError({
|
|
12253
12278
|
id: createStorageErrorId("PG", "SAVE_THREAD", "FAILED"),
|
|
12254
12279
|
domain: ErrorDomain.STORAGE,
|
|
@@ -12257,52 +12282,125 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12257
12282
|
}, error);
|
|
12258
12283
|
}
|
|
12259
12284
|
}
|
|
12260
|
-
|
|
12285
|
+
#toThread(row) {
|
|
12286
|
+
return {
|
|
12287
|
+
id: row.id,
|
|
12288
|
+
resourceId: row.resourceId,
|
|
12289
|
+
title: row.title,
|
|
12290
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
|
|
12291
|
+
createdAt: row.createdAtZ || row.createdAt,
|
|
12292
|
+
updatedAt: row.updatedAtZ || row.updatedAt
|
|
12293
|
+
};
|
|
12294
|
+
}
|
|
12295
|
+
async #lockThread(t, tableName, { id, resourceId }) {
|
|
12296
|
+
const params = [id];
|
|
12297
|
+
let query = `SELECT * FROM ${tableName} WHERE id = $1`;
|
|
12298
|
+
if (resourceId !== void 0) {
|
|
12299
|
+
query += ` AND "resourceId" = $2`;
|
|
12300
|
+
params.push(resourceId);
|
|
12301
|
+
}
|
|
12302
|
+
const row = await t.oneOrNone(`${query} FOR UPDATE`, params);
|
|
12303
|
+
return row ? this.#toThread(row) : null;
|
|
12304
|
+
}
|
|
12305
|
+
/**
|
|
12306
|
+
* Writes `title` (when given) and the full `metadata` value (when given).
|
|
12307
|
+
* Callers that merge metadata must hold the row lock from `#lockThread`.
|
|
12308
|
+
*/
|
|
12309
|
+
async #writeThread(q, tableName, { id, title, metadata }) {
|
|
12310
|
+
const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
|
|
12311
|
+
const params = [
|
|
12312
|
+
title ?? null,
|
|
12313
|
+
nowStr,
|
|
12314
|
+
nowStr,
|
|
12315
|
+
id
|
|
12316
|
+
];
|
|
12317
|
+
let metadataSet = "";
|
|
12318
|
+
if (metadata !== void 0) {
|
|
12319
|
+
params.push(toPgJson(metadata));
|
|
12320
|
+
metadataSet = "metadata = $5,";
|
|
12321
|
+
}
|
|
12322
|
+
const row = await q.oneOrNone(`UPDATE ${tableName}
|
|
12323
|
+
SET
|
|
12324
|
+
title = COALESCE($1, title),
|
|
12325
|
+
${metadataSet}
|
|
12326
|
+
"updatedAt" = $2,
|
|
12327
|
+
"updatedAtZ" = $3
|
|
12328
|
+
WHERE id = $4
|
|
12329
|
+
RETURNING *`, params);
|
|
12330
|
+
return row ? this.#toThread(row) : null;
|
|
12331
|
+
}
|
|
12332
|
+
/**
|
|
12333
|
+
* Cross-process atomic metadata read-modify-write: the thread row stays locked
|
|
12334
|
+
* (`SELECT ... FOR UPDATE`) from the read through the write, so concurrent
|
|
12335
|
+
* writers from any process serialize instead of overwriting each other.
|
|
12336
|
+
*/
|
|
12337
|
+
async updateThreadMetadata({ id, resourceId, update, fence: explicitFence }) {
|
|
12338
|
+
const fence = resolveRunFence(this, explicitFence);
|
|
12339
|
+
const threadsTable = getTableName$3({
|
|
12340
|
+
indexName: TABLE_THREADS,
|
|
12341
|
+
schemaName: getSchemaName$3(this.#schema)
|
|
12342
|
+
});
|
|
12343
|
+
try {
|
|
12344
|
+
return await this.#db.client.tx(async (t) => {
|
|
12345
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateThreadMetadata");
|
|
12346
|
+
const thread = await this.#lockThread(t, threadsTable, {
|
|
12347
|
+
id,
|
|
12348
|
+
resourceId
|
|
12349
|
+
});
|
|
12350
|
+
if (!thread) return null;
|
|
12351
|
+
const metadata = update(thread);
|
|
12352
|
+
if (!metadata) return thread;
|
|
12353
|
+
return this.#writeThread(t, threadsTable, {
|
|
12354
|
+
id,
|
|
12355
|
+
metadata: mergeMetadata(thread.metadata, metadata)
|
|
12356
|
+
});
|
|
12357
|
+
});
|
|
12358
|
+
} catch (error) {
|
|
12359
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
12360
|
+
throw new MastraError({
|
|
12361
|
+
id: createStorageErrorId("PG", "UPDATE_THREAD_METADATA", "FAILED"),
|
|
12362
|
+
domain: ErrorDomain.STORAGE,
|
|
12363
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
12364
|
+
details: { threadId: id }
|
|
12365
|
+
}, error);
|
|
12366
|
+
}
|
|
12367
|
+
}
|
|
12368
|
+
async updateThread({ id, title, metadata, fence: explicitFence }) {
|
|
12261
12369
|
const threadTableName = getTableName$3({
|
|
12262
12370
|
indexName: TABLE_THREADS,
|
|
12263
12371
|
schemaName: getSchemaName$3(this.#schema)
|
|
12264
12372
|
});
|
|
12373
|
+
const notFound = () => new MastraError({
|
|
12374
|
+
id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
|
|
12375
|
+
domain: ErrorDomain.STORAGE,
|
|
12376
|
+
category: ErrorCategory.USER,
|
|
12377
|
+
text: `Thread ${id} not found`,
|
|
12378
|
+
details: {
|
|
12379
|
+
threadId: id,
|
|
12380
|
+
title: title ?? null
|
|
12381
|
+
}
|
|
12382
|
+
});
|
|
12265
12383
|
try {
|
|
12266
|
-
return await this.#fenced(
|
|
12267
|
-
const
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
domain: ErrorDomain.STORAGE,
|
|
12271
|
-
category: ErrorCategory.USER,
|
|
12272
|
-
text: `Thread ${id} not found`,
|
|
12273
|
-
details: {
|
|
12274
|
-
threadId: id,
|
|
12275
|
-
title: title ?? null
|
|
12276
|
-
}
|
|
12384
|
+
if (metadata === void 0) return await this.#fenced(explicitFence, "updateThread", async (q) => {
|
|
12385
|
+
const thread = await this.#writeThread(q, threadTableName, {
|
|
12386
|
+
id,
|
|
12387
|
+
title
|
|
12277
12388
|
});
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
mergedMetadata,
|
|
12294
|
-
nowStr,
|
|
12295
|
-
nowStr,
|
|
12296
|
-
id
|
|
12297
|
-
]);
|
|
12298
|
-
return {
|
|
12299
|
-
id: thread.id,
|
|
12300
|
-
resourceId: thread.resourceId,
|
|
12301
|
-
title: thread.title,
|
|
12302
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
12303
|
-
createdAt: thread.createdAtZ || thread.createdAt,
|
|
12304
|
-
updatedAt: thread.updatedAtZ || thread.updatedAt
|
|
12305
|
-
};
|
|
12389
|
+
if (!thread) throw notFound();
|
|
12390
|
+
return thread;
|
|
12391
|
+
});
|
|
12392
|
+
const fence = resolveRunFence(this, explicitFence);
|
|
12393
|
+
return await this.#db.client.tx(async (t) => {
|
|
12394
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateThread");
|
|
12395
|
+
const existing = await this.#lockThread(t, threadTableName, { id });
|
|
12396
|
+
if (!existing) throw notFound();
|
|
12397
|
+
const thread = await this.#writeThread(t, threadTableName, {
|
|
12398
|
+
id,
|
|
12399
|
+
title,
|
|
12400
|
+
metadata: mergeMetadata(existing.metadata, metadata)
|
|
12401
|
+
});
|
|
12402
|
+
if (!thread) throw notFound();
|
|
12403
|
+
return thread;
|
|
12306
12404
|
});
|
|
12307
12405
|
} catch (error) {
|
|
12308
12406
|
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
@@ -17358,8 +17456,12 @@ ${onConflict}`,
|
|
|
17358
17456
|
async function insertRows(client, schema, table, records, partitioning, onConflict) {
|
|
17359
17457
|
const insert = buildInsert(schema, table, records, onConflict);
|
|
17360
17458
|
if (!insert) return;
|
|
17459
|
+
await withPartitionRetry(client, schema, table, records, partitioning, () => client.query(insert.text, insert.values));
|
|
17460
|
+
}
|
|
17461
|
+
/** Retry a complete write after creating a missing native partition, outside any failed transaction. */
|
|
17462
|
+
async function withPartitionRetry(client, schema, table, records, partitioning, write) {
|
|
17361
17463
|
try {
|
|
17362
|
-
await
|
|
17464
|
+
return await write();
|
|
17363
17465
|
} catch (error) {
|
|
17364
17466
|
if (!isMissingPartitionError(error) || await resolveMode(client, partitioning) !== "native") throw error;
|
|
17365
17467
|
const days = withinPartitionWindow(recordDays(records, SIGNAL_TIME_COLUMN[table]), partitioning);
|
|
@@ -17372,7 +17474,7 @@ async function insertRows(client, schema, table, records, partitioning, onConfli
|
|
|
17372
17474
|
cause
|
|
17373
17475
|
}), { cause });
|
|
17374
17476
|
});
|
|
17375
|
-
await
|
|
17477
|
+
return await write();
|
|
17376
17478
|
}
|
|
17377
17479
|
}
|
|
17378
17480
|
/** `42501`: the role may not create the child, or does not own the parent it must attach to. */
|
|
@@ -18306,13 +18408,47 @@ function pushFeedbackIdentity(acc, feedbackType, feedbackSource) {
|
|
|
18306
18408
|
acc.conditions.push(`"valueNumber" IS NOT NULL`);
|
|
18307
18409
|
}
|
|
18308
18410
|
async function createFeedback(client, schema, args, partitioning) {
|
|
18309
|
-
|
|
18310
|
-
|
|
18411
|
+
await writeFeedback(client, schema, [args.feedback], args.onConflict, partitioning);
|
|
18412
|
+
}
|
|
18413
|
+
async function replaceFeedback(client, schema, args, partitioning) {
|
|
18414
|
+
await writeFeedback(client, schema, [args.feedback], "replace", partitioning, true);
|
|
18311
18415
|
}
|
|
18312
18416
|
async function batchCreateFeedback(client, schema, args, partitioning) {
|
|
18313
|
-
|
|
18314
|
-
|
|
18315
|
-
|
|
18417
|
+
await writeFeedback(client, schema, args.feedbacks, args.onConflict, partitioning);
|
|
18418
|
+
}
|
|
18419
|
+
/** Serialize feedback writes by ID, including absent IDs and retries with a different timestamp. */
|
|
18420
|
+
async function writeFeedback(client, schema, feedbacks, onConflict, partitioning, preserveIdentity = false) {
|
|
18421
|
+
if (!feedbacks.length) return;
|
|
18422
|
+
const currentFeedback = new Map((onConflict === "ignore" ? [...feedbacks].reverse() : feedbacks).map((feedback) => [feedback.feedbackId, feedback]));
|
|
18423
|
+
const rows = [...currentFeedback.values()].map(feedbackRecordToRow);
|
|
18424
|
+
const ids = [...currentFeedback.keys()].sort();
|
|
18425
|
+
await withPartitionRetry(client, schema, TABLE_FEEDBACK_EVENTS, rows, partitioning, () => client.tx(async (tx) => {
|
|
18426
|
+
await tx.query(`SELECT pg_advisory_xact_lock(hashtextextended(lock_key, 0))
|
|
18427
|
+
FROM unnest($1::text[]) WITH ORDINALITY AS locks(lock_key, position)
|
|
18428
|
+
ORDER BY position`, [ids.map((id) => JSON.stringify([
|
|
18429
|
+
schema,
|
|
18430
|
+
TABLE_FEEDBACK_EVENTS,
|
|
18431
|
+
id
|
|
18432
|
+
]))]);
|
|
18433
|
+
const existing = await tx.any(`SELECT DISTINCT ON ("feedbackId") "feedbackId", "traceId", "timestamp", "organizationId", "resourceId"
|
|
18434
|
+
FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
18435
|
+
WHERE "feedbackId" = ANY($1::text[]) ORDER BY "feedbackId", "cursorId" DESC`, [ids]);
|
|
18436
|
+
const byId = new Map(existing.map((row) => [row.feedbackId, row]));
|
|
18437
|
+
const accepted = rows.filter((row) => {
|
|
18438
|
+
const previous = byId.get(row.feedbackId);
|
|
18439
|
+
if (!previous) return true;
|
|
18440
|
+
if (onConflict === "ignore") return false;
|
|
18441
|
+
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 MastraError({
|
|
18442
|
+
id: "OBSERVABILITY_FEEDBACK_IDENTITY_CONFLICT",
|
|
18443
|
+
domain: ErrorDomain.MASTRA_OBSERVABILITY,
|
|
18444
|
+
category: ErrorCategory.USER,
|
|
18445
|
+
text: "Feedback replacement must preserve its trace, timestamp, and tenant"
|
|
18446
|
+
});
|
|
18447
|
+
return true;
|
|
18448
|
+
});
|
|
18449
|
+
const insert = buildInsert(schema, TABLE_FEEDBACK_EVENTS, accepted, FEEDBACK_UPSERT_CLAUSE);
|
|
18450
|
+
if (insert) await tx.query(insert.text, insert.values);
|
|
18451
|
+
}));
|
|
18316
18452
|
}
|
|
18317
18453
|
async function getFeedbackById(client, schema, feedbackId) {
|
|
18318
18454
|
const row = await client.oneOrNone(`SELECT ${FEEDBACK_SELECT_COLUMNS} FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
@@ -22003,6 +22139,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22003
22139
|
"thread-query",
|
|
22004
22140
|
"trace-query-tenant-scope",
|
|
22005
22141
|
"feedback",
|
|
22142
|
+
"feedback-replacement",
|
|
22006
22143
|
"trace-query-context-ids",
|
|
22007
22144
|
...contextFieldFeatures,
|
|
22008
22145
|
...timingFeatures,
|
|
@@ -22031,6 +22168,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22031
22168
|
"thread-query",
|
|
22032
22169
|
"trace-query-tenant-scope",
|
|
22033
22170
|
"feedback",
|
|
22171
|
+
"feedback-replacement",
|
|
22034
22172
|
"trace-query-context-ids",
|
|
22035
22173
|
...contextFieldFeatures,
|
|
22036
22174
|
...timingFeatures,
|
|
@@ -22130,6 +22268,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22130
22268
|
async batchCreateScores(args) {
|
|
22131
22269
|
await this.#run("BATCH_CREATE_SCORES", () => batchCreateScores(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.scores.length });
|
|
22132
22270
|
}
|
|
22271
|
+
async writeFeedbackReplacement(args) {
|
|
22272
|
+
await this.#run("REPLACE_FEEDBACK", () => replaceFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22273
|
+
}
|
|
22133
22274
|
async createFeedback(args) {
|
|
22134
22275
|
await this.#run("CREATE_FEEDBACK", () => createFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22135
22276
|
}
|
|
@@ -22155,7 +22296,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22155
22296
|
return this.#run("GET_SCORE_BY_ID", () => getScoreById(this.#readClient, this.#schema, scoreId), { scoreId });
|
|
22156
22297
|
}
|
|
22157
22298
|
async getFeedbackById(feedbackId) {
|
|
22158
|
-
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#
|
|
22299
|
+
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#client, this.#schema, feedbackId), { feedbackId });
|
|
22159
22300
|
}
|
|
22160
22301
|
async listFeedback(args) {
|
|
22161
22302
|
return this.#run("LIST_FEEDBACK", () => listFeedback(this.#readClient, this.#schema, args));
|
|
@@ -25941,15 +26082,16 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25941
26082
|
this.logger?.warn?.(`Failed to create index ${stateBaseIndexName}:`, error);
|
|
25942
26083
|
}
|
|
25943
26084
|
if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
|
|
26085
|
+
const concurrently = !this.#db.isTableCreatedInThisInit(TABLE_WORKFLOW_SNAPSHOT);
|
|
25944
26086
|
const indexName = workflowSnapshotStatusIndexName(this.#schema);
|
|
25945
26087
|
try {
|
|
25946
|
-
await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema,
|
|
26088
|
+
await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema, concurrently));
|
|
25947
26089
|
} catch (error) {
|
|
25948
26090
|
this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
|
|
25949
26091
|
}
|
|
25950
26092
|
const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
|
|
25951
26093
|
try {
|
|
25952
|
-
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema,
|
|
26094
|
+
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema, concurrently));
|
|
25953
26095
|
} catch (error) {
|
|
25954
26096
|
this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
|
|
25955
26097
|
}
|