@mastra/pg 1.0.0-beta.9 → 1.1.0-alpha.0
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/CHANGELOG.md +1481 -0
- package/dist/docs/README.md +36 -0
- package/dist/docs/SKILL.md +37 -0
- package/dist/docs/SOURCE_MAP.json +6 -0
- package/dist/docs/memory/01-storage.md +261 -0
- package/dist/docs/memory/02-working-memory.md +411 -0
- package/dist/docs/memory/03-semantic-recall.md +256 -0
- package/dist/docs/memory/04-reference.md +133 -0
- package/dist/docs/processors/01-reference.md +296 -0
- package/dist/docs/rag/01-overview.md +74 -0
- package/dist/docs/rag/02-vector-databases.md +643 -0
- package/dist/docs/rag/03-retrieval.md +548 -0
- package/dist/docs/rag/04-reference.md +369 -0
- package/dist/docs/storage/01-reference.md +905 -0
- package/dist/docs/tools/01-reference.md +440 -0
- package/dist/docs/vectors/01-reference.md +307 -0
- package/dist/index.cjs +1293 -260
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1290 -262
- package/dist/index.js.map +1 -1
- package/dist/shared/config.d.ts +61 -66
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/storage/client.d.ts +91 -0
- package/dist/storage/client.d.ts.map +1 -0
- package/dist/storage/db/index.d.ts +82 -17
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/agents/index.d.ts +11 -1
- package/dist/storage/domains/agents/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +3 -2
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +24 -1
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +1 -0
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +44 -17
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/test-utils.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/sql-builder.d.ts.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
-
import { createVectorErrorId,
|
|
2
|
+
import { createVectorErrorId, TABLE_SCHEMAS, AgentsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, createStorageErrorId, normalizePerPage, calculatePagination, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, toTraceSpans, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraCompositeStore, TraceStatus, getDefaultValue, transformScoreRow as transformScoreRow$1, getSqlType } from '@mastra/core/storage';
|
|
3
3
|
import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
|
|
4
|
-
import { MastraVector } from '@mastra/core/vector';
|
|
4
|
+
import { MastraVector, validateTopK, validateUpsertInput } from '@mastra/core/vector';
|
|
5
5
|
import { Mutex } from 'async-mutex';
|
|
6
6
|
import * as pg from 'pg';
|
|
7
|
+
import { Pool } from 'pg';
|
|
7
8
|
import xxhash from 'xxhash-wasm';
|
|
8
9
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
9
|
-
import pgPromise from 'pg-promise';
|
|
10
10
|
import { MastraBase } from '@mastra/core/base';
|
|
11
11
|
import { MessageList } from '@mastra/core/agent';
|
|
12
12
|
import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
@@ -14,8 +14,11 @@ import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
|
14
14
|
// src/vector/index.ts
|
|
15
15
|
|
|
16
16
|
// src/shared/config.ts
|
|
17
|
+
var isPoolConfig = (cfg) => {
|
|
18
|
+
return "pool" in cfg;
|
|
19
|
+
};
|
|
17
20
|
var isConnectionStringConfig = (cfg) => {
|
|
18
|
-
return "connectionString" in cfg;
|
|
21
|
+
return "connectionString" in cfg && typeof cfg.connectionString === "string";
|
|
19
22
|
};
|
|
20
23
|
var isHostConfig = (cfg) => {
|
|
21
24
|
return "host" in cfg && "database" in cfg && "user" in cfg && "password" in cfg;
|
|
@@ -23,16 +26,13 @@ var isHostConfig = (cfg) => {
|
|
|
23
26
|
var isCloudSqlConfig = (cfg) => {
|
|
24
27
|
return "stream" in cfg || "password" in cfg && typeof cfg.password === "function";
|
|
25
28
|
};
|
|
26
|
-
var isClientConfig = (cfg) => {
|
|
27
|
-
return "client" in cfg;
|
|
28
|
-
};
|
|
29
29
|
var validateConfig = (name, config) => {
|
|
30
30
|
if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
|
|
31
31
|
throw new Error(`${name}: id must be provided and cannot be empty.`);
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
34
|
-
if (!config.
|
|
35
|
-
throw new Error(`${name}:
|
|
33
|
+
if (isPoolConfig(config)) {
|
|
34
|
+
if (!config.pool) {
|
|
35
|
+
throw new Error(`${name}: pool must be provided when using pool config.`);
|
|
36
36
|
}
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
@@ -53,7 +53,7 @@ var validateConfig = (name, config) => {
|
|
|
53
53
|
}
|
|
54
54
|
} else {
|
|
55
55
|
throw new Error(
|
|
56
|
-
`${name}: invalid config. Provide either {
|
|
56
|
+
`${name}: invalid config. Provide either {pool}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with \`stream\`).`
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
59
|
};
|
|
@@ -261,8 +261,14 @@ var FILTER_OPERATORS = {
|
|
|
261
261
|
};
|
|
262
262
|
},
|
|
263
263
|
// Element Operators
|
|
264
|
-
$exists: (key) => {
|
|
264
|
+
$exists: (key, paramIndex, value) => {
|
|
265
265
|
const jsonPathKey = parseJsonPathKey(key);
|
|
266
|
+
if (value === false) {
|
|
267
|
+
return {
|
|
268
|
+
sql: `NOT (metadata ? '${jsonPathKey}')`,
|
|
269
|
+
needsValue: false
|
|
270
|
+
};
|
|
271
|
+
}
|
|
266
272
|
return {
|
|
267
273
|
sql: `metadata ? '${jsonPathKey}'`,
|
|
268
274
|
needsValue: false
|
|
@@ -340,17 +346,62 @@ function buildDeleteFilterQuery(filter) {
|
|
|
340
346
|
values.push(value);
|
|
341
347
|
return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
|
|
342
348
|
}
|
|
343
|
-
const
|
|
349
|
+
const entries = Object.entries(value);
|
|
350
|
+
if (entries.length > 1) {
|
|
351
|
+
const conditions2 = entries.map(([operator2, operatorValue2]) => {
|
|
352
|
+
if (operator2 === "$not") {
|
|
353
|
+
const nestedEntries = Object.entries(operatorValue2);
|
|
354
|
+
const nestedConditions = nestedEntries.map(([nestedOp, nestedValue]) => {
|
|
355
|
+
if (!FILTER_OPERATORS[nestedOp]) {
|
|
356
|
+
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
357
|
+
}
|
|
358
|
+
const operatorFn3 = FILTER_OPERATORS[nestedOp];
|
|
359
|
+
const operatorResult3 = operatorFn3(key, values.length + 1, nestedValue);
|
|
360
|
+
if (operatorResult3.needsValue) {
|
|
361
|
+
const transformedValue = operatorResult3.transformValue ? operatorResult3.transformValue() : nestedValue;
|
|
362
|
+
if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
|
|
363
|
+
values.push(...transformedValue);
|
|
364
|
+
} else {
|
|
365
|
+
values.push(transformedValue);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return operatorResult3.sql;
|
|
369
|
+
}).join(" AND ");
|
|
370
|
+
return `NOT (${nestedConditions})`;
|
|
371
|
+
}
|
|
372
|
+
if (!FILTER_OPERATORS[operator2]) {
|
|
373
|
+
throw new Error(`Invalid operator: ${operator2}`);
|
|
374
|
+
}
|
|
375
|
+
const operatorFn2 = FILTER_OPERATORS[operator2];
|
|
376
|
+
const operatorResult2 = operatorFn2(key, values.length + 1, operatorValue2);
|
|
377
|
+
if (operatorResult2.needsValue) {
|
|
378
|
+
const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : operatorValue2;
|
|
379
|
+
if (Array.isArray(transformedValue) && operator2 === "$elemMatch") {
|
|
380
|
+
values.push(...transformedValue);
|
|
381
|
+
} else {
|
|
382
|
+
values.push(transformedValue);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return operatorResult2.sql;
|
|
386
|
+
});
|
|
387
|
+
return conditions2.join(" AND ");
|
|
388
|
+
}
|
|
389
|
+
const [[operator, operatorValue] = []] = entries;
|
|
344
390
|
if (operator === "$not") {
|
|
345
|
-
const
|
|
346
|
-
const conditions2 =
|
|
391
|
+
const nestedEntries = Object.entries(operatorValue);
|
|
392
|
+
const conditions2 = nestedEntries.map(([nestedOp, nestedValue]) => {
|
|
347
393
|
if (!FILTER_OPERATORS[nestedOp]) {
|
|
348
394
|
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
349
395
|
}
|
|
350
396
|
const operatorFn2 = FILTER_OPERATORS[nestedOp];
|
|
351
397
|
const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
|
|
352
398
|
if (operatorResult2.needsValue) {
|
|
353
|
-
|
|
399
|
+
const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : nestedValue;
|
|
400
|
+
if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
|
|
401
|
+
values.push(...transformedValue);
|
|
402
|
+
} else {
|
|
403
|
+
values.push(transformedValue);
|
|
404
|
+
}
|
|
354
405
|
}
|
|
355
406
|
return operatorResult2.sql;
|
|
356
407
|
}).join(" AND ");
|
|
@@ -417,17 +468,62 @@ function buildFilterQuery(filter, minScore, topK) {
|
|
|
417
468
|
values.push(value);
|
|
418
469
|
return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
|
|
419
470
|
}
|
|
420
|
-
const
|
|
471
|
+
const entries = Object.entries(value);
|
|
472
|
+
if (entries.length > 1) {
|
|
473
|
+
const conditions2 = entries.map(([operator2, operatorValue2]) => {
|
|
474
|
+
if (operator2 === "$not") {
|
|
475
|
+
const nestedEntries = Object.entries(operatorValue2);
|
|
476
|
+
const nestedConditions = nestedEntries.map(([nestedOp, nestedValue]) => {
|
|
477
|
+
if (!FILTER_OPERATORS[nestedOp]) {
|
|
478
|
+
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
479
|
+
}
|
|
480
|
+
const operatorFn3 = FILTER_OPERATORS[nestedOp];
|
|
481
|
+
const operatorResult3 = operatorFn3(key, values.length + 1, nestedValue);
|
|
482
|
+
if (operatorResult3.needsValue) {
|
|
483
|
+
const transformedValue = operatorResult3.transformValue ? operatorResult3.transformValue() : nestedValue;
|
|
484
|
+
if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
|
|
485
|
+
values.push(...transformedValue);
|
|
486
|
+
} else {
|
|
487
|
+
values.push(transformedValue);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return operatorResult3.sql;
|
|
491
|
+
}).join(" AND ");
|
|
492
|
+
return `NOT (${nestedConditions})`;
|
|
493
|
+
}
|
|
494
|
+
if (!FILTER_OPERATORS[operator2]) {
|
|
495
|
+
throw new Error(`Invalid operator: ${operator2}`);
|
|
496
|
+
}
|
|
497
|
+
const operatorFn2 = FILTER_OPERATORS[operator2];
|
|
498
|
+
const operatorResult2 = operatorFn2(key, values.length + 1, operatorValue2);
|
|
499
|
+
if (operatorResult2.needsValue) {
|
|
500
|
+
const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : operatorValue2;
|
|
501
|
+
if (Array.isArray(transformedValue) && operator2 === "$elemMatch") {
|
|
502
|
+
values.push(...transformedValue);
|
|
503
|
+
} else {
|
|
504
|
+
values.push(transformedValue);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return operatorResult2.sql;
|
|
508
|
+
});
|
|
509
|
+
return conditions2.join(" AND ");
|
|
510
|
+
}
|
|
511
|
+
const [[operator, operatorValue] = []] = entries;
|
|
421
512
|
if (operator === "$not") {
|
|
422
|
-
const
|
|
423
|
-
const conditions2 =
|
|
513
|
+
const nestedEntries = Object.entries(operatorValue);
|
|
514
|
+
const conditions2 = nestedEntries.map(([nestedOp, nestedValue]) => {
|
|
424
515
|
if (!FILTER_OPERATORS[nestedOp]) {
|
|
425
516
|
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
426
517
|
}
|
|
427
518
|
const operatorFn2 = FILTER_OPERATORS[nestedOp];
|
|
428
519
|
const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
|
|
429
520
|
if (operatorResult2.needsValue) {
|
|
430
|
-
|
|
521
|
+
const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : nestedValue;
|
|
522
|
+
if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
|
|
523
|
+
values.push(...transformedValue);
|
|
524
|
+
} else {
|
|
525
|
+
values.push(transformedValue);
|
|
526
|
+
}
|
|
431
527
|
}
|
|
432
528
|
return operatorResult2.sql;
|
|
433
529
|
}).join(" AND ");
|
|
@@ -518,8 +614,8 @@ var PgVector = class extends MastraVector {
|
|
|
518
614
|
} else if (isCloudSqlConfig(config)) {
|
|
519
615
|
poolConfig = {
|
|
520
616
|
...config,
|
|
521
|
-
max: config.max ?? 20,
|
|
522
|
-
idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
|
|
617
|
+
max: config.pgPoolOptions?.max ?? 20,
|
|
618
|
+
idleTimeoutMillis: config.pgPoolOptions?.idleTimeoutMillis ?? 3e4,
|
|
523
619
|
connectionTimeoutMillis: 2e3,
|
|
524
620
|
...config.pgPoolOptions
|
|
525
621
|
};
|
|
@@ -687,9 +783,7 @@ var PgVector = class extends MastraVector {
|
|
|
687
783
|
probes
|
|
688
784
|
}) {
|
|
689
785
|
try {
|
|
690
|
-
|
|
691
|
-
throw new Error("topK must be a positive integer");
|
|
692
|
-
}
|
|
786
|
+
validateTopK("PG", topK);
|
|
693
787
|
if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
|
|
694
788
|
throw new Error("queryVector must be an array of finite numbers");
|
|
695
789
|
}
|
|
@@ -774,6 +868,7 @@ var PgVector = class extends MastraVector {
|
|
|
774
868
|
ids,
|
|
775
869
|
deleteFilter
|
|
776
870
|
}) {
|
|
871
|
+
validateUpsertInput("PG", vectors, metadata, ids);
|
|
777
872
|
const { tableName } = this.getTableName(indexName);
|
|
778
873
|
const client = await this.pool.connect();
|
|
779
874
|
try {
|
|
@@ -1661,6 +1756,132 @@ var PgVector = class extends MastraVector {
|
|
|
1661
1756
|
}
|
|
1662
1757
|
}
|
|
1663
1758
|
};
|
|
1759
|
+
|
|
1760
|
+
// src/storage/client.ts
|
|
1761
|
+
function truncateQuery(query, maxLength = 100) {
|
|
1762
|
+
const normalized = query.replace(/\s+/g, " ").trim();
|
|
1763
|
+
if (normalized.length <= maxLength) {
|
|
1764
|
+
return normalized;
|
|
1765
|
+
}
|
|
1766
|
+
return normalized.slice(0, maxLength) + "...";
|
|
1767
|
+
}
|
|
1768
|
+
var PoolAdapter = class {
|
|
1769
|
+
constructor($pool) {
|
|
1770
|
+
this.$pool = $pool;
|
|
1771
|
+
}
|
|
1772
|
+
connect() {
|
|
1773
|
+
return this.$pool.connect();
|
|
1774
|
+
}
|
|
1775
|
+
async none(query, values) {
|
|
1776
|
+
await this.$pool.query(query, values);
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
async one(query, values) {
|
|
1780
|
+
const result = await this.$pool.query(query, values);
|
|
1781
|
+
if (result.rows.length === 0) {
|
|
1782
|
+
throw new Error(`No data returned from query: ${truncateQuery(query)}`);
|
|
1783
|
+
}
|
|
1784
|
+
if (result.rows.length > 1) {
|
|
1785
|
+
throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
|
|
1786
|
+
}
|
|
1787
|
+
return result.rows[0];
|
|
1788
|
+
}
|
|
1789
|
+
async oneOrNone(query, values) {
|
|
1790
|
+
const result = await this.$pool.query(query, values);
|
|
1791
|
+
if (result.rows.length === 0) {
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
if (result.rows.length > 1) {
|
|
1795
|
+
throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
|
|
1796
|
+
}
|
|
1797
|
+
return result.rows[0];
|
|
1798
|
+
}
|
|
1799
|
+
async any(query, values) {
|
|
1800
|
+
const result = await this.$pool.query(query, values);
|
|
1801
|
+
return result.rows;
|
|
1802
|
+
}
|
|
1803
|
+
async manyOrNone(query, values) {
|
|
1804
|
+
return this.any(query, values);
|
|
1805
|
+
}
|
|
1806
|
+
async many(query, values) {
|
|
1807
|
+
const result = await this.$pool.query(query, values);
|
|
1808
|
+
if (result.rows.length === 0) {
|
|
1809
|
+
throw new Error(`No data returned from query: ${truncateQuery(query)}`);
|
|
1810
|
+
}
|
|
1811
|
+
return result.rows;
|
|
1812
|
+
}
|
|
1813
|
+
async query(query, values) {
|
|
1814
|
+
return this.$pool.query(query, values);
|
|
1815
|
+
}
|
|
1816
|
+
async tx(callback) {
|
|
1817
|
+
const client = await this.$pool.connect();
|
|
1818
|
+
try {
|
|
1819
|
+
await client.query("BEGIN");
|
|
1820
|
+
const txClient = new TransactionClient(client);
|
|
1821
|
+
const result = await callback(txClient);
|
|
1822
|
+
await client.query("COMMIT");
|
|
1823
|
+
return result;
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
try {
|
|
1826
|
+
await client.query("ROLLBACK");
|
|
1827
|
+
} catch (rollbackError) {
|
|
1828
|
+
console.error("Transaction rollback failed:", rollbackError);
|
|
1829
|
+
}
|
|
1830
|
+
throw error;
|
|
1831
|
+
} finally {
|
|
1832
|
+
client.release();
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
var TransactionClient = class {
|
|
1837
|
+
constructor(client) {
|
|
1838
|
+
this.client = client;
|
|
1839
|
+
}
|
|
1840
|
+
async none(query, values) {
|
|
1841
|
+
await this.client.query(query, values);
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
async one(query, values) {
|
|
1845
|
+
const result = await this.client.query(query, values);
|
|
1846
|
+
if (result.rows.length === 0) {
|
|
1847
|
+
throw new Error(`No data returned from query: ${truncateQuery(query)}`);
|
|
1848
|
+
}
|
|
1849
|
+
if (result.rows.length > 1) {
|
|
1850
|
+
throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
|
|
1851
|
+
}
|
|
1852
|
+
return result.rows[0];
|
|
1853
|
+
}
|
|
1854
|
+
async oneOrNone(query, values) {
|
|
1855
|
+
const result = await this.client.query(query, values);
|
|
1856
|
+
if (result.rows.length === 0) {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
if (result.rows.length > 1) {
|
|
1860
|
+
throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
|
|
1861
|
+
}
|
|
1862
|
+
return result.rows[0];
|
|
1863
|
+
}
|
|
1864
|
+
async any(query, values) {
|
|
1865
|
+
const result = await this.client.query(query, values);
|
|
1866
|
+
return result.rows;
|
|
1867
|
+
}
|
|
1868
|
+
async manyOrNone(query, values) {
|
|
1869
|
+
return this.any(query, values);
|
|
1870
|
+
}
|
|
1871
|
+
async many(query, values) {
|
|
1872
|
+
const result = await this.client.query(query, values);
|
|
1873
|
+
if (result.rows.length === 0) {
|
|
1874
|
+
throw new Error(`No data returned from query: ${truncateQuery(query)}`);
|
|
1875
|
+
}
|
|
1876
|
+
return result.rows;
|
|
1877
|
+
}
|
|
1878
|
+
async query(query, values) {
|
|
1879
|
+
return this.client.query(query, values);
|
|
1880
|
+
}
|
|
1881
|
+
async batch(promises) {
|
|
1882
|
+
return Promise.all(promises);
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1664
1885
|
function resolvePgConfig(config) {
|
|
1665
1886
|
if ("client" in config) {
|
|
1666
1887
|
return {
|
|
@@ -1670,10 +1891,32 @@ function resolvePgConfig(config) {
|
|
|
1670
1891
|
indexes: config.indexes
|
|
1671
1892
|
};
|
|
1672
1893
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1894
|
+
if ("pool" in config) {
|
|
1895
|
+
return {
|
|
1896
|
+
client: new PoolAdapter(config.pool),
|
|
1897
|
+
schemaName: config.schemaName,
|
|
1898
|
+
skipDefaultIndexes: config.skipDefaultIndexes,
|
|
1899
|
+
indexes: config.indexes
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
let pool;
|
|
1903
|
+
if ("connectionString" in config) {
|
|
1904
|
+
pool = new Pool({
|
|
1905
|
+
connectionString: config.connectionString,
|
|
1906
|
+
ssl: config.ssl
|
|
1907
|
+
});
|
|
1908
|
+
} else {
|
|
1909
|
+
pool = new Pool({
|
|
1910
|
+
host: config.host,
|
|
1911
|
+
port: config.port,
|
|
1912
|
+
database: config.database,
|
|
1913
|
+
user: config.user,
|
|
1914
|
+
password: config.password,
|
|
1915
|
+
ssl: config.ssl
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1675
1918
|
return {
|
|
1676
|
-
client,
|
|
1919
|
+
client: new PoolAdapter(pool),
|
|
1677
1920
|
schemaName: config.schemaName,
|
|
1678
1921
|
skipDefaultIndexes: config.skipDefaultIndexes,
|
|
1679
1922
|
indexes: config.indexes
|
|
@@ -1688,6 +1931,91 @@ function getTableName({ indexName, schemaName }) {
|
|
|
1688
1931
|
const quotedSchemaName = schemaName;
|
|
1689
1932
|
return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
|
|
1690
1933
|
}
|
|
1934
|
+
function mapToSqlType(type) {
|
|
1935
|
+
switch (type) {
|
|
1936
|
+
case "uuid":
|
|
1937
|
+
return "UUID";
|
|
1938
|
+
case "boolean":
|
|
1939
|
+
return "BOOLEAN";
|
|
1940
|
+
default:
|
|
1941
|
+
return getSqlType(type);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function generateTableSQL({
|
|
1945
|
+
tableName,
|
|
1946
|
+
schema,
|
|
1947
|
+
schemaName,
|
|
1948
|
+
includeAllConstraints = false
|
|
1949
|
+
}) {
|
|
1950
|
+
const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
|
|
1951
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1952
|
+
return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
|
|
1953
|
+
});
|
|
1954
|
+
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1955
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1956
|
+
const constraints = [];
|
|
1957
|
+
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1958
|
+
if (!def.nullable) constraints.push("NOT NULL");
|
|
1959
|
+
return `"${parsedName}" ${mapToSqlType(def.type)} ${constraints.join(" ")}`;
|
|
1960
|
+
});
|
|
1961
|
+
const finalColumns = [...columns, ...timeZColumns].join(",\n");
|
|
1962
|
+
const parsedSchemaName = schemaName ? parseSqlIdentifier(schemaName, "schema name") : "";
|
|
1963
|
+
const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
|
|
1964
|
+
const quotedSchemaName = getSchemaName(schemaName);
|
|
1965
|
+
const sql = `
|
|
1966
|
+
CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })} (
|
|
1967
|
+
${finalColumns}
|
|
1968
|
+
);
|
|
1969
|
+
${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
|
|
1970
|
+
DO $$ BEGIN
|
|
1971
|
+
IF NOT EXISTS (
|
|
1972
|
+
SELECT 1 FROM pg_constraint WHERE conname = lower('${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key')
|
|
1973
|
+
) AND NOT EXISTS (
|
|
1974
|
+
SELECT 1 FROM pg_indexes WHERE indexname = lower('${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key')
|
|
1975
|
+
) THEN
|
|
1976
|
+
ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
|
|
1977
|
+
ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
|
|
1978
|
+
UNIQUE (workflow_name, run_id);
|
|
1979
|
+
END IF;
|
|
1980
|
+
END $$;
|
|
1981
|
+
` : ""}
|
|
1982
|
+
${// For spans table: Include PRIMARY KEY in exports, but not in runtime (handled after deduplication)
|
|
1983
|
+
tableName === TABLE_SPANS && includeAllConstraints ? `
|
|
1984
|
+
DO $$ BEGIN
|
|
1985
|
+
IF NOT EXISTS (
|
|
1986
|
+
SELECT 1 FROM pg_constraint WHERE conname = lower('${constraintPrefix}mastra_ai_spans_traceid_spanid_pk')
|
|
1987
|
+
) THEN
|
|
1988
|
+
ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
|
|
1989
|
+
ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
|
|
1990
|
+
PRIMARY KEY ("traceId", "spanId");
|
|
1991
|
+
END IF;
|
|
1992
|
+
END $$;
|
|
1993
|
+
` : ""}
|
|
1994
|
+
`;
|
|
1995
|
+
return sql;
|
|
1996
|
+
}
|
|
1997
|
+
function exportSchemas(schemaName) {
|
|
1998
|
+
const statements = [];
|
|
1999
|
+
if (schemaName) {
|
|
2000
|
+
const quotedSchemaName = getSchemaName(schemaName);
|
|
2001
|
+
statements.push(`-- Create schema if it doesn't exist`);
|
|
2002
|
+
statements.push(`CREATE SCHEMA IF NOT EXISTS ${quotedSchemaName};`);
|
|
2003
|
+
statements.push("");
|
|
2004
|
+
}
|
|
2005
|
+
for (const [tableName, schema] of Object.entries(TABLE_SCHEMAS)) {
|
|
2006
|
+
statements.push(`-- Table: ${tableName}`);
|
|
2007
|
+
const sql = generateTableSQL({
|
|
2008
|
+
tableName,
|
|
2009
|
+
schema,
|
|
2010
|
+
schemaName,
|
|
2011
|
+
includeAllConstraints: true
|
|
2012
|
+
// Include all constraints for exports/documentation
|
|
2013
|
+
});
|
|
2014
|
+
statements.push(sql.trim());
|
|
2015
|
+
statements.push("");
|
|
2016
|
+
}
|
|
2017
|
+
return statements.join("\n");
|
|
2018
|
+
}
|
|
1691
2019
|
var schemaSetupRegistry = /* @__PURE__ */ new Map();
|
|
1692
2020
|
var PgDB = class extends MastraBase {
|
|
1693
2021
|
client;
|
|
@@ -1805,16 +2133,6 @@ var PgDB = class extends MastraBase {
|
|
|
1805
2133
|
}
|
|
1806
2134
|
await registryEntry.promise;
|
|
1807
2135
|
}
|
|
1808
|
-
getSqlType(type) {
|
|
1809
|
-
switch (type) {
|
|
1810
|
-
case "uuid":
|
|
1811
|
-
return "UUID";
|
|
1812
|
-
case "boolean":
|
|
1813
|
-
return "BOOLEAN";
|
|
1814
|
-
default:
|
|
1815
|
-
return getSqlType(type);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
2136
|
getDefaultValue(type) {
|
|
1819
2137
|
switch (type) {
|
|
1820
2138
|
case "timestamp":
|
|
@@ -1832,10 +2150,27 @@ var PgDB = class extends MastraBase {
|
|
|
1832
2150
|
const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
|
|
1833
2151
|
const values = this.prepareValuesForInsert(record, tableName);
|
|
1834
2152
|
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2153
|
+
const fullTableName = getTableName({ indexName: tableName, schemaName });
|
|
2154
|
+
const columnList = columns.map((c) => `"${c}"`).join(", ");
|
|
2155
|
+
if (tableName === TABLE_SPANS) {
|
|
2156
|
+
const updateColumns = columns.filter((c) => c !== "traceId" && c !== "spanId");
|
|
2157
|
+
if (updateColumns.length > 0) {
|
|
2158
|
+
const updateClause = updateColumns.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ");
|
|
2159
|
+
await this.client.none(
|
|
2160
|
+
`INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})
|
|
2161
|
+
ON CONFLICT ("traceId", "spanId") DO UPDATE SET ${updateClause}`,
|
|
2162
|
+
values
|
|
2163
|
+
);
|
|
2164
|
+
} else {
|
|
2165
|
+
await this.client.none(
|
|
2166
|
+
`INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})
|
|
2167
|
+
ON CONFLICT ("traceId", "spanId") DO NOTHING`,
|
|
2168
|
+
values
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
} else {
|
|
2172
|
+
await this.client.none(`INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})`, values);
|
|
2173
|
+
}
|
|
1839
2174
|
} catch (error) {
|
|
1840
2175
|
throw new MastraError(
|
|
1841
2176
|
{
|
|
@@ -1859,7 +2194,7 @@ var PgDB = class extends MastraBase {
|
|
|
1859
2194
|
SELECT 1 FROM information_schema.tables
|
|
1860
2195
|
WHERE table_schema = $1 AND table_name = $2
|
|
1861
2196
|
)`,
|
|
1862
|
-
[this.schemaName || "
|
|
2197
|
+
[this.schemaName || "public", tableName]
|
|
1863
2198
|
);
|
|
1864
2199
|
if (tableExists?.exists) {
|
|
1865
2200
|
await this.client.none(`TRUNCATE TABLE ${tableNameWithSchema} CASCADE`);
|
|
@@ -1884,52 +2219,10 @@ var PgDB = class extends MastraBase {
|
|
|
1884
2219
|
}) {
|
|
1885
2220
|
try {
|
|
1886
2221
|
const timeZColumnNames = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => name);
|
|
1887
|
-
const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
|
|
1888
|
-
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1889
|
-
return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
|
|
1890
|
-
});
|
|
1891
|
-
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1892
|
-
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1893
|
-
const constraints = [];
|
|
1894
|
-
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1895
|
-
if (!def.nullable) constraints.push("NOT NULL");
|
|
1896
|
-
return `"${parsedName}" ${this.getSqlType(def.type)} ${constraints.join(" ")}`;
|
|
1897
|
-
});
|
|
1898
2222
|
if (this.schemaName) {
|
|
1899
2223
|
await this.setupSchema();
|
|
1900
2224
|
}
|
|
1901
|
-
const
|
|
1902
|
-
const constraintPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
1903
|
-
const schemaName = getSchemaName(this.schemaName);
|
|
1904
|
-
const sql = `
|
|
1905
|
-
CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName })} (
|
|
1906
|
-
${finalColumns}
|
|
1907
|
-
);
|
|
1908
|
-
${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
|
|
1909
|
-
DO $$ BEGIN
|
|
1910
|
-
IF NOT EXISTS (
|
|
1911
|
-
SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
|
|
1912
|
-
) AND NOT EXISTS (
|
|
1913
|
-
SELECT 1 FROM pg_indexes WHERE indexname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
|
|
1914
|
-
) THEN
|
|
1915
|
-
ALTER TABLE ${getTableName({ indexName: tableName, schemaName })}
|
|
1916
|
-
ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
|
|
1917
|
-
UNIQUE (workflow_name, run_id);
|
|
1918
|
-
END IF;
|
|
1919
|
-
END $$;
|
|
1920
|
-
` : ""}
|
|
1921
|
-
${tableName === TABLE_SPANS ? `
|
|
1922
|
-
DO $$ BEGIN
|
|
1923
|
-
IF NOT EXISTS (
|
|
1924
|
-
SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_ai_spans_traceid_spanid_pk'
|
|
1925
|
-
) THEN
|
|
1926
|
-
ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })}
|
|
1927
|
-
ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
|
|
1928
|
-
PRIMARY KEY ("traceId", "spanId");
|
|
1929
|
-
END IF;
|
|
1930
|
-
END $$;
|
|
1931
|
-
` : ""}
|
|
1932
|
-
`;
|
|
2225
|
+
const sql = generateTableSQL({ tableName, schema, schemaName: this.schemaName });
|
|
1933
2226
|
await this.client.none(sql);
|
|
1934
2227
|
await this.alterTable({
|
|
1935
2228
|
tableName,
|
|
@@ -1939,8 +2232,46 @@ var PgDB = class extends MastraBase {
|
|
|
1939
2232
|
if (tableName === TABLE_SPANS) {
|
|
1940
2233
|
await this.setupTimestampTriggers(tableName);
|
|
1941
2234
|
await this.migrateSpansTable();
|
|
2235
|
+
const pkExists = await this.spansPrimaryKeyExists();
|
|
2236
|
+
if (!pkExists) {
|
|
2237
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
2238
|
+
if (duplicateInfo.hasDuplicates) {
|
|
2239
|
+
const errorMessage = `
|
|
2240
|
+
===========================================================================
|
|
2241
|
+
MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}
|
|
2242
|
+
===========================================================================
|
|
2243
|
+
|
|
2244
|
+
Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations.
|
|
2245
|
+
|
|
2246
|
+
The spans table requires a unique constraint on (traceId, spanId), but your
|
|
2247
|
+
database contains duplicate entries that must be resolved first.
|
|
2248
|
+
|
|
2249
|
+
To fix this, run the manual migration command:
|
|
2250
|
+
|
|
2251
|
+
npx mastra migrate
|
|
2252
|
+
|
|
2253
|
+
This command will:
|
|
2254
|
+
1. Remove duplicate spans (keeping the most complete/recent version)
|
|
2255
|
+
2. Add the required unique constraint
|
|
2256
|
+
|
|
2257
|
+
Note: This migration may take some time for large tables.
|
|
2258
|
+
===========================================================================
|
|
2259
|
+
`;
|
|
2260
|
+
throw new MastraError({
|
|
2261
|
+
id: createStorageErrorId("PG", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"),
|
|
2262
|
+
domain: ErrorDomain.STORAGE,
|
|
2263
|
+
category: ErrorCategory.USER,
|
|
2264
|
+
text: errorMessage
|
|
2265
|
+
});
|
|
2266
|
+
} else {
|
|
2267
|
+
await this.addSpansPrimaryKey();
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
1942
2270
|
}
|
|
1943
2271
|
} catch (error) {
|
|
2272
|
+
if (error instanceof MastraError) {
|
|
2273
|
+
throw error;
|
|
2274
|
+
}
|
|
1944
2275
|
throw new MastraError(
|
|
1945
2276
|
{
|
|
1946
2277
|
id: createStorageErrorId("PG", "CREATE_TABLE", "FAILED"),
|
|
@@ -2003,12 +2334,29 @@ var PgDB = class extends MastraBase {
|
|
|
2003
2334
|
const columnExists = await this.hasColumn(TABLE_SPANS, columnName);
|
|
2004
2335
|
if (!columnExists) {
|
|
2005
2336
|
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
2006
|
-
const sqlType =
|
|
2337
|
+
const sqlType = mapToSqlType(columnDef.type);
|
|
2007
2338
|
const nullable = columnDef.nullable ? "" : "NOT NULL";
|
|
2008
2339
|
const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
|
|
2009
2340
|
const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
2010
2341
|
await this.client.none(alterSql);
|
|
2011
2342
|
this.logger?.debug?.(`Added column '${columnName}' to ${fullTableName}`);
|
|
2343
|
+
if (sqlType === "TIMESTAMP") {
|
|
2344
|
+
const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}Z" TIMESTAMPTZ DEFAULT NOW()`.trim();
|
|
2345
|
+
await this.client.none(timestampZSql);
|
|
2346
|
+
this.logger?.debug?.(`Added timezone column '${columnName}Z' to ${fullTableName}`);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
for (const [columnName, columnDef] of Object.entries(schema)) {
|
|
2351
|
+
if (columnDef.type === "timestamp") {
|
|
2352
|
+
const tzColumnName = `${columnName}Z`;
|
|
2353
|
+
const tzColumnExists = await this.hasColumn(TABLE_SPANS, tzColumnName);
|
|
2354
|
+
if (!tzColumnExists) {
|
|
2355
|
+
const parsedTzColumnName = parseSqlIdentifier(tzColumnName, "column name");
|
|
2356
|
+
const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedTzColumnName}" TIMESTAMPTZ DEFAULT NOW()`.trim();
|
|
2357
|
+
await this.client.none(timestampZSql);
|
|
2358
|
+
this.logger?.debug?.(`Added timezone column '${tzColumnName}' to ${fullTableName}`);
|
|
2359
|
+
}
|
|
2012
2360
|
}
|
|
2013
2361
|
}
|
|
2014
2362
|
this.logger?.info?.(`Migration completed for ${fullTableName}`);
|
|
@@ -2016,6 +2364,224 @@ var PgDB = class extends MastraBase {
|
|
|
2016
2364
|
this.logger?.warn?.(`Failed to migrate spans table ${fullTableName}:`, error);
|
|
2017
2365
|
}
|
|
2018
2366
|
}
|
|
2367
|
+
/**
|
|
2368
|
+
* Deduplicates spans in the mastra_ai_spans table before adding the PRIMARY KEY constraint.
|
|
2369
|
+
* Keeps spans based on priority: completed (endedAt NOT NULL) > most recent updatedAt > most recent createdAt.
|
|
2370
|
+
*
|
|
2371
|
+
* Note: This prioritizes migration completion over perfect data preservation.
|
|
2372
|
+
* Old trace data may be lost, which is acceptable for this use case.
|
|
2373
|
+
*/
|
|
2374
|
+
async deduplicateSpans() {
|
|
2375
|
+
const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
2376
|
+
try {
|
|
2377
|
+
const duplicateCheck = await this.client.oneOrNone(`
|
|
2378
|
+
SELECT EXISTS (
|
|
2379
|
+
SELECT 1
|
|
2380
|
+
FROM ${fullTableName}
|
|
2381
|
+
GROUP BY "traceId", "spanId"
|
|
2382
|
+
HAVING COUNT(*) > 1
|
|
2383
|
+
LIMIT 1
|
|
2384
|
+
) as has_duplicates
|
|
2385
|
+
`);
|
|
2386
|
+
if (!duplicateCheck?.has_duplicates) {
|
|
2387
|
+
this.logger?.debug?.(`No duplicate spans found in ${fullTableName}`);
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
this.logger?.info?.(`Duplicate spans detected in ${fullTableName}, starting deduplication...`);
|
|
2391
|
+
const result = await this.client.query(`
|
|
2392
|
+
DELETE FROM ${fullTableName} t1
|
|
2393
|
+
USING ${fullTableName} t2
|
|
2394
|
+
WHERE t1."traceId" = t2."traceId"
|
|
2395
|
+
AND t1."spanId" = t2."spanId"
|
|
2396
|
+
AND (
|
|
2397
|
+
-- Keep completed spans over incomplete
|
|
2398
|
+
(t1."endedAt" IS NULL AND t2."endedAt" IS NOT NULL)
|
|
2399
|
+
OR
|
|
2400
|
+
-- If both have same completion status, keep more recent updatedAt
|
|
2401
|
+
(
|
|
2402
|
+
(t1."endedAt" IS NULL) = (t2."endedAt" IS NULL)
|
|
2403
|
+
AND (
|
|
2404
|
+
(t1."updatedAt" < t2."updatedAt")
|
|
2405
|
+
OR (t1."updatedAt" IS NULL AND t2."updatedAt" IS NOT NULL)
|
|
2406
|
+
OR
|
|
2407
|
+
-- If updatedAt is the same, keep more recent createdAt
|
|
2408
|
+
(
|
|
2409
|
+
(t1."updatedAt" = t2."updatedAt" OR (t1."updatedAt" IS NULL AND t2."updatedAt" IS NULL))
|
|
2410
|
+
AND (
|
|
2411
|
+
(t1."createdAt" < t2."createdAt")
|
|
2412
|
+
OR (t1."createdAt" IS NULL AND t2."createdAt" IS NOT NULL)
|
|
2413
|
+
OR
|
|
2414
|
+
-- If all else equal, use ctid as tiebreaker
|
|
2415
|
+
(
|
|
2416
|
+
(t1."createdAt" = t2."createdAt" OR (t1."createdAt" IS NULL AND t2."createdAt" IS NULL))
|
|
2417
|
+
AND t1.ctid < t2.ctid
|
|
2418
|
+
)
|
|
2419
|
+
)
|
|
2420
|
+
)
|
|
2421
|
+
)
|
|
2422
|
+
)
|
|
2423
|
+
)
|
|
2424
|
+
`);
|
|
2425
|
+
this.logger?.info?.(
|
|
2426
|
+
`Deduplication complete: removed ${result.rowCount ?? 0} duplicate spans from ${fullTableName}`
|
|
2427
|
+
);
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
throw new MastraError(
|
|
2430
|
+
{
|
|
2431
|
+
id: createStorageErrorId("PG", "DEDUPLICATE_SPANS", "FAILED"),
|
|
2432
|
+
domain: ErrorDomain.STORAGE,
|
|
2433
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2434
|
+
details: {
|
|
2435
|
+
tableName: TABLE_SPANS
|
|
2436
|
+
}
|
|
2437
|
+
},
|
|
2438
|
+
error
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Checks for duplicate (traceId, spanId) combinations in the spans table.
|
|
2444
|
+
* Returns information about duplicates for logging/CLI purposes.
|
|
2445
|
+
*/
|
|
2446
|
+
async checkForDuplicateSpans() {
|
|
2447
|
+
const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
2448
|
+
try {
|
|
2449
|
+
const result = await this.client.oneOrNone(`
|
|
2450
|
+
SELECT COUNT(*) as duplicate_count
|
|
2451
|
+
FROM (
|
|
2452
|
+
SELECT "traceId", "spanId"
|
|
2453
|
+
FROM ${fullTableName}
|
|
2454
|
+
GROUP BY "traceId", "spanId"
|
|
2455
|
+
HAVING COUNT(*) > 1
|
|
2456
|
+
) duplicates
|
|
2457
|
+
`);
|
|
2458
|
+
const duplicateCount = parseInt(result?.duplicate_count ?? "0", 10);
|
|
2459
|
+
return {
|
|
2460
|
+
hasDuplicates: duplicateCount > 0,
|
|
2461
|
+
duplicateCount,
|
|
2462
|
+
tableName: fullTableName
|
|
2463
|
+
};
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
this.logger?.debug?.(`Could not check for duplicates: ${error}`);
|
|
2466
|
+
return { hasDuplicates: false, duplicateCount: 0, tableName: fullTableName };
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Checks if the PRIMARY KEY constraint on (traceId, spanId) already exists on the spans table.
|
|
2471
|
+
* Used to skip deduplication when the constraint already exists (migration already complete).
|
|
2472
|
+
*/
|
|
2473
|
+
async spansPrimaryKeyExists() {
|
|
2474
|
+
const parsedSchemaName = this.schemaName ? parseSqlIdentifier(this.schemaName, "schema name") : "";
|
|
2475
|
+
const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
|
|
2476
|
+
const constraintName = `${constraintPrefix}mastra_ai_spans_traceid_spanid_pk`;
|
|
2477
|
+
const result = await this.client.oneOrNone(
|
|
2478
|
+
`SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = $1) as exists`,
|
|
2479
|
+
[constraintName]
|
|
2480
|
+
);
|
|
2481
|
+
return result?.exists ?? false;
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Adds the PRIMARY KEY constraint on (traceId, spanId) to the spans table.
|
|
2485
|
+
* Should be called AFTER deduplication to ensure no duplicate key violations.
|
|
2486
|
+
*/
|
|
2487
|
+
async addSpansPrimaryKey() {
|
|
2488
|
+
const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
2489
|
+
const parsedSchemaName = this.schemaName ? parseSqlIdentifier(this.schemaName, "schema name") : "";
|
|
2490
|
+
const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
|
|
2491
|
+
const constraintName = `${constraintPrefix}mastra_ai_spans_traceid_spanid_pk`;
|
|
2492
|
+
try {
|
|
2493
|
+
const constraintExists = await this.client.oneOrNone(
|
|
2494
|
+
`
|
|
2495
|
+
SELECT EXISTS (
|
|
2496
|
+
SELECT 1 FROM pg_constraint WHERE conname = $1
|
|
2497
|
+
) as exists
|
|
2498
|
+
`,
|
|
2499
|
+
[constraintName]
|
|
2500
|
+
);
|
|
2501
|
+
if (constraintExists?.exists) {
|
|
2502
|
+
this.logger?.debug?.(`PRIMARY KEY constraint ${constraintName} already exists on ${fullTableName}`);
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
await this.client.none(`
|
|
2506
|
+
ALTER TABLE ${fullTableName}
|
|
2507
|
+
ADD CONSTRAINT ${constraintName}
|
|
2508
|
+
PRIMARY KEY ("traceId", "spanId")
|
|
2509
|
+
`);
|
|
2510
|
+
this.logger?.info?.(`Added PRIMARY KEY constraint ${constraintName} to ${fullTableName}`);
|
|
2511
|
+
} catch (error) {
|
|
2512
|
+
throw new MastraError(
|
|
2513
|
+
{
|
|
2514
|
+
id: createStorageErrorId("PG", "ADD_SPANS_PRIMARY_KEY", "FAILED"),
|
|
2515
|
+
domain: ErrorDomain.STORAGE,
|
|
2516
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2517
|
+
details: {
|
|
2518
|
+
tableName: TABLE_SPANS,
|
|
2519
|
+
constraintName
|
|
2520
|
+
}
|
|
2521
|
+
},
|
|
2522
|
+
error
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Manually run the spans migration to deduplicate and add the unique constraint.
|
|
2528
|
+
* This is intended to be called from the CLI when duplicates are detected.
|
|
2529
|
+
*
|
|
2530
|
+
* @returns Migration result with status and details
|
|
2531
|
+
*/
|
|
2532
|
+
async migrateSpans() {
|
|
2533
|
+
const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
2534
|
+
const pkExists = await this.spansPrimaryKeyExists();
|
|
2535
|
+
if (pkExists) {
|
|
2536
|
+
return {
|
|
2537
|
+
success: true,
|
|
2538
|
+
alreadyMigrated: true,
|
|
2539
|
+
duplicatesRemoved: 0,
|
|
2540
|
+
message: `Migration already complete. PRIMARY KEY constraint exists on ${fullTableName}.`
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
2544
|
+
if (duplicateInfo.hasDuplicates) {
|
|
2545
|
+
this.logger?.info?.(
|
|
2546
|
+
`Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...`
|
|
2547
|
+
);
|
|
2548
|
+
await this.deduplicateSpans();
|
|
2549
|
+
} else {
|
|
2550
|
+
this.logger?.info?.(`No duplicate spans found.`);
|
|
2551
|
+
}
|
|
2552
|
+
await this.addSpansPrimaryKey();
|
|
2553
|
+
return {
|
|
2554
|
+
success: true,
|
|
2555
|
+
alreadyMigrated: false,
|
|
2556
|
+
duplicatesRemoved: duplicateInfo.duplicateCount,
|
|
2557
|
+
message: duplicateInfo.hasDuplicates ? `Migration complete. Removed duplicates and added PRIMARY KEY constraint to ${fullTableName}.` : `Migration complete. Added PRIMARY KEY constraint to ${fullTableName}.`
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Check migration status for the spans table.
|
|
2562
|
+
* Returns information about whether migration is needed.
|
|
2563
|
+
*/
|
|
2564
|
+
async checkSpansMigrationStatus() {
|
|
2565
|
+
const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
2566
|
+
const pkExists = await this.spansPrimaryKeyExists();
|
|
2567
|
+
if (pkExists) {
|
|
2568
|
+
return {
|
|
2569
|
+
needsMigration: false,
|
|
2570
|
+
hasDuplicates: false,
|
|
2571
|
+
duplicateCount: 0,
|
|
2572
|
+
constraintExists: true,
|
|
2573
|
+
tableName: fullTableName
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
2577
|
+
return {
|
|
2578
|
+
needsMigration: true,
|
|
2579
|
+
hasDuplicates: duplicateInfo.hasDuplicates,
|
|
2580
|
+
duplicateCount: duplicateInfo.duplicateCount,
|
|
2581
|
+
constraintExists: false,
|
|
2582
|
+
tableName: fullTableName
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2019
2585
|
/**
|
|
2020
2586
|
* Alters table schema to add columns if they don't exist
|
|
2021
2587
|
* @param tableName Name of the table
|
|
@@ -2033,7 +2599,7 @@ var PgDB = class extends MastraBase {
|
|
|
2033
2599
|
if (schema[columnName]) {
|
|
2034
2600
|
const columnDef = schema[columnName];
|
|
2035
2601
|
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
2036
|
-
const sqlType =
|
|
2602
|
+
const sqlType = mapToSqlType(columnDef.type);
|
|
2037
2603
|
const nullable = columnDef.nullable ? "" : "NOT NULL";
|
|
2038
2604
|
const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
|
|
2039
2605
|
const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
@@ -2353,9 +2919,9 @@ var PgDB = class extends MastraBase {
|
|
|
2353
2919
|
size: result.size || "0",
|
|
2354
2920
|
definition: result.definition || "",
|
|
2355
2921
|
method: result.method || "btree",
|
|
2356
|
-
scans: parseInt(result.scans) || 0,
|
|
2357
|
-
tuples_read: parseInt(result.tuples_read) || 0,
|
|
2358
|
-
tuples_fetched: parseInt(result.tuples_fetched) || 0
|
|
2922
|
+
scans: parseInt(String(result.scans)) || 0,
|
|
2923
|
+
tuples_read: parseInt(String(result.tuples_read)) || 0,
|
|
2924
|
+
tuples_fetched: parseInt(String(result.tuples_fetched)) || 0
|
|
2359
2925
|
};
|
|
2360
2926
|
} catch (error) {
|
|
2361
2927
|
throw new MastraError(
|
|
@@ -2527,7 +3093,7 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2527
3093
|
#skipDefaultIndexes;
|
|
2528
3094
|
#indexes;
|
|
2529
3095
|
/** Tables managed by this domain */
|
|
2530
|
-
static MANAGED_TABLES = [TABLE_AGENTS];
|
|
3096
|
+
static MANAGED_TABLES = [TABLE_AGENTS, TABLE_AGENT_VERSIONS];
|
|
2531
3097
|
constructor(config) {
|
|
2532
3098
|
super();
|
|
2533
3099
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -2554,6 +3120,7 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2554
3120
|
}
|
|
2555
3121
|
async init() {
|
|
2556
3122
|
await this.#db.createTable({ tableName: TABLE_AGENTS, schema: TABLE_SCHEMAS[TABLE_AGENTS] });
|
|
3123
|
+
await this.#db.createTable({ tableName: TABLE_AGENT_VERSIONS, schema: TABLE_SCHEMAS[TABLE_AGENT_VERSIONS] });
|
|
2557
3124
|
await this.createDefaultIndexes();
|
|
2558
3125
|
await this.createCustomIndexes();
|
|
2559
3126
|
}
|
|
@@ -2573,6 +3140,7 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2573
3140
|
}
|
|
2574
3141
|
}
|
|
2575
3142
|
async dangerouslyClearAll() {
|
|
3143
|
+
await this.#db.clearTable({ tableName: TABLE_AGENT_VERSIONS });
|
|
2576
3144
|
await this.#db.clearTable({ tableName: TABLE_AGENTS });
|
|
2577
3145
|
}
|
|
2578
3146
|
parseJson(value, fieldName) {
|
|
@@ -2610,11 +3178,14 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2610
3178
|
defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"),
|
|
2611
3179
|
workflows: this.parseJson(row.workflows, "workflows"),
|
|
2612
3180
|
agents: this.parseJson(row.agents, "agents"),
|
|
3181
|
+
integrationTools: this.parseJson(row.integrationTools, "integrationTools"),
|
|
2613
3182
|
inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"),
|
|
2614
3183
|
outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"),
|
|
2615
3184
|
memory: this.parseJson(row.memory, "memory"),
|
|
2616
3185
|
scorers: this.parseJson(row.scorers, "scorers"),
|
|
2617
3186
|
metadata: this.parseJson(row.metadata, "metadata"),
|
|
3187
|
+
ownerId: row.ownerId,
|
|
3188
|
+
activeVersionId: row.activeVersionId,
|
|
2618
3189
|
createdAt: row.createdAtZ || row.createdAt,
|
|
2619
3190
|
updatedAt: row.updatedAtZ || row.updatedAt
|
|
2620
3191
|
};
|
|
@@ -2646,10 +3217,12 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2646
3217
|
const nowIso = now.toISOString();
|
|
2647
3218
|
await this.#db.client.none(
|
|
2648
3219
|
`INSERT INTO ${tableName} (
|
|
2649
|
-
id, name, description, instructions, model, tools,
|
|
2650
|
-
"defaultOptions", workflows, agents, "
|
|
3220
|
+
id, name, description, instructions, model, tools,
|
|
3221
|
+
"defaultOptions", workflows, agents, "integrationTools",
|
|
3222
|
+
"inputProcessors", "outputProcessors", memory, scorers, metadata,
|
|
3223
|
+
"ownerId", "activeVersionId",
|
|
2651
3224
|
"createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
|
|
2652
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)`,
|
|
3225
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)`,
|
|
2653
3226
|
[
|
|
2654
3227
|
agent.id,
|
|
2655
3228
|
agent.name,
|
|
@@ -2660,11 +3233,14 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2660
3233
|
agent.defaultOptions ? JSON.stringify(agent.defaultOptions) : null,
|
|
2661
3234
|
agent.workflows ? JSON.stringify(agent.workflows) : null,
|
|
2662
3235
|
agent.agents ? JSON.stringify(agent.agents) : null,
|
|
3236
|
+
agent.integrationTools ? JSON.stringify(agent.integrationTools) : null,
|
|
2663
3237
|
agent.inputProcessors ? JSON.stringify(agent.inputProcessors) : null,
|
|
2664
3238
|
agent.outputProcessors ? JSON.stringify(agent.outputProcessors) : null,
|
|
2665
3239
|
agent.memory ? JSON.stringify(agent.memory) : null,
|
|
2666
3240
|
agent.scorers ? JSON.stringify(agent.scorers) : null,
|
|
2667
3241
|
agent.metadata ? JSON.stringify(agent.metadata) : null,
|
|
3242
|
+
agent.ownerId ?? null,
|
|
3243
|
+
agent.activeVersionId ?? null,
|
|
2668
3244
|
nowIso,
|
|
2669
3245
|
nowIso,
|
|
2670
3246
|
nowIso,
|
|
@@ -2752,6 +3328,18 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2752
3328
|
setClauses.push(`scorers = $${paramIndex++}`);
|
|
2753
3329
|
values.push(JSON.stringify(updates.scorers));
|
|
2754
3330
|
}
|
|
3331
|
+
if (updates.integrationTools !== void 0) {
|
|
3332
|
+
setClauses.push(`"integrationTools" = $${paramIndex++}`);
|
|
3333
|
+
values.push(JSON.stringify(updates.integrationTools));
|
|
3334
|
+
}
|
|
3335
|
+
if (updates.ownerId !== void 0) {
|
|
3336
|
+
setClauses.push(`"ownerId" = $${paramIndex++}`);
|
|
3337
|
+
values.push(updates.ownerId);
|
|
3338
|
+
}
|
|
3339
|
+
if (updates.activeVersionId !== void 0) {
|
|
3340
|
+
setClauses.push(`"activeVersionId" = $${paramIndex++}`);
|
|
3341
|
+
values.push(updates.activeVersionId);
|
|
3342
|
+
}
|
|
2755
3343
|
if (updates.metadata !== void 0) {
|
|
2756
3344
|
const mergedMetadata = { ...existingAgent.metadata, ...updates.metadata };
|
|
2757
3345
|
setClauses.push(`metadata = $${paramIndex++}`);
|
|
@@ -2774,50 +3362,209 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2774
3362
|
throw new MastraError({
|
|
2775
3363
|
id: createStorageErrorId("PG", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"),
|
|
2776
3364
|
domain: ErrorDomain.STORAGE,
|
|
2777
|
-
category: ErrorCategory.SYSTEM,
|
|
2778
|
-
text: `Agent ${id} not found after update`,
|
|
2779
|
-
details: { agentId: id }
|
|
2780
|
-
});
|
|
3365
|
+
category: ErrorCategory.SYSTEM,
|
|
3366
|
+
text: `Agent ${id} not found after update`,
|
|
3367
|
+
details: { agentId: id }
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
return updatedAgent;
|
|
3371
|
+
} catch (error) {
|
|
3372
|
+
if (error instanceof MastraError) {
|
|
3373
|
+
throw error;
|
|
3374
|
+
}
|
|
3375
|
+
throw new MastraError(
|
|
3376
|
+
{
|
|
3377
|
+
id: createStorageErrorId("PG", "UPDATE_AGENT", "FAILED"),
|
|
3378
|
+
domain: ErrorDomain.STORAGE,
|
|
3379
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3380
|
+
details: { agentId: id }
|
|
3381
|
+
},
|
|
3382
|
+
error
|
|
3383
|
+
);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
async deleteAgent({ id }) {
|
|
3387
|
+
try {
|
|
3388
|
+
const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
|
|
3389
|
+
await this.deleteVersionsByAgentId(id);
|
|
3390
|
+
await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
|
|
3391
|
+
} catch (error) {
|
|
3392
|
+
throw new MastraError(
|
|
3393
|
+
{
|
|
3394
|
+
id: createStorageErrorId("PG", "DELETE_AGENT", "FAILED"),
|
|
3395
|
+
domain: ErrorDomain.STORAGE,
|
|
3396
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3397
|
+
details: { agentId: id }
|
|
3398
|
+
},
|
|
3399
|
+
error
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
async listAgents(args) {
|
|
3404
|
+
const { page = 0, perPage: perPageInput, orderBy } = args || {};
|
|
3405
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
3406
|
+
if (page < 0) {
|
|
3407
|
+
throw new MastraError(
|
|
3408
|
+
{
|
|
3409
|
+
id: createStorageErrorId("PG", "LIST_AGENTS", "INVALID_PAGE"),
|
|
3410
|
+
domain: ErrorDomain.STORAGE,
|
|
3411
|
+
category: ErrorCategory.USER,
|
|
3412
|
+
details: { page }
|
|
3413
|
+
},
|
|
3414
|
+
new Error("page must be >= 0")
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
3418
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
3419
|
+
try {
|
|
3420
|
+
const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
|
|
3421
|
+
const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName}`);
|
|
3422
|
+
const total = parseInt(countResult.count, 10);
|
|
3423
|
+
if (total === 0) {
|
|
3424
|
+
return {
|
|
3425
|
+
agents: [],
|
|
3426
|
+
total: 0,
|
|
3427
|
+
page,
|
|
3428
|
+
perPage: perPageForResponse,
|
|
3429
|
+
hasMore: false
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
3433
|
+
const dataResult = await this.#db.client.manyOrNone(
|
|
3434
|
+
`SELECT * FROM ${tableName} ORDER BY "${field}" ${direction} LIMIT $1 OFFSET $2`,
|
|
3435
|
+
[limitValue, offset]
|
|
3436
|
+
);
|
|
3437
|
+
const agents = (dataResult || []).map((row) => this.parseRow(row));
|
|
3438
|
+
return {
|
|
3439
|
+
agents,
|
|
3440
|
+
total,
|
|
3441
|
+
page,
|
|
3442
|
+
perPage: perPageForResponse,
|
|
3443
|
+
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
3444
|
+
};
|
|
3445
|
+
} catch (error) {
|
|
3446
|
+
throw new MastraError(
|
|
3447
|
+
{
|
|
3448
|
+
id: createStorageErrorId("PG", "LIST_AGENTS", "FAILED"),
|
|
3449
|
+
domain: ErrorDomain.STORAGE,
|
|
3450
|
+
category: ErrorCategory.THIRD_PARTY
|
|
3451
|
+
},
|
|
3452
|
+
error
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
// ==========================================================================
|
|
3457
|
+
// Agent Version Methods
|
|
3458
|
+
// ==========================================================================
|
|
3459
|
+
async createVersion(input) {
|
|
3460
|
+
try {
|
|
3461
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3462
|
+
const now = /* @__PURE__ */ new Date();
|
|
3463
|
+
const nowIso = now.toISOString();
|
|
3464
|
+
await this.#db.client.none(
|
|
3465
|
+
`INSERT INTO ${tableName} (
|
|
3466
|
+
id, "agentId", "versionNumber", name, snapshot, "changedFields", "changeMessage", "createdAt", "createdAtZ"
|
|
3467
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
3468
|
+
[
|
|
3469
|
+
input.id,
|
|
3470
|
+
input.agentId,
|
|
3471
|
+
input.versionNumber,
|
|
3472
|
+
input.name ?? null,
|
|
3473
|
+
JSON.stringify(input.snapshot),
|
|
3474
|
+
input.changedFields ? JSON.stringify(input.changedFields) : null,
|
|
3475
|
+
input.changeMessage ?? null,
|
|
3476
|
+
nowIso,
|
|
3477
|
+
nowIso
|
|
3478
|
+
]
|
|
3479
|
+
);
|
|
3480
|
+
return {
|
|
3481
|
+
...input,
|
|
3482
|
+
createdAt: now
|
|
3483
|
+
};
|
|
3484
|
+
} catch (error) {
|
|
3485
|
+
throw new MastraError(
|
|
3486
|
+
{
|
|
3487
|
+
id: createStorageErrorId("PG", "CREATE_VERSION", "FAILED"),
|
|
3488
|
+
domain: ErrorDomain.STORAGE,
|
|
3489
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3490
|
+
details: { versionId: input.id, agentId: input.agentId }
|
|
3491
|
+
},
|
|
3492
|
+
error
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
async getVersion(id) {
|
|
3497
|
+
try {
|
|
3498
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3499
|
+
const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
|
|
3500
|
+
if (!result) {
|
|
3501
|
+
return null;
|
|
2781
3502
|
}
|
|
2782
|
-
return
|
|
3503
|
+
return this.parseVersionRow(result);
|
|
2783
3504
|
} catch (error) {
|
|
2784
|
-
|
|
2785
|
-
|
|
3505
|
+
throw new MastraError(
|
|
3506
|
+
{
|
|
3507
|
+
id: createStorageErrorId("PG", "GET_VERSION", "FAILED"),
|
|
3508
|
+
domain: ErrorDomain.STORAGE,
|
|
3509
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3510
|
+
details: { versionId: id }
|
|
3511
|
+
},
|
|
3512
|
+
error
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
async getVersionByNumber(agentId, versionNumber) {
|
|
3517
|
+
try {
|
|
3518
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3519
|
+
const result = await this.#db.client.oneOrNone(
|
|
3520
|
+
`SELECT * FROM ${tableName} WHERE "agentId" = $1 AND "versionNumber" = $2`,
|
|
3521
|
+
[agentId, versionNumber]
|
|
3522
|
+
);
|
|
3523
|
+
if (!result) {
|
|
3524
|
+
return null;
|
|
2786
3525
|
}
|
|
3526
|
+
return this.parseVersionRow(result);
|
|
3527
|
+
} catch (error) {
|
|
2787
3528
|
throw new MastraError(
|
|
2788
3529
|
{
|
|
2789
|
-
id: createStorageErrorId("PG", "
|
|
3530
|
+
id: createStorageErrorId("PG", "GET_VERSION_BY_NUMBER", "FAILED"),
|
|
2790
3531
|
domain: ErrorDomain.STORAGE,
|
|
2791
3532
|
category: ErrorCategory.THIRD_PARTY,
|
|
2792
|
-
details: { agentId
|
|
3533
|
+
details: { agentId, versionNumber }
|
|
2793
3534
|
},
|
|
2794
3535
|
error
|
|
2795
3536
|
);
|
|
2796
3537
|
}
|
|
2797
3538
|
}
|
|
2798
|
-
async
|
|
3539
|
+
async getLatestVersion(agentId) {
|
|
2799
3540
|
try {
|
|
2800
|
-
const tableName = getTableName2({ indexName:
|
|
2801
|
-
await this.#db.client.
|
|
3541
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3542
|
+
const result = await this.#db.client.oneOrNone(
|
|
3543
|
+
`SELECT * FROM ${tableName} WHERE "agentId" = $1 ORDER BY "versionNumber" DESC LIMIT 1`,
|
|
3544
|
+
[agentId]
|
|
3545
|
+
);
|
|
3546
|
+
if (!result) {
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
return this.parseVersionRow(result);
|
|
2802
3550
|
} catch (error) {
|
|
2803
3551
|
throw new MastraError(
|
|
2804
3552
|
{
|
|
2805
|
-
id: createStorageErrorId("PG", "
|
|
3553
|
+
id: createStorageErrorId("PG", "GET_LATEST_VERSION", "FAILED"),
|
|
2806
3554
|
domain: ErrorDomain.STORAGE,
|
|
2807
3555
|
category: ErrorCategory.THIRD_PARTY,
|
|
2808
|
-
details: { agentId
|
|
3556
|
+
details: { agentId }
|
|
2809
3557
|
},
|
|
2810
3558
|
error
|
|
2811
3559
|
);
|
|
2812
3560
|
}
|
|
2813
3561
|
}
|
|
2814
|
-
async
|
|
2815
|
-
const { page = 0, perPage: perPageInput, orderBy } =
|
|
2816
|
-
const { field, direction } = this.parseOrderBy(orderBy);
|
|
3562
|
+
async listVersions(input) {
|
|
3563
|
+
const { agentId, page = 0, perPage: perPageInput, orderBy } = input;
|
|
2817
3564
|
if (page < 0) {
|
|
2818
3565
|
throw new MastraError(
|
|
2819
3566
|
{
|
|
2820
|
-
id: createStorageErrorId("PG", "
|
|
3567
|
+
id: createStorageErrorId("PG", "LIST_VERSIONS", "INVALID_PAGE"),
|
|
2821
3568
|
domain: ErrorDomain.STORAGE,
|
|
2822
3569
|
category: ErrorCategory.USER,
|
|
2823
3570
|
details: { page }
|
|
@@ -2825,15 +3572,18 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2825
3572
|
new Error("page must be >= 0")
|
|
2826
3573
|
);
|
|
2827
3574
|
}
|
|
2828
|
-
const perPage = normalizePerPage(perPageInput,
|
|
3575
|
+
const perPage = normalizePerPage(perPageInput, 20);
|
|
2829
3576
|
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
2830
3577
|
try {
|
|
2831
|
-
const
|
|
2832
|
-
const
|
|
3578
|
+
const { field, direction } = this.parseVersionOrderBy(orderBy);
|
|
3579
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3580
|
+
const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName} WHERE "agentId" = $1`, [
|
|
3581
|
+
agentId
|
|
3582
|
+
]);
|
|
2833
3583
|
const total = parseInt(countResult.count, 10);
|
|
2834
3584
|
if (total === 0) {
|
|
2835
3585
|
return {
|
|
2836
|
-
|
|
3586
|
+
versions: [],
|
|
2837
3587
|
total: 0,
|
|
2838
3588
|
page,
|
|
2839
3589
|
perPage: perPageForResponse,
|
|
@@ -2842,12 +3592,12 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2842
3592
|
}
|
|
2843
3593
|
const limitValue = perPageInput === false ? total : perPage;
|
|
2844
3594
|
const dataResult = await this.#db.client.manyOrNone(
|
|
2845
|
-
`SELECT * FROM ${tableName} ORDER BY "${field}" ${direction} LIMIT $
|
|
2846
|
-
[limitValue, offset]
|
|
3595
|
+
`SELECT * FROM ${tableName} WHERE "agentId" = $1 ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`,
|
|
3596
|
+
[agentId, limitValue, offset]
|
|
2847
3597
|
);
|
|
2848
|
-
const
|
|
3598
|
+
const versions = (dataResult || []).map((row) => this.parseVersionRow(row));
|
|
2849
3599
|
return {
|
|
2850
|
-
|
|
3600
|
+
versions,
|
|
2851
3601
|
total,
|
|
2852
3602
|
page,
|
|
2853
3603
|
perPage: perPageForResponse,
|
|
@@ -2856,14 +3606,81 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
|
|
|
2856
3606
|
} catch (error) {
|
|
2857
3607
|
throw new MastraError(
|
|
2858
3608
|
{
|
|
2859
|
-
id: createStorageErrorId("PG", "
|
|
3609
|
+
id: createStorageErrorId("PG", "LIST_VERSIONS", "FAILED"),
|
|
2860
3610
|
domain: ErrorDomain.STORAGE,
|
|
2861
|
-
category: ErrorCategory.THIRD_PARTY
|
|
3611
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3612
|
+
details: { agentId }
|
|
3613
|
+
},
|
|
3614
|
+
error
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
async deleteVersion(id) {
|
|
3619
|
+
try {
|
|
3620
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3621
|
+
await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
|
|
3622
|
+
} catch (error) {
|
|
3623
|
+
throw new MastraError(
|
|
3624
|
+
{
|
|
3625
|
+
id: createStorageErrorId("PG", "DELETE_VERSION", "FAILED"),
|
|
3626
|
+
domain: ErrorDomain.STORAGE,
|
|
3627
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3628
|
+
details: { versionId: id }
|
|
3629
|
+
},
|
|
3630
|
+
error
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
async deleteVersionsByAgentId(agentId) {
|
|
3635
|
+
try {
|
|
3636
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3637
|
+
await this.#db.client.none(`DELETE FROM ${tableName} WHERE "agentId" = $1`, [agentId]);
|
|
3638
|
+
} catch (error) {
|
|
3639
|
+
throw new MastraError(
|
|
3640
|
+
{
|
|
3641
|
+
id: createStorageErrorId("PG", "DELETE_VERSIONS_BY_AGENT_ID", "FAILED"),
|
|
3642
|
+
domain: ErrorDomain.STORAGE,
|
|
3643
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3644
|
+
details: { agentId }
|
|
3645
|
+
},
|
|
3646
|
+
error
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
async countVersions(agentId) {
|
|
3651
|
+
try {
|
|
3652
|
+
const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
|
|
3653
|
+
const result = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName} WHERE "agentId" = $1`, [
|
|
3654
|
+
agentId
|
|
3655
|
+
]);
|
|
3656
|
+
return parseInt(result.count, 10);
|
|
3657
|
+
} catch (error) {
|
|
3658
|
+
throw new MastraError(
|
|
3659
|
+
{
|
|
3660
|
+
id: createStorageErrorId("PG", "COUNT_VERSIONS", "FAILED"),
|
|
3661
|
+
domain: ErrorDomain.STORAGE,
|
|
3662
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3663
|
+
details: { agentId }
|
|
2862
3664
|
},
|
|
2863
3665
|
error
|
|
2864
3666
|
);
|
|
2865
3667
|
}
|
|
2866
3668
|
}
|
|
3669
|
+
// ==========================================================================
|
|
3670
|
+
// Private Helper Methods
|
|
3671
|
+
// ==========================================================================
|
|
3672
|
+
parseVersionRow(row) {
|
|
3673
|
+
return {
|
|
3674
|
+
id: row.id,
|
|
3675
|
+
agentId: row.agentId,
|
|
3676
|
+
versionNumber: row.versionNumber,
|
|
3677
|
+
name: row.name,
|
|
3678
|
+
snapshot: this.parseJson(row.snapshot, "snapshot"),
|
|
3679
|
+
changedFields: this.parseJson(row.changedFields, "changedFields"),
|
|
3680
|
+
changeMessage: row.changeMessage,
|
|
3681
|
+
createdAt: row.createdAtZ || row.createdAt
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
2867
3684
|
};
|
|
2868
3685
|
function getSchemaName3(schema) {
|
|
2869
3686
|
return schema ? `"${schema}"` : '"public"';
|
|
@@ -2872,6 +3689,9 @@ function getTableName3({ indexName, schemaName }) {
|
|
|
2872
3689
|
const quotedIndexName = `"${indexName}"`;
|
|
2873
3690
|
return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
|
|
2874
3691
|
}
|
|
3692
|
+
function inPlaceholders(count, startIndex = 1) {
|
|
3693
|
+
return Array.from({ length: count }, (_, i) => `$${i + startIndex}`).join(", ");
|
|
3694
|
+
}
|
|
2875
3695
|
var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
2876
3696
|
#db;
|
|
2877
3697
|
#schema;
|
|
@@ -2998,27 +3818,52 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
2998
3818
|
);
|
|
2999
3819
|
}
|
|
3000
3820
|
}
|
|
3001
|
-
async
|
|
3002
|
-
const {
|
|
3003
|
-
|
|
3821
|
+
async listThreads(args) {
|
|
3822
|
+
const { page = 0, perPage: perPageInput, orderBy, filter } = args;
|
|
3823
|
+
try {
|
|
3824
|
+
this.validatePaginationInput(page, perPageInput ?? 100);
|
|
3825
|
+
} catch (error) {
|
|
3004
3826
|
throw new MastraError({
|
|
3005
|
-
id: createStorageErrorId("PG", "
|
|
3827
|
+
id: createStorageErrorId("PG", "LIST_THREADS", "INVALID_PAGE"),
|
|
3006
3828
|
domain: ErrorDomain.STORAGE,
|
|
3007
3829
|
category: ErrorCategory.USER,
|
|
3008
|
-
text:
|
|
3009
|
-
details: {
|
|
3010
|
-
resourceId,
|
|
3011
|
-
page
|
|
3012
|
-
}
|
|
3830
|
+
text: error instanceof Error ? error.message : "Invalid pagination parameters",
|
|
3831
|
+
details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
|
|
3013
3832
|
});
|
|
3014
3833
|
}
|
|
3015
|
-
const { field, direction } = this.parseOrderBy(orderBy);
|
|
3016
3834
|
const perPage = normalizePerPage(perPageInput, 100);
|
|
3835
|
+
try {
|
|
3836
|
+
this.validateMetadataKeys(filter?.metadata);
|
|
3837
|
+
} catch (error) {
|
|
3838
|
+
throw new MastraError({
|
|
3839
|
+
id: createStorageErrorId("PG", "LIST_THREADS", "INVALID_METADATA_KEY"),
|
|
3840
|
+
domain: ErrorDomain.STORAGE,
|
|
3841
|
+
category: ErrorCategory.USER,
|
|
3842
|
+
text: error instanceof Error ? error.message : "Invalid metadata key",
|
|
3843
|
+
details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
|
|
3844
|
+
});
|
|
3845
|
+
}
|
|
3846
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
3017
3847
|
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
3018
3848
|
try {
|
|
3019
3849
|
const tableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
|
|
3020
|
-
const
|
|
3021
|
-
const queryParams = [
|
|
3850
|
+
const whereClauses = [];
|
|
3851
|
+
const queryParams = [];
|
|
3852
|
+
let paramIndex = 1;
|
|
3853
|
+
if (filter?.resourceId) {
|
|
3854
|
+
whereClauses.push(`"resourceId" = $${paramIndex}`);
|
|
3855
|
+
queryParams.push(filter.resourceId);
|
|
3856
|
+
paramIndex++;
|
|
3857
|
+
}
|
|
3858
|
+
if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
|
|
3859
|
+
for (const [key, value] of Object.entries(filter.metadata)) {
|
|
3860
|
+
whereClauses.push(`metadata::jsonb @> $${paramIndex}::jsonb`);
|
|
3861
|
+
queryParams.push(JSON.stringify({ [key]: value }));
|
|
3862
|
+
paramIndex++;
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
3866
|
+
const baseQuery = `FROM ${tableName} ${whereClause}`;
|
|
3022
3867
|
const countQuery = `SELECT COUNT(*) ${baseQuery}`;
|
|
3023
3868
|
const countResult = await this.#db.client.one(countQuery, queryParams);
|
|
3024
3869
|
const total = parseInt(countResult.count, 10);
|
|
@@ -3032,13 +3877,19 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3032
3877
|
};
|
|
3033
3878
|
}
|
|
3034
3879
|
const limitValue = perPageInput === false ? total : perPage;
|
|
3035
|
-
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT
|
|
3036
|
-
const rows = await this.#db.client.manyOrNone(
|
|
3880
|
+
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
|
3881
|
+
const rows = await this.#db.client.manyOrNone(
|
|
3882
|
+
dataQuery,
|
|
3883
|
+
[...queryParams, limitValue, offset]
|
|
3884
|
+
);
|
|
3037
3885
|
const threads = (rows || []).map((thread) => ({
|
|
3038
|
-
|
|
3886
|
+
id: thread.id,
|
|
3887
|
+
resourceId: thread.resourceId,
|
|
3888
|
+
title: thread.title,
|
|
3039
3889
|
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
3040
|
-
|
|
3041
|
-
|
|
3890
|
+
// Use timezone-aware columns (*Z) for correct UTC timestamps, with fallback for legacy data
|
|
3891
|
+
createdAt: thread.createdAtZ || thread.createdAt,
|
|
3892
|
+
updatedAt: thread.updatedAtZ || thread.updatedAt
|
|
3042
3893
|
}));
|
|
3043
3894
|
return {
|
|
3044
3895
|
threads,
|
|
@@ -3050,11 +3901,12 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3050
3901
|
} catch (error) {
|
|
3051
3902
|
const mastraError = new MastraError(
|
|
3052
3903
|
{
|
|
3053
|
-
id: createStorageErrorId("PG", "
|
|
3904
|
+
id: createStorageErrorId("PG", "LIST_THREADS", "FAILED"),
|
|
3054
3905
|
domain: ErrorDomain.STORAGE,
|
|
3055
3906
|
category: ErrorCategory.THIRD_PARTY,
|
|
3056
3907
|
details: {
|
|
3057
|
-
resourceId,
|
|
3908
|
+
...filter?.resourceId && { resourceId: filter.resourceId },
|
|
3909
|
+
hasMetadataFilter: !!filter?.metadata,
|
|
3058
3910
|
page
|
|
3059
3911
|
}
|
|
3060
3912
|
},
|
|
@@ -3143,17 +3995,18 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3143
3995
|
...metadata
|
|
3144
3996
|
};
|
|
3145
3997
|
try {
|
|
3998
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3146
3999
|
const thread = await this.#db.client.one(
|
|
3147
4000
|
`UPDATE ${threadTableName}
|
|
3148
4001
|
SET
|
|
3149
4002
|
title = $1,
|
|
3150
4003
|
metadata = $2,
|
|
3151
4004
|
"updatedAt" = $3,
|
|
3152
|
-
"updatedAtZ" = $
|
|
3153
|
-
WHERE id = $
|
|
4005
|
+
"updatedAtZ" = $4
|
|
4006
|
+
WHERE id = $5
|
|
3154
4007
|
RETURNING *
|
|
3155
4008
|
`,
|
|
3156
|
-
[title, mergedMetadata,
|
|
4009
|
+
[title, mergedMetadata, now, now, id]
|
|
3157
4010
|
);
|
|
3158
4011
|
return {
|
|
3159
4012
|
id: thread.id,
|
|
@@ -3296,7 +4149,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3296
4149
|
const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
|
|
3297
4150
|
const query = `
|
|
3298
4151
|
${selectStatement} FROM ${tableName}
|
|
3299
|
-
WHERE id IN (${messageIds.
|
|
4152
|
+
WHERE id IN (${inPlaceholders(messageIds.length)})
|
|
3300
4153
|
ORDER BY "createdAt" DESC
|
|
3301
4154
|
`;
|
|
3302
4155
|
const resultRows = await this.#db.client.manyOrNone(query, messageIds);
|
|
@@ -3357,8 +4210,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3357
4210
|
const orderByStatement = `ORDER BY "${field}" ${direction}`;
|
|
3358
4211
|
const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
|
|
3359
4212
|
const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
|
|
3360
|
-
const
|
|
3361
|
-
const conditions = [`thread_id IN (${threadPlaceholders})`];
|
|
4213
|
+
const conditions = [`thread_id IN (${inPlaceholders(threadIds.length)})`];
|
|
3362
4214
|
const queryParams = [...threadIds];
|
|
3363
4215
|
let paramIndex = threadIds.length + 1;
|
|
3364
4216
|
if (resourceId) {
|
|
@@ -3366,11 +4218,13 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3366
4218
|
queryParams.push(resourceId);
|
|
3367
4219
|
}
|
|
3368
4220
|
if (filter?.dateRange?.start) {
|
|
3369
|
-
|
|
4221
|
+
const startOp = filter.dateRange.startExclusive ? ">" : ">=";
|
|
4222
|
+
conditions.push(`"createdAt" ${startOp} $${paramIndex++}`);
|
|
3370
4223
|
queryParams.push(filter.dateRange.start);
|
|
3371
4224
|
}
|
|
3372
4225
|
if (filter?.dateRange?.end) {
|
|
3373
|
-
|
|
4226
|
+
const endOp = filter.dateRange.endExclusive ? "<" : "<=";
|
|
4227
|
+
conditions.push(`"createdAt" ${endOp} $${paramIndex++}`);
|
|
3374
4228
|
queryParams.push(filter.dateRange.end);
|
|
3375
4229
|
}
|
|
3376
4230
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
@@ -3515,14 +4369,15 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3515
4369
|
);
|
|
3516
4370
|
});
|
|
3517
4371
|
const threadTableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
|
|
4372
|
+
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
3518
4373
|
const threadUpdate = t.none(
|
|
3519
4374
|
`UPDATE ${threadTableName}
|
|
3520
4375
|
SET
|
|
3521
4376
|
"updatedAt" = $1,
|
|
3522
|
-
"updatedAtZ" = $
|
|
3523
|
-
WHERE id = $
|
|
4377
|
+
"updatedAtZ" = $2
|
|
4378
|
+
WHERE id = $3
|
|
3524
4379
|
`,
|
|
3525
|
-
[
|
|
4380
|
+
[nowStr, nowStr, threadId]
|
|
3526
4381
|
);
|
|
3527
4382
|
await Promise.all([...messageInserts, threadUpdate]);
|
|
3528
4383
|
});
|
|
@@ -3559,8 +4414,8 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3559
4414
|
return [];
|
|
3560
4415
|
}
|
|
3561
4416
|
const messageIds = messages.map((m) => m.id);
|
|
3562
|
-
const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) })} WHERE id IN ($
|
|
3563
|
-
const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery,
|
|
4417
|
+
const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) })} WHERE id IN (${inPlaceholders(messageIds.length)})`;
|
|
4418
|
+
const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, messageIds);
|
|
3564
4419
|
if (existingMessagesDb.length === 0) {
|
|
3565
4420
|
return [];
|
|
3566
4421
|
}
|
|
@@ -3621,10 +4476,11 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3621
4476
|
}
|
|
3622
4477
|
}
|
|
3623
4478
|
if (threadIdsToUpdate.size > 0) {
|
|
4479
|
+
const threadIds = Array.from(threadIdsToUpdate);
|
|
3624
4480
|
queries.push(
|
|
3625
4481
|
t.none(
|
|
3626
|
-
`UPDATE ${getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN ($
|
|
3627
|
-
|
|
4482
|
+
`UPDATE ${getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`,
|
|
4483
|
+
threadIds
|
|
3628
4484
|
)
|
|
3629
4485
|
);
|
|
3630
4486
|
}
|
|
@@ -3632,7 +4488,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3632
4488
|
await t.batch(queries);
|
|
3633
4489
|
}
|
|
3634
4490
|
});
|
|
3635
|
-
const updatedMessages = await this.#db.client.manyOrNone(selectQuery,
|
|
4491
|
+
const updatedMessages = await this.#db.client.manyOrNone(selectQuery, messageIds);
|
|
3636
4492
|
return (updatedMessages || []).map((row) => {
|
|
3637
4493
|
const message = this.normalizeMessageRow(row);
|
|
3638
4494
|
if (typeof message.content === "string") {
|
|
@@ -3744,15 +4600,159 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
3744
4600
|
values.push(JSON.stringify(updatedResource.metadata));
|
|
3745
4601
|
paramIndex++;
|
|
3746
4602
|
}
|
|
3747
|
-
|
|
3748
|
-
|
|
4603
|
+
const updatedAtStr = updatedResource.updatedAt.toISOString();
|
|
4604
|
+
updates.push(`"updatedAt" = $${paramIndex++}`);
|
|
4605
|
+
values.push(updatedAtStr);
|
|
3749
4606
|
updates.push(`"updatedAtZ" = $${paramIndex++}`);
|
|
3750
|
-
values.push(
|
|
3751
|
-
paramIndex++;
|
|
4607
|
+
values.push(updatedAtStr);
|
|
3752
4608
|
values.push(resourceId);
|
|
3753
4609
|
await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
|
|
3754
4610
|
return updatedResource;
|
|
3755
4611
|
}
|
|
4612
|
+
async cloneThread(args) {
|
|
4613
|
+
const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
|
|
4614
|
+
const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
|
|
4615
|
+
if (!sourceThread) {
|
|
4616
|
+
throw new MastraError({
|
|
4617
|
+
id: createStorageErrorId("PG", "CLONE_THREAD", "SOURCE_NOT_FOUND"),
|
|
4618
|
+
domain: ErrorDomain.STORAGE,
|
|
4619
|
+
category: ErrorCategory.USER,
|
|
4620
|
+
text: `Source thread with id ${sourceThreadId} not found`,
|
|
4621
|
+
details: { sourceThreadId }
|
|
4622
|
+
});
|
|
4623
|
+
}
|
|
4624
|
+
const newThreadId = providedThreadId || crypto.randomUUID();
|
|
4625
|
+
const existingThread = await this.getThreadById({ threadId: newThreadId });
|
|
4626
|
+
if (existingThread) {
|
|
4627
|
+
throw new MastraError({
|
|
4628
|
+
id: createStorageErrorId("PG", "CLONE_THREAD", "THREAD_EXISTS"),
|
|
4629
|
+
domain: ErrorDomain.STORAGE,
|
|
4630
|
+
category: ErrorCategory.USER,
|
|
4631
|
+
text: `Thread with id ${newThreadId} already exists`,
|
|
4632
|
+
details: { newThreadId }
|
|
4633
|
+
});
|
|
4634
|
+
}
|
|
4635
|
+
const threadTableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
|
|
4636
|
+
const messageTableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
|
|
4637
|
+
try {
|
|
4638
|
+
return await this.#db.client.tx(async (t) => {
|
|
4639
|
+
let messageQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"
|
|
4640
|
+
FROM ${messageTableName} WHERE thread_id = $1`;
|
|
4641
|
+
const messageParams = [sourceThreadId];
|
|
4642
|
+
let paramIndex = 2;
|
|
4643
|
+
if (options?.messageFilter?.startDate) {
|
|
4644
|
+
messageQuery += ` AND "createdAt" >= $${paramIndex++}`;
|
|
4645
|
+
messageParams.push(options.messageFilter.startDate);
|
|
4646
|
+
}
|
|
4647
|
+
if (options?.messageFilter?.endDate) {
|
|
4648
|
+
messageQuery += ` AND "createdAt" <= $${paramIndex++}`;
|
|
4649
|
+
messageParams.push(options.messageFilter.endDate);
|
|
4650
|
+
}
|
|
4651
|
+
if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) {
|
|
4652
|
+
messageQuery += ` AND id IN (${options.messageFilter.messageIds.map(() => `$${paramIndex++}`).join(", ")})`;
|
|
4653
|
+
messageParams.push(...options.messageFilter.messageIds);
|
|
4654
|
+
}
|
|
4655
|
+
messageQuery += ` ORDER BY "createdAt" ASC`;
|
|
4656
|
+
if (options?.messageLimit && options.messageLimit > 0) {
|
|
4657
|
+
const limitQuery = `SELECT * FROM (${messageQuery.replace('ORDER BY "createdAt" ASC', 'ORDER BY "createdAt" DESC')} LIMIT $${paramIndex}) AS limited ORDER BY "createdAt" ASC`;
|
|
4658
|
+
messageParams.push(options.messageLimit);
|
|
4659
|
+
messageQuery = limitQuery;
|
|
4660
|
+
}
|
|
4661
|
+
const sourceMessages = await t.manyOrNone(messageQuery, messageParams);
|
|
4662
|
+
const now = /* @__PURE__ */ new Date();
|
|
4663
|
+
const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
|
|
4664
|
+
const cloneMetadata = {
|
|
4665
|
+
sourceThreadId,
|
|
4666
|
+
clonedAt: now,
|
|
4667
|
+
...lastMessageId && { lastMessageId }
|
|
4668
|
+
};
|
|
4669
|
+
const newThread = {
|
|
4670
|
+
id: newThreadId,
|
|
4671
|
+
resourceId: resourceId || sourceThread.resourceId,
|
|
4672
|
+
title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : void 0),
|
|
4673
|
+
metadata: {
|
|
4674
|
+
...metadata,
|
|
4675
|
+
clone: cloneMetadata
|
|
4676
|
+
},
|
|
4677
|
+
createdAt: now,
|
|
4678
|
+
updatedAt: now
|
|
4679
|
+
};
|
|
4680
|
+
await t.none(
|
|
4681
|
+
`INSERT INTO ${threadTableName} (
|
|
4682
|
+
id,
|
|
4683
|
+
"resourceId",
|
|
4684
|
+
title,
|
|
4685
|
+
metadata,
|
|
4686
|
+
"createdAt",
|
|
4687
|
+
"createdAtZ",
|
|
4688
|
+
"updatedAt",
|
|
4689
|
+
"updatedAtZ"
|
|
4690
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
4691
|
+
[
|
|
4692
|
+
newThread.id,
|
|
4693
|
+
newThread.resourceId,
|
|
4694
|
+
newThread.title,
|
|
4695
|
+
newThread.metadata ? JSON.stringify(newThread.metadata) : null,
|
|
4696
|
+
now,
|
|
4697
|
+
now,
|
|
4698
|
+
now,
|
|
4699
|
+
now
|
|
4700
|
+
]
|
|
4701
|
+
);
|
|
4702
|
+
const clonedMessages = [];
|
|
4703
|
+
const targetResourceId = resourceId || sourceThread.resourceId;
|
|
4704
|
+
for (const sourceMsg of sourceMessages) {
|
|
4705
|
+
const newMessageId = crypto.randomUUID();
|
|
4706
|
+
const normalizedMsg = this.normalizeMessageRow(sourceMsg);
|
|
4707
|
+
let parsedContent = normalizedMsg.content;
|
|
4708
|
+
try {
|
|
4709
|
+
parsedContent = JSON.parse(normalizedMsg.content);
|
|
4710
|
+
} catch {
|
|
4711
|
+
}
|
|
4712
|
+
await t.none(
|
|
4713
|
+
`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
|
|
4714
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
4715
|
+
[
|
|
4716
|
+
newMessageId,
|
|
4717
|
+
newThreadId,
|
|
4718
|
+
typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
|
|
4719
|
+
normalizedMsg.createdAt,
|
|
4720
|
+
normalizedMsg.createdAt,
|
|
4721
|
+
normalizedMsg.role,
|
|
4722
|
+
normalizedMsg.type || "v2",
|
|
4723
|
+
targetResourceId
|
|
4724
|
+
]
|
|
4725
|
+
);
|
|
4726
|
+
clonedMessages.push({
|
|
4727
|
+
id: newMessageId,
|
|
4728
|
+
threadId: newThreadId,
|
|
4729
|
+
content: parsedContent,
|
|
4730
|
+
role: normalizedMsg.role,
|
|
4731
|
+
type: normalizedMsg.type,
|
|
4732
|
+
createdAt: new Date(normalizedMsg.createdAt),
|
|
4733
|
+
resourceId: targetResourceId
|
|
4734
|
+
});
|
|
4735
|
+
}
|
|
4736
|
+
return {
|
|
4737
|
+
thread: newThread,
|
|
4738
|
+
clonedMessages
|
|
4739
|
+
};
|
|
4740
|
+
});
|
|
4741
|
+
} catch (error) {
|
|
4742
|
+
if (error instanceof MastraError) {
|
|
4743
|
+
throw error;
|
|
4744
|
+
}
|
|
4745
|
+
throw new MastraError(
|
|
4746
|
+
{
|
|
4747
|
+
id: createStorageErrorId("PG", "CLONE_THREAD", "FAILED"),
|
|
4748
|
+
domain: ErrorDomain.STORAGE,
|
|
4749
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
4750
|
+
details: { sourceThreadId, newThreadId }
|
|
4751
|
+
},
|
|
4752
|
+
error
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
3756
4756
|
};
|
|
3757
4757
|
var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
|
|
3758
4758
|
#db;
|
|
@@ -3870,6 +4870,22 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
|
|
|
3870
4870
|
}
|
|
3871
4871
|
}
|
|
3872
4872
|
}
|
|
4873
|
+
/**
|
|
4874
|
+
* Manually run the spans migration to deduplicate and add the unique constraint.
|
|
4875
|
+
* This is intended to be called from the CLI when duplicates are detected.
|
|
4876
|
+
*
|
|
4877
|
+
* @returns Migration result with status and details
|
|
4878
|
+
*/
|
|
4879
|
+
async migrateSpans() {
|
|
4880
|
+
return this.#db.migrateSpans();
|
|
4881
|
+
}
|
|
4882
|
+
/**
|
|
4883
|
+
* Check migration status for the spans table.
|
|
4884
|
+
* Returns information about whether migration is needed.
|
|
4885
|
+
*/
|
|
4886
|
+
async checkSpansMigrationStatus() {
|
|
4887
|
+
return this.#db.checkSpansMigrationStatus();
|
|
4888
|
+
}
|
|
3873
4889
|
async dangerouslyClearAll() {
|
|
3874
4890
|
await this.#db.clearTable({ tableName: TABLE_SPANS });
|
|
3875
4891
|
}
|
|
@@ -4251,11 +5267,13 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
|
|
|
4251
5267
|
perPage,
|
|
4252
5268
|
hasMore: (page + 1) * perPage < count
|
|
4253
5269
|
},
|
|
4254
|
-
spans:
|
|
4255
|
-
(
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
5270
|
+
spans: toTraceSpans(
|
|
5271
|
+
spans.map(
|
|
5272
|
+
(span) => transformFromSqlRow({
|
|
5273
|
+
tableName: TABLE_SPANS,
|
|
5274
|
+
sqlRow: span
|
|
5275
|
+
})
|
|
5276
|
+
)
|
|
4259
5277
|
)
|
|
4260
5278
|
};
|
|
4261
5279
|
} catch (error) {
|
|
@@ -4382,6 +5400,11 @@ var ScoresPG = class _ScoresPG extends ScoresStorage {
|
|
|
4382
5400
|
}
|
|
4383
5401
|
async init() {
|
|
4384
5402
|
await this.#db.createTable({ tableName: TABLE_SCORERS, schema: TABLE_SCHEMAS[TABLE_SCORERS] });
|
|
5403
|
+
await this.#db.alterTable({
|
|
5404
|
+
tableName: TABLE_SCORERS,
|
|
5405
|
+
schema: TABLE_SCHEMAS[TABLE_SCORERS],
|
|
5406
|
+
ifNotExists: ["spanId", "requestContext"]
|
|
5407
|
+
});
|
|
4385
5408
|
await this.createDefaultIndexes();
|
|
4386
5409
|
await this.createCustomIndexes();
|
|
4387
5410
|
}
|
|
@@ -4733,23 +5756,8 @@ function getTableName5({ indexName, schemaName }) {
|
|
|
4733
5756
|
const quotedIndexName = `"${indexName}"`;
|
|
4734
5757
|
return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
|
|
4735
5758
|
}
|
|
4736
|
-
function
|
|
4737
|
-
|
|
4738
|
-
if (typeof parsedSnapshot === "string") {
|
|
4739
|
-
try {
|
|
4740
|
-
parsedSnapshot = JSON.parse(row.snapshot);
|
|
4741
|
-
} catch (e) {
|
|
4742
|
-
console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
4743
|
-
}
|
|
4744
|
-
}
|
|
4745
|
-
return {
|
|
4746
|
-
workflowName: row.workflow_name,
|
|
4747
|
-
runId: row.run_id,
|
|
4748
|
-
snapshot: parsedSnapshot,
|
|
4749
|
-
resourceId: row.resourceId,
|
|
4750
|
-
createdAt: new Date(row.createdAtZ || row.createdAt),
|
|
4751
|
-
updatedAt: new Date(row.updatedAtZ || row.updatedAt)
|
|
4752
|
-
};
|
|
5759
|
+
function sanitizeJsonForPg(jsonString) {
|
|
5760
|
+
return jsonString.replace(/\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})/g, "");
|
|
4753
5761
|
}
|
|
4754
5762
|
var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
4755
5763
|
#db;
|
|
@@ -4766,6 +5774,24 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
|
4766
5774
|
this.#skipDefaultIndexes = skipDefaultIndexes;
|
|
4767
5775
|
this.#indexes = indexes?.filter((idx) => _WorkflowsPG.MANAGED_TABLES.includes(idx.table));
|
|
4768
5776
|
}
|
|
5777
|
+
parseWorkflowRun(row) {
|
|
5778
|
+
let parsedSnapshot = row.snapshot;
|
|
5779
|
+
if (typeof parsedSnapshot === "string") {
|
|
5780
|
+
try {
|
|
5781
|
+
parsedSnapshot = JSON.parse(row.snapshot);
|
|
5782
|
+
} catch (e) {
|
|
5783
|
+
this.logger.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
return {
|
|
5787
|
+
workflowName: row.workflow_name,
|
|
5788
|
+
runId: row.run_id,
|
|
5789
|
+
snapshot: parsedSnapshot,
|
|
5790
|
+
resourceId: row.resourceId,
|
|
5791
|
+
createdAt: new Date(row.createdAtZ || row.createdAt),
|
|
5792
|
+
updatedAt: new Date(row.updatedAtZ || row.updatedAt)
|
|
5793
|
+
};
|
|
5794
|
+
}
|
|
4769
5795
|
/**
|
|
4770
5796
|
* Returns default index definitions for the workflows domain tables.
|
|
4771
5797
|
* Currently no default indexes are defined for workflows.
|
|
@@ -4838,12 +5864,13 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
|
4838
5864
|
const now = /* @__PURE__ */ new Date();
|
|
4839
5865
|
const createdAtValue = createdAt ? createdAt : now;
|
|
4840
5866
|
const updatedAtValue = updatedAt ? updatedAt : now;
|
|
5867
|
+
const sanitizedSnapshot = sanitizeJsonForPg(JSON.stringify(snapshot));
|
|
4841
5868
|
await this.#db.client.none(
|
|
4842
5869
|
`INSERT INTO ${getTableName5({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName5(this.#schema) })} (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt")
|
|
4843
5870
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
4844
5871
|
ON CONFLICT (workflow_name, run_id) DO UPDATE
|
|
4845
5872
|
SET "resourceId" = $3, snapshot = $4, "updatedAt" = $6`,
|
|
4846
|
-
[workflowName, runId, resourceId,
|
|
5873
|
+
[workflowName, runId, resourceId, sanitizedSnapshot, createdAtValue, updatedAtValue]
|
|
4847
5874
|
);
|
|
4848
5875
|
} catch (error) {
|
|
4849
5876
|
throw new MastraError(
|
|
@@ -4906,7 +5933,7 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
|
4906
5933
|
if (!result) {
|
|
4907
5934
|
return null;
|
|
4908
5935
|
}
|
|
4909
|
-
return parseWorkflowRun(result);
|
|
5936
|
+
return this.parseWorkflowRun(result);
|
|
4910
5937
|
} catch (error) {
|
|
4911
5938
|
throw new MastraError(
|
|
4912
5939
|
{
|
|
@@ -4962,7 +5989,9 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
|
4962
5989
|
paramIndex++;
|
|
4963
5990
|
}
|
|
4964
5991
|
if (status) {
|
|
4965
|
-
conditions.push(
|
|
5992
|
+
conditions.push(
|
|
5993
|
+
`regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status' = $${paramIndex}`
|
|
5994
|
+
);
|
|
4966
5995
|
values.push(status);
|
|
4967
5996
|
paramIndex++;
|
|
4968
5997
|
}
|
|
@@ -5007,7 +6036,7 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
|
5007
6036
|
const queryValues = usePagination ? [...values, normalizedPerPage, offset] : values;
|
|
5008
6037
|
const result = await this.#db.client.manyOrNone(query, queryValues);
|
|
5009
6038
|
const runs = (result || []).map((row) => {
|
|
5010
|
-
return parseWorkflowRun(row);
|
|
6039
|
+
return this.parseWorkflowRun(row);
|
|
5011
6040
|
});
|
|
5012
6041
|
return { runs, total: total || runs.length };
|
|
5013
6042
|
} catch (error) {
|
|
@@ -5027,9 +6056,12 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
|
|
|
5027
6056
|
};
|
|
5028
6057
|
|
|
5029
6058
|
// src/storage/index.ts
|
|
5030
|
-
var
|
|
6059
|
+
var DEFAULT_MAX_CONNECTIONS = 20;
|
|
6060
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 3e4;
|
|
6061
|
+
var PostgresStore = class extends MastraCompositeStore {
|
|
6062
|
+
#pool;
|
|
5031
6063
|
#db;
|
|
5032
|
-
#
|
|
6064
|
+
#ownsPool;
|
|
5033
6065
|
schema;
|
|
5034
6066
|
isInitialized = false;
|
|
5035
6067
|
stores;
|
|
@@ -5037,60 +6069,27 @@ var PostgresStore = class extends MastraStorage {
|
|
|
5037
6069
|
try {
|
|
5038
6070
|
validateConfig("PostgresStore", config);
|
|
5039
6071
|
super({ id: config.id, name: "PostgresStore", disableInit: config.disableInit });
|
|
5040
|
-
this.schema = config.schemaName || "public";
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
this.#
|
|
6072
|
+
this.schema = parseSqlIdentifier(config.schemaName || "public", "schema name");
|
|
6073
|
+
if (isPoolConfig(config)) {
|
|
6074
|
+
this.#pool = config.pool;
|
|
6075
|
+
this.#ownsPool = false;
|
|
5044
6076
|
} else {
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
pgConfig = {
|
|
5056
|
-
...config,
|
|
5057
|
-
id: config.id,
|
|
5058
|
-
max: config.max,
|
|
5059
|
-
idleTimeoutMillis: config.idleTimeoutMillis
|
|
5060
|
-
};
|
|
5061
|
-
} else if (isHostConfig(config)) {
|
|
5062
|
-
pgConfig = {
|
|
5063
|
-
id: config.id,
|
|
5064
|
-
host: config.host,
|
|
5065
|
-
port: config.port,
|
|
5066
|
-
database: config.database,
|
|
5067
|
-
user: config.user,
|
|
5068
|
-
password: config.password,
|
|
5069
|
-
ssl: config.ssl,
|
|
5070
|
-
max: config.max,
|
|
5071
|
-
idleTimeoutMillis: config.idleTimeoutMillis
|
|
5072
|
-
};
|
|
5073
|
-
} else {
|
|
5074
|
-
throw new Error(
|
|
5075
|
-
"PostgresStore: invalid config. Provide either {client}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
|
|
5076
|
-
);
|
|
5077
|
-
}
|
|
5078
|
-
this.#db = this.#pgp(pgConfig);
|
|
5079
|
-
}
|
|
5080
|
-
const skipDefaultIndexes = config.skipDefaultIndexes;
|
|
5081
|
-
const indexes = config.indexes;
|
|
5082
|
-
const domainConfig = { client: this.#db, schemaName: this.schema, skipDefaultIndexes, indexes };
|
|
5083
|
-
const scores = new ScoresPG(domainConfig);
|
|
5084
|
-
const workflows = new WorkflowsPG(domainConfig);
|
|
5085
|
-
const memory = new MemoryPG(domainConfig);
|
|
5086
|
-
const observability = new ObservabilityPG(domainConfig);
|
|
5087
|
-
const agents = new AgentsPG(domainConfig);
|
|
6077
|
+
this.#pool = this.createPool(config);
|
|
6078
|
+
this.#ownsPool = true;
|
|
6079
|
+
}
|
|
6080
|
+
this.#db = new PoolAdapter(this.#pool);
|
|
6081
|
+
const domainConfig = {
|
|
6082
|
+
client: this.#db,
|
|
6083
|
+
schemaName: this.schema,
|
|
6084
|
+
skipDefaultIndexes: config.skipDefaultIndexes,
|
|
6085
|
+
indexes: config.indexes
|
|
6086
|
+
};
|
|
5088
6087
|
this.stores = {
|
|
5089
|
-
scores,
|
|
5090
|
-
workflows,
|
|
5091
|
-
memory,
|
|
5092
|
-
observability,
|
|
5093
|
-
agents
|
|
6088
|
+
scores: new ScoresPG(domainConfig),
|
|
6089
|
+
workflows: new WorkflowsPG(domainConfig),
|
|
6090
|
+
memory: new MemoryPG(domainConfig),
|
|
6091
|
+
observability: new ObservabilityPG(domainConfig),
|
|
6092
|
+
agents: new AgentsPG(domainConfig)
|
|
5094
6093
|
};
|
|
5095
6094
|
} catch (e) {
|
|
5096
6095
|
throw new MastraError(
|
|
@@ -5103,6 +6102,32 @@ var PostgresStore = class extends MastraStorage {
|
|
|
5103
6102
|
);
|
|
5104
6103
|
}
|
|
5105
6104
|
}
|
|
6105
|
+
createPool(config) {
|
|
6106
|
+
if (isConnectionStringConfig(config)) {
|
|
6107
|
+
return new Pool({
|
|
6108
|
+
connectionString: config.connectionString,
|
|
6109
|
+
ssl: config.ssl,
|
|
6110
|
+
max: config.max ?? DEFAULT_MAX_CONNECTIONS,
|
|
6111
|
+
idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
|
|
6112
|
+
});
|
|
6113
|
+
}
|
|
6114
|
+
if (isHostConfig(config)) {
|
|
6115
|
+
return new Pool({
|
|
6116
|
+
host: config.host,
|
|
6117
|
+
port: config.port,
|
|
6118
|
+
database: config.database,
|
|
6119
|
+
user: config.user,
|
|
6120
|
+
password: config.password,
|
|
6121
|
+
ssl: config.ssl,
|
|
6122
|
+
max: config.max ?? DEFAULT_MAX_CONNECTIONS,
|
|
6123
|
+
idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
|
|
6124
|
+
});
|
|
6125
|
+
}
|
|
6126
|
+
if (isCloudSqlConfig(config)) {
|
|
6127
|
+
return new Pool(config);
|
|
6128
|
+
}
|
|
6129
|
+
throw new Error("PostgresStore: invalid config");
|
|
6130
|
+
}
|
|
5106
6131
|
async init() {
|
|
5107
6132
|
if (this.isInitialized) {
|
|
5108
6133
|
return;
|
|
@@ -5112,6 +6137,9 @@ var PostgresStore = class extends MastraStorage {
|
|
|
5112
6137
|
await super.init();
|
|
5113
6138
|
} catch (error) {
|
|
5114
6139
|
this.isInitialized = false;
|
|
6140
|
+
if (error instanceof MastraError) {
|
|
6141
|
+
throw error;
|
|
6142
|
+
}
|
|
5115
6143
|
throw new MastraError(
|
|
5116
6144
|
{
|
|
5117
6145
|
id: createStorageErrorId("PG", "INIT", "FAILED"),
|
|
@@ -5122,32 +6150,32 @@ var PostgresStore = class extends MastraStorage {
|
|
|
5122
6150
|
);
|
|
5123
6151
|
}
|
|
5124
6152
|
}
|
|
6153
|
+
/**
|
|
6154
|
+
* Database client for executing queries.
|
|
6155
|
+
*
|
|
6156
|
+
* @example
|
|
6157
|
+
* ```typescript
|
|
6158
|
+
* const rows = await store.db.any('SELECT * FROM users WHERE active = $1', [true]);
|
|
6159
|
+
* const user = await store.db.one('SELECT * FROM users WHERE id = $1', [userId]);
|
|
6160
|
+
* ```
|
|
6161
|
+
*/
|
|
5125
6162
|
get db() {
|
|
5126
6163
|
return this.#db;
|
|
5127
6164
|
}
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
get
|
|
5132
|
-
return
|
|
5133
|
-
selectByIncludeResourceScope: true,
|
|
5134
|
-
resourceWorkingMemory: true,
|
|
5135
|
-
hasColumn: true,
|
|
5136
|
-
createTable: true,
|
|
5137
|
-
deleteMessages: true,
|
|
5138
|
-
observability: true,
|
|
5139
|
-
indexManagement: true,
|
|
5140
|
-
listScoresBySpan: true,
|
|
5141
|
-
agents: true
|
|
5142
|
-
};
|
|
6165
|
+
/**
|
|
6166
|
+
* The underlying pg.Pool for direct database access or ORM integration.
|
|
6167
|
+
*/
|
|
6168
|
+
get pool() {
|
|
6169
|
+
return this.#pool;
|
|
5143
6170
|
}
|
|
5144
6171
|
/**
|
|
5145
|
-
* Closes the
|
|
5146
|
-
*
|
|
5147
|
-
* This will close ALL connections in the pool, including pre-configured clients.
|
|
6172
|
+
* Closes the connection pool if it was created by this store.
|
|
6173
|
+
* If a pool was passed in via config, it will not be closed.
|
|
5148
6174
|
*/
|
|
5149
6175
|
async close() {
|
|
5150
|
-
this
|
|
6176
|
+
if (this.#ownsPool) {
|
|
6177
|
+
await this.#pool.end();
|
|
6178
|
+
}
|
|
5151
6179
|
}
|
|
5152
6180
|
};
|
|
5153
6181
|
|
|
@@ -5250,6 +6278,6 @@ Example Complex Query:
|
|
|
5250
6278
|
]
|
|
5251
6279
|
}`;
|
|
5252
6280
|
|
|
5253
|
-
export { PGVECTOR_PROMPT, PgVector, PostgresStore };
|
|
6281
|
+
export { AgentsPG, MemoryPG, ObservabilityPG, PGVECTOR_PROMPT, PgVector, PoolAdapter, PostgresStore, ScoresPG, WorkflowsPG, exportSchemas };
|
|
5254
6282
|
//# sourceMappingURL=index.js.map
|
|
5255
6283
|
//# sourceMappingURL=index.js.map
|