neon 3.0.0 → 3.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.
Files changed (198) hide show
  1. package/README.md +54 -0
  2. package/dist/_shared/auth_selection.js +76 -79
  3. package/dist/_shared/credentials.js +166 -187
  4. package/dist/_shared/env-core/env.js +354 -517
  5. package/dist/_shared/env-core/reuse-secrets.js +159 -203
  6. package/dist/_shared/paths.js +129 -126
  7. package/dist/_shared/profiles.js +192 -242
  8. package/dist/_shared/secure_file.js +36 -38
  9. package/dist/_virtual/_rolldown/runtime.js +13 -0
  10. package/dist/analytics.js +163 -207
  11. package/dist/api.js +815 -758
  12. package/dist/auth.js +121 -141
  13. package/dist/auth_context.js +39 -53
  14. package/dist/cli.js +4 -7
  15. package/dist/commands/api.js +220 -250
  16. package/dist/commands/api_keys.js +251 -314
  17. package/dist/commands/auth.js +283 -328
  18. package/dist/commands/bootstrap.js +372 -437
  19. package/dist/commands/branches.js +304 -455
  20. package/dist/commands/bucket.js +374 -514
  21. package/dist/commands/checkout.js +213 -298
  22. package/dist/commands/config.js +573 -690
  23. package/dist/commands/connection_string.js +137 -165
  24. package/dist/commands/data_api.js +238 -260
  25. package/dist/commands/databases.js +67 -76
  26. package/dist/commands/deploy.js +31 -25
  27. package/dist/commands/dev.js +639 -719
  28. package/dist/commands/diff.js +156 -200
  29. package/dist/commands/env.js +243 -303
  30. package/dist/commands/functions.js +275 -355
  31. package/dist/commands/index.js +70 -65
  32. package/dist/commands/init.js +84 -119
  33. package/dist/commands/inspect.js +55 -55
  34. package/dist/commands/ip_allow.js +88 -120
  35. package/dist/commands/link.js +874 -1019
  36. package/dist/commands/logs.js +291 -0
  37. package/dist/commands/neon_auth.js +725 -933
  38. package/dist/commands/operations.js +34 -25
  39. package/dist/commands/orgs.js +28 -18
  40. package/dist/commands/profile.js +614 -845
  41. package/dist/commands/projects.js +313 -373
  42. package/dist/commands/psql.js +60 -58
  43. package/dist/commands/roles.js +55 -58
  44. package/dist/commands/schema_diff.js +87 -131
  45. package/dist/commands/set_context.js +34 -26
  46. package/dist/commands/snapshots.js +288 -413
  47. package/dist/commands/status.js +41 -37
  48. package/dist/commands/user.js +21 -10
  49. package/dist/commands/vpc_endpoints.js +85 -113
  50. package/dist/config.js +7 -6
  51. package/dist/config_format.js +50 -66
  52. package/dist/config_template.js +128 -157
  53. package/dist/context.js +183 -235
  54. package/dist/current_branch_fast_path.js +40 -49
  55. package/dist/dev/env.js +313 -394
  56. package/dist/dev/functions.js +54 -68
  57. package/dist/dev/inputs.js +46 -58
  58. package/dist/dev/runtime.js +135 -164
  59. package/dist/dev/websocket.js +766 -959
  60. package/dist/env.js +27 -33
  61. package/dist/env_file.js +118 -132
  62. package/dist/env_services.js +36 -38
  63. package/dist/errors.js +57 -68
  64. package/dist/functions_api.js +45 -43
  65. package/dist/help.js +189 -140
  66. package/dist/index.js +182 -257
  67. package/dist/init/agents.js +137 -118
  68. package/dist/init/auth.js +58 -68
  69. package/dist/init/bootstrap.js +325 -396
  70. package/dist/init/build_config.js +4 -2
  71. package/dist/init/detect_agent.js +56 -101
  72. package/dist/init/editors.js +35 -52
  73. package/dist/init/enrich_output.js +51 -66
  74. package/dist/init/extension.js +134 -171
  75. package/dist/init/inspect.js +179 -266
  76. package/dist/init/interactive.js +510 -622
  77. package/dist/init/neonctl.js +117 -168
  78. package/dist/init/orchestrate.js +157 -173
  79. package/dist/init/phases/auth.js +188 -202
  80. package/dist/init/phases/cleanup.js +23 -23
  81. package/dist/init/phases/db.js +251 -277
  82. package/dist/init/phases/getting_started.js +213 -223
  83. package/dist/init/phases/mcp.js +174 -224
  84. package/dist/init/phases/migrations.js +247 -248
  85. package/dist/init/phases/neon_auth.js +114 -133
  86. package/dist/init/phases/setup.js +546 -703
  87. package/dist/init/phases/skills.js +75 -86
  88. package/dist/init/phases/status.js +72 -67
  89. package/dist/init/resolve_context.js +102 -99
  90. package/dist/init/route_command.js +91 -98
  91. package/dist/init/skills.js +174 -218
  92. package/dist/init/vsix.js +77 -99
  93. package/dist/log.js +17 -16
  94. package/dist/neon_services.js +104 -129
  95. package/dist/parameters.gen.js +481 -471
  96. package/dist/pkg.js +17 -19
  97. package/dist/profile_keys.js +44 -47
  98. package/dist/psql/cli.js +44 -47
  99. package/dist/psql/command/cmd_cond.js +231 -406
  100. package/dist/psql/command/cmd_connect.js +557 -764
  101. package/dist/psql/command/cmd_copy.js +727 -983
  102. package/dist/psql/command/cmd_describe.js +1499 -1688
  103. package/dist/psql/command/cmd_format.js +733 -905
  104. package/dist/psql/command/cmd_io.js +1293 -2082
  105. package/dist/psql/command/cmd_lo.js +297 -359
  106. package/dist/psql/command/cmd_meta.js +727 -878
  107. package/dist/psql/command/cmd_misc.js +138 -172
  108. package/dist/psql/command/cmd_pipeline.js +547 -1099
  109. package/dist/psql/command/cmd_restrict.js +119 -155
  110. package/dist/psql/command/cmd_show.js +529 -688
  111. package/dist/psql/command/dispatch.js +261 -325
  112. package/dist/psql/command/inputQueue.js +35 -33
  113. package/dist/psql/command/shared.js +49 -63
  114. package/dist/psql/complete/filenames.js +90 -133
  115. package/dist/psql/complete/index.js +59 -97
  116. package/dist/psql/complete/matcher.js +236 -300
  117. package/dist/psql/complete/psqlVars.js +218 -223
  118. package/dist/psql/complete/queries.js +159 -177
  119. package/dist/psql/complete/rules.js +1493 -2299
  120. package/dist/psql/core/common.js +762 -1180
  121. package/dist/psql/core/help.js +456 -546
  122. package/dist/psql/core/mainloop.js +692 -1302
  123. package/dist/psql/core/prompt.js +391 -408
  124. package/dist/psql/core/settings.js +429 -644
  125. package/dist/psql/core/sqlHelp.js +480 -554
  126. package/dist/psql/core/startup.js +626 -815
  127. package/dist/psql/core/syncVars.js +67 -110
  128. package/dist/psql/core/variables.js +156 -278
  129. package/dist/psql/describe/formatters.js +884 -1285
  130. package/dist/psql/describe/processNamePattern.js +173 -260
  131. package/dist/psql/describe/queries.js +1368 -2403
  132. package/dist/psql/describe/versionGate.js +32 -41
  133. package/dist/psql/index.js +1414 -1927
  134. package/dist/psql/io/history.js +232 -271
  135. package/dist/psql/io/input.js +103 -108
  136. package/dist/psql/io/lineEditor/buffer.js +238 -319
  137. package/dist/psql/io/lineEditor/complete.js +135 -213
  138. package/dist/psql/io/lineEditor/filename.js +139 -148
  139. package/dist/psql/io/lineEditor/index.js +653 -870
  140. package/dist/psql/io/lineEditor/keymap.js +544 -702
  141. package/dist/psql/io/lineEditor/vt100.js +294 -341
  142. package/dist/psql/io/pgpass.js +158 -187
  143. package/dist/psql/io/pgservice.js +146 -183
  144. package/dist/psql/io/psqlrc.js +328 -403
  145. package/dist/psql/print/aligned.js +1020 -1683
  146. package/dist/psql/print/asciidoc.js +180 -214
  147. package/dist/psql/print/crosstab.js +281 -442
  148. package/dist/psql/print/csv.js +48 -70
  149. package/dist/psql/print/html.js +195 -226
  150. package/dist/psql/print/json.js +75 -88
  151. package/dist/psql/print/latex.js +291 -364
  152. package/dist/psql/print/pager.js +171 -242
  153. package/dist/psql/print/troff.js +194 -226
  154. package/dist/psql/print/unaligned.js +69 -95
  155. package/dist/psql/print/units.js +167 -169
  156. package/dist/psql/scanner/slash.js +428 -483
  157. package/dist/psql/scanner/sql.js +445 -889
  158. package/dist/psql/scanner/stringutils.js +309 -379
  159. package/dist/psql/types/index.js +2 -7
  160. package/dist/psql/types/scanner.js +25 -22
  161. package/dist/psql/wire/connection.js +2042 -2803
  162. package/dist/psql/wire/copy.js +84 -100
  163. package/dist/psql/wire/notify.js +39 -59
  164. package/dist/psql/wire/pipeline.js +305 -518
  165. package/dist/psql/wire/protocol.js +349 -417
  166. package/dist/psql/wire/sasl.js +180 -265
  167. package/dist/psql/wire/tls.js +400 -561
  168. package/dist/storage_api.js +115 -129
  169. package/dist/test_utils/fixtures.js +94 -113
  170. package/dist/test_utils/oauth_server.js +10 -7
  171. package/dist/test_utils/project_dir.js +33 -0
  172. package/dist/utils/ai_gateway_notice.js +131 -162
  173. package/dist/utils/api_enums.js +21 -28
  174. package/dist/utils/auth.js +10 -4
  175. package/dist/utils/branch_notice.js +20 -19
  176. package/dist/utils/branch_picker.js +83 -89
  177. package/dist/utils/cli_name.js +15 -12
  178. package/dist/utils/compute_units.js +20 -27
  179. package/dist/utils/config_diff.js +127 -158
  180. package/dist/utils/enrichers.js +95 -148
  181. package/dist/utils/esbuild.js +130 -189
  182. package/dist/utils/flags.js +35 -47
  183. package/dist/utils/formats.js +8 -15
  184. package/dist/utils/git_diff.js +69 -80
  185. package/dist/utils/inspect_db.js +101 -143
  186. package/dist/utils/inspect_queries.js +179 -142
  187. package/dist/utils/middlewares.js +37 -44
  188. package/dist/utils/openapi.js +87 -99
  189. package/dist/utils/package_manager.js +312 -110
  190. package/dist/utils/point_in_time.js +49 -53
  191. package/dist/utils/psql.js +89 -106
  192. package/dist/utils/service_picker.js +55 -58
  193. package/dist/utils/string.js +5 -5
  194. package/dist/utils/ui.js +38 -55
  195. package/dist/utils/write_sync.js +26 -35
  196. package/dist/utils/zip.js +4 -3
  197. package/dist/writer.js +67 -87
  198. package/package.json +7 -5
@@ -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 };