neon 3.0.0 → 3.1.1

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