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