@mastra/pg 1.33.0 → 1.34.0-alpha.1
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/README.md +1 -1
- package/dist/docs/SKILL.md +2 -2
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +20 -20
- package/dist/docs/references/integrations-databases-neon.md +1 -1
- package/dist/docs/references/integrations-databases-postgresql.md +31 -0
- package/dist/docs/references/{reference-vectors-pg.md → integrations-vectors-pg.md} +12 -0
- package/dist/docs/references/reference-memory-memory-class.md +77 -0
- package/dist/docs/references/reference-rag-metadata-filters.md +11 -11
- package/dist/docs/references/reference-rag-vector-databases.md +3 -3
- package/dist/index.cjs +641 -304
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +642 -305
- package/dist/index.js.map +1 -1
- package/dist/storage/client.d.ts +1 -0
- package/dist/storage/client.d.ts.map +1 -1
- package/dist/storage/db/index-build.d.ts +5 -0
- package/dist/storage/db/index-build.d.ts.map +1 -0
- package/dist/storage/db/index.d.ts +0 -8
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/pg-errors.d.ts +6 -0
- package/dist/storage/db/pg-errors.d.ts.map +1 -1
- package/dist/storage/db/schema-snapshot.d.ts +4 -7
- package/dist/storage/db/schema-snapshot.d.ts.map +1 -1
- package/dist/storage/domains/background-tasks/index.d.ts +1 -0
- package/dist/storage/domains/background-tasks/index.d.ts.map +1 -1
- package/dist/storage/domains/experiments/index.d.ts +3 -3
- package/dist/storage/domains/experiments/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts +2 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/feedback.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/feedback.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +9 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/logs.d.ts +2 -1
- package/dist/storage/domains/observability/v-next/logs.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/metrics.d.ts +2 -1
- package/dist/storage/domains/observability/v-next/metrics.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/partitioning.d.ts +29 -19
- package/dist/storage/domains/observability/v-next/partitioning.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts +4 -0
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/scores.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/scores.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts +4 -0
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/sql.d.ts +12 -0
- package/dist/storage/domains/observability/v-next/sql.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/thread-query.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/thread-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +10 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/tracing.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/tracing.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/test-utils.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ let pg_connection_string = require("pg-connection-string");
|
|
|
34
34
|
let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
|
|
35
35
|
let _mastra_core_base = require("@mastra/core/base");
|
|
36
36
|
let crypto$1 = require("crypto");
|
|
37
|
+
let timers_promises = require("timers/promises");
|
|
37
38
|
let _mastra_core_features = require("@mastra/core/features");
|
|
38
39
|
let _mastra_core_agent = require("@mastra/core/agent");
|
|
39
40
|
let _mastra_core_evals = require("@mastra/core/evals");
|
|
@@ -2276,6 +2277,9 @@ var PinnedClientAdapter = class {
|
|
|
2276
2277
|
this.#tail = next.catch(() => void 0);
|
|
2277
2278
|
return next;
|
|
2278
2279
|
}
|
|
2280
|
+
async drain() {
|
|
2281
|
+
await this.#tail;
|
|
2282
|
+
}
|
|
2279
2283
|
connect() {
|
|
2280
2284
|
throw new Error("PinnedClientAdapter.connect() is not supported during PostgresStore.init(). All DDL must flow through the pinned client.");
|
|
2281
2285
|
}
|
|
@@ -2458,6 +2462,39 @@ function buildConstraintName({ baseName, schemaName, maxLength = 63, hashWhenTru
|
|
|
2458
2462
|
return truncateIdentifier(fullName, maxLength);
|
|
2459
2463
|
}
|
|
2460
2464
|
// src/storage/db/pg-errors.ts
|
|
2465
|
+
/**
|
|
2466
|
+
* Postgres error classifiers for concurrent DDL.
|
|
2467
|
+
*
|
|
2468
|
+
* `CREATE SCHEMA / TABLE / INDEX IF NOT EXISTS` checks aren't atomic against
|
|
2469
|
+
* concurrent backends. Two callers racing past the existence probe can both
|
|
2470
|
+
* reach the catalog insert; the loser sees a duplicate-object error. These
|
|
2471
|
+
* helpers identify the exact codes we treat as "already exists by the time we
|
|
2472
|
+
* look" so every other error surfaces normally.
|
|
2473
|
+
*/
|
|
2474
|
+
var IndexBuildError = class extends Error {
|
|
2475
|
+
constructor(indexName, schemaName, state, cause) {
|
|
2476
|
+
super(`Could not create index ${schemaName}.${indexName}: ${state}`, { cause });
|
|
2477
|
+
this.name = "IndexBuildError";
|
|
2478
|
+
}
|
|
2479
|
+
};
|
|
2480
|
+
function* causes(error) {
|
|
2481
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2482
|
+
while (typeof error === "object" && error !== null && !seen.has(error)) {
|
|
2483
|
+
seen.add(error);
|
|
2484
|
+
yield error;
|
|
2485
|
+
error = "cause" in error ? error.cause : void 0;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
/** Index recovery owns its retry budget, even through an outer MastraError. */
|
|
2489
|
+
function isRetryableDeadlock(error) {
|
|
2490
|
+
for (const cause of causes(error)) if (cause instanceof IndexBuildError) return false;
|
|
2491
|
+
for (const cause of causes(error)) if ("code" in cause && cause.code === "40P01") return true;
|
|
2492
|
+
return false;
|
|
2493
|
+
}
|
|
2494
|
+
async function backoffAfterDeadlock(attempt) {
|
|
2495
|
+
const base = 50 * 2 ** (attempt - 1);
|
|
2496
|
+
await new Promise((resolve) => setTimeout(resolve, base + Math.random() * base * 2));
|
|
2497
|
+
}
|
|
2461
2498
|
function asPgError(error) {
|
|
2462
2499
|
return error ?? {};
|
|
2463
2500
|
}
|
|
@@ -2485,6 +2522,99 @@ function isDuplicateSchemaError(error) {
|
|
|
2485
2522
|
if (code === "23505" && constraint === "pg_namespace_nspname_index") return true;
|
|
2486
2523
|
return /schema .* already exists/i.test(message);
|
|
2487
2524
|
}
|
|
2525
|
+
// src/storage/db/index-build.ts
|
|
2526
|
+
/** A cold snapshot cannot arbitrate DDL submitted by independent processes. */
|
|
2527
|
+
async function buildIndex(client, schemaName, indexName, build, logger) {
|
|
2528
|
+
const deadline = performance.now() + 3e4;
|
|
2529
|
+
let attempts = 0;
|
|
2530
|
+
let failure;
|
|
2531
|
+
let state = "not built";
|
|
2532
|
+
let repairFailures = 0;
|
|
2533
|
+
const observe = (db = client) => db.oneOrNone(`SELECT c.oid, i.indrelid AS table_oid, t.relname AS table_name,
|
|
2534
|
+
tn.nspname AS table_schema, i.indisvalid AS valid
|
|
2535
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
2536
|
+
LEFT JOIN pg_index i ON i.indexrelid = c.oid
|
|
2537
|
+
LEFT JOIN pg_class t ON t.oid = i.indrelid
|
|
2538
|
+
LEFT JOIN pg_namespace tn ON tn.oid = t.relnamespace
|
|
2539
|
+
WHERE n.nspname = $1 AND c.relname = $2`, [schemaName, indexName]);
|
|
2540
|
+
while (true) {
|
|
2541
|
+
const relation = await observe();
|
|
2542
|
+
if (relation?.valid) return true;
|
|
2543
|
+
if (relation && relation.valid === null) throw new IndexBuildError(indexName, schemaName, "name belongs to a non-index relation", failure);
|
|
2544
|
+
if (relation) {
|
|
2545
|
+
if (await client.oneOrNone(`SELECT p.pid FROM pg_stat_progress_create_index p
|
|
2546
|
+
WHERE p.datid = (SELECT oid FROM pg_database WHERE datname = current_database())
|
|
2547
|
+
AND p.pid <> pg_backend_pid()
|
|
2548
|
+
AND (p.index_relid = $1 OR ((p.index_relid IS NULL OR p.index_relid = 0) AND EXISTS (
|
|
2549
|
+
SELECT 1 FROM pg_locks l WHERE l.pid = p.pid AND l.database = p.datid
|
|
2550
|
+
AND l.locktype = 'relation' AND l.relation = $2 AND l.granted
|
|
2551
|
+
AND l.mode IN ('ShareUpdateExclusiveLock', 'ShareLock')
|
|
2552
|
+
)))
|
|
2553
|
+
LIMIT 1`, [relation.oid, relation.table_oid])) {
|
|
2554
|
+
logger.warn(`Index ${indexName} is being built by another session; skipping`);
|
|
2555
|
+
return false;
|
|
2556
|
+
}
|
|
2557
|
+
if (performance.now() >= deadline && state === "table busy during invalid-index repair") {
|
|
2558
|
+
logger.warn(`Index ${indexName} could not be repaired because its table is busy; skipping`);
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
if (attempts >= 3 || performance.now() >= deadline) break;
|
|
2562
|
+
state = "abandoned invalid index";
|
|
2563
|
+
attempts++;
|
|
2564
|
+
let lockUnavailable = false;
|
|
2565
|
+
try {
|
|
2566
|
+
const result = await client.tx(async (tx) => {
|
|
2567
|
+
const table = `${(0, pg.escapeIdentifier)(relation.table_schema)}.${(0, pg.escapeIdentifier)(relation.table_name)}`;
|
|
2568
|
+
await tx.none(`SET LOCAL lock_timeout = '100ms'`);
|
|
2569
|
+
try {
|
|
2570
|
+
await tx.none(`LOCK TABLE ${table} IN ACCESS EXCLUSIVE MODE`);
|
|
2571
|
+
} catch (error) {
|
|
2572
|
+
lockUnavailable = typeof error === "object" && error !== null && "code" in error && error.code === "55P03";
|
|
2573
|
+
throw error;
|
|
2574
|
+
}
|
|
2575
|
+
const current = await observe(tx);
|
|
2576
|
+
if (current?.valid) return "valid";
|
|
2577
|
+
const locked = await tx.one("SELECT to_regclass($1)::oid AS oid", [table]);
|
|
2578
|
+
if (!current || current.oid !== relation.oid || current.table_oid !== locked.oid) return "changed";
|
|
2579
|
+
logger.warn(`Dropping invalid index ${indexName} left by an interrupted build; rebuilding it`);
|
|
2580
|
+
await tx.none(`DROP INDEX ${(0, pg.escapeIdentifier)(schemaName)}.${(0, pg.escapeIdentifier)(indexName)}`);
|
|
2581
|
+
return "dropped";
|
|
2582
|
+
});
|
|
2583
|
+
if (result === "valid") return true;
|
|
2584
|
+
if (result === "changed") {
|
|
2585
|
+
attempts--;
|
|
2586
|
+
if (performance.now() >= deadline) break;
|
|
2587
|
+
continue;
|
|
2588
|
+
}
|
|
2589
|
+
} catch (error) {
|
|
2590
|
+
attempts--;
|
|
2591
|
+
if (lockUnavailable) {
|
|
2592
|
+
state = "table busy during invalid-index repair";
|
|
2593
|
+
failure ??= error;
|
|
2594
|
+
await (0, timers_promises.setTimeout)(100);
|
|
2595
|
+
continue;
|
|
2596
|
+
}
|
|
2597
|
+
if (!isDuplicateRelationError(error) && !isRetryableDeadlock(error) && !(typeof error === "object" && error !== null && "code" in error && error.code === "42P01")) throw error;
|
|
2598
|
+
failure ??= error;
|
|
2599
|
+
if (++repairFailures >= 3 || performance.now() >= deadline) break;
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
} else {
|
|
2603
|
+
if (attempts >= 3 || performance.now() >= deadline) break;
|
|
2604
|
+
attempts++;
|
|
2605
|
+
}
|
|
2606
|
+
try {
|
|
2607
|
+
await build();
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
if (!isDuplicateRelationError(error) && !isRetryableDeadlock(error)) throw error;
|
|
2610
|
+
failure ??= error;
|
|
2611
|
+
const deadlock = isRetryableDeadlock(error);
|
|
2612
|
+
state = deadlock ? "index build deadlock" : "competing index creation";
|
|
2613
|
+
if (deadlock && attempts < 3 && performance.now() < deadline) await backoffAfterDeadlock(attempts);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
throw new IndexBuildError(indexName, schemaName, state, failure);
|
|
2617
|
+
}
|
|
2488
2618
|
// src/storage/db/sanitize-json.ts
|
|
2489
2619
|
const repair = (value) => value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "�").replaceAll("\0", "");
|
|
2490
2620
|
function repairJson(value) {
|
|
@@ -2540,9 +2670,10 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2540
2670
|
AND c.relkind IN ('r', 'p')
|
|
2541
2671
|
AND a.attnum > 0
|
|
2542
2672
|
AND NOT a.attisdropped`, [schema]),
|
|
2543
|
-
client.manyOrNone(`SELECT c.relname AS indexname, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary, i.indisvalid AS is_valid
|
|
2673
|
+
client.manyOrNone(`SELECT c.relname AS indexname, t.relname AS table_name, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary, i.indisvalid AS is_valid
|
|
2544
2674
|
FROM pg_catalog.pg_index i
|
|
2545
2675
|
JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
|
|
2676
|
+
JOIN pg_catalog.pg_class t ON t.oid = i.indrelid
|
|
2546
2677
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
2547
2678
|
WHERE n.nspname = $1`, [schema])
|
|
2548
2679
|
]);
|
|
@@ -2573,13 +2704,11 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2573
2704
|
}
|
|
2574
2705
|
const indexes = /* @__PURE__ */ new Set();
|
|
2575
2706
|
const replicaIdentityIndexes = /* @__PURE__ */ new Set();
|
|
2576
|
-
const primaryKeyIndexes = /* @__PURE__ */ new
|
|
2577
|
-
const invalidIndexes = /* @__PURE__ */ new Set();
|
|
2707
|
+
const primaryKeyIndexes = /* @__PURE__ */ new Map();
|
|
2578
2708
|
for (const row of indexRows) {
|
|
2579
2709
|
if (row.is_valid) indexes.add(row.indexname);
|
|
2580
|
-
else invalidIndexes.add(row.indexname);
|
|
2581
2710
|
if (row.is_replica_identity) replicaIdentityIndexes.add(row.indexname);
|
|
2582
|
-
if (row.is_primary) primaryKeyIndexes.
|
|
2711
|
+
if (row.is_primary) primaryKeyIndexes.set(row.indexname.toLowerCase(), row.table_name);
|
|
2583
2712
|
}
|
|
2584
2713
|
return {
|
|
2585
2714
|
schemaName: schema,
|
|
@@ -2589,8 +2718,7 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2589
2718
|
notNullColumns,
|
|
2590
2719
|
indexes,
|
|
2591
2720
|
replicaIdentityIndexes,
|
|
2592
|
-
primaryKeyIndexes
|
|
2593
|
-
invalidIndexes
|
|
2721
|
+
primaryKeyIndexes
|
|
2594
2722
|
};
|
|
2595
2723
|
}
|
|
2596
2724
|
// src/storage/db/index.ts
|
|
@@ -3502,14 +3630,17 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3502
3630
|
baseName: "mastra_ai_spans_traceid_spanid_pk",
|
|
3503
3631
|
schemaName: (this.schemaName ? schemaNamePrefix(this.schemaName) : "") || void 0
|
|
3504
3632
|
});
|
|
3505
|
-
const
|
|
3633
|
+
const fullTableName = getTableName$6({
|
|
3634
|
+
indexName: _mastra_core_storage.TABLE_SPANS,
|
|
3635
|
+
schemaName: getSchemaName$6(this.schemaName)
|
|
3636
|
+
});
|
|
3506
3637
|
const snapshot = this.schemaSnapshot;
|
|
3507
|
-
if (snapshot) return snapshot.primaryKeyIndexes.
|
|
3508
|
-
return this.spansPrimaryKeyExistsLive(constraintName,
|
|
3638
|
+
if (snapshot) return snapshot.primaryKeyIndexes.get(constraintName.toLowerCase()) === _mastra_core_storage.TABLE_SPANS;
|
|
3639
|
+
return this.spansPrimaryKeyExistsLive(constraintName, fullTableName);
|
|
3509
3640
|
}
|
|
3510
3641
|
/** Live-catalog variant of {@link spansPrimaryKeyExists}, bypassing the snapshot. */
|
|
3511
|
-
async spansPrimaryKeyExistsLive(constraintName,
|
|
3512
|
-
return (await this.client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = lower($1) AND
|
|
3642
|
+
async spansPrimaryKeyExistsLive(constraintName, fullTableName) {
|
|
3643
|
+
return (await this.client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = lower($1) AND contype = 'p' AND conrelid = to_regclass($2)) as exists`, [constraintName, fullTableName]))?.exists ?? false;
|
|
3513
3644
|
}
|
|
3514
3645
|
/**
|
|
3515
3646
|
* Adds the PRIMARY KEY constraint on (traceId, spanId) to the spans table.
|
|
@@ -3524,13 +3655,8 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3524
3655
|
baseName: "mastra_ai_spans_traceid_spanid_pk",
|
|
3525
3656
|
schemaName: (this.schemaName ? schemaNamePrefix(this.schemaName) : "") || void 0
|
|
3526
3657
|
});
|
|
3527
|
-
const schemaFilter = this.schemaName || "public";
|
|
3528
3658
|
try {
|
|
3529
|
-
if (
|
|
3530
|
-
SELECT EXISTS (
|
|
3531
|
-
SELECT 1 FROM pg_constraint WHERE conname = lower($1) AND connamespace = (SELECT oid FROM pg_namespace WHERE nspname = $2)
|
|
3532
|
-
) as exists
|
|
3533
|
-
`, [constraintName, schemaFilter]))?.exists) {
|
|
3659
|
+
if (await this.spansPrimaryKeyExistsLive(constraintName, fullTableName)) {
|
|
3534
3660
|
this.logger?.debug?.(`PRIMARY KEY constraint ${constraintName} already exists on ${fullTableName}`);
|
|
3535
3661
|
return;
|
|
3536
3662
|
}
|
|
@@ -3539,12 +3665,13 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3539
3665
|
ADD CONSTRAINT ${constraintName}
|
|
3540
3666
|
PRIMARY KEY ("traceId", "spanId")
|
|
3541
3667
|
`);
|
|
3542
|
-
this.schemaSnapshot?.primaryKeyIndexes.
|
|
3668
|
+
this.schemaSnapshot?.primaryKeyIndexes.set(constraintName.toLowerCase(), _mastra_core_storage.TABLE_SPANS);
|
|
3543
3669
|
this.logger?.info?.(`Added PRIMARY KEY constraint ${constraintName} to ${fullTableName}`);
|
|
3544
3670
|
} catch (error) {
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3671
|
+
const primaryKeyRace = typeof error === "object" && error !== null && "code" in error && error.code === "42P16";
|
|
3672
|
+
if (isDuplicateRelationError(error) || primaryKeyRace) {
|
|
3673
|
+
if (await this.spansPrimaryKeyExistsLive(constraintName, fullTableName)) {
|
|
3674
|
+
this.schemaSnapshot?.primaryKeyIndexes.set(constraintName.toLowerCase(), _mastra_core_storage.TABLE_SPANS);
|
|
3548
3675
|
this.logger?.debug?.(`PRIMARY KEY constraint ${constraintName} was created by another process`);
|
|
3549
3676
|
return;
|
|
3550
3677
|
}
|
|
@@ -3761,7 +3888,6 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3761
3888
|
});
|
|
3762
3889
|
const snapshot = this.schemaSnapshot;
|
|
3763
3890
|
if (snapshot?.indexes.has(name)) return;
|
|
3764
|
-
if (!await this.prepareIndexBuild(name, schemaName)) return;
|
|
3765
3891
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
3766
3892
|
const concurrentStr = concurrent ? "CONCURRENTLY " : "";
|
|
3767
3893
|
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
@@ -3779,9 +3905,8 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3779
3905
|
const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
|
|
3780
3906
|
let withStr = "";
|
|
3781
3907
|
if (storage && Object.keys(storage).length > 0) withStr = ` WITH (${Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ")})`;
|
|
3782
|
-
const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${`"${(0, _mastra_core_utils.parseSqlIdentifier)(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
|
|
3783
|
-
await this.timedIndexBuild(name, sql);
|
|
3784
|
-
snapshot?.indexes.add(name);
|
|
3908
|
+
const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}IF NOT EXISTS ${`"${(0, _mastra_core_utils.parseSqlIdentifier)(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
|
|
3909
|
+
if (await buildIndex(this.client, schemaName, name, () => this.timedIndexBuild(name, sql), this.logger)) snapshot?.indexes.add(name);
|
|
3785
3910
|
} catch (error) {
|
|
3786
3911
|
throw new _mastra_core_error.MastraError({
|
|
3787
3912
|
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "INDEX_CREATE", "FAILED"),
|
|
@@ -3806,36 +3931,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3806
3931
|
async createIndexFromStatement(indexName, sql) {
|
|
3807
3932
|
const snapshot = this.schemaSnapshot;
|
|
3808
3933
|
if (snapshot?.indexes.has(indexName)) return;
|
|
3809
|
-
if (
|
|
3810
|
-
await this.timedIndexBuild(indexName, sql);
|
|
3811
|
-
snapshot?.indexes.add(indexName);
|
|
3812
|
-
}
|
|
3813
|
-
/**
|
|
3814
|
-
* Decides whether `indexName` still needs building. Returns false when a valid
|
|
3815
|
-
* index already exists, or when another session is currently building it.
|
|
3816
|
-
* An invalid index left by an interrupted `CREATE INDEX CONCURRENTLY` is
|
|
3817
|
-
* dropped (concurrently) so the caller rebuilds it — `IF NOT EXISTS` would
|
|
3818
|
-
* otherwise keep the broken index forever.
|
|
3819
|
-
*/
|
|
3820
|
-
async prepareIndexBuild(indexName, schemaName) {
|
|
3821
|
-
const snapshot = this.schemaSnapshot;
|
|
3822
|
-
if (snapshot && !snapshot.invalidIndexes.has(indexName)) return true;
|
|
3823
|
-
const existing = await this.client.oneOrNone(`SELECT i.indisvalid AS is_valid,
|
|
3824
|
-
EXISTS (SELECT 1 FROM pg_stat_progress_create_index p WHERE p.index_relid = c.oid) AS building
|
|
3825
|
-
FROM pg_catalog.pg_index i
|
|
3826
|
-
JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
|
|
3827
|
-
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
3828
|
-
WHERE c.relname = $1 AND n.nspname = $2`, [indexName, schemaName]);
|
|
3829
|
-
if (!existing) return true;
|
|
3830
|
-
if (existing.is_valid) return false;
|
|
3831
|
-
if (existing.building) {
|
|
3832
|
-
this.logger.warn(`Index ${indexName} is being built by another session; skipping`);
|
|
3833
|
-
return false;
|
|
3834
|
-
}
|
|
3835
|
-
this.logger.warn(`Dropping invalid index ${indexName} left by an interrupted build; rebuilding it`);
|
|
3836
|
-
const quotedIndexName = `"${(0, _mastra_core_utils.parseSqlIdentifier)(indexName, "index name")}"`;
|
|
3837
|
-
await this.client.none(`DROP INDEX CONCURRENTLY IF EXISTS ${getSchemaName$6(this.schemaName)}.${quotedIndexName}`);
|
|
3838
|
-
return true;
|
|
3934
|
+
if (await buildIndex(this.client, this.schemaName || "public", indexName, () => this.timedIndexBuild(indexName, sql), this.logger)) snapshot?.indexes.add(indexName);
|
|
3839
3935
|
}
|
|
3840
3936
|
async timedIndexBuild(indexName, sql) {
|
|
3841
3937
|
const startedAt = Date.now();
|
|
@@ -5189,6 +5285,7 @@ function rowToTask(row) {
|
|
|
5189
5285
|
const suspendedAt = row.suspendedAtZ || row.suspendedAt;
|
|
5190
5286
|
const completedAt = row.completedAtZ || row.completedAt;
|
|
5191
5287
|
const leaseExpiresAt = row.leaseExpiresAtZ || row.leaseExpiresAt;
|
|
5288
|
+
const expiresAt = row.expiresAt != null ? row.expiresAtZ || row.expiresAt : void 0;
|
|
5192
5289
|
return {
|
|
5193
5290
|
id: row.id,
|
|
5194
5291
|
status: row.status,
|
|
@@ -5210,10 +5307,13 @@ function rowToTask(row) {
|
|
|
5210
5307
|
suspendedAt: suspendedAt ? suspendedAt instanceof Date ? suspendedAt : new Date(suspendedAt) : void 0,
|
|
5211
5308
|
completedAt: completedAt ? completedAt instanceof Date ? completedAt : new Date(completedAt) : void 0,
|
|
5212
5309
|
ownerId: row.ownerId ?? void 0,
|
|
5213
|
-
leaseExpiresAt: leaseExpiresAt ? leaseExpiresAt instanceof Date ? leaseExpiresAt : new Date(leaseExpiresAt) : void 0
|
|
5310
|
+
leaseExpiresAt: leaseExpiresAt ? leaseExpiresAt instanceof Date ? leaseExpiresAt : new Date(leaseExpiresAt) : void 0,
|
|
5311
|
+
expiresAt: expiresAt ? expiresAt instanceof Date ? expiresAt : new Date(expiresAt) : void 0,
|
|
5312
|
+
metadata: parseJson$2(row.metadata)
|
|
5214
5313
|
};
|
|
5215
5314
|
}
|
|
5216
5315
|
var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.BackgroundTasksStorage {
|
|
5316
|
+
supportsTaskExpiry = true;
|
|
5217
5317
|
#db;
|
|
5218
5318
|
#schema;
|
|
5219
5319
|
#skipDefaultIndexes;
|
|
@@ -5254,7 +5354,9 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
|
|
|
5254
5354
|
"suspend_payload",
|
|
5255
5355
|
"suspendedAt",
|
|
5256
5356
|
"ownerId",
|
|
5257
|
-
"leaseExpiresAt"
|
|
5357
|
+
"leaseExpiresAt",
|
|
5358
|
+
"expiresAt",
|
|
5359
|
+
"metadata"
|
|
5258
5360
|
]
|
|
5259
5361
|
});
|
|
5260
5362
|
await this.createDefaultIndexes();
|
|
@@ -5388,7 +5490,10 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
|
|
|
5388
5490
|
completedAtZ: task.completedAt?.toISOString() ?? null,
|
|
5389
5491
|
ownerId: task.ownerId ?? null,
|
|
5390
5492
|
leaseExpiresAt: task.leaseExpiresAt?.toISOString() ?? null,
|
|
5391
|
-
leaseExpiresAtZ: task.leaseExpiresAt?.toISOString() ?? null
|
|
5493
|
+
leaseExpiresAtZ: task.leaseExpiresAt?.toISOString() ?? null,
|
|
5494
|
+
expiresAt: task.expiresAt?.toISOString() ?? null,
|
|
5495
|
+
expiresAtZ: task.expiresAt?.toISOString() ?? null,
|
|
5496
|
+
metadata: serializeJson(task.metadata)
|
|
5392
5497
|
}
|
|
5393
5498
|
});
|
|
5394
5499
|
}
|
|
@@ -5444,6 +5549,16 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
|
|
|
5444
5549
|
const val = update.leaseExpiresAt?.toISOString() ?? null;
|
|
5445
5550
|
params.push(val, val);
|
|
5446
5551
|
}
|
|
5552
|
+
if ("expiresAt" in update) {
|
|
5553
|
+
setClauses.push(`"expiresAt" = $${paramIdx++}`);
|
|
5554
|
+
setClauses.push(`"expiresAtZ" = $${paramIdx++}`);
|
|
5555
|
+
const val = update.expiresAt?.toISOString() ?? null;
|
|
5556
|
+
params.push(val, val);
|
|
5557
|
+
}
|
|
5558
|
+
if ("metadata" in update) {
|
|
5559
|
+
setClauses.push(`"metadata" = $${paramIdx++}`);
|
|
5560
|
+
params.push(serializeJson(update.metadata));
|
|
5561
|
+
}
|
|
5447
5562
|
if (setClauses.length === 0) return false;
|
|
5448
5563
|
const table = getTableName$4(getSchemaName$4(this.#schema));
|
|
5449
5564
|
params.push(taskId);
|
|
@@ -5466,6 +5581,13 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
|
|
|
5466
5581
|
params.push(options.expectedLeaseExpiresAt.toISOString());
|
|
5467
5582
|
}
|
|
5468
5583
|
}
|
|
5584
|
+
if (options?.expectedSuspendedAt !== void 0) {
|
|
5585
|
+
if (options.expectedSuspendedAt === null) where += ` AND "suspendedAtZ" IS NULL`;
|
|
5586
|
+
else {
|
|
5587
|
+
where += ` AND "suspendedAtZ" = $${paramIdx++}`;
|
|
5588
|
+
params.push(options.expectedSuspendedAt.toISOString());
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5469
5591
|
return ((await this.#db.client.query(`UPDATE ${table} SET ${setClauses.join(", ")} WHERE ${where}`, params)).rowCount ?? 0) > 0;
|
|
5470
5592
|
}
|
|
5471
5593
|
async getTask(taskId) {
|
|
@@ -5508,7 +5630,9 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
|
|
|
5508
5630
|
conditions.push(`"tool_call_id" = $${paramIdx++}`);
|
|
5509
5631
|
params.push(filter.toolCallId);
|
|
5510
5632
|
}
|
|
5511
|
-
const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : "\"createdAt\"";
|
|
5633
|
+
const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : filter.dateFilterBy === "expiresAt" ? "\"expiresAtZ\"" : "\"createdAt\"";
|
|
5634
|
+
if (filter.hasExpiresAt !== void 0) conditions.push(filter.hasExpiresAt ? "\"expiresAt\" IS NOT NULL" : "\"expiresAt\" IS NULL");
|
|
5635
|
+
if (filter.dateFilterBy === "expiresAt" && (filter.fromDate || filter.toDate)) conditions.push("\"expiresAt\" IS NOT NULL");
|
|
5512
5636
|
if (filter.fromDate) {
|
|
5513
5637
|
conditions.push(`${dateCol} >= $${paramIdx++}`);
|
|
5514
5638
|
params.push(filter.fromDate.toISOString());
|
|
@@ -5549,7 +5673,9 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
|
|
|
5549
5673
|
conditions.push(`"status" IN (${placeholders.join(", ")})`);
|
|
5550
5674
|
params.push(...statuses);
|
|
5551
5675
|
}
|
|
5552
|
-
const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : "\"createdAt\"";
|
|
5676
|
+
const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : filter.dateFilterBy === "expiresAt" ? "\"expiresAtZ\"" : "\"createdAt\"";
|
|
5677
|
+
if (filter.hasExpiresAt !== void 0) conditions.push(filter.hasExpiresAt ? "\"expiresAt\" IS NOT NULL" : "\"expiresAt\" IS NULL");
|
|
5678
|
+
if (filter.dateFilterBy === "expiresAt" && (filter.fromDate || filter.toDate)) conditions.push("\"expiresAt\" IS NOT NULL");
|
|
5553
5679
|
if (filter.fromDate) {
|
|
5554
5680
|
conditions.push(`${dateCol} >= $${paramIdx++}`);
|
|
5555
5681
|
params.push(filter.fromDate.toISOString());
|
|
@@ -7165,6 +7291,19 @@ var DatasetsPG = class DatasetsPG extends _mastra_core_storage.DatasetsStorage {
|
|
|
7165
7291
|
};
|
|
7166
7292
|
// src/storage/domains/experiments/index.ts
|
|
7167
7293
|
const DEFAULT_PRUNE_BATCH_SIZE = 1e3;
|
|
7294
|
+
/**
|
|
7295
|
+
* Column list for reading experiment rows. Each `timestamp` column comes back
|
|
7296
|
+
* twice: as itself, and as its `*Z` mirror derived in the query with
|
|
7297
|
+
* `AT TIME ZONE 'UTC'`. A plain `timestamp` otherwise round-trips through the
|
|
7298
|
+
* session timezone, and reading the stored `*Z` column instead would return the
|
|
7299
|
+
* insert time for rows written before the mirrors were filled in. Deriving it
|
|
7300
|
+
* here makes legacy rows read correctly with no backfill to run at startup.
|
|
7301
|
+
*/
|
|
7302
|
+
function selectWithMirrors(schema) {
|
|
7303
|
+
return Object.entries(schema).flatMap(([name, definition]) => definition.type === "timestamp" ? [`"${name}"`, `"${name}" AT TIME ZONE 'UTC' AS "${name}Z"`] : [`"${name}"`]).join(", ");
|
|
7304
|
+
}
|
|
7305
|
+
const EXPERIMENT_COLUMNS = selectWithMirrors(_mastra_core_storage.EXPERIMENTS_SCHEMA);
|
|
7306
|
+
const EXPERIMENT_RESULT_COLUMNS = selectWithMirrors(_mastra_core_storage.EXPERIMENT_RESULTS_SCHEMA);
|
|
7168
7307
|
var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.ExperimentsStorage {
|
|
7169
7308
|
#db;
|
|
7170
7309
|
#schema;
|
|
@@ -7173,9 +7312,9 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7173
7312
|
static MANAGED_TABLES = [_mastra_core_storage.TABLE_EXPERIMENTS, _mastra_core_storage.TABLE_EXPERIMENT_RESULTS];
|
|
7174
7313
|
/**
|
|
7175
7314
|
* Experiments prune as whole units: an aged experiment and its result rows go
|
|
7176
|
-
* together, mirroring `deleteExperiment`. Anchored on `completedAt`
|
|
7177
|
-
* `completedAtZ` mirror
|
|
7178
|
-
*
|
|
7315
|
+
* together, mirroring `deleteExperiment`. Anchored on `completedAt` rather
|
|
7316
|
+
* than its `completedAtZ` mirror: both are written together, but
|
|
7317
|
+
* `completedAt` is the column the retention index covers. `completedAt` is
|
|
7179
7318
|
* written as a UTC ISO string and stays NULL while running, so
|
|
7180
7319
|
* `completedAt < cutoff` is false for in-flight experiments.
|
|
7181
7320
|
*/
|
|
@@ -7495,7 +7634,11 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7495
7634
|
startedAt: null,
|
|
7496
7635
|
completedAt: null,
|
|
7497
7636
|
createdAt: nowIso,
|
|
7498
|
-
updatedAt: nowIso
|
|
7637
|
+
updatedAt: nowIso,
|
|
7638
|
+
startedAtZ: null,
|
|
7639
|
+
completedAtZ: null,
|
|
7640
|
+
createdAtZ: nowIso,
|
|
7641
|
+
updatedAtZ: nowIso
|
|
7499
7642
|
}
|
|
7500
7643
|
});
|
|
7501
7644
|
return {
|
|
@@ -7584,12 +7727,14 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7584
7727
|
values.push(input.skippedCount);
|
|
7585
7728
|
}
|
|
7586
7729
|
if (input.startedAt !== void 0) {
|
|
7587
|
-
|
|
7588
|
-
|
|
7730
|
+
const iso = input.startedAt?.toISOString() ?? null;
|
|
7731
|
+
setClauses.push(`"startedAt" = $${paramIndex++}`, `"startedAtZ" = $${paramIndex++}`);
|
|
7732
|
+
values.push(iso, iso);
|
|
7589
7733
|
}
|
|
7590
7734
|
if (input.completedAt !== void 0) {
|
|
7591
|
-
|
|
7592
|
-
|
|
7735
|
+
const iso = input.completedAt?.toISOString() ?? null;
|
|
7736
|
+
setClauses.push(`"completedAt" = $${paramIndex++}`, `"completedAtZ" = $${paramIndex++}`);
|
|
7737
|
+
values.push(iso, iso);
|
|
7593
7738
|
}
|
|
7594
7739
|
values.push(input.id);
|
|
7595
7740
|
await this.#db.client.none(`UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE "id" = $${paramIndex}`, values);
|
|
@@ -7618,7 +7763,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7618
7763
|
});
|
|
7619
7764
|
const { conditions, params } = tenancyWhere(filters, 2);
|
|
7620
7765
|
const whereSql = ["\"id\" = $1", ...conditions].join(" AND ");
|
|
7621
|
-
const result = await client.oneOrNone(`SELECT
|
|
7766
|
+
const result = await client.oneOrNone(`SELECT ${EXPERIMENT_COLUMNS} FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
|
|
7622
7767
|
return result ? this.transformExperimentRow(result) : null;
|
|
7623
7768
|
} catch (error) {
|
|
7624
7769
|
throw new _mastra_core_error.MastraError({
|
|
@@ -7705,7 +7850,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7705
7850
|
const { offset, perPage: perPageForResponse } = (0, _mastra_core_storage.calculatePagination)(page, perPageInput, perPage);
|
|
7706
7851
|
const limitValue = perPageInput === false ? total : perPage;
|
|
7707
7852
|
return {
|
|
7708
|
-
experiments: (await this.#db.readClient.manyOrNone(`SELECT
|
|
7853
|
+
experiments: (await this.#db.readClient.manyOrNone(`SELECT ${EXPERIMENT_COLUMNS} FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
|
|
7709
7854
|
...queryParams,
|
|
7710
7855
|
limitValue,
|
|
7711
7856
|
offset
|
|
@@ -7783,9 +7928,11 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7783
7928
|
return t.one(`INSERT INTO ${resultsTable} (
|
|
7784
7929
|
"id", "experimentId", "itemId", "itemDatasetVersion", "organizationId", "projectId",
|
|
7785
7930
|
"input", "output", "groundTruth", "metadata", "error", "startedAt", "completedAt",
|
|
7786
|
-
"retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt"
|
|
7787
|
-
|
|
7788
|
-
|
|
7931
|
+
"retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt",
|
|
7932
|
+
"startedAtZ", "completedAtZ", "createdAtZ"
|
|
7933
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,
|
|
7934
|
+
$21, $22, $23)
|
|
7935
|
+
RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
|
|
7789
7936
|
id,
|
|
7790
7937
|
input.experimentId,
|
|
7791
7938
|
input.itemId,
|
|
@@ -7805,6 +7952,9 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7805
7952
|
input.status ?? null,
|
|
7806
7953
|
purgeMetadata || input.tags == null ? null : toPgJson(input.tags),
|
|
7807
7954
|
purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport),
|
|
7955
|
+
nowIso,
|
|
7956
|
+
input.startedAt.toISOString(),
|
|
7957
|
+
input.completedAt.toISOString(),
|
|
7808
7958
|
nowIso
|
|
7809
7959
|
]);
|
|
7810
7960
|
});
|
|
@@ -7831,6 +7981,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7831
7981
|
const row = await this.#db.client.tx(async (t) => {
|
|
7832
7982
|
const owner = await t.oneOrNone(`SELECT "datasetId" FROM ${experimentsTable} WHERE "id" = $1`, [input.experimentId]);
|
|
7833
7983
|
const purgeMetadata = await this.#resolvePurgeMetadata(t, owner?.datasetId, input.itemId);
|
|
7984
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
7834
7985
|
const existing = await t.oneOrNone(`SELECT "id" FROM ${tableName} WHERE "experimentId" = $1 AND "itemId" = $2 AND COALESCE("attempt", 0) = $3 FOR UPDATE`, [
|
|
7835
7986
|
input.experimentId,
|
|
7836
7987
|
input.itemId,
|
|
@@ -7841,9 +7992,11 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7841
7992
|
return t.one(`INSERT INTO ${tableName} (
|
|
7842
7993
|
"id", "experimentId", "itemId", "itemDatasetVersion", "organizationId", "projectId",
|
|
7843
7994
|
"input", "output", "groundTruth", "metadata", "error", "startedAt", "completedAt",
|
|
7844
|
-
"retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt"
|
|
7845
|
-
|
|
7846
|
-
|
|
7995
|
+
"retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt",
|
|
7996
|
+
"startedAtZ", "completedAtZ", "createdAtZ"
|
|
7997
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,
|
|
7998
|
+
$21, $22, $23)
|
|
7999
|
+
RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
|
|
7847
8000
|
id,
|
|
7848
8001
|
input.experimentId,
|
|
7849
8002
|
input.itemId,
|
|
@@ -7863,15 +8016,19 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7863
8016
|
input.status ?? null,
|
|
7864
8017
|
purgeMetadata || input.tags == null ? null : toPgJson(input.tags),
|
|
7865
8018
|
purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport),
|
|
7866
|
-
|
|
8019
|
+
nowIso,
|
|
8020
|
+
input.startedAt.toISOString(),
|
|
8021
|
+
input.completedAt.toISOString(),
|
|
8022
|
+
nowIso
|
|
7867
8023
|
]);
|
|
7868
8024
|
}
|
|
7869
8025
|
return t.one(`UPDATE ${tableName} SET
|
|
7870
8026
|
"itemDatasetVersion" = $2, "organizationId" = $3, "projectId" = $4,
|
|
7871
8027
|
"input" = $5, "output" = $6, "groundTruth" = $7, "metadata" = $8, "error" = $9,
|
|
7872
8028
|
"startedAt" = $10, "completedAt" = $11, "retryCount" = $12, "attempt" = $13,
|
|
7873
|
-
"traceId" = $14, "status" = $15, "tags" = $16, "toolMockReport" = $17
|
|
7874
|
-
|
|
8029
|
+
"traceId" = $14, "status" = $15, "tags" = $16, "toolMockReport" = $17,
|
|
8030
|
+
"startedAtZ" = $18, "completedAtZ" = $19
|
|
8031
|
+
WHERE "id" = $1 RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
|
|
7875
8032
|
existing.id,
|
|
7876
8033
|
input.itemDatasetVersion ?? null,
|
|
7877
8034
|
input.organizationId ?? null,
|
|
@@ -7888,7 +8045,9 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7888
8045
|
input.traceId ?? null,
|
|
7889
8046
|
input.status ?? null,
|
|
7890
8047
|
purgeMetadata || input.tags == null ? null : toPgJson(input.tags),
|
|
7891
|
-
purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport)
|
|
8048
|
+
purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport),
|
|
8049
|
+
input.startedAt.toISOString(),
|
|
8050
|
+
input.completedAt.toISOString()
|
|
7892
8051
|
]);
|
|
7893
8052
|
});
|
|
7894
8053
|
return this.transformExperimentResultRow(row);
|
|
@@ -7922,7 +8081,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7922
8081
|
"tags" = CASE WHEN $4 THEN NULL WHEN $5 THEN $6::jsonb ELSE "tags" END,
|
|
7923
8082
|
"comment" = CASE WHEN $4 THEN NULL WHEN $7 THEN $8 ELSE "comment" END
|
|
7924
8083
|
WHERE "id" = $1${input.experimentId !== void 0 ? " AND \"experimentId\" = $9" : ""}
|
|
7925
|
-
RETURNING
|
|
8084
|
+
RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
|
|
7926
8085
|
input.id,
|
|
7927
8086
|
input.status !== void 0,
|
|
7928
8087
|
input.status ?? null,
|
|
@@ -7965,7 +8124,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
7965
8124
|
});
|
|
7966
8125
|
const { conditions, params } = tenancyWhere(filters, 2);
|
|
7967
8126
|
const whereSql = ["\"id\" = $1", ...conditions].join(" AND ");
|
|
7968
|
-
const result = await client.oneOrNone(`SELECT
|
|
8127
|
+
const result = await client.oneOrNone(`SELECT ${EXPERIMENT_RESULT_COLUMNS} FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
|
|
7969
8128
|
return result ? this.transformExperimentResultRow(result) : null;
|
|
7970
8129
|
} catch (error) {
|
|
7971
8130
|
throw new _mastra_core_error.MastraError({
|
|
@@ -8028,7 +8187,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
|
|
|
8028
8187
|
const { offset, perPage: perPageForResponse } = (0, _mastra_core_storage.calculatePagination)(page, perPageInput, perPage);
|
|
8029
8188
|
const limitValue = perPageInput === false ? total : perPage;
|
|
8030
8189
|
return {
|
|
8031
|
-
results: (await this.#db.readClient.manyOrNone(`SELECT
|
|
8190
|
+
results: (await this.#db.readClient.manyOrNone(`SELECT ${EXPERIMENT_RESULT_COLUMNS} FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
|
|
8032
8191
|
...queryParams,
|
|
8033
8192
|
limitValue,
|
|
8034
8193
|
offset
|
|
@@ -17322,6 +17481,203 @@ function parseUpdateFeedbackReviewStatusArgs(args) {
|
|
|
17322
17481
|
reviewStatus: args.reviewStatus
|
|
17323
17482
|
};
|
|
17324
17483
|
}
|
|
17484
|
+
// src/storage/domains/observability/v-next/partitioning.ts
|
|
17485
|
+
const DEFAULT_FUTURE_DAYS = 14;
|
|
17486
|
+
async function detectTimescale(client) {
|
|
17487
|
+
const row = await client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'timescaledb') AS "exists"`);
|
|
17488
|
+
return Boolean(row?.exists);
|
|
17489
|
+
}
|
|
17490
|
+
async function detectPartman(client) {
|
|
17491
|
+
const row = await client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_partman') AS "exists"`);
|
|
17492
|
+
return Boolean(row?.exists);
|
|
17493
|
+
}
|
|
17494
|
+
async function getPartmanVersion(client) {
|
|
17495
|
+
return (await client.oneOrNone(`SELECT extversion FROM pg_extension WHERE extname = 'pg_partman'`))?.extversion ?? null;
|
|
17496
|
+
}
|
|
17497
|
+
function getPartmanMajor(version) {
|
|
17498
|
+
if (!version) return null;
|
|
17499
|
+
const match = /^(\d+)/.exec(version);
|
|
17500
|
+
return match ? Number(match[1]) : null;
|
|
17501
|
+
}
|
|
17502
|
+
async function resolveMode(client, options = {}) {
|
|
17503
|
+
if (options.mode && options.mode !== "auto") return options.mode;
|
|
17504
|
+
if (await detectTimescale(client)) return "timescale";
|
|
17505
|
+
if (await detectPartman(client)) return "partman";
|
|
17506
|
+
return "native";
|
|
17507
|
+
}
|
|
17508
|
+
function dayBounds(d) {
|
|
17509
|
+
const year = d.getUTCFullYear();
|
|
17510
|
+
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
17511
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
17512
|
+
const start = `${year}-${month}-${day} 00:00:00+00`;
|
|
17513
|
+
const next = new Date(Date.UTC(year, d.getUTCMonth(), d.getUTCDate() + 1));
|
|
17514
|
+
return {
|
|
17515
|
+
start,
|
|
17516
|
+
end: `${next.getUTCFullYear()}-${String(next.getUTCMonth() + 1).padStart(2, "0")}-${String(next.getUTCDate()).padStart(2, "0")} 00:00:00+00`,
|
|
17517
|
+
suffix: `${year}${month}${day}`
|
|
17518
|
+
};
|
|
17519
|
+
}
|
|
17520
|
+
function partitionName(table, suffix) {
|
|
17521
|
+
return `${table}_p${suffix}`;
|
|
17522
|
+
}
|
|
17523
|
+
/**
|
|
17524
|
+
* Creates daily partition tables in [today - 1 day, today + futureDays].
|
|
17525
|
+
* Idempotent: uses CREATE TABLE IF NOT EXISTS.
|
|
17526
|
+
*/
|
|
17527
|
+
async function ensureNativePartitions(client, schema, options = {}) {
|
|
17528
|
+
const windowDays = partitionWindow(options);
|
|
17529
|
+
for (const table of ALL_SIGNAL_TABLES) await ensureDayPartitions(client, schema, table, windowDays);
|
|
17530
|
+
}
|
|
17531
|
+
/** The UTC days init() pre-creates: yesterday (optional) through today + futureDays. */
|
|
17532
|
+
function partitionWindow(options = {}, nowMs = Date.now()) {
|
|
17533
|
+
const futureDays = options.futureDays ?? DEFAULT_FUTURE_DAYS;
|
|
17534
|
+
const start = options.includeYesterday ?? true ? -1 : 0;
|
|
17535
|
+
const days = [];
|
|
17536
|
+
for (let i = start; i <= futureDays; i++) days.push(new Date(nowMs + i * 864e5));
|
|
17537
|
+
return days;
|
|
17538
|
+
}
|
|
17539
|
+
/**
|
|
17540
|
+
* Keeps only the days inside the current partition window. The write path
|
|
17541
|
+
* creates partitions on demand within that window only, so a row with a
|
|
17542
|
+
* client-supplied timestamp far in the past or future cannot make the
|
|
17543
|
+
* database grow a table per day it names.
|
|
17544
|
+
*/
|
|
17545
|
+
function withinPartitionWindow(days, options = {}) {
|
|
17546
|
+
const allowed = new Set(partitionWindow(options).map((d) => dayBounds(d).suffix));
|
|
17547
|
+
return days.filter((d) => allowed.has(dayBounds(d).suffix));
|
|
17548
|
+
}
|
|
17549
|
+
/**
|
|
17550
|
+
* Creates and attaches a partition of `schema.table` for each UTC day in
|
|
17551
|
+
* `days` that has none. Each day is a `CREATE TABLE (LIKE parent)` followed by
|
|
17552
|
+
* `ATTACH PARTITION`, in one transaction, so a failed attach leaves no
|
|
17553
|
+
* detached child behind. When either step fails, the catalog is read again: a
|
|
17554
|
+
* day that a concurrent caller attached meanwhile counts as done, and any
|
|
17555
|
+
* other failure is rethrown.
|
|
17556
|
+
*/
|
|
17557
|
+
async function ensureDayPartitions(client, schema, table, days) {
|
|
17558
|
+
const parent = qualifiedTable(schema, table);
|
|
17559
|
+
const attached = await attachedPartitionNames(client, schema, table);
|
|
17560
|
+
for (const d of days) {
|
|
17561
|
+
const { start: partStart, end: partEnd, suffix } = dayBounds(d);
|
|
17562
|
+
const childName = partitionName(table, suffix);
|
|
17563
|
+
if (attached.has(childName)) continue;
|
|
17564
|
+
const child = qualifiedName(schema, childName);
|
|
17565
|
+
try {
|
|
17566
|
+
await client.tx(async (t) => {
|
|
17567
|
+
await t.none(`CREATE TABLE IF NOT EXISTS ${child} (LIKE ${parent} INCLUDING ALL)`);
|
|
17568
|
+
await t.none(`ALTER TABLE ${parent} ATTACH PARTITION ${child}
|
|
17569
|
+
FOR VALUES FROM ('${partStart}') TO ('${partEnd}')`);
|
|
17570
|
+
});
|
|
17571
|
+
} catch (error) {
|
|
17572
|
+
if (!(await attachedPartitionNames(client, schema, table)).has(childName)) throw error;
|
|
17573
|
+
}
|
|
17574
|
+
}
|
|
17575
|
+
}
|
|
17576
|
+
/** Names of the children attached to `schema.table`, read from the catalog so no search_path can shadow it. */
|
|
17577
|
+
async function attachedPartitionNames(client, schema, table) {
|
|
17578
|
+
const rows = await client.manyOrNone(`SELECT c.relname
|
|
17579
|
+
FROM pg_catalog.pg_inherits i
|
|
17580
|
+
JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid
|
|
17581
|
+
WHERE i.inhparent = (
|
|
17582
|
+
SELECT p.oid
|
|
17583
|
+
FROM pg_catalog.pg_class p
|
|
17584
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace
|
|
17585
|
+
WHERE n.nspname = $1 AND p.relname = $2
|
|
17586
|
+
)`, [schema, table]);
|
|
17587
|
+
return new Set(rows.map((r) => r.relname));
|
|
17588
|
+
}
|
|
17589
|
+
/**
|
|
17590
|
+
* `23514` raised by `ExecFindPartition`: the row's day has no attached
|
|
17591
|
+
* partition. The message is matched as a fallback only, because Postgres
|
|
17592
|
+
* translates it when `lc_messages` is not English.
|
|
17593
|
+
*/
|
|
17594
|
+
function isMissingPartitionError(error) {
|
|
17595
|
+
const { code, routine, message = "" } = error ?? {};
|
|
17596
|
+
return code === "23514" && (routine === "ExecFindPartition" || /no partition of relation/i.test(message));
|
|
17597
|
+
}
|
|
17598
|
+
/**
|
|
17599
|
+
* Calls `create_hypertable()` for each signal table.
|
|
17600
|
+
*
|
|
17601
|
+
* Postgres declarative partitioning and Timescale hypertables are mutually
|
|
17602
|
+
* exclusive, so the base DDL must have been generated with TableDDLMode
|
|
17603
|
+
* 'timescale' (no `PARTITION BY` clause). `if_not_exists` makes this
|
|
17604
|
+
* idempotent across re-inits.
|
|
17605
|
+
*/
|
|
17606
|
+
async function ensureTimescaleHypertables(client, schema) {
|
|
17607
|
+
for (const table of ALL_SIGNAL_TABLES) {
|
|
17608
|
+
const tableExpr = qualifiedTable(schema, table);
|
|
17609
|
+
const timeColumn = SIGNAL_TIME_COLUMN[table].replace(/'/g, "''");
|
|
17610
|
+
await client.none(`SELECT create_hypertable($1::regclass, '${timeColumn}', chunk_time_interval => INTERVAL '1 day', if_not_exists => true)`, [tableExpr]);
|
|
17611
|
+
}
|
|
17612
|
+
}
|
|
17613
|
+
/**
|
|
17614
|
+
* Registers each signal table with pg_partman as a daily-interval parent and
|
|
17615
|
+
* lets partman create the child partitions.
|
|
17616
|
+
*
|
|
17617
|
+
* Retention is intentionally NOT configured here — it stays opt-in via the
|
|
17618
|
+
* future Mastra CLI surface.
|
|
17619
|
+
*
|
|
17620
|
+
* Prefers the pg_partman 5.x+ declarative API (`p_type := 'range'`) and keeps
|
|
17621
|
+
* a 4.x fallback (`p_type := 'native'`) for older installs. That lets the
|
|
17622
|
+
* adapter run against current arm64-friendly images without breaking existing
|
|
17623
|
+
* 4.x deployments.
|
|
17624
|
+
*/
|
|
17625
|
+
async function partmanParentRegistered(client, schema, table) {
|
|
17626
|
+
const row = await client.oneOrNone(`SELECT EXISTS (
|
|
17627
|
+
SELECT 1 FROM partman.part_config
|
|
17628
|
+
WHERE parent_table = format('%I.%I', $1::text, $2::text)
|
|
17629
|
+
) AS "exists"`, [schema, table]);
|
|
17630
|
+
return Boolean(row?.exists);
|
|
17631
|
+
}
|
|
17632
|
+
async function ensurePartmanHypertables(client, schema) {
|
|
17633
|
+
const partmanMajor = getPartmanMajor(await getPartmanVersion(client));
|
|
17634
|
+
for (const table of ALL_SIGNAL_TABLES) {
|
|
17635
|
+
if (await partmanParentRegistered(client, schema, table)) continue;
|
|
17636
|
+
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
17637
|
+
if (partmanMajor != null && partmanMajor >= 5) await client.none(`SELECT partman.create_parent(
|
|
17638
|
+
p_parent_table := format('%I.%I', $1::text, $2::text),
|
|
17639
|
+
p_control := $3,
|
|
17640
|
+
p_interval := '1 day',
|
|
17641
|
+
p_type := 'range'
|
|
17642
|
+
)`, [
|
|
17643
|
+
schema,
|
|
17644
|
+
table,
|
|
17645
|
+
SIGNAL_TIME_COLUMN[table]
|
|
17646
|
+
]);
|
|
17647
|
+
else await client.none(`SELECT partman.create_parent(
|
|
17648
|
+
p_parent_table := format('%I.%I', $1::text, $2::text),
|
|
17649
|
+
p_control := $3,
|
|
17650
|
+
p_type := 'native',
|
|
17651
|
+
p_interval := '1 day'
|
|
17652
|
+
)`, [
|
|
17653
|
+
schema,
|
|
17654
|
+
table,
|
|
17655
|
+
SIGNAL_TIME_COLUMN[table]
|
|
17656
|
+
]);
|
|
17657
|
+
break;
|
|
17658
|
+
} catch (error) {
|
|
17659
|
+
const message = error?.message ?? "";
|
|
17660
|
+
if (error?.code === "23505" || /already managed by pg_partman/i.test(message) || /relation .* already exists/i.test(message)) break;
|
|
17661
|
+
if (!/deadlock detected/i.test(message)) throw error;
|
|
17662
|
+
if (await partmanParentRegistered(client, schema, table)) break;
|
|
17663
|
+
if (attempt === 2) throw error;
|
|
17664
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
17665
|
+
}
|
|
17666
|
+
}
|
|
17667
|
+
}
|
|
17668
|
+
async function setupPartitioning(client, schema, options = {}) {
|
|
17669
|
+
switch (await resolveMode(client, options)) {
|
|
17670
|
+
case "timescale":
|
|
17671
|
+
await ensureTimescaleHypertables(client, schema);
|
|
17672
|
+
return "timescale";
|
|
17673
|
+
case "partman":
|
|
17674
|
+
await ensurePartmanHypertables(client, schema);
|
|
17675
|
+
return "partman";
|
|
17676
|
+
case "native":
|
|
17677
|
+
await ensureNativePartitions(client, schema, options);
|
|
17678
|
+
return "native";
|
|
17679
|
+
}
|
|
17680
|
+
}
|
|
17325
17681
|
// src/storage/domains/observability/v-next/sql.ts
|
|
17326
17682
|
/**
|
|
17327
17683
|
* SQL helpers for the v-next Postgres observability domain.
|
|
@@ -17402,6 +17758,54 @@ ${onConflict}`,
|
|
|
17402
17758
|
values
|
|
17403
17759
|
};
|
|
17404
17760
|
}
|
|
17761
|
+
/**
|
|
17762
|
+
* Insert into a signal table. In native partition mode, a row whose day is
|
|
17763
|
+
* inside the partition window but has no partition yet gets one created, then
|
|
17764
|
+
* the insert runs once more. TimescaleDB and pg_partman own their partitions,
|
|
17765
|
+
* so in those modes the original error propagates untouched. The mode is the
|
|
17766
|
+
* one the store resolved; a store that never ran init() (`disableInit`)
|
|
17767
|
+
* detects it here, after the failed insert, so the hot path stays one query.
|
|
17768
|
+
*/
|
|
17769
|
+
async function insertRows(client, schema, table, records, partitioning, onConflict) {
|
|
17770
|
+
const insert = buildInsert(schema, table, records, onConflict);
|
|
17771
|
+
if (!insert) return;
|
|
17772
|
+
try {
|
|
17773
|
+
await client.query(insert.text, insert.values);
|
|
17774
|
+
} catch (error) {
|
|
17775
|
+
if (!isMissingPartitionError(error) || await resolveMode(client, partitioning) !== "native") throw error;
|
|
17776
|
+
const days = withinPartitionWindow(recordDays(records, SIGNAL_TIME_COLUMN[table]), partitioning);
|
|
17777
|
+
if (days.length === 0) throw error;
|
|
17778
|
+
for (const day of days) await ensureDayPartitions(client, schema, table, [day]).catch((cause) => {
|
|
17779
|
+
if (!isInsufficientPrivilegeError(cause)) throw cause;
|
|
17780
|
+
throw new Error(partitionCreateFailure({
|
|
17781
|
+
table,
|
|
17782
|
+
day,
|
|
17783
|
+
cause
|
|
17784
|
+
}), { cause });
|
|
17785
|
+
});
|
|
17786
|
+
await client.query(insert.text, insert.values);
|
|
17787
|
+
}
|
|
17788
|
+
}
|
|
17789
|
+
/** `42501`: the role may not create the child, or does not own the parent it must attach to. */
|
|
17790
|
+
function isInsufficientPrivilegeError(error) {
|
|
17791
|
+
return error?.code === "42501";
|
|
17792
|
+
}
|
|
17793
|
+
function partitionCreateFailure({ table, day, cause }) {
|
|
17794
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
17795
|
+
return `Could not create the ${table} partition for ${day.toISOString().slice(0, 10)} (UTC): ${reason}. Create the partitions with a role that can create tables in the schema and owns the observability tables, for example by calling ensurePartitions() on a schedule.`;
|
|
17796
|
+
}
|
|
17797
|
+
function recordDays(records, timeColumn) {
|
|
17798
|
+
const days = /* @__PURE__ */ new Map();
|
|
17799
|
+
for (const record of records) {
|
|
17800
|
+
const value = record[timeColumn];
|
|
17801
|
+
if (value == null) continue;
|
|
17802
|
+
const d = new Date(value);
|
|
17803
|
+
if (isNaN(d.getTime())) continue;
|
|
17804
|
+
const day = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
17805
|
+
days.set(day.toISOString(), day);
|
|
17806
|
+
}
|
|
17807
|
+
return [...days.values()];
|
|
17808
|
+
}
|
|
17405
17809
|
/** Primary key of the span table; also the conflict target for span upserts. */
|
|
17406
17810
|
const SPAN_KEY_COLUMNS = [
|
|
17407
17811
|
"traceId",
|
|
@@ -18312,16 +18716,14 @@ function pushFeedbackIdentity(acc, feedbackType, feedbackSource) {
|
|
|
18312
18716
|
}
|
|
18313
18717
|
acc.conditions.push(`"valueNumber" IS NOT NULL`);
|
|
18314
18718
|
}
|
|
18315
|
-
async function createFeedback(client, schema, args) {
|
|
18719
|
+
async function createFeedback(client, schema, args, partitioning) {
|
|
18316
18720
|
const row = feedbackRecordToRow(args.feedback);
|
|
18317
|
-
|
|
18318
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
18721
|
+
await insertRows(client, schema, TABLE_FEEDBACK_EVENTS, [row], partitioning, FEEDBACK_UPSERT_CLAUSE);
|
|
18319
18722
|
}
|
|
18320
|
-
async function batchCreateFeedback(client, schema, args) {
|
|
18723
|
+
async function batchCreateFeedback(client, schema, args, partitioning) {
|
|
18321
18724
|
if (args.feedbacks.length === 0) return;
|
|
18322
18725
|
const rows = [...new Map(args.feedbacks.map((feedback) => [feedback.feedbackId, feedback])).values()].map(feedbackRecordToRow);
|
|
18323
|
-
|
|
18324
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
18726
|
+
await insertRows(client, schema, TABLE_FEEDBACK_EVENTS, rows, partitioning, FEEDBACK_UPSERT_CLAUSE);
|
|
18325
18727
|
}
|
|
18326
18728
|
async function updateFeedbackReviewStatus(client, schema, args) {
|
|
18327
18729
|
const { feedbackId, reviewStatus } = parseUpdateFeedbackReviewStatusArgs(args);
|
|
@@ -18519,11 +18921,10 @@ function applyLogFilters(acc, filters) {
|
|
|
18519
18921
|
applyCommonFilters(acc, filters);
|
|
18520
18922
|
applySingleOrArrayFilter(acc, "level", filters?.level);
|
|
18521
18923
|
}
|
|
18522
|
-
async function batchCreateLogs(client, schema, args) {
|
|
18924
|
+
async function batchCreateLogs(client, schema, args, partitioning) {
|
|
18523
18925
|
if (args.logs.length === 0) return;
|
|
18524
18926
|
const rows = args.logs.map(logRecordToRow);
|
|
18525
|
-
|
|
18526
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
18927
|
+
await insertRows(client, schema, TABLE_LOG_EVENTS, rows, partitioning);
|
|
18527
18928
|
}
|
|
18528
18929
|
async function listLogs(client, schema, args) {
|
|
18529
18930
|
const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listLogsArgsSchema.parse(args);
|
|
@@ -18594,11 +18995,10 @@ function pushMetricNameFilter(acc, names) {
|
|
|
18594
18995
|
acc.conditions.push(`"name" IN (${placeholders})`);
|
|
18595
18996
|
acc.params.push(...names);
|
|
18596
18997
|
}
|
|
18597
|
-
async function batchCreateMetrics(client, schema, args) {
|
|
18998
|
+
async function batchCreateMetrics(client, schema, args, partitioning) {
|
|
18598
18999
|
if (args.metrics.length === 0) return;
|
|
18599
19000
|
const rows = args.metrics.map(metricRecordToRow);
|
|
18600
|
-
|
|
18601
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
19001
|
+
await insertRows(client, schema, TABLE_METRIC_EVENTS, rows, partitioning);
|
|
18602
19002
|
}
|
|
18603
19003
|
async function listMetrics(client, schema, args) {
|
|
18604
19004
|
const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listMetricsArgsSchema.parse(args);
|
|
@@ -18800,165 +19200,6 @@ async function getMetricPercentiles(client, schema, args) {
|
|
|
18800
19200
|
`;
|
|
18801
19201
|
return { series: percentileSeriesFromRows(await client.manyOrNone(sql, acc.params), args.percentiles) };
|
|
18802
19202
|
}
|
|
18803
|
-
// src/storage/domains/observability/v-next/partitioning.ts
|
|
18804
|
-
const DEFAULT_FUTURE_DAYS = 14;
|
|
18805
|
-
async function detectTimescale(client) {
|
|
18806
|
-
const row = await client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'timescaledb') AS "exists"`);
|
|
18807
|
-
return Boolean(row?.exists);
|
|
18808
|
-
}
|
|
18809
|
-
async function detectPartman(client) {
|
|
18810
|
-
const row = await client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_partman') AS "exists"`);
|
|
18811
|
-
return Boolean(row?.exists);
|
|
18812
|
-
}
|
|
18813
|
-
async function getPartmanVersion(client) {
|
|
18814
|
-
return (await client.oneOrNone(`SELECT extversion FROM pg_extension WHERE extname = 'pg_partman'`))?.extversion ?? null;
|
|
18815
|
-
}
|
|
18816
|
-
function getPartmanMajor(version) {
|
|
18817
|
-
if (!version) return null;
|
|
18818
|
-
const match = /^(\d+)/.exec(version);
|
|
18819
|
-
return match ? Number(match[1]) : null;
|
|
18820
|
-
}
|
|
18821
|
-
async function resolveMode(client, options = {}) {
|
|
18822
|
-
if (options.mode && options.mode !== "auto") return options.mode;
|
|
18823
|
-
if (await detectTimescale(client)) return "timescale";
|
|
18824
|
-
if (await detectPartman(client)) return "partman";
|
|
18825
|
-
return "native";
|
|
18826
|
-
}
|
|
18827
|
-
function dayBounds(d) {
|
|
18828
|
-
const year = d.getUTCFullYear();
|
|
18829
|
-
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
18830
|
-
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
18831
|
-
const start = `${year}-${month}-${day} 00:00:00+00`;
|
|
18832
|
-
const next = new Date(Date.UTC(year, d.getUTCMonth(), d.getUTCDate() + 1));
|
|
18833
|
-
return {
|
|
18834
|
-
start,
|
|
18835
|
-
end: `${next.getUTCFullYear()}-${String(next.getUTCMonth() + 1).padStart(2, "0")}-${String(next.getUTCDate()).padStart(2, "0")} 00:00:00+00`,
|
|
18836
|
-
suffix: `${year}${month}${day}`
|
|
18837
|
-
};
|
|
18838
|
-
}
|
|
18839
|
-
function partitionName(table, suffix) {
|
|
18840
|
-
return `${table}_p${suffix}`;
|
|
18841
|
-
}
|
|
18842
|
-
/**
|
|
18843
|
-
* Creates daily partition tables in [today - 1 day, today + futureDays].
|
|
18844
|
-
* Idempotent: uses CREATE TABLE IF NOT EXISTS.
|
|
18845
|
-
*/
|
|
18846
|
-
async function ensureNativePartitions(client, schema, options = {}) {
|
|
18847
|
-
const futureDays = options.futureDays ?? DEFAULT_FUTURE_DAYS;
|
|
18848
|
-
const start = options.includeYesterday ?? true ? -1 : 0;
|
|
18849
|
-
const baseNowMs = Date.now();
|
|
18850
|
-
for (const table of ALL_SIGNAL_TABLES) for (let i = start; i <= futureDays; i++) {
|
|
18851
|
-
const { start: partStart, end: partEnd, suffix } = dayBounds(new Date(baseNowMs + i * 864e5));
|
|
18852
|
-
const child = qualifiedName(schema, partitionName(table, suffix));
|
|
18853
|
-
const parent = qualifiedTable(schema, table);
|
|
18854
|
-
try {
|
|
18855
|
-
await client.none(`CREATE TABLE IF NOT EXISTS ${child} (LIKE ${parent} INCLUDING ALL)`);
|
|
18856
|
-
} catch (error) {
|
|
18857
|
-
if (!isDuplicateRelationError(error)) throw error;
|
|
18858
|
-
}
|
|
18859
|
-
try {
|
|
18860
|
-
await client.none(`ALTER TABLE ${parent} ATTACH PARTITION ${child}
|
|
18861
|
-
FOR VALUES FROM ('${partStart}') TO ('${partEnd}')`);
|
|
18862
|
-
} catch (error) {
|
|
18863
|
-
if (!isAlreadyAttachedError(error)) throw error;
|
|
18864
|
-
}
|
|
18865
|
-
}
|
|
18866
|
-
}
|
|
18867
|
-
/**
|
|
18868
|
-
* `ALTER TABLE ATTACH PARTITION` fails with `42P17 invalid_object_definition`
|
|
18869
|
-
* when the target table is already a partition of *any* parent (including
|
|
18870
|
-
* the one we want). On older PG it can also surface as 42710 or a generic
|
|
18871
|
-
* message. We err on the side of treating any "already a partition" wording
|
|
18872
|
-
* as success so concurrent inits converge cleanly.
|
|
18873
|
-
*/
|
|
18874
|
-
function isAlreadyAttachedError(error) {
|
|
18875
|
-
const code = error?.code;
|
|
18876
|
-
const message = error?.message ?? "";
|
|
18877
|
-
if (code === "42P17" || code === "42710") return /is already a partition/i.test(message) || /already a partition/i.test(message);
|
|
18878
|
-
return /is already a partition/i.test(message);
|
|
18879
|
-
}
|
|
18880
|
-
/**
|
|
18881
|
-
* Calls `create_hypertable()` for each signal table.
|
|
18882
|
-
*
|
|
18883
|
-
* Postgres declarative partitioning and Timescale hypertables are mutually
|
|
18884
|
-
* exclusive, so the base DDL must have been generated with TableDDLMode
|
|
18885
|
-
* 'timescale' (no `PARTITION BY` clause). `if_not_exists` makes this
|
|
18886
|
-
* idempotent across re-inits.
|
|
18887
|
-
*/
|
|
18888
|
-
async function ensureTimescaleHypertables(client, schema) {
|
|
18889
|
-
for (const table of ALL_SIGNAL_TABLES) {
|
|
18890
|
-
const tableExpr = qualifiedTable(schema, table);
|
|
18891
|
-
const timeColumn = SIGNAL_TIME_COLUMN[table].replace(/'/g, "''");
|
|
18892
|
-
await client.none(`SELECT create_hypertable($1::regclass, '${timeColumn}', chunk_time_interval => INTERVAL '1 day', if_not_exists => true)`, [tableExpr]);
|
|
18893
|
-
}
|
|
18894
|
-
}
|
|
18895
|
-
/**
|
|
18896
|
-
* Registers each signal table with pg_partman as a daily-interval parent and
|
|
18897
|
-
* lets partman create the child partitions.
|
|
18898
|
-
*
|
|
18899
|
-
* Retention is intentionally NOT configured here — it stays opt-in via the
|
|
18900
|
-
* future Mastra CLI surface.
|
|
18901
|
-
*
|
|
18902
|
-
* Prefers the pg_partman 5.x+ declarative API (`p_type := 'range'`) and keeps
|
|
18903
|
-
* a 4.x fallback (`p_type := 'native'`) for older installs. That lets the
|
|
18904
|
-
* adapter run against current arm64-friendly images without breaking existing
|
|
18905
|
-
* 4.x deployments.
|
|
18906
|
-
*/
|
|
18907
|
-
async function ensurePartmanHypertables(client, schema) {
|
|
18908
|
-
const partmanMajor = getPartmanMajor(await getPartmanVersion(client));
|
|
18909
|
-
for (const table of ALL_SIGNAL_TABLES) {
|
|
18910
|
-
if ((await client.oneOrNone(`SELECT EXISTS (
|
|
18911
|
-
SELECT 1 FROM partman.part_config
|
|
18912
|
-
WHERE parent_table = format('%I.%I', $1::text, $2::text)
|
|
18913
|
-
) AS "exists"`, [schema, table]))?.exists) continue;
|
|
18914
|
-
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
18915
|
-
if (partmanMajor != null && partmanMajor >= 5) await client.none(`SELECT partman.create_parent(
|
|
18916
|
-
p_parent_table := format('%I.%I', $1::text, $2::text),
|
|
18917
|
-
p_control := $3,
|
|
18918
|
-
p_interval := '1 day',
|
|
18919
|
-
p_type := 'range'
|
|
18920
|
-
)`, [
|
|
18921
|
-
schema,
|
|
18922
|
-
table,
|
|
18923
|
-
SIGNAL_TIME_COLUMN[table]
|
|
18924
|
-
]);
|
|
18925
|
-
else await client.none(`SELECT partman.create_parent(
|
|
18926
|
-
p_parent_table := format('%I.%I', $1::text, $2::text),
|
|
18927
|
-
p_control := $3,
|
|
18928
|
-
p_type := 'native',
|
|
18929
|
-
p_interval := '1 day'
|
|
18930
|
-
)`, [
|
|
18931
|
-
schema,
|
|
18932
|
-
table,
|
|
18933
|
-
SIGNAL_TIME_COLUMN[table]
|
|
18934
|
-
]);
|
|
18935
|
-
break;
|
|
18936
|
-
} catch (error) {
|
|
18937
|
-
const message = error?.message ?? "";
|
|
18938
|
-
if (error?.code === "23505" || /already managed by pg_partman/i.test(message) || /relation .* already exists/i.test(message)) break;
|
|
18939
|
-
if (!/deadlock detected/i.test(message)) throw error;
|
|
18940
|
-
if ((await client.oneOrNone(`SELECT EXISTS (
|
|
18941
|
-
SELECT 1 FROM partman.part_config
|
|
18942
|
-
WHERE parent_table = format('%I.%I', $1::text, $2::text)
|
|
18943
|
-
) AS "exists"`, [schema, table]))?.exists) break;
|
|
18944
|
-
if (attempt === 2) throw error;
|
|
18945
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
18946
|
-
}
|
|
18947
|
-
}
|
|
18948
|
-
}
|
|
18949
|
-
async function setupPartitioning(client, schema, options = {}) {
|
|
18950
|
-
switch (await resolveMode(client, options)) {
|
|
18951
|
-
case "timescale":
|
|
18952
|
-
await ensureTimescaleHypertables(client, schema);
|
|
18953
|
-
return "timescale";
|
|
18954
|
-
case "partman":
|
|
18955
|
-
await ensurePartmanHypertables(client, schema);
|
|
18956
|
-
return "partman";
|
|
18957
|
-
case "native":
|
|
18958
|
-
await ensureNativePartitions(client, schema, options);
|
|
18959
|
-
return "native";
|
|
18960
|
-
}
|
|
18961
|
-
}
|
|
18962
19203
|
// src/storage/domains/observability/v-next/retention.ts
|
|
18963
19204
|
/**
|
|
18964
19205
|
* Age-based retention for the v-next Postgres observability domain.
|
|
@@ -19124,16 +19365,14 @@ function collapseExactScoreConflicts(rows) {
|
|
|
19124
19365
|
}
|
|
19125
19366
|
return [...records.values()];
|
|
19126
19367
|
}
|
|
19127
|
-
async function createScore(client, schema, args) {
|
|
19368
|
+
async function createScore(client, schema, args, partitioning) {
|
|
19128
19369
|
const row = scoreRecordToRow(args.score);
|
|
19129
|
-
|
|
19130
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
19370
|
+
await insertRows(client, schema, TABLE_SCORE_EVENTS, [row], partitioning, scoreRewriteConflict(row));
|
|
19131
19371
|
}
|
|
19132
|
-
async function batchCreateScores(client, schema, args) {
|
|
19372
|
+
async function batchCreateScores(client, schema, args, partitioning) {
|
|
19133
19373
|
if (args.scores.length === 0) return;
|
|
19134
19374
|
const rows = collapseExactScoreConflicts(args.scores.map(scoreRecordToRow));
|
|
19135
|
-
|
|
19136
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
19375
|
+
await insertRows(client, schema, TABLE_SCORE_EVENTS, rows, partitioning, scoreRewriteConflict(rows[0]));
|
|
19137
19376
|
}
|
|
19138
19377
|
/**
|
|
19139
19378
|
* Delete score events by scoreId. Optional `organizationId` and `resourceId`
|
|
@@ -19364,7 +19603,22 @@ const TRACE_STATUS_SQL = `CASE WHEN r."error" IS NOT NULL THEN 'error' ELSE 'suc
|
|
|
19364
19603
|
const timeToFirstChunkSupported = "timeToFirstChunkMs" in _mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY.trace;
|
|
19365
19604
|
/** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
|
|
19366
19605
|
const timeToFirstChunkSortSupported = _mastra_core_storage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
|
|
19367
|
-
|
|
19606
|
+
/** Same rule for time to first token: returned and sorted only when core knows the field. */
|
|
19607
|
+
const timeToFirstTokenSupported = "timeToFirstTokenMs" in _mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY.trace;
|
|
19608
|
+
const timeToFirstTokenSortSupported = _mastra_core_storage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstTokenMs") === true;
|
|
19609
|
+
/**
|
|
19610
|
+
* Outcome fields follow the same rule: core before them rejects their predicates and strict rows,
|
|
19611
|
+
* so they are only returned and declared when core knows them.
|
|
19612
|
+
*/
|
|
19613
|
+
const spanQueryOutcomeFieldsSupported = "outcome" in (_mastra_core_storage.SPAN_QUERY_FIELD_REGISTRY ?? {});
|
|
19614
|
+
const traceQueryOutcomeFieldsSupported = "outcome" in (_mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
|
|
19615
|
+
const outcomeFeatures = [...spanQueryOutcomeFieldsSupported ? ["span-query-outcome-fields"] : [], ...traceQueryOutcomeFieldsSupported ? ["trace-query-outcome-fields"] : []];
|
|
19616
|
+
const timingFeatures = [
|
|
19617
|
+
...timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [],
|
|
19618
|
+
...timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk-sort"] : [],
|
|
19619
|
+
...timeToFirstTokenSupported ? ["trace-query-time-to-first-token"] : [],
|
|
19620
|
+
...timeToFirstTokenSortSupported ? ["trace-query-time-to-first-token-sort"] : []
|
|
19621
|
+
];
|
|
19368
19622
|
/**
|
|
19369
19623
|
* Keyset bounds for a cursor's sort value: terms for rows strictly past it in `direction`, and the
|
|
19370
19624
|
* condition for rows level with it. Empty values sort last in both directions, so they are past
|
|
@@ -19397,6 +19651,7 @@ const TRACE_FIELDS = {
|
|
|
19397
19651
|
endedAt: "r.\"endedAt\"",
|
|
19398
19652
|
durationMs: durationMsSql("r.\"startedAt\"", "r.\"endedAt\""),
|
|
19399
19653
|
timeToFirstChunkMs: "r.\"timeToFirstChunkMs\"",
|
|
19654
|
+
timeToFirstTokenMs: "r.\"timeToFirstTokenMs\"",
|
|
19400
19655
|
entityId: "r.\"entityId\"",
|
|
19401
19656
|
entityName: "r.\"entityName\"",
|
|
19402
19657
|
entityType: "r.\"entityType\"",
|
|
@@ -19404,6 +19659,8 @@ const TRACE_FIELDS = {
|
|
|
19404
19659
|
spanType: "r.\"spanType\"",
|
|
19405
19660
|
environment: "r.\"environment\"",
|
|
19406
19661
|
status: TRACE_STATUS_SQL,
|
|
19662
|
+
outcome: "r.\"outcome\"",
|
|
19663
|
+
stopReason: "r.\"stopReason\"",
|
|
19407
19664
|
tags: "r.\"tags\""
|
|
19408
19665
|
};
|
|
19409
19666
|
const SPAN_FIELDS = {
|
|
@@ -19415,7 +19672,10 @@ const SPAN_FIELDS = {
|
|
|
19415
19672
|
endedAt: "s.\"endedAt\"",
|
|
19416
19673
|
durationMs: "s.\"durationMs\"",
|
|
19417
19674
|
timeToFirstChunkMs: "s.\"timeToFirstChunkMs\"",
|
|
19675
|
+
timeToFirstTokenMs: "s.\"timeToFirstTokenMs\"",
|
|
19418
19676
|
status: "s.\"status\"",
|
|
19677
|
+
outcome: "s.\"outcome\"",
|
|
19678
|
+
stopReason: "s.\"stopReason\"",
|
|
19419
19679
|
error: "s.\"error\"",
|
|
19420
19680
|
entityType: "s.\"entityType\"",
|
|
19421
19681
|
entityId: "s.\"entityId\"",
|
|
@@ -19485,10 +19745,14 @@ const TRACE_SELECT = `
|
|
|
19485
19745
|
r."startedAt" AS "startedAt",
|
|
19486
19746
|
r."endedAt" AS "endedAt",
|
|
19487
19747
|
r."timeToFirstChunkMs" AS "timeToFirstChunkMs",
|
|
19748
|
+
r."timeToFirstTokenMs" AS "timeToFirstTokenMs",
|
|
19488
19749
|
r."entityName" AS "entityName",
|
|
19489
19750
|
r."entityType" AS "entityType",
|
|
19490
19751
|
r."environment" AS "environment",
|
|
19491
|
-
${TRACE_STATUS_SQL} AS "status"
|
|
19752
|
+
${TRACE_STATUS_SQL} AS "status",
|
|
19753
|
+
r."outcome" AS "outcome",
|
|
19754
|
+
r."stopReason" AS "stopReason",
|
|
19755
|
+
r."stopReasonContext" AS "stopReasonContext"`;
|
|
19492
19756
|
function fieldSql(registry, field) {
|
|
19493
19757
|
const sql = registry[field];
|
|
19494
19758
|
if (sql === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
|
|
@@ -19845,8 +20109,8 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
19845
20109
|
s."traceId",
|
|
19846
20110
|
s."name",
|
|
19847
20111
|
s."spanType",
|
|
19848
|
-
CASE WHEN jsonb_typeof(s."attributes" -> 'model') = 'string' THEN s."attributes" ->> 'model' END AS "model",
|
|
19849
|
-
CASE WHEN jsonb_typeof(s."attributes" -> 'provider') = 'string' THEN s."attributes" ->> 'provider' END AS "provider",
|
|
20112
|
+
COALESCE(s."model", CASE WHEN jsonb_typeof(s."attributes" -> 'model') = 'string' THEN s."attributes" ->> 'model' END) AS "model",
|
|
20113
|
+
COALESCE(s."provider", CASE WHEN jsonb_typeof(s."attributes" -> 'provider') = 'string' THEN s."attributes" ->> 'provider' END) AS "provider",
|
|
19850
20114
|
s."startedAt",
|
|
19851
20115
|
CASE WHEN s."isPending" THEN NULL ELSE s."endedAt" END AS "endedAt",
|
|
19852
20116
|
CASE
|
|
@@ -19854,7 +20118,10 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
19854
20118
|
ELSE ${durationMsSql("s.\"startedAt\"", "s.\"endedAt\"")}
|
|
19855
20119
|
END AS "durationMs",
|
|
19856
20120
|
s."timeToFirstChunkMs",
|
|
20121
|
+
s."timeToFirstTokenMs",
|
|
19857
20122
|
CASE WHEN s."error" IS NOT NULL THEN 'error' ELSE 'success' END AS "status",
|
|
20123
|
+
s."outcome",
|
|
20124
|
+
s."stopReason",
|
|
19858
20125
|
s."error",
|
|
19859
20126
|
s."entityType",
|
|
19860
20127
|
s."entityId",
|
|
@@ -20228,10 +20495,16 @@ function traceRowToResult(row) {
|
|
|
20228
20495
|
startedAt: asIsoTimestamp$1(row.startedAt),
|
|
20229
20496
|
endedAt: asIsoTimestamp$1(row.endedAt),
|
|
20230
20497
|
...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) },
|
|
20498
|
+
...timeToFirstTokenSupported && { timeToFirstTokenMs: row.timeToFirstTokenMs == null ? null : Number(row.timeToFirstTokenMs) },
|
|
20231
20499
|
entityName: row.entityName == null ? null : String(row.entityName),
|
|
20232
20500
|
entityType: row.entityType == null ? null : String(row.entityType),
|
|
20233
20501
|
environment: row.environment == null ? null : String(row.environment),
|
|
20234
|
-
status: row.status
|
|
20502
|
+
status: row.status,
|
|
20503
|
+
...traceQueryOutcomeFieldsSupported && {
|
|
20504
|
+
outcome: row.outcome == null ? null : String(row.outcome),
|
|
20505
|
+
stopReason: row.stopReason == null ? null : String(row.stopReason),
|
|
20506
|
+
stopReasonContext: row.stopReasonContext ?? null
|
|
20507
|
+
}
|
|
20235
20508
|
};
|
|
20236
20509
|
}
|
|
20237
20510
|
async function queryTraces(client, schema, plan, timeoutMs) {
|
|
@@ -20383,7 +20656,10 @@ const fields = [
|
|
|
20383
20656
|
"experimentId",
|
|
20384
20657
|
"environment",
|
|
20385
20658
|
"executionSource",
|
|
20386
|
-
"timeToFirstChunkMs"
|
|
20659
|
+
"timeToFirstChunkMs",
|
|
20660
|
+
"timeToFirstTokenMs",
|
|
20661
|
+
"outcome",
|
|
20662
|
+
"stopReason"
|
|
20387
20663
|
];
|
|
20388
20664
|
const identities = identityFields.map((field) => `"${field}"`).join(", ");
|
|
20389
20665
|
const identityOrder = identityFields.map((field) => `"${field}" COLLATE "C" ASC NULLS LAST`).join(", ");
|
|
@@ -20433,8 +20709,6 @@ function compilePostgresSpanSelection(schema, plan) {
|
|
|
20433
20709
|
};
|
|
20434
20710
|
values.push(...predicate.values);
|
|
20435
20711
|
const table = qualifiedTable(schema, TABLE_SPAN_EVENTS);
|
|
20436
|
-
const legacyModelFields = _mastra_core_storage.SPAN_QUERY_FIELD_REGISTRY ? "" : `CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
|
|
20437
|
-
CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider,`;
|
|
20438
20712
|
return {
|
|
20439
20713
|
ctes: [
|
|
20440
20714
|
`candidate_ids AS MATERIALIZED (
|
|
@@ -20445,7 +20719,7 @@ function compilePostgresSpanSelection(schema, plan) {
|
|
|
20445
20719
|
`current_spans AS MATERIALIZED (
|
|
20446
20720
|
SELECT DISTINCT ON (${identities})
|
|
20447
20721
|
${fields.map((field) => `"${field}"`).join(", ")}, "startedAt", "endedAt", "cursorId"::text AS version,
|
|
20448
|
-
|
|
20722
|
+
"model", "provider",
|
|
20449
20723
|
(EXTRACT(EPOCH FROM ("endedAt" - "startedAt")) * 1000)::float8 AS "durationMs",
|
|
20450
20724
|
CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
|
|
20451
20725
|
error IS NOT NULL AS "hasError"
|
|
@@ -20515,9 +20789,14 @@ async function querySpans(client, schema, plan, timeoutMs) {
|
|
|
20515
20789
|
return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
|
|
20516
20790
|
const query = compilePostgresSpanQuery(schema, plan);
|
|
20517
20791
|
const rows = await tx.any(query.text, query.values);
|
|
20518
|
-
const selected = rows.map(({ version: _version, startedAt, endedAt, timeToFirstChunkMs, ...row }) => ({
|
|
20792
|
+
const selected = rows.map(({ version: _version, startedAt, endedAt, timeToFirstChunkMs, timeToFirstTokenMs, outcome, stopReason, ...row }) => ({
|
|
20519
20793
|
...row,
|
|
20520
20794
|
...timeToFirstChunkSupported && { timeToFirstChunkMs },
|
|
20795
|
+
...timeToFirstTokenSupported && { timeToFirstTokenMs },
|
|
20796
|
+
...spanQueryOutcomeFieldsSupported && {
|
|
20797
|
+
outcome,
|
|
20798
|
+
stopReason
|
|
20799
|
+
},
|
|
20521
20800
|
startedAt: startedAt.toISOString(),
|
|
20522
20801
|
endedAt: endedAt.toISOString()
|
|
20523
20802
|
}));
|
|
@@ -20535,18 +20814,22 @@ async function querySpans(client, schema, plan, timeoutMs) {
|
|
|
20535
20814
|
payloadValues.push(row.version);
|
|
20536
20815
|
return `(${key} AND "cursorId" = $${payloadValues.length}::bigint)`;
|
|
20537
20816
|
}).join(" OR ");
|
|
20538
|
-
const payloadRows = await tx.any(`SELECT ${identities},
|
|
20539
|
-
CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
|
|
20540
|
-
CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider,
|
|
20817
|
+
const payloadRows = await tx.any(`SELECT ${identities}, "stopReasonContext",
|
|
20818
|
+
COALESCE("model", CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END) AS model,
|
|
20819
|
+
COALESCE("provider", CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END) AS provider,
|
|
20541
20820
|
${PREVIEW_PAYLOADS$1.map(([preview, payload]) => `"${preview}", CASE WHEN ${missingPreviewSql(`"${preview}"`)} THEN "${payload}" END AS "${payload}"`).join(",\n ")}
|
|
20542
20821
|
FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} WHERE ${payloadWhere}`, payloadValues);
|
|
20543
20822
|
const payloads = payloadRows.map(toSpanQueryPayload);
|
|
20544
20823
|
const metadata = new Map(payloadRows.map((row) => [_mastra_core_storage.spanQueryIdentityKey(row), row]));
|
|
20545
|
-
const hydrated = selected.map((row) =>
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20824
|
+
const hydrated = selected.map((row) => {
|
|
20825
|
+
const stored = metadata.get(_mastra_core_storage.spanQueryIdentityKey(row));
|
|
20826
|
+
return {
|
|
20827
|
+
...row,
|
|
20828
|
+
model: stored?.model ?? null,
|
|
20829
|
+
provider: stored?.provider ?? null,
|
|
20830
|
+
...spanQueryOutcomeFieldsSupported && { stopReasonContext: stored?.stopReasonContext ?? null }
|
|
20831
|
+
};
|
|
20832
|
+
});
|
|
20550
20833
|
let metrics = [];
|
|
20551
20834
|
if (!_mastra_core_storage.SPAN_QUERY_FIELD_REGISTRY) {
|
|
20552
20835
|
await remaining();
|
|
@@ -20649,7 +20932,11 @@ function compilePostgresThreadRowsQuery(schema, plan, strategy = "full") {
|
|
|
20649
20932
|
}
|
|
20650
20933
|
const qualify = plan.timeRange.mode === "traces" ? "s.\"inWindow\"" : `s."${plan.timeRange.field}" >= $1 AND s."${plan.timeRange.field}" < $2`;
|
|
20651
20934
|
const filtered = predicateFields(plan.where);
|
|
20652
|
-
const firstContext = [
|
|
20935
|
+
const firstContext = [
|
|
20936
|
+
...contextFields,
|
|
20937
|
+
"timeToFirstChunkMs",
|
|
20938
|
+
"timeToFirstTokenMs"
|
|
20939
|
+
].filter((field) => field === "resourceId" || filtered.has(field));
|
|
20653
20940
|
const lastFields = [
|
|
20654
20941
|
"traceId",
|
|
20655
20942
|
"spanId",
|
|
@@ -20663,7 +20950,7 @@ function compilePostgresThreadRowsQuery(schema, plan, strategy = "full") {
|
|
|
20663
20950
|
text: `WITH ${scopedRoots}, ${strategy === "candidates" ? `${candidates}, ` : ""}current_roots AS (
|
|
20664
20951
|
SELECT DISTINCT ON ("organizationId", "traceId")
|
|
20665
20952
|
"organizationId", "threadId", "traceId", "spanId", "cursorId", "startedAt", "endedAt", "isPending",
|
|
20666
|
-
${firstContext.map((field) => field === "timeToFirstChunkMs" ? `CASE WHEN "spanType" = 'agent_run' THEN "
|
|
20953
|
+
${firstContext.map((field) => field === "timeToFirstChunkMs" || field === "timeToFirstTokenMs" ? `CASE WHEN "spanType" = 'agent_run' THEN "${field}" END AS "${field}"` : `"${field}"`).join(", ")}
|
|
20667
20954
|
${filtered.has("status") ? ", CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status" : ""}
|
|
20668
20955
|
FROM scoped_roots ${strategy === "candidates" ? `WHERE (${identity("traceId")}) IN (SELECT ${identity("traceId")} FROM candidate_traces)` : ""}
|
|
20669
20956
|
ORDER BY "organizationId", "traceId", "startedAt" ASC, "spanId" COLLATE "C", "cursorId" DESC
|
|
@@ -20729,6 +21016,7 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
|
|
|
20729
21016
|
}));
|
|
20730
21017
|
const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", ${contextFields.map((field) => `"${field}"`).join(", ")},
|
|
20731
21018
|
CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs",
|
|
21019
|
+
CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstTokenMs" END AS "timeToFirstTokenMs",
|
|
20732
21020
|
CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
|
|
20733
21021
|
${[
|
|
20734
21022
|
"input",
|
|
@@ -20757,6 +21045,7 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
|
|
|
20757
21045
|
endedAt: row.endedAt.toISOString(),
|
|
20758
21046
|
traceCount: Number(row.traceCount),
|
|
20759
21047
|
timeToFirstChunkMs: first.timeToFirstChunkMs,
|
|
21048
|
+
...timeToFirstTokenSupported && { timeToFirstTokenMs: first.timeToFirstTokenMs },
|
|
20760
21049
|
inputPreview: readPreview(first.inputPreview, first.input),
|
|
20761
21050
|
outputPreview: readPreview(last.outputPreview, last.output),
|
|
20762
21051
|
errorPreview: readPreview(last.errorPreview, last.error)
|
|
@@ -20782,6 +21071,8 @@ const DIMENSION_SQL = {
|
|
|
20782
21071
|
entityId: "r.\"entityId\"",
|
|
20783
21072
|
environment: "r.\"environment\"",
|
|
20784
21073
|
status: TRACE_STATUS_SQL,
|
|
21074
|
+
outcome: "r.\"outcome\"",
|
|
21075
|
+
stopReason: "r.\"stopReason\"",
|
|
20785
21076
|
serviceName: "r.\"serviceName\"",
|
|
20786
21077
|
executionSource: "r.\"executionSource\"",
|
|
20787
21078
|
threadId: "r.\"threadId\"",
|
|
@@ -20804,6 +21095,8 @@ const SPAN_DIMENSION_SQL = Object.fromEntries([
|
|
|
20804
21095
|
"rootEntityName",
|
|
20805
21096
|
"environment",
|
|
20806
21097
|
"status",
|
|
21098
|
+
"outcome",
|
|
21099
|
+
"stopReason",
|
|
20807
21100
|
"serviceName",
|
|
20808
21101
|
"executionSource",
|
|
20809
21102
|
"threadId",
|
|
@@ -20811,7 +21104,9 @@ const SPAN_DIMENSION_SQL = Object.fromEntries([
|
|
|
20811
21104
|
"userId",
|
|
20812
21105
|
"sessionId",
|
|
20813
21106
|
"organizationId",
|
|
20814
|
-
"experimentId"
|
|
21107
|
+
"experimentId",
|
|
21108
|
+
"model",
|
|
21109
|
+
"provider"
|
|
20815
21110
|
].map((field) => [field, `r."${field}"`]));
|
|
20816
21111
|
const PERCENTILES = {
|
|
20817
21112
|
"duration.p50": .5,
|
|
@@ -21713,10 +22008,9 @@ async function readBranchesStreamHeadCursor(client, span, filters) {
|
|
|
21713
22008
|
return encodeDeltaCursor(await readSafeXactHorizon(client), 0);
|
|
21714
22009
|
}
|
|
21715
22010
|
// src/storage/domains/observability/v-next/tracing.ts
|
|
21716
|
-
async function writeSpanRows(client, schema, rows) {
|
|
22011
|
+
async function writeSpanRows(client, schema, rows, partitioning) {
|
|
21717
22012
|
const deduped = dedupeSpanRows(rows);
|
|
21718
|
-
|
|
21719
|
-
if (insert) await client.query(insert.text, insert.values);
|
|
22013
|
+
await insertRows(client, schema, TABLE_SPAN_EVENTS, deduped, partitioning, spanConflictClause(Object.keys(deduped[0] ?? {})));
|
|
21720
22014
|
}
|
|
21721
22015
|
/**
|
|
21722
22016
|
* Collapse rows that share a primary key within a single batch. Postgres
|
|
@@ -21736,12 +22030,12 @@ function dedupeSpanRows(rows) {
|
|
|
21736
22030
|
}
|
|
21737
22031
|
return [...byKey.values()];
|
|
21738
22032
|
}
|
|
21739
|
-
async function createSpan(client, schema, args) {
|
|
21740
|
-
await writeSpanRows(client, schema, [spanRecordToRow(args.span)]);
|
|
22033
|
+
async function createSpan(client, schema, args, partitioning) {
|
|
22034
|
+
await writeSpanRows(client, schema, [spanRecordToRow(args.span)], partitioning);
|
|
21741
22035
|
}
|
|
21742
|
-
async function batchCreateSpans(client, schema, args) {
|
|
22036
|
+
async function batchCreateSpans(client, schema, args, partitioning) {
|
|
21743
22037
|
if (args.records.length === 0) return;
|
|
21744
|
-
await writeSpanRows(client, schema, args.records.map(spanRecordToRow));
|
|
22038
|
+
await writeSpanRows(client, schema, args.records.map(spanRecordToRow), partitioning);
|
|
21745
22039
|
}
|
|
21746
22040
|
async function getSpans(client, schema, args) {
|
|
21747
22041
|
if (args.spanIds.length === 0) return {
|
|
@@ -21865,8 +22159,12 @@ async function dangerouslyClearTracing(client, schema) {
|
|
|
21865
22159
|
* application database — observability writes will degrade app performance.
|
|
21866
22160
|
* Use it through `MastraCompositeStore` with a dedicated Postgres connection.
|
|
21867
22161
|
*/
|
|
21868
|
-
const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? [
|
|
21869
|
-
|
|
22162
|
+
const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? [
|
|
22163
|
+
"span-query",
|
|
22164
|
+
"span-query-context-fields",
|
|
22165
|
+
..."model" in (_mastra_core_storage.SPAN_QUERY_FIELD_REGISTRY ?? {}) ? ["span-query-model-fields"] : []
|
|
22166
|
+
] : [];
|
|
22167
|
+
const spanAggregateFeatures = typeof _mastra_core_storage.planSpanAggregate === "function" ? ["span-aggregate", ..."model" in (_mastra_core_storage.SPAN_AGGREGATE_DIMENSION_REGISTRY ?? {}) ? ["span-aggregate-model-dimensions"] : []] : [];
|
|
21870
22168
|
function wrapError(op, error, details) {
|
|
21871
22169
|
if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryCursorError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
|
|
21872
22170
|
throw new _mastra_core_error.MastraError({
|
|
@@ -21962,6 +22260,18 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
21962
22260
|
return this.#partitionMode;
|
|
21963
22261
|
}
|
|
21964
22262
|
/**
|
|
22263
|
+
* Create any missing day partitions from yesterday through today +
|
|
22264
|
+
* `futureDays`, without the rest of init(). For apps whose runtime role
|
|
22265
|
+
* cannot run DDL: call it on a schedule with a role that owns the
|
|
22266
|
+
* observability tables. Safe to call repeatedly on one instance. Does
|
|
22267
|
+
* nothing with TimescaleDB or pg_partman, which create their own partitions.
|
|
22268
|
+
*/
|
|
22269
|
+
async ensurePartitions() {
|
|
22270
|
+
return this.#run("VNEXT_ENSURE_PARTITIONS", async () => {
|
|
22271
|
+
if ((this.#partitionMode ??= await resolveMode(this.#client, this.#partitioning)) === "native") await ensureNativePartitions(this.#client, this.#schema, this.#partitioning);
|
|
22272
|
+
});
|
|
22273
|
+
}
|
|
22274
|
+
/**
|
|
21965
22275
|
* All five signal tables are insert-only growth tables. The anchor column is
|
|
21966
22276
|
* each table's partition / chunk key, so age-based expiry drops whole day
|
|
21967
22277
|
* partitions instead of deleting rows (see `./retention.ts`). `indexed: true`
|
|
@@ -22065,7 +22375,8 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22065
22375
|
"trace-query-tenant-scope",
|
|
22066
22376
|
"feedback",
|
|
22067
22377
|
"trace-query-context-ids",
|
|
22068
|
-
...
|
|
22378
|
+
...timingFeatures,
|
|
22379
|
+
...outcomeFeatures,
|
|
22069
22380
|
"trace-query-root-span-fields",
|
|
22070
22381
|
"trace-query-root-entity-id"
|
|
22071
22382
|
];
|
|
@@ -22090,11 +22401,24 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22090
22401
|
"trace-query-tenant-scope",
|
|
22091
22402
|
"feedback",
|
|
22092
22403
|
"trace-query-context-ids",
|
|
22093
|
-
...
|
|
22404
|
+
...timingFeatures,
|
|
22405
|
+
...outcomeFeatures,
|
|
22094
22406
|
"trace-query-root-span-fields",
|
|
22095
22407
|
"trace-query-root-entity-id"
|
|
22096
22408
|
];
|
|
22097
22409
|
}
|
|
22410
|
+
/**
|
|
22411
|
+
* Partitioning options for the write path, with `mode` fixed to the mode
|
|
22412
|
+
* this store resolved (in init(), or lazily in prune()) so the write path
|
|
22413
|
+
* does not detect it again. Until then the configured mode stands in and
|
|
22414
|
+
* the write path resolves it itself when a partition is missing.
|
|
22415
|
+
*/
|
|
22416
|
+
#writePartitioning() {
|
|
22417
|
+
return {
|
|
22418
|
+
...this.#partitioning,
|
|
22419
|
+
mode: this.#partitionMode ?? this.#partitioning.mode
|
|
22420
|
+
};
|
|
22421
|
+
}
|
|
22098
22422
|
async #run(op, fn, details) {
|
|
22099
22423
|
try {
|
|
22100
22424
|
return await fn();
|
|
@@ -22103,13 +22427,13 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22103
22427
|
}
|
|
22104
22428
|
}
|
|
22105
22429
|
async createSpan(args) {
|
|
22106
|
-
await this.#run("CREATE_SPAN", () => createSpan(this.#client, this.#schema, args), {
|
|
22430
|
+
await this.#run("CREATE_SPAN", () => createSpan(this.#client, this.#schema, args, this.#writePartitioning()), {
|
|
22107
22431
|
traceId: args.span.traceId,
|
|
22108
22432
|
spanId: args.span.spanId
|
|
22109
22433
|
});
|
|
22110
22434
|
}
|
|
22111
22435
|
async batchCreateSpans(args) {
|
|
22112
|
-
await this.#run("BATCH_CREATE_SPANS", () => batchCreateSpans(this.#client, this.#schema, args), { count: args.records.length });
|
|
22436
|
+
await this.#run("BATCH_CREATE_SPANS", () => batchCreateSpans(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.records.length });
|
|
22113
22437
|
}
|
|
22114
22438
|
async getSpan(args) {
|
|
22115
22439
|
return this.#run("GET_SPAN", () => getSpan(this.#readClient, this.#schema, args), {
|
|
@@ -22163,22 +22487,22 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22163
22487
|
return this.#run("LIST_BRANCHES", () => listBranches(this.#readClient, this.#schema, args));
|
|
22164
22488
|
}
|
|
22165
22489
|
async batchCreateLogs(args) {
|
|
22166
|
-
await this.#run("BATCH_CREATE_LOGS", () => batchCreateLogs(this.#client, this.#schema, args), { count: args.logs.length });
|
|
22490
|
+
await this.#run("BATCH_CREATE_LOGS", () => batchCreateLogs(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.logs.length });
|
|
22167
22491
|
}
|
|
22168
22492
|
async batchCreateMetrics(args) {
|
|
22169
|
-
await this.#run("BATCH_CREATE_METRICS", () => batchCreateMetrics(this.#client, this.#schema, args), { count: args.metrics.length });
|
|
22493
|
+
await this.#run("BATCH_CREATE_METRICS", () => batchCreateMetrics(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.metrics.length });
|
|
22170
22494
|
}
|
|
22171
22495
|
async createScore(args) {
|
|
22172
|
-
await this.#run("CREATE_SCORE", () => createScore(this.#client, this.#schema, args));
|
|
22496
|
+
await this.#run("CREATE_SCORE", () => createScore(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22173
22497
|
}
|
|
22174
22498
|
async batchCreateScores(args) {
|
|
22175
|
-
await this.#run("BATCH_CREATE_SCORES", () => batchCreateScores(this.#client, this.#schema, args), { count: args.scores.length });
|
|
22499
|
+
await this.#run("BATCH_CREATE_SCORES", () => batchCreateScores(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.scores.length });
|
|
22176
22500
|
}
|
|
22177
22501
|
async createFeedback(args) {
|
|
22178
|
-
await this.#run("CREATE_FEEDBACK", () => createFeedback(this.#client, this.#schema, args));
|
|
22502
|
+
await this.#run("CREATE_FEEDBACK", () => createFeedback(this.#client, this.#schema, args, this.#writePartitioning()));
|
|
22179
22503
|
}
|
|
22180
22504
|
async batchCreateFeedback(args) {
|
|
22181
|
-
await this.#run("BATCH_CREATE_FEEDBACK", () => batchCreateFeedback(this.#client, this.#schema, args), { count: args.feedbacks.length });
|
|
22505
|
+
await this.#run("BATCH_CREATE_FEEDBACK", () => batchCreateFeedback(this.#client, this.#schema, args, this.#writePartitioning()), { count: args.feedbacks.length });
|
|
22182
22506
|
}
|
|
22183
22507
|
async deleteScores(args) {
|
|
22184
22508
|
await this.#run("DELETE_SCORES", () => deleteScores(this.#client, this.#schema, args), { count: args.scoreIds.length });
|
|
@@ -27588,12 +27912,20 @@ var PostgresStore = class extends _mastra_core_storage.MastraCompositeStore {
|
|
|
27588
27912
|
}
|
|
27589
27913
|
async #runPinnedInit() {
|
|
27590
27914
|
let pinnedClient;
|
|
27915
|
+
let pinned;
|
|
27591
27916
|
try {
|
|
27592
27917
|
pinnedClient = await this.#writePool.connect();
|
|
27593
|
-
|
|
27918
|
+
pinned = new PinnedClientAdapter(this.#writePool, pinnedClient);
|
|
27594
27919
|
this.#db.pin(pinned);
|
|
27595
|
-
|
|
27596
|
-
|
|
27920
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
27921
|
+
this.#db.setSchemaSnapshot(await loadSchemaSnapshot(pinned, this.schema));
|
|
27922
|
+
const failures = (await Promise.allSettled(Object.entries(this.stores).filter(([name, store]) => name !== "knowledge" && store).map(([, store]) => Promise.resolve().then(() => store.init())))).filter((result) => result.status === "rejected");
|
|
27923
|
+
if (failures.length === 0) break;
|
|
27924
|
+
const terminal = failures.find((result) => !isRetryableDeadlock(result.reason));
|
|
27925
|
+
if (terminal || attempt === 3) throw (terminal ?? failures[0]).reason;
|
|
27926
|
+
this.#db.setSchemaSnapshot(null);
|
|
27927
|
+
await backoffAfterDeadlock(attempt);
|
|
27928
|
+
}
|
|
27597
27929
|
this.isInitialized = true;
|
|
27598
27930
|
} catch (error) {
|
|
27599
27931
|
this.#initPromise = null;
|
|
@@ -27604,6 +27936,7 @@ var PostgresStore = class extends _mastra_core_storage.MastraCompositeStore {
|
|
|
27604
27936
|
category: _mastra_core_error.ErrorCategory.THIRD_PARTY
|
|
27605
27937
|
}, error);
|
|
27606
27938
|
} finally {
|
|
27939
|
+
await pinned?.drain();
|
|
27607
27940
|
this.#db.setSchemaSnapshot(null);
|
|
27608
27941
|
if (pinnedClient) {
|
|
27609
27942
|
this.#db.unpin();
|
|
@@ -27758,6 +28091,10 @@ var PostgresStoreVNext = class extends PostgresStore {
|
|
|
27758
28091
|
owned: true
|
|
27759
28092
|
};
|
|
27760
28093
|
}
|
|
28094
|
+
/** See {@link ObservabilityStoragePostgresVNext.ensurePartitions}. */
|
|
28095
|
+
async ensurePartitions() {
|
|
28096
|
+
await this.stores.observability.ensurePartitions();
|
|
28097
|
+
}
|
|
27761
28098
|
/**
|
|
27762
28099
|
* Closes both the primary pool (when owned) and the observability pool
|
|
27763
28100
|
* (when this store created it). Safe to call multiple times.
|