@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.
Files changed (77) hide show
  1. package/README.md +229 -18
  2. package/RELEASES.md +95 -0
  3. package/dist/cli.js +57 -13
  4. package/dist/commands/configgen.js +25 -25
  5. package/dist/commands/explore.js +86 -4
  6. package/dist/config/load.js +11 -73
  7. package/dist/config/resolve-environment.js +10 -62
  8. package/dist/config/resolvers/index.js +1 -1
  9. package/dist/config/resolvers/prompt.js +2 -2
  10. package/dist/config/resolvers/vault.js +1 -83
  11. package/dist/db/store.js +57 -0
  12. package/dist/environments.js +5 -2
  13. package/dist/explorer/alter.js +493 -0
  14. package/dist/explorer/catalog.js +31 -0
  15. package/dist/explorer/connect-entry.js +29 -18
  16. package/dist/explorer/cypher-parse.js +478 -0
  17. package/dist/explorer/cypher.js +963 -0
  18. package/dist/explorer/ddl.js +502 -0
  19. package/dist/explorer/dependents.js +222 -0
  20. package/dist/explorer/design-load.js +254 -0
  21. package/dist/explorer/design.js +45 -0
  22. package/dist/explorer/edit.js +108 -0
  23. package/dist/explorer/explain.js +44 -3
  24. package/dist/explorer/filter.js +121 -0
  25. package/dist/explorer/graph.js +679 -0
  26. package/dist/explorer/gucs.js +31 -0
  27. package/dist/explorer/profile.js +102 -0
  28. package/dist/explorer/references.js +95 -0
  29. package/dist/explorer/rows.js +100 -13
  30. package/dist/explorer/schema-diff.js +360 -0
  31. package/dist/explorer/search.js +309 -0
  32. package/dist/explorer/security.js +152 -0
  33. package/dist/explorer/seed.js +1376 -0
  34. package/dist/explorer/session.js +25 -1
  35. package/dist/explorer/stats.js +135 -0
  36. package/dist/explorer/timeout.js +13 -0
  37. package/dist/screens/environments.js +6 -3
  38. package/dist/screens/explorer.js +0 -3
  39. package/dist/screens/splash.js +4 -2
  40. package/dist/web/server.js +655 -22
  41. package/package.json +4 -1
  42. package/web/dist/assets/antonio-latin-400-normal-B7iMKlzw.woff +0 -0
  43. package/web/dist/assets/antonio-latin-400-normal-CZjzwNAN.woff2 +0 -0
  44. package/web/dist/assets/antonio-latin-700-normal-BY3mCh47.woff +0 -0
  45. package/web/dist/assets/antonio-latin-700-normal-DSH-lEDG.woff2 +0 -0
  46. package/web/dist/assets/antonio-latin-ext-400-normal-nE82dizb.woff2 +0 -0
  47. package/web/dist/assets/antonio-latin-ext-400-normal-qA9Jb92z.woff +0 -0
  48. package/web/dist/assets/antonio-latin-ext-700-normal-BdSkr58W.woff +0 -0
  49. package/web/dist/assets/antonio-latin-ext-700-normal-D-dvgqjr.woff2 +0 -0
  50. package/web/dist/assets/button-BiFx4Lyw.js +1 -0
  51. package/web/dist/assets/code-xml-CPEjNFOw.js +1 -0
  52. package/web/dist/assets/compare-view-COQ3yE76.js +2 -0
  53. package/web/dist/assets/copy-C2N9tUYJ.js +1 -0
  54. package/web/dist/assets/cypher-view-BzyL4mNU.js +13 -0
  55. package/web/dist/assets/design-view-BPm7dSTY.js +9 -0
  56. package/web/dist/assets/erd-view-DqGDws2_.js +1 -0
  57. package/web/dist/assets/format-sql-CoddYUOF.js +5 -0
  58. package/web/dist/assets/graph-force-ChCGXQUt.js +1 -0
  59. package/web/dist/assets/graph-link-C8P9s_0W.js +1 -0
  60. package/web/dist/assets/graph-view-BpoUrUCP.js +3 -0
  61. package/web/dist/assets/index-CpHnjRLv.js +10 -0
  62. package/web/dist/assets/index-OzJqnJWG.css +2 -0
  63. package/web/dist/assets/key-round-JiGCgCWS.js +1 -0
  64. package/web/dist/assets/loader-circle-BxDJOoJO.js +1 -0
  65. package/web/dist/assets/query-builder-view-CtRexMEX.js +4 -0
  66. package/web/dist/assets/rotate-ccw-CwdA-J3t.js +1 -0
  67. package/web/dist/assets/route-7eu-iAqD.js +15 -0
  68. package/web/dist/assets/seed-view-BIkBTiU6.js +7 -0
  69. package/web/dist/assets/shield-alert-keJIAND5.js +1 -0
  70. package/web/dist/assets/sliders-horizontal-DKtL_ql7.js +1 -0
  71. package/web/dist/assets/sql-tabs-DUSXdYCr.js +2 -0
  72. package/web/dist/assets/table-Drc_n1gF.js +1 -0
  73. package/web/dist/assets/triangle-alert-C5nF69rW.js +1 -0
  74. package/web/dist/assets/zone-CHDOrDBF.js +5 -0
  75. package/web/dist/index.html +9 -2
  76. package/web/dist/assets/index-BMLFCtFY.js +0 -17
  77. 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
+ }