@nifrajs/mcp-db 3.3.0 → 4.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 +83 -0
- package/dist/engine.d.ts +71 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +494 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +8 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -552
- package/dist/index.js.map +1 -1
- package/dist/internal/pg-exec.d.ts +113 -0
- package/dist/internal/pg-exec.d.ts.map +1 -0
- package/dist/internal/pg-exec.js +286 -0
- package/dist/internal/pg-exec.js.map +1 -0
- package/dist/internal/pg-lexer.d.ts +52 -0
- package/dist/internal/pg-lexer.d.ts.map +1 -0
- package/dist/internal/pg-lexer.js +273 -0
- package/dist/internal/pg-lexer.js.map +1 -0
- package/dist/internal/shared.d.ts +161 -0
- package/dist/internal/shared.d.ts.map +1 -0
- package/dist/internal/shared.js +538 -0
- package/dist/internal/shared.js.map +1 -0
- package/dist/internal/sqlite-session.d.ts +21 -0
- package/dist/internal/sqlite-session.d.ts.map +1 -0
- package/dist/internal/sqlite-session.js +254 -0
- package/dist/internal/sqlite-session.js.map +1 -0
- package/dist/postgres.d.ts +148 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +580 -0
- package/dist/postgres.js.map +1 -0
- package/package.json +15 -2
- package/src/engine.ts +664 -0
- package/src/index.ts +43 -588
- package/src/internal/pg-exec.ts +386 -0
- package/src/internal/pg-lexer.ts +279 -0
- package/src/internal/shared.ts +705 -0
- package/src/internal/sqlite-session.ts +273 -0
- package/src/postgres.ts +777 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The execution lanes `run_query` uses: a reusable read-only worker for a file-backed database, and an
|
|
3
|
+
* in-process lane for one that cannot be reopened. Not part of the public engine - the CLI's database
|
|
4
|
+
* tool runs each query in its own subprocess instead, which bounds the work by killing the process.
|
|
5
|
+
*/
|
|
6
|
+
export class QueryTimeoutError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super("query exceeded the time limit");
|
|
9
|
+
this.name = "QueryTimeoutError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The isolated lane. The worker reopens the SAME database FILE read-only and answers one statement
|
|
14
|
+
* at a time; it is spawned once and reused for the life of the server. It deliberately does not
|
|
15
|
+
* take a `serialize()` snapshot - that copies the whole database into memory on EVERY call, which
|
|
16
|
+
* is a larger availability problem than the slow query the deadline exists to bound.
|
|
17
|
+
*/
|
|
18
|
+
const SQLITE_WORKER_SOURCE = `
|
|
19
|
+
const { Database } = require("bun:sqlite")
|
|
20
|
+
let db
|
|
21
|
+
self.onmessage = (event) => {
|
|
22
|
+
const { id, filename, sql, type } = event.data
|
|
23
|
+
if (type === "close") {
|
|
24
|
+
let response
|
|
25
|
+
try {
|
|
26
|
+
// prepare().all() leaves a statement alive until it is finalized or collected. Bun's
|
|
27
|
+
// default close(false) preserves those statements and can keep the database file locked;
|
|
28
|
+
// close(true) finalizes every outstanding statement before the worker exits.
|
|
29
|
+
db?.close(true)
|
|
30
|
+
db = undefined
|
|
31
|
+
response = { id, closed: true, ok: true }
|
|
32
|
+
} catch (error) {
|
|
33
|
+
response = { id, closed: true, ok: false, error: String(error) }
|
|
34
|
+
}
|
|
35
|
+
// A forced parent-side terminate can leave native SQLite handles locked on Windows even after
|
|
36
|
+
// close() returns. Let the worker exit its own event turn so the runtime can finish releasing
|
|
37
|
+
// those handles before the parent considers graceful cleanup complete.
|
|
38
|
+
self.postMessage(response)
|
|
39
|
+
self.close()
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
if (db === undefined) {
|
|
44
|
+
db = new Database(filename, { readonly: true })
|
|
45
|
+
db.run("PRAGMA query_only = ON")
|
|
46
|
+
}
|
|
47
|
+
self.postMessage({ id, ok: true, rows: db.prepare(sql).all() })
|
|
48
|
+
} catch (error) {
|
|
49
|
+
self.postMessage({ id, ok: false, error: String(error) })
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
function isWorkerAvailable() {
|
|
54
|
+
return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined";
|
|
55
|
+
}
|
|
56
|
+
/** Only a file-backed database can be reopened in a worker. `:memory:` and the anonymous temp
|
|
57
|
+
* database live in this process alone, so they take the in-process lane instead. */
|
|
58
|
+
function reopenableFilename(db) {
|
|
59
|
+
const filename = db.filename;
|
|
60
|
+
if (typeof filename !== "string" || filename === "" || filename === ":memory:")
|
|
61
|
+
return undefined;
|
|
62
|
+
return filename.startsWith("file::memory:") ? undefined : filename;
|
|
63
|
+
}
|
|
64
|
+
const WORKER_CLOSE_TIMEOUT_MS = 1_000;
|
|
65
|
+
function workerSession(filename) {
|
|
66
|
+
let worker;
|
|
67
|
+
let sourceUrl;
|
|
68
|
+
let nextId = 0;
|
|
69
|
+
const pending = new Map();
|
|
70
|
+
let lane = Promise.resolve();
|
|
71
|
+
let closed = false;
|
|
72
|
+
let closePromise;
|
|
73
|
+
let pendingClose;
|
|
74
|
+
const detachWorker = (active = worker) => {
|
|
75
|
+
if (active === undefined)
|
|
76
|
+
return;
|
|
77
|
+
if (worker !== active)
|
|
78
|
+
return;
|
|
79
|
+
worker = undefined;
|
|
80
|
+
if (sourceUrl !== undefined)
|
|
81
|
+
URL.revokeObjectURL(sourceUrl);
|
|
82
|
+
sourceUrl = undefined;
|
|
83
|
+
};
|
|
84
|
+
const releaseWorker = (active = worker) => {
|
|
85
|
+
if (active === undefined)
|
|
86
|
+
return;
|
|
87
|
+
active.terminate();
|
|
88
|
+
detachWorker(active);
|
|
89
|
+
};
|
|
90
|
+
const rejectPending = (error) => {
|
|
91
|
+
const waiters = [...pending.values()];
|
|
92
|
+
pending.clear();
|
|
93
|
+
for (const waiter of waiters)
|
|
94
|
+
waiter.reject(error);
|
|
95
|
+
};
|
|
96
|
+
/** Drop the worker and fail everything riding on it. The only bound this side can enforce over a
|
|
97
|
+
* statement already running inside native SQLite is to stop owning the connection it runs on. */
|
|
98
|
+
const discard = (error) => {
|
|
99
|
+
const closing = pendingClose;
|
|
100
|
+
pendingClose = undefined;
|
|
101
|
+
releaseWorker();
|
|
102
|
+
rejectPending(error);
|
|
103
|
+
closing?.resolve();
|
|
104
|
+
};
|
|
105
|
+
const ensureWorker = () => {
|
|
106
|
+
if (worker !== undefined)
|
|
107
|
+
return worker;
|
|
108
|
+
sourceUrl = URL.createObjectURL(new Blob([SQLITE_WORKER_SOURCE], { type: "text/javascript" }));
|
|
109
|
+
const spawned = new Worker(sourceUrl);
|
|
110
|
+
spawned.onmessage = (event) => {
|
|
111
|
+
const { id, ok, rows, error } = event.data;
|
|
112
|
+
if (typeof id !== "number")
|
|
113
|
+
return;
|
|
114
|
+
if (event.data.closed === true) {
|
|
115
|
+
const closing = pendingClose;
|
|
116
|
+
if (closing?.id !== id)
|
|
117
|
+
return;
|
|
118
|
+
pendingClose = undefined;
|
|
119
|
+
closing.resolve(event.data.ok === true);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const waiter = pending.get(id);
|
|
123
|
+
if (waiter === undefined)
|
|
124
|
+
return;
|
|
125
|
+
if (Date.now() >= waiter.deadline) {
|
|
126
|
+
discard(new QueryTimeoutError());
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
pending.delete(id);
|
|
130
|
+
if (ok === true)
|
|
131
|
+
waiter.resolve(rows ?? []);
|
|
132
|
+
else
|
|
133
|
+
waiter.reject(new Error(error ?? "query failed in worker"));
|
|
134
|
+
};
|
|
135
|
+
spawned.onerror = () => discard(new Error("query failed in worker"));
|
|
136
|
+
spawned.unref?.();
|
|
137
|
+
worker = spawned;
|
|
138
|
+
return spawned;
|
|
139
|
+
};
|
|
140
|
+
const dispatch = (sql, deadline) => {
|
|
141
|
+
if (closed)
|
|
142
|
+
return Promise.reject(new Error("query session is closed"));
|
|
143
|
+
const active = ensureWorker();
|
|
144
|
+
const remaining = deadline - Date.now();
|
|
145
|
+
if (remaining <= 0) {
|
|
146
|
+
discard(new QueryTimeoutError());
|
|
147
|
+
return Promise.reject(new QueryTimeoutError());
|
|
148
|
+
}
|
|
149
|
+
const id = ++nextId;
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const timer = setTimeout(() => discard(new QueryTimeoutError()), remaining);
|
|
152
|
+
pending.set(id, {
|
|
153
|
+
deadline,
|
|
154
|
+
resolve: (rows) => {
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
resolve(rows);
|
|
157
|
+
},
|
|
158
|
+
reject: (error) => {
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
reject(error);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
active.postMessage({ id, filename, sql });
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
pending.delete(id);
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
run(sql, deadline) {
|
|
175
|
+
// One worker owns the connection, so overlapping calls queue behind each other rather than
|
|
176
|
+
// interleaving statements on it.
|
|
177
|
+
const result = lane.then(() => dispatch(sql, deadline));
|
|
178
|
+
lane = result.catch(() => undefined);
|
|
179
|
+
return result;
|
|
180
|
+
},
|
|
181
|
+
close() {
|
|
182
|
+
if (closePromise !== undefined)
|
|
183
|
+
return closePromise;
|
|
184
|
+
closed = true;
|
|
185
|
+
closePromise = (async () => {
|
|
186
|
+
const active = worker;
|
|
187
|
+
if (active !== undefined) {
|
|
188
|
+
await new Promise((resolve) => {
|
|
189
|
+
const id = ++nextId;
|
|
190
|
+
let settled = false;
|
|
191
|
+
const finish = (error, graceful = false) => {
|
|
192
|
+
if (settled)
|
|
193
|
+
return;
|
|
194
|
+
settled = true;
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
if (pendingClose?.id === id)
|
|
197
|
+
pendingClose = undefined;
|
|
198
|
+
if (error !== undefined)
|
|
199
|
+
rejectPending(error);
|
|
200
|
+
if (graceful)
|
|
201
|
+
detachWorker(active);
|
|
202
|
+
else
|
|
203
|
+
releaseWorker(active);
|
|
204
|
+
resolve();
|
|
205
|
+
};
|
|
206
|
+
const timer = setTimeout(() => {
|
|
207
|
+
finish(new Error("query session is closed"));
|
|
208
|
+
}, WORKER_CLOSE_TIMEOUT_MS);
|
|
209
|
+
pendingClose = { id, resolve: (graceful) => finish(undefined, graceful) };
|
|
210
|
+
try {
|
|
211
|
+
active.postMessage({ id, type: "close" });
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
finish(new Error("query session is closed"));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
await lane;
|
|
219
|
+
})();
|
|
220
|
+
return closePromise;
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The in-process lane, used when the database cannot be reopened elsewhere. The deadline is checked
|
|
226
|
+
* around the statement and nothing more: `prepare().all()` is synchronous, so it holds the only
|
|
227
|
+
* thread there is and no timer of ours can fire while it runs. That makes this lane BEST EFFORT -
|
|
228
|
+
* it reports an overrun, it does not cut one short.
|
|
229
|
+
*/
|
|
230
|
+
function inProcessSession(db) {
|
|
231
|
+
return {
|
|
232
|
+
async run(sql, deadline) {
|
|
233
|
+
if (Date.now() >= deadline)
|
|
234
|
+
throw new QueryTimeoutError();
|
|
235
|
+
const rows = db.prepare(sql).all();
|
|
236
|
+
if (Date.now() > deadline)
|
|
237
|
+
throw new QueryTimeoutError();
|
|
238
|
+
return rows;
|
|
239
|
+
},
|
|
240
|
+
async close() { },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Pick the lane once, at construction. A file-backed database is isolated in a reusable read-only
|
|
245
|
+
* worker, which is the only arrangement that can actually stop a running statement. Anything else
|
|
246
|
+
* runs in process - every database keeps working, none is refused.
|
|
247
|
+
*/
|
|
248
|
+
export function openQuerySession(db) {
|
|
249
|
+
const filename = reopenableFilename(db);
|
|
250
|
+
if (filename !== undefined && isWorkerAvailable())
|
|
251
|
+
return workerSession(filename);
|
|
252
|
+
return inProcessSession(db);
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=sqlite-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-session.js","sourceRoot":"","sources":["../../src/internal/sqlite-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/mcp-db/postgres` - a read-only Postgres engine for a development database, on Bun's
|
|
3
|
+
* built-in `Bun.SQL` (no npm driver). Every layer below is enforced on each call, in this order:
|
|
4
|
+
*
|
|
5
|
+
* 1. Host gate ({@link connectPostgres}): loopback, `*.localhost` or a unix socket, else only a host
|
|
6
|
+
* named in `allowHosts`. Connection fields are passed explicitly, so `DATABASE_URL`/`PG*` in the
|
|
7
|
+
* environment can never redirect the connection.
|
|
8
|
+
* 2. Role gate: a superuser, a member of a superuser role or of `pg_execute_server_program` /
|
|
9
|
+
* `pg_read_server_files` / `pg_write_server_files`, or a role that may execute the server-file
|
|
10
|
+
* functions is refused - a read-only transaction does not stop `COPY ... TO PROGRAM` or
|
|
11
|
+
* `pg_read_file`. {@link postgresRoleSql} prints the role to use instead.
|
|
12
|
+
* 3. Extension gate: `dblink`, `postgres_fdw`, `file_fdw` or an untrusted language the role can use
|
|
13
|
+
* is refused unless `allowExtensions` names it - `dblink_exec` writes to another database from a
|
|
14
|
+
* read-only transaction.
|
|
15
|
+
* 4. Transaction: every connection starts with `default_transaction_read_only=on`; each call runs in
|
|
16
|
+
* `BEGIN READ ONLY` with `statement_timeout`, `lock_timeout` and
|
|
17
|
+
* `idle_in_transaction_session_timeout` set locally, and always rolls back.
|
|
18
|
+
* 5. Statement gate: the query goes over the extended protocol (the server refuses a second
|
|
19
|
+
* statement) as `DECLARE ... NO SCROLL CURSOR` (the server accepts only a query there), and at most
|
|
20
|
+
* `maxRows + 1` rows are fetched. A tokenizer in front refuses side-effect functions.
|
|
21
|
+
* 6. Table scope: the plan's relations (and every table they inherit from) must sit in the allowed
|
|
22
|
+
* schemas and outside `exclude`. A function can still read a table internally; hard isolation
|
|
23
|
+
* comes from the role's grants, which {@link postgresRoleSql} writes with a REVOKE per exclusion.
|
|
24
|
+
*
|
|
25
|
+
* const target = parsePostgresUrl("postgres://nifra_dev_reader@localhost:5432/app")
|
|
26
|
+
* const client = connectPostgres(target, { timeoutMs: 5000 })
|
|
27
|
+
* const rows = await queryPostgres(client, "SELECT status, count(*) FROM orders GROUP BY 1", {
|
|
28
|
+
* timeoutMs: 5000, maxRows: 100, maxResultBytes: 100 * 1024,
|
|
29
|
+
* })
|
|
30
|
+
*/
|
|
31
|
+
import { type PostgresClient } from "./internal/pg-exec.js";
|
|
32
|
+
import { type DbPlan, type DbRedaction, type DbRefusal, type DbRows, type DbSchemaReport, type ShapeRowsOptions } from "./internal/shared.js";
|
|
33
|
+
export type { PgConnection, PgQuery, PostgresClient } from "./internal/pg-exec.js";
|
|
34
|
+
/** Where to connect, parsed from a `postgres://` URL. */
|
|
35
|
+
export interface PostgresTarget {
|
|
36
|
+
/** TCP host; absent for a unix socket. */
|
|
37
|
+
readonly host?: string;
|
|
38
|
+
/** Unix socket file path. */
|
|
39
|
+
readonly socket?: string;
|
|
40
|
+
readonly port: number;
|
|
41
|
+
readonly username: string;
|
|
42
|
+
readonly password?: string;
|
|
43
|
+
readonly database: string;
|
|
44
|
+
readonly tls?: "disable" | "allow" | "prefer" | "require" | "verify-ca" | "verify-full";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse a `postgres://` (or `postgresql://`) URL. Only the host, port, user, password, database and
|
|
48
|
+
* `sslmode` are read - any other parameter (`options=-c ...` included) is ignored, so a URL cannot
|
|
49
|
+
* switch off the read-only session settings.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parsePostgresUrl(url: string): PostgresTarget | DbRefusal;
|
|
52
|
+
/** True for `localhost`, `*.localhost`, 127.0.0.0/8 and `::1` (also as an IPv4-mapped address). */
|
|
53
|
+
export declare function isLocalPostgresHost(host: string): boolean;
|
|
54
|
+
/** Refuse a TCP host that is neither local nor named in `allowHosts`. */
|
|
55
|
+
export declare function postgresHostRefusal(target: PostgresTarget, allowHosts?: readonly string[]): DbRefusal | undefined;
|
|
56
|
+
/** Options for {@link connectPostgres}. */
|
|
57
|
+
export interface ConnectPostgresOptions {
|
|
58
|
+
readonly timeoutMs: number;
|
|
59
|
+
readonly allowHosts?: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Open a one-connection `Bun.SQL` client for `target` after the host gate. Every session starts
|
|
63
|
+
* read-only with `standard_conforming_strings` on (the statement tokenizer reads strings that way).
|
|
64
|
+
*/
|
|
65
|
+
export declare function connectPostgres(target: PostgresTarget, options: ConnectPostgresOptions): PostgresClient | DbRefusal;
|
|
66
|
+
/** What the role gate and extension gate read about the connected role. */
|
|
67
|
+
export interface PostgresRoleReport {
|
|
68
|
+
readonly role: string;
|
|
69
|
+
readonly superuser: boolean;
|
|
70
|
+
readonly serverVersionNum: number;
|
|
71
|
+
/** Superuser roles the role is a member of. */
|
|
72
|
+
readonly superuserRoles: readonly string[];
|
|
73
|
+
/** Server-file and server-program roles the role is a member of. */
|
|
74
|
+
readonly serverRoles: readonly string[];
|
|
75
|
+
/** Server-file functions the role may execute. */
|
|
76
|
+
readonly fileFunctions: readonly string[];
|
|
77
|
+
/** `dblink`, `postgres_fdw`, `file_fdw`: installed and usable by the role. */
|
|
78
|
+
readonly extensions: readonly string[];
|
|
79
|
+
/** Untrusted procedural languages the role can use or call functions in. */
|
|
80
|
+
readonly languages: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
/** Read the connected role's privileges, in a read-only transaction. */
|
|
83
|
+
export declare function inspectPostgresRole(client: PostgresClient, options: {
|
|
84
|
+
readonly timeoutMs: number;
|
|
85
|
+
}): Promise<PostgresRoleReport | DbRefusal>;
|
|
86
|
+
/** The role gate and the extension gate, as one decision over a {@link PostgresRoleReport}. */
|
|
87
|
+
export declare function postgresRoleRefusal(report: PostgresRoleReport, options?: {
|
|
88
|
+
readonly allowExtensions?: readonly string[];
|
|
89
|
+
}): DbRefusal | undefined;
|
|
90
|
+
/** Refuse what the tokenizer layer refuses: a non-query, several statements, a denied function. */
|
|
91
|
+
export declare function lintPostgresStatement(sql: string): DbRefusal | undefined;
|
|
92
|
+
/** Which relations a query may read. */
|
|
93
|
+
export interface PostgresScope {
|
|
94
|
+
/** Schemas a query may read (default `["public"]`). */
|
|
95
|
+
readonly schemas?: readonly string[];
|
|
96
|
+
/** Tables a query may not read: `name` in any allowed schema, or `schema.name`. */
|
|
97
|
+
readonly exclude?: readonly string[];
|
|
98
|
+
}
|
|
99
|
+
/** Options for {@link queryPostgres}. */
|
|
100
|
+
export interface PostgresQueryOptions extends ShapeRowsOptions, PostgresScope {
|
|
101
|
+
readonly timeoutMs: number;
|
|
102
|
+
/** Extensions or untrusted languages to accept although the role can use them. */
|
|
103
|
+
readonly allowExtensions?: readonly string[];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Run one read-only query through every layer and return capped rows. A column `redaction.column`
|
|
107
|
+
* masks is refused wherever the plan uses it (selected under any alias, inside an expression, in a
|
|
108
|
+
* filter, or within a whole row); a matching result name is masked as well.
|
|
109
|
+
*/
|
|
110
|
+
export declare function queryPostgres(client: PostgresClient, sql: string, options: PostgresQueryOptions): Promise<DbRows | DbRefusal>;
|
|
111
|
+
/** `EXPLAIN (FORMAT JSON)` for one query, after the same layers as {@link queryPostgres}. */
|
|
112
|
+
export declare function explainPostgres(client: PostgresClient, sql: string, options: PostgresQueryOptions & {
|
|
113
|
+
readonly analyze?: boolean;
|
|
114
|
+
}): Promise<DbPlan | DbRefusal>;
|
|
115
|
+
/** Options for {@link readPostgresSchema}. */
|
|
116
|
+
export interface PostgresSchemaOptions extends PostgresScope {
|
|
117
|
+
readonly timeoutMs: number;
|
|
118
|
+
/** Describe only this table (`name` or `schema.name`). */
|
|
119
|
+
readonly table?: string | undefined;
|
|
120
|
+
readonly redaction?: DbRedaction;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Describe the readable tables and views in the allowed schemas from `pg_catalog`, with queries this
|
|
124
|
+
* module writes (no caller SQL runs), so it works on any role: columns, primary keys, foreign keys,
|
|
125
|
+
* indexes and the planner's row estimate.
|
|
126
|
+
*/
|
|
127
|
+
export declare function readPostgresSchema(client: PostgresClient, options: PostgresSchemaOptions): Promise<DbSchemaReport | DbRefusal>;
|
|
128
|
+
/** Options for {@link postgresRoleSql}. */
|
|
129
|
+
export interface PostgresRoleSqlOptions extends PostgresScope {
|
|
130
|
+
readonly timeoutMs: number;
|
|
131
|
+
/** The role to create (default `nifra_dev_reader`). */
|
|
132
|
+
readonly role?: string;
|
|
133
|
+
}
|
|
134
|
+
/** The SQL that creates a read-only role for this database, for the developer to run themselves. */
|
|
135
|
+
export interface PostgresRoleSql {
|
|
136
|
+
readonly role: string;
|
|
137
|
+
readonly database: string;
|
|
138
|
+
readonly serverVersionNum: number;
|
|
139
|
+
readonly statements: readonly string[];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Write the SQL for a login role that can read the allowed schemas and nothing else: `pg_read_all_data`
|
|
143
|
+
* on Postgres 14+ when nothing is excluded; otherwise `GRANT SELECT` per schema plus default
|
|
144
|
+
* privileges, and a `REVOKE` for each excluded table and its partitions (`pg_read_all_data` would
|
|
145
|
+
* override a REVOKE, so it is not used then). Nothing is executed.
|
|
146
|
+
*/
|
|
147
|
+
export declare function postgresRoleSql(client: PostgresClient, options: PostgresRoleSqlOptions): Promise<PostgresRoleSql | DbRefusal>;
|
|
148
|
+
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"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"}
|