@mastra/pg 1.16.1-alpha.0 → 1.17.0-alpha.3
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 +54 -6
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-tools-vector-query-tool.md +22 -0
- package/dist/index.cjs +450 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +452 -5
- package/dist/index.js.map +1 -1
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts +46 -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/storage/test-utils.d.ts.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 1.17.0-alpha.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed a missing semicolon in the SQL generated by exportSchemas(). The CREATE INDEX statement for the favorites table ran into the next CREATE TABLE statement, so applying the exported schema as a single query (for example with pg's client.query() or a Supabase migration) failed with a Postgres syntax error. Fixes https://github.com/mastra-ai/mastra/issues/19942 ([#19944](https://github.com/mastra-ai/mastra/pull/19944))
|
|
8
|
+
|
|
9
|
+
## 1.17.0-alpha.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. ([#19849](https://github.com/mastra-ai/mastra/pull/19849))
|
|
14
|
+
|
|
15
|
+
Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option.
|
|
16
|
+
|
|
17
|
+
**Before**
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir };
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**After**
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const state = {
|
|
27
|
+
factoryProjectId: 'factory-project-1',
|
|
28
|
+
projectRepositoryId: 'project-repository-1',
|
|
29
|
+
sandboxId,
|
|
30
|
+
sandboxWorkdir,
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`41a5392`](https://github.com/mastra-ai/mastra/commit/41a5392d9f6c5e18d6b227f0fc0ddf49c50774e9), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`da009e1`](https://github.com/mastra-ai/mastra/commit/da009e1aacd89ed94b8d1b2af09c9d4fe7c4db49), [`35c2181`](https://github.com/mastra-ai/mastra/commit/35c2181e6a50e47c90ba36260db7c9723d54696f), [`b4b7ea8`](https://github.com/mastra-ai/mastra/commit/b4b7ea8733f033fc441ea47ed03f6afb17ec2248), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`c328769`](https://github.com/mastra-ai/mastra/commit/c3287698ff8ef98dba86d415faa566fa3e5f4d56), [`232fcbc`](https://github.com/mastra-ai/mastra/commit/232fcbc14fce625dd672ba043329c0b732c62be2), [`3491666`](https://github.com/mastra-ai/mastra/commit/34916663c4fdd43b48c21f4ab2d5fb6dcccc94f9)]:
|
|
35
|
+
- @mastra/core@1.52.0-alpha.10
|
|
36
|
+
|
|
37
|
+
## 1.17.0-alpha.1
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- 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))
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { PgFactoryStorage } from '@mastra/pg';
|
|
45
|
+
|
|
46
|
+
const storage = new PgFactoryStorage({ connectionString: process.env.DATABASE_URL! });
|
|
47
|
+
await storage.init();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- 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)]:
|
|
53
|
+
- @mastra/core@1.52.0-alpha.8
|
|
54
|
+
|
|
3
55
|
## 1.16.1-alpha.0
|
|
4
56
|
|
|
5
57
|
### Patch Changes
|
|
@@ -1080,9 +1132,7 @@
|
|
|
1080
1132
|
**Example**
|
|
1081
1133
|
|
|
1082
1134
|
```ts
|
|
1083
|
-
const storage = new LibSQLStore({
|
|
1084
|
-
/* config */
|
|
1085
|
-
});
|
|
1135
|
+
const storage = new LibSQLStore({/* config */});
|
|
1086
1136
|
const favorites = await storage.getStore('favorites');
|
|
1087
1137
|
|
|
1088
1138
|
await favorites?.favorite({
|
|
@@ -1110,9 +1160,7 @@
|
|
|
1110
1160
|
**Example**
|
|
1111
1161
|
|
|
1112
1162
|
```ts
|
|
1113
|
-
const storage = new LibSQLStore({
|
|
1114
|
-
/* config */
|
|
1115
|
-
});
|
|
1163
|
+
const storage = new LibSQLStore({/* config */});
|
|
1116
1164
|
const favorites = await storage.getStore('favorites');
|
|
1117
1165
|
|
|
1118
1166
|
await favorites?.favorite({
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -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
|
@@ -7326,7 +7326,7 @@ var FavoritesPG = class _FavoritesPG extends storage.FavoritesStorage {
|
|
|
7326
7326
|
}
|
|
7327
7327
|
const fullFavoritesTable = getTableName2({ indexName: storage.TABLE_FAVORITES, schemaName: getSchemaName2(schemaName) });
|
|
7328
7328
|
statements.push(
|
|
7329
|
-
`CREATE INDEX IF NOT EXISTS idx_favorites_entity ON ${fullFavoritesTable} ("entityType", "entityId")
|
|
7329
|
+
`CREATE INDEX IF NOT EXISTS idx_favorites_entity ON ${fullFavoritesTable} ("entityType", "entityId");`
|
|
7330
7330
|
);
|
|
7331
7331
|
return statements;
|
|
7332
7332
|
}
|
|
@@ -20197,6 +20197,453 @@ var WorkspacesPG = class _WorkspacesPG extends storage.WorkspacesStorage {
|
|
|
20197
20197
|
};
|
|
20198
20198
|
}
|
|
20199
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
|
+
#queryable;
|
|
20259
|
+
#transactionClient;
|
|
20260
|
+
#schemas;
|
|
20261
|
+
constructor(queryable, schemas, transactionClient) {
|
|
20262
|
+
this.#queryable = queryable;
|
|
20263
|
+
this.#transactionClient = transactionClient;
|
|
20264
|
+
this.#schemas = schemas;
|
|
20265
|
+
}
|
|
20266
|
+
#schema(collection) {
|
|
20267
|
+
const schema = this.#schemas.get(collection);
|
|
20268
|
+
if (!schema) {
|
|
20269
|
+
throw new Error(
|
|
20270
|
+
`PgFactoryStorage: unknown collection '${collection}' \u2014 register it via ensureCollections() first`
|
|
20271
|
+
);
|
|
20272
|
+
}
|
|
20273
|
+
return schema;
|
|
20274
|
+
}
|
|
20275
|
+
#column(schema, column) {
|
|
20276
|
+
const spec = schema.columns[column];
|
|
20277
|
+
if (!spec) {
|
|
20278
|
+
throw new Error(`PgFactoryStorage: unknown column '${column}' on collection '${schema.name}'`);
|
|
20279
|
+
}
|
|
20280
|
+
return spec;
|
|
20281
|
+
}
|
|
20282
|
+
#serialize(spec, value) {
|
|
20283
|
+
if (value === null || value === void 0) return null;
|
|
20284
|
+
switch (spec.type) {
|
|
20285
|
+
case "timestamp":
|
|
20286
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
20287
|
+
case "boolean":
|
|
20288
|
+
return Boolean(value);
|
|
20289
|
+
case "json":
|
|
20290
|
+
return JSON.stringify(value);
|
|
20291
|
+
case "bigint":
|
|
20292
|
+
case "integer":
|
|
20293
|
+
return Number(value);
|
|
20294
|
+
default:
|
|
20295
|
+
return String(value);
|
|
20296
|
+
}
|
|
20297
|
+
}
|
|
20298
|
+
#deserializeRow(schema, raw) {
|
|
20299
|
+
const row = {};
|
|
20300
|
+
for (const [name, spec] of Object.entries(schema.columns)) {
|
|
20301
|
+
const value = raw[name];
|
|
20302
|
+
if (value === null || value === void 0) {
|
|
20303
|
+
row[name] = null;
|
|
20304
|
+
continue;
|
|
20305
|
+
}
|
|
20306
|
+
switch (spec.type) {
|
|
20307
|
+
case "timestamp":
|
|
20308
|
+
row[name] = value instanceof Date ? value : new Date(String(value));
|
|
20309
|
+
break;
|
|
20310
|
+
case "boolean":
|
|
20311
|
+
row[name] = Boolean(value);
|
|
20312
|
+
break;
|
|
20313
|
+
case "json":
|
|
20314
|
+
row[name] = typeof value === "string" ? JSON.parse(value) : value;
|
|
20315
|
+
break;
|
|
20316
|
+
case "bigint":
|
|
20317
|
+
case "integer":
|
|
20318
|
+
row[name] = Number(value);
|
|
20319
|
+
break;
|
|
20320
|
+
default:
|
|
20321
|
+
row[name] = String(value);
|
|
20322
|
+
}
|
|
20323
|
+
}
|
|
20324
|
+
return row;
|
|
20325
|
+
}
|
|
20326
|
+
/** Builds `WHERE` SQL (without the keyword). `{}` yields a match-all clause. */
|
|
20327
|
+
#buildWhere(schema, where, startIndex = 1) {
|
|
20328
|
+
const clauses = [];
|
|
20329
|
+
const args = [];
|
|
20330
|
+
let index = startIndex;
|
|
20331
|
+
for (const [column, condition] of Object.entries(where)) {
|
|
20332
|
+
const spec = this.#column(schema, column);
|
|
20333
|
+
if (condition !== null && typeof condition === "object" && !(condition instanceof Date) && "in" in condition) {
|
|
20334
|
+
if (condition.in.length === 0) {
|
|
20335
|
+
clauses.push("FALSE");
|
|
20336
|
+
continue;
|
|
20337
|
+
}
|
|
20338
|
+
const nonNull = condition.in.filter((value) => value !== null);
|
|
20339
|
+
const includesNull = nonNull.length !== condition.in.length;
|
|
20340
|
+
const inClause = nonNull.length > 0 ? `"${column}" IN (${nonNull.map(() => `$${index++}`).join(", ")})` : void 0;
|
|
20341
|
+
clauses.push(
|
|
20342
|
+
inClause && includesNull ? `(${inClause} OR "${column}" IS NULL)` : inClause ?? `"${column}" IS NULL`
|
|
20343
|
+
);
|
|
20344
|
+
args.push(...nonNull.map((value) => this.#serialize(spec, value)));
|
|
20345
|
+
} else if (condition === null) {
|
|
20346
|
+
clauses.push(`"${column}" IS NULL`);
|
|
20347
|
+
} else {
|
|
20348
|
+
clauses.push(`"${column}" = $${index++}`);
|
|
20349
|
+
args.push(this.#serialize(spec, condition));
|
|
20350
|
+
}
|
|
20351
|
+
}
|
|
20352
|
+
return { sql: clauses.length > 0 ? clauses.join(" AND ") : "TRUE", args, nextIndex: index };
|
|
20353
|
+
}
|
|
20354
|
+
/** Keyset condition: rows strictly after `cursor.values` in the `orderBy` order. */
|
|
20355
|
+
#buildCursor(schema, orderBy, values, startIndex) {
|
|
20356
|
+
if (values.length !== orderBy.length) {
|
|
20357
|
+
throw new Error(`PgFactoryStorage: cursor has ${values.length} values but orderBy has ${orderBy.length} columns`);
|
|
20358
|
+
}
|
|
20359
|
+
const branches = [];
|
|
20360
|
+
const args = [];
|
|
20361
|
+
let index = startIndex;
|
|
20362
|
+
for (let i = 0; i < orderBy.length; i++) {
|
|
20363
|
+
const parts = [];
|
|
20364
|
+
for (let j = 0; j < i; j++) {
|
|
20365
|
+
const [column2] = orderBy[j];
|
|
20366
|
+
parts.push(`"${column2}" = $${index++}`);
|
|
20367
|
+
args.push(this.#serialize(this.#column(schema, column2), values[j]));
|
|
20368
|
+
}
|
|
20369
|
+
const [column, dir] = orderBy[i];
|
|
20370
|
+
parts.push(`"${column}" ${dir === "desc" ? "<" : ">"} $${index++}`);
|
|
20371
|
+
args.push(this.#serialize(this.#column(schema, column), values[i]));
|
|
20372
|
+
branches.push(`(${parts.join(" AND ")})`);
|
|
20373
|
+
}
|
|
20374
|
+
return { sql: `(${branches.join(" OR ")})`, args, nextIndex: index };
|
|
20375
|
+
}
|
|
20376
|
+
#buildSelect(schema, where, opts, forUpdate = false) {
|
|
20377
|
+
const filter = this.#buildWhere(schema, where);
|
|
20378
|
+
let sql = `SELECT * FROM "${schema.name}" WHERE ${filter.sql}`;
|
|
20379
|
+
const args = [...filter.args];
|
|
20380
|
+
let index = filter.nextIndex;
|
|
20381
|
+
if (opts?.cursor) {
|
|
20382
|
+
if (!opts.orderBy || opts.orderBy.length === 0) {
|
|
20383
|
+
throw new Error("PgFactoryStorage: cursor pagination requires orderBy");
|
|
20384
|
+
}
|
|
20385
|
+
const cursor = this.#buildCursor(schema, opts.orderBy, opts.cursor.values, index);
|
|
20386
|
+
sql += ` AND ${cursor.sql}`;
|
|
20387
|
+
args.push(...cursor.args);
|
|
20388
|
+
index = cursor.nextIndex;
|
|
20389
|
+
}
|
|
20390
|
+
if (opts?.orderBy && opts.orderBy.length > 0) {
|
|
20391
|
+
const order = opts.orderBy.map(([column, dir]) => {
|
|
20392
|
+
this.#column(schema, column);
|
|
20393
|
+
return `"${column}" ${dir === "desc" ? "DESC" : "ASC"}`;
|
|
20394
|
+
}).join(", ");
|
|
20395
|
+
sql += ` ORDER BY ${order}`;
|
|
20396
|
+
}
|
|
20397
|
+
if (opts?.limit !== void 0) {
|
|
20398
|
+
sql += ` LIMIT $${index++}`;
|
|
20399
|
+
args.push(opts.limit);
|
|
20400
|
+
}
|
|
20401
|
+
if (forUpdate) sql += " FOR UPDATE";
|
|
20402
|
+
return { sql, args };
|
|
20403
|
+
}
|
|
20404
|
+
async #select(queryable, collection, where, opts, forUpdate = false) {
|
|
20405
|
+
const schema = this.#schema(collection);
|
|
20406
|
+
const { sql, args } = this.#buildSelect(schema, where, opts, forUpdate);
|
|
20407
|
+
const result = await queryable.query(sql, args);
|
|
20408
|
+
return result.rows.map((raw) => this.#deserializeRow(schema, raw));
|
|
20409
|
+
}
|
|
20410
|
+
async findOne(collection, where) {
|
|
20411
|
+
const rows = await this.#select(this.#queryable, collection, where, { limit: 1 });
|
|
20412
|
+
return rows[0] ?? null;
|
|
20413
|
+
}
|
|
20414
|
+
async findMany(collection, where, opts) {
|
|
20415
|
+
return this.#select(this.#queryable, collection, where, opts);
|
|
20416
|
+
}
|
|
20417
|
+
async insertOne(collection, row) {
|
|
20418
|
+
const schema = this.#schema(collection);
|
|
20419
|
+
const pk = primaryKeyOf(schema);
|
|
20420
|
+
const values = { ...row };
|
|
20421
|
+
if (schema.columns[pk].type === "uuid-pk" && values[pk] === void 0) {
|
|
20422
|
+
values[pk] = crypto$1.randomUUID();
|
|
20423
|
+
}
|
|
20424
|
+
const columns = Object.keys(values).filter((column) => values[column] !== void 0);
|
|
20425
|
+
for (const column of columns) this.#column(schema, column);
|
|
20426
|
+
const sql = `INSERT INTO "${schema.name}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${columns.map((_, i) => `$${i + 1}`).join(", ")}) RETURNING *`;
|
|
20427
|
+
const args = columns.map((column) => this.#serialize(this.#column(schema, column), values[column]));
|
|
20428
|
+
try {
|
|
20429
|
+
const result = await this.#queryable.query(sql, args);
|
|
20430
|
+
return this.#deserializeRow(schema, result.rows[0]);
|
|
20431
|
+
} catch (error) {
|
|
20432
|
+
if (isUniqueViolation(error)) throw new storage.UniqueViolationError(collection, { cause: error });
|
|
20433
|
+
throw error;
|
|
20434
|
+
}
|
|
20435
|
+
}
|
|
20436
|
+
async upsertOne(collection, conflictKeys, row) {
|
|
20437
|
+
const schema = this.#schema(collection);
|
|
20438
|
+
const pk = primaryKeyOf(schema);
|
|
20439
|
+
assertUpsertConflict(schema, conflictKeys, row);
|
|
20440
|
+
const keyWhere = Object.fromEntries(conflictKeys.map((key) => [key, row[key]]));
|
|
20441
|
+
let lastError;
|
|
20442
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
20443
|
+
const existing = await this.findOne(collection, keyWhere);
|
|
20444
|
+
if (existing) {
|
|
20445
|
+
const set = Object.fromEntries(
|
|
20446
|
+
Object.entries(row).filter(
|
|
20447
|
+
([column, value]) => value !== void 0 && column !== pk && !conflictKeys.includes(column)
|
|
20448
|
+
)
|
|
20449
|
+
);
|
|
20450
|
+
if (Object.keys(set).length > 0) {
|
|
20451
|
+
await this.updateMany(collection, { [pk]: existing[pk] }, set);
|
|
20452
|
+
}
|
|
20453
|
+
const updated = await this.findOne(collection, { [pk]: existing[pk] });
|
|
20454
|
+
if (!updated) continue;
|
|
20455
|
+
return updated;
|
|
20456
|
+
}
|
|
20457
|
+
try {
|
|
20458
|
+
return await this.insertOne(collection, row);
|
|
20459
|
+
} catch (error) {
|
|
20460
|
+
if (!(error instanceof storage.UniqueViolationError)) throw error;
|
|
20461
|
+
lastError = error;
|
|
20462
|
+
}
|
|
20463
|
+
}
|
|
20464
|
+
throw lastError ?? new Error(`PgFactoryStorage: upsert into '${collection}' did not converge`);
|
|
20465
|
+
}
|
|
20466
|
+
async updateMany(collection, where, set) {
|
|
20467
|
+
return this.#updateMany(this.#queryable, collection, where, set);
|
|
20468
|
+
}
|
|
20469
|
+
async #updateMany(queryable, collection, where, set) {
|
|
20470
|
+
const schema = this.#schema(collection);
|
|
20471
|
+
const columns = Object.keys(set).filter((column) => set[column] !== void 0);
|
|
20472
|
+
if (columns.length === 0) return 0;
|
|
20473
|
+
const assignments = columns.map((column, i) => `"${column}" = $${i + 1}`).join(", ");
|
|
20474
|
+
const filter = this.#buildWhere(schema, where, columns.length + 1);
|
|
20475
|
+
const args = [...columns.map((column) => this.#serialize(this.#column(schema, column), set[column])), ...filter.args];
|
|
20476
|
+
const result = await queryable.query(`UPDATE "${schema.name}" SET ${assignments} WHERE ${filter.sql}`, args);
|
|
20477
|
+
return result.rowCount ?? 0;
|
|
20478
|
+
}
|
|
20479
|
+
async deleteMany(collection, where) {
|
|
20480
|
+
const schema = this.#schema(collection);
|
|
20481
|
+
const filter = this.#buildWhere(schema, where);
|
|
20482
|
+
const result = await this.#queryable.query(`DELETE FROM "${schema.name}" WHERE ${filter.sql}`, filter.args);
|
|
20483
|
+
return result.rowCount ?? 0;
|
|
20484
|
+
}
|
|
20485
|
+
async updateAtomic(collection, where, fn) {
|
|
20486
|
+
const run = async (client2) => {
|
|
20487
|
+
const schema = this.#schema(collection);
|
|
20488
|
+
const pk = primaryKeyOf(schema);
|
|
20489
|
+
const rows = await this.#select(client2, collection, where, { limit: 1 }, true);
|
|
20490
|
+
const row = rows[0];
|
|
20491
|
+
if (!row) return null;
|
|
20492
|
+
const patch = await fn(row);
|
|
20493
|
+
if (patch === null) return row;
|
|
20494
|
+
const pkWhere = { [pk]: row[pk] };
|
|
20495
|
+
await this.#updateMany(client2, collection, pkWhere, patch);
|
|
20496
|
+
const updated = await this.#select(client2, collection, pkWhere, { limit: 1 });
|
|
20497
|
+
return updated[0] ?? null;
|
|
20498
|
+
};
|
|
20499
|
+
if (this.#transactionClient) return run(this.#transactionClient);
|
|
20500
|
+
const pool = this.#queryable;
|
|
20501
|
+
const client = await pool.connect();
|
|
20502
|
+
try {
|
|
20503
|
+
await client.query("BEGIN");
|
|
20504
|
+
try {
|
|
20505
|
+
const result = await run(client);
|
|
20506
|
+
await client.query("COMMIT");
|
|
20507
|
+
return result;
|
|
20508
|
+
} catch (error) {
|
|
20509
|
+
await client.query("ROLLBACK");
|
|
20510
|
+
throw error;
|
|
20511
|
+
}
|
|
20512
|
+
} finally {
|
|
20513
|
+
client.release();
|
|
20514
|
+
}
|
|
20515
|
+
}
|
|
20516
|
+
};
|
|
20517
|
+
var PgFactoryStorage = class extends storage.FactoryStorage {
|
|
20518
|
+
ops;
|
|
20519
|
+
#pool;
|
|
20520
|
+
#ownsPool;
|
|
20521
|
+
#config;
|
|
20522
|
+
#schemas = /* @__PURE__ */ new Map();
|
|
20523
|
+
#mastraStorage;
|
|
20524
|
+
constructor(config) {
|
|
20525
|
+
super();
|
|
20526
|
+
this.#config = config;
|
|
20527
|
+
if ("store" in config) {
|
|
20528
|
+
this.#pool = config.store.pool;
|
|
20529
|
+
this.#ownsPool = false;
|
|
20530
|
+
this.#mastraStorage = config.store;
|
|
20531
|
+
} else {
|
|
20532
|
+
this.#pool = new pg__namespace.default.Pool({ connectionString: config.connectionString });
|
|
20533
|
+
this.#ownsPool = true;
|
|
20534
|
+
}
|
|
20535
|
+
this.ops = new PgFactoryStorageOps(this.#pool, this.#schemas);
|
|
20536
|
+
}
|
|
20537
|
+
getMastraStorage() {
|
|
20538
|
+
if (!this.#mastraStorage) {
|
|
20539
|
+
const config = this.#config;
|
|
20540
|
+
this.#mastraStorage = new PostgresStore({
|
|
20541
|
+
id: config.id ?? "pg-factory",
|
|
20542
|
+
pool: this.#pool,
|
|
20543
|
+
...config.retention ? { retention: config.retention } : {}
|
|
20544
|
+
});
|
|
20545
|
+
}
|
|
20546
|
+
return this.#mastraStorage;
|
|
20547
|
+
}
|
|
20548
|
+
async initStorage() {
|
|
20549
|
+
await this.#pool.query("SELECT 1");
|
|
20550
|
+
}
|
|
20551
|
+
async withTransaction(fn) {
|
|
20552
|
+
const client = await this.#pool.connect();
|
|
20553
|
+
try {
|
|
20554
|
+
await client.query("BEGIN");
|
|
20555
|
+
try {
|
|
20556
|
+
const result = await fn(new PgFactoryStorageOps(client, this.#schemas, client));
|
|
20557
|
+
await client.query("COMMIT");
|
|
20558
|
+
return result;
|
|
20559
|
+
} catch (error) {
|
|
20560
|
+
await client.query("ROLLBACK");
|
|
20561
|
+
throw error;
|
|
20562
|
+
}
|
|
20563
|
+
} finally {
|
|
20564
|
+
client.release();
|
|
20565
|
+
}
|
|
20566
|
+
}
|
|
20567
|
+
async ensureCollections(schemas) {
|
|
20568
|
+
for (const schema of schemas) {
|
|
20569
|
+
await this.#ensureCollection(schema);
|
|
20570
|
+
this.#schemas.set(schema.name, schema);
|
|
20571
|
+
}
|
|
20572
|
+
}
|
|
20573
|
+
async close() {
|
|
20574
|
+
if (this.#ownsPool) {
|
|
20575
|
+
await this.#pool.end();
|
|
20576
|
+
} else {
|
|
20577
|
+
await this.#mastraStorage?.close();
|
|
20578
|
+
}
|
|
20579
|
+
}
|
|
20580
|
+
authDatabase() {
|
|
20581
|
+
return { dialect: "postgres", pool: this.#pool };
|
|
20582
|
+
}
|
|
20583
|
+
/**
|
|
20584
|
+
* Run `fn` while holding a Postgres transaction-scoped advisory lock for
|
|
20585
|
+
* `key`, serializing callers across replicas. The lock releases when the
|
|
20586
|
+
* transaction ends (commit, rollback, or connection loss), so a crashed
|
|
20587
|
+
* replica can never hold it forever.
|
|
20588
|
+
*/
|
|
20589
|
+
async withDistributedLock(key, fn) {
|
|
20590
|
+
const [k1, k2] = hashAdvisoryLockKey(key);
|
|
20591
|
+
const client = await this.#pool.connect();
|
|
20592
|
+
try {
|
|
20593
|
+
await client.query("BEGIN");
|
|
20594
|
+
await client.query("SELECT pg_advisory_xact_lock($1, $2)", [k1, k2]);
|
|
20595
|
+
try {
|
|
20596
|
+
const result = await fn();
|
|
20597
|
+
await client.query("COMMIT");
|
|
20598
|
+
return result;
|
|
20599
|
+
} catch (error) {
|
|
20600
|
+
await client.query("ROLLBACK");
|
|
20601
|
+
throw error;
|
|
20602
|
+
}
|
|
20603
|
+
} finally {
|
|
20604
|
+
client.release();
|
|
20605
|
+
}
|
|
20606
|
+
}
|
|
20607
|
+
#columnDdl(name, spec) {
|
|
20608
|
+
assertIdentifier("column", name);
|
|
20609
|
+
let ddl = `"${name}" ${COLUMN_DDL[spec.type]}`;
|
|
20610
|
+
if (spec.type === "uuid-pk" || spec.primaryKey) ddl += " PRIMARY KEY";
|
|
20611
|
+
if (!spec.nullable && spec.type !== "uuid-pk" && !spec.primaryKey) ddl += " NOT NULL";
|
|
20612
|
+
if (spec.default !== void 0) ddl += ` DEFAULT ${serializeDefault(spec.default)}`;
|
|
20613
|
+
return ddl;
|
|
20614
|
+
}
|
|
20615
|
+
async #ensureCollection(schema) {
|
|
20616
|
+
assertIdentifier("collection", schema.name);
|
|
20617
|
+
primaryKeyOf(schema);
|
|
20618
|
+
const columns = Object.entries(schema.columns).map(([name, spec]) => this.#columnDdl(name, spec));
|
|
20619
|
+
await this.#pool.query(`CREATE TABLE IF NOT EXISTS "${schema.name}" (${columns.join(", ")})`);
|
|
20620
|
+
for (const [name, spec] of Object.entries(schema.columns)) {
|
|
20621
|
+
await this.#pool.query(`ALTER TABLE "${schema.name}" ADD COLUMN IF NOT EXISTS ${this.#columnDdl(name, spec)}`);
|
|
20622
|
+
}
|
|
20623
|
+
for (const index of schema.uniqueIndexes ?? []) {
|
|
20624
|
+
assertIdentifier("index", index.name);
|
|
20625
|
+
index.columns.forEach((column) => assertIdentifier("column", column));
|
|
20626
|
+
let where = "";
|
|
20627
|
+
if (index.whereNotNull) {
|
|
20628
|
+
assertIdentifier("column", index.whereNotNull);
|
|
20629
|
+
where = ` WHERE "${index.whereNotNull}" IS NOT NULL`;
|
|
20630
|
+
} else if (index.whereNull) {
|
|
20631
|
+
assertIdentifier("column", index.whereNull);
|
|
20632
|
+
where = ` WHERE "${index.whereNull}" IS NULL`;
|
|
20633
|
+
}
|
|
20634
|
+
await this.#pool.query(
|
|
20635
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "${index.name}" ON "${schema.name}" (${index.columns.map((c) => `"${c}"`).join(", ")})${where}`
|
|
20636
|
+
);
|
|
20637
|
+
}
|
|
20638
|
+
for (const index of schema.indexes ?? []) {
|
|
20639
|
+
assertIdentifier("index", index.name);
|
|
20640
|
+
index.columns.forEach((column) => assertIdentifier("column", column));
|
|
20641
|
+
await this.#pool.query(
|
|
20642
|
+
`CREATE INDEX IF NOT EXISTS "${index.name}" ON "${schema.name}" (${index.columns.map((c) => `"${c}"`).join(", ")})`
|
|
20643
|
+
);
|
|
20644
|
+
}
|
|
20645
|
+
}
|
|
20646
|
+
};
|
|
20200
20647
|
|
|
20201
20648
|
// src/storage/index.ts
|
|
20202
20649
|
var DEFAULT_MAX_CONNECTIONS = 20;
|
|
@@ -20602,6 +21049,7 @@ exports.NotificationsPG = NotificationsPG;
|
|
|
20602
21049
|
exports.ObservabilityPG = ObservabilityPG;
|
|
20603
21050
|
exports.ObservabilityStoragePostgresVNext = ObservabilityStoragePostgresVNext;
|
|
20604
21051
|
exports.PGVECTOR_PROMPT = PGVECTOR_PROMPT;
|
|
21052
|
+
exports.PgFactoryStorage = PgFactoryStorage;
|
|
20605
21053
|
exports.PgVector = PgVector;
|
|
20606
21054
|
exports.PoolAdapter = PoolAdapter;
|
|
20607
21055
|
exports.PostgresStore = PostgresStore;
|
|
@@ -20615,5 +21063,6 @@ exports.ToolProviderConnectionsPG = ToolProviderConnectionsPG;
|
|
|
20615
21063
|
exports.WorkflowsPG = WorkflowsPG;
|
|
20616
21064
|
exports.WorkspacesPG = WorkspacesPG;
|
|
20617
21065
|
exports.exportSchemas = exportSchemas;
|
|
21066
|
+
exports.hashAdvisoryLockKey = hashAdvisoryLockKey;
|
|
20618
21067
|
//# sourceMappingURL=index.cjs.map
|
|
20619
21068
|
//# sourceMappingURL=index.cjs.map
|