pg-admin-mcp 0.0.0-stage → 0.1.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 +608 -2
- package/dist/config/env.js +67 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/profiles.js +257 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/core/audit-context.js +21 -0
- package/dist/core/audit-context.js.map +1 -0
- package/dist/core/audit.js +91 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/brands.js +24 -0
- package/dist/core/brands.js.map +1 -0
- package/dist/core/classifier.js +279 -0
- package/dist/core/classifier.js.map +1 -0
- package/dist/core/errors.js +197 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/format.js +258 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/guards.js +513 -0
- package/dist/core/guards.js.map +1 -0
- package/dist/core/identifiers.js +431 -0
- package/dist/core/identifiers.js.map +1 -0
- package/dist/core/limits.js +79 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/logger.js +34 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/paginate.js +195 -0
- package/dist/core/paginate.js.map +1 -0
- package/dist/core/paths.js +392 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/policy.js +98 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/shutdown.js +10 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/stdout-guard.js +25 -0
- package/dist/core/stdout-guard.js.map +1 -0
- package/dist/db/catalog.js +1006 -0
- package/dist/db/catalog.js.map +1 -0
- package/dist/db/pool.js +158 -0
- package/dist/db/pool.js.map +1 -0
- package/dist/db/session.js +258 -0
- package/dist/db/session.js.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/index.js +88 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/runtime/binaries.js +356 -0
- package/dist/runtime/binaries.js.map +1 -0
- package/dist/runtime/env-file.js +62 -0
- package/dist/runtime/env-file.js.map +1 -0
- package/dist/runtime/environment.js +34 -0
- package/dist/runtime/environment.js.map +1 -0
- package/dist/server.js +106 -0
- package/dist/server.js.map +1 -0
- package/dist/snapshot/capture.js +189 -0
- package/dist/snapshot/capture.js.map +1 -0
- package/dist/snapshot/diff.js +254 -0
- package/dist/snapshot/diff.js.map +1 -0
- package/dist/snapshot/migrate.js +1596 -0
- package/dist/snapshot/migrate.js.map +1 -0
- package/dist/snapshot/model.js +80 -0
- package/dist/snapshot/model.js.map +1 -0
- package/dist/tools/_archive_meta.js +138 -0
- package/dist/tools/_archive_meta.js.map +1 -0
- package/dist/tools/_batch_shared.js +73 -0
- package/dist/tools/_batch_shared.js.map +1 -0
- package/dist/tools/_export_format.js +107 -0
- package/dist/tools/_export_format.js.map +1 -0
- package/dist/tools/_object_kinds.js +97 -0
- package/dist/tools/_object_kinds.js.map +1 -0
- package/dist/tools/_relation_detail.js +55 -0
- package/dist/tools/_relation_detail.js.map +1 -0
- package/dist/tools/_shared.js +17 -0
- package/dist/tools/_shared.js.map +1 -0
- package/dist/tools/_snapshot_io.js +238 -0
- package/dist/tools/_snapshot_io.js.map +1 -0
- package/dist/tools/admin/_shared.js +18 -0
- package/dist/tools/admin/_shared.js.map +1 -0
- package/dist/tools/admin/extensions.js +80 -0
- package/dist/tools/admin/extensions.js.map +1 -0
- package/dist/tools/admin/grants.js +207 -0
- package/dist/tools/admin/grants.js.map +1 -0
- package/dist/tools/admin/index.js +138 -0
- package/dist/tools/admin/index.js.map +1 -0
- package/dist/tools/admin/maintenance.js +106 -0
- package/dist/tools/admin/maintenance.js.map +1 -0
- package/dist/tools/admin/roles.js +144 -0
- package/dist/tools/admin/roles.js.map +1 -0
- package/dist/tools/admin/sessions.js +104 -0
- package/dist/tools/admin/sessions.js.map +1 -0
- package/dist/tools/admin/stats.js +63 -0
- package/dist/tools/admin/stats.js.map +1 -0
- package/dist/tools/backup.js +288 -0
- package/dist/tools/backup.js.map +1 -0
- package/dist/tools/connection_status.js +138 -0
- package/dist/tools/connection_status.js.map +1 -0
- package/dist/tools/copy.js +355 -0
- package/dist/tools/copy.js.map +1 -0
- package/dist/tools/delete_batch.js +73 -0
- package/dist/tools/delete_batch.js.map +1 -0
- package/dist/tools/describe_object.js +87 -0
- package/dist/tools/describe_object.js.map +1 -0
- package/dist/tools/execute_sql.js +395 -0
- package/dist/tools/execute_sql.js.map +1 -0
- package/dist/tools/explain.js +134 -0
- package/dist/tools/explain.js.map +1 -0
- package/dist/tools/export.js +215 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/get_schema.js +148 -0
- package/dist/tools/get_schema.js.map +1 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/insert_batch.js +212 -0
- package/dist/tools/insert_batch.js.map +1 -0
- package/dist/tools/list_objects.js +160 -0
- package/dist/tools/list_objects.js.map +1 -0
- package/dist/tools/prepare_migration.js +112 -0
- package/dist/tools/prepare_migration.js.map +1 -0
- package/dist/tools/restore.js +301 -0
- package/dist/tools/restore.js.map +1 -0
- package/dist/tools/schema_diff.js +79 -0
- package/dist/tools/schema_diff.js.map +1 -0
- package/dist/tools/schema_snapshot.js +57 -0
- package/dist/tools/schema_snapshot.js.map +1 -0
- package/dist/tools/select.js +162 -0
- package/dist/tools/select.js.map +1 -0
- package/dist/tools/update_batch.js +154 -0
- package/dist/tools/update_batch.js.map +1 -0
- package/package.json +47 -5
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { listSchemas } from '../db/catalog.js';
|
|
2
|
+
import { withSession } from '../db/session.js';
|
|
3
|
+
import { unsafeBrand } from './brands.js';
|
|
4
|
+
import { PgMcpError } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* The single legitimate producer of {@link SafeIdent}.
|
|
7
|
+
*
|
|
8
|
+
* Every identifier this server ever puts into SQL text (schema, table, column,
|
|
9
|
+
* …) is resolved against `pg_catalog` here first — **never `information_schema`**,
|
|
10
|
+
* which silently drops partial-index predicates and row-level-security policies
|
|
11
|
+
* (design.md risk 5). A name that does not exist in the catalog can never become
|
|
12
|
+
* a `SafeIdent`; it throws `IDENTIFIER_UNKNOWN` instead, with near-miss
|
|
13
|
+
* suggestions so the caller (an LLM agent) can self-correct without a round trip
|
|
14
|
+
* to `postgres_list_objects`.
|
|
15
|
+
*/
|
|
16
|
+
const CANDIDATE_RELKINDS = ['r', 'p', 'v', 'm', 'f'];
|
|
17
|
+
/** `pg_class.relkind` -> the public `RelationKind` this server exposes. */
|
|
18
|
+
const RELKIND_MAP = {
|
|
19
|
+
r: 'table',
|
|
20
|
+
p: 'partitioned_table',
|
|
21
|
+
v: 'view',
|
|
22
|
+
m: 'materialized_view',
|
|
23
|
+
f: 'foreign_table',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Quotes a catalog-returned identifier for safe interpolation into SQL text.
|
|
27
|
+
* Doubles embedded `"` (PostgreSQL's own escaping rule) and wraps in `"..."`
|
|
28
|
+
* unconditionally — that also documents at every call site that the value
|
|
29
|
+
* originated from the catalog, not from user input.
|
|
30
|
+
*
|
|
31
|
+
* Only ever call this on a string that just came back from `pg_catalog` — see
|
|
32
|
+
* `core/brands.ts` for why a raw user-supplied string must never reach here.
|
|
33
|
+
*/
|
|
34
|
+
export function quoteIdent(name) {
|
|
35
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
36
|
+
}
|
|
37
|
+
/** Levenshtein edit distance — cheap, dependency-free, good enough for a handful of near-miss suggestions. */
|
|
38
|
+
function levenshtein(a, b) {
|
|
39
|
+
const rows = a.length + 1;
|
|
40
|
+
const cols = b.length + 1;
|
|
41
|
+
// Single flat array, row-major: prev/curr are just index offsets. Avoids
|
|
42
|
+
// allocating `rows` sub-arrays for what is always a short identifier.
|
|
43
|
+
let prev = Array.from({ length: cols }, (_, j) => j);
|
|
44
|
+
let curr = new Array(cols).fill(0);
|
|
45
|
+
for (let i = 1; i < rows; i++) {
|
|
46
|
+
curr[0] = i;
|
|
47
|
+
for (let j = 1; j < cols; j++) {
|
|
48
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
49
|
+
curr[j] = Math.min((prev[j] ?? 0) + 1, // deletion
|
|
50
|
+
(curr[j - 1] ?? 0) + 1, // insertion
|
|
51
|
+
(prev[j - 1] ?? 0) + cost);
|
|
52
|
+
}
|
|
53
|
+
[prev, curr] = [curr, prev];
|
|
54
|
+
}
|
|
55
|
+
return prev[cols - 1] ?? 0;
|
|
56
|
+
}
|
|
57
|
+
/** Small enough to feel like a typo, not a different word — three edits on a short identifier is usually unrelated. */
|
|
58
|
+
const MAX_SUGGESTION_DISTANCE = 3;
|
|
59
|
+
const MAX_SUGGESTIONS = 5;
|
|
60
|
+
/**
|
|
61
|
+
* Ranks `candidates` by edit distance to `target` and returns the closest
|
|
62
|
+
* handful. Pure and exported standalone so unit tests can check ranking
|
|
63
|
+
* without a database.
|
|
64
|
+
*/
|
|
65
|
+
export function suggestSimilar(target, candidates, limit = MAX_SUGGESTIONS) {
|
|
66
|
+
const needle = target.toLowerCase();
|
|
67
|
+
return candidates
|
|
68
|
+
.map((c) => ({ name: c, distance: levenshtein(needle, c.toLowerCase()) }))
|
|
69
|
+
.filter((c) => c.distance <= MAX_SUGGESTION_DISTANCE)
|
|
70
|
+
.sort((x, y) => x.distance - y.distance || x.name.localeCompare(y.name))
|
|
71
|
+
.slice(0, limit)
|
|
72
|
+
.map((c) => c.name);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Schemas PostgreSQL itself owns — excluded from "all schemas" whenever a
|
|
76
|
+
* profile leaves `schemas` unset, so an unscoped profile does not accidentally
|
|
77
|
+
* expose `pg_catalog`/`information_schema` internals through `list_objects`.
|
|
78
|
+
*/
|
|
79
|
+
const SYSTEM_SCHEMAS = new Set(['pg_catalog', 'information_schema', 'pg_toast']);
|
|
80
|
+
function isSystemSchema(name) {
|
|
81
|
+
return SYSTEM_SCHEMAS.has(name) || name.startsWith('pg_toast_temp') || name.startsWith('pg_temp');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Enforces `Profile.schemas` (unset = every non-system schema is in scope).
|
|
85
|
+
* Throws `IDENTIFIER_UNKNOWN` — from the caller's perspective an out-of-scope
|
|
86
|
+
* schema and a nonexistent one look identical, and treating scope as a
|
|
87
|
+
* separate error code would leak which schemas exist on the server to a
|
|
88
|
+
* caller who is not allowed to see them.
|
|
89
|
+
*/
|
|
90
|
+
export function assertSchemaAllowed(profile, schema) {
|
|
91
|
+
if (profile.schemas === undefined) {
|
|
92
|
+
if (!isSystemSchema(schema))
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
else if (profile.schemas.includes(schema)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const allowed = profile.schemas ?? [];
|
|
99
|
+
throw new PgMcpError({
|
|
100
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
101
|
+
message: `schema "${schema}" is not accessible on profile "${profile.alias}"`,
|
|
102
|
+
hint: allowed.length > 0
|
|
103
|
+
? `this profile is scoped to: ${allowed.join(', ')} — use one of those`
|
|
104
|
+
: `run postgres_list_objects on profile "${profile.alias}" to see the schemas actually in scope`,
|
|
105
|
+
details: { profile: profile.alias, schema, allowedSchemas: allowed },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async function lookupRelation(client, schema, name) {
|
|
109
|
+
const { rows } = await client.query(`SELECT c.oid::text AS oid, n.nspname AS schema, c.relname AS name, c.relkind AS relkind
|
|
110
|
+
FROM pg_catalog.pg_class c
|
|
111
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
112
|
+
WHERE n.nspname = $1
|
|
113
|
+
AND c.relname = $2
|
|
114
|
+
AND c.relkind = ANY($3::"char"[])`, [schema, name, CANDIDATE_RELKINDS]);
|
|
115
|
+
return rows[0];
|
|
116
|
+
}
|
|
117
|
+
async function schemaCandidates(client, profile) {
|
|
118
|
+
if (profile.schemas !== undefined)
|
|
119
|
+
return [...profile.schemas];
|
|
120
|
+
const { rows } = await client.query(`SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname !~ '^pg_' AND nspname <> 'information_schema'`);
|
|
121
|
+
return rows.map((r) => r.nspname);
|
|
122
|
+
}
|
|
123
|
+
async function relationCandidates(client, schemas) {
|
|
124
|
+
if (schemas.length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
const { rows } = await client.query(`SELECT c.relname
|
|
127
|
+
FROM pg_catalog.pg_class c
|
|
128
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
129
|
+
WHERE n.nspname = ANY($1)
|
|
130
|
+
AND c.relkind = ANY($2::"char"[])`, [schemas, CANDIDATE_RELKINDS]);
|
|
131
|
+
return rows.map((r) => r.relname);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolves `schema.name` against `pg_catalog` and brands both parts as
|
|
135
|
+
* {@link SafeIdent}. Runs a single-shot lookup via `withSession` — deliberately
|
|
136
|
+
* outside any guard/dry-run transaction, since identifier resolution is a
|
|
137
|
+
* read-only step that happens *before* `BEGIN` in the dry-run protocol
|
|
138
|
+
* (design.md).
|
|
139
|
+
*
|
|
140
|
+
* Unknown identifier -> `IDENTIFIER_UNKNOWN` with near-miss suggestions drawn
|
|
141
|
+
* from whichever level actually has candidates: if the schema itself has no
|
|
142
|
+
* match, suggestions are schema names; if the schema is fine, suggestions are
|
|
143
|
+
* relation names within it.
|
|
144
|
+
*/
|
|
145
|
+
export async function resolveRelation(profile, schema, name) {
|
|
146
|
+
return withSession(profile, async (client) => {
|
|
147
|
+
assertSchemaAllowed(profile, schema);
|
|
148
|
+
const row = await lookupRelation(client, schema, name);
|
|
149
|
+
if (row !== undefined) {
|
|
150
|
+
const kind = RELKIND_MAP[row.relkind];
|
|
151
|
+
return {
|
|
152
|
+
oid: Number(row.oid),
|
|
153
|
+
schema: unsafeBrand(quoteIdent(row.schema)),
|
|
154
|
+
name: unsafeBrand(quoteIdent(row.name)),
|
|
155
|
+
kind,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const schemas = await schemaCandidates(client, profile);
|
|
159
|
+
const schemaExists = schemas.includes(schema);
|
|
160
|
+
const suggestions = schemaExists
|
|
161
|
+
? suggestSimilar(name, await relationCandidates(client, [schema]))
|
|
162
|
+
: suggestSimilar(schema, schemas);
|
|
163
|
+
throw new PgMcpError({
|
|
164
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
165
|
+
message: schemaExists
|
|
166
|
+
? `relation "${schema}.${name}" does not exist`
|
|
167
|
+
: `schema "${schema}" does not exist`,
|
|
168
|
+
hint: suggestions.length > 0
|
|
169
|
+
? `did you mean: ${suggestions.join(', ')}? Run postgres_list_objects to see the full list`
|
|
170
|
+
: 'run postgres_list_objects to see the exact schema/table names available on this profile',
|
|
171
|
+
details: { profile: profile.alias, schema, name, suggestions },
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolves the schema scope for `list_objects`/`get_schema`: an explicit
|
|
177
|
+
* `requested` list (validated one-by-one via {@link assertSchemaAllowed}), or
|
|
178
|
+
* `Profile.schemas` when unset, or every non-system schema when neither is
|
|
179
|
+
* set. Takes an already-checked-out `client` (same convention as `db/catalog.ts`)
|
|
180
|
+
* so callers can batch it with their other catalog calls in one session.
|
|
181
|
+
*/
|
|
182
|
+
export async function resolveSchemaScope(client, profile, requested) {
|
|
183
|
+
if (requested !== undefined) {
|
|
184
|
+
for (const schema of requested)
|
|
185
|
+
assertSchemaAllowed(profile, schema);
|
|
186
|
+
return [...requested];
|
|
187
|
+
}
|
|
188
|
+
if (profile.schemas !== undefined)
|
|
189
|
+
return [...profile.schemas];
|
|
190
|
+
const schemas = await listSchemas(client);
|
|
191
|
+
return schemas.map((s) => s.name);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Builds a {@link RelationRef} directly from data that already came out of a
|
|
195
|
+
* `pg_catalog` listing (e.g. `listRelations`) rather than re-resolving it via
|
|
196
|
+
* {@link resolveRelation} — saves a redundant round trip when `get_schema` is
|
|
197
|
+
* enriching dozens/hundreds of relations it just listed. The caller vouches
|
|
198
|
+
* that `schema`/`name`/`kind` are catalog-sourced, not raw user input; this
|
|
199
|
+
* does the exact same `unsafeBrand(quoteIdent(...))` transform `lookupRelation`
|
|
200
|
+
* above does for its own catalog rows.
|
|
201
|
+
*/
|
|
202
|
+
export function relationRefFromCatalog(oid, schema, name, kind) {
|
|
203
|
+
return { oid, schema: unsafeBrand(quoteIdent(schema)), name: unsafeBrand(quoteIdent(name)), kind };
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Brands a column name that already came from `db/catalog.ts#getColumns`
|
|
207
|
+
* (trusted, catalog-sourced) — the column-level counterpart to
|
|
208
|
+
* {@link relationRefFromCatalog}.
|
|
209
|
+
*/
|
|
210
|
+
export function columnIdentFromCatalog(name) {
|
|
211
|
+
return unsafeBrand(quoteIdent(name));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Validates a column name against a relation's real columns and brands it —
|
|
215
|
+
* the column-level counterpart to {@link resolveRelation}. `knownColumns`
|
|
216
|
+
* comes from `db/catalog.ts#getColumns` for the exact relation being
|
|
217
|
+
* queried, so this never issues SQL of its own. An unknown column throws
|
|
218
|
+
* `IDENTIFIER_UNKNOWN` with near-miss suggestions, never a raw SQL error —
|
|
219
|
+
* `postgres_select` relies on this running for every column name (`columns`,
|
|
220
|
+
* `where[].column`, `orderBy[].column`) before any of them reach SQL text.
|
|
221
|
+
*/
|
|
222
|
+
export function resolveColumn(schema, table, columnName, knownColumns) {
|
|
223
|
+
if (!knownColumns.includes(columnName)) {
|
|
224
|
+
const suggestions = suggestSimilar(columnName, knownColumns);
|
|
225
|
+
throw new PgMcpError({
|
|
226
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
227
|
+
message: `column "${columnName}" does not exist on "${schema}.${table}"`,
|
|
228
|
+
hint: suggestions.length > 0
|
|
229
|
+
? `did you mean: ${suggestions.join(', ')}? Run postgres_describe_object on "${schema}.${table}" to see the exact column list`
|
|
230
|
+
: `run postgres_describe_object on "${schema}.${table}" to see the exact column list`,
|
|
231
|
+
details: { schema, table, column: columnName, suggestions },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return columnIdentFromCatalog(columnName);
|
|
235
|
+
}
|
|
236
|
+
async function roleCandidates(client) {
|
|
237
|
+
const { rows } = await client.query(`SELECT rolname FROM pg_catalog.pg_roles`);
|
|
238
|
+
return rows.map((r) => r.rolname);
|
|
239
|
+
}
|
|
240
|
+
/** Resolves an existing role by name against `pg_roles` — the role-level counterpart to {@link resolveRelation}. */
|
|
241
|
+
export async function resolveRole(profile, name) {
|
|
242
|
+
return withSession(profile, async (client) => {
|
|
243
|
+
const { rows } = await client.query(`SELECT oid::text AS oid, rolname FROM pg_catalog.pg_roles WHERE rolname = $1`, [name]);
|
|
244
|
+
const row = rows[0];
|
|
245
|
+
if (row !== undefined)
|
|
246
|
+
return { oid: Number(row.oid), name: unsafeBrand(quoteIdent(row.rolname)) };
|
|
247
|
+
const suggestions = suggestSimilar(name, await roleCandidates(client));
|
|
248
|
+
throw new PgMcpError({
|
|
249
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
250
|
+
message: `role "${name}" does not exist`,
|
|
251
|
+
hint: suggestions.length > 0
|
|
252
|
+
? `did you mean: ${suggestions.join(', ')}? Run postgres_admin with op "roles_list" to see the full list`
|
|
253
|
+
: 'run postgres_admin with op "roles_list" to see the exact role names available',
|
|
254
|
+
details: { profile: profile.alias, role: name, suggestions },
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* For `roles_create`: confirms no existing role uses `name`, then quotes and
|
|
260
|
+
* brands it — the "does not yet exist" counterpart to {@link resolveRole}.
|
|
261
|
+
* Quoting (not the existence check) is what makes this injection-safe; the
|
|
262
|
+
* check is a UX precondition, a readable "already exists" error instead of
|
|
263
|
+
* PostgreSQL's raw `42710 duplicate_object`.
|
|
264
|
+
*/
|
|
265
|
+
export async function assertRoleAvailable(profile, name) {
|
|
266
|
+
return withSession(profile, async (client) => {
|
|
267
|
+
const { rows } = await client.query(`SELECT rolname FROM pg_catalog.pg_roles WHERE rolname = $1`, [name]);
|
|
268
|
+
if (rows.length > 0) {
|
|
269
|
+
throw new PgMcpError({
|
|
270
|
+
code: 'PG_ERROR',
|
|
271
|
+
message: `role "${name}" already exists`,
|
|
272
|
+
hint: 'use op "roles_alter" to change an existing role, or pick a different name',
|
|
273
|
+
details: { profile: profile.alias, role: name },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return unsafeBrand(quoteIdent(name));
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async function extensionCandidates(client, source) {
|
|
280
|
+
const col = source === 'pg_extension' ? 'extname' : 'name';
|
|
281
|
+
const { rows } = await client.query(`SELECT ${col} AS n FROM pg_catalog.${source}`);
|
|
282
|
+
return rows.map((r) => r.n);
|
|
283
|
+
}
|
|
284
|
+
/** Resolves an installed extension name against `pg_extension` — used by `extensions_drop`. */
|
|
285
|
+
export async function resolveInstalledExtension(profile, name) {
|
|
286
|
+
return withSession(profile, async (client) => {
|
|
287
|
+
const { rows } = await client.query(`SELECT extname FROM pg_catalog.pg_extension WHERE extname = $1`, [name]);
|
|
288
|
+
const row = rows[0];
|
|
289
|
+
if (row !== undefined)
|
|
290
|
+
return unsafeBrand(quoteIdent(row.extname));
|
|
291
|
+
const suggestions = suggestSimilar(name, await extensionCandidates(client, 'pg_extension'));
|
|
292
|
+
throw new PgMcpError({
|
|
293
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
294
|
+
message: `extension "${name}" is not installed`,
|
|
295
|
+
hint: suggestions.length > 0
|
|
296
|
+
? `did you mean: ${suggestions.join(', ')}? Run postgres_admin with op "extensions_list" to see installed extensions`
|
|
297
|
+
: 'run postgres_admin with op "extensions_list" to see installed extensions',
|
|
298
|
+
details: { profile: profile.alias, extension: name, suggestions },
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Resolves an extension name against `pg_available_extensions` — used by
|
|
304
|
+
* `extensions_create`. Validating against the available list (rather than
|
|
305
|
+
* just quoting) catches a typo before `CREATE EXTENSION` runs arbitrary
|
|
306
|
+
* install-script SQL as superuser.
|
|
307
|
+
*/
|
|
308
|
+
export async function resolveAvailableExtension(profile, name) {
|
|
309
|
+
return withSession(profile, async (client) => {
|
|
310
|
+
const { rows } = await client.query(`SELECT name FROM pg_catalog.pg_available_extensions WHERE name = $1`, [name]);
|
|
311
|
+
const row = rows[0];
|
|
312
|
+
if (row !== undefined)
|
|
313
|
+
return unsafeBrand(quoteIdent(row.name));
|
|
314
|
+
const suggestions = suggestSimilar(name, await extensionCandidates(client, 'pg_available_extensions'));
|
|
315
|
+
throw new PgMcpError({
|
|
316
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
317
|
+
message: `extension "${name}" is not available on this server (not found in pg_available_extensions)`,
|
|
318
|
+
hint: suggestions.length > 0 ? `did you mean: ${suggestions.join(', ')}?` : "the extension's control file is not installed on this PostgreSQL server; ask an operator to install the OS package",
|
|
319
|
+
details: { profile: profile.alias, extension: name, suggestions },
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/** Resolves `schema.name` against `pg_class` where `relkind = 'S'` — `resolveRelation` deliberately excludes sequences (see `CANDIDATE_RELKINDS`), so `grants` needs its own lookup for `GRANT ... ON SEQUENCE`. */
|
|
324
|
+
export async function resolveSequence(profile, schema, name) {
|
|
325
|
+
return withSession(profile, async (client) => {
|
|
326
|
+
assertSchemaAllowed(profile, schema);
|
|
327
|
+
const { rows } = await client.query(`SELECT c.oid::text AS oid, n.nspname AS schema, c.relname AS name
|
|
328
|
+
FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
329
|
+
WHERE n.nspname = $1 AND c.relname = $2 AND c.relkind = 'S'`, [schema, name]);
|
|
330
|
+
const row = rows[0];
|
|
331
|
+
if (row !== undefined)
|
|
332
|
+
return { oid: Number(row.oid), schema: unsafeBrand(quoteIdent(row.schema)), name: unsafeBrand(quoteIdent(row.name)) };
|
|
333
|
+
const { rows: cand } = await client.query(`SELECT c.relname FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relkind = 'S'`, [schema]);
|
|
334
|
+
const suggestions = suggestSimilar(name, cand.map((r) => r.relname));
|
|
335
|
+
throw new PgMcpError({
|
|
336
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
337
|
+
message: `sequence "${schema}.${name}" does not exist`,
|
|
338
|
+
hint: suggestions.length > 0 ? `did you mean: ${suggestions.join(', ')}?` : 'run postgres_list_objects to see the exact sequence names available',
|
|
339
|
+
details: { profile: profile.alias, schema, name, suggestions },
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/** Resolves `schema.name` against `pg_class` where `relkind = 'i'` — used by `maintenance_reindex`'s `target: 'index'` form. */
|
|
344
|
+
export async function resolveIndex(profile, schema, name) {
|
|
345
|
+
return withSession(profile, async (client) => {
|
|
346
|
+
assertSchemaAllowed(profile, schema);
|
|
347
|
+
const { rows } = await client.query(`SELECT c.oid::text AS oid, n.nspname AS schema, c.relname AS name
|
|
348
|
+
FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
349
|
+
WHERE n.nspname = $1 AND c.relname = $2 AND c.relkind = 'i'`, [schema, name]);
|
|
350
|
+
const row = rows[0];
|
|
351
|
+
if (row !== undefined)
|
|
352
|
+
return { oid: Number(row.oid), schema: unsafeBrand(quoteIdent(row.schema)), name: unsafeBrand(quoteIdent(row.name)) };
|
|
353
|
+
const { rows: cand } = await client.query(`SELECT c.relname FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relkind = 'i'`, [schema]);
|
|
354
|
+
const suggestions = suggestSimilar(name, cand.map((r) => r.relname));
|
|
355
|
+
throw new PgMcpError({
|
|
356
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
357
|
+
message: `index "${schema}.${name}" does not exist`,
|
|
358
|
+
hint: suggestions.length > 0 ? `did you mean: ${suggestions.join(', ')}?` : 'run postgres_list_objects to see the exact index names available',
|
|
359
|
+
details: { profile: profile.alias, schema, name, suggestions },
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** Resolves an existing schema name against `pg_namespace` (after the usual scope check) and brands it — used by `GRANT ... ON SCHEMA`. */
|
|
364
|
+
export async function resolveSchemaIdent(profile, schema) {
|
|
365
|
+
return withSession(profile, async (client) => {
|
|
366
|
+
assertSchemaAllowed(profile, schema);
|
|
367
|
+
const { rows } = await client.query(`SELECT oid::text AS oid, nspname FROM pg_catalog.pg_namespace WHERE nspname = $1`, [schema]);
|
|
368
|
+
const row = rows[0];
|
|
369
|
+
if (row !== undefined)
|
|
370
|
+
return { oid: Number(row.oid), name: unsafeBrand(quoteIdent(row.nspname)) };
|
|
371
|
+
throw new PgMcpError({
|
|
372
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
373
|
+
message: `schema "${schema}" does not exist`,
|
|
374
|
+
hint: 'run postgres_list_objects to see the exact schema names available',
|
|
375
|
+
details: { profile: profile.alias, schema },
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/** The current database's own row in `pg_database`, branded — `GRANT ... ON DATABASE` always targets the profile's own database, never a caller-supplied name, so there is nothing to validate beyond confirming the round trip works. */
|
|
380
|
+
export async function resolveCurrentDatabase(profile) {
|
|
381
|
+
return withSession(profile, async (client) => {
|
|
382
|
+
const { rows } = await client.query(`SELECT oid::text AS oid, datname FROM pg_catalog.pg_database WHERE datname = current_database()`);
|
|
383
|
+
const row = rows[0];
|
|
384
|
+
if (row === undefined) {
|
|
385
|
+
throw new PgMcpError({
|
|
386
|
+
code: 'PG_ERROR',
|
|
387
|
+
message: 'could not resolve the current database in pg_database',
|
|
388
|
+
hint: 'this should never happen on a reachable connection; report it',
|
|
389
|
+
details: { profile: profile.alias },
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return { oid: Number(row.oid), name: unsafeBrand(quoteIdent(row.datname)) };
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Resolves `schema.name` to exactly one function's full signature. PostgreSQL
|
|
397
|
+
* allows overloading, and `GRANT ON FUNCTION` needs the argument types to
|
|
398
|
+
* disambiguate — when more than one overload matches, this refuses rather
|
|
399
|
+
* than guessing, and points at `postgres_execute_sql` (which can take the
|
|
400
|
+
* exact signature the caller wants) instead.
|
|
401
|
+
*/
|
|
402
|
+
export async function resolveFunctionSignature(profile, schema, name) {
|
|
403
|
+
return withSession(profile, async (client) => {
|
|
404
|
+
assertSchemaAllowed(profile, schema);
|
|
405
|
+
const { rows } = await client.query(`SELECT p.oid::text AS oid, pg_catalog.pg_get_function_identity_arguments(p.oid) AS arg_types
|
|
406
|
+
FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
407
|
+
WHERE n.nspname = $1 AND p.proname = $2`, [schema, name]);
|
|
408
|
+
if (rows.length === 0) {
|
|
409
|
+
throw new PgMcpError({
|
|
410
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
411
|
+
message: `function "${schema}.${name}" does not exist`,
|
|
412
|
+
hint: 'run postgres_list_objects to see the exact function names available',
|
|
413
|
+
details: { profile: profile.alias, schema, name },
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
if (rows.length > 1) {
|
|
417
|
+
const overloads = rows.map((r) => `${name}(${r.arg_types})`);
|
|
418
|
+
throw new PgMcpError({
|
|
419
|
+
code: 'IDENTIFIER_UNKNOWN',
|
|
420
|
+
message: `"${schema}.${name}" is overloaded (${rows.length} signatures) — postgres_admin cannot tell which one you mean`,
|
|
421
|
+
hint: `use postgres_execute_sql naming the exact signature, e.g. one of: ${overloads.join(', ')}`,
|
|
422
|
+
details: { profile: profile.alias, schema, name, overloads },
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const row = rows[0];
|
|
426
|
+
if (row === undefined)
|
|
427
|
+
throw new Error('unreachable: length === 1 guaranteed a row');
|
|
428
|
+
return { oid: Number(row.oid), signature: unsafeBrand(`${quoteIdent(schema)}.${quoteIdent(name)}(${row.arg_types})`) };
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
//# sourceMappingURL=identifiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.js","sourceRoot":"","sources":["../../src/core/identifiers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readInt } from '../config/env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Central output caps — resolved once per tool call as
|
|
4
|
+
* `min(global, per-profile, per-call)`; the tightest of the three always wins.
|
|
5
|
+
*
|
|
6
|
+
* **Row caps.** `config/profiles.ts#buildProfile` already resolves
|
|
7
|
+
* `min(PG_MCP_MAX_ROWS, PG_<ALIAS>_MAX_ROWS)` into `Profile.maxRows` — that
|
|
8
|
+
* number IS the ceiling this module receives, so `resolveLimits` only has to
|
|
9
|
+
* apply the optional per-call value on top of it.
|
|
10
|
+
*
|
|
11
|
+
* **Byte caps.** There is no per-profile byte override (`docs/design.md`
|
|
12
|
+
* §"Env var contract" lists only the global `PG_MCP_MAX_BYTES`, no
|
|
13
|
+
* `PG_<ALIAS>_MAX_BYTES`), so the ceiling here is the global value alone —
|
|
14
|
+
* parsed with the shared `readInt` helper from `config/env.ts` rather than a
|
|
15
|
+
* bespoke parse, per the phase brief.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_MAX_ROWS = 500;
|
|
18
|
+
const DEFAULT_MAX_BYTES = 262_144;
|
|
19
|
+
/** A fallback for callers that render outside a resolved-profile context (e.g. tests). Mirrors the documented global defaults. */
|
|
20
|
+
export const DEFAULT_LIMITS = { maxRows: DEFAULT_MAX_ROWS, maxBytes: DEFAULT_MAX_BYTES };
|
|
21
|
+
/** `undefined` or a non-positive request is "no opinion" — falls back to the ceiling untouched, never loosens it. */
|
|
22
|
+
function tighten(ceiling, requested) {
|
|
23
|
+
if (requested === undefined || requested <= 0)
|
|
24
|
+
return ceiling;
|
|
25
|
+
return Math.min(ceiling, requested);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the effective `{ maxRows, maxBytes }` for one tool call.
|
|
29
|
+
*
|
|
30
|
+
* @param profileMaxRows the calling profile's already-resolved `Profile.maxRows`
|
|
31
|
+
* @param call optional per-call caps; each can only tighten its ceiling
|
|
32
|
+
* @param env injectable for tests; defaults to `process.env`
|
|
33
|
+
*/
|
|
34
|
+
export function resolveLimits(profileMaxRows, call = {}, env = process.env) {
|
|
35
|
+
const globalMaxBytes = readInt(env, 'PG_MCP_MAX_BYTES', { default: DEFAULT_MAX_BYTES, min: 1 });
|
|
36
|
+
return {
|
|
37
|
+
maxRows: tighten(profileMaxRows, call.maxRows),
|
|
38
|
+
maxBytes: tighten(globalMaxBytes, call.maxBytes),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Keeps rows up to `caps.maxRows` and `caps.maxBytes`, whichever binds first.
|
|
43
|
+
*
|
|
44
|
+
* **Never serializes past the cap.** Each row is serialized once, in order,
|
|
45
|
+
* to measure its real byte size (`Buffer.byteLength`, not an estimate); as
|
|
46
|
+
* soon as adding it would cross `caps.maxBytes` — or `caps.maxRows` is
|
|
47
|
+
* already met — the loop stops. No row beyond that point is ever touched, so
|
|
48
|
+
* a multi-GB result set is never serialized in full just to discover it is
|
|
49
|
+
* too big.
|
|
50
|
+
*/
|
|
51
|
+
export function truncateRows(rows, caps, serialize) {
|
|
52
|
+
const kept = [];
|
|
53
|
+
let bytes = 0;
|
|
54
|
+
let reason = null;
|
|
55
|
+
for (let i = 0; i < rows.length; i++) {
|
|
56
|
+
if (kept.length >= caps.maxRows) {
|
|
57
|
+
reason = 'rows';
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
// Safe: `i < rows.length` is the loop condition, so this index always exists.
|
|
61
|
+
const row = rows[i];
|
|
62
|
+
const size = Buffer.byteLength(serialize(row), 'utf8');
|
|
63
|
+
if (bytes + size > caps.maxBytes) {
|
|
64
|
+
reason = 'bytes';
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
bytes += size;
|
|
68
|
+
kept.push(row);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
rows: kept,
|
|
72
|
+
truncation: {
|
|
73
|
+
truncatedRows: rows.length - kept.length,
|
|
74
|
+
truncatedBytes: bytes,
|
|
75
|
+
reason,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AAEH,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAOlC,kIAAkI;AAClI,MAAM,CAAC,MAAM,cAAc,GAAmB,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;AAQzG,qHAAqH;AACrH,SAAS,OAAO,CAAC,OAAe,EAAE,SAA6B;IAC7D,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,cAAsB,EACtB,OAAiB,EAAE,EACnB,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAChG,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC;QAC9C,QAAQ,EAAE,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC;KACjD,CAAC;AACJ,CAAC;AAoBD;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAkB,EAClB,IAAoB,EACpB,SAA6B;IAE7B,MAAM,IAAI,GAAQ,EAAE,CAAC;IACrB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAyB,IAAI,CAAC;IAExC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChC,MAAM,GAAG,MAAM,CAAC;YAChB,MAAM;QACR,CAAC;QACD,8EAA8E;QAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAM,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACvD,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjC,MAAM,GAAG,OAAO,CAAC;YACjB,MAAM;QACR,CAAC;QACD,KAAK,IAAI,IAAI,CAAC;QACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI;QACV,UAAU,EAAE;YACV,aAAa,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;YACxC,cAAc,EAAE,KAAK;YACrB,MAAM;SACP;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
/**
|
|
3
|
+
* Tiny leveled logger. **Writes to stderr only, always.**
|
|
4
|
+
*
|
|
5
|
+
* stdout belongs exclusively to the JSON-RPC frame stream; a single stray byte
|
|
6
|
+
* there corrupts the session. Level comes from `PG_MCP_LOG_LEVEL` (default
|
|
7
|
+
* `warn`) and is read per call so tests can flip it without module reloads.
|
|
8
|
+
*/
|
|
9
|
+
const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 };
|
|
10
|
+
function threshold(env) {
|
|
11
|
+
const raw = (env['PG_MCP_LOG_LEVEL'] ?? 'warn').toLowerCase();
|
|
12
|
+
return Object.hasOwn(LEVELS, raw) ? LEVELS[raw] : LEVELS.warn;
|
|
13
|
+
}
|
|
14
|
+
function emit(level, message, extra) {
|
|
15
|
+
if (LEVELS[level] > threshold(process.env))
|
|
16
|
+
return;
|
|
17
|
+
const tail = extra === undefined ? '' : ` ${safeJson(extra)}`;
|
|
18
|
+
process.stderr.write(`[pg-admin-mcp] ${level}: ${message}${tail}\n`);
|
|
19
|
+
}
|
|
20
|
+
function safeJson(value) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(value) ?? String(value);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return String(value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const logger = {
|
|
29
|
+
error: (message, extra) => emit('error', message, extra),
|
|
30
|
+
warn: (message, extra) => emit('warn', message, extra),
|
|
31
|
+
info: (message, extra) => emit('info', message, extra),
|
|
32
|
+
debug: (message, extra) => emit('debug', message, extra),
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC;;;;;;GAMG;AAEH,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAW,CAAC;AAIjE,SAAS,SAAS,CAAC,GAAsB;IACvC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAe,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AAC5E,CAAC;AAED,SAAS,IAAI,CAAC,KAAe,EAAE,OAAe,EAAE,KAAe;IAC7D,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO;IACnD,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,KAAK,KAAK,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,OAAe,EAAE,KAAe,EAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC;IAChF,IAAI,EAAE,CAAC,OAAe,EAAE,KAAe,EAAQ,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;IAC9E,IAAI,EAAE,CAAC,OAAe,EAAE,KAAe,EAAQ,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;IAC9E,KAAK,EAAE,CAAC,OAAe,EAAE,KAAe,EAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC;CACjF,CAAC"}
|