@kazurolabs/psql-explorer 0.0.0-stage → 0.3.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +505 -2
  3. package/RELEASES.md +87 -0
  4. package/dist/app.js +39 -0
  5. package/dist/cli.js +307 -0
  6. package/dist/commands/configgen.js +128 -0
  7. package/dist/commands/explore.js +388 -0
  8. package/dist/commands/terminal-prompt.js +68 -0
  9. package/dist/config/load.js +324 -0
  10. package/dist/config/resolve-environment.js +112 -0
  11. package/dist/config/resolvers/env.js +12 -0
  12. package/dist/config/resolvers/file.js +24 -0
  13. package/dist/config/resolvers/groups.js +1 -0
  14. package/dist/config/resolvers/index.js +49 -0
  15. package/dist/config/resolvers/literal.js +7 -0
  16. package/dist/config/resolvers/prompt.js +159 -0
  17. package/dist/config/resolvers/stored.js +13 -0
  18. package/dist/config/resolvers/types.js +18 -0
  19. package/dist/config/resolvers/vault.js +283 -0
  20. package/dist/config/schema.js +24 -0
  21. package/dist/config/source.js +105 -0
  22. package/dist/db/connect.js +55 -0
  23. package/dist/db/identifiers.js +6 -0
  24. package/dist/db/paths.js +14 -0
  25. package/dist/db/store.js +323 -0
  26. package/dist/db/tester.js +190 -0
  27. package/dist/db/validate.js +99 -0
  28. package/dist/environments.js +140 -0
  29. package/dist/explorer/alter.js +493 -0
  30. package/dist/explorer/catalog.js +414 -0
  31. package/dist/explorer/connect-entry.js +42 -0
  32. package/dist/explorer/cypher-parse.js +478 -0
  33. package/dist/explorer/cypher.js +963 -0
  34. package/dist/explorer/ddl.js +502 -0
  35. package/dist/explorer/dependents.js +222 -0
  36. package/dist/explorer/design-load.js +254 -0
  37. package/dist/explorer/design.js +45 -0
  38. package/dist/explorer/edit.js +108 -0
  39. package/dist/explorer/explain.js +37 -0
  40. package/dist/explorer/filter.js +121 -0
  41. package/dist/explorer/format.js +134 -0
  42. package/dist/explorer/graph.js +679 -0
  43. package/dist/explorer/gucs.js +31 -0
  44. package/dist/explorer/profile.js +102 -0
  45. package/dist/explorer/references.js +95 -0
  46. package/dist/explorer/rows.js +64 -0
  47. package/dist/explorer/schema-diff.js +360 -0
  48. package/dist/explorer/search.js +309 -0
  49. package/dist/explorer/security.js +152 -0
  50. package/dist/explorer/seed.js +1376 -0
  51. package/dist/explorer/session.js +122 -0
  52. package/dist/explorer/stats.js +135 -0
  53. package/dist/explorer/timeout.js +13 -0
  54. package/dist/prompts.js +52 -0
  55. package/dist/screens/environments.js +229 -0
  56. package/dist/screens/explorer.js +145 -0
  57. package/dist/screens/header.js +8 -0
  58. package/dist/screens/index.js +13 -0
  59. package/dist/screens/new-connection.js +178 -0
  60. package/dist/screens/panes/common.js +35 -0
  61. package/dist/screens/panes/history.js +36 -0
  62. package/dist/screens/panes/info.js +61 -0
  63. package/dist/screens/panes/query.js +113 -0
  64. package/dist/screens/panes/table.js +178 -0
  65. package/dist/screens/panes/tables.js +142 -0
  66. package/dist/screens/prompt-modal.js +18 -0
  67. package/dist/screens/record-view.js +42 -0
  68. package/dist/screens/result-grid.js +126 -0
  69. package/dist/screens/scroll.js +12 -0
  70. package/dist/screens/splash.js +78 -0
  71. package/dist/screens/terminal.js +25 -0
  72. package/dist/screens/types.js +1 -0
  73. package/dist/services.js +1 -0
  74. package/dist/startup.js +84 -0
  75. package/dist/web/server.js +988 -0
  76. package/package.json +69 -5
  77. package/web/dist/assets/anton-latin-400-normal-AUNGEG_V.woff +0 -0
  78. package/web/dist/assets/anton-latin-400-normal-Byf51wtH.woff2 +0 -0
  79. package/web/dist/assets/anton-latin-ext-400-normal-BMODBQc6.woff +0 -0
  80. package/web/dist/assets/anton-latin-ext-400-normal-SyiqE2Jt.woff2 +0 -0
  81. package/web/dist/assets/anton-vietnamese-400-normal-2FfR1wHA.woff +0 -0
  82. package/web/dist/assets/anton-vietnamese-400-normal-CkBxLiRJ.woff2 +0 -0
  83. package/web/dist/assets/antonio-latin-400-normal-B7iMKlzw.woff +0 -0
  84. package/web/dist/assets/antonio-latin-400-normal-CZjzwNAN.woff2 +0 -0
  85. package/web/dist/assets/antonio-latin-700-normal-BY3mCh47.woff +0 -0
  86. package/web/dist/assets/antonio-latin-700-normal-DSH-lEDG.woff2 +0 -0
  87. package/web/dist/assets/antonio-latin-ext-400-normal-nE82dizb.woff2 +0 -0
  88. package/web/dist/assets/antonio-latin-ext-400-normal-qA9Jb92z.woff +0 -0
  89. package/web/dist/assets/antonio-latin-ext-700-normal-BdSkr58W.woff +0 -0
  90. package/web/dist/assets/antonio-latin-ext-700-normal-D-dvgqjr.woff2 +0 -0
  91. package/web/dist/assets/button-ClA70PLR.js +1 -0
  92. package/web/dist/assets/code-xml-CXkpHeU9.js +1 -0
  93. package/web/dist/assets/compare-view-DKl5l_Jw.js +2 -0
  94. package/web/dist/assets/copy-CCXzeW-s.js +1 -0
  95. package/web/dist/assets/cypher-view-Pd0WFH8F.js +13 -0
  96. package/web/dist/assets/design-view-CIHSmH4u.js +9 -0
  97. package/web/dist/assets/erd-view-CV7mFGAN.js +1 -0
  98. package/web/dist/assets/format-sql-BLj5_RDZ.js +5 -0
  99. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  100. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  101. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  102. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  103. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  104. package/web/dist/assets/graph-force-yBLyErpS.js +1 -0
  105. package/web/dist/assets/graph-link-OCo_s0zO.js +1 -0
  106. package/web/dist/assets/graph-view-sM1OlByf.js +3 -0
  107. package/web/dist/assets/index-CDcnUacl.js +10 -0
  108. package/web/dist/assets/index-OzJqnJWG.css +2 -0
  109. package/web/dist/assets/key-round-Cv_7QeED.js +1 -0
  110. package/web/dist/assets/loader-circle-BK_GMdvN.js +1 -0
  111. package/web/dist/assets/orbitron-latin-700-normal-4jsRXGGJ.woff2 +0 -0
  112. package/web/dist/assets/orbitron-latin-700-normal-DmYOh-W9.woff +0 -0
  113. package/web/dist/assets/orbitron-latin-900-normal-BbJatbhp.woff +0 -0
  114. package/web/dist/assets/orbitron-latin-900-normal-DrIi7unX.woff2 +0 -0
  115. package/web/dist/assets/query-builder-view-ChNFb51g.js +4 -0
  116. package/web/dist/assets/rotate-ccw-CdCaf7Q6.js +1 -0
  117. package/web/dist/assets/route-DO6IZ7of.js +15 -0
  118. package/web/dist/assets/seed-view-DFNB8WHE.js +7 -0
  119. package/web/dist/assets/share-tech-mono-latin-400-normal-15MgdvEM.woff +0 -0
  120. package/web/dist/assets/share-tech-mono-latin-400-normal-CCqdbj5z.woff2 +0 -0
  121. package/web/dist/assets/shield-alert-CMS-3j4U.js +1 -0
  122. package/web/dist/assets/sliders-horizontal-Dg51yc-C.js +1 -0
  123. package/web/dist/assets/sql-tabs-B4jt6bQj.js +2 -0
  124. package/web/dist/assets/table-DfMz2f8M.js +1 -0
  125. package/web/dist/assets/triangle-alert-COGnHCD1.js +1 -0
  126. package/web/dist/assets/zone-D9dXk8tn.js +5 -0
  127. package/web/dist/index.html +26 -0
