@mastra/pg 1.36.0-alpha.2 → 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 +20 -20
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +2 -0
- package/dist/index.cjs +192 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +192 -56
- package/dist/index.js.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/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11655,6 +11655,13 @@ function dedupeMessagesForSave(messages) {
|
|
|
11655
11655
|
}
|
|
11656
11656
|
return Array.from(deduped.values());
|
|
11657
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
|
+
}
|
|
11658
11665
|
var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
11659
11666
|
supportsPartialThreadUpdate = true;
|
|
11660
11667
|
supportsObservationalMemory = true;
|
|
@@ -12221,7 +12228,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12221
12228
|
const createdAt = toUtcISOString(thread.createdAt);
|
|
12222
12229
|
const updatedAt = toUtcISOString(thread.updatedAt);
|
|
12223
12230
|
const metadataJson = thread.metadata ? toPgJson(thread.metadata) : null;
|
|
12224
|
-
await this.#fenced(fence, "saveThread", (q) => q.
|
|
12231
|
+
const saved = await this.#fenced(fence, "saveThread", (q) => q.oneOrNone(`INSERT INTO ${tableName} AS t (
|
|
12225
12232
|
id,
|
|
12226
12233
|
"resourceId",
|
|
12227
12234
|
title,
|
|
@@ -12232,13 +12239,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12232
12239
|
"updatedAtZ"
|
|
12233
12240
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
12234
12241
|
ON CONFLICT (id) DO UPDATE SET
|
|
12235
|
-
"resourceId" = EXCLUDED."resourceId",
|
|
12236
12242
|
title = EXCLUDED.title,
|
|
12237
12243
|
metadata = EXCLUDED.metadata,
|
|
12238
|
-
"createdAt" = EXCLUDED."createdAt",
|
|
12239
|
-
"createdAtZ" = EXCLUDED."createdAtZ",
|
|
12240
12244
|
"updatedAt" = EXCLUDED."updatedAt",
|
|
12241
|
-
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12245
|
+
"updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
12246
|
+
WHERE t."resourceId" = EXCLUDED."resourceId"
|
|
12247
|
+
RETURNING *`, [
|
|
12242
12248
|
thread.id,
|
|
12243
12249
|
thread.resourceId,
|
|
12244
12250
|
thread.title,
|
|
@@ -12248,12 +12254,26 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12248
12254
|
updatedAt,
|
|
12249
12255
|
updatedAt
|
|
12250
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
|
+
});
|
|
12251
12267
|
return {
|
|
12252
|
-
|
|
12253
|
-
|
|
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
|
|
12254
12274
|
};
|
|
12255
12275
|
} catch (error) {
|
|
12256
|
-
if (isRunFenceConflictError(error)) throw error;
|
|
12276
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
12257
12277
|
throw new MastraError({
|
|
12258
12278
|
id: createStorageErrorId("PG", "SAVE_THREAD", "FAILED"),
|
|
12259
12279
|
domain: ErrorDomain.STORAGE,
|
|
@@ -12262,52 +12282,125 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12262
12282
|
}, error);
|
|
12263
12283
|
}
|
|
12264
12284
|
}
|
|
12265
|
-
|
|
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 }) {
|
|
12266
12369
|
const threadTableName = getTableName$3({
|
|
12267
12370
|
indexName: TABLE_THREADS,
|
|
12268
12371
|
schemaName: getSchemaName$3(this.#schema)
|
|
12269
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
|
+
});
|
|
12270
12383
|
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
|
-
}
|
|
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
|
|
12282
12388
|
});
|
|
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
|
-
};
|
|
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;
|
|
12311
12404
|
});
|
|
12312
12405
|
} catch (error) {
|
|
12313
12406
|
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
@@ -17363,8 +17456,12 @@ ${onConflict}`,
|
|
|
17363
17456
|
async function insertRows(client, schema, table, records, partitioning, onConflict) {
|
|
17364
17457
|
const insert = buildInsert(schema, table, records, onConflict);
|
|
17365
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) {
|
|
17366
17463
|
try {
|
|
17367
|
-
await
|
|
17464
|
+
return await write();
|
|
17368
17465
|
} catch (error) {
|
|
17369
17466
|
if (!isMissingPartitionError(error) || await resolveMode(client, partitioning) !== "native") throw error;
|
|
17370
17467
|
const days = withinPartitionWindow(recordDays(records, SIGNAL_TIME_COLUMN[table]), partitioning);
|
|
@@ -17377,7 +17474,7 @@ async function insertRows(client, schema, table, records, partitioning, onConfli
|
|
|
17377
17474
|
cause
|
|
17378
17475
|
}), { cause });
|
|
17379
17476
|
});
|
|
17380
|
-
await
|
|
17477
|
+
return await write();
|
|
17381
17478
|
}
|
|
17382
17479
|
}
|
|
17383
17480
|
/** `42501`: the role may not create the child, or does not own the parent it must attach to. */
|
|
@@ -18311,13 +18408,47 @@ function pushFeedbackIdentity(acc, feedbackType, feedbackSource) {
|
|
|
18311
18408
|
acc.conditions.push(`"valueNumber" IS NOT NULL`);
|
|
18312
18409
|
}
|
|
18313
18410
|
async function createFeedback(client, schema, args, partitioning) {
|
|
18314
|
-
|
|
18315
|
-
|
|
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);
|
|
18316
18415
|
}
|
|
18317
18416
|
async function batchCreateFeedback(client, schema, args, partitioning) {
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
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
|
+
}));
|
|
18321
18452
|
}
|
|
18322
18453
|
async function getFeedbackById(client, schema, feedbackId) {
|
|
18323
18454
|
const row = await client.oneOrNone(`SELECT ${FEEDBACK_SELECT_COLUMNS} FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
@@ -22008,6 +22139,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22008
22139
|
"thread-query",
|
|
22009
22140
|
"trace-query-tenant-scope",
|
|
22010
22141
|
"feedback",
|
|
22142
|
+
"feedback-replacement",
|
|
22011
22143
|
"trace-query-context-ids",
|
|
22012
22144
|
...contextFieldFeatures,
|
|
22013
22145
|
...timingFeatures,
|
|
@@ -22036,6 +22168,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22036
22168
|
"thread-query",
|
|
22037
22169
|
"trace-query-tenant-scope",
|
|
22038
22170
|
"feedback",
|
|
22171
|
+
"feedback-replacement",
|
|
22039
22172
|
"trace-query-context-ids",
|
|
22040
22173
|
...contextFieldFeatures,
|
|
22041
22174
|
...timingFeatures,
|
|
@@ -22135,6 +22268,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22135
22268
|
async batchCreateScores(args) {
|
|
22136
22269
|
await this.#run("BATCH_CREATE_SCORES", () => batchCreateScores(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.scores.length });
|
|
22137
22270
|
}
|
|
22271
|
+
async writeFeedbackReplacement(args) {
|
|
22272
|
+
await this.#run("REPLACE_FEEDBACK", () => replaceFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22273
|
+
}
|
|
22138
22274
|
async createFeedback(args) {
|
|
22139
22275
|
await this.#run("CREATE_FEEDBACK", () => createFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22140
22276
|
}
|
|
@@ -22160,7 +22296,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22160
22296
|
return this.#run("GET_SCORE_BY_ID", () => getScoreById(this.#readClient, this.#schema, scoreId), { scoreId });
|
|
22161
22297
|
}
|
|
22162
22298
|
async getFeedbackById(feedbackId) {
|
|
22163
|
-
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#
|
|
22299
|
+
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#client, this.#schema, feedbackId), { feedbackId });
|
|
22164
22300
|
}
|
|
22165
22301
|
async listFeedback(args) {
|
|
22166
22302
|
return this.#run("LIST_FEEDBACK", () => listFeedback(this.#readClient, this.#schema, args));
|