neon 3.0.0 → 3.1.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 (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,299 +1,260 @@
1
- /**
2
- * psql history file I/O.
3
- *
4
- * TypeScript port of the history-handling portion of PostgreSQL's
5
- * `src/bin/psql/input.c`. Implements the read/append/truncate operations the
6
- * REPL drives plus the small env-resolution helpers (`PSQL_HISTORY`,
7
- * `HISTSIZE`).
8
- *
9
- * On-disk format follows GNU readline's history file:
10
- *
11
- * - One entry per line, UTF-8.
12
- * - Multi-line entries are encoded with the literal escape sequences
13
- * `\n` (backslash + n), `\r`, and `\\`. We decode them back to real
14
- * newlines / carriage returns / backslashes on load and re-encode on
15
- * write. Upstream psql uses an in-memory NL_IN_HISTORY (0x01) marker
16
- * that libreadline then translates to the same on-disk form; we skip
17
- * the intermediate marker and operate on real strings throughout.
18
- * - Lines whose first character is `#` are treated as timestamp / comment
19
- * markers (libreadline writes them when `history_write_timestamps` is
20
- * set) and silently skipped on load. psql itself never writes them.
21
- *
22
- * Intentional deviations from upstream:
23
- *
24
- * - Append-only writes use `fs.promises.appendFile`, which opens the file
25
- * with `O_APPEND` on POSIX so concurrent appends from multiple psql
26
- * instances do not interleave within a single write call. On Windows
27
- * there is a small race window we accept; the alternative (an external
28
- * lock file) is not worth the dependency churn.
29
- * - `truncateHistory` writes the trimmed history to a sibling temp file
30
- * and `rename`s it into place. POSIX `rename(2)` is atomic w.r.t. other
31
- * processes observing the path; Windows is best-effort.
32
- * - HISTCONTROL comparisons run on the raw (decoded) entry — this matches
33
- * bash/readline behaviour where the user's literal input is what gets
34
- * deduped, not the escaped on-disk form.
35
- */
36
- import { randomUUID } from "node:crypto";
37
- import { promises as fs } from "node:fs";
38
- import * as os from "node:os";
1
+ import { promises } from "node:fs";
39
2
  import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import { randomUUID } from "node:crypto";
5
+ //#region src/psql/io/history.ts
6
+ /**
7
+ * psql history file I/O.
8
+ *
9
+ * TypeScript port of the history-handling portion of PostgreSQL's
10
+ * `src/bin/psql/input.c`. Implements the read/append/truncate operations the
11
+ * REPL drives plus the small env-resolution helpers (`PSQL_HISTORY`,
12
+ * `HISTSIZE`).
13
+ *
14
+ * On-disk format follows GNU readline's history file:
15
+ *
16
+ * - One entry per line, UTF-8.
17
+ * - Multi-line entries are encoded with the literal escape sequences
18
+ * `\n` (backslash + n), `\r`, and `\\`. We decode them back to real
19
+ * newlines / carriage returns / backslashes on load and re-encode on
20
+ * write. Upstream psql uses an in-memory NL_IN_HISTORY (0x01) marker
21
+ * that libreadline then translates to the same on-disk form; we skip
22
+ * the intermediate marker and operate on real strings throughout.
23
+ * - Lines whose first character is `#` are treated as timestamp / comment
24
+ * markers (libreadline writes them when `history_write_timestamps` is
25
+ * set) and silently skipped on load. psql itself never writes them.
26
+ *
27
+ * Intentional deviations from upstream:
28
+ *
29
+ * - Append-only writes use `fs.promises.appendFile`, which opens the file
30
+ * with `O_APPEND` on POSIX so concurrent appends from multiple psql
31
+ * instances do not interleave within a single write call. On Windows
32
+ * there is a small race window we accept; the alternative (an external
33
+ * lock file) is not worth the dependency churn.
34
+ * - `truncateHistory` writes the trimmed history to a sibling temp file
35
+ * and `rename`s it into place. POSIX `rename(2)` is atomic w.r.t. other
36
+ * processes observing the path; Windows is best-effort.
37
+ * - HISTCONTROL comparisons run on the raw (decoded) entry — this matches
38
+ * bash/readline behaviour where the user's literal input is what gets
39
+ * deduped, not the escaped on-disk form.
40
+ */
40
41
  /** psql's compiled-in default for HISTSIZE (see `src/bin/psql/settings.h`). */
41
42
  const DEFAULT_HISTSIZE = 500;
42
43
  /**
43
- * In-memory command-line history for the current session, oldest first.
44
- *
45
- * Upstream psql delegates history to GNU readline, whose `history_list()`
46
- * backs the `\s` meta-command (`exec_command_s` in `command.c` prints the
47
- * live in-memory history, NOT the on-disk file). We keep our own
48
- * process-local mirror here, populated by {@link recordHistory} from the
49
- * same funnel the REPL uses to persist a submitted line ({@link
50
- * appendHistory}). `\s` reads it via {@link getHistory}.
51
- *
52
- * Module-level state matches the single-session lifetime of a psql process;
53
- * tests reset it with {@link clearHistory}.
54
- */
44
+ * In-memory command-line history for the current session, oldest first.
45
+ *
46
+ * Upstream psql delegates history to GNU readline, whose `history_list()`
47
+ * backs the `\s` meta-command (`exec_command_s` in `command.c` prints the
48
+ * live in-memory history, NOT the on-disk file). We keep our own
49
+ * process-local mirror here, populated by {@link recordHistory} from the
50
+ * same funnel the REPL uses to persist a submitted line ({@link
51
+ * appendHistory}). `\s` reads it via {@link getHistory}.
52
+ *
53
+ * Module-level state matches the single-session lifetime of a psql process;
54
+ * tests reset it with {@link clearHistory}.
55
+ */
55
56
  let inMemoryHistory = [];
56
57
  /**
57
- * Record one submitted line in the session's in-memory history.
58
- *
59
- * Empty lines and an exact duplicate of the immediately preceding entry are
60
- * dropped, mirroring readline's default behaviour (and {@link
61
- * LineEditor.pushHistory}); HISTCONTROL filtering is applied by the caller
62
- * ({@link appendHistory}) before this is reached.
63
- */
64
- export const recordHistory = (entry) => {
65
- if (entry.length === 0)
66
- return;
67
- if (inMemoryHistory[inMemoryHistory.length - 1] === entry)
68
- return;
69
- inMemoryHistory.push(entry);
58
+ * Record one submitted line in the session's in-memory history.
59
+ *
60
+ * Empty lines and an exact duplicate of the immediately preceding entry are
61
+ * dropped, mirroring readline's default behaviour (and {@link
62
+ * LineEditor.pushHistory}); HISTCONTROL filtering is applied by the caller
63
+ * ({@link appendHistory}) before this is reached.
64
+ */
65
+ const recordHistory = (entry) => {
66
+ if (entry.length === 0) return;
67
+ if (inMemoryHistory[inMemoryHistory.length - 1] === entry) return;
68
+ inMemoryHistory.push(entry);
70
69
  };