package/dist/cli.js ADDED
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { render } from 'ink';
6
+ import meow from 'meow';
7
+ import App from './app.js';
8
+ import { createVaultClient, defaultRegistry, PromptRequiredError, } from './config/resolvers/index.js';
9
+ import { loadConfig } from './config/load.js';
10
+ import { dataDir } from './db/paths.js';
11
+ import { openStore } from './db/store.js';
12
+ import { testConnection } from './db/tester.js';
13
+ import { configgen } from './commands/configgen.js';
14
+ import { commandNames, isCommand, runCommand } from './commands/explore.js';
15
+ import { createTerminalPrompter } from './commands/terminal-prompt.js';
16
+ import { openSession } from './explorer/session.js';
17
+ import { createPromptQueue } from './prompts.js';
18
+ import { createStartup } from './startup.js';
19
+ import { createWebServer, DEFAULT_WEB_PORT, listen, openBrowser } from './web/server.js';
20
+ // meow reads package.json via importMeta, so the version and description have
21
+ // a single source of truth. It handles --help and --version (exit 0) itself.
22
+ const cli = meow(`
23
+ Usage
24
+ $ psql-explorer start the interactive TUI (also serves the web UI; --port picks its port)
25
+ $ psql-explorer envs
26
+ $ psql-explorer schemas --env <name>
27
+ $ psql-explorer tables --env <name> [--schema <schema>]
28
+ $ psql-explorer describe --env <name> <table>
29
+ $ psql-explorer rows --env <name> <table> [--limit 100] [--offset 0] [--order <col>] [--desc]
30
+ $ psql-explorer query --env <name> "<sql>" (or "-", or pipe the SQL in)
31
+ $ psql-explorer info --env <name>
32
+ $ psql-explorer history --env <name>
33
+ $ psql-explorer diff --env <name> --with <name> [--schema <schema>] [--with-schema <schema>]
34
+ $ psql-explorer --web [--port 7878] [--no-open] serve the web UI and open it
35
+ $ psql-explorer web same as --web
36
+ $ psql-explorer configgen:file
37
+ $ psql-explorer configgen:vault
38
+ $ psql-explorer secret:set <name> (asks for it, or pipe it in)
39
+ $ psql-explorer secret:delete <name>
40
+
41
+ Commands
42
+ envs List the environments: the shared ones and your local connections.
43
+ schemas List the schemas of the database.
44
+ tables List the tables and views of a schema, with row estimates and sizes.
45
+ describe Show a table's columns, indexes and constraints.
46
+ rows Print a page of a table's rows.
47
+ query Run one SQL statement and print what it returns. On a read-only
48
+ environment anything that would change data is refused by the server.
49
+ info Show the server version, database size and session settings.
50
+ history The statements run with query or the TUI, newest first.
51
+ diff Compare the structure of two environments' databases: tables,
52
+ columns, indexes, constraints, triggers, policies, views, functions,
53
+ sequences, types and extensions. All schemas, or the one --schema names.
54
+ web Serve the web UI and its API on http://127.0.0.1:<port> until stopped.
55
+ configgen:file Print a fully commented example environment kept in a directory.
56
+ configgen:vault Print the same for one kept in Vault.
57
+ secret:set Keep a secret in the local store, for config values written as
58
+ source: stored. secret:delete removes it.
59
+
60
+ Options
61
+ --config <base> Where the shared config lives (default: $PSQL_EXPLORER_ENVIRONMENTS): a
62
+ directory, or vault:<KV v2 path> such as vault:secret/data/psql-explorer.
63
+ It holds one <environment>/explorer.yaml per environment.
64
+ --env <name> The environment: a shared one, or a local connection by name
65
+ --schema <name> tables, describe, rows: the schema (default: the environment's);
66
+ diff: compare only this schema
67
+ --with <name> diff: the environment to compare --env with
68
+ --with-schema <name>
69
+ diff: the schema on the --with side, when it is named differently
70
+ --format <f> table (default), csv or json
71
+ --json Same as --format json
72
+ --limit <n> rows: rows per page (default 100); history: how many (default 50)
73
+ --offset <n> rows: rows to skip
74
+ --order <cols> rows: sort by these columns, comma separated
75
+ --desc rows: sort descending
76
+ --max-rows <n> query: read at most this many rows (default 1000)
77
+ --timeout <s> Cancel a statement after this many seconds (default 30, 0 for none)
78
+ --web Start the web UI instead of the TUI (same as the web command)
79
+ --no-open web: do not open the browser
80
+ --port <n> web: the port to listen on (default 7878; the next free one is used if taken)
81
+ --help Show this help
82
+ --version Show the version
83
+ --relnotes Show the release notes
84
+
85
+ Run without a command to start the interactive TUI.
86
+ `, {
87
+ importMeta: import.meta,
88
+ flags: {
89
+ config: { type: 'string' },
90
+ env: { type: 'string' },
91
+ with: { type: 'string' },
92
+ withSchema: { type: 'string' },
93
+ schema: { type: 'string' },
94
+ format: { type: 'string' },
95
+ json: { type: 'boolean' },
96
+ limit: { type: 'number' },
97
+ offset: { type: 'number' },
98
+ order: { type: 'string' },
99
+ desc: { type: 'boolean' },
100
+ maxRows: { type: 'number' },
101
+ timeout: { type: 'number' },
102
+ port: { type: 'number' },
103
+ web: { type: 'boolean' },
104
+ relnotes: { type: 'boolean' },
105
+ open: { type: 'boolean', default: true },
106
+ },
107
+ });
108
+ // RELEASES.md ships next to dist, like web/dist, and needs neither the database nor a config.
109
+ if (cli.flags.relnotes) {
110
+ try {
111
+ process.stdout.write(readFileSync(join(import.meta.dirname, '..', 'RELEASES.md'), 'utf8'));
112
+ process.exit(0);
113
+ }
114
+ catch {
115
+ process.stderr.write('psql-explorer: the release notes are missing from this install\n');
116
+ process.exit(1);
117
+ }
118
+ }
119
+ // Prints an example config and needs neither the local database nor a config.
120
+ const [command, ...args] = cli.input;
121
+ if (command === 'configgen:file' || command === 'configgen:vault') {
122
+ process.stdout.write(configgen(command === 'configgen:file' ? 'file' : 'vault'));
123
+ process.exit(0);
124
+ }
125
+ const vault = createVaultClient();
126
+ // Opened before the TTY check so a broken data dir is reported the same way everywhere.
127
+ let store;
128
+ try {
129
+ store = openStore(dataDir());
130
+ }
131
+ catch (error) {
132
+ process.stderr.write(`psql-explorer: ${error instanceof Error ? error.message : String(error)}\n` +
133
+ 'Check the directory permissions, or set PSQL_EXPLORER_CONFIG to another directory.\n');
134
+ process.exit(1);
135
+ }
136
+ const readStdin = async () => {
137
+ const chunks = [];
138
+ for await (const chunk of process.stdin)
139
+ chunks.push(chunk);
140
+ return Buffer.concat(chunks).toString('utf8');
141
+ };
142
+ // Secrets for `source: stored` values: they need the store but no config.
143
+ if (command === 'secret:set' || command === 'secret:delete') {
144
+ const [name] = args;
145
+ let code = 0;
146
+ if (!name) {
147
+ process.stderr.write(`psql-explorer: ${command} needs the secret's name\n`);
148
+ code = 2;
149
+ }
150
+ else if (command === 'secret:delete') {
151
+ if (!store.deleteSecret(name)) {
152
+ process.stderr.write(`psql-explorer: there is no stored secret named "${name}"\n`);
153
+ code = 1;
154
+ }
155
+ }
156
+ else {
157
+ try {
158
+ const value = process.stdin.isTTY
159
+ ? await createTerminalPrompter().ask({ envName: 'secret', field: name, masked: true })
160
+ : // A trailing newline from echo or a file is not part of the secret.
161
+ (await readStdin()).replace(/\r?\n$/, '');
162
+ if (value) {
163
+ store.setSecret(name, value);
164
+ process.stderr.write(`Stored secret ${name}\n`);
165
+ }
166
+ else {
167
+ process.stderr.write('psql-explorer: the secret is empty; nothing was stored\n');
168
+ code = 1;
169
+ }
170
+ }
171
+ catch {
172
+ process.stderr.write('psql-explorer: cancelled; nothing was stored\n');
173
+ code = 1;
174
+ }
175
+ }
176
+ store.close();
177
+ process.exit(code);
178
+ }
179
+ if (command === 'web' || (command === undefined && cli.flags.web)) {
180
+ const { config, error: configError } = await loadConfig({ base: cli.flags.config, vault });
181
+ const prompts = createPromptQueue();
182
+ const services = {
183
+ store,
184
+ tester: testConnection,
185
+ config,
186
+ configError,
187
+ // Questions are answered in the browser: the page polls /api/prompt and posts the answer.
188
+ resolvers: defaultRegistry(undefined, prompts, store, vault),
189
+ prompts,
190
+ openSession,
191
+ };
192
+ const server = createWebServer({
193
+ services,
194
+ staticDir: join(import.meta.dirname, '..', 'web', 'dist'),
195
+ });
196
+ try {
197
+ const requested = cli.flags.port ?? DEFAULT_WEB_PORT;
198
+ const address = await listen(server, requested);
199
+ const port = Number(new URL(address).port);
200
+ if (port !== requested) {
201
+ process.stderr.write(`psql-explorer: port ${requested} is in use, using port ${port} instead\n`);
202
+ }
203
+ process.stderr.write(`psql-explorer web UI on ${address} (Ctrl-C to stop)\n`);
204
+ if (cli.flags.open)
205
+ openBrowser(address);
206
+ }
207
+ catch (error) {
208
+ store.close();
209
+ process.stderr.write(`psql-explorer: ${error instanceof Error ? error.message : String(error)}\n`);
210
+ process.exit(1);
211
+ }
212
+ process.once('SIGINT', () => {
213
+ server.close();
214
+ store.close();
215
+ process.exit(0);
216
+ });
217
+ await new Promise(() => { });
218
+ }
219
+ if (command !== undefined) {
220
+ let code = 2;
221
+ if (!isCommand(command)) {
222
+ process.stderr.write(`psql-explorer: unknown command "${command}"; use one of: ${commandNames.join(', ')}\n`);
223
+ }
224
+ else {
225
+ const { config, error: configError } = await loadConfig({ base: cli.flags.config, vault });
226
+ // A person at a terminal can be asked for what the config says to prompt for.
227
+ const prompter = process.stdin.isTTY
228
+ ? createTerminalPrompter()
229
+ : { ask: ({ envName, field }) => Promise.reject(new PromptRequiredError(envName, field)) };
230
+ const services = {
231
+ store,
232
+ tester: testConnection,
233
+ config,
234
+ configError,
235
+ resolvers: defaultRegistry(undefined, prompter, store, vault),
236
+ openSession,
237
+ };
238
+ code = await runCommand({
239
+ services,
240
+ command,
241
+ args,
242
+ flags: cli.flags,
243
+ out: (text) => process.stdout.write(text),
244
+ err: (text) => process.stderr.write(text),
245
+ readStdin,
246
+ stdinIsTTY: process.stdin.isTTY === true,
247
+ });
248
+ }
249
+ store.close();
250
+ // Let piped output drain before exiting.
251
+ await new Promise((resolve) => process.stdout.write('', resolve));
252
+ process.exit(code);
253
+ }
254
+ // Ink needs raw-mode keyboard input; without a TTY it would crash, so fail clearly.
255
+ if (!process.stdin.isTTY) {
256
+ store.close();
257
+ process.stderr.write('psql-explorer: the TUI needs a terminal (stdin is not a TTY).\n' +
258
+ 'Run it directly in a terminal, or use a command such as query (see --help).\n');
259
+ process.exit(1);
260
+ }
261
+ const prompts = createPromptQueue();
262
+ // The screen opens at once; reading the config (which may be in Vault) happens behind the splash
263
+ // screen, which shows each step. It fills in `config` and `configError`. What is wrong with the
264
+ // config is shown on the Environments screen (local connections still work, and so do the
265
+ // environments that are fine).
266
+ const services = {
267
+ store,
268
+ tester: testConnection,
269
+ resolvers: defaultRegistry(undefined, prompts, store, vault),
270
+ prompts,
271
+ openSession,
272
+ openUrl: openBrowser,
273
+ };
274
+ services.startup = createStartup({ services, base: cli.flags.config, vault });
275
+ // The web UI is served from the same process, so the Environments screen can say where it is.
276
+ // Questions it triggers are answered in the browser, so it gets a prompt queue of its own; the
277
+ // config is the one the splash screen loads. Failing to start it never stops the TUI.
278
+ const webPrompts = createPromptQueue();
279
+ const webServices = {
280
+ store,
281
+ tester: testConnection,
282
+ resolvers: defaultRegistry(undefined, webPrompts, store, vault),
283
+ prompts: webPrompts,
284
+ openSession,
285
+ get config() {
286
+ return services.config;
287
+ },
288
+ get configError() {
289
+ return services.configError;
290
+ },
291
+ };
292
+ const webServer = createWebServer({
293
+ services: webServices,
294
+ staticDir: join(import.meta.dirname, '..', 'web', 'dist'),
295
+ });
296
+ try {
297
+ services.webUrl = await listen(webServer, cli.flags.port ?? DEFAULT_WEB_PORT);
298
+ }
299
+ catch {
300
+ webServer.close();
301
+ }
302
+ const app = render(_jsx(App, { version: cli.pkg.version ?? '0.0.0', fullScreen: true, services: services }),
303
+ // The app owns the whole terminal, and the shell's screen comes back when it exits.
304
+ { alternateScreen: true });
305
+ await app.waitUntilExit();
306
+ webServer.close();
307
+ store.close();
@@ -0,0 +1,128 @@
1
+ // `psql-explorer configgen:file` and `psql-explorer configgen:vault` print a fully commented
2
+ // example environment file, ready to copy. Every key the loader accepts appears, optional ones
3
+ // commented out.
4
+ const sources = `A setting is plain text (\`host: db.prod.internal\`) or a source and a value:
5
+ source: plaintext | inline | env | file | stored | vault | prompt`;
6
+ const access = `# production | reference | demo | development | local. Required.
7
+ tier: production
8
+
9
+ # Shown with a shield and in red. Optional, default: true for the production tier.
10
+ protected: true
11
+
12
+ # Open every session read only (default_transaction_read_only), so nothing run here can change
13
+ # data. Optional, default: the value of protected.
14
+ readOnly: true
15
+
16
+ # Opening the environment first asks for two random words to be typed back, so it is never
17
+ # entered by accident. Optional, default: the value of protected.
18
+ confirm: true
19
+
20
+ # Offer the web UI's Seed page, which fills tables with generated rows. Optional, default: true
21
+ # for the development and local tiers, false otherwise.
22
+ # seeder: false
23
+
24
+ # Let the web UI's Design page run statements that create, alter and drop tables; without it the
25
+ # page only previews the SQL. Optional, default: true for the development and local tiers, false
26
+ # otherwise.
27
+ # designer: false
28
+ `;
29
+ const fileEnvironment = `# One environment: <environment>/explorer.yaml below the base path.
30
+ # Use with: psql-explorer --config <base path> (or export PSQL_EXPLORER_ENVIRONMENTS=<base path>)
31
+ # The environment is named after its folder: lower case letters, digits, - and _.
32
+ # ${sources.replaceAll('\n', '\n# ')}
33
+ # Unknown keys are an error.
34
+
35
+ ${access}
36
+ # Where the database is. Required. Any of these can use a source instead of plain text.
37
+ host: db.prod.internal
38
+ port: 5432
39
+ database: app
40
+
41
+ # The schema browsing starts in. Optional, default: public
42
+ # schema: app
43
+
44
+ # Database login. Required.
45
+ username: readonly
46
+
47
+ # password (default) needs a password; none means the database takes no password.
48
+ auth: password
49
+
50
+ # Required when auth is password. Must be left out when auth is none.
51
+ password:
52
+ source: file
53
+ value: ./password.txt # relative to this file's folder; the whole content, trimmed
54
+ # Other ways to supply it:
55
+ # password: s3cret # plain text, in the file itself
56
+ # password: {source: env, value: DB_PASSWORD} # an environment variable
57
+ # password: {source: stored, value: prod-password} # a secret kept in the local store
58
+ # password: {source: prompt} # asked for when connecting
59
+ # password: {source: vault, value: "secret/data/db/prod#password"} # a key of a Vault KV v2 secret
60
+
61
+ # TLS. Optional, default: mode disable
62
+ tls:
63
+ # disable | require | verify-ca | verify-full
64
+ mode: verify-full
65
+ # CA certificate (PEM text). Required for verify-ca and verify-full.
66
+ ca:
67
+ source: file
68
+ value: ./certs/ca.pem
69
+ # Client certificate and its private key, for mutual TLS. Give both or neither.
70
+ # cert:
71
+ # source: file
72
+ # value: ./certs/client.pem
73
+ # key:
74
+ # source: file
75
+ # value: ./certs/client-key.pem
76
+ # Only when the key is encrypted.
77
+ # keyPassphrase:
78
+ # source: file
79
+ # value: ./certs/client-key.pass
80
+ `;
81
+ const vaultEnvironment = `# One environment, stored as the explorer.yaml key of a secret below the base path:
82
+ # vault kv put secret/psql-explorer/production explorer.yaml=@production.yaml
83
+ # Use with: psql-explorer --config vault:secret/data/psql-explorer
84
+ # The environment is named after the last part of the secret's path: lower case letters,
85
+ # digits, - and _. Who can read the secret decides who sees the environment.
86
+ # ${sources.replaceAll('\n', '\n# ')}
87
+ # \`file\` values are relative to the working directory here, not to a folder.
88
+ # Unknown keys are an error.
89
+
90
+ ${access}
91
+ # Where the database is. Required. Any of these can use a source instead of plain text.
92
+ host:
93
+ source: vault
94
+ value: secret/data/db/prod#host # "<KV v2 data path>#<key>"
95
+ port: 5432
96
+ database: app
97
+
98
+ # The schema browsing starts in. Optional, default: public
99
+ # schema: app
100
+
101
+ username: readonly
102
+ auth: password # password (default) | none; none takes no password, so leave password out
103
+ password: # required when auth is password
104
+ source: vault
105
+ value: secret/data/db/prod#password
106
+
107
+ # TLS. Optional, default: mode disable
108
+ tls:
109
+ # disable | require | verify-ca | verify-full
110
+ mode: verify-full
111
+ # CA certificate (PEM text). Required for verify-ca and verify-full.
112
+ ca:
113
+ source: vault
114
+ value: secret/data/db/prod#ca
115
+ # Client certificate and key, for mutual TLS. Give both or neither.
116
+ # cert:
117
+ # source: vault
118
+ # value: secret/data/db/prod#cert
119
+ # key:
120
+ # source: vault
121
+ # value: secret/data/db/prod#key
122
+ # keyPassphrase: # only when the key is encrypted
123
+ # source: vault
124
+ # value: secret/data/db/prod#key-passphrase
125
+ `;
126
+ export function configgen(kind) {
127
+ return kind === 'file' ? fileEnvironment : vaultEnvironment;
128
+ }