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,393 +1,331 @@
1
- /**
2
- * psql large-object backslash commands (WP-23).
3
- *
4
- * TypeScript port of upstream `src/bin/psql/large_obj.c`:
5
- *
6
- * - `\lo_list` / `\lo_list+` — list large-object metadata
7
- * - `\lo_import FILE [COMMENT]` — read a local file, store as new LO
8
- * - `\lo_export OID FILE` — read LO, write to a local file
9
- * - `\lo_unlink OID` — delete an LO
10
- *
11
- * The upstream `do_lo_*` functions wrap their work in
12
- * `start_lo_xact`/`finish_lo_xact` and call libpq's lo client API
13
- * (`lo_import`, `lo_export`, `lo_unlink`). We don't have libpq's
14
- * client-side large-object API in this TS port, so we drive the same
15
- * operations through the server-side functions that psql ≥ 9.4 exposes:
16
- *
17
- * - `pg_catalog.lo_from_bytea(0, $bytea)` returns the new OID in one
18
- * call (replaces the upstream lo_creat + lo_write loop).
19
- * - `pg_catalog.lo_get($oid)` returns the bytes as a `bytea` value.
20
- * - `pg_catalog.lo_unlink($oid)` deletes the LO.
21
- *
22
- * All three calls use the connection's extended-query path (WP-21) for
23
- * parameter binding, which keeps us out of the libpq escape dance.
24
- * Bytea payloads are sent as `\x<hex>` text — the server's text-format
25
- * bytea parser converts back to bytes.
26
- *
27
- * `\lo_list` runs the same SELECT against `pg_largeobject_metadata` that
28
- * upstream's `listLargeObjects()` from `describe.c` uses (already ported
29
- * in WP-20's `queries.ts::listLargeObjects`). We register a primary
30
- * `lo_list` / `lo_list+` spec here so the dispatcher takes our entry
31
- * over the existing alias `dl::lo_list` (which would not match the
32
- * `+` suffix).
33
- *
34
- * Variable side-effects: `\lo_import` sets `LASTOID` to the new OID
35
- * (mirrors `do_lo_import`'s `SetVariable(pset.vars, "LASTOID", oidbuf)`).
36
- */
37
- import { Buffer } from "node:buffer";
38
- import { promises as fsPromises } from "node:fs";
39
- import { listLargeObjects } from "../describe/queries.js";
40
- import { alignedPrinter } from "../print/aligned.js";
41
1
  import { writeErr, writeOut } from "./shared.js";
42
- // ---------------------------------------------------------------------------
43
- // Helpers shared by all four commands
44
- // ---------------------------------------------------------------------------
2
+ import { alignedPrinter } from "../print/aligned.js";
3
+ import { listLargeObjects } from "../describe/queries.js";
4
+ import { promises } from "node:fs";
5
+ import { Buffer } from "node:buffer";
6
+ //#region src/psql/command/cmd_lo.ts
7
+ /**
8
+ * psql large-object backslash commands (WP-23).
9
+ *
10
+ * TypeScript port of upstream `src/bin/psql/large_obj.c`:
11
+ *
12
+ * - `\lo_list` / `\lo_list+` — list large-object metadata
13
+ * - `\lo_import FILE [COMMENT]` — read a local file, store as new LO
14
+ * - `\lo_export OID FILE` — read LO, write to a local file
15
+ * - `\lo_unlink OID` — delete an LO
16
+ *
17
+ * The upstream `do_lo_*` functions wrap their work in
18
+ * `start_lo_xact`/`finish_lo_xact` and call libpq's lo client API
19
+ * (`lo_import`, `lo_export`, `lo_unlink`). We don't have libpq's
20
+ * client-side large-object API in this TS port, so we drive the same
21
+ * operations through the server-side functions that psql ≥ 9.4 exposes:
22
+ *
23
+ * - `pg_catalog.lo_from_bytea(0, $bytea)` returns the new OID in one
24
+ * call (replaces the upstream lo_creat + lo_write loop).
25
+ * - `pg_catalog.lo_get($oid)` returns the bytes as a `bytea` value.
26
+ * - `pg_catalog.lo_unlink($oid)` deletes the LO.
27
+ *
28
+ * All three calls use the connection's extended-query path (WP-21) for
29
+ * parameter binding, which keeps us out of the libpq escape dance.
30
+ * Bytea payloads are sent as `\x<hex>` text — the server's text-format
31
+ * bytea parser converts back to bytes.
32
+ *
33
+ * `\lo_list` runs the same SELECT against `pg_largeobject_metadata` that
34
+ * upstream's `listLargeObjects()` from `describe.c` uses (already ported
35
+ * in WP-20's `queries.ts::listLargeObjects`). We register a primary
36
+ * `lo_list` / `lo_list+` spec here so the dispatcher takes our entry
37
+ * over the existing alias `dl::lo_list` (which would not match the
38
+ * `+` suffix).
39
+ *
40
+ * Variable side-effects: `\lo_import` sets `LASTOID` to the new OID
41
+ * (mirrors `do_lo_import`'s `SetVariable(pset.vars, "LASTOID", oidbuf)`).
42
+ */
45
43
  /** Return the live connection, or null. */
