@mcp-abap-adt/pg-vector-rag 26.0.0 → 28.0.0
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/README.md +27 -5
- package/dist/__tests__/catalog.test.d.ts +2 -0
- package/dist/__tests__/catalog.test.d.ts.map +1 -0
- package/dist/__tests__/catalog.test.js +250 -0
- package/dist/__tests__/catalog.test.js.map +1 -0
- package/dist/__tests__/connection.test.js +39 -11
- package/dist/__tests__/connection.test.js.map +1 -1
- package/dist/__tests__/credential.test.d.ts +2 -0
- package/dist/__tests__/credential.test.d.ts.map +1 -0
- package/dist/__tests__/credential.test.js +72 -0
- package/dist/__tests__/credential.test.js.map +1 -0
- package/dist/__tests__/fake-pg.d.ts +21 -0
- package/dist/__tests__/fake-pg.d.ts.map +1 -0
- package/dist/__tests__/fake-pg.js +100 -0
- package/dist/__tests__/fake-pg.js.map +1 -0
- package/dist/__tests__/pg-vector-rag-provider.test.js +10 -11
- package/dist/__tests__/pg-vector-rag-provider.test.js.map +1 -1
- package/dist/__tests__/pg-vector-rag.test.js +63 -0
- package/dist/__tests__/pg-vector-rag.test.js.map +1 -1
- package/dist/__tests__/rag-filter-conformance.test.d.ts +2 -0
- package/dist/__tests__/rag-filter-conformance.test.d.ts.map +1 -0
- package/dist/__tests__/rag-filter-conformance.test.js +17 -0
- package/dist/__tests__/rag-filter-conformance.test.js.map +1 -0
- package/dist/__tests__/storing-pg.d.ts +18 -0
- package/dist/__tests__/storing-pg.d.ts.map +1 -0
- package/dist/__tests__/storing-pg.js +77 -0
- package/dist/__tests__/storing-pg.js.map +1 -0
- package/dist/catalog.d.ts +19 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +42 -0
- package/dist/catalog.js.map +1 -0
- package/dist/connection.d.ts +27 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +28 -4
- package/dist/connection.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pg-vector-rag-provider.d.ts +45 -14
- package/dist/pg-vector-rag-provider.d.ts.map +1 -1
- package/dist/pg-vector-rag-provider.js +205 -23
- package/dist/pg-vector-rag-provider.js.map +1 -1
- package/dist/pg-vector-rag.d.ts +2 -1
- package/dist/pg-vector-rag.d.ts.map +1 -1
- package/dist/pg-vector-rag.js +57 -18
- package/dist/pg-vector-rag.js.map +1 -1
- package/dist/schema.d.ts +3 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -2
- package/dist/schema.js.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -23,16 +23,38 @@ npm install @mcp-abap-adt/pg-vector-rag pg
|
|
|
23
23
|
## Minimal config
|
|
24
24
|
|
|
25
25
|
```yaml
|
|
26
|
+
# the address only; the login is a credential
|
|
26
27
|
rag:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
store:
|
|
29
|
+
type: pg-vector
|
|
30
|
+
connectionString: postgres://host:5432/mydb
|
|
31
|
+
collectionName: llm_agent_docs
|
|
32
|
+
dimension: 1536
|
|
33
|
+
autoCreateSchema: true
|
|
34
|
+
credentialRef: RAG_PG # the llm-agent binary reads RAG_PG_USER and RAG_PG_PASSWORD
|
|
32
35
|
```
|
|
33
36
|
|
|
37
|
+
In code: `new PgVectorRag({ connectionString: 'postgres://host:5432/mydb', credential: staticLogin(user, password), … })`
|
|
38
|
+
— a connection string carrying `user:pass` is refused at construction.
|
|
39
|
+
|
|
34
40
|
See the monorepo root README for full configuration surface.
|
|
35
41
|
|
|
42
|
+
## Catalog and the rights it needs
|
|
43
|
+
|
|
44
|
+
`PgVectorRagProvider` keeps one record per collection in a catalog table of its own
|
|
45
|
+
(`rag_collection_catalog` by default, `catalogTable` to rename it). `createCollection` creates the
|
|
46
|
+
collection's table and then writes its record; `deleteCollection` deletes the record first, then
|
|
47
|
+
drops the table; `describeCollections` reads the catalog back. The account the connection uses needs:
|
|
48
|
+
|
|
49
|
+
| with `autoCreateSchema` | rights |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `true` (default) | `CREATE` on the schema (the catalog table on first use, one table per collection); `SELECT`, `INSERT`, `DELETE` on the catalog; ownership of the collection tables it creates (for `DROP TABLE`); the `vector` extension installed, or the right to create it |
|
|
52
|
+
| `false` | no creating DDL is issued: the operator creates the catalog with `createCatalogTableSql(table)` (exported) and each collection table. The account needs `SELECT`, `INSERT`, `DELETE` on the catalog, read and write on the collection tables, and ownership of them — the flag governs creation only, and `deleteCollection` still drops the table |
|
|
53
|
+
|
|
54
|
+
Without those rights `createCollection` fails where it used to succeed. A collection created before
|
|
55
|
+
this release has a table and no record: take it over once with
|
|
56
|
+
`createCollection(..., { adoptExisting: true })`.
|
|
57
|
+
|
|
36
58
|
## License
|
|
37
59
|
|
|
38
60
|
**GNU Lesser General Public License v3.0 only** (`LGPL-3.0-only`) — see
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/catalog.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { CatalogRecordDeleteError } from '@mcp-abap-adt/llm-agent';
|
|
4
|
+
import { PgVectorRagProvider } from '../pg-vector-rag-provider.js';
|
|
5
|
+
import { fakePg } from './fake-pg.js';
|
|
6
|
+
const CATALOG = 'rag_collection_catalog';
|
|
7
|
+
const STORE = 'my_notes_a1b2c3d4e5f6';
|
|
8
|
+
const embedder = { embed: async () => ({ vector: [0, 0, 0] }) };
|
|
9
|
+
const createOpts = {
|
|
10
|
+
scope: 'user',
|
|
11
|
+
userId: 'u-1',
|
|
12
|
+
collectionName: 'my notes',
|
|
13
|
+
attributes: { role: 'analyst', level: 2 },
|
|
14
|
+
};
|
|
15
|
+
const row = (over = {}) => ({
|
|
16
|
+
storeName: STORE,
|
|
17
|
+
name: 'my notes',
|
|
18
|
+
scope: 'global',
|
|
19
|
+
userId: null,
|
|
20
|
+
sessionId: null,
|
|
21
|
+
attributesJson: null,
|
|
22
|
+
...over,
|
|
23
|
+
});
|
|
24
|
+
function providerOn(fake, autoCreateSchema = true) {
|
|
25
|
+
return new PgVectorRagProvider({
|
|
26
|
+
name: 'pg',
|
|
27
|
+
embedder,
|
|
28
|
+
connection: { host: 'h', collectionName: '__unused' },
|
|
29
|
+
defaultDimension: 3,
|
|
30
|
+
autoCreateSchema,
|
|
31
|
+
clientFactory: () => fake.client,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const at = (fake, re) => fake.statements.findIndex((s) => re.test(s));
|
|
35
|
+
const storeCreate = new RegExp(`^CREATE TABLE "${STORE}"`);
|
|
36
|
+
const recordInsert = new RegExp(`^INSERT INTO "${CATALOG}"`);
|
|
37
|
+
describe('pg catalog: createCollection', () => {
|
|
38
|
+
it('creates the store with a statement that fails if it exists, and records it last', async () => {
|
|
39
|
+
const fake = fakePg();
|
|
40
|
+
const res = await providerOn(fake).createCollection(STORE, createOpts);
|
|
41
|
+
assert.equal(res.ok, true);
|
|
42
|
+
const createdAt = at(fake, storeCreate);
|
|
43
|
+
assert.ok(createdAt >= 0 && at(fake, recordInsert) > createdAt, 'store first, record last');
|
|
44
|
+
assert.equal(at(fake, new RegExp(`CREATE TABLE IF NOT EXISTS "${STORE}"`)), -1);
|
|
45
|
+
const described = await providerOn(fake).describeCollections();
|
|
46
|
+
assert.ok(described.ok);
|
|
47
|
+
assert.deepEqual(described.value, {
|
|
48
|
+
records: [
|
|
49
|
+
{
|
|
50
|
+
scope: 'user',
|
|
51
|
+
userId: 'u-1',
|
|
52
|
+
storeName: STORE,
|
|
53
|
+
name: 'my notes',
|
|
54
|
+
attributes: { role: 'analyst', level: 2 },
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
rejected: [],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('records the store name as the logical name when none is given', async () => {
|
|
61
|
+
const fake = fakePg();
|
|
62
|
+
assert.ok((await providerOn(fake).createCollection(STORE, { scope: 'global' })).ok);
|
|
63
|
+
assert.equal(fake.records.get(STORE)?.name, STORE);
|
|
64
|
+
});
|
|
65
|
+
it('refuses a collection whose record exists, and touches no store', async () => {
|
|
66
|
+
const fake = fakePg();
|
|
67
|
+
fake.tables.add(CATALOG);
|
|
68
|
+
fake.tables.add(STORE);
|
|
69
|
+
fake.records.set(STORE, row());
|
|
70
|
+
const res = await providerOn(fake).createCollection(STORE, createOpts);
|
|
71
|
+
assert.equal(!res.ok && res.error.code, 'RAG_DUPLICATE_COLLECTION');
|
|
72
|
+
assert.equal(at(fake, storeCreate), -1);
|
|
73
|
+
});
|
|
74
|
+
it('refuses a store that exists without a record, naming it', async () => {
|
|
75
|
+
const fake = fakePg();
|
|
76
|
+
fake.tables.add(STORE);
|
|
77
|
+
const res = await providerOn(fake).createCollection(STORE, createOpts);
|
|
78
|
+
assert.equal(!res.ok && res.error.code, 'RAG_ORPHAN_STORE');
|
|
79
|
+
assert.match(!res.ok ? res.error.message : '', new RegExp(STORE));
|
|
80
|
+
assert.equal(at(fake, recordInsert), -1, 'nothing recorded');
|
|
81
|
+
assert.ok(fake.tables.has(STORE), 'and nothing removed');
|
|
82
|
+
});
|
|
83
|
+
it('takes an orphan over with adoptExisting, creating nothing', async () => {
|
|
84
|
+
const fake = fakePg();
|
|
85
|
+
fake.tables.add(STORE);
|
|
86
|
+
const res = await providerOn(fake).createCollection(STORE, {
|
|
87
|
+
...createOpts,
|
|
88
|
+
adoptExisting: true,
|
|
89
|
+
});
|
|
90
|
+
assert.equal(res.ok, true);
|
|
91
|
+
assert.equal(at(fake, storeCreate), -1);
|
|
92
|
+
assert.ok(fake.records.has(STORE));
|
|
93
|
+
});
|
|
94
|
+
it('refuses adoptExisting for a store that is not there, with the backend error', async () => {
|
|
95
|
+
const fake = fakePg();
|
|
96
|
+
const res = await providerOn(fake).createCollection(STORE, {
|
|
97
|
+
...createOpts,
|
|
98
|
+
adoptExisting: true,
|
|
99
|
+
});
|
|
100
|
+
assert.equal(!res.ok && res.error.code, 'RAG_CREATE_ERROR');
|
|
101
|
+
assert.match(!res.ok ? res.error.message : '', /does not exist/);
|
|
102
|
+
assert.ok(!fake.tables.has(STORE) && !fake.records.has(STORE), 'adoption creates nothing');
|
|
103
|
+
});
|
|
104
|
+
it('leaves the store in place, named, when the record cannot be written', async () => {
|
|
105
|
+
const fake = fakePg();
|
|
106
|
+
fake.failOn = recordInsert;
|
|
107
|
+
const res = await providerOn(fake).createCollection(STORE, createOpts);
|
|
108
|
+
assert.equal(!res.ok && res.error.code, 'RAG_ORPHAN_STORE');
|
|
109
|
+
assert.match(!res.ok ? res.error.message : '', new RegExp(STORE));
|
|
110
|
+
assert.ok(fake.tables.has(STORE), 'never removed: another registry may already adopt it');
|
|
111
|
+
assert.equal(at(fake, /^DROP TABLE/), -1);
|
|
112
|
+
});
|
|
113
|
+
it('loses cleanly to a registry that recorded the same store first', async () => {
|
|
114
|
+
const fake = fakePg();
|
|
115
|
+
fake.beforeInsertRecord = () => {
|
|
116
|
+
fake.records.set(STORE, row({ scope: 'user', userId: 'u-1' }));
|
|
117
|
+
};
|
|
118
|
+
const res = await providerOn(fake).createCollection(STORE, createOpts);
|
|
119
|
+
assert.equal(!res.ok && res.error.code, 'RAG_DUPLICATE_COLLECTION');
|
|
120
|
+
assert.ok(fake.tables.has(STORE), "the winner's record points at it");
|
|
121
|
+
});
|
|
122
|
+
it('refuses an owner without its key, and non-JSON attributes, before any statement', async () => {
|
|
123
|
+
const fake = fakePg();
|
|
124
|
+
const provider = providerOn(fake);
|
|
125
|
+
const noKey = await provider.createCollection(STORE, {
|
|
126
|
+
scope: 'user',
|
|
127
|
+
});
|
|
128
|
+
assert.equal(!noKey.ok && noKey.error.code, 'RAG_INVALID_OWNER');
|
|
129
|
+
const nan = await provider.createCollection(STORE, {
|
|
130
|
+
scope: 'global',
|
|
131
|
+
attributes: Number.NaN,
|
|
132
|
+
});
|
|
133
|
+
assert.equal(!nan.ok && nan.error.code, 'RAG_INVALID_ATTRIBUTES');
|
|
134
|
+
assert.deepEqual(fake.statements, []);
|
|
135
|
+
});
|
|
136
|
+
it('refuses its own catalog table as a collection', async () => {
|
|
137
|
+
const fake = fakePg();
|
|
138
|
+
const res = await providerOn(fake).createCollection(CATALOG, {
|
|
139
|
+
scope: 'global',
|
|
140
|
+
});
|
|
141
|
+
assert.equal(res.ok, false);
|
|
142
|
+
assert.deepEqual(fake.statements, []);
|
|
143
|
+
});
|
|
144
|
+
it('with autoCreateSchema false issues no DDL, requires the store, and records it', async () => {
|
|
145
|
+
const fake = fakePg();
|
|
146
|
+
fake.tables.add(CATALOG);
|
|
147
|
+
const missing = await providerOn(fake, false).createCollection(STORE, createOpts);
|
|
148
|
+
assert.equal(!missing.ok && missing.error.code, 'RAG_CREATE_ERROR');
|
|
149
|
+
fake.tables.add(STORE);
|
|
150
|
+
const res = await providerOn(fake, false).createCollection(STORE, createOpts);
|
|
151
|
+
assert.equal(res.ok, true);
|
|
152
|
+
assert.equal(at(fake, /^CREATE /), -1, 'the operator makes tables in this mode');
|
|
153
|
+
assert.ok(fake.records.has(STORE));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('pg catalog: describeCollections', () => {
|
|
157
|
+
it('returns nothing, and creates nothing, when no catalog exists yet', async () => {
|
|
158
|
+
const fake = fakePg();
|
|
159
|
+
const res = await providerOn(fake).describeCollections();
|
|
160
|
+
assert.deepEqual(res, { ok: true, value: { records: [], rejected: [] } });
|
|
161
|
+
assert.equal(at(fake, /^CREATE /), -1);
|
|
162
|
+
});
|
|
163
|
+
it('reports malformed rows instead of returning them', async () => {
|
|
164
|
+
const fake = fakePg();
|
|
165
|
+
fake.tables.add(CATALOG);
|
|
166
|
+
fake.records.set('good_1', row({ storeName: 'good_1', name: 'good' }));
|
|
167
|
+
fake.records.set('no_user_1', row({ storeName: 'no_user_1', scope: 'user' }));
|
|
168
|
+
fake.records.set('team_1', row({ storeName: 'team_1', scope: 'team' }));
|
|
169
|
+
fake.records.set('bad_json_1', row({ storeName: 'bad_json_1', attributesJson: '{' }));
|
|
170
|
+
fake.records.set('no_name_1', row({ storeName: 'no_name_1', name: '' }));
|
|
171
|
+
const res = await providerOn(fake).describeCollections();
|
|
172
|
+
assert.ok(res.ok);
|
|
173
|
+
assert.deepEqual(res.value.records, [
|
|
174
|
+
{ scope: 'global', storeName: 'good_1', name: 'good' },
|
|
175
|
+
]);
|
|
176
|
+
assert.deepEqual(res.value.rejected.map((r) => r.storeName), ['no_user_1', 'team_1', 'bad_json_1', 'no_name_1']);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('pg catalog: openCollection', () => {
|
|
180
|
+
const record = {
|
|
181
|
+
storeName: STORE,
|
|
182
|
+
name: 'my notes',
|
|
183
|
+
scope: 'global',
|
|
184
|
+
};
|
|
185
|
+
it('builds handles without a statement, and a write to a missing store fails instead of creating it', async () => {
|
|
186
|
+
const fake = fakePg();
|
|
187
|
+
const opened = await providerOn(fake).openCollection(record);
|
|
188
|
+
assert.ok(opened.ok);
|
|
189
|
+
assert.deepEqual(fake.statements, [], 'opening issues nothing');
|
|
190
|
+
const written = await opened.value.editor.upsert('hello', { id: 'r1' });
|
|
191
|
+
assert.equal(written.ok, false);
|
|
192
|
+
assert.ok(!fake.tables.has(STORE), 'no handle creates its store');
|
|
193
|
+
assert.equal(at(fake, /^CREATE /), -1);
|
|
194
|
+
});
|
|
195
|
+
it('refuses a record without its owner key', async () => {
|
|
196
|
+
const fake = fakePg();
|
|
197
|
+
const res = await providerOn(fake).openCollection({
|
|
198
|
+
storeName: STORE,
|
|
199
|
+
name: 'n',
|
|
200
|
+
scope: 'user',
|
|
201
|
+
});
|
|
202
|
+
assert.equal(!res.ok && res.error.code, 'RAG_INVALID_OWNER');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('pg catalog: deleteCollection', () => {
|
|
206
|
+
const seeded = () => {
|
|
207
|
+
const fake = fakePg();
|
|
208
|
+
fake.tables.add(CATALOG);
|
|
209
|
+
fake.tables.add(STORE);
|
|
210
|
+
fake.records.set(STORE, row());
|
|
211
|
+
return fake;
|
|
212
|
+
};
|
|
213
|
+
it('deletes the record before the data', async () => {
|
|
214
|
+
const fake = seeded();
|
|
215
|
+
const res = await providerOn(fake).deleteCollection(STORE);
|
|
216
|
+
assert.equal(res.ok, true);
|
|
217
|
+
const recordAt = at(fake, new RegExp(`^DELETE FROM "${CATALOG}"`));
|
|
218
|
+
const dataAt = at(fake, new RegExp(`^DROP TABLE IF EXISTS "${STORE}"`));
|
|
219
|
+
assert.ok(recordAt >= 0 && dataAt > recordAt);
|
|
220
|
+
assert.ok(!fake.records.has(STORE) && !fake.tables.has(STORE));
|
|
221
|
+
});
|
|
222
|
+
it('leaves record and data alone when the record cannot be deleted', async () => {
|
|
223
|
+
const fake = seeded();
|
|
224
|
+
fake.failOn = new RegExp(`^DELETE FROM "${CATALOG}"`);
|
|
225
|
+
const res = await providerOn(fake).deleteCollection(STORE);
|
|
226
|
+
assert.ok(!res.ok && res.error instanceof CatalogRecordDeleteError);
|
|
227
|
+
assert.equal(at(fake, /^DROP TABLE/), -1);
|
|
228
|
+
assert.ok(fake.records.has(STORE) && fake.tables.has(STORE));
|
|
229
|
+
});
|
|
230
|
+
it('reports a data failure after the record is gone as a plain delete error', async () => {
|
|
231
|
+
const fake = seeded();
|
|
232
|
+
fake.failOn = /^DROP TABLE/;
|
|
233
|
+
const res = await providerOn(fake).deleteCollection(STORE);
|
|
234
|
+
assert.ok(!res.ok && !(res.error instanceof CatalogRecordDeleteError));
|
|
235
|
+
assert.equal(!res.ok && res.error.code, 'RAG_DELETE_ERROR');
|
|
236
|
+
assert.ok(!fake.records.has(STORE));
|
|
237
|
+
});
|
|
238
|
+
it('still drops a store that predates the catalog', async () => {
|
|
239
|
+
const fake = fakePg();
|
|
240
|
+
fake.tables.add(STORE);
|
|
241
|
+
assert.equal((await providerOn(fake).deleteCollection(STORE)).ok, true);
|
|
242
|
+
assert.ok(!fake.tables.has(STORE));
|
|
243
|
+
});
|
|
244
|
+
it('refuses to drop its own catalog table', async () => {
|
|
245
|
+
const fake = seeded();
|
|
246
|
+
assert.equal((await providerOn(fake).deleteCollection(CATALOG)).ok, false);
|
|
247
|
+
assert.ok(fake.tables.has(CATALOG));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
//# sourceMappingURL=catalog.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.test.js","sourceRoot":"","sources":["../../src/__tests__/catalog.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { describe, it } from 'node:test';
|
|
4
|
+
import { staticLogin } from '@mcp-abap-adt/llm-agent';
|
|
3
5
|
import { resolvePgConnectArgs } from '../connection.js';
|
|
6
|
+
// pg's own resolution of a pool config into what a client connects with.
|
|
7
|
+
const ConnectionParameters = createRequire(import.meta.url)('pg/lib/connection-parameters.js');
|
|
4
8
|
describe('resolvePgConnectArgs', () => {
|
|
5
|
-
it('parses postgres:// URL', () => {
|
|
6
|
-
const a = resolvePgConnectArgs({
|
|
7
|
-
connectionString: 'postgres://
|
|
9
|
+
it('parses postgres:// URL carrying the address only', async () => {
|
|
10
|
+
const a = await resolvePgConnectArgs({
|
|
11
|
+
connectionString: 'postgres://host:5432/db',
|
|
8
12
|
collectionName: 't',
|
|
9
13
|
});
|
|
10
|
-
assert.equal(a.
|
|
14
|
+
assert.equal(a.host, 'host');
|
|
15
|
+
assert.equal(a.port, 5432);
|
|
16
|
+
assert.equal(a.database, 'db');
|
|
11
17
|
assert.equal(a.max, 10);
|
|
12
18
|
});
|
|
13
|
-
it('uses explicit fields', () => {
|
|
14
|
-
const a = resolvePgConnectArgs({
|
|
19
|
+
it('uses explicit fields', async () => {
|
|
20
|
+
const a = await resolvePgConnectArgs({
|
|
15
21
|
host: 'h',
|
|
16
22
|
port: 6543,
|
|
17
|
-
|
|
18
|
-
password: 'p',
|
|
23
|
+
credential: staticLogin('u', 'p'),
|
|
19
24
|
database: 'db',
|
|
20
25
|
poolMax: 3,
|
|
21
26
|
collectionName: 't',
|
|
@@ -23,12 +28,35 @@ describe('resolvePgConnectArgs', () => {
|
|
|
23
28
|
assert.equal(a.host, 'h');
|
|
24
29
|
assert.equal(a.port, 6543);
|
|
25
30
|
assert.equal(a.user, 'u');
|
|
26
|
-
assert.equal(a.password, '
|
|
31
|
+
assert.equal(typeof a.password, 'function');
|
|
32
|
+
assert.equal(await a.password(), 'p');
|
|
27
33
|
assert.equal(a.database, 'db');
|
|
28
34
|
assert.equal(a.max, 3);
|
|
29
35
|
});
|
|
30
|
-
it('rejects missing host and connectionString', () => {
|
|
31
|
-
assert.
|
|
36
|
+
it('rejects missing host and connectionString', async () => {
|
|
37
|
+
await assert.rejects(() => resolvePgConnectArgs({
|
|
38
|
+
credential: staticLogin('u', 'p'),
|
|
39
|
+
collectionName: 't',
|
|
40
|
+
}), /host|connectionString/i);
|
|
41
|
+
});
|
|
42
|
+
it('a connectionString keeps the credential: pg must connect as the principal', async () => {
|
|
43
|
+
// pg merges the parsed connection string OVER the config, and parsing a
|
|
44
|
+
// URL without userinfo yields user "" and password "" — so passing the
|
|
45
|
+
// string beside user/password threw the credential away (found against a
|
|
46
|
+
// real SCRAM Postgres: "client password must be a string").
|
|
47
|
+
const a = await resolvePgConnectArgs({
|
|
48
|
+
connectionString: 'postgres://db.example:15432/rag?sslmode=no-verify',
|
|
49
|
+
credential: staticLogin('ragapp', 'ragpass'),
|
|
50
|
+
collectionName: 't',
|
|
51
|
+
});
|
|
52
|
+
const p = new ConnectionParameters(a);
|
|
53
|
+
assert.equal(p.user, 'ragapp');
|
|
54
|
+
assert.equal(typeof p.password, 'function');
|
|
55
|
+
assert.equal(await p.password(), 'ragpass');
|
|
56
|
+
assert.equal(p.host, 'db.example');
|
|
57
|
+
assert.equal(p.port, 15432);
|
|
58
|
+
assert.equal(p.database, 'rag');
|
|
59
|
+
assert.deepEqual(p.ssl, { rejectUnauthorized: false });
|
|
32
60
|
});
|
|
33
61
|
});
|
|
34
62
|
//# sourceMappingURL=connection.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.test.js","sourceRoot":"","sources":["../../src/__tests__/connection.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"connection.test.js","sourceRoot":"","sources":["../../src/__tests__/connection.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/credential.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { staticLogin } from '@mcp-abap-adt/llm-agent';
|
|
4
|
+
import { resolvePgConnectArgs } from '../connection.js';
|
|
5
|
+
describe('resolvePgConnectArgs', () => {
|
|
6
|
+
it('takes the identity and the secret from the credential', async () => {
|
|
7
|
+
const args = await resolvePgConnectArgs({
|
|
8
|
+
host: 'h',
|
|
9
|
+
collectionName: 't',
|
|
10
|
+
credential: staticLogin('cred_user', 'cred_pw'),
|
|
11
|
+
});
|
|
12
|
+
assert.equal(args.user, 'cred_user');
|
|
13
|
+
// The password is a function handed to `pg`, not a resolved string —
|
|
14
|
+
// see the "hands the pool a password FUNCTION" test below for why.
|
|
15
|
+
assert.equal(typeof args.password, 'function');
|
|
16
|
+
assert.equal(await args.password(), 'cred_pw');
|
|
17
|
+
});
|
|
18
|
+
it('accepts a connection string that carries the ADDRESS only', async () => {
|
|
19
|
+
const args = await resolvePgConnectArgs({
|
|
20
|
+
connectionString: 'postgres://h:5432/db',
|
|
21
|
+
collectionName: 't',
|
|
22
|
+
credential: staticLogin('cred_user', 'cred_pw'),
|
|
23
|
+
});
|
|
24
|
+
assert.equal(args.user, 'cred_user');
|
|
25
|
+
});
|
|
26
|
+
it('REFUSES a connection string carrying credentials, naming the fix', async () => {
|
|
27
|
+
await assert.rejects(() => resolvePgConnectArgs({
|
|
28
|
+
connectionString: 'postgres://u:pw@h/db',
|
|
29
|
+
collectionName: 't',
|
|
30
|
+
credential: staticLogin('cred_user', 'cred_pw'),
|
|
31
|
+
}), /staticLogin/, 'silently ignoring the embedded password is the failure this replaces');
|
|
32
|
+
});
|
|
33
|
+
it('hands the pool a password FUNCTION so a rotating credential is asked per connection, not resolved once', async () => {
|
|
34
|
+
// A `PgVectorRag`'s pool reuses one config object for every physical
|
|
35
|
+
// connection it opens over its lifetime. If `resolvePgConnectArgs`
|
|
36
|
+
// resolved `credential.secret()` to a string here, that string would be
|
|
37
|
+
// frozen into the pool for as long as it lives — a rotating credential
|
|
38
|
+
// would authenticate the first connection and then fail every
|
|
39
|
+
// connection opened after the original secret's TTL. `pg` accepts the
|
|
40
|
+
// secret as a function precisely so it can ask fresh per connection
|
|
41
|
+
// (`@types/pg`: `password?: string | (() => string | Promise<string>)`;
|
|
42
|
+
// the installed runtime checks `typeof this.password === 'function'`
|
|
43
|
+
// per client in `lib/client.js`), so the fix is to hand over the
|
|
44
|
+
// function itself, unresolved.
|
|
45
|
+
let calls = 0;
|
|
46
|
+
const credential = {
|
|
47
|
+
kind: 'secret-login',
|
|
48
|
+
principal: 'cred_user',
|
|
49
|
+
secret: async () => {
|
|
50
|
+
calls++;
|
|
51
|
+
return `secret-${calls}`;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const args = await resolvePgConnectArgs({
|
|
55
|
+
host: 'h',
|
|
56
|
+
collectionName: 't',
|
|
57
|
+
credential,
|
|
58
|
+
});
|
|
59
|
+
assert.equal(typeof args.password, 'function', 'the pool must receive the secret as a function, not a resolved string — ' +
|
|
60
|
+
'a mutation that resolves it eagerly turns this into a string and fails here');
|
|
61
|
+
assert.equal(calls, 0, 'resolvePgConnectArgs must not itself resolve the secret');
|
|
62
|
+
const passwordFn = args.password;
|
|
63
|
+
const firstConnection = await passwordFn();
|
|
64
|
+
const secondConnection = await passwordFn();
|
|
65
|
+
assert.equal(firstConnection, 'secret-1');
|
|
66
|
+
assert.equal(secondConnection, 'secret-2');
|
|
67
|
+
assert.notEqual(firstConnection, secondConnection, 'two physical connections must see two different passwords when the credential rotates');
|
|
68
|
+
// The identity, unlike the secret, is resolved once — it does not rotate.
|
|
69
|
+
assert.equal(args.user, 'cred_user');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=credential.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.test.js","sourceRoot":"","sources":["../../src/__tests__/credential.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PgClient } from '../pg-vector-rag.js';
|
|
2
|
+
export type FakeCatalogRow = {
|
|
3
|
+
storeName: string;
|
|
4
|
+
name: unknown;
|
|
5
|
+
scope: unknown;
|
|
6
|
+
userId: unknown;
|
|
7
|
+
sessionId: unknown;
|
|
8
|
+
attributesJson: unknown;
|
|
9
|
+
};
|
|
10
|
+
export type FakePg = {
|
|
11
|
+
readonly client: PgClient;
|
|
12
|
+
readonly tables: Set<string>;
|
|
13
|
+
readonly records: Map<string, FakeCatalogRow>;
|
|
14
|
+
readonly statements: string[];
|
|
15
|
+
/** A statement matching this throws, as a refusing server would. */
|
|
16
|
+
failOn?: RegExp;
|
|
17
|
+
/** Runs inside the catalog INSERT, before its key check — another writer racing. */
|
|
18
|
+
beforeInsertRecord?: () => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function fakePg(catalog?: string): FakePg;
|
|
21
|
+
//# sourceMappingURL=fake-pg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-pg.d.ts","sourceRoot":"","sources":["../../src/__tests__/fake-pg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9B,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CACjC,CAAC;AASF,wBAAgB,MAAM,CAAC,OAAO,SAA2B,GAAG,MAAM,CAWjE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const NONE = { rows: [], rowCount: 0 };
|
|
2
|
+
const one = (row) => ({
|
|
3
|
+
rows: [row],
|
|
4
|
+
rowCount: 1,
|
|
5
|
+
});
|
|
6
|
+
export function fakePg(catalog = 'rag_collection_catalog') {
|
|
7
|
+
const fake = {
|
|
8
|
+
tables: new Set(),
|
|
9
|
+
records: new Map(),
|
|
10
|
+
statements: [],
|
|
11
|
+
client: {
|
|
12
|
+
query: async (sql, params = []) => answer(fake, catalog, sql, params),
|
|
13
|
+
end: async () => { },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
return fake;
|
|
17
|
+
}
|
|
18
|
+
function requireTable(fake, table) {
|
|
19
|
+
if (!fake.tables.has(table))
|
|
20
|
+
throw new Error(`relation "${table}" does not exist`);
|
|
21
|
+
}
|
|
22
|
+
function answer(fake, catalog, sql, params) {
|
|
23
|
+
fake.statements.push(sql);
|
|
24
|
+
if (fake.failOn?.test(sql))
|
|
25
|
+
throw new Error(`refused by the test: ${sql.slice(0, 60)}`);
|
|
26
|
+
const q = `"${catalog}"`;
|
|
27
|
+
if (sql.startsWith('SELECT to_regclass')) {
|
|
28
|
+
return one({
|
|
29
|
+
present: fake.tables.has(String(params[0]).replace(/^"|"$/g, '')),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (sql.startsWith('CREATE EXTENSION'))
|
|
33
|
+
return NONE;
|
|
34
|
+
const create = sql.match(/^CREATE TABLE (IF NOT EXISTS )?"([^"]+)"/);
|
|
35
|
+
if (create) {
|
|
36
|
+
const [, ifNotExists, table] = create;
|
|
37
|
+
if (fake.tables.has(table)) {
|
|
38
|
+
if (ifNotExists)
|
|
39
|
+
return NONE;
|
|
40
|
+
throw new Error(`relation "${table}" already exists`);
|
|
41
|
+
}
|
|
42
|
+
fake.tables.add(table);
|
|
43
|
+
return NONE;
|
|
44
|
+
}
|
|
45
|
+
const probe = sql.match(/^SELECT COUNT\(\*\) AS n FROM "([^"]+)" WHERE 1 = 0$/);
|
|
46
|
+
if (probe) {
|
|
47
|
+
requireTable(fake, probe[1]);
|
|
48
|
+
return one({ n: 0 });
|
|
49
|
+
}
|
|
50
|
+
if (sql.startsWith(`SELECT 1 AS present FROM ${q}`)) {
|
|
51
|
+
requireTable(fake, catalog);
|
|
52
|
+
return fake.records.has(String(params[0])) ? one({ present: 1 }) : NONE;
|
|
53
|
+
}
|
|
54
|
+
if (sql.startsWith(`INSERT INTO ${q}`)) {
|
|
55
|
+
requireTable(fake, catalog);
|
|
56
|
+
fake.beforeInsertRecord?.();
|
|
57
|
+
const storeName = String(params[0]);
|
|
58
|
+
if (fake.records.has(storeName)) {
|
|
59
|
+
throw new Error(`duplicate key value violates unique constraint (store_name)=(${storeName})`);
|
|
60
|
+
}
|
|
61
|
+
fake.records.set(storeName, {
|
|
62
|
+
storeName,
|
|
63
|
+
name: params[1],
|
|
64
|
+
scope: params[2],
|
|
65
|
+
userId: params[3],
|
|
66
|
+
sessionId: params[4],
|
|
67
|
+
attributesJson: params[5],
|
|
68
|
+
});
|
|
69
|
+
return { rows: [], rowCount: 1 };
|
|
70
|
+
}
|
|
71
|
+
if (sql.startsWith('SELECT store_name AS "storeName"')) {
|
|
72
|
+
requireTable(fake, catalog);
|
|
73
|
+
return { rows: [...fake.records.values()], rowCount: fake.records.size };
|
|
74
|
+
}
|
|
75
|
+
if (sql.startsWith(`DELETE FROM ${q}`)) {
|
|
76
|
+
requireTable(fake, catalog);
|
|
77
|
+
return {
|
|
78
|
+
rows: [],
|
|
79
|
+
rowCount: fake.records.delete(String(params[0])) ? 1 : 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const drop = sql.match(/^DROP TABLE IF EXISTS "([^"]+)"$/);
|
|
83
|
+
if (drop) {
|
|
84
|
+
fake.tables.delete(drop[1]);
|
|
85
|
+
return NONE;
|
|
86
|
+
}
|
|
87
|
+
const write = sql.match(/^INSERT INTO "([^"]+)" \(id, text, vector, metadata\)/);
|
|
88
|
+
if (write) {
|
|
89
|
+
requireTable(fake, write[1]);
|
|
90
|
+
return { rows: [], rowCount: 1 };
|
|
91
|
+
}
|
|
92
|
+
if (sql.startsWith('SELECT table_name FROM information_schema.tables')) {
|
|
93
|
+
return {
|
|
94
|
+
rows: [...fake.tables].map((t) => ({ table_name: t })),
|
|
95
|
+
rowCount: fake.tables.size,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`fake pg: unhandled statement: ${sql}`);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=fake-pg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-pg.js","sourceRoot":"","sources":["../../src/__tests__/fake-pg.ts"],"names":[],"mappings":"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"}
|