@mastra/pg 1.36.0-alpha.2 → 1.36.0-alpha.4
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 +32 -32
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +2 -0
- package/dist/index.cjs +256 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +256 -96
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.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/schedules/index.d.ts.map +1 -1
- package/dist/vector/sql-builder.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -250,31 +250,29 @@ function buildElemMatchConditions(value, paramIndex) {
|
|
|
250
250
|
if (typeof value !== "object" || Array.isArray(value)) throw new Error("$elemMatch requires an object with conditions");
|
|
251
251
|
const conditions = [];
|
|
252
252
|
const values = [];
|
|
253
|
+
function buildFieldCondition(field, operator, operatorValue) {
|
|
254
|
+
if (operator === "$not") {
|
|
255
|
+
const nested = Object.entries(operatorValue);
|
|
256
|
+
if (nested.length === 0) throw new Error("$not requires at least one operator");
|
|
257
|
+
return `NOT (COALESCE(${nested.map(([nestedOp, nestedValue]) => buildFieldCondition(field, nestedOp, nestedValue)).join(" AND ")}, FALSE))`;
|
|
258
|
+
}
|
|
259
|
+
const operatorFn = FILTER_OPERATORS[operator];
|
|
260
|
+
if (!operatorFn) throw new Error(`Invalid operator: ${operator}`);
|
|
261
|
+
const scalarElement = field.startsWith("$");
|
|
262
|
+
const result = operatorFn(scalarElement ? "" : field, paramIndex + values.length, operatorValue);
|
|
263
|
+
const sql = result.sql.replaceAll("metadata->>", "elem->>").replaceAll("metadata->", "elem->").replaceAll("metadata#>>", "elem#>>").replaceAll("metadata#>", "elem#>").replaceAll("metadata ?", "elem ?");
|
|
264
|
+
const elementSql = scalarElement ? sql.replaceAll("elem->>''", "elem #>> '{}'").replaceAll("elem->''", "elem") : sql;
|
|
265
|
+
if (result.needsValue) {
|
|
266
|
+
const transformedValue = result.transformValue ? result.transformValue() : operatorValue;
|
|
267
|
+
if (operator === "$elemMatch") values.push(...transformedValue);
|
|
268
|
+
else values.push(transformedValue);
|
|
269
|
+
}
|
|
270
|
+
return elementSql;
|
|
271
|
+
}
|
|
253
272
|
Object.entries(value).forEach(([field, val]) => {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
let paramValue;
|
|
258
|
-
if (field.startsWith("$")) {
|
|
259
|
-
paramOperator = field;
|
|
260
|
-
paramKey = "";
|
|
261
|
-
paramValue = val;
|
|
262
|
-
} else if (typeof val === "object" && !Array.isArray(val)) {
|
|
263
|
-
const [op, opValue] = Object.entries(val || {})[0] || [];
|
|
264
|
-
paramOperator = op;
|
|
265
|
-
paramKey = field;
|
|
266
|
-
paramValue = opValue;
|
|
267
|
-
} else {
|
|
268
|
-
paramOperator = "$eq";
|
|
269
|
-
paramKey = field;
|
|
270
|
-
paramValue = val;
|
|
271
|
-
}
|
|
272
|
-
const operatorFn = FILTER_OPERATORS[paramOperator];
|
|
273
|
-
if (!operatorFn) throw new Error(`Invalid operator: ${paramOperator}`);
|
|
274
|
-
const result = operatorFn(paramKey, nextParamIndex, paramValue);
|
|
275
|
-
const sql = result.sql.replaceAll("metadata->>", "elem->>").replaceAll("metadata->", "elem->").replaceAll("metadata#>>", "elem#>>").replaceAll("metadata#>", "elem#>");
|
|
276
|
-
conditions.push(sql);
|
|
277
|
-
if (result.needsValue) values.push(paramValue);
|
|
273
|
+
const operators = field.startsWith("$") ? [[field, val]] : typeof val === "object" && val !== null && !Array.isArray(val) ? Object.entries(val) : [["$eq", val]];
|
|
274
|
+
if (operators.length === 0) throw new Error(`$elemMatch field ${field} requires at least one operator`);
|
|
275
|
+
for (const [operator, operatorValue] of operators) conditions.push(buildFieldCondition(field, operator, operatorValue));
|
|
278
276
|
});
|
|
279
277
|
return {
|
|
280
278
|
sql: conditions.join(" AND "),
|
|
@@ -3405,12 +3403,16 @@ var PgDB = class extends MastraBase {
|
|
|
3405
3403
|
}
|
|
3406
3404
|
if (snapshot) {
|
|
3407
3405
|
snapshot.tables.add(tableName);
|
|
3408
|
-
if (isNewTable)
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
created.
|
|
3413
|
-
|
|
3406
|
+
if (isNewTable) {
|
|
3407
|
+
snapshot.createdTables.add(tableName);
|
|
3408
|
+
snapshot.columns.set(tableName, new Set(await this.getTableColumns(tableName)));
|
|
3409
|
+
} else {
|
|
3410
|
+
const created = this.snapshotColumns(snapshot, tableName);
|
|
3411
|
+
for (const [name, def] of Object.entries(schema)) {
|
|
3412
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
3413
|
+
created.add(parsedName);
|
|
3414
|
+
if (def.type === "timestamp") created.add(`${parsedName}Z`);
|
|
3415
|
+
}
|
|
3414
3416
|
}
|
|
3415
3417
|
}
|
|
3416
3418
|
}
|
|
@@ -11655,6 +11657,13 @@ function dedupeMessagesForSave(messages) {
|
|
|
11655
11657
|
}
|
|
11656
11658
|
return Array.from(deduped.values());
|
|
11657
11659
|
}
|
|
11660
|
+
/** Shallow merge; keys patched to `undefined` are dropped when serialized. Non-object metadata is replaced. */
|
|
11661
|
+
function mergeMetadata(existing, patch) {
|
|
11662
|
+
return {
|
|
11663
|
+
...existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {},
|
|
11664
|
+
...patch
|
|
11665
|
+
};
|
|
11666
|
+
}
|
|
11658
11667
|
var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
11659
11668
|
supportsPartialThreadUpdate = true;
|
|
11660
11669
|
supportsObservationalMemory = true;
|
|
@@ -11792,15 +11801,25 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
11792
11801
|
* @param schemaPrefix - Prefix for index names (e.g. "my_schema_" or "")
|
|
11793
11802
|
*/
|
|
11794
11803
|
static getDefaultIndexDefs(schemaPrefix) {
|
|
11795
|
-
return [
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
+
return [
|
|
11805
|
+
{
|
|
11806
|
+
name: `${schemaPrefix}mastra_threads_resourceid_createdat_idx`,
|
|
11807
|
+
table: TABLE_THREADS,
|
|
11808
|
+
columns: ["resourceId", "createdAt DESC"]
|
|
11809
|
+
},
|
|
11810
|
+
{
|
|
11811
|
+
name: `${schemaPrefix}mastra_threads_metadata_gin_idx`,
|
|
11812
|
+
table: TABLE_THREADS,
|
|
11813
|
+
columns: ["metadata"],
|
|
11814
|
+
method: "gin",
|
|
11815
|
+
opclass: "jsonb_path_ops"
|
|
11816
|
+
},
|
|
11817
|
+
{
|
|
11818
|
+
name: `${schemaPrefix}mastra_messages_thread_id_createdat_idx`,
|
|
11819
|
+
table: TABLE_MESSAGES,
|
|
11820
|
+
columns: ["thread_id", "createdAt DESC"]
|
|
11821
|
+
}
|
|
11822
|
+
];
|
|
11804
11823
|
}
|
|
11805
11824
|
/**
|
|
11806
11825
|
* Returns all DDL statements for this domain: tables (threads, messages, resources, OM), indexes.
|
|
@@ -12221,7 +12240,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12221
12240
|
const createdAt = toUtcISOString(thread.createdAt);
|
|
12222
12241
|
const updatedAt = toUtcISOString(thread.updatedAt);
|
|
12223
12242
|
const metadataJson = thread.metadata ? toPgJson(thread.metadata) : null;
|
|
12224
|
-
await this.#fenced(fence, "saveThread", (q) => q.
|
|
12243
|
+
const saved = await this.#fenced(fence, "saveThread", (q) => q.oneOrNone(`INSERT INTO ${tableName} AS t (
|
|
12225
12244
|
id,
|
|
12226
12245
|
"resourceId",
|
|
12227
12246
|
title,
|
|
@@ -12232,13 +12251,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12232
12251
|
"updatedAtZ"
|
|
12233
12252
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
12234
12253
|
ON CONFLICT (id) DO UPDATE SET
|
|
12235
|
-
"resourceId" = EXCLUDED."resourceId",
|
|
12236
12254
|
title = EXCLUDED.title,
|
|
12237
12255
|
metadata = EXCLUDED.metadata,
|
|
12238
|
-
"createdAt" = EXCLUDED."createdAt",
|
|
12239
|
-
"createdAtZ" = EXCLUDED."createdAtZ",
|
|
12240
12256
|
"updatedAt" = EXCLUDED."updatedAt",
|
|
12241
|
-
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12257
|
+
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12258
|
+
WHERE t."resourceId" = EXCLUDED."resourceId"
|
|
12259
|
+
RETURNING *`, [
|
|
12242
12260
|
thread.id,
|
|
12243
12261
|
thread.resourceId,
|
|
12244
12262
|
thread.title,
|
|
@@ -12248,12 +12266,26 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12248
12266
|
updatedAt,
|
|
12249
12267
|
updatedAt
|
|
12250
12268
|
]));
|
|
12269
|
+
if (!saved) throw new MastraError({
|
|
12270
|
+
id: createStorageErrorId("PG", "SAVE_THREAD", "RESOURCE_ID_MISMATCH"),
|
|
12271
|
+
domain: ErrorDomain.STORAGE,
|
|
12272
|
+
category: ErrorCategory.USER,
|
|
12273
|
+
text: `Thread ${thread.id} already exists and belongs to a different resource. Use updateThreadResourceId to move a thread and its messages to another resource.`,
|
|
12274
|
+
details: {
|
|
12275
|
+
threadId: thread.id,
|
|
12276
|
+
resourceId: thread.resourceId
|
|
12277
|
+
}
|
|
12278
|
+
});
|
|
12251
12279
|
return {
|
|
12252
|
-
|
|
12253
|
-
|
|
12280
|
+
id: saved.id,
|
|
12281
|
+
resourceId: saved.resourceId,
|
|
12282
|
+
title: saved.title,
|
|
12283
|
+
metadata: saved.metadata == null ? void 0 : typeof saved.metadata === "string" ? JSON.parse(saved.metadata) : saved.metadata,
|
|
12284
|
+
createdAt: saved.createdAtZ || saved.createdAt,
|
|
12285
|
+
updatedAt: saved.updatedAtZ || saved.updatedAt
|
|
12254
12286
|
};
|
|
12255
12287
|
} catch (error) {
|
|
12256
|
-
if (isRunFenceConflictError(error)) throw error;
|
|
12288
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
12257
12289
|
throw new MastraError({
|
|
12258
12290
|
id: createStorageErrorId("PG", "SAVE_THREAD", "FAILED"),
|
|
12259
12291
|
domain: ErrorDomain.STORAGE,
|
|
@@ -12262,52 +12294,125 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12262
12294
|
}, error);
|
|
12263
12295
|
}
|
|
12264
12296
|
}
|
|
12265
|
-
|
|
12297
|
+
#toThread(row) {
|
|
12298
|
+
return {
|
|
12299
|
+
id: row.id,
|
|
12300
|
+
resourceId: row.resourceId,
|
|
12301
|
+
title: row.title,
|
|
12302
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
|
|
12303
|
+
createdAt: row.createdAtZ || row.createdAt,
|
|
12304
|
+
updatedAt: row.updatedAtZ || row.updatedAt
|
|
12305
|
+
};
|
|
12306
|
+
}
|
|
12307
|
+
async #lockThread(t, tableName, { id, resourceId }) {
|
|
12308
|
+
const params = [id];
|
|
12309
|
+
let query = `SELECT * FROM ${tableName} WHERE id = $1`;
|
|
12310
|
+
if (resourceId !== void 0) {
|
|
12311
|
+
query += ` AND "resourceId" = $2`;
|
|
12312
|
+
params.push(resourceId);
|
|
12313
|
+
}
|
|
12314
|
+
const row = await t.oneOrNone(`${query} FOR UPDATE`, params);
|
|
12315
|
+
return row ? this.#toThread(row) : null;
|
|
12316
|
+
}
|
|
12317
|
+
/**
|
|
12318
|
+
* Writes `title` (when given) and the full `metadata` value (when given).
|
|
12319
|
+
* Callers that merge metadata must hold the row lock from `#lockThread`.
|
|
12320
|
+
*/
|
|
12321
|
+
async #writeThread(q, tableName, { id, title, metadata }) {
|
|
12322
|
+
const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
|
|
12323
|
+
const params = [
|
|
12324
|
+
title ?? null,
|
|
12325
|
+
nowStr,
|
|
12326
|
+
nowStr,
|
|
12327
|
+
id
|
|
12328
|
+
];
|
|
12329
|
+
let metadataSet = "";
|
|
12330
|
+
if (metadata !== void 0) {
|
|
12331
|
+
params.push(toPgJson(metadata));
|
|
12332
|
+
metadataSet = "metadata = $5,";
|
|
12333
|
+
}
|
|
12334
|
+
const row = await q.oneOrNone(`UPDATE ${tableName}
|
|
12335
|
+
SET
|
|
12336
|
+
title = COALESCE($1, title),
|
|
12337
|
+
${metadataSet}
|
|
12338
|
+
"updatedAt" = $2,
|
|
12339
|
+
"updatedAtZ" = $3
|
|
12340
|
+
WHERE id = $4
|
|
12341
|
+
RETURNING *`, params);
|
|
12342
|
+
return row ? this.#toThread(row) : null;
|
|
12343
|
+
}
|
|
12344
|
+
/**
|
|
12345
|
+
* Cross-process atomic metadata read-modify-write: the thread row stays locked
|
|
12346
|
+
* (`SELECT ... FOR UPDATE`) from the read through the write, so concurrent
|
|
12347
|
+
* writers from any process serialize instead of overwriting each other.
|
|
12348
|
+
*/
|
|
12349
|
+
async updateThreadMetadata({ id, resourceId, update, fence: explicitFence }) {
|
|
12350
|
+
const fence = resolveRunFence(this, explicitFence);
|
|
12351
|
+
const threadsTable = getTableName$3({
|
|
12352
|
+
indexName: TABLE_THREADS,
|
|
12353
|
+
schemaName: getSchemaName$3(this.#schema)
|
|
12354
|
+
});
|
|
12355
|
+
try {
|
|
12356
|
+
return await this.#db.client.tx(async (t) => {
|
|
12357
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateThreadMetadata");
|
|
12358
|
+
const thread = await this.#lockThread(t, threadsTable, {
|
|
12359
|
+
id,
|
|
12360
|
+
resourceId
|
|
12361
|
+
});
|
|
12362
|
+
if (!thread) return null;
|
|
12363
|
+
const metadata = update(thread);
|
|
12364
|
+
if (!metadata) return thread;
|
|
12365
|
+
return this.#writeThread(t, threadsTable, {
|
|
12366
|
+
id,
|
|
12367
|
+
metadata: mergeMetadata(thread.metadata, metadata)
|
|
12368
|
+
});
|
|
12369
|
+
});
|
|
12370
|
+
} catch (error) {
|
|
12371
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
12372
|
+
throw new MastraError({
|
|
12373
|
+
id: createStorageErrorId("PG", "UPDATE_THREAD_METADATA", "FAILED"),
|
|
12374
|
+
domain: ErrorDomain.STORAGE,
|
|
12375
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
12376
|
+
details: { threadId: id }
|
|
12377
|
+
}, error);
|
|
12378
|
+
}
|
|
12379
|
+
}
|
|
12380
|
+
async updateThread({ id, title, metadata, fence: explicitFence }) {
|
|
12266
12381
|
const threadTableName = getTableName$3({
|
|
12267
12382
|
indexName: TABLE_THREADS,
|
|
12268
12383
|
schemaName: getSchemaName$3(this.#schema)
|
|
12269
12384
|
});
|
|
12385
|
+
const notFound = () => new MastraError({
|
|
12386
|
+
id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
|
|
12387
|
+
domain: ErrorDomain.STORAGE,
|
|
12388
|
+
category: ErrorCategory.USER,
|
|
12389
|
+
text: `Thread ${id} not found`,
|
|
12390
|
+
details: {
|
|
12391
|
+
threadId: id,
|
|
12392
|
+
title: title ?? null
|
|
12393
|
+
}
|
|
12394
|
+
});
|
|
12270
12395
|
try {
|
|
12271
|
-
return await this.#fenced(
|
|
12272
|
-
const
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
domain: ErrorDomain.STORAGE,
|
|
12276
|
-
category: ErrorCategory.USER,
|
|
12277
|
-
text: `Thread ${id} not found`,
|
|
12278
|
-
details: {
|
|
12279
|
-
threadId: id,
|
|
12280
|
-
title: title ?? null
|
|
12281
|
-
}
|
|
12396
|
+
if (metadata === void 0) return await this.#fenced(explicitFence, "updateThread", async (q) => {
|
|
12397
|
+
const thread = await this.#writeThread(q, threadTableName, {
|
|
12398
|
+
id,
|
|
12399
|
+
title
|
|
12282
12400
|
});
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
mergedMetadata,
|
|
12299
|
-
nowStr,
|
|
12300
|
-
nowStr,
|
|
12301
|
-
id
|
|
12302
|
-
]);
|
|
12303
|
-
return {
|
|
12304
|
-
id: thread.id,
|
|
12305
|
-
resourceId: thread.resourceId,
|
|
12306
|
-
title: thread.title,
|
|
12307
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
12308
|
-
createdAt: thread.createdAtZ || thread.createdAt,
|
|
12309
|
-
updatedAt: thread.updatedAtZ || thread.updatedAt
|
|
12310
|
-
};
|
|
12401
|
+
if (!thread) throw notFound();
|
|
12402
|
+
return thread;
|
|
12403
|
+
});
|
|
12404
|
+
const fence = resolveRunFence(this, explicitFence);
|
|
12405
|
+
return await this.#db.client.tx(async (t) => {
|
|
12406
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateThread");
|
|
12407
|
+
const existing = await this.#lockThread(t, threadTableName, { id });
|
|
12408
|
+
if (!existing) throw notFound();
|
|
12409
|
+
const thread = await this.#writeThread(t, threadTableName, {
|
|
12410
|
+
id,
|
|
12411
|
+
title,
|
|
12412
|
+
metadata: mergeMetadata(existing.metadata, metadata)
|
|
12413
|
+
});
|
|
12414
|
+
if (!thread) throw notFound();
|
|
12415
|
+
return thread;
|
|
12311
12416
|
});
|
|
12312
12417
|
} catch (error) {
|
|
12313
12418
|
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
@@ -17363,8 +17468,12 @@ ${onConflict}`,
|
|
|
17363
17468
|
async function insertRows(client, schema, table, records, partitioning, onConflict) {
|
|
17364
17469
|
const insert = buildInsert(schema, table, records, onConflict);
|
|
17365
17470
|
if (!insert) return;
|
|
17471
|
+
await withPartitionRetry(client, schema, table, records, partitioning, () => client.query(insert.text, insert.values));
|
|
17472
|
+
}
|
|
17473
|
+
/** Retry a complete write after creating a missing native partition, outside any failed transaction. */
|
|
17474
|
+
async function withPartitionRetry(client, schema, table, records, partitioning, write) {
|
|
17366
17475
|
try {
|
|
17367
|
-
await
|
|
17476
|
+
return await write();
|
|
17368
17477
|
} catch (error) {
|
|
17369
17478
|
if (!isMissingPartitionError(error) || await resolveMode(client, partitioning) !== "native") throw error;
|
|
17370
17479
|
const days = withinPartitionWindow(recordDays(records, SIGNAL_TIME_COLUMN[table]), partitioning);
|
|
@@ -17377,7 +17486,7 @@ async function insertRows(client, schema, table, records, partitioning, onConfli
|
|
|
17377
17486
|
cause
|
|
17378
17487
|
}), { cause });
|
|
17379
17488
|
});
|
|
17380
|
-
await
|
|
17489
|
+
return await write();
|
|
17381
17490
|
}
|
|
17382
17491
|
}
|
|
17383
17492
|
/** `42501`: the role may not create the child, or does not own the parent it must attach to. */
|
|
@@ -18311,13 +18420,47 @@ function pushFeedbackIdentity(acc, feedbackType, feedbackSource) {
|
|
|
18311
18420
|
acc.conditions.push(`"valueNumber" IS NOT NULL`);
|
|
18312
18421
|
}
|
|
18313
18422
|
async function createFeedback(client, schema, args, partitioning) {
|
|
18314
|
-
|
|
18315
|
-
|
|
18423
|
+
await writeFeedback(client, schema, [args.feedback], args.onConflict, partitioning);
|
|
18424
|
+
}
|
|
18425
|
+
async function replaceFeedback(client, schema, args, partitioning) {
|
|
18426
|
+
await writeFeedback(client, schema, [args.feedback], "replace", partitioning, true);
|
|
18316
18427
|
}
|
|
18317
18428
|
async function batchCreateFeedback(client, schema, args, partitioning) {
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18429
|
+
await writeFeedback(client, schema, args.feedbacks, args.onConflict, partitioning);
|
|
18430
|
+
}
|
|
18431
|
+
/** Serialize feedback writes by ID, including absent IDs and retries with a different timestamp. */
|
|
18432
|
+
async function writeFeedback(client, schema, feedbacks, onConflict, partitioning, preserveIdentity = false) {
|
|
18433
|
+
if (!feedbacks.length) return;
|
|
18434
|
+
const currentFeedback = new Map((onConflict === "ignore" ? [...feedbacks].reverse() : feedbacks).map((feedback) => [feedback.feedbackId, feedback]));
|
|
18435
|
+
const rows = [...currentFeedback.values()].map(feedbackRecordToRow);
|
|
18436
|
+
const ids = [...currentFeedback.keys()].sort();
|
|
18437
|
+
await withPartitionRetry(client, schema, TABLE_FEEDBACK_EVENTS, rows, partitioning, () => client.tx(async (tx) => {
|
|
18438
|
+
await tx.query(`SELECT pg_advisory_xact_lock(hashtextextended(lock_key, 0))
|
|
18439
|
+
FROM unnest($1::text[]) WITH ORDINALITY AS locks(lock_key, position)
|
|
18440
|
+
ORDER BY position`, [ids.map((id) => JSON.stringify([
|
|
18441
|
+
schema,
|
|
18442
|
+
TABLE_FEEDBACK_EVENTS,
|
|
18443
|
+
id
|
|
18444
|
+
]))]);
|
|
18445
|
+
const existing = await tx.any(`SELECT DISTINCT ON ("feedbackId") "feedbackId", "traceId", "timestamp", "organizationId", "resourceId"
|
|
18446
|
+
FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
18447
|
+
WHERE "feedbackId" = ANY($1::text[]) ORDER BY "feedbackId", "cursorId" DESC`, [ids]);
|
|
18448
|
+
const byId = new Map(existing.map((row) => [row.feedbackId, row]));
|
|
18449
|
+
const accepted = rows.filter((row) => {
|
|
18450
|
+
const previous = byId.get(row.feedbackId);
|
|
18451
|
+
if (!previous) return true;
|
|
18452
|
+
if (onConflict === "ignore") return false;
|
|
18453
|
+
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({
|
|
18454
|
+
id: "OBSERVABILITY_FEEDBACK_IDENTITY_CONFLICT",
|
|
18455
|
+
domain: ErrorDomain.MASTRA_OBSERVABILITY,
|
|
18456
|
+
category: ErrorCategory.USER,
|
|
18457
|
+
text: "Feedback replacement must preserve its trace, timestamp, and tenant"
|
|
18458
|
+
});
|
|
18459
|
+
return true;
|
|
18460
|
+
});
|
|
18461
|
+
const insert = buildInsert(schema, TABLE_FEEDBACK_EVENTS, accepted, FEEDBACK_UPSERT_CLAUSE);
|
|
18462
|
+
if (insert) await tx.query(insert.text, insert.values);
|
|
18463
|
+
}));
|
|
18321
18464
|
}
|
|
18322
18465
|
async function getFeedbackById(client, schema, feedbackId) {
|
|
18323
18466
|
const row = await client.oneOrNone(`SELECT ${FEEDBACK_SELECT_COLUMNS} FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
@@ -21795,6 +21938,7 @@ async function dangerouslyClearTracing(client, schema) {
|
|
|
21795
21938
|
const spanQueryFeatures = typeof coreStorage.planSpanQuery === "function" ? [
|
|
21796
21939
|
"span-query",
|
|
21797
21940
|
"span-query-context-fields",
|
|
21941
|
+
...coreStorage.SPAN_QUERY_ORDER_FIELDS?.includes("durationMs") ? ["span-query-duration-sort"] : [],
|
|
21798
21942
|
..."model" in (coreStorage.SPAN_QUERY_FIELD_REGISTRY ?? {}) ? ["span-query-model-fields"] : []
|
|
21799
21943
|
] : [];
|
|
21800
21944
|
const spanAggregateFeatures = typeof coreStorage.planSpanAggregate === "function" ? ["span-aggregate", ..."model" in (coreStorage.SPAN_AGGREGATE_DIMENSION_REGISTRY ?? {}) ? ["span-aggregate-model-dimensions"] : []] : [];
|
|
@@ -22008,6 +22152,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22008
22152
|
"thread-query",
|
|
22009
22153
|
"trace-query-tenant-scope",
|
|
22010
22154
|
"feedback",
|
|
22155
|
+
"feedback-replacement",
|
|
22011
22156
|
"trace-query-context-ids",
|
|
22012
22157
|
...contextFieldFeatures,
|
|
22013
22158
|
...timingFeatures,
|
|
@@ -22036,6 +22181,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22036
22181
|
"thread-query",
|
|
22037
22182
|
"trace-query-tenant-scope",
|
|
22038
22183
|
"feedback",
|
|
22184
|
+
"feedback-replacement",
|
|
22039
22185
|
"trace-query-context-ids",
|
|
22040
22186
|
...contextFieldFeatures,
|
|
22041
22187
|
...timingFeatures,
|
|
@@ -22135,6 +22281,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22135
22281
|
async batchCreateScores(args) {
|
|
22136
22282
|
await this.#run("BATCH_CREATE_SCORES", () => batchCreateScores(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.scores.length });
|
|
22137
22283
|
}
|
|
22284
|
+
async writeFeedbackReplacement(args) {
|
|
22285
|
+
await this.#run("REPLACE_FEEDBACK", () => replaceFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22286
|
+
}
|
|
22138
22287
|
async createFeedback(args) {
|
|
22139
22288
|
await this.#run("CREATE_FEEDBACK", () => createFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22140
22289
|
}
|
|
@@ -22160,7 +22309,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22160
22309
|
return this.#run("GET_SCORE_BY_ID", () => getScoreById(this.#readClient, this.#schema, scoreId), { scoreId });
|
|
22161
22310
|
}
|
|
22162
22311
|
async getFeedbackById(feedbackId) {
|
|
22163
|
-
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#
|
|
22312
|
+
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#client, this.#schema, feedbackId), { feedbackId });
|
|
22164
22313
|
}
|
|
22165
22314
|
async listFeedback(args) {
|
|
22166
22315
|
return this.#run("LIST_FEEDBACK", () => listFeedback(this.#readClient, this.#schema, args));
|
|
@@ -22861,6 +23010,8 @@ function rowToSchedule(row) {
|
|
|
22861
23010
|
if (metadata !== void 0) schedule.metadata = metadata;
|
|
22862
23011
|
if (row.owner_type != null) schedule.ownerType = String(row.owner_type);
|
|
22863
23012
|
if (row.owner_id != null) schedule.ownerId = String(row.owner_id);
|
|
23013
|
+
if (row.run_at != null) schedule.runAt = Number(row.run_at);
|
|
23014
|
+
if (row.end_at != null) schedule.endAt = Number(row.end_at);
|
|
22864
23015
|
return schedule;
|
|
22865
23016
|
}
|
|
22866
23017
|
function rowToTrigger(row) {
|
|
@@ -22920,6 +23071,11 @@ var SchedulesPG = class SchedulesPG extends SchedulesStorage {
|
|
|
22920
23071
|
tableName: TABLE_SCHEDULES,
|
|
22921
23072
|
schema: TABLE_SCHEMAS[TABLE_SCHEDULES]
|
|
22922
23073
|
});
|
|
23074
|
+
await this.#db.alterTable({
|
|
23075
|
+
tableName: TABLE_SCHEDULES,
|
|
23076
|
+
schema: TABLE_SCHEMAS[TABLE_SCHEDULES],
|
|
23077
|
+
ifNotExists: ["run_at", "end_at"]
|
|
23078
|
+
});
|
|
22923
23079
|
await this.#db.createTable({
|
|
22924
23080
|
tableName: TABLE_SCHEDULE_TRIGGERS,
|
|
22925
23081
|
schema: TABLE_SCHEMAS[TABLE_SCHEDULE_TRIGGERS]
|
|
@@ -23047,7 +23203,9 @@ var SchedulesPG = class SchedulesPG extends SchedulesStorage {
|
|
|
23047
23203
|
updated_at: schedule.updatedAt,
|
|
23048
23204
|
metadata: schedule.metadata ?? null,
|
|
23049
23205
|
owner_type: schedule.ownerType ?? null,
|
|
23050
|
-
owner_id: schedule.ownerId ?? null
|
|
23206
|
+
owner_id: schedule.ownerId ?? null,
|
|
23207
|
+
run_at: schedule.runAt ?? null,
|
|
23208
|
+
end_at: schedule.endAt ?? null
|
|
23051
23209
|
}
|
|
23052
23210
|
});
|
|
23053
23211
|
return schedule;
|
|
@@ -23117,6 +23275,8 @@ var SchedulesPG = class SchedulesPG extends SchedulesStorage {
|
|
|
23117
23275
|
if ("metadata" in patch) push("metadata = ?::jsonb", patch.metadata != null ? toPgJson(patch.metadata) : null);
|
|
23118
23276
|
if ("ownerType" in patch) push("owner_type = ?", patch.ownerType ?? null);
|
|
23119
23277
|
if ("ownerId" in patch) push("owner_id = ?", patch.ownerId ?? null);
|
|
23278
|
+
if ("runAt" in patch) push("run_at = ?", patch.runAt ?? null);
|
|
23279
|
+
if ("endAt" in patch) push("end_at = ?", patch.endAt ?? null);
|
|
23120
23280
|
push("updated_at = ?", Date.now());
|
|
23121
23281
|
if (setClauses.length === 1) {
|
|
23122
23282
|
const existing = await this.#getSchedule(this.#client, id);
|