46
44
  const conn = (ctx) => ctx.settings.db;
47
45
  /** Emit "no current connection" error in the psql style. */
48
46
  const noConn = (ctx) => {
49
- writeErr(`\\${ctx.cmdName}: no connection to the server\n`);
50
- ctx.settings.lastErrorResult = { message: "no connection to the server" };
51
- return { status: "error" };
47
+ writeErr(`\\${ctx.cmdName}: no connection to the server\n`);
48
+ ctx.settings.lastErrorResult = { message: "no connection to the server" };
49
+ return { status: "error" };
52
50
  };
53
51
  /** Pull the diagnostic-style error message off a thrown value. */
54
52
  const errMsg = (err) => err instanceof Error ? err.message : String(err);
55
53
  /**
56
- * Encode a `Buffer` as a Postgres text-format bytea literal: `\x<hex>`.
57
- * Hex form is unambiguous and works regardless of `bytea_output` or
58
- * `standard_conforming_strings`.
59
- */
54
+ * Encode a `Buffer` as a Postgres text-format bytea literal: `\x<hex>`.
55
+ * Hex form is unambiguous and works regardless of `bytea_output` or
56
+ * `standard_conforming_strings`.
57
+ */
60
58
  const byteaText = (buf) => `\\x${buf.toString("hex")}`;
61
59
  /**
62
- * Parse a string argument as an unsigned 32-bit OID. Returns `null` on
63
- * malformed input (negative, non-integer, or out of range). Matches the
64
- * permissive behaviour of upstream `atooid`, which accepts any leading
65
- * digit run.
66
- */
60
+ * Parse a string argument as an unsigned 32-bit OID. Returns `null` on
61
+ * malformed input (negative, non-integer, or out of range). Matches the
62
+ * permissive behaviour of upstream `atooid`, which accepts any leading
63
+ * digit run.
64
+ */
67
65
  const parseOid = (raw) => {
68
- if (!/^\d+$/.test(raw))
69
- return null;
70
- const n = Number(raw);
71
- if (!Number.isFinite(n) || n < 0 || n > 0xffffffff)
72
- return null;
73
- return n;
66
+ if (!/^\d+$/.test(raw)) return null;
67
+ const n = Number(raw);
68
+ if (!Number.isFinite(n) || n < 0 || n > 4294967295) return null;
69
+ return n;
74
70
  };
