patchwork-os 0.2.0-beta.6.canary.21 → 0.2.0-beta.6.canary.22

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.
@@ -0,0 +1,455 @@
1
+ /**
2
+ * MongoDB connector — direct-connection-string database connector.
3
+ *
4
+ * Stores a MongoDB connection URI ("mongodb://" or "mongodb+srv://") in
5
+ * secure token storage and exposes a small read-only query surface:
6
+ * listDatabases, listCollections, describeCollection, find, aggregate,
7
+ * count.
8
+ *
9
+ * The `mongodb` driver is loaded lazily via dynamic import so callers
10
+ * without the package installed see a friendly message instead of a
11
+ * module-resolution crash at startup. Tests inject a fake module via
12
+ * `__setMongoModuleForTest`.
13
+ *
14
+ * HTTP routes (wired centrally in src/connectorRoutes.ts):
15
+ * POST /connections/mongodb/connect — body: { connectionString, database? }
16
+ * POST /connections/mongodb/test — ping admin
17
+ * DELETE /connections/mongodb — disconnect + delete token
18
+ */
19
+ import { BaseConnector, } from "./baseConnector.js";
20
+ import { escHtml } from "./htmlEscape.js";
21
+ import { deleteSecretJsonSync, getSecretJsonSync, storeSecretJsonSync, } from "./tokenStorage.js";
22
+ let _injectedModule = null;
23
+ let _modulePromise = null;
24
+ /** Inject a fake `mongodb` module for hermetic tests. */
25
+ export function __setMongoModuleForTest(mod) {
26
+ _injectedModule = mod;
27
+ _modulePromise = null;
28
+ // Reset the singleton client so a re-injected fake takes effect.
29
+ if (_client) {
30
+ void _client.close().catch(() => undefined);
31
+ _client = null;
32
+ }
33
+ }
34
+ async function loadMongoModule() {
35
+ if (_injectedModule)
36
+ return _injectedModule;
37
+ if (_modulePromise)
38
+ return _modulePromise;
39
+ _modulePromise = (async () => {
40
+ try {
41
+ // @ts-expect-error optional peer dep; resolved at runtime
42
+ const mod = (await import("mongodb"));
43
+ return mod;
44
+ }
45
+ catch {
46
+ throw new Error("MongoDB driver not installed. Run: npm install mongodb");
47
+ }
48
+ })();
49
+ return _modulePromise;
50
+ }
51
+ // ── Singleton client ─────────────────────────────────────────────────────────
52
+ let _client = null;
53
+ let _clientUri = null;
54
+ async function getClient(uri) {
55
+ if (_client && _clientUri === uri)
56
+ return _client;
57
+ if (_client && _clientUri !== uri) {
58
+ try {
59
+ await _client.close();
60
+ }
61
+ catch {
62
+ // Best-effort close
63
+ }
64
+ _client = null;
65
+ }
66
+ const mod = await loadMongoModule();
67
+ const client = new mod.MongoClient(uri);
68
+ await client.connect();
69
+ _client = client;
70
+ _clientUri = uri;
71
+ return client;
72
+ }
73
+ async function disconnectClient() {
74
+ if (!_client)
75
+ return;
76
+ try {
77
+ await _client.close();
78
+ }
79
+ catch {
80
+ // Best-effort
81
+ }
82
+ _client = null;
83
+ _clientUri = null;
84
+ }
85
+ // ── Token persistence ────────────────────────────────────────────────────────
86
+ const PROVIDER = "mongodb";
87
+ export function loadTokens() {
88
+ const envUri = process.env.MONGODB_URI;
89
+ if (envUri) {
90
+ return {
91
+ connectionString: envUri,
92
+ database: process.env.MONGODB_DATABASE,
93
+ connected_at: new Date().toISOString(),
94
+ };
95
+ }
96
+ return getSecretJsonSync(PROVIDER);
97
+ }
98
+ export function saveTokens(tokens) {
99
+ storeSecretJsonSync(PROVIDER, tokens);
100
+ }
101
+ export function clearTokens() {
102
+ deleteSecretJsonSync(PROVIDER);
103
+ }
104
+ // ── Connection string validation ─────────────────────────────────────────────
105
+ function validateConnectionString(uri) {
106
+ if (typeof uri !== "string" || uri.length === 0) {
107
+ throw new Error("connectionString must be a non-empty string");
108
+ }
109
+ if (!/^mongodb(\+srv)?:\/\//i.test(uri)) {
110
+ throw new Error("connectionString must start with mongodb:// or mongodb+srv://");
111
+ }
112
+ // Catch obvious tampering up front (control chars, newlines).
113
+ if (/[\x00-\x1f]/.test(uri)) {
114
+ throw new Error("connectionString contains control characters");
115
+ }
116
+ return uri;
117
+ }
118
+ // ── Read-only operation guard ────────────────────────────────────────────────
119
+ const FORBIDDEN_KEY_RE = /^\$(where|function|accumulator|out|merge)$/i;
120
+ /**
121
+ * Walk a filter/projection/pipeline value and reject any object key that
122
+ * matches dangerous Mongo operators at any depth:
123
+ *
124
+ * $where — server-side JS evaluation
125
+ * $function — server-side JS in aggregation
126
+ * $accumulator — server-side JS accumulator
127
+ * $out — pipeline stage that writes a collection
128
+ * $merge — pipeline stage that writes/upserts into a collection
129
+ *
130
+ * Returns true when the input is safe; throws otherwise so callers don't
131
+ * have to remember to check a boolean.
132
+ */
133
+ export function isReadOnlyMongoOp(value) {
134
+ walk(value, 0);
135
+ return true;
136
+ }
137
+ function walk(value, depth) {
138
+ if (depth > 64) {
139
+ throw new Error("MongoDB filter/pipeline nested too deeply");
140
+ }
141
+ if (value === null || value === undefined)
142
+ return;
143
+ if (Array.isArray(value)) {
144
+ for (const item of value)
145
+ walk(item, depth + 1);
146
+ return;
147
+ }
148
+ if (typeof value !== "object")
149
+ return;
150
+ for (const key of Object.keys(value)) {
151
+ if (FORBIDDEN_KEY_RE.test(key)) {
152
+ throw new Error(`MongoDB operator not allowed (read-only guard): ${key}`);
153
+ }
154
+ walk(value[key], depth + 1);
155
+ }
156
+ }
157
+ // ── Connector class ──────────────────────────────────────────────────────────
158
+ export class MongoConnector extends BaseConnector {
159
+ providerName = "mongodb";
160
+ getOAuthConfig() {
161
+ return null; // Direct connection string, no OAuth.
162
+ }
163
+ async authenticate() {
164
+ const tokens = loadTokens();
165
+ if (!tokens) {
166
+ throw new Error("MongoDB not connected. POST /connections/mongodb/connect first.");
167
+ }
168
+ return { token: tokens.connectionString };
169
+ }
170
+ getStatus() {
171
+ const tokens = loadTokens();
172
+ return {
173
+ id: PROVIDER,
174
+ status: tokens ? "connected" : "disconnected",
175
+ lastSync: tokens?.connected_at,
176
+ workspace: tokens?.database,
177
+ };
178
+ }
179
+ async healthCheck() {
180
+ const tokens = loadTokens();
181
+ if (!tokens) {
182
+ return {
183
+ ok: false,
184
+ error: {
185
+ code: "auth_expired",
186
+ message: "MongoDB not connected",
187
+ retryable: false,
188
+ },
189
+ };
190
+ }
191
+ try {
192
+ const client = await getClient(tokens.connectionString);
193
+ await client.db("admin").command({ ping: 1 });
194
+ return { ok: true };
195
+ }
196
+ catch (err) {
197
+ return { ok: false, error: this.normalizeError(err) };
198
+ }
199
+ }
200
+ normalizeError(error) {
201
+ const message = error instanceof Error ? error.message : String(error);
202
+ const codeRaw = error?.code;
203
+ const code = typeof codeRaw === "number" ? codeRaw : undefined;
204
+ const name = error?.name;
205
+ // Network errors — check name first since some have no numeric code.
206
+ if (name === "MongoNetworkError" ||
207
+ name === "MongoServerSelectionError" ||
208
+ name === "MongoNetworkTimeoutError" ||
209
+ /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network/i.test(message)) {
210
+ return {
211
+ code: "network_error",
212
+ message,
213
+ providerDetail: error,
214
+ retryable: true,
215
+ };
216
+ }
217
+ switch (code) {
218
+ case 18: // AuthenticationFailed
219
+ return {
220
+ code: "auth_expired",
221
+ message,
222
+ providerDetail: error,
223
+ retryable: false,
224
+ suggestedAction: "Re-connect MongoDB via POST /connections/mongodb/connect",
225
+ };
226
+ case 13: // Unauthorized
227
+ return {
228
+ code: "permission_denied",
229
+ message,
230
+ providerDetail: error,
231
+ retryable: false,
232
+ };
233
+ case 11: // UserNotFound
234
+ case 26: // NamespaceNotFound
235
+ return {
236
+ code: "not_found",
237
+ message,
238
+ providerDetail: error,
239
+ retryable: false,
240
+ };
241
+ default:
242
+ return {
243
+ code: "provider_error",
244
+ message,
245
+ providerDetail: error,
246
+ retryable: false,
247
+ };
248
+ }
249
+ }
250
+ async disconnect() {
251
+ await disconnectClient();
252
+ clearTokens();
253
+ this.auth = null;
254
+ }
255
+ }
256
+ // ── Singleton ────────────────────────────────────────────────────────────────
257
+ let _connector = null;
258
+ export function mongoConnector() {
259
+ if (!_connector)
260
+ _connector = new MongoConnector();
261
+ return _connector;
262
+ }
263
+ // ── Read-only tool surface ───────────────────────────────────────────────────
264
+ const DEFAULT_LIMIT = 100;
265
+ const MAX_LIMIT = 1000;
266
+ function clampLimit(n) {
267
+ if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) {
268
+ return DEFAULT_LIMIT;
269
+ }
270
+ return Math.min(Math.floor(n), MAX_LIMIT);
271
+ }
272
+ async function activeDb(name) {
273
+ const tokens = loadTokens();
274
+ if (!tokens) {
275
+ throw new Error("MongoDB not connected. POST /connections/mongodb/connect first.");
276
+ }
277
+ const client = await getClient(tokens.connectionString);
278
+ return client.db(name);
279
+ }
280
+ export async function listDatabases() {
281
+ const tokens = loadTokens();
282
+ if (!tokens) {
283
+ throw new Error("MongoDB not connected");
284
+ }
285
+ const client = await getClient(tokens.connectionString);
286
+ const res = await client.db().admin().listDatabases();
287
+ return (res.databases ?? []).map((d) => d.name);
288
+ }
289
+ export async function listCollections(database) {
290
+ if (!database)
291
+ throw new Error("database is required");
292
+ const db = await activeDb(database);
293
+ const rows = await db.listCollections({}, { nameOnly: true }).toArray();
294
+ return rows.map((r) => r.name);
295
+ }
296
+ export async function describeCollection(database, collection) {
297
+ if (!database)
298
+ throw new Error("database is required");
299
+ if (!collection)
300
+ throw new Error("collection is required");
301
+ const db = await activeDb(database);
302
+ const col = db.collection(collection);
303
+ const [sample, indexes] = await Promise.all([col.findOne({}), col.indexes()]);
304
+ return { sample, indexes };
305
+ }
306
+ export async function find(database, collection, filter = {}, options = {}) {
307
+ if (!database)
308
+ throw new Error("database is required");
309
+ if (!collection)
310
+ throw new Error("collection is required");
311
+ isReadOnlyMongoOp(filter);
312
+ if (options.projection)
313
+ isReadOnlyMongoOp(options.projection);
314
+ const db = await activeDb(database);
315
+ const cursor = db.collection(collection).find(filter, {
316
+ projection: options.projection,
317
+ limit: clampLimit(options.limit),
318
+ });
319
+ return cursor.toArray();
320
+ }
321
+ const FORBIDDEN_STAGE_RE = /^\$(out|merge|function|where|accumulator)$/i;
322
+ function rejectForbiddenStages(pipeline) {
323
+ for (const stage of pipeline) {
324
+ if (!stage || typeof stage !== "object")
325
+ continue;
326
+ for (const key of Object.keys(stage)) {
327
+ if (FORBIDDEN_STAGE_RE.test(key)) {
328
+ throw new Error(`MongoDB aggregation stage not allowed (read-only guard): ${key}`);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ export async function aggregate(database, collection, pipeline, limit = DEFAULT_LIMIT) {
334
+ if (!database)
335
+ throw new Error("database is required");
336
+ if (!collection)
337
+ throw new Error("collection is required");
338
+ if (!Array.isArray(pipeline))
339
+ throw new Error("pipeline must be an array");
340
+ rejectForbiddenStages(pipeline);
341
+ isReadOnlyMongoOp(pipeline);
342
+ const cappedLimit = clampLimit(limit);
343
+ const capped = [...pipeline, { $limit: cappedLimit }];
344
+ const db = await activeDb(database);
345
+ return db.collection(collection).aggregate(capped).toArray();
346
+ }
347
+ export async function count(database, collection, filter) {
348
+ if (!database)
349
+ throw new Error("database is required");
350
+ if (!collection)
351
+ throw new Error("collection is required");
352
+ const db = await activeDb(database);
353
+ const col = db.collection(collection);
354
+ if (!filter || Object.keys(filter).length === 0) {
355
+ return col.estimatedDocumentCount();
356
+ }
357
+ isReadOnlyMongoOp(filter);
358
+ return col.countDocuments(filter);
359
+ }
360
+ function jsonResult(status, payload) {
361
+ return {
362
+ status,
363
+ contentType: "application/json",
364
+ body: JSON.stringify(payload),
365
+ };
366
+ }
367
+ /**
368
+ * POST /connections/mongodb/connect
369
+ * Body: { connectionString: string, database?: string }
370
+ *
371
+ * Validates by issuing a ping. On success persists the credential.
372
+ */
373
+ export async function handleMongoConnect(body) {
374
+ let connectionString;
375
+ let database;
376
+ try {
377
+ const parsed = (body ?? {});
378
+ connectionString = validateConnectionString(parsed.connectionString);
379
+ if (parsed.database !== undefined) {
380
+ if (typeof parsed.database !== "string") {
381
+ throw new Error("database must be a string");
382
+ }
383
+ database = parsed.database;
384
+ }
385
+ }
386
+ catch (err) {
387
+ return jsonResult(400, {
388
+ ok: false,
389
+ error: err instanceof Error ? err.message : String(err),
390
+ });
391
+ }
392
+ // Verify by pinging admin before persisting.
393
+ try {
394
+ const client = await getClient(connectionString);
395
+ await client.db("admin").command({ ping: 1 });
396
+ }
397
+ catch (err) {
398
+ // Close the failed client so a retry uses fresh state.
399
+ await disconnectClient();
400
+ return jsonResult(400, {
401
+ ok: false,
402
+ error: err instanceof Error ? err.message : String(err),
403
+ });
404
+ }
405
+ const tokens = {
406
+ connectionString,
407
+ database,
408
+ connected_at: new Date().toISOString(),
409
+ };
410
+ saveTokens(tokens);
411
+ return jsonResult(200, {
412
+ ok: true,
413
+ status: mongoConnector().getStatus(),
414
+ });
415
+ }
416
+ /**
417
+ * POST /connections/mongodb/test — re-ping the stored connection.
418
+ */
419
+ export async function handleMongoTest() {
420
+ const tokens = loadTokens();
421
+ if (!tokens) {
422
+ return jsonResult(400, { ok: false, error: "MongoDB not connected" });
423
+ }
424
+ try {
425
+ const client = await getClient(tokens.connectionString);
426
+ await client.db("admin").command({ ping: 1 });
427
+ return jsonResult(200, { ok: true, message: "connected" });
428
+ }
429
+ catch (err) {
430
+ return jsonResult(400, {
431
+ ok: false,
432
+ error: err instanceof Error ? err.message : String(err),
433
+ });
434
+ }
435
+ }
436
+ /**
437
+ * DELETE /connections/mongodb — close client, drop token.
438
+ */
439
+ export async function handleMongoDisconnect() {
440
+ await mongoConnector().disconnect();
441
+ return jsonResult(200, { ok: true });
442
+ }
443
+ /**
444
+ * Optional HTML status page parity with other connectors. Not wired by
445
+ * default — exported for symmetry should the router need it.
446
+ */
447
+ export function renderMongoStatusHtml() {
448
+ const status = mongoConnector().getStatus();
449
+ return {
450
+ status: 200,
451
+ contentType: "text/html",
452
+ body: `<html><body><h2>MongoDB</h2><pre>${escHtml(JSON.stringify(status, null, 2))}</pre></body></html>`,
453
+ };
454
+ }
455
+ //# sourceMappingURL=mongodb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb.js","sourceRoot":"","sources":["../../src/connectors/mongodb.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Postgres connector — read-only SQL access via the `pg` driver.
3
+ *
4
+ * Auth: connection string OR discrete host/port/database/user/password.
5
+ * - Stored: getSecretJsonSync("postgres") → PostgresTokens
6
+ *
7
+ * Tools: listTables, describeTable, query (SELECT-only), explain (SELECT-only)
8
+ *
9
+ * Driver: `pg` is loaded lazily via dynamic import. It is NOT a project
10
+ * dependency — operators must `npm install pg` in their workspace before
11
+ * using this connector.
12
+ *
13
+ * Extends BaseConnector for unified auth, retry, rate-limit, error handling.
14
+ *
15
+ * This file is the canonical template for the other data-store connectors
16
+ * (MongoDB, Redis, Elasticsearch). Keep the shape consistent.
17
+ */
18
+ import { type AuthContext, BaseConnector, type ConnectorError, type ConnectorStatus } from "./baseConnector.js";
19
+ export interface PostgresTokens {
20
+ /** Full DSN: postgres://user:pass@host:port/db */
21
+ connectionString?: string;
22
+ host?: string;
23
+ port?: number;
24
+ database?: string;
25
+ user?: string;
26
+ password?: string;
27
+ ssl?: boolean;
28
+ connected_at: string;
29
+ }
30
+ export interface PostgresTable {
31
+ table_schema: string;
32
+ table_name: string;
33
+ }
34
+ export interface PostgresColumn {
35
+ column_name: string;
36
+ data_type: string;
37
+ is_nullable: string;
38
+ column_default: string | null;
39
+ }
40
+ export interface PostgresQueryResult {
41
+ rows: Record<string, unknown>[];
42
+ rowCount: number;
43
+ fields: {
44
+ name: string;
45
+ dataTypeID: number;
46
+ }[];
47
+ truncated: boolean;
48
+ }
49
+ type PgPoolClient = {
50
+ query: (text: string, values?: unknown[]) => Promise<PgQueryResultRaw>;
51
+ release: () => void;
52
+ };
53
+ type PgQueryResultRaw = {
54
+ rows: Record<string, unknown>[];
55
+ rowCount: number | null;
56
+ fields?: {
57
+ name: string;
58
+ dataTypeID: number;
59
+ }[];
60
+ };
61
+ type PgPool = {
62
+ query: (text: string, values?: unknown[]) => Promise<PgQueryResultRaw>;
63
+ connect: () => Promise<PgPoolClient>;
64
+ end: () => Promise<void>;
65
+ };
66
+ type PgModule = {
67
+ Pool: new (config: Record<string, unknown>) => PgPool;
68
+ };
69
+ export declare function __setPgModuleForTest(mod: PgModule | null): void;
70
+ /**
71
+ * True iff `sql` is a read-only statement (SELECT / SHOW / EXPLAIN / WITH).
72
+ * Strips leading comments + whitespace and inspects the first keyword.
73
+ * Defence in depth: pair with role-level read-only grants on the DB side.
74
+ */
75
+ export declare function isReadOnlySql(sql: string): boolean;
76
+ /**
77
+ * Wrap a SELECT-style statement with `LIMIT n` if it does not already cap
78
+ * rows. Cheap heuristic: case-insensitive "limit" word search. The runtime
79
+ * row-count cap in `query()` is the real safety net.
80
+ */
81
+ export declare function applyRowLimit(sql: string, limit: number): string;
82
+ export declare class PostgresConnector extends BaseConnector {
83
+ readonly providerName = "postgres";
84
+ private tokens;
85
+ private pool;
86
+ protected getOAuthConfig(): null;
87
+ authenticate(): Promise<AuthContext>;
88
+ healthCheck(): Promise<{
89
+ ok: boolean;
90
+ error?: ConnectorError;
91
+ }>;
92
+ normalizeError(error: unknown): ConnectorError;
93
+ getStatus(): ConnectorStatus;
94
+ private getPool;
95
+ /** Close the pool. Called by the HTTP disconnect handler. */
96
+ disconnect(): Promise<void>;
97
+ listTables(schema?: string): Promise<PostgresTable[]>;
98
+ describeTable(table: string, schema?: string): Promise<PostgresColumn[]>;
99
+ query(sql: string, params?: unknown[], rowLimit?: number): Promise<PostgresQueryResult>;
100
+ explain(sql: string): Promise<unknown>;
101
+ }
102
+ export declare function loadTokens(): PostgresTokens | null;
103
+ export declare function saveTokens(tokens: PostgresTokens): void;
104
+ export declare function clearTokens(): void;
105
+ export declare function getPostgresConnector(): PostgresConnector;
106
+ export { getPostgresConnector as postgres };
107
+ export interface ConnectorHandlerResult {
108
+ status: number;
109
+ body: string;
110
+ contentType?: string;
111
+ }
112
+ /**
113
+ * POST /connections/postgres/connect
114
+ * body: { connectionString } OR { host, port?, database, user, password, ssl? }
115
+ *
116
+ * Validates by opening a connection, running `SELECT 1`, then closing.
117
+ * Only stores tokens after a successful round-trip.
118
+ */
119
+ export declare function handlePostgresConnect(body: string): Promise<ConnectorHandlerResult>;
120
+ /**
121
+ * POST /connections/postgres/test
122
+ */
123
+ export declare function handlePostgresTest(): Promise<ConnectorHandlerResult>;
124
+ /**
125
+ * DELETE /connections/postgres
126
+ */
127
+ export declare function handlePostgresDisconnect(): Promise<ConnectorHandlerResult>;