71
70
  /**
72
- * Return the session's in-memory history (oldest first). Returns a copy so
73
- * callers can't mutate the backing store.
74
- */
75
- export const getHistory = () => inMemoryHistory.slice();
71
+ * Return the session's in-memory history (oldest first). Returns a copy so
72
+ * callers can't mutate the backing store.
73
+ */
74
+ const getHistory = () => inMemoryHistory.slice();
76
75
  /** Reset the in-memory history. Primarily for tests and `\s`-less restarts. */
77
- export const clearHistory = () => {
78
- inMemoryHistory = [];
76
+ const clearHistory = () => {
77
+ inMemoryHistory = [];
79
78
  };
80
79
  /** Encode a single in-memory entry to the on-disk libreadline form. */
81
80
  const encodeEntry = (entry) => entry.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
82
81
  /**
83
- * Decode a single on-disk libreadline line back to its in-memory form.
84
- *
85
- * Recognised escapes: `\\` → `\`, `\n` → newline, `\r` → CR. Any other
86
- * `\<x>` sequence is left as-is (matching readline's lenient decoder), so
87
- * a stray backslash at the end of the file does not eat the next entry.
88
- */
82
+ * Decode a single on-disk libreadline line back to its in-memory form.
83
+ *
84
+ * Recognised escapes: `\\` → `\`, `\n` → newline, `\r` → CR. Any other
85
+ * `\<x>` sequence is left as-is (matching readline's lenient decoder), so
86
+ * a stray backslash at the end of the file does not eat the next entry.
87
+ */
89
88
  const decodeEntry = (line) => {
90
- let out = "";
91
- for (let i = 0; i < line.length; i++) {
92
- const c = line.charCodeAt(i);
93
- if (c === 0x5c /* '\\' */ && i + 1 < line.length) {
94
- const next = line[i + 1];
95
- if (next === "n") {
96
- out += "\n";
97
- i++;
98
- continue;
99
- }
100
- if (next === "r") {
101
- out += "\r";
102
- i++;
103
- continue;
104
- }
105
- if (next === "\\") {
106
- out += "\\";
107
- i++;
108
- continue;
109
- }
110
- }
111
- out += line[i];
112
- }
113
- return out;
89
+ let out = "";
90
+ for (let i = 0; i < line.length; i++) {
91
+ if (line.charCodeAt(i) === 92 && i + 1 < line.length) {
92
+ const next = line[i + 1];
93
+ if (next === "n") {
94
+ out += "\n";
95
+ i++;
96
+ continue;
97
+ }
98
+ if (next === "r") {
99
+ out += "\r";
100
+ i++;
101
+ continue;
102
+ }
103
+ if (next === "\\") {
104
+ out += "\\";
105
+ i++;
106
+ continue;
107
+ }
108
+ }
109
+ out += line[i];
110
+ }
111
+ return out;
114
112
  };
115
113
  /**
116
- * Return `true` if `entry` should be filtered out under `histcontrol`
117
- * relative to the most recent entry already in history (`prev`).
118
- *
119
- * Mirrors `pg_send_history()`'s filter in `input.c`.
120
- */
114
+ * Return `true` if `entry` should be filtered out under `histcontrol`
115
+ * relative to the most recent entry already in history (`prev`).
116
+ *
117
+ * Mirrors `pg_send_history()`'s filter in `input.c`.
118
+ */
121
119
  const shouldIgnore = (entry, prev, histcontrol) => {
122
- const ignoreSpace = histcontrol === "ignorespace" || histcontrol === "ignoreboth";
123
- const ignoreDups = histcontrol === "ignoredups" || histcontrol === "ignoreboth";
124
- if (ignoreSpace && entry.length > 0 && /^\s/.test(entry))
125
- return true;
126
- if (ignoreDups && prev !== undefined && prev === entry)
127
- return true;
128
- return false;
120
+ const ignoreSpace = histcontrol === "ignorespace" || histcontrol === "ignoreboth";
121
+ const ignoreDups = histcontrol === "ignoredups" || histcontrol === "ignoreboth";
122
+ if (ignoreSpace && entry.length > 0 && /^\s/.test(entry)) return true;
123
+ if (ignoreDups && prev !== void 0 && prev === entry) return true;
124
+ return false;
129
125
  };
130
126
  /**
131
- * Read a libreadline history file and return entries in chronological
132
- * order (oldest first). A missing file resolves to `[]` so callers can
133
- * unconditionally `loadHistory` at startup.
134
- *
135
- * Lines beginning with `#` are silently skipped (libreadline timestamp
136
- * markers; psql itself never writes them but we tolerate them).
137
- *
138
- * Other I/O errors (EACCES, EISDIR, …) propagate to the caller.
139
- */
140
- export const loadHistory = async (filePath) => {
141
- let raw;
142
- try {
143
- raw = await fs.readFile(filePath, "utf8");
144
- }
145
- catch (err) {
146
- if (err &&
147
- typeof err === "object" &&
148
- "code" in err &&
149
- err.code === "ENOENT") {
150
- return [];
151
- }
152
- throw err;
153
- }
154
- // Strip a single trailing newline so a well-formed file ending in `\n`
155
- // doesn't produce a phantom empty entry. Don't strip more than one — a
156
- // blank line in the middle of the file represents an entry that was
157
- // literally the empty string (rare but possible), and we round-trip it.
158
- if (raw.endsWith("\n"))
159
- raw = raw.slice(0, -1);
160
- if (raw.length === 0)
161
- return [];
162
- const entries = [];
163
- for (const line of raw.split("\n")) {
164
- if (line.startsWith("#"))
165
- continue;
166
- entries.push(decodeEntry(line));
167
- }
168
- return entries;
127
+ * Read a libreadline history file and return entries in chronological
128
+ * order (oldest first). A missing file resolves to `[]` so callers can
129
+ * unconditionally `loadHistory` at startup.
130
+ *
131
+ * Lines beginning with `#` are silently skipped (libreadline timestamp
132
+ * markers; psql itself never writes them but we tolerate them).
133
+ *
134
+ * Other I/O errors (EACCES, EISDIR, …) propagate to the caller.
135
+ */
136
+ const loadHistory = async (filePath) => {
137
+ let raw;
138
+ try {
139
+ raw = await promises.readFile(filePath, "utf8");
140
+ } catch (err) {
141
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return [];
142
+ throw err;
143
+ }
144
+ if (raw.endsWith("\n")) raw = raw.slice(0, -1);
145
+ if (raw.length === 0) return [];
146
+ const entries = [];
147
+ for (const line of raw.split("\n")) {
148
+ if (line.startsWith("#")) continue;
149
+ entries.push(decodeEntry(line));
150
+ }
151
+ return entries;
169
152
  };
170
153
  /**
171
- * Append a single entry to the history file, subject to HISTCONTROL.
172
- *
173
- * Implementation notes:
174
- *
175
- * - On POSIX, `fs.appendFile` opens with `O_APPEND`, which guarantees
176
- * each `write(2)` lands at the current end-of-file. A single
177
- * encoded entry plus its trailing `\n` is one write, so concurrent
178
- * psql instances writing to the same history file never interleave
179
- * within an entry. We accept a small race on Windows.
180
- * - `ignoredups` consults the *last* entry currently on disk. We read
181
- * the tail of the file rather than the whole history to keep this
182
- * O(1)-ish for large histories; for simplicity we read everything
183
- * when ignoredups is in effect — typical history files are well
184
- * under 1 MiB.
185
- */
186
- export const appendHistory = async (filePath, entry, histcontrol = "none") => {
187
- if (histcontrol === "ignoredups" ||
188
- histcontrol === "ignoreboth" ||
189
- histcontrol === "ignorespace") {
190
- let prev;
191
- if (histcontrol === "ignoredups" || histcontrol === "ignoreboth") {
192
- const existing = await loadHistory(filePath);
193
- prev = existing[existing.length - 1];
194
- }
195
- if (shouldIgnore(entry, prev, histcontrol))
196
- return;
197
- }
198
- // Mirror the line into the session's in-memory history (what `\s` prints)
199
- // using the same funnel that persists it to disk, so the two stay in
200
- // lock-step under HISTCONTROL. Recording happens AFTER the ignore check
201
- // so an ignored line is absent from both.
202
- recordHistory(entry);
203
- await fs.appendFile(filePath, encodeEntry(entry) + "\n", "utf8");
154
+ * Append a single entry to the history file, subject to HISTCONTROL.
155
+ *
156
+ * Implementation notes:
157
+ *
158
+ * - On POSIX, `fs.appendFile` opens with `O_APPEND`, which guarantees
159
+ * each `write(2)` lands at the current end-of-file. A single
160
+ * encoded entry plus its trailing `\n` is one write, so concurrent
161
+ * psql instances writing to the same history file never interleave
162
+ * within an entry. We accept a small race on Windows.
163
+ * - `ignoredups` consults the *last* entry currently on disk. We read
164
+ * the tail of the file rather than the whole history to keep this
165
+ * O(1)-ish for large histories; for simplicity we read everything
166
+ * when ignoredups is in effect — typical history files are well
167
+ * under 1 MiB.
168
+ */
169
+ const appendHistory = async (filePath, entry, histcontrol = "none") => {
170
+ if (histcontrol === "ignoredups" || histcontrol === "ignoreboth" || histcontrol === "ignorespace") {
171
+ let prev;
172
+ if (histcontrol === "ignoredups" || histcontrol === "ignoreboth") {
173
+ const existing = await loadHistory(filePath);
174
+ prev = existing[existing.length - 1];
175
+ }
176
+ if (shouldIgnore(entry, prev, histcontrol)) return;
177
+ }
178
+ recordHistory(entry);
179
+ await promises.appendFile(filePath, encodeEntry(entry) + "\n", "utf8");
204
180
  };
205
181
  /**
206
- * Trim the history file to its last `maxLines` entries.
207
- *
208
- * If `maxLines <= 0` the file is removed entirely (matching psql's
209
- * behaviour when HISTSIZE is set to 0). If the file already fits the
210
- * cap, it's left untouched. Otherwise we write the kept tail to a
211
- * sibling temp file and `rename` it into place.
212
- *
213
- * `rename(2)` on POSIX is atomic w.r.t. observers of the destination
214
- * path; on Windows the platform call may briefly fail if another
215
- * process has the destination open, in which case the error propagates.
216
- */
217
- export const truncateHistory = async (filePath, maxLines) => {
218
- if (maxLines <= 0) {
219
- try {
220
- await fs.unlink(filePath);
221
- }
222
- catch (err) {
223
- if (err &&
224
- typeof err === "object" &&
225
- "code" in err &&
226
- err.code === "ENOENT") {
227
- return;
228
- }
229
- throw err;
230
- }
231
- return;
232
- }
233
- const entries = await loadHistory(filePath);
234
- if (entries.length <= maxLines)
235
- return;
236
- const kept = entries.slice(entries.length - maxLines);
237
- const body = kept.map(encodeEntry).join("\n") + "\n";
238
- const dir = path.dirname(filePath);
239
- const base = path.basename(filePath);
240
- const tmpPath = path.join(dir, `.${base}.${randomUUID()}.tmp`);
241
- await fs.writeFile(tmpPath, body, { encoding: "utf8", mode: 0o600 });
242
- try {
243
- await fs.rename(tmpPath, filePath);
244
- }
245
- catch (err) {
246
- // Best-effort cleanup; the temp file is harmless but noisy.
247
- try {
248
- await fs.unlink(tmpPath);
249
- }
250
- catch {
251
- /* ignore */
252
- }
253
- throw err;
254
- }
182
+ * Trim the history file to its last `maxLines` entries.
183
+ *
184
+ * If `maxLines <= 0` the file is removed entirely (matching psql's
185
+ * behaviour when HISTSIZE is set to 0). If the file already fits the
186
+ * cap, it's left untouched. Otherwise we write the kept tail to a
187
+ * sibling temp file and `rename` it into place.
188
+ *
189
+ * `rename(2)` on POSIX is atomic w.r.t. observers of the destination
190
+ * path; on Windows the platform call may briefly fail if another
191
+ * process has the destination open, in which case the error propagates.
192
+ */
193
+ const truncateHistory = async (filePath, maxLines) => {
194
+ if (maxLines <= 0) {
195
+ try {
196
+ await promises.unlink(filePath);
197
+ } catch (err) {
198
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return;
199
+ throw err;
200
+ }
201
+ return;
202
+ }
203
+ const entries = await loadHistory(filePath);
204
+ if (entries.length <= maxLines) return;
205
+ const body = entries.slice(entries.length - maxLines).map(encodeEntry).join("\n") + "\n";
206
+ const dir = path.dirname(filePath);
207
+ const base = path.basename(filePath);
208
+ const tmpPath = path.join(dir, `.${base}.${randomUUID()}.tmp`);
209
+ await promises.writeFile(tmpPath, body, {
210
+ encoding: "utf8",
211
+ mode: 384
212
+ });
213
+ try {
214
+ await promises.rename(tmpPath, filePath);
215
+ } catch (err) {
216
+ try {
217
+ await promises.unlink(tmpPath);
218
+ } catch {}
219
+ throw err;
220
+ }
255
221
  };
256
222
  /**
257
- * Resolve the on-disk history file path psql would use, in order of
258
- * precedence:
259
- *
260
- * 1. `$PSQL_HISTORY` if set and non-empty.
261
- * 2. On Windows: `%APPDATA%\postgresql\psql_history`.
262
- * 3. Otherwise: `$HOME/.psql_history` (falling back to `os.homedir()`
263
- * if `$HOME` is unset, e.g. in some sandboxes).
264
- *
265
- * Pure function — `env` defaults to `process.env` but can be injected
266
- * for tests.
267
- */
268
- export const defaultHistoryPath = (env = process.env) => {
269
- const explicit = env.PSQL_HISTORY;
270
- if (explicit !== undefined && explicit.length > 0)
271
- return explicit;
272
- if (process.platform === "win32") {
273
- const appdata = env.APPDATA;
274
- if (appdata !== undefined && appdata.length > 0) {
275
- return path.join(appdata, "postgresql", "psql_history");
276
- }
277
- // Fall through to homedir() if APPDATA isn't set; matches psql's
278
- // graceful degradation on a minimally-configured Windows session.
279
- }
280
- const home = env.HOME ?? os.homedir();
281
- return path.join(home, ".psql_history");
223
+ * Resolve the on-disk history file path psql would use, in order of
224
+ * precedence:
225
+ *
226
+ * 1. `$PSQL_HISTORY` if set and non-empty.
227
+ * 2. On Windows: `%APPDATA%\postgresql\psql_history`.
228
+ * 3. Otherwise: `$HOME/.psql_history` (falling back to `os.homedir()`
229
+ * if `$HOME` is unset, e.g. in some sandboxes).
230
+ *
231
+ * Pure function — `env` defaults to `process.env` but can be injected
232
+ * for tests.
233
+ */
234
+ const defaultHistoryPath = (env = process.env) => {
235
+ const explicit = env.PSQL_HISTORY;
236
+ if (explicit !== void 0 && explicit.length > 0) return explicit;
237
+ if (process.platform === "win32") {
238
+ const appdata = env.APPDATA;
239
+ if (appdata !== void 0 && appdata.length > 0) return path.join(appdata, "postgresql", "psql_history");
240
+ }
241
+ const home = env.HOME ?? os.homedir();
242
+ return path.join(home, ".psql_history");
282
243
  };
283
244
  /**
284
- * Resolve the effective HISTSIZE: the `HISTSIZE` env var (if a
285
- * non-negative integer) or psql's compiled-in default of 500.
286
- *
287
- * Values that fail to parse as a non-negative integer fall back to the
288
- * default — psql itself ignores malformed HISTSIZE and warns, but at
289
- * this layer we don't have a logger.
290
- */
291
- export const resolveHistSize = (env = process.env) => {
292
- const raw = env.HISTSIZE;
293
- if (raw === undefined || raw === "")
294
- return DEFAULT_HISTSIZE;
295
- const n = Number(raw);
296
- if (!Number.isInteger(n) || n < 0)
297
- return DEFAULT_HISTSIZE;
298
- return n;
245
+ * Resolve the effective HISTSIZE: the `HISTSIZE` env var (if a
246
+ * non-negative integer) or psql's compiled-in default of 500.
247
+ *
248
+ * Values that fail to parse as a non-negative integer fall back to the
249
+ * default — psql itself ignores malformed HISTSIZE and warns, but at
250
+ * this layer we don't have a logger.
251
+ */
252
+ const resolveHistSize = (env = process.env) => {
253
+ const raw = env.HISTSIZE;
254
+ if (raw === void 0 || raw === "") return DEFAULT_HISTSIZE;
255
+ const n = Number(raw);
256
+ if (!Number.isInteger(n) || n < 0) return DEFAULT_HISTSIZE;
257
+ return n;
299
258
  };
259
+ //#endregion
260
+ export { appendHistory, clearHistory, defaultHistoryPath, getHistory, loadHistory, recordHistory, resolveHistSize, truncateHistory };