@kazurolabs/psql-explorer 0.1.0 → 0.3.1
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 +229 -18
- package/RELEASES.md +95 -0
- package/dist/cli.js +57 -13
- package/dist/commands/configgen.js +25 -25
- package/dist/commands/explore.js +86 -4
- package/dist/config/load.js +11 -73
- package/dist/config/resolve-environment.js +10 -62
- package/dist/config/resolvers/index.js +1 -1
- package/dist/config/resolvers/prompt.js +2 -2
- package/dist/config/resolvers/vault.js +1 -83
- package/dist/db/store.js +57 -0
- package/dist/environments.js +5 -2
- package/dist/explorer/alter.js +493 -0
- package/dist/explorer/catalog.js +31 -0
- package/dist/explorer/connect-entry.js +29 -18
- package/dist/explorer/cypher-parse.js +478 -0
- package/dist/explorer/cypher.js +963 -0
- package/dist/explorer/ddl.js +502 -0
- package/dist/explorer/dependents.js +222 -0
- package/dist/explorer/design-load.js +254 -0
- package/dist/explorer/design.js +45 -0
- package/dist/explorer/edit.js +108 -0
- package/dist/explorer/explain.js +44 -3
- package/dist/explorer/filter.js +121 -0
- package/dist/explorer/graph.js +679 -0
- package/dist/explorer/gucs.js +31 -0
- package/dist/explorer/profile.js +102 -0
- package/dist/explorer/references.js +95 -0
- package/dist/explorer/rows.js +100 -13
- package/dist/explorer/schema-diff.js +360 -0
- package/dist/explorer/search.js +309 -0
- package/dist/explorer/security.js +152 -0
- package/dist/explorer/seed.js +1376 -0
- package/dist/explorer/session.js +25 -1
- package/dist/explorer/stats.js +135 -0
- package/dist/explorer/timeout.js +13 -0
- package/dist/screens/environments.js +6 -3
- package/dist/screens/explorer.js +0 -3
- package/dist/screens/splash.js +4 -2
- package/dist/web/server.js +655 -22
- package/package.json +4 -1
- package/web/dist/assets/antonio-latin-400-normal-B7iMKlzw.woff +0 -0
- package/web/dist/assets/antonio-latin-400-normal-CZjzwNAN.woff2 +0 -0
- package/web/dist/assets/antonio-latin-700-normal-BY3mCh47.woff +0 -0
- package/web/dist/assets/antonio-latin-700-normal-DSH-lEDG.woff2 +0 -0
- package/web/dist/assets/antonio-latin-ext-400-normal-nE82dizb.woff2 +0 -0
- package/web/dist/assets/antonio-latin-ext-400-normal-qA9Jb92z.woff +0 -0
- package/web/dist/assets/antonio-latin-ext-700-normal-BdSkr58W.woff +0 -0
- package/web/dist/assets/antonio-latin-ext-700-normal-D-dvgqjr.woff2 +0 -0
- package/web/dist/assets/button-BiFx4Lyw.js +1 -0
- package/web/dist/assets/code-xml-CPEjNFOw.js +1 -0
- package/web/dist/assets/compare-view-COQ3yE76.js +2 -0
- package/web/dist/assets/copy-C2N9tUYJ.js +1 -0
- package/web/dist/assets/cypher-view-BzyL4mNU.js +13 -0
- package/web/dist/assets/design-view-BPm7dSTY.js +9 -0
- package/web/dist/assets/erd-view-DqGDws2_.js +1 -0
- package/web/dist/assets/format-sql-CoddYUOF.js +5 -0
- package/web/dist/assets/graph-force-ChCGXQUt.js +1 -0
- package/web/dist/assets/graph-link-C8P9s_0W.js +1 -0
- package/web/dist/assets/graph-view-BpoUrUCP.js +3 -0
- package/web/dist/assets/index-CpHnjRLv.js +10 -0
- package/web/dist/assets/index-OzJqnJWG.css +2 -0
- package/web/dist/assets/key-round-JiGCgCWS.js +1 -0
- package/web/dist/assets/loader-circle-BxDJOoJO.js +1 -0
- package/web/dist/assets/query-builder-view-CtRexMEX.js +4 -0
- package/web/dist/assets/rotate-ccw-CwdA-J3t.js +1 -0
- package/web/dist/assets/route-7eu-iAqD.js +15 -0
- package/web/dist/assets/seed-view-BIkBTiU6.js +7 -0
- package/web/dist/assets/shield-alert-keJIAND5.js +1 -0
- package/web/dist/assets/sliders-horizontal-DKtL_ql7.js +1 -0
- package/web/dist/assets/sql-tabs-DUSXdYCr.js +2 -0
- package/web/dist/assets/table-Drc_n1gF.js +1 -0
- package/web/dist/assets/triangle-alert-C5nF69rW.js +1 -0
- package/web/dist/assets/zone-CHDOrDBF.js +5 -0
- package/web/dist/index.html +9 -2
- package/web/dist/assets/index-BMLFCtFY.js +0 -17
- package/web/dist/assets/index-CMXJRL9O.css +0 -2
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const flag = (value) => value === 't';
|
|
2
|
+
const commands = {
|
|
3
|
+
'*': 'ALL',
|
|
4
|
+
r: 'SELECT',
|
|
5
|
+
a: 'INSERT',
|
|
6
|
+
w: 'UPDATE',
|
|
7
|
+
d: 'DELETE',
|
|
8
|
+
};
|
|
9
|
+
/** Roles of the server that skip row level security: superusers and those with BYPASSRLS. */
|
|
10
|
+
async function bypassRoles(session) {
|
|
11
|
+
const rows = await session.rows(`SELECT rolname AS name FROM pg_catalog.pg_roles
|
|
12
|
+
WHERE (rolsuper OR rolbypassrls) AND rolname !~ '^pg_' ORDER BY rolname`);
|
|
13
|
+
return rows.map((row) => row.name ?? '');
|
|
14
|
+
}
|
|
15
|
+
const relationKinds = {
|
|
16
|
+
r: 'table',
|
|
17
|
+
p: 'partitioned',
|
|
18
|
+
v: 'view',
|
|
19
|
+
m: 'materialized',
|
|
20
|
+
f: 'foreign',
|
|
21
|
+
};
|
|
22
|
+
/** RLS state, policies and grants of a table or view; null when there is no such relation. */
|
|
23
|
+
export async function describeSecurity(session, table) {
|
|
24
|
+
const [relation] = await session.rows(`SELECT c.oid::text AS oid, c.relkind AS kind, pg_catalog.pg_get_userbyid(c.relowner) AS owner,
|
|
25
|
+
c.relrowsecurity AS enabled, c.relforcerowsecurity AS forced,
|
|
26
|
+
coalesce(c.reloptions::text[] && ARRAY['security_invoker=true', 'security_invoker=on',
|
|
27
|
+
'security_invoker=1', 'security_invoker=yes'], false) AS invoker
|
|
28
|
+
FROM pg_catalog.pg_class c
|
|
29
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
30
|
+
WHERE n.nspname = $1 AND c.relname = $2 AND c.relkind IN ('r', 'p', 'v', 'm', 'f')`, [table.schema, table.name]);
|
|
31
|
+
if (!relation?.oid)
|
|
32
|
+
return null;
|
|
33
|
+
const policies = await session.rows(`SELECT p.polname AS name, p.polcmd AS command, p.polpermissive AS permissive,
|
|
34
|
+
(SELECT coalesce(json_agg(CASE WHEN r = 0 THEN 'public'
|
|
35
|
+
ELSE pg_catalog.pg_get_userbyid(r) END), '[]')::text
|
|
36
|
+
FROM unnest(p.polroles) AS r) AS roles,
|
|
37
|
+
pg_catalog.pg_get_expr(p.polqual, p.polrelid) AS qual,
|
|
38
|
+
pg_catalog.pg_get_expr(p.polwithcheck, p.polrelid) AS with_check
|
|
39
|
+
FROM pg_catalog.pg_policy p
|
|
40
|
+
WHERE p.polrelid = $1::oid
|
|
41
|
+
ORDER BY p.polname`, [relation.oid]);
|
|
42
|
+
const grants = await session.rows(`SELECT CASE WHEN a.grantee = 0 THEN 'public' ELSE pg_catalog.pg_get_userbyid(a.grantee) END
|
|
43
|
+
AS grantee,
|
|
44
|
+
json_agg(a.privilege_type ORDER BY a.privilege_type)::text AS privileges
|
|
45
|
+
FROM pg_catalog.pg_class c,
|
|
46
|
+
LATERAL aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) AS a
|
|
47
|
+
WHERE c.oid = $1::oid
|
|
48
|
+
GROUP BY a.grantee
|
|
49
|
+
ORDER BY a.grantee = 0 DESC, 1`, [relation.oid]);
|
|
50
|
+
const kind = relationKinds[relation.kind ?? ''] ?? 'table';
|
|
51
|
+
const sources = kind === 'view' || kind === 'materialized'
|
|
52
|
+
? await session.rows(`SELECT DISTINCT n.nspname AS schema, t.relname AS name,
|
|
53
|
+
pg_catalog.pg_get_userbyid(t.relowner) AS owner,
|
|
54
|
+
t.relrowsecurity AS enabled, t.relforcerowsecurity AS forced,
|
|
55
|
+
(SELECT count(*) FROM pg_catalog.pg_policy p WHERE p.polrelid = t.oid) AS policies
|
|
56
|
+
FROM pg_catalog.pg_rewrite rw
|
|
57
|
+
JOIN pg_catalog.pg_depend d ON d.classid = 'pg_catalog.pg_rewrite'::regclass
|
|
58
|
+
AND d.objid = rw.oid AND d.refclassid = 'pg_catalog.pg_class'::regclass
|
|
59
|
+
JOIN pg_catalog.pg_class t ON t.oid = d.refobjid AND t.relkind IN ('r', 'p')
|
|
60
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
61
|
+
WHERE rw.ev_class = $1::oid AND t.oid <> rw.ev_class
|
|
62
|
+
ORDER BY 1, 2`, [relation.oid])
|
|
63
|
+
: [];
|
|
64
|
+
return {
|
|
65
|
+
kind,
|
|
66
|
+
owner: relation.owner ?? '',
|
|
67
|
+
securityInvoker: flag(relation.invoker),
|
|
68
|
+
sources: sources.map((row) => ({
|
|
69
|
+
schema: row.schema ?? '',
|
|
70
|
+
name: row.name ?? '',
|
|
71
|
+
owner: row.owner ?? '',
|
|
72
|
+
rlsEnabled: flag(row.enabled),
|
|
73
|
+
rlsForced: flag(row.forced),
|
|
74
|
+
policies: Number(row.policies),
|
|
75
|
+
})),
|
|
76
|
+
rlsEnabled: flag(relation.enabled),
|
|
77
|
+
rlsForced: flag(relation.forced),
|
|
78
|
+
policies: policies.map((row) => ({
|
|
79
|
+
name: row.name ?? '',
|
|
80
|
+
command: commands[row.command ?? ''] ?? 'ALL',
|
|
81
|
+
permissive: flag(row.permissive),
|
|
82
|
+
roles: JSON.parse(row.roles ?? '[]'),
|
|
83
|
+
using: row.qual,
|
|
84
|
+
withCheck: row.with_check,
|
|
85
|
+
})),
|
|
86
|
+
grants: grants.map((row) => ({
|
|
87
|
+
grantee: row.grantee ?? '',
|
|
88
|
+
privileges: JSON.parse(row.privileges ?? '[]'),
|
|
89
|
+
})),
|
|
90
|
+
bypassRoles: await bypassRoles(session).catch(() => []),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Who a function runs as and may be run by; null when there is no such function. */
|
|
94
|
+
export async function describeFunctionSecurity(session, ref) {
|
|
95
|
+
const [fn] = await session.rows(`SELECT p.oid::text AS oid, pg_catalog.pg_get_userbyid(p.proowner) AS owner,
|
|
96
|
+
p.prosecdef AS definer, p.proleakproof AS leakproof,
|
|
97
|
+
to_json(coalesce(p.proconfig, '{}'::text[]))::text AS config
|
|
98
|
+
FROM pg_catalog.pg_proc p
|
|
99
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
100
|
+
WHERE n.nspname = $1 AND p.proname = $2
|
|
101
|
+
AND pg_catalog.pg_get_function_identity_arguments(p.oid) = $3`, [ref.schema, ref.name, ref.args]);
|
|
102
|
+
if (!fn?.oid)
|
|
103
|
+
return null;
|
|
104
|
+
const grants = await session.rows(`SELECT CASE WHEN a.grantee = 0 THEN 'public' ELSE pg_catalog.pg_get_userbyid(a.grantee) END
|
|
105
|
+
AS grantee,
|
|
106
|
+
json_agg(a.privilege_type ORDER BY a.privilege_type)::text AS privileges
|
|
107
|
+
FROM pg_catalog.pg_proc p,
|
|
108
|
+
LATERAL aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) AS a
|
|
109
|
+
WHERE p.oid = $1::oid
|
|
110
|
+
GROUP BY a.grantee
|
|
111
|
+
ORDER BY a.grantee = 0 DESC, 1`, [fn.oid]);
|
|
112
|
+
return {
|
|
113
|
+
owner: fn.owner ?? '',
|
|
114
|
+
securityDefiner: flag(fn.definer),
|
|
115
|
+
leakproof: flag(fn.leakproof),
|
|
116
|
+
config: JSON.parse(fn.config ?? '[]'),
|
|
117
|
+
grants: grants.map((row) => ({
|
|
118
|
+
grantee: row.grantee ?? '',
|
|
119
|
+
privileges: JSON.parse(row.privileges ?? '[]'),
|
|
120
|
+
})),
|
|
121
|
+
bypassRoles: await bypassRoles(session).catch(() => []),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** The roles of the server (not the built in pg_ ones) and how each stands against RLS. */
|
|
125
|
+
export async function listRoles(session) {
|
|
126
|
+
const rows = await session.rows(`SELECT r.rolname AS name, r.rolsuper AS superuser, r.rolcanlogin AS can_login,
|
|
127
|
+
r.rolcreaterole AS create_role, r.rolcreatedb AS create_db,
|
|
128
|
+
r.rolreplication AS replication, r.rolbypassrls AS bypass_rls,
|
|
129
|
+
r.rolconnlimit AS connection_limit,
|
|
130
|
+
(SELECT count(*) FROM pg_catalog.pg_policy p WHERE r.oid = ANY (p.polroles)) AS policies,
|
|
131
|
+
(SELECT count(*) FROM pg_catalog.pg_class c
|
|
132
|
+
WHERE c.relowner = r.oid AND c.relkind IN ('r', 'p') AND c.relrowsecurity) AS owned_rls,
|
|
133
|
+
(SELECT count(*) FROM pg_catalog.pg_class c
|
|
134
|
+
WHERE c.relowner = r.oid AND c.relkind IN ('r', 'p') AND c.relforcerowsecurity)
|
|
135
|
+
AS owned_forced
|
|
136
|
+
FROM pg_catalog.pg_roles r
|
|
137
|
+
WHERE r.rolname !~ '^pg_'
|
|
138
|
+
ORDER BY r.rolsuper DESC, r.rolname`);
|
|
139
|
+
return rows.map((row) => ({
|
|
140
|
+
name: row.name ?? '',
|
|
141
|
+
superuser: flag(row.superuser),
|
|
142
|
+
canLogin: flag(row.can_login),
|
|
143
|
+
createRole: flag(row.create_role),
|
|
144
|
+
createDb: flag(row.create_db),
|
|
145
|
+
replication: flag(row.replication),
|
|
146
|
+
bypassRls: flag(row.bypass_rls),
|
|
147
|
+
connectionLimit: Number(row.connection_limit),
|
|
148
|
+
policies: Number(row.policies),
|
|
149
|
+
ownedRlsTables: Number(row.owned_rls),
|
|
150
|
+
ownedForcedTables: Number(row.owned_forced),
|
|
151
|
+
}));
|
|
152
|
+
}
|