@mastra/pg 1.14.3 → 1.15.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/CHANGELOG.md +181 -0
- package/dist/docs/SKILL.md +2 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +8 -0
- package/dist/docs/references/docs-memory-storage.md +6 -0
- package/dist/docs/references/docs-memory-working-memory.md +2 -0
- package/dist/docs/references/docs-rag-overview.md +2 -0
- package/dist/docs/references/docs-rag-retrieval.md +2 -0
- package/dist/docs/references/docs-rag-vector-databases.md +2 -0
- package/dist/docs/references/reference-memory-memory-class.md +3 -0
- package/dist/docs/references/reference-processors-message-history-processor.md +3 -0
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +3 -0
- package/dist/docs/references/reference-processors-working-memory-processor.md +3 -0
- package/dist/docs/references/reference-rag-metadata-filters.md +2 -0
- package/dist/docs/references/reference-storage-composite.md +2 -0
- package/dist/docs/references/reference-storage-dynamodb.md +2 -0
- package/dist/docs/references/reference-storage-postgresql.md +2 -0
- package/dist/docs/references/reference-storage-retention.md +180 -0
- package/dist/docs/references/reference-tools-vector-query-tool.md +2 -0
- package/dist/docs/references/reference-vectors-pg.md +2 -0
- package/dist/index.cjs +932 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +933 -54
- package/dist/index.js.map +1 -1
- package/dist/shared/config.d.ts +8 -1
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/storage/db/index.d.ts +51 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/background-tasks/index.d.ts +19 -1
- package/dist/storage/domains/background-tasks/index.d.ts.map +1 -1
- package/dist/storage/domains/datasets/index.d.ts +6 -7
- package/dist/storage/domains/datasets/index.d.ts.map +1 -1
- package/dist/storage/domains/experiments/index.d.ts +39 -5
- package/dist/storage/domains/experiments/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +39 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/notifications/index.d.ts +18 -1
- package/dist/storage/domains/notifications/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +22 -1
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +17 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/retention.d.ts +64 -0
- package/dist/storage/domains/observability/v-next/retention.d.ts.map +1 -0
- package/dist/storage/domains/schedules/index.d.ts +23 -1
- package/dist/storage/domains/schedules/index.d.ts.map +1 -1
- package/dist/storage/domains/scorer-definitions/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +26 -5
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/utils.d.ts +20 -1
- package/dist/storage/domains/utils.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +19 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +76 -0
- package/dist/storage/retention.d.ts.map +1 -0
- package/dist/storage/test-utils.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -2542,6 +2542,11 @@ CREATE TRIGGER ${triggerName}
|
|
|
2542
2542
|
EXECUTE FUNCTION ${functionName}();`;
|
|
2543
2543
|
}
|
|
2544
2544
|
var schemaSetupRegistry = /* @__PURE__ */ new Map();
|
|
2545
|
+
function assertPositiveLimit(limit) {
|
|
2546
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
2547
|
+
throw new Error(`prune limit must be a positive integer; received ${limit}`);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2545
2550
|
var PgDB = class extends base.MastraBase {
|
|
2546
2551
|
client;
|
|
2547
2552
|
schemaName;
|
|
@@ -3578,6 +3583,96 @@ Note: This migration may take some time for large tables.
|
|
|
3578
3583
|
async deleteData({ tableName }) {
|
|
3579
3584
|
return this.clearTable({ tableName });
|
|
3580
3585
|
}
|
|
3586
|
+
// ---------------------------------------------------------------------------
|
|
3587
|
+
// Retention helpers (prune)
|
|
3588
|
+
// ---------------------------------------------------------------------------
|
|
3589
|
+
/**
|
|
3590
|
+
* Deletes up to `limit` rows from `tableName` whose `column` value is strictly
|
|
3591
|
+
* older than `cutoff`, in a single bounded statement. Returns the number of
|
|
3592
|
+
* rows deleted so the caller's batch loop can decide whether the table is
|
|
3593
|
+
* drained.
|
|
3594
|
+
*
|
|
3595
|
+
* PostgreSQL has no `DELETE ... LIMIT`, so this targets a bounded set of
|
|
3596
|
+
* physical rows via the `ctid` system column (PG's row identity, analogous to
|
|
3597
|
+
* SQLite's `rowid`). `cutoff` is bound as a parameter — a `Date`/ISO-8601
|
|
3598
|
+
* string compared against a `timestamptz` anchor column, or a `number`
|
|
3599
|
+
* compared against a `bigint` epoch-ms anchor column.
|
|
3600
|
+
*/
|
|
3601
|
+
async pruneBatch({
|
|
3602
|
+
tableName,
|
|
3603
|
+
column,
|
|
3604
|
+
cutoff,
|
|
3605
|
+
limit
|
|
3606
|
+
}) {
|
|
3607
|
+
assertPositiveLimit(limit);
|
|
3608
|
+
const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
3609
|
+
const parsedColumn = `"${utils.parseSqlIdentifier(column, "column name")}"`;
|
|
3610
|
+
const sql = `
|
|
3611
|
+
DELETE FROM ${fullTableName}
|
|
3612
|
+
WHERE ctid IN (
|
|
3613
|
+
SELECT ctid FROM ${fullTableName}
|
|
3614
|
+
WHERE ${parsedColumn} < $1
|
|
3615
|
+
LIMIT $2
|
|
3616
|
+
)
|
|
3617
|
+
`;
|
|
3618
|
+
const result = await this.client.query(sql, [cutoff, limit]);
|
|
3619
|
+
return result.rowCount ?? 0;
|
|
3620
|
+
}
|
|
3621
|
+
/**
|
|
3622
|
+
* Deletes up to `limit` aged parent rows *and* their child rows together, in
|
|
3623
|
+
* a single transaction (used by whole-unit pruning such as experiments →
|
|
3624
|
+
* experiment_results). The aged parent IDs are selected first and both
|
|
3625
|
+
* deletes target that exact ID set, so a bound or abort between batches never
|
|
3626
|
+
* leaves a parent hollow (kept, but with its children gone) or children
|
|
3627
|
+
* orphaned.
|
|
3628
|
+
*/
|
|
3629
|
+
async pruneUnitsBatch({
|
|
3630
|
+
parentTable,
|
|
3631
|
+
parentKey,
|
|
3632
|
+
parentColumn,
|
|
3633
|
+
childTable,
|
|
3634
|
+
childForeignKey,
|
|
3635
|
+
cutoff,
|
|
3636
|
+
limit
|
|
3637
|
+
}) {
|
|
3638
|
+
assertPositiveLimit(limit);
|
|
3639
|
+
const schemaName = getSchemaName(this.schemaName);
|
|
3640
|
+
const fullChildTable = getTableName({ indexName: childTable, schemaName });
|
|
3641
|
+
const fullParentTable = getTableName({ indexName: parentTable, schemaName });
|
|
3642
|
+
const childFk = `"${utils.parseSqlIdentifier(childForeignKey, "column name")}"`;
|
|
3643
|
+
const parentPk = `"${utils.parseSqlIdentifier(parentKey, "column name")}"`;
|
|
3644
|
+
const parentCol = `"${utils.parseSqlIdentifier(parentColumn, "column name")}"`;
|
|
3645
|
+
return this.client.tx(async (t) => {
|
|
3646
|
+
const rows = await t.manyOrNone(
|
|
3647
|
+
`SELECT ${parentPk} AS id FROM ${fullParentTable} WHERE ${parentCol} < $1 LIMIT $2 FOR UPDATE SKIP LOCKED`,
|
|
3648
|
+
[cutoff, limit]
|
|
3649
|
+
);
|
|
3650
|
+
const ids = rows.map((r) => r.id);
|
|
3651
|
+
if (ids.length === 0) return { parents: 0, children: 0 };
|
|
3652
|
+
const childResult = await t.query(`DELETE FROM ${fullChildTable} WHERE ${childFk} = ANY($1)`, [ids]);
|
|
3653
|
+
const parentResult = await t.query(`DELETE FROM ${fullParentTable} WHERE ${parentPk} = ANY($1)`, [ids]);
|
|
3654
|
+
return {
|
|
3655
|
+
parents: parentResult.rowCount ?? 0,
|
|
3656
|
+
children: childResult.rowCount ?? 0
|
|
3657
|
+
};
|
|
3658
|
+
});
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Creates a btree index on `column` for `tableName` if it does not already
|
|
3662
|
+
* exist, so age-based prune deletes stay fast. Delegates to {@link createIndex}
|
|
3663
|
+
* (which is a no-op when the index is present).
|
|
3664
|
+
*
|
|
3665
|
+
* The name is lowercased and truncated to Postgres' 63-byte identifier limit
|
|
3666
|
+
* (schema-prefixed names can exceed it), mirroring {@link buildConstraintName}.
|
|
3667
|
+
*/
|
|
3668
|
+
async ensureIndex({
|
|
3669
|
+
indexName,
|
|
3670
|
+
tableName,
|
|
3671
|
+
column
|
|
3672
|
+
}) {
|
|
3673
|
+
const name = buildConstraintName({ baseName: indexName });
|
|
3674
|
+
await this.createIndex({ name, table: tableName, columns: [column] });
|
|
3675
|
+
}
|
|
3581
3676
|
async executeUpdate(client, {
|
|
3582
3677
|
tableName,
|
|
3583
3678
|
keys,
|
|
@@ -3653,6 +3748,20 @@ function transformFromSqlRow({
|
|
|
3653
3748
|
});
|
|
3654
3749
|
return result;
|
|
3655
3750
|
}
|
|
3751
|
+
function tenancyWhere(filters, startIndex) {
|
|
3752
|
+
const conditions = [];
|
|
3753
|
+
const params = [];
|
|
3754
|
+
let idx = startIndex;
|
|
3755
|
+
if (filters?.organizationId !== void 0) {
|
|
3756
|
+
conditions.push(`"organizationId" = $${idx++}`);
|
|
3757
|
+
params.push(filters.organizationId);
|
|
3758
|
+
}
|
|
3759
|
+
if (filters?.projectId !== void 0) {
|
|
3760
|
+
conditions.push(`"projectId" = $${idx++}`);
|
|
3761
|
+
params.push(filters.projectId);
|
|
3762
|
+
}
|
|
3763
|
+
return { conditions, params, nextIndex: idx };
|
|
3764
|
+
}
|
|
3656
3765
|
|
|
3657
3766
|
// src/storage/domains/agents/index.ts
|
|
3658
3767
|
var AgentsPG = class _AgentsPG extends storage.AgentsStorage {
|
|
@@ -4536,6 +4645,98 @@ var AgentsPG = class _AgentsPG extends storage.AgentsStorage {
|
|
|
4536
4645
|
};
|
|
4537
4646
|
}
|
|
4538
4647
|
};
|
|
4648
|
+
var DEFAULT_BATCH_SIZE = 1e3;
|
|
4649
|
+
async function sleep(ms, signal) {
|
|
4650
|
+
if (ms <= 0 || signal?.aborted) return;
|
|
4651
|
+
await new Promise((resolve) => {
|
|
4652
|
+
const timer = setTimeout(() => {
|
|
4653
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4654
|
+
resolve();
|
|
4655
|
+
}, ms);
|
|
4656
|
+
const onAbort = () => {
|
|
4657
|
+
clearTimeout(timer);
|
|
4658
|
+
resolve();
|
|
4659
|
+
};
|
|
4660
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4661
|
+
});
|
|
4662
|
+
}
|
|
4663
|
+
function cutoffFor(policy, anchorType, now = Date.now()) {
|
|
4664
|
+
const cutoffMs = now - storage.parseDuration(policy.maxAge);
|
|
4665
|
+
return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs);
|
|
4666
|
+
}
|
|
4667
|
+
async function runBatchedDelete({
|
|
4668
|
+
deleteBatch,
|
|
4669
|
+
batchSize,
|
|
4670
|
+
options
|
|
4671
|
+
}) {
|
|
4672
|
+
if (!Number.isSafeInteger(batchSize) || batchSize <= 0) {
|
|
4673
|
+
throw new Error(`retention batchSize must be a positive integer; received ${batchSize}`);
|
|
4674
|
+
}
|
|
4675
|
+
let deleted = 0;
|
|
4676
|
+
let batches = 0;
|
|
4677
|
+
while (true) {
|
|
4678
|
+
if (options?.signal?.aborted) return { deleted, done: false };
|
|
4679
|
+
if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return { deleted, done: false };
|
|
4680
|
+
let limit = batchSize;
|
|
4681
|
+
if (options?.maxRows !== void 0) {
|
|
4682
|
+
const remaining = options.maxRows - deleted;
|
|
4683
|
+
if (remaining <= 0) return { deleted, done: false };
|
|
4684
|
+
limit = Math.min(limit, remaining);
|
|
4685
|
+
}
|
|
4686
|
+
const affected = await deleteBatch(limit);
|
|
4687
|
+
deleted += affected;
|
|
4688
|
+
batches += 1;
|
|
4689
|
+
if (affected < limit) return { deleted, done: true };
|
|
4690
|
+
if (options?.pauseMs) {
|
|
4691
|
+
await sleep(options.pauseMs, options.signal);
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
async function runPrune({
|
|
4696
|
+
db,
|
|
4697
|
+
domain,
|
|
4698
|
+
targets,
|
|
4699
|
+
options
|
|
4700
|
+
}) {
|
|
4701
|
+
const results = [];
|
|
4702
|
+
const now = Date.now();
|
|
4703
|
+
for (const target of targets) {
|
|
4704
|
+
if (options?.signal?.aborted) {
|
|
4705
|
+
results.push({ domain, table: target.table, deleted: 0, done: false });
|
|
4706
|
+
continue;
|
|
4707
|
+
}
|
|
4708
|
+
const cutoff = cutoffFor(target.policy, target.anchorType, now);
|
|
4709
|
+
const batchSize = target.policy.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
4710
|
+
const { deleted, done } = await runBatchedDelete({
|
|
4711
|
+
deleteBatch: (limit) => db.pruneBatch({ tableName: target.table, column: target.column, cutoff, limit }),
|
|
4712
|
+
batchSize,
|
|
4713
|
+
options
|
|
4714
|
+
});
|
|
4715
|
+
results.push({ domain, table: target.table, deleted, done });
|
|
4716
|
+
}
|
|
4717
|
+
return results;
|
|
4718
|
+
}
|
|
4719
|
+
function resolveTargets({
|
|
4720
|
+
policies,
|
|
4721
|
+
descriptor,
|
|
4722
|
+
order
|
|
4723
|
+
}) {
|
|
4724
|
+
const targets = [];
|
|
4725
|
+
for (const key of order) {
|
|
4726
|
+
const policy = policies[key];
|
|
4727
|
+
const entry = descriptor[key];
|
|
4728
|
+
if (!policy || !entry) continue;
|
|
4729
|
+
targets.push({
|
|
4730
|
+
table: entry.table,
|
|
4731
|
+
column: entry.column,
|
|
4732
|
+
anchorType: entry.anchorType ?? "timestamp",
|
|
4733
|
+
policy
|
|
4734
|
+
});
|
|
4735
|
+
}
|
|
4736
|
+
return targets;
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
// src/storage/domains/background-tasks/index.ts
|
|
4539
4740
|
function getSchemaName3(schema) {
|
|
4540
4741
|
return schema ? `"${schema}"` : '"public"';
|
|
4541
4742
|
}
|
|
@@ -4590,6 +4791,14 @@ var BackgroundTasksPG = class _BackgroundTasksPG extends storage.BackgroundTasks
|
|
|
4590
4791
|
#skipDefaultIndexes;
|
|
4591
4792
|
#indexes;
|
|
4592
4793
|
static MANAGED_TABLES = [storage.TABLE_BACKGROUND_TASKS];
|
|
4794
|
+
/**
|
|
4795
|
+
* Completed background tasks accumulate as dead weight. Anchored on the
|
|
4796
|
+
* timezone-aware `completedAtZ` mirror column: NULL (in-flight) rows are
|
|
4797
|
+
* excluded automatically since `completedAtZ < cutoff` is false for NULL.
|
|
4798
|
+
*/
|
|
4799
|
+
static retentionTables = {
|
|
4800
|
+
backgroundTasks: { table: storage.TABLE_BACKGROUND_TASKS, column: "completedAtZ", indexed: true }
|
|
4801
|
+
};
|
|
4593
4802
|
constructor(config) {
|
|
4594
4803
|
super();
|
|
4595
4804
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -4611,6 +4820,40 @@ var BackgroundTasksPG = class _BackgroundTasksPG extends storage.BackgroundTasks
|
|
|
4611
4820
|
await this.createDefaultIndexes();
|
|
4612
4821
|
await this.createCustomIndexes();
|
|
4613
4822
|
}
|
|
4823
|
+
/**
|
|
4824
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
4825
|
+
* anchor column so age-based `prune()` deletes stay fast on large tables.
|
|
4826
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
4827
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
4828
|
+
* logged and pruning proceeds (correct, just slower).
|
|
4829
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
4830
|
+
* so its supporting index is not part of the default index set.
|
|
4831
|
+
*/
|
|
4832
|
+
async ensureRetentionIndexes(policies) {
|
|
4833
|
+
const prefix = this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
4834
|
+
for (const [key, entry] of Object.entries(_BackgroundTasksPG.retentionTables)) {
|
|
4835
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
4836
|
+
try {
|
|
4837
|
+
await this.#db.ensureIndex({
|
|
4838
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
4839
|
+
tableName: entry.table,
|
|
4840
|
+
column: entry.column
|
|
4841
|
+
});
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
/** Delete completed tasks older than the `backgroundTasks` policy's `maxAge`, batched. */
|
|
4848
|
+
async prune(policies, options) {
|
|
4849
|
+
await this.ensureRetentionIndexes(policies);
|
|
4850
|
+
const targets = resolveTargets({
|
|
4851
|
+
policies,
|
|
4852
|
+
descriptor: _BackgroundTasksPG.retentionTables,
|
|
4853
|
+
order: ["backgroundTasks"]
|
|
4854
|
+
});
|
|
4855
|
+
return runPrune({ db: this.#db, domain: "backgroundTasks", targets, options });
|
|
4856
|
+
}
|
|
4614
4857
|
static getDefaultIndexDefs(schemaPrefix) {
|
|
4615
4858
|
return [
|
|
4616
4859
|
{
|
|
@@ -5374,8 +5617,8 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5374
5617
|
groundTruthSchema: input.groundTruthSchema ?? null,
|
|
5375
5618
|
requestContextSchema: input.requestContextSchema ?? null,
|
|
5376
5619
|
targetType: input.targetType ?? null,
|
|
5377
|
-
targetIds: input.targetIds
|
|
5378
|
-
scorerIds: input.scorerIds
|
|
5620
|
+
targetIds: input.targetIds ?? null,
|
|
5621
|
+
scorerIds: input.scorerIds ?? null,
|
|
5379
5622
|
organizationId: input.organizationId ?? null,
|
|
5380
5623
|
projectId: input.projectId ?? null,
|
|
5381
5624
|
candidateKey: input.candidateKey ?? null,
|
|
@@ -5415,10 +5658,15 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5415
5658
|
);
|
|
5416
5659
|
}
|
|
5417
5660
|
}
|
|
5418
|
-
async getDatasetById({
|
|
5661
|
+
async getDatasetById({
|
|
5662
|
+
id,
|
|
5663
|
+
filters
|
|
5664
|
+
}) {
|
|
5419
5665
|
try {
|
|
5420
5666
|
const tableName = getTableName2({ indexName: storage.TABLE_DATASETS, schemaName: getSchemaName2(this.#schema) });
|
|
5421
|
-
const
|
|
5667
|
+
const { conditions, params } = tenancyWhere(filters, 2);
|
|
5668
|
+
const whereSql = ['"id" = $1', ...conditions].join(" AND ");
|
|
5669
|
+
const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
|
|
5422
5670
|
return result ? this.transformDatasetRow(result) : null;
|
|
5423
5671
|
} catch (error$1) {
|
|
5424
5672
|
throw new error.MastraError(
|
|
@@ -5433,7 +5681,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5433
5681
|
}
|
|
5434
5682
|
async _doUpdateDataset(args) {
|
|
5435
5683
|
try {
|
|
5436
|
-
const existing = await this.getDatasetById({ id: args.id });
|
|
5684
|
+
const existing = await this.getDatasetById({ id: args.id, filters: args.filters });
|
|
5437
5685
|
if (!existing) {
|
|
5438
5686
|
throw new error.MastraError({
|
|
5439
5687
|
id: storage.createStorageErrorId("PG", "UPDATE_DATASET", "NOT_FOUND"),
|
|
@@ -5522,7 +5770,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5522
5770
|
);
|
|
5523
5771
|
}
|
|
5524
5772
|
}
|
|
5525
|
-
async deleteDataset({ id }) {
|
|
5773
|
+
async deleteDataset({ id, filters }) {
|
|
5526
5774
|
try {
|
|
5527
5775
|
const datasetsTable = getTableName2({ indexName: storage.TABLE_DATASETS, schemaName: getSchemaName2(this.#schema) });
|
|
5528
5776
|
const itemsTable = getTableName2({ indexName: storage.TABLE_DATASET_ITEMS, schemaName: getSchemaName2(this.#schema) });
|
|
@@ -5535,24 +5783,31 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5535
5783
|
indexName: storage.TABLE_EXPERIMENT_RESULTS,
|
|
5536
5784
|
schemaName: getSchemaName2(this.#schema)
|
|
5537
5785
|
});
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
`DELETE FROM ${experimentResultsTable} WHERE "experimentId" IN (SELECT "id" FROM ${experimentsTable} WHERE "datasetId" = $1)`,
|
|
5541
|
-
[id]
|
|
5542
|
-
);
|
|
5543
|
-
} catch {
|
|
5544
|
-
}
|
|
5545
|
-
try {
|
|
5546
|
-
await this.#db.client.none(
|
|
5547
|
-
`UPDATE ${experimentsTable} SET "datasetId" = NULL, "datasetVersion" = NULL WHERE "datasetId" = $1`,
|
|
5548
|
-
[id]
|
|
5549
|
-
);
|
|
5550
|
-
} catch {
|
|
5551
|
-
}
|
|
5786
|
+
const { conditions, params } = tenancyWhere(filters, 2);
|
|
5787
|
+
const scopedWhere = ['"id" = $1', ...conditions].join(" AND ");
|
|
5552
5788
|
await this.#db.client.tx(async (t) => {
|
|
5789
|
+
const locked = await t.oneOrNone(`SELECT "id" FROM ${datasetsTable} WHERE ${scopedWhere} FOR UPDATE`, [
|
|
5790
|
+
id,
|
|
5791
|
+
...params
|
|
5792
|
+
]);
|
|
5793
|
+
if (!locked) return;
|
|
5794
|
+
const experimentTablesExist = await t.oneOrNone(
|
|
5795
|
+
`SELECT to_regclass($1) IS NOT NULL AND to_regclass($2) IS NOT NULL AS exists`,
|
|
5796
|
+
[experimentResultsTable, experimentsTable]
|
|
5797
|
+
);
|
|
5798
|
+
if (experimentTablesExist?.exists) {
|
|
5799
|
+
await t.none(
|
|
5800
|
+
`DELETE FROM ${experimentResultsTable} WHERE "experimentId" IN (SELECT "id" FROM ${experimentsTable} WHERE "datasetId" = $1)`,
|
|
5801
|
+
[id]
|
|
5802
|
+
);
|
|
5803
|
+
await t.none(
|
|
5804
|
+
`UPDATE ${experimentsTable} SET "datasetId" = NULL, "datasetVersion" = NULL WHERE "datasetId" = $1`,
|
|
5805
|
+
[id]
|
|
5806
|
+
);
|
|
5807
|
+
}
|
|
5553
5808
|
await t.none(`DELETE FROM ${versionsTable} WHERE "datasetId" = $1`, [id]);
|
|
5554
5809
|
await t.none(`DELETE FROM ${itemsTable} WHERE "datasetId" = $1`, [id]);
|
|
5555
|
-
await t.none(`DELETE FROM ${datasetsTable} WHERE
|
|
5810
|
+
await t.none(`DELETE FROM ${datasetsTable} WHERE ${scopedWhere}`, [id, ...params]);
|
|
5556
5811
|
});
|
|
5557
5812
|
} catch (error$1) {
|
|
5558
5813
|
if (error$1 instanceof error.MastraError) throw error$1;
|
|
@@ -5574,7 +5829,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5574
5829
|
const queryParams = [];
|
|
5575
5830
|
let paramIndex = 1;
|
|
5576
5831
|
if (args.filters) {
|
|
5577
|
-
const { organizationId, projectId, candidateKey, candidateId } = args.filters;
|
|
5832
|
+
const { organizationId, projectId, candidateKey, candidateId, targetType, targetIds, name } = args.filters;
|
|
5578
5833
|
if (organizationId !== void 0) {
|
|
5579
5834
|
conditions.push(`"organizationId" = $${paramIndex++}`);
|
|
5580
5835
|
queryParams.push(organizationId);
|
|
@@ -5591,6 +5846,18 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
5591
5846
|
conditions.push(`"candidateId" = $${paramIndex++}`);
|
|
5592
5847
|
queryParams.push(candidateId);
|
|
5593
5848
|
}
|
|
5849
|
+
if (targetType !== void 0) {
|
|
5850
|
+
conditions.push(`"targetType" = $${paramIndex++}`);
|
|
5851
|
+
queryParams.push(targetType);
|
|
5852
|
+
}
|
|
5853
|
+
if (targetIds !== void 0 && targetIds.length > 0) {
|
|
5854
|
+
conditions.push(`"targetIds" ?| $${paramIndex++}::text[]`);
|
|
5855
|
+
queryParams.push(targetIds);
|
|
5856
|
+
}
|
|
5857
|
+
if (name !== void 0 && name.length > 0) {
|
|
5858
|
+
conditions.push(`"name" ILIKE $${paramIndex++}`);
|
|
5859
|
+
queryParams.push(`%${name}%`);
|
|
5860
|
+
}
|
|
5594
5861
|
}
|
|
5595
5862
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5596
5863
|
const countResult = await this.#db.client.one(
|
|
@@ -6248,12 +6515,24 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
|
|
|
6248
6515
|
await this.#db.clearTable({ tableName: storage.TABLE_DATASETS });
|
|
6249
6516
|
}
|
|
6250
6517
|
};
|
|
6518
|
+
var DEFAULT_PRUNE_BATCH_SIZE = 1e3;
|
|
6251
6519
|
var ExperimentsPG = class _ExperimentsPG extends storage.ExperimentsStorage {
|
|
6252
6520
|
#db;
|
|
6253
6521
|
#schema;
|
|
6254
6522
|
#skipDefaultIndexes;
|
|
6255
6523
|
#indexes;
|
|
6256
6524
|
static MANAGED_TABLES = [storage.TABLE_EXPERIMENTS, storage.TABLE_EXPERIMENT_RESULTS];
|
|
6525
|
+
/**
|
|
6526
|
+
* Experiments prune as whole units: an aged experiment and its result rows go
|
|
6527
|
+
* together, mirroring `deleteExperiment`. Anchored on `completedAt` (not the
|
|
6528
|
+
* `completedAtZ` mirror, which carries a `DEFAULT NOW()` this domain never
|
|
6529
|
+
* overrides — it holds insert time even for running rows). `completedAt` is
|
|
6530
|
+
* written as a UTC ISO string and stays NULL while running, so
|
|
6531
|
+
* `completedAt < cutoff` is false for in-flight experiments.
|
|
6532
|
+
*/
|
|
6533
|
+
static retentionTables = {
|
|
6534
|
+
experiments: { table: storage.TABLE_EXPERIMENTS, column: "completedAt", indexed: true }
|
|
6535
|
+
};
|
|
6257
6536
|
constructor(config) {
|
|
6258
6537
|
super();
|
|
6259
6538
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -6292,6 +6571,75 @@ var ExperimentsPG = class _ExperimentsPG extends storage.ExperimentsStorage {
|
|
|
6292
6571
|
await this.createDefaultIndexes();
|
|
6293
6572
|
await this.createCustomIndexes();
|
|
6294
6573
|
}
|
|
6574
|
+
/**
|
|
6575
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
6576
|
+
* anchor column so age-based `prune()` deletes stay fast on large tables.
|
|
6577
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
6578
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
6579
|
+
* logged and pruning proceeds (correct, just slower).
|
|
6580
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
6581
|
+
* so its supporting index is not part of the default index set.
|
|
6582
|
+
*/
|
|
6583
|
+
async ensureRetentionIndexes(policies) {
|
|
6584
|
+
const prefix = this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
6585
|
+
for (const [key, entry] of Object.entries(_ExperimentsPG.retentionTables)) {
|
|
6586
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
6587
|
+
try {
|
|
6588
|
+
await this.#db.ensureIndex({
|
|
6589
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
6590
|
+
tableName: entry.table,
|
|
6591
|
+
column: entry.column
|
|
6592
|
+
});
|
|
6593
|
+
} catch (error) {
|
|
6594
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
/**
|
|
6599
|
+
* Delete experiments whose `completedAt` is older than the policy's `maxAge`.
|
|
6600
|
+
*
|
|
6601
|
+
* Each batch selects up to `batchSize` aged experiments and deletes their
|
|
6602
|
+
* `experiment_results` rows and the experiment rows in one transaction —
|
|
6603
|
+
* mirroring `deleteExperiment` — so hitting `maxBatches`/`maxRows` or the
|
|
6604
|
+
* abort signal between batches never leaves a run hollow (parent kept,
|
|
6605
|
+
* results gone). NULL `completedAt` (still running) is excluded by the
|
|
6606
|
+
* `< cutoff` predicate. Bounds count whole experiments, not rows.
|
|
6607
|
+
*/
|
|
6608
|
+
async prune(policies, options) {
|
|
6609
|
+
const policy = policies["experiments"];
|
|
6610
|
+
if (!policy || options?.signal?.aborted) {
|
|
6611
|
+
return policy ? [
|
|
6612
|
+
{ domain: "experiments", table: storage.TABLE_EXPERIMENT_RESULTS, deleted: 0, done: false },
|
|
6613
|
+
{ domain: "experiments", table: storage.TABLE_EXPERIMENTS, deleted: 0, done: false }
|
|
6614
|
+
] : [];
|
|
6615
|
+
}
|
|
6616
|
+
await this.ensureRetentionIndexes(policies);
|
|
6617
|
+
const rawCutoff = cutoffFor(policy, "timestamp");
|
|
6618
|
+
const cutoff = rawCutoff instanceof Date ? rawCutoff.toISOString() : rawCutoff;
|
|
6619
|
+
const batchSize = policy.batchSize ?? DEFAULT_PRUNE_BATCH_SIZE;
|
|
6620
|
+
let childDeleted = 0;
|
|
6621
|
+
const parent = await runBatchedDelete({
|
|
6622
|
+
deleteBatch: async (limit) => {
|
|
6623
|
+
const { parents, children } = await this.#db.pruneUnitsBatch({
|
|
6624
|
+
parentTable: storage.TABLE_EXPERIMENTS,
|
|
6625
|
+
parentKey: "id",
|
|
6626
|
+
parentColumn: "completedAt",
|
|
6627
|
+
childTable: storage.TABLE_EXPERIMENT_RESULTS,
|
|
6628
|
+
childForeignKey: "experimentId",
|
|
6629
|
+
cutoff,
|
|
6630
|
+
limit
|
|
6631
|
+
});
|
|
6632
|
+
childDeleted += children;
|
|
6633
|
+
return parents;
|
|
6634
|
+
},
|
|
6635
|
+
batchSize,
|
|
6636
|
+
options
|
|
6637
|
+
});
|
|
6638
|
+
return [
|
|
6639
|
+
{ domain: "experiments", table: storage.TABLE_EXPERIMENT_RESULTS, deleted: childDeleted, done: parent.done },
|
|
6640
|
+
{ domain: "experiments", table: storage.TABLE_EXPERIMENTS, deleted: parent.deleted, done: parent.done }
|
|
6641
|
+
];
|
|
6642
|
+
}
|
|
6295
6643
|
getDefaultIndexDefinitions() {
|
|
6296
6644
|
return [
|
|
6297
6645
|
{ name: "idx_experiments_datasetid", table: storage.TABLE_EXPERIMENTS, columns: ["datasetId"] },
|
|
@@ -6521,10 +6869,15 @@ var ExperimentsPG = class _ExperimentsPG extends storage.ExperimentsStorage {
|
|
|
6521
6869
|
);
|
|
6522
6870
|
}
|
|
6523
6871
|
}
|
|
6524
|
-
async getExperimentById({
|
|
6872
|
+
async getExperimentById({
|
|
6873
|
+
id,
|
|
6874
|
+
filters
|
|
6875
|
+
}) {
|
|
6525
6876
|
try {
|
|
6526
6877
|
const tableName = getTableName2({ indexName: storage.TABLE_EXPERIMENTS, schemaName: getSchemaName2(this.#schema) });
|
|
6527
|
-
const
|
|
6878
|
+
const { conditions, params } = tenancyWhere(filters, 2);
|
|
6879
|
+
const whereSql = ['"id" = $1', ...conditions].join(" AND ");
|
|
6880
|
+
const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
|
|
6528
6881
|
return result ? this.transformExperimentRow(result) : null;
|
|
6529
6882
|
} catch (error$1) {
|
|
6530
6883
|
throw new error.MastraError(
|
|
@@ -6611,15 +6964,21 @@ var ExperimentsPG = class _ExperimentsPG extends storage.ExperimentsStorage {
|
|
|
6611
6964
|
);
|
|
6612
6965
|
}
|
|
6613
6966
|
}
|
|
6614
|
-
async deleteExperiment({ id }) {
|
|
6967
|
+
async deleteExperiment({ id, filters }) {
|
|
6615
6968
|
try {
|
|
6616
6969
|
const resultsTable = getTableName2({
|
|
6617
6970
|
indexName: storage.TABLE_EXPERIMENT_RESULTS,
|
|
6618
6971
|
schemaName: getSchemaName2(this.#schema)
|
|
6619
6972
|
});
|
|
6620
6973
|
const experimentsTable = getTableName2({ indexName: storage.TABLE_EXPERIMENTS, schemaName: getSchemaName2(this.#schema) });
|
|
6621
|
-
|
|
6622
|
-
|
|
6974
|
+
const { conditions, params } = tenancyWhere(filters, 2);
|
|
6975
|
+
const gateSql = `SELECT "id" FROM ${experimentsTable} WHERE ${['"id" = $1', ...conditions].join(" AND ")} FOR UPDATE`;
|
|
6976
|
+
await this.#db.client.tx(async (t) => {
|
|
6977
|
+
const parent = await t.oneOrNone(gateSql, [id, ...params]);
|
|
6978
|
+
if (!parent) return;
|
|
6979
|
+
await t.none(`DELETE FROM ${resultsTable} WHERE "experimentId" = $1`, [id]);
|
|
6980
|
+
await t.none(`DELETE FROM ${experimentsTable} WHERE "id" = $1`, [id]);
|
|
6981
|
+
});
|
|
6623
6982
|
} catch (error$1) {
|
|
6624
6983
|
throw new error.MastraError(
|
|
6625
6984
|
{
|
|
@@ -6749,10 +7108,15 @@ var ExperimentsPG = class _ExperimentsPG extends storage.ExperimentsStorage {
|
|
|
6749
7108
|
);
|
|
6750
7109
|
}
|
|
6751
7110
|
}
|
|
6752
|
-
async getExperimentResultById({
|
|
7111
|
+
async getExperimentResultById({
|
|
7112
|
+
id,
|
|
7113
|
+
filters
|
|
7114
|
+
}) {
|
|
6753
7115
|
try {
|
|
6754
7116
|
const tableName = getTableName2({ indexName: storage.TABLE_EXPERIMENT_RESULTS, schemaName: getSchemaName2(this.#schema) });
|
|
6755
|
-
const
|
|
7117
|
+
const { conditions, params } = tenancyWhere(filters, 2);
|
|
7118
|
+
const whereSql = ['"id" = $1', ...conditions].join(" AND ");
|
|
7119
|
+
const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
|
|
6756
7120
|
return result ? this.transformExperimentResultRow(result) : null;
|
|
6757
7121
|
} catch (error$1) {
|
|
6758
7122
|
throw new error.MastraError(
|
|
@@ -6827,9 +7191,23 @@ var ExperimentsPG = class _ExperimentsPG extends storage.ExperimentsStorage {
|
|
|
6827
7191
|
);
|
|
6828
7192
|
}
|
|
6829
7193
|
}
|
|
6830
|
-
async deleteExperimentResults({
|
|
7194
|
+
async deleteExperimentResults({
|
|
7195
|
+
experimentId,
|
|
7196
|
+
filters
|
|
7197
|
+
}) {
|
|
6831
7198
|
try {
|
|
6832
7199
|
const tableName = getTableName2({ indexName: storage.TABLE_EXPERIMENT_RESULTS, schemaName: getSchemaName2(this.#schema) });
|
|
7200
|
+
const experimentsTable = getTableName2({ indexName: storage.TABLE_EXPERIMENTS, schemaName: getSchemaName2(this.#schema) });
|
|
7201
|
+
if (filters?.organizationId !== void 0 || filters?.projectId !== void 0) {
|
|
7202
|
+
const { conditions, params } = tenancyWhere(filters, 2);
|
|
7203
|
+
const gateSql = `SELECT "id" FROM ${experimentsTable} WHERE ${['"id" = $1', ...conditions].join(" AND ")} FOR UPDATE`;
|
|
7204
|
+
await this.#db.client.tx(async (t) => {
|
|
7205
|
+
const parent = await t.oneOrNone(gateSql, [experimentId, ...params]);
|
|
7206
|
+
if (!parent) return;
|
|
7207
|
+
await t.none(`DELETE FROM ${tableName} WHERE "experimentId" = $1`, [experimentId]);
|
|
7208
|
+
});
|
|
7209
|
+
return;
|
|
7210
|
+
}
|
|
6833
7211
|
await this.#db.client.none(`DELETE FROM ${tableName} WHERE "experimentId" = $1`, [experimentId]);
|
|
6834
7212
|
} catch (error$1) {
|
|
6835
7213
|
throw new error.MastraError(
|
|
@@ -8460,6 +8838,18 @@ function dedupeMessagesForSave(messages) {
|
|
|
8460
8838
|
}
|
|
8461
8839
|
var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
|
|
8462
8840
|
supportsObservationalMemory = true;
|
|
8841
|
+
/**
|
|
8842
|
+
* Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
|
|
8843
|
+
* on the timezone-aware `createdAtZ` mirror column (kept in sync by triggers),
|
|
8844
|
+
* and are indexed for fast batched deletes. Cascade order is enforced in
|
|
8845
|
+
* `prune()` (children before threads), not here. Observational memory has no
|
|
8846
|
+
* timestamp anchor and is deliberately excluded.
|
|
8847
|
+
*/
|
|
8848
|
+
static retentionTables = {
|
|
8849
|
+
messages: { table: storage.TABLE_MESSAGES, column: "createdAtZ", indexed: true },
|
|
8850
|
+
resources: { table: storage.TABLE_RESOURCES, column: "createdAtZ", indexed: true },
|
|
8851
|
+
threads: { table: storage.TABLE_THREADS, column: "createdAtZ", indexed: true }
|
|
8852
|
+
};
|
|
8463
8853
|
#db;
|
|
8464
8854
|
#schema;
|
|
8465
8855
|
#skipDefaultIndexes;
|
|
@@ -8505,6 +8895,30 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
|
|
|
8505
8895
|
await this.createDefaultIndexes();
|
|
8506
8896
|
await this.createCustomIndexes();
|
|
8507
8897
|
}
|
|
8898
|
+
/**
|
|
8899
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
8900
|
+
* anchor column so age-based `prune()` deletes stay fast on large tables.
|
|
8901
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
8902
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
8903
|
+
* logged and pruning proceeds (correct, just slower).
|
|
8904
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
8905
|
+
* so its supporting index is not part of the default index set.
|
|
8906
|
+
*/
|
|
8907
|
+
async ensureRetentionIndexes(policies) {
|
|
8908
|
+
const prefix = this.#schema && this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
8909
|
+
for (const [key, entry] of Object.entries(_MemoryPG.retentionTables)) {
|
|
8910
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
8911
|
+
try {
|
|
8912
|
+
await this.#db.ensureIndex({
|
|
8913
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
8914
|
+
tableName: entry.table,
|
|
8915
|
+
column: entry.column
|
|
8916
|
+
});
|
|
8917
|
+
} catch (error) {
|
|
8918
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
}
|
|
8508
8922
|
/**
|
|
8509
8923
|
* Returns default index definitions for the memory domain tables.
|
|
8510
8924
|
* @param schemaPrefix - Prefix for index names (e.g. "my_schema_" or "")
|
|
@@ -8605,6 +9019,76 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
|
|
|
8605
9019
|
await this.#db.clearTable({ tableName: storage.TABLE_THREADS });
|
|
8606
9020
|
await this.#db.clearTable({ tableName: storage.TABLE_RESOURCES });
|
|
8607
9021
|
}
|
|
9022
|
+
/**
|
|
9023
|
+
* Deletes rows older than the configured `maxAge` per table, in bounded,
|
|
9024
|
+
* batched, cancellable chunks. Tables are pruned children-first (messages and
|
|
9025
|
+
* resources before threads) since PostgreSQL has no FK cascade in this schema.
|
|
9026
|
+
* Unset tables are kept forever.
|
|
9027
|
+
*
|
|
9028
|
+
* When a `messages` policy is set, semantic-recall embeddings for pruned
|
|
9029
|
+
* messages are also swept from same-schema `memory_messages*` vector tables
|
|
9030
|
+
* (best-effort, mirroring `deleteThread`). Embeddings held in an external
|
|
9031
|
+
* vector store are out of reach and must be pruned by the operator.
|
|
9032
|
+
*/
|
|
9033
|
+
async prune(policies, options) {
|
|
9034
|
+
await this.ensureRetentionIndexes(policies);
|
|
9035
|
+
const targets = resolveTargets({
|
|
9036
|
+
policies,
|
|
9037
|
+
descriptor: _MemoryPG.retentionTables,
|
|
9038
|
+
order: ["messages", "resources", "threads"]
|
|
9039
|
+
});
|
|
9040
|
+
const results = await runPrune({ db: this.#db, domain: "memory", targets, options });
|
|
9041
|
+
if (policies["messages"]) {
|
|
9042
|
+
await this.pruneOrphanedVectorRows(policies["messages"], options);
|
|
9043
|
+
}
|
|
9044
|
+
return results;
|
|
9045
|
+
}
|
|
9046
|
+
/**
|
|
9047
|
+
* Best-effort sweep of semantic-recall vector rows whose source message no
|
|
9048
|
+
* longer exists (e.g. it was just pruned), so recall doesn't keep returning
|
|
9049
|
+
* embeddings that resolve to nothing. Only same-schema default vector tables
|
|
9050
|
+
* (`memory_messages*`) are covered — the same set `deleteThread` cleans up.
|
|
9051
|
+
* Failures are logged, never thrown: vector cleanup must not fail the prune.
|
|
9052
|
+
*/
|
|
9053
|
+
async pruneOrphanedVectorRows(policy, options) {
|
|
9054
|
+
try {
|
|
9055
|
+
const schemaName = this.#schema || "public";
|
|
9056
|
+
const vectorTables = await this.#db.client.manyOrNone(
|
|
9057
|
+
`
|
|
9058
|
+
SELECT tablename
|
|
9059
|
+
FROM pg_tables
|
|
9060
|
+
WHERE schemaname = $1
|
|
9061
|
+
AND (tablename = 'memory_messages' OR tablename LIKE 'memory_messages_%')
|
|
9062
|
+
`,
|
|
9063
|
+
[schemaName]
|
|
9064
|
+
);
|
|
9065
|
+
const messagesTable = getTableName4({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName4(this.#schema) });
|
|
9066
|
+
for (const { tablename } of vectorTables) {
|
|
9067
|
+
const vectorTableName = getTableName4({ indexName: tablename, schemaName: getSchemaName4(this.#schema) });
|
|
9068
|
+
await runBatchedDelete({
|
|
9069
|
+
deleteBatch: async (limit) => {
|
|
9070
|
+
const result = await this.#db.client.query(
|
|
9071
|
+
`
|
|
9072
|
+
DELETE FROM ${vectorTableName}
|
|
9073
|
+
WHERE ctid IN (
|
|
9074
|
+
SELECT v.ctid FROM ${vectorTableName} v
|
|
9075
|
+
WHERE v.metadata->>'message_id' IS NOT NULL
|
|
9076
|
+
AND NOT EXISTS (SELECT 1 FROM ${messagesTable} m WHERE m.id = v.metadata->>'message_id')
|
|
9077
|
+
LIMIT $1
|
|
9078
|
+
)
|
|
9079
|
+
`,
|
|
9080
|
+
[limit]
|
|
9081
|
+
);
|
|
9082
|
+
return result.rowCount ?? 0;
|
|
9083
|
+
},
|
|
9084
|
+
batchSize: policy.batchSize ?? 1e3,
|
|
9085
|
+
options
|
|
9086
|
+
});
|
|
9087
|
+
}
|
|
9088
|
+
} catch (error) {
|
|
9089
|
+
this.logger?.warn?.("Failed to sweep orphaned semantic-recall vector rows after prune:", error);
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
8608
9092
|
/**
|
|
8609
9093
|
* Normalizes message row from database by applying createdAtZ fallback
|
|
8610
9094
|
*/
|
|
@@ -10916,6 +11400,13 @@ var NotificationsPG = class _NotificationsPG extends storage.NotificationsStorag
|
|
|
10916
11400
|
#skipDefaultIndexes;
|
|
10917
11401
|
#indexes;
|
|
10918
11402
|
static MANAGED_TABLES = [storage.TABLE_NOTIFICATIONS];
|
|
11403
|
+
/**
|
|
11404
|
+
* Notifications are an append-only event feed that grows unbounded. Single
|
|
11405
|
+
* table, anchored on the timezone-aware `createdAtZ` mirror column.
|
|
11406
|
+
*/
|
|
11407
|
+
static retentionTables = {
|
|
11408
|
+
notifications: { table: storage.TABLE_NOTIFICATIONS, column: "createdAtZ", indexed: true }
|
|
11409
|
+
};
|
|
10919
11410
|
constructor(config) {
|
|
10920
11411
|
super();
|
|
10921
11412
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -10932,6 +11423,40 @@ var NotificationsPG = class _NotificationsPG extends storage.NotificationsStorag
|
|
|
10932
11423
|
await this.createDefaultIndexes();
|
|
10933
11424
|
await this.createCustomIndexes();
|
|
10934
11425
|
}
|
|
11426
|
+
/**
|
|
11427
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
11428
|
+
* anchor column so age-based `prune()` deletes stay fast on large tables.
|
|
11429
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
11430
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
11431
|
+
* logged and pruning proceeds (correct, just slower).
|
|
11432
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
11433
|
+
* so its supporting index is not part of the default index set.
|
|
11434
|
+
*/
|
|
11435
|
+
async ensureRetentionIndexes(policies) {
|
|
11436
|
+
const prefix = this.#schema && this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
11437
|
+
for (const [key, entry] of Object.entries(_NotificationsPG.retentionTables)) {
|
|
11438
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
11439
|
+
try {
|
|
11440
|
+
await this.#db.ensureIndex({
|
|
11441
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
11442
|
+
tableName: entry.table,
|
|
11443
|
+
column: entry.column
|
|
11444
|
+
});
|
|
11445
|
+
} catch (error) {
|
|
11446
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
11447
|
+
}
|
|
11448
|
+
}
|
|
11449
|
+
}
|
|
11450
|
+
/** Delete notifications older than the `notifications` policy's `maxAge`, batched. */
|
|
11451
|
+
async prune(policies, options) {
|
|
11452
|
+
await this.ensureRetentionIndexes(policies);
|
|
11453
|
+
const targets = resolveTargets({
|
|
11454
|
+
policies,
|
|
11455
|
+
descriptor: _NotificationsPG.retentionTables,
|
|
11456
|
+
order: ["notifications"]
|
|
11457
|
+
});
|
|
11458
|
+
return runPrune({ db: this.#db, domain: "notifications", targets, options });
|
|
11459
|
+
}
|
|
10935
11460
|
static getDefaultIndexDefs(schemaPrefix) {
|
|
10936
11461
|
return [
|
|
10937
11462
|
{
|
|
@@ -11173,6 +11698,14 @@ var NotificationsPG = class _NotificationsPG extends storage.NotificationsStorag
|
|
|
11173
11698
|
}
|
|
11174
11699
|
};
|
|
11175
11700
|
var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorage {
|
|
11701
|
+
/**
|
|
11702
|
+
* Retention-eligible tables. The observability domain has a single physical
|
|
11703
|
+
* table (`mastra_ai_spans`); spans anchor on the timezone-aware `startedAtZ`
|
|
11704
|
+
* mirror column and are indexed for fast batched deletes.
|
|
11705
|
+
*/
|
|
11706
|
+
static retentionTables = {
|
|
11707
|
+
spans: { table: storage.TABLE_SPANS, column: "startedAtZ", indexed: true }
|
|
11708
|
+
};
|
|
11176
11709
|
#db;
|
|
11177
11710
|
#schema;
|
|
11178
11711
|
#skipDefaultIndexes;
|
|
@@ -11197,6 +11730,30 @@ var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorag
|
|
|
11197
11730
|
await this.createDefaultIndexes();
|
|
11198
11731
|
await this.createCustomIndexes();
|
|
11199
11732
|
}
|
|
11733
|
+
/**
|
|
11734
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
11735
|
+
* anchor column so age-based `prune()` deletes stay fast on large span tables.
|
|
11736
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
11737
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
11738
|
+
* logged and pruning proceeds (correct, just slower).
|
|
11739
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
11740
|
+
* so its supporting index is not part of the default index set.
|
|
11741
|
+
*/
|
|
11742
|
+
async ensureRetentionIndexes(policies) {
|
|
11743
|
+
const prefix = this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
11744
|
+
for (const [key, entry] of Object.entries(_ObservabilityPG.retentionTables)) {
|
|
11745
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
11746
|
+
try {
|
|
11747
|
+
await this.#db.ensureIndex({
|
|
11748
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
11749
|
+
tableName: entry.table,
|
|
11750
|
+
column: entry.column
|
|
11751
|
+
});
|
|
11752
|
+
} catch (error) {
|
|
11753
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
11754
|
+
}
|
|
11755
|
+
}
|
|
11756
|
+
}
|
|
11200
11757
|
/**
|
|
11201
11758
|
* Returns default index definitions for the observability domain tables.
|
|
11202
11759
|
* @param schemaPrefix - Prefix for index names (e.g. "my_schema_" or "")
|
|
@@ -11341,6 +11898,19 @@ var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorag
|
|
|
11341
11898
|
async dangerouslyClearAll() {
|
|
11342
11899
|
await this.#db.clearTable({ tableName: storage.TABLE_SPANS });
|
|
11343
11900
|
}
|
|
11901
|
+
/**
|
|
11902
|
+
* Deletes spans older than the configured `maxAge`, in bounded, batched,
|
|
11903
|
+
* cancellable chunks. Unset = keep forever.
|
|
11904
|
+
*/
|
|
11905
|
+
async prune(policies, options) {
|
|
11906
|
+
await this.ensureRetentionIndexes(policies);
|
|
11907
|
+
const targets = resolveTargets({
|
|
11908
|
+
policies,
|
|
11909
|
+
descriptor: _ObservabilityPG.retentionTables,
|
|
11910
|
+
order: ["spans"]
|
|
11911
|
+
});
|
|
11912
|
+
return runPrune({ db: this.#db, domain: "observability", targets, options });
|
|
11913
|
+
}
|
|
11344
11914
|
get tracingStrategy() {
|
|
11345
11915
|
return {
|
|
11346
11916
|
preferred: "batch-with-updates",
|
|
@@ -14037,6 +14607,78 @@ async function setupPartitioning(client, schema, options = {}) {
|
|
|
14037
14607
|
return "native";
|
|
14038
14608
|
}
|
|
14039
14609
|
}
|
|
14610
|
+
function retentionCutoff(policy, now = Date.now()) {
|
|
14611
|
+
return new Date(now - storage.parseDuration(policy.maxAge));
|
|
14612
|
+
}
|
|
14613
|
+
async function listChildPartitions(client, schema, table) {
|
|
14614
|
+
const rows = await client.manyOrNone(
|
|
14615
|
+
`SELECT c.relname AS "name", pg_get_expr(c.relpartbound, c.oid) AS "bound"
|
|
14616
|
+
FROM pg_inherits i
|
|
14617
|
+
JOIN pg_class c ON c.oid = i.inhrelid
|
|
14618
|
+
JOIN pg_class p ON p.oid = i.inhparent
|
|
14619
|
+
JOIN pg_namespace n ON n.oid = p.relnamespace
|
|
14620
|
+
WHERE n.nspname = $1 AND p.relname = $2`,
|
|
14621
|
+
[schema, table]
|
|
14622
|
+
);
|
|
14623
|
+
const partitions = [];
|
|
14624
|
+
for (const row of rows ?? []) {
|
|
14625
|
+
const match = /TO \('([^']+)'\)/.exec(row.bound ?? "");
|
|
14626
|
+
if (!match?.[1]) continue;
|
|
14627
|
+
const upperBound = new Date(match[1]);
|
|
14628
|
+
if (Number.isNaN(upperBound.getTime())) continue;
|
|
14629
|
+
partitions.push({ name: row.name, upperBound });
|
|
14630
|
+
}
|
|
14631
|
+
return partitions.sort((a, b) => a.upperBound.getTime() - b.upperBound.getTime());
|
|
14632
|
+
}
|
|
14633
|
+
async function prunePartitionedTable({
|
|
14634
|
+
client,
|
|
14635
|
+
schema,
|
|
14636
|
+
table,
|
|
14637
|
+
cutoff,
|
|
14638
|
+
options
|
|
14639
|
+
}) {
|
|
14640
|
+
const partitions = await listChildPartitions(client, schema, table);
|
|
14641
|
+
const droppable = partitions.filter((p) => p.upperBound.getTime() <= cutoff.getTime());
|
|
14642
|
+
let deleted = 0;
|
|
14643
|
+
let batches = 0;
|
|
14644
|
+
for (const partition of droppable) {
|
|
14645
|
+
if (options?.signal?.aborted) return { deleted, done: false };
|
|
14646
|
+
if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return { deleted, done: false };
|
|
14647
|
+
if (options?.maxRows !== void 0 && deleted >= options.maxRows) return { deleted, done: false };
|
|
14648
|
+
const child = qualifiedName(schema, partition.name);
|
|
14649
|
+
const parent = qualifiedTable(schema, table);
|
|
14650
|
+
const row = await client.one(`SELECT count(*)::int AS "n" FROM ${child}`);
|
|
14651
|
+
await client.none(`ALTER TABLE ${parent} DETACH PARTITION ${child}`);
|
|
14652
|
+
await client.none(`DROP TABLE IF EXISTS ${child}`);
|
|
14653
|
+
deleted += row.n;
|
|
14654
|
+
batches += 1;
|
|
14655
|
+
}
|
|
14656
|
+
return { deleted, done: true };
|
|
14657
|
+
}
|
|
14658
|
+
async function pruneTimescaleTable({
|
|
14659
|
+
client,
|
|
14660
|
+
schema,
|
|
14661
|
+
table,
|
|
14662
|
+
cutoff,
|
|
14663
|
+
options
|
|
14664
|
+
}) {
|
|
14665
|
+
if (options?.signal?.aborted || options?.maxBatches === 0) return { deleted: 0, done: false };
|
|
14666
|
+
const tableExpr = qualifiedTable(schema, table);
|
|
14667
|
+
const chunks = await client.manyOrNone(
|
|
14668
|
+
`SELECT show_chunks($1::regclass, older_than => $2::timestamptz)::text AS "chunk"`,
|
|
14669
|
+
[tableExpr, cutoff.toISOString()]
|
|
14670
|
+
);
|
|
14671
|
+
let deleted = 0;
|
|
14672
|
+
for (const { chunk } of chunks ?? []) {
|
|
14673
|
+
const row = await client.one(`SELECT count(*)::int AS "n" FROM ${chunk}`);
|
|
14674
|
+
deleted += row.n;
|
|
14675
|
+
}
|
|
14676
|
+
await client.none(`SELECT drop_chunks($1::regclass, older_than => $2::timestamptz)`, [
|
|
14677
|
+
tableExpr,
|
|
14678
|
+
cutoff.toISOString()
|
|
14679
|
+
]);
|
|
14680
|
+
return { deleted, done: true };
|
|
14681
|
+
}
|
|
14040
14682
|
function applyScoreFilters(acc, filters) {
|
|
14041
14683
|
applyCommonFilters(acc, filters);
|
|
14042
14684
|
applySingleOrArrayFilter(acc, "scorerId", filters?.scorerId);
|
|
@@ -14783,7 +15425,7 @@ function wrapError(op, error$1, details) {
|
|
|
14783
15425
|
error$1
|
|
14784
15426
|
);
|
|
14785
15427
|
}
|
|
14786
|
-
var ObservabilityStoragePostgresVNext = class extends storage.ObservabilityStorage {
|
|
15428
|
+
var ObservabilityStoragePostgresVNext = class _ObservabilityStoragePostgresVNext extends storage.ObservabilityStorage {
|
|
14787
15429
|
#client;
|
|
14788
15430
|
#schema;
|
|
14789
15431
|
#partitioning;
|
|
@@ -14867,6 +15509,50 @@ var ObservabilityStoragePostgresVNext = class extends storage.ObservabilityStora
|
|
|
14867
15509
|
get partitionMode() {
|
|
14868
15510
|
return this.#partitionMode;
|
|
14869
15511
|
}
|
|
15512
|
+
// -------------------------------------------------------------------------
|
|
15513
|
+
// Retention
|
|
15514
|
+
// -------------------------------------------------------------------------
|
|
15515
|
+
/**
|
|
15516
|
+
* All five signal tables are insert-only growth tables. The anchor column is
|
|
15517
|
+
* each table's partition / chunk key, so age-based expiry drops whole day
|
|
15518
|
+
* partitions instead of deleting rows (see `./retention.ts`). `indexed: true`
|
|
15519
|
+
* reflects that expiry never scans — it acts on partition bounds.
|
|
15520
|
+
*/
|
|
15521
|
+
static retentionTables = {
|
|
15522
|
+
spans: { table: TABLE_SPAN_EVENTS, column: "endedAt", indexed: true },
|
|
15523
|
+
metrics: { table: TABLE_METRIC_EVENTS, column: "timestamp", indexed: true },
|
|
15524
|
+
logs: { table: TABLE_LOG_EVENTS, column: "timestamp", indexed: true },
|
|
15525
|
+
scores: { table: TABLE_SCORE_EVENTS, column: "timestamp", indexed: true },
|
|
15526
|
+
feedback: { table: TABLE_FEEDBACK_EVENTS, column: "timestamp", indexed: true }
|
|
15527
|
+
};
|
|
15528
|
+
/**
|
|
15529
|
+
* Expire signal events older than each table's `maxAge` by dropping whole
|
|
15530
|
+
* day partitions (native / pg_partman) or chunks (Timescale). Only
|
|
15531
|
+
* partitions wholly older than the cutoff are dropped, so the effective
|
|
15532
|
+
* granularity is one day. Each partition drop counts as one batch for
|
|
15533
|
+
* `maxBatches` / `maxRows` / abort-signal purposes; `deleted` reports the
|
|
15534
|
+
* row count of the dropped partitions.
|
|
15535
|
+
*/
|
|
15536
|
+
async prune(policies, options) {
|
|
15537
|
+
return this.#run("VNEXT_PRUNE", async () => {
|
|
15538
|
+
const mode = this.#partitionMode ??= await resolveMode(this.#client, this.#partitioning);
|
|
15539
|
+
const results = [];
|
|
15540
|
+
const now = Date.now();
|
|
15541
|
+
for (const [key, entry] of Object.entries(_ObservabilityStoragePostgresVNext.retentionTables)) {
|
|
15542
|
+
const policy = policies[key];
|
|
15543
|
+
if (!policy) continue;
|
|
15544
|
+
if (options?.signal?.aborted) {
|
|
15545
|
+
results.push({ domain: "observability", table: entry.table, deleted: 0, done: false });
|
|
15546
|
+
continue;
|
|
15547
|
+
}
|
|
15548
|
+
const cutoff = retentionCutoff(policy, now);
|
|
15549
|
+
const args = { client: this.#client, schema: this.#schema, table: entry.table, cutoff, options };
|
|
15550
|
+
const outcome = mode === "timescale" ? await pruneTimescaleTable(args) : await prunePartitionedTable(args);
|
|
15551
|
+
results.push({ domain: "observability", table: entry.table, deleted: outcome.deleted, done: outcome.done });
|
|
15552
|
+
}
|
|
15553
|
+
return results;
|
|
15554
|
+
});
|
|
15555
|
+
}
|
|
14870
15556
|
get observabilityStrategy() {
|
|
14871
15557
|
return { preferred: "insert-only", supported: ["insert-only"] };
|
|
14872
15558
|
}
|
|
@@ -15811,6 +16497,15 @@ var SchedulesPG = class _SchedulesPG extends storage.SchedulesStorage {
|
|
|
15811
16497
|
#indexes;
|
|
15812
16498
|
/** Tables managed by this domain */
|
|
15813
16499
|
static MANAGED_TABLES = [storage.TABLE_SCHEDULES, storage.TABLE_SCHEDULE_TRIGGERS];
|
|
16500
|
+
/**
|
|
16501
|
+
* The fire/run history (`schedule_triggers`, one row per fire) is the growth
|
|
16502
|
+
* table; schedule definitions are config and excluded. Anchored on
|
|
16503
|
+
* `actual_fire_at`, a bigint epoch-ms column (numeric comparison, not
|
|
16504
|
+
* timestamptz).
|
|
16505
|
+
*/
|
|
16506
|
+
static retentionTables = {
|
|
16507
|
+
triggers: { table: storage.TABLE_SCHEDULE_TRIGGERS, column: "actual_fire_at", indexed: true, anchorType: "epoch-ms" }
|
|
16508
|
+
};
|
|
15814
16509
|
constructor(config) {
|
|
15815
16510
|
super();
|
|
15816
16511
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -15832,6 +16527,43 @@ var SchedulesPG = class _SchedulesPG extends storage.SchedulesStorage {
|
|
|
15832
16527
|
await this.createDefaultIndexes();
|
|
15833
16528
|
await this.createCustomIndexes();
|
|
15834
16529
|
}
|
|
16530
|
+
/**
|
|
16531
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
16532
|
+
* anchor column so age-based `prune()` deletes stay fast. The default
|
|
16533
|
+
* composite index leads with `schedule_id`, so a bare `actual_fire_at` range
|
|
16534
|
+
* scan can't use it. Called from the prune path (not init) so only
|
|
16535
|
+
* deployments that configure retention pay the index's write/disk overhead.
|
|
16536
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
16537
|
+
* so its supporting index is not part of the default index set.
|
|
16538
|
+
*/
|
|
16539
|
+
async ensureRetentionIndexes(policies) {
|
|
16540
|
+
const prefix = this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
16541
|
+
for (const [key, entry] of Object.entries(_SchedulesPG.retentionTables)) {
|
|
16542
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
16543
|
+
try {
|
|
16544
|
+
await this.#db.ensureIndex({
|
|
16545
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
16546
|
+
tableName: entry.table,
|
|
16547
|
+
column: entry.column
|
|
16548
|
+
});
|
|
16549
|
+
} catch (error) {
|
|
16550
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
16551
|
+
}
|
|
16552
|
+
}
|
|
16553
|
+
}
|
|
16554
|
+
/**
|
|
16555
|
+
* Delete trigger (fire history) rows whose `actual_fire_at` is older than the
|
|
16556
|
+
* `triggers` policy's `maxAge`, batched. Schedule definitions are never pruned.
|
|
16557
|
+
*/
|
|
16558
|
+
async prune(policies, options) {
|
|
16559
|
+
await this.ensureRetentionIndexes(policies);
|
|
16560
|
+
const targets = resolveTargets({
|
|
16561
|
+
policies,
|
|
16562
|
+
descriptor: _SchedulesPG.retentionTables,
|
|
16563
|
+
order: ["triggers"]
|
|
16564
|
+
});
|
|
16565
|
+
return runPrune({ db: this.#db, domain: "schedules", targets, options });
|
|
16566
|
+
}
|
|
15835
16567
|
/**
|
|
15836
16568
|
* Returns default index definitions for the schedules domain.
|
|
15837
16569
|
* @param schemaPrefix - Prefix for index names (e.g. "my_schema_" or "")
|
|
@@ -16164,6 +16896,11 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16164
16896
|
tableName: storage.TABLE_SCORER_DEFINITIONS,
|
|
16165
16897
|
schema: storage.TABLE_SCHEMAS[storage.TABLE_SCORER_DEFINITIONS]
|
|
16166
16898
|
});
|
|
16899
|
+
await this.#db.alterTable({
|
|
16900
|
+
tableName: storage.TABLE_SCORER_DEFINITIONS,
|
|
16901
|
+
schema: storage.TABLE_SCHEMAS[storage.TABLE_SCORER_DEFINITIONS],
|
|
16902
|
+
ifNotExists: ["organizationId", "projectId"]
|
|
16903
|
+
});
|
|
16167
16904
|
await this.#db.createTable({
|
|
16168
16905
|
tableName: storage.TABLE_SCORER_DEFINITION_VERSIONS,
|
|
16169
16906
|
schema: storage.TABLE_SCHEMAS[storage.TABLE_SCORER_DEFINITION_VERSIONS]
|
|
@@ -16219,14 +16956,16 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16219
16956
|
const nowIso = now.toISOString();
|
|
16220
16957
|
await this.#db.client.none(
|
|
16221
16958
|
`INSERT INTO ${tableName} (
|
|
16222
|
-
id, status, "activeVersionId", "authorId", metadata,
|
|
16959
|
+
id, status, "activeVersionId", "authorId", "organizationId", "projectId", metadata,
|
|
16223
16960
|
"createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
|
|
16224
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
16961
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
|
|
16225
16962
|
[
|
|
16226
16963
|
scorerDefinition.id,
|
|
16227
16964
|
"draft",
|
|
16228
16965
|
null,
|
|
16229
16966
|
scorerDefinition.authorId ?? null,
|
|
16967
|
+
scorerDefinition.organizationId ?? null,
|
|
16968
|
+
scorerDefinition.projectId ?? null,
|
|
16230
16969
|
scorerDefinition.metadata ? JSON.stringify(scorerDefinition.metadata) : null,
|
|
16231
16970
|
nowIso,
|
|
16232
16971
|
nowIso,
|
|
@@ -16234,7 +16973,14 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16234
16973
|
nowIso
|
|
16235
16974
|
]
|
|
16236
16975
|
);
|
|
16237
|
-
const {
|
|
16976
|
+
const {
|
|
16977
|
+
id: _id,
|
|
16978
|
+
authorId: _authorId,
|
|
16979
|
+
organizationId: _organizationId,
|
|
16980
|
+
projectId: _projectId,
|
|
16981
|
+
metadata: _metadata,
|
|
16982
|
+
...snapshotConfig
|
|
16983
|
+
} = scorerDefinition;
|
|
16238
16984
|
const versionId = crypto.randomUUID();
|
|
16239
16985
|
await this.createVersion({
|
|
16240
16986
|
id: versionId,
|
|
@@ -16249,6 +16995,8 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16249
16995
|
status: "draft",
|
|
16250
16996
|
activeVersionId: void 0,
|
|
16251
16997
|
authorId: scorerDefinition.authorId,
|
|
16998
|
+
organizationId: scorerDefinition.organizationId,
|
|
16999
|
+
projectId: scorerDefinition.projectId,
|
|
16252
17000
|
metadata: scorerDefinition.metadata,
|
|
16253
17001
|
createdAt: now,
|
|
16254
17002
|
updatedAt: now
|
|
@@ -16362,7 +17110,16 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16362
17110
|
}
|
|
16363
17111
|
}
|
|
16364
17112
|
async list(args) {
|
|
16365
|
-
const {
|
|
17113
|
+
const {
|
|
17114
|
+
page = 0,
|
|
17115
|
+
perPage: perPageInput,
|
|
17116
|
+
orderBy,
|
|
17117
|
+
authorId,
|
|
17118
|
+
organizationId,
|
|
17119
|
+
projectId,
|
|
17120
|
+
metadata,
|
|
17121
|
+
status
|
|
17122
|
+
} = args || {};
|
|
16366
17123
|
const { field, direction } = this.parseOrderBy(orderBy);
|
|
16367
17124
|
if (page < 0) {
|
|
16368
17125
|
throw new error.MastraError(
|
|
@@ -16390,6 +17147,14 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16390
17147
|
conditions.push(`"authorId" = $${paramIdx++}`);
|
|
16391
17148
|
queryParams.push(authorId);
|
|
16392
17149
|
}
|
|
17150
|
+
if (organizationId !== void 0) {
|
|
17151
|
+
conditions.push(`"organizationId" = $${paramIdx++}`);
|
|
17152
|
+
queryParams.push(organizationId);
|
|
17153
|
+
}
|
|
17154
|
+
if (projectId !== void 0) {
|
|
17155
|
+
conditions.push(`"projectId" = $${paramIdx++}`);
|
|
17156
|
+
queryParams.push(projectId);
|
|
17157
|
+
}
|
|
16393
17158
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
16394
17159
|
conditions.push(`metadata @> $${paramIdx++}::jsonb`);
|
|
16395
17160
|
queryParams.push(JSON.stringify(metadata));
|
|
@@ -16716,6 +17481,8 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16716
17481
|
status: row.status,
|
|
16717
17482
|
activeVersionId: row.activeVersionId,
|
|
16718
17483
|
authorId: row.authorId,
|
|
17484
|
+
organizationId: row.organizationId,
|
|
17485
|
+
projectId: row.projectId,
|
|
16719
17486
|
metadata: parseJsonResilient(row.metadata),
|
|
16720
17487
|
createdAt: new Date(row.createdAtZ || row.createdAt),
|
|
16721
17488
|
updatedAt: new Date(row.updatedAtZ || row.updatedAt)
|
|
@@ -16740,6 +17507,18 @@ var ScorerDefinitionsPG = class _ScorerDefinitionsPG extends storage.ScorerDefin
|
|
|
16740
17507
|
};
|
|
16741
17508
|
}
|
|
16742
17509
|
};
|
|
17510
|
+
function applyTenancyFilters(conditions, queryParams, startIndex, filters) {
|
|
17511
|
+
let paramIndex = startIndex;
|
|
17512
|
+
if (filters?.organizationId !== void 0) {
|
|
17513
|
+
conditions.push(`"organizationId" = $${paramIndex++}`);
|
|
17514
|
+
queryParams.push(filters.organizationId);
|
|
17515
|
+
}
|
|
17516
|
+
if (filters?.projectId !== void 0) {
|
|
17517
|
+
conditions.push(`"projectId" = $${paramIndex++}`);
|
|
17518
|
+
queryParams.push(filters.projectId);
|
|
17519
|
+
}
|
|
17520
|
+
return paramIndex;
|
|
17521
|
+
}
|
|
16743
17522
|
function getSchemaName6(schema) {
|
|
16744
17523
|
return schema ? `"${schema}"` : '"public"';
|
|
16745
17524
|
}
|
|
@@ -16762,6 +17541,13 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
16762
17541
|
#indexes;
|
|
16763
17542
|
/** Tables managed by this domain */
|
|
16764
17543
|
static MANAGED_TABLES = [storage.TABLE_SCORERS];
|
|
17544
|
+
/**
|
|
17545
|
+
* Scorer results accumulate as evals run. Single table, anchored on the
|
|
17546
|
+
* timezone-aware `createdAtZ` mirror column (kept in sync by triggers).
|
|
17547
|
+
*/
|
|
17548
|
+
static retentionTables = {
|
|
17549
|
+
scorers: { table: storage.TABLE_SCORERS, column: "createdAtZ", indexed: true }
|
|
17550
|
+
};
|
|
16765
17551
|
constructor(config) {
|
|
16766
17552
|
super();
|
|
16767
17553
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -16775,11 +17561,35 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
16775
17561
|
await this.#db.alterTable({
|
|
16776
17562
|
tableName: storage.TABLE_SCORERS,
|
|
16777
17563
|
schema: storage.TABLE_SCHEMAS[storage.TABLE_SCORERS],
|
|
16778
|
-
ifNotExists: ["spanId", "requestContext"]
|
|
17564
|
+
ifNotExists: ["spanId", "requestContext", "organizationId", "projectId", "batchId", "datasetId", "datasetItemId"]
|
|
16779
17565
|
});
|
|
16780
17566
|
await this.createDefaultIndexes();
|
|
16781
17567
|
await this.createCustomIndexes();
|
|
16782
17568
|
}
|
|
17569
|
+
/**
|
|
17570
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
17571
|
+
* anchor column so age-based `prune()` deletes stay fast on large tables.
|
|
17572
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
17573
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
17574
|
+
* logged and pruning proceeds (correct, just slower).
|
|
17575
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
17576
|
+
* so its supporting index is not part of the default index set.
|
|
17577
|
+
*/
|
|
17578
|
+
async ensureRetentionIndexes(policies) {
|
|
17579
|
+
const prefix = this.#schema && this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
17580
|
+
for (const [key, entry] of Object.entries(_ScoresPG.retentionTables)) {
|
|
17581
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
17582
|
+
try {
|
|
17583
|
+
await this.#db.ensureIndex({
|
|
17584
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
17585
|
+
tableName: entry.table,
|
|
17586
|
+
column: entry.column
|
|
17587
|
+
});
|
|
17588
|
+
} catch (error) {
|
|
17589
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
17590
|
+
}
|
|
17591
|
+
}
|
|
17592
|
+
}
|
|
16783
17593
|
/**
|
|
16784
17594
|
* Returns default index definitions for the scores domain tables.
|
|
16785
17595
|
* @param schemaPrefix - Prefix for index names (e.g. "my_schema_" or "")
|
|
@@ -16854,6 +17664,16 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
16854
17664
|
async dangerouslyClearAll() {
|
|
16855
17665
|
await this.#db.clearTable({ tableName: storage.TABLE_SCORERS });
|
|
16856
17666
|
}
|
|
17667
|
+
/** Delete scorer results older than the `scorers` policy's `maxAge`, batched. */
|
|
17668
|
+
async prune(policies, options) {
|
|
17669
|
+
await this.ensureRetentionIndexes(policies);
|
|
17670
|
+
const targets = resolveTargets({
|
|
17671
|
+
policies,
|
|
17672
|
+
descriptor: _ScoresPG.retentionTables,
|
|
17673
|
+
order: ["scorers"]
|
|
17674
|
+
});
|
|
17675
|
+
return runPrune({ db: this.#db, domain: "scores", targets, options });
|
|
17676
|
+
}
|
|
16857
17677
|
async getScoreById({ id }) {
|
|
16858
17678
|
try {
|
|
16859
17679
|
const result = await this.#db.client.oneOrNone(
|
|
@@ -16877,7 +17697,8 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
16877
17697
|
pagination,
|
|
16878
17698
|
entityId,
|
|
16879
17699
|
entityType,
|
|
16880
|
-
source
|
|
17700
|
+
source,
|
|
17701
|
+
filters
|
|
16881
17702
|
}) {
|
|
16882
17703
|
try {
|
|
16883
17704
|
const conditions = [`"scorerId" = $1`];
|
|
@@ -16895,6 +17716,7 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
16895
17716
|
conditions.push(`"source" = $${paramIndex++}`);
|
|
16896
17717
|
queryParams.push(source);
|
|
16897
17718
|
}
|
|
17719
|
+
paramIndex = applyTenancyFilters(conditions, queryParams, paramIndex, filters);
|
|
16898
17720
|
const whereClause = conditions.join(" AND ");
|
|
16899
17721
|
const total = await this.#db.client.oneOrNone(
|
|
16900
17722
|
`SELECT COUNT(*) FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE ${whereClause}`,
|
|
@@ -17008,12 +17830,17 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
17008
17830
|
}
|
|
17009
17831
|
async listScoresByRunId({
|
|
17010
17832
|
runId,
|
|
17011
|
-
pagination
|
|
17833
|
+
pagination,
|
|
17834
|
+
filters
|
|
17012
17835
|
}) {
|
|
17013
17836
|
try {
|
|
17837
|
+
const conditions = [`"runId" = $1`];
|
|
17838
|
+
const queryParams = [runId];
|
|
17839
|
+
let paramIndex = applyTenancyFilters(conditions, queryParams, 2, filters);
|
|
17840
|
+
const whereClause = conditions.join(" AND ");
|
|
17014
17841
|
const total = await this.#db.client.oneOrNone(
|
|
17015
|
-
`SELECT COUNT(*) FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE
|
|
17016
|
-
|
|
17842
|
+
`SELECT COUNT(*) FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE ${whereClause}`,
|
|
17843
|
+
queryParams
|
|
17017
17844
|
);
|
|
17018
17845
|
const { page, perPage: perPageInput } = pagination;
|
|
17019
17846
|
const perPage = storage.normalizePerPage(perPageInput, 100);
|
|
@@ -17032,8 +17859,8 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
17032
17859
|
const limitValue = perPageInput === false ? Number(total?.count) : perPage;
|
|
17033
17860
|
const end = perPageInput === false ? Number(total?.count) : start + perPage;
|
|
17034
17861
|
const result = await this.#db.client.manyOrNone(
|
|
17035
|
-
`SELECT * FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE "
|
|
17036
|
-
[
|
|
17862
|
+
`SELECT * FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
17863
|
+
[...queryParams, limitValue, start]
|
|
17037
17864
|
);
|
|
17038
17865
|
return {
|
|
17039
17866
|
pagination: {
|
|
@@ -17058,12 +17885,17 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
17058
17885
|
async listScoresByEntityId({
|
|
17059
17886
|
entityId,
|
|
17060
17887
|
entityType,
|
|
17061
|
-
pagination
|
|
17888
|
+
pagination,
|
|
17889
|
+
filters
|
|
17062
17890
|
}) {
|
|
17063
17891
|
try {
|
|
17892
|
+
const conditions = [`"entityId" = $1`, `"entityType" = $2`];
|
|
17893
|
+
const queryParams = [entityId, entityType];
|
|
17894
|
+
let paramIndex = applyTenancyFilters(conditions, queryParams, 3, filters);
|
|
17895
|
+
const whereClause = conditions.join(" AND ");
|
|
17064
17896
|
const total = await this.#db.client.oneOrNone(
|
|
17065
|
-
`SELECT COUNT(*) FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE
|
|
17066
|
-
|
|
17897
|
+
`SELECT COUNT(*) FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE ${whereClause}`,
|
|
17898
|
+
queryParams
|
|
17067
17899
|
);
|
|
17068
17900
|
const { page, perPage: perPageInput } = pagination;
|
|
17069
17901
|
const perPage = storage.normalizePerPage(perPageInput, 100);
|
|
@@ -17082,8 +17914,8 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
17082
17914
|
const limitValue = perPageInput === false ? Number(total?.count) : perPage;
|
|
17083
17915
|
const end = perPageInput === false ? Number(total?.count) : start + perPage;
|
|
17084
17916
|
const result = await this.#db.client.manyOrNone(
|
|
17085
|
-
`SELECT * FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE
|
|
17086
|
-
[
|
|
17917
|
+
`SELECT * FROM ${getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) })} WHERE ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
17918
|
+
[...queryParams, limitValue, start]
|
|
17087
17919
|
);
|
|
17088
17920
|
return {
|
|
17089
17921
|
pagination: {
|
|
@@ -17108,13 +17940,18 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
17108
17940
|
async listScoresBySpan({
|
|
17109
17941
|
traceId,
|
|
17110
17942
|
spanId,
|
|
17111
|
-
pagination
|
|
17943
|
+
pagination,
|
|
17944
|
+
filters
|
|
17112
17945
|
}) {
|
|
17113
17946
|
try {
|
|
17114
17947
|
const tableName = getTableName6({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName6(this.#schema) });
|
|
17948
|
+
const conditions = [`"traceId" = $1`, `"spanId" = $2`];
|
|
17949
|
+
const queryParams = [traceId, spanId];
|
|
17950
|
+
let paramIndex = applyTenancyFilters(conditions, queryParams, 3, filters);
|
|
17951
|
+
const whereClause = conditions.join(" AND ");
|
|
17115
17952
|
const countSQLResult = await this.#db.client.oneOrNone(
|
|
17116
|
-
`SELECT COUNT(*) as count FROM ${tableName} WHERE
|
|
17117
|
-
|
|
17953
|
+
`SELECT COUNT(*) as count FROM ${tableName} WHERE ${whereClause}`,
|
|
17954
|
+
queryParams
|
|
17118
17955
|
);
|
|
17119
17956
|
const total = Number(countSQLResult?.count ?? 0);
|
|
17120
17957
|
const { page, perPage: perPageInput } = pagination;
|
|
@@ -17123,8 +17960,8 @@ var ScoresPG = class _ScoresPG extends storage.ScoresStorage {
|
|
|
17123
17960
|
const limitValue = perPageInput === false ? total : perPage;
|
|
17124
17961
|
const end = perPageInput === false ? total : start + perPage;
|
|
17125
17962
|
const result = await this.#db.client.manyOrNone(
|
|
17126
|
-
`SELECT * FROM ${tableName} WHERE
|
|
17127
|
-
[
|
|
17963
|
+
`SELECT * FROM ${tableName} WHERE ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
17964
|
+
[...queryParams, limitValue, start]
|
|
17128
17965
|
);
|
|
17129
17966
|
const hasMore = end < total;
|
|
17130
17967
|
const scores = result.map((row) => transformScoreRow(row)) ?? [];
|
|
@@ -18175,6 +19012,14 @@ var WorkflowsPG = class _WorkflowsPG extends storage.WorkflowsStorage {
|
|
|
18175
19012
|
#indexes;
|
|
18176
19013
|
/** Tables managed by this domain */
|
|
18177
19014
|
static MANAGED_TABLES = [storage.TABLE_WORKFLOW_SNAPSHOT];
|
|
19015
|
+
/**
|
|
19016
|
+
* Workflow run snapshots accumulate as runs execute. Anchored on the
|
|
19017
|
+
* timezone-aware `updatedAtZ` mirror column (last activity) so suspended or
|
|
19018
|
+
* long-running runs are not pruned by start age.
|
|
19019
|
+
*/
|
|
19020
|
+
static retentionTables = {
|
|
19021
|
+
workflowSnapshot: { table: storage.TABLE_WORKFLOW_SNAPSHOT, column: "updatedAtZ", indexed: true }
|
|
19022
|
+
};
|
|
18178
19023
|
constructor(config) {
|
|
18179
19024
|
super();
|
|
18180
19025
|
const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
|
|
@@ -18246,6 +19091,40 @@ var WorkflowsPG = class _WorkflowsPG extends storage.WorkflowsStorage {
|
|
|
18246
19091
|
await this.createDefaultIndexes();
|
|
18247
19092
|
await this.createCustomIndexes();
|
|
18248
19093
|
}
|
|
19094
|
+
/**
|
|
19095
|
+
* Lazily ensures a btree index exists on each configured policy's retention
|
|
19096
|
+
* anchor column so age-based `prune()` deletes stay fast on large tables.
|
|
19097
|
+
* Called from the prune path (not init) so only deployments that configure
|
|
19098
|
+
* retention pay the index's write/disk overhead. Best-effort: failures are
|
|
19099
|
+
* logged and pruning proceeds (correct, just slower).
|
|
19100
|
+
* Created even with `skipDefaultIndexes` — retention is an explicit opt-in,
|
|
19101
|
+
* so its supporting index is not part of the default index set.
|
|
19102
|
+
*/
|
|
19103
|
+
async ensureRetentionIndexes(policies) {
|
|
19104
|
+
const prefix = this.#schema && this.#schema !== "public" ? `${this.#schema}_` : "";
|
|
19105
|
+
for (const [key, entry] of Object.entries(_WorkflowsPG.retentionTables)) {
|
|
19106
|
+
if (!entry.indexed || !policies[key]) continue;
|
|
19107
|
+
try {
|
|
19108
|
+
await this.#db.ensureIndex({
|
|
19109
|
+
indexName: `${prefix}mastra_${key}_retention_idx`,
|
|
19110
|
+
tableName: entry.table,
|
|
19111
|
+
column: entry.column
|
|
19112
|
+
});
|
|
19113
|
+
} catch (error) {
|
|
19114
|
+
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
19115
|
+
}
|
|
19116
|
+
}
|
|
19117
|
+
}
|
|
19118
|
+
/** Delete workflow run snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
|
|
19119
|
+
async prune(policies, options) {
|
|
19120
|
+
await this.ensureRetentionIndexes(policies);
|
|
19121
|
+
const targets = resolveTargets({
|
|
19122
|
+
policies,
|
|
19123
|
+
descriptor: _WorkflowsPG.retentionTables,
|
|
19124
|
+
order: ["workflowSnapshot"]
|
|
19125
|
+
});
|
|
19126
|
+
return runPrune({ db: this.#db, domain: "workflows", targets, options });
|
|
19127
|
+
}
|
|
18249
19128
|
/**
|
|
18250
19129
|
* Creates custom user-defined indexes for this domain's tables.
|
|
18251
19130
|
*/
|
|
@@ -19359,7 +20238,7 @@ var PostgresStore = class extends storage.MastraCompositeStore {
|
|
|
19359
20238
|
constructor(config) {
|
|
19360
20239
|
try {
|
|
19361
20240
|
validateConfig("PostgresStore", config);
|
|
19362
|
-
super({ id: config.id, name: "PostgresStore", disableInit: config.disableInit });
|
|
20241
|
+
super({ id: config.id, name: "PostgresStore", disableInit: config.disableInit, retention: config.retention });
|
|
19363
20242
|
this.schema = utils.parseSqlIdentifier(config.schemaName || "public", "schema name");
|
|
19364
20243
|
if (isPoolConfig(config)) {
|
|
19365
20244
|
this.#pool = config.pool;
|