75
71
  /**
76
- * Coerce a single cell coming back from the protocol layer into a string.
77
- * Used by the `\lo_list` renderer. Matches the helper in
78
- * `describe/formatters.ts`.
79
- */
72
+ * Coerce a single cell coming back from the protocol layer into a string.
73
+ * Used by the `\lo_list` renderer. Matches the helper in
74
+ * `describe/formatters.ts`.
75
+ */
80
76
  const cellToString = (v) => {
81
- if (v === null || v === undefined)
82
- return "";
83
- if (typeof v === "string")
84
- return v;
85
- if (Buffer.isBuffer(v))
86
- return v.toString("utf-8");
87
- if (typeof v === "number" ||
88
- typeof v === "boolean" ||
89
- typeof v === "bigint") {
90
- return String(v);
91
- }
92
- try {
93
- return JSON.stringify(v);
94
- }
95
- catch {
96
- return "";
97
- }
77
+ if (v === null || v === void 0) return "";
78
+ if (typeof v === "string") return v;
79
+ if (Buffer.isBuffer(v)) return v.toString("utf-8");
80
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
81
+ try {
82
+ return JSON.stringify(v);
83
+ } catch {
84
+ return "";
85
+ }
98
86
  };
99
- // ---------------------------------------------------------------------------
100
- // \lo_list / \lo_list+
101
- // ---------------------------------------------------------------------------
102
87
  /**
103
- * Render the result of `listLargeObjects` through the aligned printer.
104
- * The query body itself lives in `describe/queries.ts` (WP-20) — we just
105
- * dispatch to it and feed the result through `alignedPrinter` with the
106
- * upstream "Large objects" title.
107
- */
88
+ * Render the result of `listLargeObjects` through the aligned printer.
89
+ * The query body itself lives in `describe/queries.ts` (WP-20) — we just
90
+ * dispatch to it and feed the result through `alignedPrinter` with the
91
+ * upstream "Large objects" title.
92
+ */
108
93
  const runLoList = async (ctx, verbose) => {
109
- const c = conn(ctx);
110
- if (!c)
111
- return noConn(ctx);
112
- const query = listLargeObjects({ verbose, serverVersion: c.serverVersion });
113
- try {
114
- const rs = await c.query(query.sql, query.params);
115
- const coerced = {
116
- ...rs,
117
- rows: rs.rows.map((row) => row.map((v) => v === null || v === undefined ? null : cellToString(v))),
118
- };
119
- const titleOverride = query.description ?? ctx.settings.popt.title;
120
- const opts = {
121
- ...ctx.settings.popt,
122
- title: titleOverride,
123
- topt: {
124
- ...ctx.settings.popt.topt,
125
- title: titleOverride ?? ctx.settings.popt.topt.title,
126
- },
127
- };
128
- await alignedPrinter.printQuery(coerced, opts, process.stdout);
129
- return { status: "ok" };
130
- }
131
- catch (err) {
132
- writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
133
- ctx.settings.lastErrorResult = { message: errMsg(err) };
134
- return { status: "error" };
135
- }
94
+ const c = conn(ctx);
95
+ if (!c) return noConn(ctx);
96
+ const query = listLargeObjects({
97
+ verbose,
98
+ serverVersion: c.serverVersion
99
+ });
100
+ try {
101
+ const rs = await c.query(query.sql, query.params);
102
+ const coerced = {
103
+ ...rs,
104
+ rows: rs.rows.map((row) => row.map((v) => v === null || v === void 0 ? null : cellToString(v)))
105
+ };
106
+ const titleOverride = query.description ?? ctx.settings.popt.title;
107
+ const opts = {
108
+ ...ctx.settings.popt,
109
+ title: titleOverride,
110
+ topt: {
111
+ ...ctx.settings.popt.topt,
112
+ title: titleOverride ?? ctx.settings.popt.topt.title
113
+ }
114
+ };
115
+ await alignedPrinter.printQuery(coerced, opts, process.stdout);
116
+ return { status: "ok" };
117
+ } catch (err) {
118
+ writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
119
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
120
+ return { status: "error" };
121
+ }
136
122
  };
137
123
  /** `\lo_list` — non-verbose listing. */
