@mastra/pg 1.16.0 → 1.17.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 +43 -6
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-working-memory.md +1 -1
- package/dist/docs/references/reference-storage-retention.md +1 -1
- package/dist/docs/references/reference-tools-vector-query-tool.md +22 -0
- package/dist/index.cjs +468 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +470 -6
- package/dist/index.js.map +1 -1
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +1 -1
- package/dist/storage/factory-storage.d.ts +45 -0
- package/dist/storage/factory-storage.d.ts.map +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 1.17.0-alpha.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added PgFactoryStorage for persisting Mastra agent state and lifecycle-managed application domains through one PostgreSQL pool. ([#19681](https://github.com/mastra-ai/mastra/pull/19681))
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { PgFactoryStorage } from '@mastra/pg';
|
|
11
|
+
|
|
12
|
+
const storage = new PgFactoryStorage({ connectionString: process.env.DATABASE_URL! });
|
|
13
|
+
await storage.init();
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [[`3b77e77`](https://github.com/mastra-ai/mastra/commit/3b77e7704936522e4769d29de1b5ea6901f302bd), [`6b1bf3b`](https://github.com/mastra-ai/mastra/commit/6b1bf3b9494bd51aa8f654c68c9355d6046fa2a1), [`72e437c`](https://github.com/mastra-ai/mastra/commit/72e437c515942c80b9def5b026e0bdee61b469d9)]:
|
|
19
|
+
- @mastra/core@1.52.0-alpha.8
|
|
20
|
+
|
|
21
|
+
## 1.16.1-alpha.0
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Fixed an issue where the process could crash when Postgres drops an idle database connection (e.g., due to a backend restart, failover, or network failure). ([#19469](https://github.com/mastra-ai/mastra/pull/19469))
|
|
26
|
+
|
|
27
|
+
Previously, a dropped idle connection caused an uncaught exception that terminated the process. Now the connection drop is caught and logged as a warning, and the store automatically reconnects on the next database operation.
|
|
28
|
+
|
|
29
|
+
User-provided connection pools are unaffected and keep their existing error handling.
|
|
30
|
+
|
|
31
|
+
- Added stable metrics and logs capability reporting for observability storage. The system packages response now includes `observabilityStorageCapabilities` with `metrics` and `logs` flags, enabling capability-based detection that is resilient to bundler-generated constructor name changes. ([#19305](https://github.com/mastra-ai/mastra/pull/19305))
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const packages = await client.getSystemPackages();
|
|
35
|
+
console.log(packages.observabilityStorageCapabilities?.metrics); // true
|
|
36
|
+
console.log(packages.observabilityStorageCapabilities?.logs); // true
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Studio now uses the capability response instead of relying on constructor names, with a fallback for older servers.
|
|
40
|
+
|
|
41
|
+
- Updated dependencies [[`1426af2`](https://github.com/mastra-ai/mastra/commit/1426af24975879c000d13ac75673f630fcc970c1), [`975295d`](https://github.com/mastra-ai/mastra/commit/975295d418552f0d46a59edfef4c3ee555f9930a), [`85e4fb5`](https://github.com/mastra-ai/mastra/commit/85e4fb50087a81c74df3a762f53b56373db0b912), [`ef03c0c`](https://github.com/mastra-ai/mastra/commit/ef03c0cfc62367a458e4cc56462e2148b35681c5), [`4fb4d88`](https://github.com/mastra-ai/mastra/commit/4fb4d881bc107acee13890ad4d78661016c510ed), [`4eba27a`](https://github.com/mastra-ai/mastra/commit/4eba27adcf60f991df0e62f94b3e75b4e67f3b4b), [`c701be3`](https://github.com/mastra-ai/mastra/commit/c701be32d7d9aa94a66da8c6cc38dcac6856f464)]:
|
|
42
|
+
- @mastra/core@1.52.0-alpha.3
|
|
43
|
+
|
|
3
44
|
## 1.16.0
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
|
@@ -1057,9 +1098,7 @@
|
|
|
1057
1098
|
**Example**
|
|
1058
1099
|
|
|
1059
1100
|
```ts
|
|
1060
|
-
const storage = new LibSQLStore({
|
|
1061
|
-
/* config */
|
|
1062
|
-
});
|
|
1101
|
+
const storage = new LibSQLStore({/* config */});
|
|
1063
1102
|
const favorites = await storage.getStore('favorites');
|
|
1064
1103
|
|
|
1065
1104
|
await favorites?.favorite({
|
|
@@ -1087,9 +1126,7 @@
|
|
|
1087
1126
|
**Example**
|
|
1088
1127
|
|
|
1089
1128
|
```ts
|
|
1090
|
-
const storage = new LibSQLStore({
|
|
1091
|
-
/* config */
|
|
1092
|
-
});
|
|
1129
|
+
const storage = new LibSQLStore({/* config */});
|
|
1093
1130
|
const favorites = await storage.getStore('favorites');
|
|
1094
1131
|
|
|
1095
1132
|
await favorites?.favorite({
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -403,7 +403,7 @@ By default, working memory reaches the model as part of the system message. You
|
|
|
403
403
|
|
|
404
404
|
```typescript
|
|
405
405
|
const memory = new Memory({
|
|
406
|
-
storage: new LibSQLStore({ url: 'file:./mastra.db' }),
|
|
406
|
+
storage: new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
|
|
407
407
|
options: {
|
|
408
408
|
workingMemory: {
|
|
409
409
|
enabled: true,
|
|
@@ -228,7 +228,7 @@ const storage = new MongoDBStore({
|
|
|
228
228
|
})
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
-
> **Tip:** TTL indexes delete documents shortly after they expire (background thread runs every \~60 seconds), but the exact timing
|
|
231
|
+
> **Tip:** TTL indexes delete documents shortly after they expire (background thread runs every \~60 seconds), but the exact timing isn't guaranteed. For precise, immediate cleanup, use `prune()` instead.
|
|
232
232
|
|
|
233
233
|
## Reclaiming disk
|
|
234
234
|
|
|
@@ -269,6 +269,28 @@ const chromaQueryTool = createVectorQueryTool({
|
|
|
269
269
|
- **whereDocument**: Filter by document content
|
|
270
270
|
- **Use Cases**: Advanced filtering, content-based search
|
|
271
271
|
|
|
272
|
+
**Turbopuffer**:
|
|
273
|
+
|
|
274
|
+
### Turbopuffer Configuration
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const turbopufferQueryTool = createVectorQueryTool({
|
|
278
|
+
vectorStoreName: 'turbopuffer',
|
|
279
|
+
indexName: 'docs',
|
|
280
|
+
model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
|
|
281
|
+
databaseConfig: {
|
|
282
|
+
turbopuffer: {
|
|
283
|
+
consistency: 'eventual', // Lower latency, recently written data may not be visible yet
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Turbopuffer Features:**
|
|
290
|
+
|
|
291
|
+
- **consistency**: Choose between `strong` (default, read-your-writes) and `eventual` (lower latency)
|
|
292
|
+
- **Use Cases**: Latency-sensitive queries where slightly stale data is acceptable
|
|
293
|
+
|
|
272
294
|
**Multiple Configs**:
|
|
273
295
|
|
|
274
296
|
### Multiple Database Configurations
|
package/dist/index.cjs
CHANGED
|
@@ -670,6 +670,14 @@ var PgVector = class extends vector.MastraVector {
|
|
|
670
670
|
throw new Error("PgVector: invalid configuration provided");
|
|
671
671
|
}
|
|
672
672
|
this.pool = new pg__namespace.Pool(poolConfig);
|
|
673
|
+
this.pool.on("error", (err) => {
|
|
674
|
+
this.logger?.warn?.(
|
|
675
|
+
"PgVector: idle pool client error (pool discards the client and reconnects on next checkout)",
|
|
676
|
+
{
|
|
677
|
+
err: err instanceof Error ? err.message : err
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
});
|
|
673
681
|
this.cacheWarmupPromise = (async () => {
|
|
674
682
|
try {
|
|
675
683
|
const existingIndexes = await this.listIndexes();
|
|
@@ -2372,6 +2380,11 @@ function resolvePgConfig(config) {
|
|
|
2372
2380
|
ssl: config.ssl
|
|
2373
2381
|
});
|
|
2374
2382
|
}
|
|
2383
|
+
pool.on("error", (err) => {
|
|
2384
|
+
console.warn(
|
|
2385
|
+
`resolvePgConfig: idle pool client error (pool discards the client and reconnects on next checkout): ${err instanceof Error ? err.message : String(err)}`
|
|
2386
|
+
);
|
|
2387
|
+
});
|
|
2375
2388
|
return {
|
|
2376
2389
|
client: new PoolAdapter(pool),
|
|
2377
2390
|
schemaName: config.schemaName,
|
|
@@ -15577,8 +15590,8 @@ var ObservabilityStoragePostgresVNext = class _ObservabilityStoragePostgresVNext
|
|
|
15577
15590
|
return { preferred: "insert-only", supported: ["insert-only"] };
|
|
15578
15591
|
}
|
|
15579
15592
|
getFeatures() {
|
|
15580
|
-
if (!deltaPollingFeatureEnabled()) return
|
|
15581
|
-
return ["delta-polling"];
|
|
15593
|
+
if (!deltaPollingFeatureEnabled()) return ["metrics", "logs"];
|
|
15594
|
+
return ["metrics", "logs", "delta-polling"];
|
|
15582
15595
|
}
|
|
15583
15596
|
async #run(op, fn, details) {
|
|
15584
15597
|
try {
|
|
@@ -20184,6 +20197,435 @@ var WorkspacesPG = class _WorkspacesPG extends storage.WorkspacesStorage {
|
|
|
20184
20197
|
};
|
|
20185
20198
|
}
|
|
20186
20199
|
};
|
|
20200
|
+
var IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
20201
|
+
var PG_UNIQUE_VIOLATION = "23505";
|
|
20202
|
+
var COLUMN_DDL = {
|
|
20203
|
+
text: "TEXT",
|
|
20204
|
+
bigint: "BIGINT",
|
|
20205
|
+
integer: "INTEGER",
|
|
20206
|
+
boolean: "BOOLEAN",
|
|
20207
|
+
json: "JSONB",
|
|
20208
|
+
timestamp: "TIMESTAMPTZ",
|
|
20209
|
+
"uuid-pk": "UUID"
|
|
20210
|
+
};
|
|
20211
|
+
function assertIdentifier(kind, name) {
|
|
20212
|
+
if (!IDENTIFIER_RE.test(name)) {
|
|
20213
|
+
throw new Error(`PgFactoryStorage: invalid ${kind} identifier '${name}'`);
|
|
20214
|
+
}
|
|
20215
|
+
}
|
|
20216
|
+
function isUniqueViolation(error) {
|
|
20217
|
+
return typeof error === "object" && error !== null && error.code === PG_UNIQUE_VIOLATION;
|
|
20218
|
+
}
|
|
20219
|
+
function primaryKeyOf(schema) {
|
|
20220
|
+
const pks = Object.entries(schema.columns).filter(([, spec]) => spec.type === "uuid-pk" || spec.primaryKey);
|
|
20221
|
+
if (pks.length !== 1) {
|
|
20222
|
+
throw new Error(
|
|
20223
|
+
`PgFactoryStorage: collection '${schema.name}' must declare exactly one primary key (found ${pks.length})`
|
|
20224
|
+
);
|
|
20225
|
+
}
|
|
20226
|
+
return pks[0][0];
|
|
20227
|
+
}
|
|
20228
|
+
function assertUpsertConflict(schema, conflictKeys, row) {
|
|
20229
|
+
const keys = new Set(conflictKeys);
|
|
20230
|
+
if (keys.size !== conflictKeys.length || conflictKeys.some((key) => row[key] === void 0)) {
|
|
20231
|
+
throw new Error(
|
|
20232
|
+
`PgFactoryStorage: upsert conflict keys for '${schema.name}' must be unique and present in the row`
|
|
20233
|
+
);
|
|
20234
|
+
}
|
|
20235
|
+
const primaryKey = primaryKeyOf(schema);
|
|
20236
|
+
const candidates = [{ columns: [primaryKey] }, ...schema.uniqueIndexes ?? []];
|
|
20237
|
+
const matches = candidates.some((candidate) => {
|
|
20238
|
+
if (candidate.columns.length !== keys.size || candidate.columns.some((column) => !keys.has(column))) return false;
|
|
20239
|
+
if ("whereNotNull" in candidate && candidate.whereNotNull && row[candidate.whereNotNull] == null) return false;
|
|
20240
|
+
if ("whereNull" in candidate && candidate.whereNull && row[candidate.whereNull] != null) return false;
|
|
20241
|
+
return true;
|
|
20242
|
+
});
|
|
20243
|
+
if (!matches) {
|
|
20244
|
+
throw new Error(
|
|
20245
|
+
`PgFactoryStorage: upsert conflict keys [${conflictKeys.join(", ")}] do not match an applicable primary key or unique index on '${schema.name}'`
|
|
20246
|
+
);
|
|
20247
|
+
}
|
|
20248
|
+
}
|
|
20249
|
+
function serializeDefault(value) {
|
|
20250
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
20251
|
+
return String(value);
|
|
20252
|
+
}
|
|
20253
|
+
function hashAdvisoryLockKey(key) {
|
|
20254
|
+
const digest = crypto$1.createHash("sha256").update(key).digest();
|
|
20255
|
+
return [digest.readInt32BE(0), digest.readInt32BE(4)];
|
|
20256
|
+
}
|
|
20257
|
+
var PgFactoryStorageOps = class {
|
|
20258
|
+
#pool;
|
|
20259
|
+
#schemas;
|
|
20260
|
+
constructor(pool, schemas) {
|
|
20261
|
+
this.#pool = pool;
|
|
20262
|
+
this.#schemas = schemas;
|
|
20263
|
+
}
|
|
20264
|
+
#schema(collection) {
|
|
20265
|
+
const schema = this.#schemas.get(collection);
|
|
20266
|
+
if (!schema) {
|
|
20267
|
+
throw new Error(
|
|
20268
|
+
`PgFactoryStorage: unknown collection '${collection}' \u2014 register it via ensureCollections() first`
|
|
20269
|
+
);
|
|
20270
|
+
}
|
|
20271
|
+
return schema;
|
|
20272
|
+
}
|
|
20273
|
+
#column(schema, column) {
|
|
20274
|
+
const spec = schema.columns[column];
|
|
20275
|
+
if (!spec) {
|
|
20276
|
+
throw new Error(`PgFactoryStorage: unknown column '${column}' on collection '${schema.name}'`);
|
|
20277
|
+
}
|
|
20278
|
+
return spec;
|
|
20279
|
+
}
|
|
20280
|
+
#serialize(spec, value) {
|
|
20281
|
+
if (value === null || value === void 0) return null;
|
|
20282
|
+
switch (spec.type) {
|
|
20283
|
+
case "timestamp":
|
|
20284
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
20285
|
+
case "boolean":
|
|
20286
|
+
return Boolean(value);
|
|
20287
|
+
case "json":
|
|
20288
|
+
return JSON.stringify(value);
|
|
20289
|
+
case "bigint":
|
|
20290
|
+
case "integer":
|
|
20291
|
+
return Number(value);
|
|
20292
|
+
default:
|
|
20293
|
+
return String(value);
|
|
20294
|
+
}
|
|
20295
|
+
}
|
|
20296
|
+
#deserializeRow(schema, raw) {
|
|
20297
|
+
const row = {};
|
|
20298
|
+
for (const [name, spec] of Object.entries(schema.columns)) {
|
|
20299
|
+
const value = raw[name];
|
|
20300
|
+
if (value === null || value === void 0) {
|
|
20301
|
+
row[name] = null;
|
|
20302
|
+
continue;
|
|
20303
|
+
}
|
|
20304
|
+
switch (spec.type) {
|
|
20305
|
+
case "timestamp":
|
|
20306
|
+
row[name] = value instanceof Date ? value : new Date(String(value));
|
|
20307
|
+
break;
|
|
20308
|
+
case "boolean":
|
|
20309
|
+
row[name] = Boolean(value);
|
|
20310
|
+
break;
|
|
20311
|
+
case "json":
|
|
20312
|
+
row[name] = typeof value === "string" ? JSON.parse(value) : value;
|
|
20313
|
+
break;
|
|
20314
|
+
case "bigint":
|
|
20315
|
+
case "integer":
|
|
20316
|
+
row[name] = Number(value);
|
|
20317
|
+
break;
|
|
20318
|
+
default:
|
|
20319
|
+
row[name] = String(value);
|
|
20320
|
+
}
|
|
20321
|
+
}
|
|
20322
|
+
return row;
|
|
20323
|
+
}
|
|
20324
|
+
/** Builds `WHERE` SQL (without the keyword). `{}` yields a match-all clause. */
|
|
20325
|
+
#buildWhere(schema, where, startIndex = 1) {
|
|
20326
|
+
const clauses = [];
|
|
20327
|
+
const args = [];
|
|
20328
|
+
let index = startIndex;
|
|
20329
|
+
for (const [column, condition] of Object.entries(where)) {
|
|
20330
|
+
const spec = this.#column(schema, column);
|
|
20331
|
+
if (condition !== null && typeof condition === "object" && !(condition instanceof Date) && "in" in condition) {
|
|
20332
|
+
if (condition.in.length === 0) {
|
|
20333
|
+
clauses.push("FALSE");
|
|
20334
|
+
continue;
|
|
20335
|
+
}
|
|
20336
|
+
const nonNull = condition.in.filter((value) => value !== null);
|
|
20337
|
+
const includesNull = nonNull.length !== condition.in.length;
|
|
20338
|
+
const inClause = nonNull.length > 0 ? `"${column}" IN (${nonNull.map(() => `$${index++}`).join(", ")})` : void 0;
|
|
20339
|
+
clauses.push(
|
|
20340
|
+
inClause && includesNull ? `(${inClause} OR "${column}" IS NULL)` : inClause ?? `"${column}" IS NULL`
|
|
20341
|
+
);
|
|
20342
|
+
args.push(...nonNull.map((value) => this.#serialize(spec, value)));
|
|
20343
|
+
} else if (condition === null) {
|
|
20344
|
+
clauses.push(`"${column}" IS NULL`);
|
|
20345
|
+
} else {
|
|
20346
|
+
clauses.push(`"${column}" = $${index++}`);
|
|
20347
|
+
args.push(this.#serialize(spec, condition));
|
|
20348
|
+
}
|
|
20349
|
+
}
|
|
20350
|
+
return { sql: clauses.length > 0 ? clauses.join(" AND ") : "TRUE", args, nextIndex: index };
|
|
20351
|
+
}
|
|
20352
|
+
/** Keyset condition: rows strictly after `cursor.values` in the `orderBy` order. */
|
|
20353
|
+
#buildCursor(schema, orderBy, values, startIndex) {
|
|
20354
|
+
if (values.length !== orderBy.length) {
|
|
20355
|
+
throw new Error(`PgFactoryStorage: cursor has ${values.length} values but orderBy has ${orderBy.length} columns`);
|
|
20356
|
+
}
|
|
20357
|
+
const branches = [];
|
|
20358
|
+
const args = [];
|
|
20359
|
+
let index = startIndex;
|
|
20360
|
+
for (let i = 0; i < orderBy.length; i++) {
|
|
20361
|
+
const parts = [];
|
|
20362
|
+
for (let j = 0; j < i; j++) {
|
|
20363
|
+
const [column2] = orderBy[j];
|
|
20364
|
+
parts.push(`"${column2}" = $${index++}`);
|
|
20365
|
+
args.push(this.#serialize(this.#column(schema, column2), values[j]));
|
|
20366
|
+
}
|
|
20367
|
+
const [column, dir] = orderBy[i];
|
|
20368
|
+
parts.push(`"${column}" ${dir === "desc" ? "<" : ">"} $${index++}`);
|
|
20369
|
+
args.push(this.#serialize(this.#column(schema, column), values[i]));
|
|
20370
|
+
branches.push(`(${parts.join(" AND ")})`);
|
|
20371
|
+
}
|
|
20372
|
+
return { sql: `(${branches.join(" OR ")})`, args, nextIndex: index };
|
|
20373
|
+
}
|
|
20374
|
+
#buildSelect(schema, where, opts, forUpdate = false) {
|
|
20375
|
+
const filter = this.#buildWhere(schema, where);
|
|
20376
|
+
let sql = `SELECT * FROM "${schema.name}" WHERE ${filter.sql}`;
|
|
20377
|
+
const args = [...filter.args];
|
|
20378
|
+
let index = filter.nextIndex;
|
|
20379
|
+
if (opts?.cursor) {
|
|
20380
|
+
if (!opts.orderBy || opts.orderBy.length === 0) {
|
|
20381
|
+
throw new Error("PgFactoryStorage: cursor pagination requires orderBy");
|
|
20382
|
+
}
|
|
20383
|
+
const cursor = this.#buildCursor(schema, opts.orderBy, opts.cursor.values, index);
|
|
20384
|
+
sql += ` AND ${cursor.sql}`;
|
|
20385
|
+
args.push(...cursor.args);
|
|
20386
|
+
index = cursor.nextIndex;
|
|
20387
|
+
}
|
|
20388
|
+
if (opts?.orderBy && opts.orderBy.length > 0) {
|
|
20389
|
+
const order = opts.orderBy.map(([column, dir]) => {
|
|
20390
|
+
this.#column(schema, column);
|
|
20391
|
+
return `"${column}" ${dir === "desc" ? "DESC" : "ASC"}`;
|
|
20392
|
+
}).join(", ");
|
|
20393
|
+
sql += ` ORDER BY ${order}`;
|
|
20394
|
+
}
|
|
20395
|
+
if (opts?.limit !== void 0) {
|
|
20396
|
+
sql += ` LIMIT $${index++}`;
|
|
20397
|
+
args.push(opts.limit);
|
|
20398
|
+
}
|
|
20399
|
+
if (forUpdate) sql += " FOR UPDATE";
|
|
20400
|
+
return { sql, args };
|
|
20401
|
+
}
|
|
20402
|
+
async #select(queryable, collection, where, opts, forUpdate = false) {
|
|
20403
|
+
const schema = this.#schema(collection);
|
|
20404
|
+
const { sql, args } = this.#buildSelect(schema, where, opts, forUpdate);
|
|
20405
|
+
const result = await queryable.query(sql, args);
|
|
20406
|
+
return result.rows.map((raw) => this.#deserializeRow(schema, raw));
|
|
20407
|
+
}
|
|
20408
|
+
async findOne(collection, where) {
|
|
20409
|
+
const rows = await this.#select(this.#pool, collection, where, { limit: 1 });
|
|
20410
|
+
return rows[0] ?? null;
|
|
20411
|
+
}
|
|
20412
|
+
async findMany(collection, where, opts) {
|
|
20413
|
+
return this.#select(this.#pool, collection, where, opts);
|
|
20414
|
+
}
|
|
20415
|
+
async insertOne(collection, row) {
|
|
20416
|
+
const schema = this.#schema(collection);
|
|
20417
|
+
const pk = primaryKeyOf(schema);
|
|
20418
|
+
const values = { ...row };
|
|
20419
|
+
if (schema.columns[pk].type === "uuid-pk" && values[pk] === void 0) {
|
|
20420
|
+
values[pk] = crypto$1.randomUUID();
|
|
20421
|
+
}
|
|
20422
|
+
const columns = Object.keys(values).filter((column) => values[column] !== void 0);
|
|
20423
|
+
for (const column of columns) this.#column(schema, column);
|
|
20424
|
+
const sql = `INSERT INTO "${schema.name}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${columns.map((_, i) => `$${i + 1}`).join(", ")}) RETURNING *`;
|
|
20425
|
+
const args = columns.map((column) => this.#serialize(this.#column(schema, column), values[column]));
|
|
20426
|
+
try {
|
|
20427
|
+
const result = await this.#pool.query(sql, args);
|
|
20428
|
+
return this.#deserializeRow(schema, result.rows[0]);
|
|
20429
|
+
} catch (error) {
|
|
20430
|
+
if (isUniqueViolation(error)) throw new storage.UniqueViolationError(collection, { cause: error });
|
|
20431
|
+
throw error;
|
|
20432
|
+
}
|
|
20433
|
+
}
|
|
20434
|
+
async upsertOne(collection, conflictKeys, row) {
|
|
20435
|
+
const schema = this.#schema(collection);
|
|
20436
|
+
const pk = primaryKeyOf(schema);
|
|
20437
|
+
assertUpsertConflict(schema, conflictKeys, row);
|
|
20438
|
+
const keyWhere = Object.fromEntries(conflictKeys.map((key) => [key, row[key]]));
|
|
20439
|
+
let lastError;
|
|
20440
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
20441
|
+
const existing = await this.findOne(collection, keyWhere);
|
|
20442
|
+
if (existing) {
|
|
20443
|
+
const set = Object.fromEntries(
|
|
20444
|
+
Object.entries(row).filter(
|
|
20445
|
+
([column, value]) => value !== void 0 && column !== pk && !conflictKeys.includes(column)
|
|
20446
|
+
)
|
|
20447
|
+
);
|
|
20448
|
+
if (Object.keys(set).length > 0) {
|
|
20449
|
+
await this.updateMany(collection, { [pk]: existing[pk] }, set);
|
|
20450
|
+
}
|
|
20451
|
+
const updated = await this.findOne(collection, { [pk]: existing[pk] });
|
|
20452
|
+
if (!updated) continue;
|
|
20453
|
+
return updated;
|
|
20454
|
+
}
|
|
20455
|
+
try {
|
|
20456
|
+
return await this.insertOne(collection, row);
|
|
20457
|
+
} catch (error) {
|
|
20458
|
+
if (!(error instanceof storage.UniqueViolationError)) throw error;
|
|
20459
|
+
lastError = error;
|
|
20460
|
+
}
|
|
20461
|
+
}
|
|
20462
|
+
throw lastError ?? new Error(`PgFactoryStorage: upsert into '${collection}' did not converge`);
|
|
20463
|
+
}
|
|
20464
|
+
async updateMany(collection, where, set) {
|
|
20465
|
+
return this.#updateMany(this.#pool, collection, where, set);
|
|
20466
|
+
}
|
|
20467
|
+
async #updateMany(queryable, collection, where, set) {
|
|
20468
|
+
const schema = this.#schema(collection);
|
|
20469
|
+
const columns = Object.keys(set).filter((column) => set[column] !== void 0);
|
|
20470
|
+
if (columns.length === 0) return 0;
|
|
20471
|
+
const assignments = columns.map((column, i) => `"${column}" = $${i + 1}`).join(", ");
|
|
20472
|
+
const filter = this.#buildWhere(schema, where, columns.length + 1);
|
|
20473
|
+
const args = [...columns.map((column) => this.#serialize(this.#column(schema, column), set[column])), ...filter.args];
|
|
20474
|
+
const result = await queryable.query(`UPDATE "${schema.name}" SET ${assignments} WHERE ${filter.sql}`, args);
|
|
20475
|
+
return result.rowCount ?? 0;
|
|
20476
|
+
}
|
|
20477
|
+
async deleteMany(collection, where) {
|
|
20478
|
+
const schema = this.#schema(collection);
|
|
20479
|
+
const filter = this.#buildWhere(schema, where);
|
|
20480
|
+
const result = await this.#pool.query(`DELETE FROM "${schema.name}" WHERE ${filter.sql}`, filter.args);
|
|
20481
|
+
return result.rowCount ?? 0;
|
|
20482
|
+
}
|
|
20483
|
+
async updateAtomic(collection, where, fn) {
|
|
20484
|
+
const schema = this.#schema(collection);
|
|
20485
|
+
const pk = primaryKeyOf(schema);
|
|
20486
|
+
const client = await this.#pool.connect();
|
|
20487
|
+
try {
|
|
20488
|
+
await client.query("BEGIN");
|
|
20489
|
+
try {
|
|
20490
|
+
const rows = await this.#select(client, collection, where, { limit: 1 }, true);
|
|
20491
|
+
const row = rows[0];
|
|
20492
|
+
if (!row) {
|
|
20493
|
+
await client.query("COMMIT");
|
|
20494
|
+
return null;
|
|
20495
|
+
}
|
|
20496
|
+
const patch = await fn(row);
|
|
20497
|
+
let result = row;
|
|
20498
|
+
if (patch !== null) {
|
|
20499
|
+
const pkWhere = { [pk]: row[pk] };
|
|
20500
|
+
await this.#updateMany(client, collection, pkWhere, patch);
|
|
20501
|
+
const updated = await this.#select(client, collection, pkWhere, { limit: 1 });
|
|
20502
|
+
result = updated[0] ?? null;
|
|
20503
|
+
}
|
|
20504
|
+
await client.query("COMMIT");
|
|
20505
|
+
return result;
|
|
20506
|
+
} catch (error) {
|
|
20507
|
+
await client.query("ROLLBACK");
|
|
20508
|
+
throw error;
|
|
20509
|
+
}
|
|
20510
|
+
} finally {
|
|
20511
|
+
client.release();
|
|
20512
|
+
}
|
|
20513
|
+
}
|
|
20514
|
+
};
|
|
20515
|
+
var PgFactoryStorage = class extends storage.FactoryStorage {
|
|
20516
|
+
ops;
|
|
20517
|
+
#pool;
|
|
20518
|
+
#ownsPool;
|
|
20519
|
+
#config;
|
|
20520
|
+
#schemas = /* @__PURE__ */ new Map();
|
|
20521
|
+
#mastraStorage;
|
|
20522
|
+
constructor(config) {
|
|
20523
|
+
super();
|
|
20524
|
+
this.#config = config;
|
|
20525
|
+
if ("store" in config) {
|
|
20526
|
+
this.#pool = config.store.pool;
|
|
20527
|
+
this.#ownsPool = false;
|
|
20528
|
+
this.#mastraStorage = config.store;
|
|
20529
|
+
} else {
|
|
20530
|
+
this.#pool = new pg__namespace.default.Pool({ connectionString: config.connectionString });
|
|
20531
|
+
this.#ownsPool = true;
|
|
20532
|
+
}
|
|
20533
|
+
this.ops = new PgFactoryStorageOps(this.#pool, this.#schemas);
|
|
20534
|
+
}
|
|
20535
|
+
getMastraStorage() {
|
|
20536
|
+
if (!this.#mastraStorage) {
|
|
20537
|
+
const config = this.#config;
|
|
20538
|
+
this.#mastraStorage = new PostgresStore({
|
|
20539
|
+
id: config.id ?? "pg-factory",
|
|
20540
|
+
pool: this.#pool,
|
|
20541
|
+
...config.retention ? { retention: config.retention } : {}
|
|
20542
|
+
});
|
|
20543
|
+
}
|
|
20544
|
+
return this.#mastraStorage;
|
|
20545
|
+
}
|
|
20546
|
+
async initStorage() {
|
|
20547
|
+
await this.#pool.query("SELECT 1");
|
|
20548
|
+
}
|
|
20549
|
+
async ensureCollections(schemas) {
|
|
20550
|
+
for (const schema of schemas) {
|
|
20551
|
+
await this.#ensureCollection(schema);
|
|
20552
|
+
this.#schemas.set(schema.name, schema);
|
|
20553
|
+
}
|
|
20554
|
+
}
|
|
20555
|
+
async close() {
|
|
20556
|
+
if (this.#ownsPool) {
|
|
20557
|
+
await this.#pool.end();
|
|
20558
|
+
} else {
|
|
20559
|
+
await this.#mastraStorage?.close();
|
|
20560
|
+
}
|
|
20561
|
+
}
|
|
20562
|
+
authDatabase() {
|
|
20563
|
+
return { dialect: "postgres", pool: this.#pool };
|
|
20564
|
+
}
|
|
20565
|
+
/**
|
|
20566
|
+
* Run `fn` while holding a Postgres transaction-scoped advisory lock for
|
|
20567
|
+
* `key`, serializing callers across replicas. The lock releases when the
|
|
20568
|
+
* transaction ends (commit, rollback, or connection loss), so a crashed
|
|
20569
|
+
* replica can never hold it forever.
|
|
20570
|
+
*/
|
|
20571
|
+
async withDistributedLock(key, fn) {
|
|
20572
|
+
const [k1, k2] = hashAdvisoryLockKey(key);
|
|
20573
|
+
const client = await this.#pool.connect();
|
|
20574
|
+
try {
|
|
20575
|
+
await client.query("BEGIN");
|
|
20576
|
+
await client.query("SELECT pg_advisory_xact_lock($1, $2)", [k1, k2]);
|
|
20577
|
+
try {
|
|
20578
|
+
const result = await fn();
|
|
20579
|
+
await client.query("COMMIT");
|
|
20580
|
+
return result;
|
|
20581
|
+
} catch (error) {
|
|
20582
|
+
await client.query("ROLLBACK");
|
|
20583
|
+
throw error;
|
|
20584
|
+
}
|
|
20585
|
+
} finally {
|
|
20586
|
+
client.release();
|
|
20587
|
+
}
|
|
20588
|
+
}
|
|
20589
|
+
#columnDdl(name, spec) {
|
|
20590
|
+
assertIdentifier("column", name);
|
|
20591
|
+
let ddl = `"${name}" ${COLUMN_DDL[spec.type]}`;
|
|
20592
|
+
if (spec.type === "uuid-pk" || spec.primaryKey) ddl += " PRIMARY KEY";
|
|
20593
|
+
if (!spec.nullable && spec.type !== "uuid-pk" && !spec.primaryKey) ddl += " NOT NULL";
|
|
20594
|
+
if (spec.default !== void 0) ddl += ` DEFAULT ${serializeDefault(spec.default)}`;
|
|
20595
|
+
return ddl;
|
|
20596
|
+
}
|
|
20597
|
+
async #ensureCollection(schema) {
|
|
20598
|
+
assertIdentifier("collection", schema.name);
|
|
20599
|
+
primaryKeyOf(schema);
|
|
20600
|
+
const columns = Object.entries(schema.columns).map(([name, spec]) => this.#columnDdl(name, spec));
|
|
20601
|
+
await this.#pool.query(`CREATE TABLE IF NOT EXISTS "${schema.name}" (${columns.join(", ")})`);
|
|
20602
|
+
for (const [name, spec] of Object.entries(schema.columns)) {
|
|
20603
|
+
await this.#pool.query(`ALTER TABLE "${schema.name}" ADD COLUMN IF NOT EXISTS ${this.#columnDdl(name, spec)}`);
|
|
20604
|
+
}
|
|
20605
|
+
for (const index of schema.uniqueIndexes ?? []) {
|
|
20606
|
+
assertIdentifier("index", index.name);
|
|
20607
|
+
index.columns.forEach((column) => assertIdentifier("column", column));
|
|
20608
|
+
let where = "";
|
|
20609
|
+
if (index.whereNotNull) {
|
|
20610
|
+
assertIdentifier("column", index.whereNotNull);
|
|
20611
|
+
where = ` WHERE "${index.whereNotNull}" IS NOT NULL`;
|
|
20612
|
+
} else if (index.whereNull) {
|
|
20613
|
+
assertIdentifier("column", index.whereNull);
|
|
20614
|
+
where = ` WHERE "${index.whereNull}" IS NULL`;
|
|
20615
|
+
}
|
|
20616
|
+
await this.#pool.query(
|
|
20617
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "${index.name}" ON "${schema.name}" (${index.columns.map((c) => `"${c}"`).join(", ")})${where}`
|
|
20618
|
+
);
|
|
20619
|
+
}
|
|
20620
|
+
for (const index of schema.indexes ?? []) {
|
|
20621
|
+
assertIdentifier("index", index.name);
|
|
20622
|
+
index.columns.forEach((column) => assertIdentifier("column", column));
|
|
20623
|
+
await this.#pool.query(
|
|
20624
|
+
`CREATE INDEX IF NOT EXISTS "${index.name}" ON "${schema.name}" (${index.columns.map((c) => `"${c}"`).join(", ")})`
|
|
20625
|
+
);
|
|
20626
|
+
}
|
|
20627
|
+
}
|
|
20628
|
+
};
|
|
20187
20629
|
|
|
20188
20630
|
// src/storage/index.ts
|
|
20189
20631
|
var DEFAULT_MAX_CONNECTIONS = 20;
|
|
@@ -20308,6 +20750,18 @@ var PostgresStore = class extends storage.MastraCompositeStore {
|
|
|
20308
20750
|
}
|
|
20309
20751
|
}
|
|
20310
20752
|
createPool(config) {
|
|
20753
|
+
const pool = this.#buildPool(config);
|
|
20754
|
+
pool.on("error", (err) => {
|
|
20755
|
+
this.logger?.warn?.(
|
|
20756
|
+
"PostgresStore: idle pool client error (pool discards the client and reconnects on next checkout)",
|
|
20757
|
+
{
|
|
20758
|
+
err: err instanceof Error ? err.message : err
|
|
20759
|
+
}
|
|
20760
|
+
);
|
|
20761
|
+
});
|
|
20762
|
+
return pool;
|
|
20763
|
+
}
|
|
20764
|
+
#buildPool(config) {
|
|
20311
20765
|
if (isConnectionStringConfig(config)) {
|
|
20312
20766
|
return createConnectionStringPool(config);
|
|
20313
20767
|
}
|
|
@@ -20415,6 +20869,16 @@ var PostgresStoreVNext = class extends PostgresStore {
|
|
|
20415
20869
|
const observabilityClient = built.client;
|
|
20416
20870
|
this.#observabilityPool = built.pool;
|
|
20417
20871
|
this.#ownsObservabilityPool = built.owned;
|
|
20872
|
+
if (built.owned) {
|
|
20873
|
+
built.pool.on("error", (err) => {
|
|
20874
|
+
this.logger?.warn?.(
|
|
20875
|
+
"PostgresStoreVNext: idle observability pool client error (pool discards the client and reconnects on next checkout)",
|
|
20876
|
+
{
|
|
20877
|
+
err: err instanceof Error ? err.message : err
|
|
20878
|
+
}
|
|
20879
|
+
);
|
|
20880
|
+
});
|
|
20881
|
+
}
|
|
20418
20882
|
const observability = new ObservabilityStoragePostgresVNext({
|
|
20419
20883
|
client: observabilityClient,
|
|
20420
20884
|
schemaName: obsConfig.schemaName ?? config.schemaName,
|
|
@@ -20567,6 +21031,7 @@ exports.NotificationsPG = NotificationsPG;
|
|
|
20567
21031
|
exports.ObservabilityPG = ObservabilityPG;
|
|
20568
21032
|
exports.ObservabilityStoragePostgresVNext = ObservabilityStoragePostgresVNext;
|
|
20569
21033
|
exports.PGVECTOR_PROMPT = PGVECTOR_PROMPT;
|
|
21034
|
+
exports.PgFactoryStorage = PgFactoryStorage;
|
|
20570
21035
|
exports.PgVector = PgVector;
|
|
20571
21036
|
exports.PoolAdapter = PoolAdapter;
|
|
20572
21037
|
exports.PostgresStore = PostgresStore;
|
|
@@ -20580,5 +21045,6 @@ exports.ToolProviderConnectionsPG = ToolProviderConnectionsPG;
|
|
|
20580
21045
|
exports.WorkflowsPG = WorkflowsPG;
|
|
20581
21046
|
exports.WorkspacesPG = WorkspacesPG;
|
|
20582
21047
|
exports.exportSchemas = exportSchemas;
|
|
21048
|
+
exports.hashAdvisoryLockKey = hashAdvisoryLockKey;
|
|
20583
21049
|
//# sourceMappingURL=index.cjs.map
|
|
20584
21050
|
//# sourceMappingURL=index.cjs.map
|