@mastra/pg 0.2.11-alpha.2 → 0.3.0-alpha.4
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +19 -0
- package/dist/_tsup-dts-rollup.d.cts +4 -0
- package/dist/_tsup-dts-rollup.d.ts +4 -0
- package/dist/index.cjs +28 -2
- package/dist/index.js +28 -2
- package/package.json +2 -2
- package/src/storage/index.test.ts +93 -49
- package/src/storage/index.ts +35 -2
- package/src/vector/index.test.ts +20 -2
- package/src/vector/index.ts +6 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/pg@0.
|
|
2
|
+
> @mastra/pg@0.3.0-alpha.4 build /home/runner/work/mastra/mastra/stores/pg
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.4.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 10612ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/pg/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/pg/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 11768ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m46.99 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 1698ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m47.42 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 1697ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 0.3.0-alpha.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 373458f: updated schema for storage config to align with pgvector and added validation for pg connections
|
|
8
|
+
- Updated dependencies [7e92011]
|
|
9
|
+
- @mastra/core@0.9.0-alpha.4
|
|
10
|
+
|
|
11
|
+
## 0.3.0-alpha.3
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- fe3ae4d: Remove \_\_ functions in storage and move to storage proxy to make sure init is called
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [fe3ae4d]
|
|
20
|
+
- @mastra/core@0.9.0-alpha.3
|
|
21
|
+
|
|
3
22
|
## 0.2.11-alpha.2
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -246,6 +246,10 @@ export { PgVector }
|
|
|
246
246
|
export { PgVector as PgVector_alias_1 }
|
|
247
247
|
|
|
248
248
|
declare type PostgresConfig = {
|
|
249
|
+
schemaName?: string;
|
|
250
|
+
/**
|
|
251
|
+
* @deprecated Use `schemaName` instead. Support for `schema` will be removed in a future release.
|
|
252
|
+
*/
|
|
249
253
|
schema?: string;
|
|
250
254
|
} & ({
|
|
251
255
|
host: string;
|
|
@@ -246,6 +246,10 @@ export { PgVector }
|
|
|
246
246
|
export { PgVector as PgVector_alias_1 }
|
|
247
247
|
|
|
248
248
|
declare type PostgresConfig = {
|
|
249
|
+
schemaName?: string;
|
|
250
|
+
/**
|
|
251
|
+
* @deprecated Use `schemaName` instead. Support for `schema` will be removed in a future release.
|
|
252
|
+
*/
|
|
249
253
|
schema?: string;
|
|
250
254
|
} & ({
|
|
251
255
|
host: string;
|
package/dist/index.cjs
CHANGED
|
@@ -305,8 +305,13 @@ var PgVector = class extends vector.MastraVector {
|
|
|
305
305
|
vectorExtensionInstalled = void 0;
|
|
306
306
|
schemaSetupComplete = void 0;
|
|
307
307
|
constructor(config) {
|
|
308
|
-
super();
|
|
309
308
|
const connectionString = typeof config === "string" ? config : config.connectionString;
|
|
309
|
+
if (!connectionString || typeof connectionString !== "string" || connectionString.trim() === "") {
|
|
310
|
+
throw new Error(
|
|
311
|
+
"PgVector: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
super();
|
|
310
315
|
this.schema = typeof config === "string" ? void 0 : config.schemaName;
|
|
311
316
|
const basePool = new pg__default.default.Pool({
|
|
312
317
|
connectionString,
|
|
@@ -793,9 +798,30 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
793
798
|
setupSchemaPromise = null;
|
|
794
799
|
schemaSetupComplete = void 0;
|
|
795
800
|
constructor(config) {
|
|
801
|
+
if ("connectionString" in config) {
|
|
802
|
+
if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
|
|
803
|
+
throw new Error(
|
|
804
|
+
"PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
const required = ["host", "database", "user", "password"];
|
|
809
|
+
for (const key of required) {
|
|
810
|
+
if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
796
817
|
super({ name: "PostgresStore" });
|
|
797
818
|
this.pgp = pgPromise__default.default();
|
|
798
|
-
|
|
819
|
+
if ("schema" in config && config.schema) {
|
|
820
|
+
console.warn(
|
|
821
|
+
'[DEPRECATION NOTICE] The "schema" option in PostgresStore is deprecated. Please use "schemaName" instead. Support for "schema" will be removed in a future release.'
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
this.schema = config.schemaName ?? config.schema;
|
|
799
825
|
this.db = this.pgp(
|
|
800
826
|
`connectionString` in config ? { connectionString: config.connectionString } : {
|
|
801
827
|
host: config.host,
|
package/dist/index.js
CHANGED
|
@@ -297,8 +297,13 @@ var PgVector = class extends MastraVector {
|
|
|
297
297
|
vectorExtensionInstalled = void 0;
|
|
298
298
|
schemaSetupComplete = void 0;
|
|
299
299
|
constructor(config) {
|
|
300
|
-
super();
|
|
301
300
|
const connectionString = typeof config === "string" ? config : config.connectionString;
|
|
301
|
+
if (!connectionString || typeof connectionString !== "string" || connectionString.trim() === "") {
|
|
302
|
+
throw new Error(
|
|
303
|
+
"PgVector: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
super();
|
|
302
307
|
this.schema = typeof config === "string" ? void 0 : config.schemaName;
|
|
303
308
|
const basePool = new pg.Pool({
|
|
304
309
|
connectionString,
|
|
@@ -785,9 +790,30 @@ var PostgresStore = class extends MastraStorage {
|
|
|
785
790
|
setupSchemaPromise = null;
|
|
786
791
|
schemaSetupComplete = void 0;
|
|
787
792
|
constructor(config) {
|
|
793
|
+
if ("connectionString" in config) {
|
|
794
|
+
if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
|
|
795
|
+
throw new Error(
|
|
796
|
+
"PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
const required = ["host", "database", "user", "password"];
|
|
801
|
+
for (const key of required) {
|
|
802
|
+
if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
788
809
|
super({ name: "PostgresStore" });
|
|
789
810
|
this.pgp = pgPromise();
|
|
790
|
-
|
|
811
|
+
if ("schema" in config && config.schema) {
|
|
812
|
+
console.warn(
|
|
813
|
+
'[DEPRECATION NOTICE] The "schema" option in PostgresStore is deprecated. Please use "schemaName" instead. Support for "schema" will be removed in a future release.'
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
this.schema = config.schemaName ?? config.schema;
|
|
791
817
|
this.db = this.pgp(
|
|
792
818
|
`connectionString` in config ? { connectionString: config.connectionString } : {
|
|
793
819
|
host: config.host,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/pg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-alpha.4",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"pg": "^8.13.3",
|
|
25
25
|
"pg-promise": "^11.11.0",
|
|
26
26
|
"xxhash-wasm": "^1.1.0",
|
|
27
|
-
"@mastra/core": "^0.
|
|
27
|
+
"@mastra/core": "^0.9.0-alpha.4"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@microsoft/api-extractor": "^7.52.1",
|
|
@@ -104,21 +104,65 @@ describe('PostgresStore', () => {
|
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
// --- Validation tests ---
|
|
108
|
+
describe('Validation', () => {
|
|
109
|
+
const validConfig = TEST_CONFIG;
|
|
110
|
+
it('throws if connectionString is empty', () => {
|
|
111
|
+
expect(() => new PostgresStore({ connectionString: '' })).toThrow(
|
|
112
|
+
/connectionString must be provided and cannot be empty/,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
it('throws if host is missing or empty', () => {
|
|
116
|
+
expect(() => new PostgresStore({ ...validConfig, host: '' })).toThrow(
|
|
117
|
+
/host must be provided and cannot be empty/,
|
|
118
|
+
);
|
|
119
|
+
const { host, ...rest } = validConfig;
|
|
120
|
+
expect(() => new PostgresStore(rest as any)).toThrow(/host must be provided and cannot be empty/);
|
|
121
|
+
});
|
|
122
|
+
it('throws if user is missing or empty', () => {
|
|
123
|
+
expect(() => new PostgresStore({ ...validConfig, user: '' })).toThrow(
|
|
124
|
+
/user must be provided and cannot be empty/,
|
|
125
|
+
);
|
|
126
|
+
const { user, ...rest } = validConfig;
|
|
127
|
+
expect(() => new PostgresStore(rest as any)).toThrow(/user must be provided and cannot be empty/);
|
|
128
|
+
});
|
|
129
|
+
it('throws if database is missing or empty', () => {
|
|
130
|
+
expect(() => new PostgresStore({ ...validConfig, database: '' })).toThrow(
|
|
131
|
+
/database must be provided and cannot be empty/,
|
|
132
|
+
);
|
|
133
|
+
const { database, ...rest } = validConfig;
|
|
134
|
+
expect(() => new PostgresStore(rest as any)).toThrow(/database must be provided and cannot be empty/);
|
|
135
|
+
});
|
|
136
|
+
it('throws if password is missing or empty', () => {
|
|
137
|
+
expect(() => new PostgresStore({ ...validConfig, password: '' })).toThrow(
|
|
138
|
+
/password must be provided and cannot be empty/,
|
|
139
|
+
);
|
|
140
|
+
const { password, ...rest } = validConfig;
|
|
141
|
+
expect(() => new PostgresStore(rest as any)).toThrow(/password must be provided and cannot be empty/);
|
|
142
|
+
});
|
|
143
|
+
it('does not throw on valid config (host-based)', () => {
|
|
144
|
+
expect(() => new PostgresStore(validConfig)).not.toThrow();
|
|
145
|
+
});
|
|
146
|
+
it('does not throw on non-empty connection string', () => {
|
|
147
|
+
expect(() => new PostgresStore({ connectionString })).not.toThrow();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
107
151
|
describe('Thread Operations', () => {
|
|
108
152
|
it('should create and retrieve a thread', async () => {
|
|
109
153
|
const thread = createSampleThread();
|
|
110
154
|
|
|
111
155
|
// Save thread
|
|
112
|
-
const savedThread = await store.
|
|
156
|
+
const savedThread = await store.saveThread({ thread });
|
|
113
157
|
expect(savedThread).toEqual(thread);
|
|
114
158
|
|
|
115
159
|
// Retrieve thread
|
|
116
|
-
const retrievedThread = await store.
|
|
160
|
+
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
117
161
|
expect(retrievedThread?.title).toEqual(thread.title);
|
|
118
162
|
});
|
|
119
163
|
|
|
120
164
|
it('should return null for non-existent thread', async () => {
|
|
121
|
-
const result = await store.
|
|
165
|
+
const result = await store.getThreadById({ threadId: 'non-existent' });
|
|
122
166
|
expect(result).toBeNull();
|
|
123
167
|
});
|
|
124
168
|
|
|
@@ -126,20 +170,20 @@ describe('PostgresStore', () => {
|
|
|
126
170
|
const thread1 = createSampleThread();
|
|
127
171
|
const thread2 = { ...createSampleThread(), resourceId: thread1.resourceId };
|
|
128
172
|
|
|
129
|
-
await store.
|
|
130
|
-
await store.
|
|
173
|
+
await store.saveThread({ thread: thread1 });
|
|
174
|
+
await store.saveThread({ thread: thread2 });
|
|
131
175
|
|
|
132
|
-
const threads = await store.
|
|
176
|
+
const threads = await store.getThreadsByResourceId({ resourceId: thread1.resourceId });
|
|
133
177
|
expect(threads).toHaveLength(2);
|
|
134
178
|
expect(threads.map(t => t.id)).toEqual(expect.arrayContaining([thread1.id, thread2.id]));
|
|
135
179
|
});
|
|
136
180
|
|
|
137
181
|
it('should update thread title and metadata', async () => {
|
|
138
182
|
const thread = createSampleThread();
|
|
139
|
-
await store.
|
|
183
|
+
await store.saveThread({ thread });
|
|
140
184
|
|
|
141
185
|
const newMetadata = { newKey: 'newValue' };
|
|
142
|
-
const updatedThread = await store.
|
|
186
|
+
const updatedThread = await store.updateThread({
|
|
143
187
|
id: thread.id,
|
|
144
188
|
title: 'Updated Title',
|
|
145
189
|
metadata: newMetadata,
|
|
@@ -152,25 +196,25 @@ describe('PostgresStore', () => {
|
|
|
152
196
|
});
|
|
153
197
|
|
|
154
198
|
// Verify persistence
|
|
155
|
-
const retrievedThread = await store.
|
|
199
|
+
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
156
200
|
expect(retrievedThread).toEqual(updatedThread);
|
|
157
201
|
});
|
|
158
202
|
|
|
159
203
|
it('should delete thread and its messages', async () => {
|
|
160
204
|
const thread = createSampleThread();
|
|
161
|
-
await store.
|
|
205
|
+
await store.saveThread({ thread });
|
|
162
206
|
|
|
163
207
|
// Add some messages
|
|
164
208
|
const messages = [createSampleMessage(thread.id), createSampleMessage(thread.id)];
|
|
165
|
-
await store.
|
|
209
|
+
await store.saveMessages({ messages });
|
|
166
210
|
|
|
167
|
-
await store.
|
|
211
|
+
await store.deleteThread({ threadId: thread.id });
|
|
168
212
|
|
|
169
|
-
const retrievedThread = await store.
|
|
213
|
+
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
170
214
|
expect(retrievedThread).toBeNull();
|
|
171
215
|
|
|
172
216
|
// Verify messages were also deleted
|
|
173
|
-
const retrievedMessages = await store.
|
|
217
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id });
|
|
174
218
|
expect(retrievedMessages).toHaveLength(0);
|
|
175
219
|
});
|
|
176
220
|
});
|
|
@@ -178,28 +222,28 @@ describe('PostgresStore', () => {
|
|
|
178
222
|
describe('Message Operations', () => {
|
|
179
223
|
it('should save and retrieve messages', async () => {
|
|
180
224
|
const thread = createSampleThread();
|
|
181
|
-
await store.
|
|
225
|
+
await store.saveThread({ thread });
|
|
182
226
|
|
|
183
227
|
const messages = [createSampleMessage(thread.id), createSampleMessage(thread.id)];
|
|
184
228
|
|
|
185
229
|
// Save messages
|
|
186
|
-
const savedMessages = await store.
|
|
230
|
+
const savedMessages = await store.saveMessages({ messages });
|
|
187
231
|
expect(savedMessages).toEqual(messages);
|
|
188
232
|
|
|
189
233
|
// Retrieve messages
|
|
190
|
-
const retrievedMessages = await store.
|
|
234
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id });
|
|
191
235
|
expect(retrievedMessages).toHaveLength(2);
|
|
192
236
|
expect(retrievedMessages).toEqual(expect.arrayContaining(messages));
|
|
193
237
|
});
|
|
194
238
|
|
|
195
239
|
it('should handle empty message array', async () => {
|
|
196
|
-
const result = await store.
|
|
240
|
+
const result = await store.saveMessages({ messages: [] });
|
|
197
241
|
expect(result).toEqual([]);
|
|
198
242
|
});
|
|
199
243
|
|
|
200
244
|
it('should maintain message order', async () => {
|
|
201
245
|
const thread = createSampleThread();
|
|
202
|
-
await store.
|
|
246
|
+
await store.saveThread({ thread });
|
|
203
247
|
|
|
204
248
|
const messages = [
|
|
205
249
|
{ ...createSampleMessage(thread.id), content: [{ type: 'text', text: 'First' }] as MessageType['content'] },
|
|
@@ -207,9 +251,9 @@ describe('PostgresStore', () => {
|
|
|
207
251
|
{ ...createSampleMessage(thread.id), content: [{ type: 'text', text: 'Third' }] as MessageType['content'] },
|
|
208
252
|
];
|
|
209
253
|
|
|
210
|
-
await store.
|
|
254
|
+
await store.saveMessages({ messages });
|
|
211
255
|
|
|
212
|
-
const retrievedMessages = await store.
|
|
256
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id });
|
|
213
257
|
expect(retrievedMessages).toHaveLength(3);
|
|
214
258
|
|
|
215
259
|
// Verify order is maintained
|
|
@@ -220,17 +264,17 @@ describe('PostgresStore', () => {
|
|
|
220
264
|
|
|
221
265
|
it('should rollback on error during message save', async () => {
|
|
222
266
|
const thread = createSampleThread();
|
|
223
|
-
await store.
|
|
267
|
+
await store.saveThread({ thread });
|
|
224
268
|
|
|
225
269
|
const messages = [
|
|
226
270
|
createSampleMessage(thread.id),
|
|
227
271
|
{ ...createSampleMessage(thread.id), id: null } as any, // This will cause an error
|
|
228
272
|
];
|
|
229
273
|
|
|
230
|
-
await expect(store.
|
|
274
|
+
await expect(store.saveMessages({ messages })).rejects.toThrow();
|
|
231
275
|
|
|
232
276
|
// Verify no messages were saved
|
|
233
|
-
const savedMessages = await store.
|
|
277
|
+
const savedMessages = await store.getMessages({ threadId: thread.id });
|
|
234
278
|
expect(savedMessages).toHaveLength(0);
|
|
235
279
|
});
|
|
236
280
|
});
|
|
@@ -248,8 +292,8 @@ describe('PostgresStore', () => {
|
|
|
248
292
|
metadata: largeMetadata,
|
|
249
293
|
};
|
|
250
294
|
|
|
251
|
-
await store.
|
|
252
|
-
const retrieved = await store.
|
|
295
|
+
await store.saveThread({ thread: threadWithLargeMetadata });
|
|
296
|
+
const retrieved = await store.getThreadById({ threadId: thread.id });
|
|
253
297
|
|
|
254
298
|
expect(retrieved?.metadata).toEqual(largeMetadata);
|
|
255
299
|
});
|
|
@@ -260,19 +304,19 @@ describe('PostgresStore', () => {
|
|
|
260
304
|
title: 'Special \'quotes\' and "double quotes" and emoji 🎉',
|
|
261
305
|
};
|
|
262
306
|
|
|
263
|
-
await store.
|
|
264
|
-
const retrieved = await store.
|
|
307
|
+
await store.saveThread({ thread });
|
|
308
|
+
const retrieved = await store.getThreadById({ threadId: thread.id });
|
|
265
309
|
|
|
266
310
|
expect(retrieved?.title).toBe(thread.title);
|
|
267
311
|
});
|
|
268
312
|
|
|
269
313
|
it('should handle concurrent thread updates', async () => {
|
|
270
314
|
const thread = createSampleThread();
|
|
271
|
-
await store.
|
|
315
|
+
await store.saveThread({ thread });
|
|
272
316
|
|
|
273
317
|
// Perform multiple updates concurrently
|
|
274
318
|
const updates = Array.from({ length: 5 }, (_, i) =>
|
|
275
|
-
store.
|
|
319
|
+
store.updateThread({
|
|
276
320
|
id: thread.id,
|
|
277
321
|
title: `Update ${i}`,
|
|
278
322
|
metadata: { update: i },
|
|
@@ -282,7 +326,7 @@ describe('PostgresStore', () => {
|
|
|
282
326
|
await expect(Promise.all(updates)).resolves.toBeDefined();
|
|
283
327
|
|
|
284
328
|
// Verify final state
|
|
285
|
-
const finalThread = await store.
|
|
329
|
+
const finalThread = await store.getThreadById({ threadId: thread.id });
|
|
286
330
|
expect(finalThread).toBeDefined();
|
|
287
331
|
});
|
|
288
332
|
});
|
|
@@ -433,7 +477,7 @@ describe('PostgresStore', () => {
|
|
|
433
477
|
await store.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
434
478
|
});
|
|
435
479
|
it('returns empty array when no workflows exist', async () => {
|
|
436
|
-
const { runs, total } = await store.
|
|
480
|
+
const { runs, total } = await store.getWorkflowRuns();
|
|
437
481
|
expect(runs).toEqual([]);
|
|
438
482
|
expect(total).toBe(0);
|
|
439
483
|
});
|
|
@@ -449,7 +493,7 @@ describe('PostgresStore', () => {
|
|
|
449
493
|
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
|
|
450
494
|
await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
|
|
451
495
|
|
|
452
|
-
const { runs, total } = await store.
|
|
496
|
+
const { runs, total } = await store.getWorkflowRuns();
|
|
453
497
|
expect(runs).toHaveLength(2);
|
|
454
498
|
expect(total).toBe(2);
|
|
455
499
|
expect(runs[0]!.workflowName).toBe(workflowName2); // Most recent first
|
|
@@ -471,7 +515,7 @@ describe('PostgresStore', () => {
|
|
|
471
515
|
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
|
|
472
516
|
await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
|
|
473
517
|
|
|
474
|
-
const { runs, total } = await store.
|
|
518
|
+
const { runs, total } = await store.getWorkflowRuns({ workflowName: workflowName1 });
|
|
475
519
|
expect(runs).toHaveLength(1);
|
|
476
520
|
expect(total).toBe(1);
|
|
477
521
|
expect(runs[0]!.workflowName).toBe(workflowName1);
|
|
@@ -522,7 +566,7 @@ describe('PostgresStore', () => {
|
|
|
522
566
|
},
|
|
523
567
|
});
|
|
524
568
|
|
|
525
|
-
const { runs } = await store.
|
|
569
|
+
const { runs } = await store.getWorkflowRuns({
|
|
526
570
|
fromDate: yesterday,
|
|
527
571
|
toDate: now,
|
|
528
572
|
});
|
|
@@ -552,7 +596,7 @@ describe('PostgresStore', () => {
|
|
|
552
596
|
await store.persistWorkflowSnapshot({ workflowName: workflowName3, runId: runId3, snapshot: workflow3 });
|
|
553
597
|
|
|
554
598
|
// Get first page
|
|
555
|
-
const page1 = await store.
|
|
599
|
+
const page1 = await store.getWorkflowRuns({ limit: 2, offset: 0 });
|
|
556
600
|
expect(page1.runs).toHaveLength(2);
|
|
557
601
|
expect(page1.total).toBe(3); // Total count of all records
|
|
558
602
|
expect(page1.runs[0]!.workflowName).toBe(workflowName3);
|
|
@@ -563,7 +607,7 @@ describe('PostgresStore', () => {
|
|
|
563
607
|
expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
|
|
564
608
|
|
|
565
609
|
// Get second page
|
|
566
|
-
const page2 = await store.
|
|
610
|
+
const page2 = await store.getWorkflowRuns({ limit: 2, offset: 2 });
|
|
567
611
|
expect(page2.runs).toHaveLength(1);
|
|
568
612
|
expect(page2.total).toBe(3);
|
|
569
613
|
expect(page2.runs[0]!.workflowName).toBe(workflowName1);
|
|
@@ -662,7 +706,7 @@ describe('PostgresStore', () => {
|
|
|
662
706
|
beforeAll(async () => {
|
|
663
707
|
customSchemaStore = new PostgresStore({
|
|
664
708
|
...TEST_CONFIG,
|
|
665
|
-
|
|
709
|
+
schemaName: customSchema,
|
|
666
710
|
});
|
|
667
711
|
|
|
668
712
|
await customSchemaStore.init();
|
|
@@ -691,10 +735,10 @@ describe('PostgresStore', () => {
|
|
|
691
735
|
it('should create and query tables in custom schema', async () => {
|
|
692
736
|
// Create thread in custom schema
|
|
693
737
|
const thread = createSampleThread();
|
|
694
|
-
await customSchemaStore.
|
|
738
|
+
await customSchemaStore.saveThread({ thread });
|
|
695
739
|
|
|
696
740
|
// Verify thread exists in custom schema
|
|
697
|
-
const retrieved = await customSchemaStore.
|
|
741
|
+
const retrieved = await customSchemaStore.getThreadById({ threadId: thread.id });
|
|
698
742
|
expect(retrieved?.title).toBe(thread.title);
|
|
699
743
|
});
|
|
700
744
|
|
|
@@ -703,19 +747,19 @@ describe('PostgresStore', () => {
|
|
|
703
747
|
const defaultThread = createSampleThread();
|
|
704
748
|
const customThread = createSampleThread();
|
|
705
749
|
|
|
706
|
-
await store.
|
|
707
|
-
await customSchemaStore.
|
|
750
|
+
await store.saveThread({ thread: defaultThread });
|
|
751
|
+
await customSchemaStore.saveThread({ thread: customThread });
|
|
708
752
|
|
|
709
753
|
// Verify threads exist in respective schemas
|
|
710
|
-
const defaultResult = await store.
|
|
711
|
-
const customResult = await customSchemaStore.
|
|
754
|
+
const defaultResult = await store.getThreadById({ threadId: defaultThread.id });
|
|
755
|
+
const customResult = await customSchemaStore.getThreadById({ threadId: customThread.id });
|
|
712
756
|
|
|
713
757
|
expect(defaultResult?.id).toBe(defaultThread.id);
|
|
714
758
|
expect(customResult?.id).toBe(customThread.id);
|
|
715
759
|
|
|
716
760
|
// Verify cross-schema isolation
|
|
717
|
-
const defaultInCustom = await customSchemaStore.
|
|
718
|
-
const customInDefault = await store.
|
|
761
|
+
const defaultInCustom = await customSchemaStore.getThreadById({ threadId: defaultThread.id });
|
|
762
|
+
const customInDefault = await store.getThreadById({ threadId: customThread.id });
|
|
719
763
|
|
|
720
764
|
expect(defaultInCustom).toBeNull();
|
|
721
765
|
expect(customInDefault).toBeNull();
|
|
@@ -844,7 +888,7 @@ describe('PostgresStore', () => {
|
|
|
844
888
|
...TEST_CONFIG,
|
|
845
889
|
user: schemaRestrictedUser,
|
|
846
890
|
password: restrictedPassword,
|
|
847
|
-
|
|
891
|
+
schemaName: testSchema,
|
|
848
892
|
});
|
|
849
893
|
|
|
850
894
|
// Create a fresh connection for verification
|
|
@@ -876,7 +920,7 @@ describe('PostgresStore', () => {
|
|
|
876
920
|
...TEST_CONFIG,
|
|
877
921
|
user: schemaRestrictedUser,
|
|
878
922
|
password: restrictedPassword,
|
|
879
|
-
|
|
923
|
+
schemaName: testSchema,
|
|
880
924
|
});
|
|
881
925
|
|
|
882
926
|
// Create a fresh connection for verification
|
|
@@ -887,7 +931,7 @@ describe('PostgresStore', () => {
|
|
|
887
931
|
await expect(async () => {
|
|
888
932
|
await restrictedDB.init();
|
|
889
933
|
const thread = createSampleThread();
|
|
890
|
-
await restrictedDB.
|
|
934
|
+
await restrictedDB.saveThread({ thread });
|
|
891
935
|
}).rejects.toThrow(
|
|
892
936
|
`Unable to create schema "${testSchema}". This requires CREATE privilege on the database.`,
|
|
893
937
|
);
|
package/src/storage/index.ts
CHANGED
|
@@ -13,7 +13,13 @@ import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
|
13
13
|
import pgPromise from 'pg-promise';
|
|
14
14
|
import type { ISSLConfig } from 'pg-promise/typescript/pg-subset';
|
|
15
15
|
|
|
16
|
-
export type PostgresConfig = {
|
|
16
|
+
export type PostgresConfig = {
|
|
17
|
+
schemaName?: string;
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Use `schemaName` instead. Support for `schema` will be removed in a future release.
|
|
20
|
+
*/
|
|
21
|
+
schema?: string;
|
|
22
|
+
} & (
|
|
17
23
|
| {
|
|
18
24
|
host: string;
|
|
19
25
|
port: number;
|
|
@@ -35,9 +41,36 @@ export class PostgresStore extends MastraStorage {
|
|
|
35
41
|
private schemaSetupComplete: boolean | undefined = undefined;
|
|
36
42
|
|
|
37
43
|
constructor(config: PostgresConfig) {
|
|
44
|
+
// Validation: connectionString or host/database/user/password must not be empty
|
|
45
|
+
if ('connectionString' in config) {
|
|
46
|
+
if (
|
|
47
|
+
!config.connectionString ||
|
|
48
|
+
typeof config.connectionString !== 'string' ||
|
|
49
|
+
config.connectionString.trim() === ''
|
|
50
|
+
) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
const required = ['host', 'database', 'user', 'password'];
|
|
57
|
+
for (const key of required) {
|
|
58
|
+
if (!(key in config) || typeof (config as any)[key] !== 'string' || (config as any)[key].trim() === '') {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
38
65
|
super({ name: 'PostgresStore' });
|
|
39
66
|
this.pgp = pgPromise();
|
|
40
|
-
|
|
67
|
+
// Deprecation notice for schema (old option)
|
|
68
|
+
if ('schema' in config && config.schema) {
|
|
69
|
+
console.warn(
|
|
70
|
+
'[DEPRECATION NOTICE] The "schema" option in PostgresStore is deprecated. Please use "schemaName" instead. Support for "schema" will be removed in a future release.',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
this.schema = config.schemaName ?? config.schema;
|
|
41
74
|
this.db = this.pgp(
|
|
42
75
|
`connectionString` in config
|
|
43
76
|
? { connectionString: config.connectionString }
|
package/src/vector/index.test.ts
CHANGED
|
@@ -21,6 +21,24 @@ describe('PgVector', () => {
|
|
|
21
21
|
await vectorDB.disconnect();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// --- Validation tests ---
|
|
25
|
+
describe('Validation', () => {
|
|
26
|
+
it('throws if connectionString is empty (string)', () => {
|
|
27
|
+
expect(() => new PgVector('')).toThrow(/connectionString must be provided and cannot be empty/);
|
|
28
|
+
});
|
|
29
|
+
it('throws if connectionString is empty (object)', () => {
|
|
30
|
+
expect(() => new PgVector({ connectionString: '' })).toThrow(
|
|
31
|
+
/connectionString must be provided and cannot be empty/,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
it('does not throw on non-empty connection string (string)', () => {
|
|
35
|
+
expect(() => new PgVector(connectionString)).not.toThrow();
|
|
36
|
+
});
|
|
37
|
+
it('does not throw on non-empty connection string (object)', () => {
|
|
38
|
+
expect(() => new PgVector({ connectionString })).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
24
42
|
// Index Management Tests
|
|
25
43
|
describe('Index Management', () => {
|
|
26
44
|
describe('createIndex', () => {
|
|
@@ -329,8 +347,8 @@ describe('PgVector', () => {
|
|
|
329
347
|
expect(results[0]?.vector).toEqual(newVector);
|
|
330
348
|
});
|
|
331
349
|
|
|
332
|
-
it('should throw exception when no updates are given', () => {
|
|
333
|
-
expect(vectorDB.updateIndexById(testIndexName, 'id', {})).rejects.toThrow('No updates provided');
|
|
350
|
+
it('should throw exception when no updates are given', async () => {
|
|
351
|
+
await expect(vectorDB.updateIndexById(testIndexName, 'id', {})).rejects.toThrow('No updates provided');
|
|
334
352
|
});
|
|
335
353
|
});
|
|
336
354
|
|
package/src/vector/index.ts
CHANGED
|
@@ -73,9 +73,14 @@ export class PgVector extends MastraVector {
|
|
|
73
73
|
constructor(connectionString: string);
|
|
74
74
|
constructor(config: { connectionString: string; schemaName?: string });
|
|
75
75
|
constructor(config: string | { connectionString: string; schemaName?: string }) {
|
|
76
|
+
const connectionString = typeof config === 'string' ? config : config.connectionString;
|
|
77
|
+
if (!connectionString || typeof connectionString !== 'string' || connectionString.trim() === '') {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'PgVector: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
76
82
|
super();
|
|
77
83
|
|
|
78
|
-
const connectionString = typeof config === 'string' ? config : config.connectionString;
|
|
79
84
|
this.schema = typeof config === 'string' ? undefined : config.schemaName;
|
|
80
85
|
|
|
81
86
|
const basePool = new pg.Pool({
|