138
- export const cmdLoList = {
139
- name: "lo_list",
140
- helpKey: "lo_list",
141
- run: (ctx) => runLoList(ctx, false),
124
+ const cmdLoList = {
125
+ name: "lo_list",
126
+ helpKey: "lo_list",
127
+ run: (ctx) => runLoList(ctx, false)
142
128
  };
143
129
  /** `\lo_list+` — verbose listing (adds Access privileges column). */
144
- export const cmdLoListPlus = {
145
- name: "lo_list+",
146
- helpKey: "lo_list",
147
- run: (ctx) => runLoList(ctx, true),
130
+ const cmdLoListPlus = {
131
+ name: "lo_list+",
132
+ helpKey: "lo_list",
133
+ run: (ctx) => runLoList(ctx, true)
148
134
  };
149
- // ---------------------------------------------------------------------------
150
- // \lo_import FILE [COMMENT]
151
- // ---------------------------------------------------------------------------
152
135
  /**
153
- * `\lo_import FILE [COMMENT]`.
154
- *
155
- * Strategy:
156
- * 1. `fs.readFile(file)` → Buffer.
157
- * 2. `SELECT pg_catalog.lo_from_bytea(0, $1::bytea)` → new OID. The
158
- * bytea param is the file's bytes serialized as `\x<hex>` text.
159
- * 3. If COMMENT was supplied: `COMMENT ON LARGE OBJECT <oid> IS
160
- * '<escaped>'` (single execSimple round-trip, escaped via the
161
- * connection's `escapeLiteral`).
162
- * 4. Print `lo_import <oid>\n` and set the `LASTOID` variable.
163
- *
164
- * Errors fall through to the standard `\lo_import: <msg>` diagnostic.
165
- */
166
- export const cmdLoImport = {
167
- name: "lo_import",
168
- helpKey: "lo_import",
169
- run: async (ctx) => {
170
- const c = conn(ctx);
171
- if (!c)
172
- return noConn(ctx);
173
- const file = ctx.nextArg("normal");
174
- if (file === null || file.length === 0) {
175
- writeErr("\\lo_import: missing required argument\n");
176
- ctx.settings.lastErrorResult = {
177
- message: "missing required argument",
178
- };
179
- return { status: "error" };
180
- }
181
- // Comment is the next lexed slash-arg token, mirroring upstream
182
- // `do_lo_import`'s second `psql_scan_slash_option(OT_NORMAL)`. Using the
183
- // lexer (not the raw line) means it picks up the token AFTER the file —
184
- // `restOfLine()` ignored the read cursor and re-included the filename
185
- // itself in the comment.
186
- const commentRaw = ctx.nextArg("normal");
187
- const comment = commentRaw !== null && commentRaw.length > 0 ? commentRaw : null;
188
- let bytes;
189
- try {
190
- bytes = await fsPromises.readFile(file);
191
- }
192
- catch (err) {
193
- writeErr(`\\lo_import: ${errMsg(err)}\n`);
194
- ctx.settings.lastErrorResult = { message: errMsg(err) };
195
- return { status: "error" };
196
- }
197
- let oidStr;
198
- try {
199
- const rs = await c.query("SELECT pg_catalog.lo_from_bytea(0, $1::bytea)", [byteaText(bytes)]);
200
- if (rs.rows.length === 0) {
201
- throw new Error("lo_from_bytea returned no rows");
202
- }
203
- oidStr = cellToString(rs.rows[0][0]);
204
- if (!/^\d+$/.test(oidStr)) {
205
- throw new Error(`lo_from_bytea returned invalid oid: ${oidStr}`);
206
- }
207
- }
208
- catch (err) {
209
- writeErr(`\\lo_import: ${errMsg(err)}\n`);
210
- ctx.settings.lastErrorResult = { message: errMsg(err) };
211
- return { status: "error" };
212
- }
213
- if (comment !== null) {
214
- try {
215
- await c.execSimple(`COMMENT ON LARGE OBJECT ${oidStr} IS ${c.escapeLiteral(comment)}`);
216
- }
217
- catch (err) {
218
- writeErr(`\\lo_import: ${errMsg(err)}\n`);
219
- ctx.settings.lastErrorResult = { message: errMsg(err) };
220
- return { status: "error" };
221
- }
222
- }
223
- // Side effect: set LASTOID (matches upstream `do_lo_import`).
224
- ctx.settings.vars.set("LASTOID", oidStr);
225
- writeOut(`lo_import ${oidStr}\n`);
226
- return { status: "ok" };
227
- },
136
+ * `\lo_import FILE [COMMENT]`.
137
+ *
138
+ * Strategy:
139
+ * 1. `fs.readFile(file)` → Buffer.
140
+ * 2. `SELECT pg_catalog.lo_from_bytea(0, $1::bytea)` → new OID. The
141
+ * bytea param is the file's bytes serialized as `\x<hex>` text.
142
+ * 3. If COMMENT was supplied: `COMMENT ON LARGE OBJECT <oid> IS
143
+ * '<escaped>'` (single execSimple round-trip, escaped via the
144
+ * connection's `escapeLiteral`).
145
+ * 4. Print `lo_import <oid>\n` and set the `LASTOID` variable.
146
+ *
147
+ * Errors fall through to the standard `\lo_import: <msg>` diagnostic.
148
+ */
149
+ const cmdLoImport = {
150
+ name: "lo_import",
151
+ helpKey: "lo_import",
152
+ run: async (ctx) => {
153
+ const c = conn(ctx);
154
+ if (!c) return noConn(ctx);
155
+ const file = ctx.nextArg("normal");
156
+ if (file === null || file.length === 0) {
157
+ writeErr("\\lo_import: missing required argument\n");
158
+ ctx.settings.lastErrorResult = { message: "missing required argument" };
159
+ return { status: "error" };
160
+ }
161
+ const commentRaw = ctx.nextArg("normal");
162
+ const comment = commentRaw !== null && commentRaw.length > 0 ? commentRaw : null;
163
+ let bytes;
164
+ try {
165
+ bytes = await promises.readFile(file);
166
+ } catch (err) {
167
+ writeErr(`\\lo_import: ${errMsg(err)}\n`);
168
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
169
+ return { status: "error" };
170
+ }
171
+ let oidStr;
172
+ try {
173
+ const rs = await c.query("SELECT pg_catalog.lo_from_bytea(0, $1::bytea)", [byteaText(bytes)]);
174
+ if (rs.rows.length === 0) throw new Error("lo_from_bytea returned no rows");
175
+ oidStr = cellToString(rs.rows[0][0]);
176
+ if (!/^\d+$/.test(oidStr)) throw new Error(`lo_from_bytea returned invalid oid: ${oidStr}`);
177
+ } catch (err) {
178
+ writeErr(`\\lo_import: ${errMsg(err)}\n`);
179
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
180
+ return { status: "error" };
181
+ }
182
+ if (comment !== null) try {
183
+ await c.execSimple(`COMMENT ON LARGE OBJECT ${oidStr} IS ${c.escapeLiteral(comment)}`);
184
+ } catch (err) {
185
+ writeErr(`\\lo_import: ${errMsg(err)}\n`);
186
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
187
+ return { status: "error" };
188
+ }
189
+ ctx.settings.vars.set("LASTOID", oidStr);
190
+ writeOut(`lo_import ${oidStr}\n`);
191
+ return { status: "ok" };
192
+ }
228
193
  };
229
- // ---------------------------------------------------------------------------
230
- // \lo_export OID FILE
231
- // ---------------------------------------------------------------------------
232
194
  /**
233
- * `\lo_export OID FILE`.
234
- *
235
- * `SELECT pg_catalog.lo_get($1::oid)` returns a single-row, single-col
236
- * result whose cell is the LO's bytes. We then `fs.writeFile` to the
237
- * supplied path. The protocol layer decodes bytea text into a `Buffer`
238
- * for us when the column oid is bytea; if we get a `\x...` string back
239
- * we decode it explicitly.
240
- *
241
- * Print `lo_export\n` on success — matches upstream `do_lo_export`.
242
- */
243
- export const cmdLoExport = {
244
- name: "lo_export",
245
- helpKey: "lo_export",
246
- run: async (ctx) => {
247
- const c = conn(ctx);
248
- if (!c)
249
- return noConn(ctx);
250
- const oidArg = ctx.nextArg("normal");
251
- const file = ctx.nextArg("normal");
252
- if (oidArg === null || file === null || file.length === 0) {
253
- writeErr("\\lo_export: missing required argument\n");
254
- ctx.settings.lastErrorResult = {
255
- message: "missing required argument",
256
- };
257
- return { status: "error" };
258
- }
259
- const oid = parseOid(oidArg);
260
- if (oid === null) {
261
- writeErr(`\\lo_export: "${oidArg}" is not a valid large object OID\n`);
262
- ctx.settings.lastErrorResult = { message: "invalid OID" };
263
- return { status: "error" };
264
- }
265
- let bytes;
266
- try {
267
- const rs = await c.query("SELECT pg_catalog.lo_get($1::oid)", [
268
- oid,
269
- ]);
270
- if (rs.rows.length === 0) {
271
- throw new Error("lo_get returned no rows");
272
- }
273
- const cell = rs.rows[0][0];
274
- bytes = coerceBytea(cell);
275
- }
276
- catch (err) {
277
- writeErr(`\\lo_export: ${errMsg(err)}\n`);
278
- ctx.settings.lastErrorResult = { message: errMsg(err) };
279
- return { status: "error" };
280
- }
281
- try {
282
- await fsPromises.writeFile(file, bytes);
283
- }
284
- catch (err) {
285
- writeErr(`\\lo_export: ${errMsg(err)}\n`);
286
- ctx.settings.lastErrorResult = { message: errMsg(err) };
287
- return { status: "error" };
288
- }
289
- writeOut("lo_export\n");
290
- return { status: "ok" };
291
- },
195
+ * `\lo_export OID FILE`.
196
+ *
197
+ * `SELECT pg_catalog.lo_get($1::oid)` returns a single-row, single-col
198
+ * result whose cell is the LO's bytes. We then `fs.writeFile` to the
199
+ * supplied path. The protocol layer decodes bytea text into a `Buffer`
200
+ * for us when the column oid is bytea; if we get a `\x...` string back
201
+ * we decode it explicitly.
202
+ *
203
+ * Print `lo_export\n` on success — matches upstream `do_lo_export`.
204
+ */
205
+ const cmdLoExport = {
206
+ name: "lo_export",
207
+ helpKey: "lo_export",
208
+ run: async (ctx) => {
209
+ const c = conn(ctx);
210
+ if (!c) return noConn(ctx);
211
+ const oidArg = ctx.nextArg("normal");
212
+ const file = ctx.nextArg("normal");
213
+ if (oidArg === null || file === null || file.length === 0) {
214
+ writeErr("\\lo_export: missing required argument\n");
215
+ ctx.settings.lastErrorResult = { message: "missing required argument" };
216
+ return { status: "error" };
217
+ }
218
+ const oid = parseOid(oidArg);
219
+ if (oid === null) {
220
+ writeErr(`\\lo_export: "${oidArg}" is not a valid large object OID\n`);
221
+ ctx.settings.lastErrorResult = { message: "invalid OID" };
222
+ return { status: "error" };
223
+ }
224
+ let bytes;
225
+ try {
226
+ const rs = await c.query("SELECT pg_catalog.lo_get($1::oid)", [oid]);
227
+ if (rs.rows.length === 0) throw new Error("lo_get returned no rows");
228
+ const cell = rs.rows[0][0];
229
+ bytes = coerceBytea(cell);
230
+ } catch (err) {
231
+ writeErr(`\\lo_export: ${errMsg(err)}\n`);
232
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
233
+ return { status: "error" };
234
+ }
235
+ try {
236
+ await promises.writeFile(file, bytes);
237
+ } catch (err) {
238
+ writeErr(`\\lo_export: ${errMsg(err)}\n`);
239
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
240
+ return { status: "error" };
241
+ }
242
+ writeOut("lo_export\n");
243
+ return { status: "ok" };
244
+ }
292
245
  };
293
246
  /**
294
- * Decode a bytea cell coming back from the protocol. The connection may
295
- * deliver:
296
- * - a `Buffer` (decoded by a future binary-format path)
297
- * - a `\x<hex>` string (text format, modern)
298
- * - a legacy `octal-escape` string (text format, pre-9.0 servers; we
299
- * don't generate this but it's the historical default).
300
- */
247
+ * Decode a bytea cell coming back from the protocol. The connection may
248
+ * deliver:
249
+ * - a `Buffer` (decoded by a future binary-format path)
250
+ * - a `\x<hex>` string (text format, modern)
251
+ * - a legacy `octal-escape` string (text format, pre-9.0 servers; we
252
+ * don't generate this but it's the historical default).
253
+ */
301
254
  const coerceBytea = (cell) => {
302
- if (Buffer.isBuffer(cell))
303
- return cell;
304
- if (typeof cell !== "string") {
305
- throw new Error(`lo_get returned unexpected cell type: ${typeof cell}`);
306
- }
307
- if (cell.startsWith("\\x")) {
308
- return Buffer.from(cell.slice(2), "hex");
309
- }
310
- // Legacy octal-escape decode (`\\\\NNN` → byte, `\\\\` → `\\`, others
311
- // pass through). Upstream `PQunescapeBytea` does the same.
312
- const out = [];
313
- let i = 0;
314
- while (i < cell.length) {
315
- if (cell[i] === "\\") {
316
- if (cell[i + 1] === "\\") {
317
- out.push(0x5c);
318
- i += 2;
319
- continue;
320
- }
321
- if (/^[0-7][0-7][0-7]$/.test(cell.slice(i + 1, i + 4))) {
322
- out.push(parseInt(cell.slice(i + 1, i + 4), 8));
323
- i += 4;
324
- continue;
325
- }
326
- }
327
- out.push(cell.charCodeAt(i));
328
- i++;
329
- }
330
- return Buffer.from(out);
255
+ if (Buffer.isBuffer(cell)) return cell;
256
+ if (typeof cell !== "string") throw new Error(`lo_get returned unexpected cell type: ${typeof cell}`);
257
+ if (cell.startsWith("\\x")) return Buffer.from(cell.slice(2), "hex");
258
+ const out = [];
259
+ let i = 0;
260
+ while (i < cell.length) {
261
+ if (cell[i] === "\\") {
262
+ if (cell[i + 1] === "\\") {
263
+ out.push(92);
264
+ i += 2;
265
+ continue;
266
+ }
267
+ if (/^[0-7][0-7][0-7]$/.test(cell.slice(i + 1, i + 4))) {
268
+ out.push(parseInt(cell.slice(i + 1, i + 4), 8));
269
+ i += 4;
270
+ continue;
271
+ }
272
+ }
273
+ out.push(cell.charCodeAt(i));
274
+ i++;
275
+ }
276
+ return Buffer.from(out);
331
277
  };
332
- // ---------------------------------------------------------------------------
333
- // \lo_unlink OID
334
- // ---------------------------------------------------------------------------
335
278
  /**
336
- * `\lo_unlink OID` — drop a large object by OID. Implemented via
337
- * `SELECT pg_catalog.lo_unlink($1::oid)`. The function returns `1` on
338
- * success or raises an ERROR on missing OID; we just surface either
339
- * outcome.
340
- *
341
- * Print `lo_unlink <oid>\n` on success.
342
- */
343
- export const cmdLoUnlink = {
344
- name: "lo_unlink",
345
- helpKey: "lo_unlink",
346
- run: async (ctx) => {
347
- const c = conn(ctx);
348
- if (!c)
349
- return noConn(ctx);
350
- const oidArg = ctx.nextArg("normal");
351
- if (oidArg === null) {
352
- writeErr("\\lo_unlink: missing required argument\n");
353
- ctx.settings.lastErrorResult = {
354
- message: "missing required argument",
355
- };
356
- return { status: "error" };
357
- }
358
- const oid = parseOid(oidArg);
359
- if (oid === null) {
360
- writeErr(`\\lo_unlink: "${oidArg}" is not a valid large object OID\n`);
361
- ctx.settings.lastErrorResult = { message: "invalid OID" };
362
- return { status: "error" };
363
- }
364
- try {
365
- await c.query("SELECT pg_catalog.lo_unlink($1::oid)", [oid]);
366
- }
367
- catch (err) {
368
- writeErr(`\\lo_unlink: ${errMsg(err)}\n`);
369
- ctx.settings.lastErrorResult = { message: errMsg(err) };
370
- return { status: "error" };
371
- }
372
- writeOut(`lo_unlink ${String(oid)}\n`);
373
- return { status: "ok" };
374
- },
279
+ * `\lo_unlink OID` — drop a large object by OID. Implemented via
280
+ * `SELECT pg_catalog.lo_unlink($1::oid)`. The function returns `1` on
281
+ * success or raises an ERROR on missing OID; we just surface either
282
+ * outcome.
283
+ *
284
+ * Print `lo_unlink <oid>\n` on success.
285
+ */
286
+ const cmdLoUnlink = {
287
+ name: "lo_unlink",
288
+ helpKey: "lo_unlink",
289
+ run: async (ctx) => {
290
+ const c = conn(ctx);
291
+ if (!c) return noConn(ctx);
292
+ const oidArg = ctx.nextArg("normal");
293
+ if (oidArg === null) {
294
+ writeErr("\\lo_unlink: missing required argument\n");
295
+ ctx.settings.lastErrorResult = { message: "missing required argument" };
296
+ return { status: "error" };
297
+ }
298
+ const oid = parseOid(oidArg);
299
+ if (oid === null) {
300
+ writeErr(`\\lo_unlink: "${oidArg}" is not a valid large object OID\n`);
301
+ ctx.settings.lastErrorResult = { message: "invalid OID" };
302
+ return { status: "error" };
303
+ }
304
+ try {
305
+ await c.query("SELECT pg_catalog.lo_unlink($1::oid)", [oid]);
306
+ } catch (err) {
307
+ writeErr(`\\lo_unlink: ${errMsg(err)}\n`);
308
+ ctx.settings.lastErrorResult = { message: errMsg(err) };
309
+ return { status: "error" };
310
+ }
311
+ writeOut(`lo_unlink ${String(oid)}\n`);
312
+ return { status: "ok" };
313
+ }
375
314
  };
376
- // ---------------------------------------------------------------------------
377
- // Registration
378
- // ---------------------------------------------------------------------------
379
315
  /**
380
- * Register the four large-object commands on the supplied registry.
381
- * Called from `dispatch.ts::defaultRegistry()` (one new line).
382
- *
383
- * Note: `\lo_list` and `\lo_list+` shadow the existing `dl::lo_list`
384
- * alias from `cmd_describe.ts` — the registry's lookup checks primary
385
- * names before alias mappings, so this registration is the winning one.
386
- */
387
- export const registerLargeObjectCommands = (registry) => {
388
- registry.register(cmdLoList);
389
- registry.register(cmdLoListPlus);
390
- registry.register(cmdLoImport);
391
- registry.register(cmdLoExport);
392
- registry.register(cmdLoUnlink);
316
+ * Register the four large-object commands on the supplied registry.
317
+ * Called from `dispatch.ts::defaultRegistry()` (one new line).
318
+ *
319
+ * Note: `\lo_list` and `\lo_list+` shadow the existing `dl::lo_list`
320
+ * alias from `cmd_describe.ts` — the registry's lookup checks primary
321
+ * names before alias mappings, so this registration is the winning one.
322
+ */
323
+ const registerLargeObjectCommands = (registry) => {
324
+ registry.register(cmdLoList);
325
+ registry.register(cmdLoListPlus);
326
+ registry.register(cmdLoImport);
327
+ registry.register(cmdLoExport);
328
+ registry.register(cmdLoUnlink);
393
329
  };
330
+ //#endregion
331
+ export { cmdLoExport, cmdLoImport, cmdLoList, cmdLoListPlus, cmdLoUnlink, registerLargeObjectCommands };