@mastra/pg 1.30.0 → 1.31.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/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +2 -2
- package/dist/docs/references/docs-memory-working-memory.md +16 -0
- package/dist/docs/references/reference-memory-memory-class.md +2 -2
- package/dist/docs/references/reference-processors-message-history-processor.md +3 -2
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +2 -3
- package/dist/docs/references/reference-processors-working-memory-processor.md +3 -2
- package/dist/docs/references/reference-rag-metadata-filters.md +1 -1
- package/dist/docs/references/reference-rag-vector-databases.md +1 -1
- package/dist/index.cjs +147 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +147 -25
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +9 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/schema-snapshot.d.ts +7 -1
- package/dist/storage/db/schema-snapshot.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +5 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2483,7 +2483,7 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2483
2483
|
AND c.relkind IN ('r', 'p')
|
|
2484
2484
|
AND a.attnum > 0
|
|
2485
2485
|
AND NOT a.attisdropped`, [schema]),
|
|
2486
|
-
client.manyOrNone(`SELECT c.relname AS indexname, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary
|
|
2486
|
+
client.manyOrNone(`SELECT c.relname AS indexname, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary, i.indisvalid AS is_valid
|
|
2487
2487
|
FROM pg_catalog.pg_index i
|
|
2488
2488
|
JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
|
|
2489
2489
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
@@ -2508,8 +2508,10 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2508
2508
|
const indexes = /* @__PURE__ */ new Set();
|
|
2509
2509
|
const replicaIdentityIndexes = /* @__PURE__ */ new Set();
|
|
2510
2510
|
const primaryKeyIndexes = /* @__PURE__ */ new Set();
|
|
2511
|
+
const invalidIndexes = /* @__PURE__ */ new Set();
|
|
2511
2512
|
for (const row of indexRows) {
|
|
2512
|
-
indexes.add(row.indexname);
|
|
2513
|
+
if (row.is_valid) indexes.add(row.indexname);
|
|
2514
|
+
else invalidIndexes.add(row.indexname);
|
|
2513
2515
|
if (row.is_replica_identity) replicaIdentityIndexes.add(row.indexname);
|
|
2514
2516
|
if (row.is_primary) primaryKeyIndexes.add(row.indexname.toLowerCase());
|
|
2515
2517
|
}
|
|
@@ -2520,7 +2522,8 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2520
2522
|
columnTypes,
|
|
2521
2523
|
indexes,
|
|
2522
2524
|
replicaIdentityIndexes,
|
|
2523
|
-
primaryKeyIndexes
|
|
2525
|
+
primaryKeyIndexes,
|
|
2526
|
+
invalidIndexes
|
|
2524
2527
|
};
|
|
2525
2528
|
}
|
|
2526
2529
|
//#endregion
|
|
@@ -3668,11 +3671,8 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3668
3671
|
schemaName: getSchemaName$6(this.schemaName)
|
|
3669
3672
|
});
|
|
3670
3673
|
const snapshot = this.schemaSnapshot;
|
|
3671
|
-
if (snapshot)
|
|
3672
|
-
|
|
3673
|
-
} else if (await this.client.oneOrNone(`SELECT 1 FROM pg_indexes
|
|
3674
|
-
WHERE indexname = $1
|
|
3675
|
-
AND schemaname = $2`, [name, schemaName])) return;
|
|
3674
|
+
if (snapshot?.indexes.has(name)) return;
|
|
3675
|
+
if (!await this.prepareIndexBuild(name, schemaName)) return;
|
|
3676
3676
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
3677
3677
|
const concurrentStr = concurrent ? "CONCURRENTLY " : "";
|
|
3678
3678
|
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
@@ -3691,16 +3691,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3691
3691
|
let withStr = "";
|
|
3692
3692
|
if (storage && Object.keys(storage).length > 0) withStr = ` WITH (${Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ")})`;
|
|
3693
3693
|
const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${`"${parseSqlIdentifier(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
|
|
3694
|
-
await this.
|
|
3694
|
+
await this.timedIndexBuild(name, sql);
|
|
3695
3695
|
snapshot?.indexes.add(name);
|
|
3696
3696
|
} catch (error) {
|
|
3697
|
-
if (error instanceof Error && error.message.includes("CONCURRENTLY")) {
|
|
3698
|
-
const retryOptions = {
|
|
3699
|
-
...options,
|
|
3700
|
-
concurrent: false
|
|
3701
|
-
};
|
|
3702
|
-
return this.createIndex(retryOptions);
|
|
3703
|
-
}
|
|
3704
3697
|
throw new MastraError({
|
|
3705
3698
|
id: createStorageErrorId("PG", "INDEX_CREATE", "FAILED"),
|
|
3706
3699
|
domain: ErrorDomain.STORAGE,
|
|
@@ -3724,9 +3717,43 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3724
3717
|
async createIndexFromStatement(indexName, sql) {
|
|
3725
3718
|
const snapshot = this.schemaSnapshot;
|
|
3726
3719
|
if (snapshot?.indexes.has(indexName)) return;
|
|
3727
|
-
await this.
|
|
3720
|
+
if (!await this.prepareIndexBuild(indexName, this.schemaName || "public")) return;
|
|
3721
|
+
await this.timedIndexBuild(indexName, sql);
|
|
3728
3722
|
snapshot?.indexes.add(indexName);
|
|
3729
3723
|
}
|
|
3724
|
+
/**
|
|
3725
|
+
* Decides whether `indexName` still needs building. Returns false when a valid
|
|
3726
|
+
* index already exists, or when another session is currently building it.
|
|
3727
|
+
* An invalid index left by an interrupted `CREATE INDEX CONCURRENTLY` is
|
|
3728
|
+
* dropped (concurrently) so the caller rebuilds it — `IF NOT EXISTS` would
|
|
3729
|
+
* otherwise keep the broken index forever.
|
|
3730
|
+
*/
|
|
3731
|
+
async prepareIndexBuild(indexName, schemaName) {
|
|
3732
|
+
const snapshot = this.schemaSnapshot;
|
|
3733
|
+
if (snapshot && !snapshot.invalidIndexes.has(indexName)) return true;
|
|
3734
|
+
const existing = await this.client.oneOrNone(`SELECT i.indisvalid AS is_valid,
|
|
3735
|
+
EXISTS (SELECT 1 FROM pg_stat_progress_create_index p WHERE p.index_relid = c.oid) AS building
|
|
3736
|
+
FROM pg_catalog.pg_index i
|
|
3737
|
+
JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
|
|
3738
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
3739
|
+
WHERE c.relname = $1 AND n.nspname = $2`, [indexName, schemaName]);
|
|
3740
|
+
if (!existing) return true;
|
|
3741
|
+
if (existing.is_valid) return false;
|
|
3742
|
+
if (existing.building) {
|
|
3743
|
+
this.logger.warn(`Index ${indexName} is being built by another session; skipping`);
|
|
3744
|
+
return false;
|
|
3745
|
+
}
|
|
3746
|
+
this.logger.warn(`Dropping invalid index ${indexName} left by an interrupted build; rebuilding it`);
|
|
3747
|
+
const quotedIndexName = `"${parseSqlIdentifier(indexName, "index name")}"`;
|
|
3748
|
+
await this.client.none(`DROP INDEX CONCURRENTLY IF EXISTS ${getSchemaName$6(this.schemaName)}.${quotedIndexName}`);
|
|
3749
|
+
return true;
|
|
3750
|
+
}
|
|
3751
|
+
async timedIndexBuild(indexName, sql) {
|
|
3752
|
+
const startedAt = Date.now();
|
|
3753
|
+
this.logger.info(`Creating index ${indexName}`);
|
|
3754
|
+
await this.client.none(sql);
|
|
3755
|
+
this.logger.info(`Created index ${indexName} in ${Date.now() - startedAt}ms`);
|
|
3756
|
+
}
|
|
3730
3757
|
async dropIndex(indexName) {
|
|
3731
3758
|
try {
|
|
3732
3759
|
const schemaName = this.schemaName || "public";
|
|
@@ -10471,6 +10498,7 @@ function dedupeMessagesForSave(messages) {
|
|
|
10471
10498
|
var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
10472
10499
|
supportsPartialThreadUpdate = true;
|
|
10473
10500
|
supportsObservationalMemory = true;
|
|
10501
|
+
supportsAtomicWorkingMemoryMerge = true;
|
|
10474
10502
|
supportsObservationalMemoryHistorySearch = true;
|
|
10475
10503
|
/**
|
|
10476
10504
|
* Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
|
|
@@ -11817,6 +11845,57 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
11817
11845
|
metadata: metadataJson ? JSON.parse(metadataJson) : resource.metadata
|
|
11818
11846
|
};
|
|
11819
11847
|
}
|
|
11848
|
+
async mergeResourceWorkingMemory({ resourceId, merge }) {
|
|
11849
|
+
const tableName = getTableName$3({
|
|
11850
|
+
indexName: TABLE_RESOURCES,
|
|
11851
|
+
schemaName: getSchemaName$3(this.#schema)
|
|
11852
|
+
});
|
|
11853
|
+
const callbackError = {};
|
|
11854
|
+
try {
|
|
11855
|
+
return await this.#db.client.tx(async (t) => {
|
|
11856
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11857
|
+
await t.none(`INSERT INTO ${tableName} (id, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ")
|
|
11858
|
+
VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO NOTHING`, [
|
|
11859
|
+
resourceId,
|
|
11860
|
+
toPgJson({}),
|
|
11861
|
+
now,
|
|
11862
|
+
now,
|
|
11863
|
+
now,
|
|
11864
|
+
now
|
|
11865
|
+
]);
|
|
11866
|
+
await t.one(`SELECT id FROM ${tableName} WHERE id = $1 FOR UPDATE`, [resourceId]);
|
|
11867
|
+
const existing = await this.#getResourceById(t, resourceId);
|
|
11868
|
+
let workingMemory;
|
|
11869
|
+
try {
|
|
11870
|
+
workingMemory = merge(existing.workingMemory ?? void 0);
|
|
11871
|
+
} catch (error) {
|
|
11872
|
+
callbackError.error = error;
|
|
11873
|
+
throw error;
|
|
11874
|
+
}
|
|
11875
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
11876
|
+
const updatedAtStr = updatedAt.toISOString();
|
|
11877
|
+
await t.none(`UPDATE ${tableName} SET "workingMemory" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`, [
|
|
11878
|
+
workingMemory,
|
|
11879
|
+
updatedAtStr,
|
|
11880
|
+
updatedAtStr,
|
|
11881
|
+
resourceId
|
|
11882
|
+
]);
|
|
11883
|
+
return {
|
|
11884
|
+
...existing,
|
|
11885
|
+
workingMemory,
|
|
11886
|
+
updatedAt
|
|
11887
|
+
};
|
|
11888
|
+
});
|
|
11889
|
+
} catch (error) {
|
|
11890
|
+
if (error === callbackError.error || error instanceof MastraError) throw error;
|
|
11891
|
+
throw new MastraError({
|
|
11892
|
+
id: createStorageErrorId("PG", "MERGE_RESOURCE_WORKING_MEMORY", "FAILED"),
|
|
11893
|
+
domain: ErrorDomain.STORAGE,
|
|
11894
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
11895
|
+
details: { resourceId }
|
|
11896
|
+
}, error);
|
|
11897
|
+
}
|
|
11898
|
+
}
|
|
11820
11899
|
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
11821
11900
|
const existingResource = await this.#getResourceById(this.#db.client, resourceId);
|
|
11822
11901
|
if (!existingResource) {
|
|
@@ -17516,6 +17595,35 @@ function isMetadataField(field) {
|
|
|
17516
17595
|
function placeholders(values, offset) {
|
|
17517
17596
|
return values.map((_, index) => `$${offset + index}`).join(", ");
|
|
17518
17597
|
}
|
|
17598
|
+
/**
|
|
17599
|
+
* Unicode letters, marks, and digits as PostgreSQL bracket ranges, so `[^<ranges>]` splits words
|
|
17600
|
+
* like the planner's `[^\p{L}\p{M}\p{N}]`. PostgreSQL regexes have no `\p{…}` classes, and
|
|
17601
|
+
* `[[:alnum:]]` depends on the locale: it leaves out digits such as `² ½ ₂ ①`, and glibc files
|
|
17602
|
+
* some marks, such as the Devanagari virama, under punct. Derived once from the runtime's own
|
|
17603
|
+
* Unicode tables. A run may also absorb unassigned code points, which are never stored.
|
|
17604
|
+
* Private-use code points are not absorbed: the other stores treat them as separators.
|
|
17605
|
+
*/
|
|
17606
|
+
let pgWordRanges;
|
|
17607
|
+
function getPgWordRanges() {
|
|
17608
|
+
if (pgWordRanges !== void 0) return pgWordRanges;
|
|
17609
|
+
const escape = (cp) => cp > 65535 ? `\\U${cp.toString(16).padStart(8, "0")}` : `\\u${cp.toString(16).padStart(4, "0")}`;
|
|
17610
|
+
const chunks = [];
|
|
17611
|
+
for (let start = 0; start <= 1114111; start += 8192) {
|
|
17612
|
+
const codePoints = [];
|
|
17613
|
+
for (let cp = start; cp < start + 8192 && cp <= 1114111; cp++) if (cp < 55296 || cp > 57343) codePoints.push(cp);
|
|
17614
|
+
chunks.push(String.fromCodePoint(...codePoints));
|
|
17615
|
+
}
|
|
17616
|
+
const text = chunks.join("");
|
|
17617
|
+
const ranges = [];
|
|
17618
|
+
for (const match of text.matchAll(/[\p{L}\p{M}\p{N}\p{Cn}]+/gu)) {
|
|
17619
|
+
const first = match[0].codePointAt(0);
|
|
17620
|
+
const lastUnit = match.index + match[0].length - 1;
|
|
17621
|
+
const last = text.codePointAt(text.charCodeAt(lastUnit) >= 56320 && text.charCodeAt(lastUnit) <= 57343 ? lastUnit - 1 : lastUnit);
|
|
17622
|
+
ranges.push(first === last ? escape(first) : `${escape(first)}-${escape(last)}`);
|
|
17623
|
+
}
|
|
17624
|
+
pgWordRanges = ranges.join("");
|
|
17625
|
+
return pgWordRanges;
|
|
17626
|
+
}
|
|
17519
17627
|
function compileScalarPredicate(predicate, registry, parameterOffset, allowMetadata = false) {
|
|
17520
17628
|
if (predicate.type === "boolean") {
|
|
17521
17629
|
const values = [];
|
|
@@ -17559,6 +17667,13 @@ function compileScalarPredicate(predicate, registry, parameterOffset, allowMetad
|
|
|
17559
17667
|
values: fieldValues
|
|
17560
17668
|
};
|
|
17561
17669
|
}
|
|
17670
|
+
if (predicate.type === "text") {
|
|
17671
|
+
const found = `strpos(${`' ' || replace(lower(regexp_replace(normalize(${field}), '[^${getPgWordRanges()}]+', ' ', 'g')), 'ς', 'σ') || ' '`}, $${parameterOffset}) > 0`;
|
|
17672
|
+
return {
|
|
17673
|
+
sql: `COALESCE(${predicate.operator === "matches" ? found : `NOT (${found})`}, false)`,
|
|
17674
|
+
values: [...fieldValues, ` ${predicate.value} `]
|
|
17675
|
+
};
|
|
17676
|
+
}
|
|
17562
17677
|
if (predicate.type === "membership") {
|
|
17563
17678
|
const list = placeholders(predicate.values, parameterOffset);
|
|
17564
17679
|
if (predicate.operator === "in") return {
|
|
@@ -23148,12 +23263,17 @@ function workflowSnapshotStatusIndexName(schemaName) {
|
|
|
23148
23263
|
/**
|
|
23149
23264
|
* Expression index on `(workflow_name, snapshot->>'status', "createdAt" DESC)` so
|
|
23150
23265
|
* listWorkflowRuns() status filters can use an index instead of scanning every snapshot.
|
|
23266
|
+
*
|
|
23267
|
+
* `concurrently` is set by init, which may build the index on a large existing table while
|
|
23268
|
+
* other processes keep writing to it. The exported DDL stays plain because migration
|
|
23269
|
+
* tooling often wraps statements in a transaction, where CONCURRENTLY is rejected.
|
|
23151
23270
|
*/
|
|
23152
|
-
function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
|
|
23153
|
-
|
|
23271
|
+
function workflowSnapshotStatusIndexSQL(indexName, schemaName, concurrently = false) {
|
|
23272
|
+
const tableName = getTableName({
|
|
23154
23273
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
23155
23274
|
schemaName: getSchemaName(schemaName)
|
|
23156
|
-
})
|
|
23275
|
+
});
|
|
23276
|
+
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
|
|
23157
23277
|
}
|
|
23158
23278
|
/** Base name (before any schema prefix) of the expression index backing the threadId filter. */
|
|
23159
23279
|
const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
|
|
@@ -23190,12 +23310,14 @@ const WORKFLOW_SNAPSHOT_THREAD_ID_EXPR = `COALESCE(jsonb_path_query_first(snapsh
|
|
|
23190
23310
|
/**
|
|
23191
23311
|
* Expression index on the snapshot-embedded thread id so listWorkflowRuns() threadId filters
|
|
23192
23312
|
* (Agent.listSuspendedRuns) can use an index instead of detoasting every snapshot.
|
|
23313
|
+
* See {@link workflowSnapshotStatusIndexSQL} for `concurrently`.
|
|
23193
23314
|
*/
|
|
23194
|
-
function workflowSnapshotThreadIdIndexSQL(indexName, schemaName) {
|
|
23195
|
-
|
|
23315
|
+
function workflowSnapshotThreadIdIndexSQL(indexName, schemaName, concurrently = false) {
|
|
23316
|
+
const tableName = getTableName({
|
|
23196
23317
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
23197
23318
|
schemaName: getSchemaName(schemaName)
|
|
23198
|
-
})
|
|
23319
|
+
});
|
|
23320
|
+
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
|
|
23199
23321
|
}
|
|
23200
23322
|
var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
23201
23323
|
#db;
|
|
@@ -23292,13 +23414,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
23292
23414
|
if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
|
|
23293
23415
|
const indexName = workflowSnapshotStatusIndexName(this.#schema);
|
|
23294
23416
|
try {
|
|
23295
|
-
await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema));
|
|
23417
|
+
await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema, true));
|
|
23296
23418
|
} catch (error) {
|
|
23297
23419
|
this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
|
|
23298
23420
|
}
|
|
23299
23421
|
const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
|
|
23300
23422
|
try {
|
|
23301
|
-
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema));
|
|
23423
|
+
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema, true));
|
|
23302
23424
|
} catch (error) {
|
|
23303
23425
|
this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
|
|
23304
23426
|
}
|