neon 2.47.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 +395 -0
- package/dist/_shared/env-core/reuse-secrets.js +179 -0
- 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 +575 -658
- 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 +642 -681
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -341
- 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 -64
- 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 +133 -147
- 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 +9 -7
|
@@ -1,521 +1,308 @@
|
|
|
1
|
-
|
|
2
|
-
* PipelineSession — high-level wrapper around the extended-protocol pipeline
|
|
3
|
-
* driver in {@link PgConnection} (WP-21).
|
|
4
|
-
*
|
|
5
|
-
* In pipeline mode (libpq's `PQpipelineSync`), the client streams multiple
|
|
6
|
-
* extended-protocol commands (Parse/Bind/Describe/Execute/Close) without
|
|
7
|
-
* waiting for results. The server processes each in order and queues replies.
|
|
8
|
-
* `sync()` inserts a Sync into the stream — that's the error boundary: any
|
|
9
|
-
* failure before the next Sync causes the server to skip subsequent commands
|
|
10
|
-
* until that Sync. `end()` finalizes the session.
|
|
11
|
-
*
|
|
12
|
-
* Design:
|
|
13
|
-
*
|
|
14
|
-
* - Each method writes the wire frame *and* enqueues a matching op record
|
|
15
|
-
* on `PgConnection.extDriver.queue`. The connection's dispatch loop
|
|
16
|
-
* resolves ops in order as replies arrive.
|
|
17
|
-
* - `execute()` records its own ResultSet promise; callers can `await` each
|
|
18
|
-
* individually or use the implicit ordering via the resolution sequence.
|
|
19
|
-
* - `flush()` sends `Flush`, which forces the server to send any queued
|
|
20
|
-
* replies without committing a Sync barrier. `sync()` sends `Sync` (and
|
|
21
|
-
* resolves on the corresponding ReadyForQuery).
|
|
22
|
-
* - `end()` sends a final Sync, waits for ReadyForQuery, and returns the
|
|
23
|
-
* concatenated ResultSets in execute-order. After end() the connection
|
|
24
|
-
* drops back to `idle`.
|
|
25
|
-
*/
|
|
1
|
+
import { Bind, Close, Describe, Execute, Flush, Parse, Sync } from "./protocol.js";
|
|
26
2
|
import { Buffer } from "node:buffer";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// Don't propagate a non-FATAL ErrorResponse here: upstream
|
|
320
|
-
// `\syncpipeline` is silent on stderr — the server-side error
|
|
321
|
-
// surfaces at `\endpipeline` time (see expected/psql_pipeline.out
|
|
322
|
-
// line 433 where the ParseError is printed AFTER the trailing
|
|
323
|
-
// `\endpipeline` echo, not inline with `\syncpipeline`). Stash
|
|
324
|
-
// it on `_lastError` instead so the cmd layer can render it
|
|
325
|
-
// when the pipeline drains.
|
|
326
|
-
await p.catch((err) => {
|
|
327
|
-
if (this._lastError === null)
|
|
328
|
-
this._lastError = err;
|
|
329
|
-
});
|
|
330
|
-
// Settle any per-op promises that arrived before this Sync — Parse,
|
|
331
|
-
// Bind, Describe, Close, and Execute. We don't surface their results
|
|
332
|
-
// here (callers consume `results` via end()), but we do need to drain
|
|
333
|
-
// any rejections so Node doesn't see them as unhandled. The first
|
|
334
|
-
// rejection becomes the sticky `_lastError` if Sync itself was clean.
|
|
335
|
-
const settled = await Promise.allSettled(this.pending.splice(0));
|
|
336
|
-
for (const r of settled) {
|
|
337
|
-
if (r.status === "rejected" && this._lastError === null) {
|
|
338
|
-
this._lastError = r.reason;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
get lastError() {
|
|
343
|
-
return this._lastError;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Drop any sticky error stashed via `_lastError = err` AND latch a
|
|
347
|
-
* "do not re-stash" sentinel so the next `end()` won't overwrite the
|
|
348
|
-
* cleared state from its final-Sync `fatal` capture or its per-op
|
|
349
|
-
* rejection scan. Used by `\getresults` when it surfaced a rejection
|
|
350
|
-
* inline so the follow-on `\endpipeline` doesn't re-emit the same
|
|
351
|
-
* diagnostic.
|
|
352
|
-
*
|
|
353
|
-
* The sentinel only applies to the IMMEDIATELY-FOLLOWING `end()`
|
|
354
|
-
* call: if a fresh error arrives on the wire AFTER clearLastError
|
|
355
|
-
* (e.g. a `\sendpipeline` queued after the `\getresults` that
|
|
356
|
-
* itself triggers a server-side ERROR), `_lastError` is re-armed
|
|
357
|
-
* normally because the per-op rejection scan only skips entries
|
|
358
|
-
* the cmd layer has already inspected (see `_externalDrained`).
|
|
359
|
-
*/
|
|
360
|
-
clearLastError() {
|
|
361
|
-
this._lastError = null;
|
|
362
|
-
this._errorConsumedExternally = true;
|
|
363
|
-
}
|
|
364
|
-
markDrained(count) {
|
|
365
|
-
if (count > this._externalDrained)
|
|
366
|
-
this._externalDrained = count;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Peek at the FIRST real (non-`pipelineAborted`) rejection anywhere
|
|
370
|
-
* in `pending` ∪ `results`, INSPECTING ONLY promises that are already
|
|
371
|
-
* settled. Used by `\getresults` / `\endpipeline` to find the
|
|
372
|
-
* originating diagnostic when the wire layer's cascade-reject landed
|
|
373
|
-
* the real `ConnectError` on a `pending` Parse/Bind/Close (not visible
|
|
374
|
-
* to `results`-only walks) and stamped `pipelineAborted` onto the
|
|
375
|
-
* visible Execute slots.
|
|
376
|
-
*
|
|
377
|
-
* Each candidate is raced against a microtask-resolved sentinel so a
|
|
378
|
-
* still-pending promise (e.g. the next Execute waiting for its
|
|
379
|
-
* Sync round-trip) doesn't block the peek. Returns `null` when
|
|
380
|
-
* nothing has rejected yet — or when the only rejections so far are
|
|
381
|
-
* the synthetic `pipelineAborted` marker.
|
|
382
|
-
*/
|
|
383
|
-
async peekRealError() {
|
|
384
|
-
const allP = [...this.pending, ...this.results];
|
|
385
|
-
const sentinel = Symbol("pending");
|
|
386
|
-
// Race each candidate against a `setImmediate`-deferred sentinel so
|
|
387
|
-
// any promise that was already settled (synchronously, e.g. by the
|
|
388
|
-
// wire layer's cascade-reject inside its ErrorResponse handler) has
|
|
389
|
-
// a full microtask drain to fulfil its `.then(...)` chain before
|
|
390
|
-
// the sentinel resolves. A bare `Promise.resolve(sentinel)` would
|
|
391
|
-
// race in the same microtask tier and the sentinel sometimes wins
|
|
392
|
-
// even when the candidate has a settled value — that's the
|
|
393
|
-
// ordering quirk this `setImmediate` step pins down.
|
|
394
|
-
const deferred = () => new Promise((resolve) => setImmediate(() => {
|
|
395
|
-
resolve(sentinel);
|
|
396
|
-
}));
|
|
397
|
-
for (const p of allP) {
|
|
398
|
-
const r = (await Promise.race([
|
|
399
|
-
p.then((v) => ({
|
|
400
|
-
status: "fulfilled",
|
|
401
|
-
value: v,
|
|
402
|
-
}), (e) => ({
|
|
403
|
-
status: "rejected",
|
|
404
|
-
reason: e,
|
|
405
|
-
})),
|
|
406
|
-
deferred(),
|
|
407
|
-
]));
|
|
408
|
-
if (r === sentinel)
|
|
409
|
-
continue;
|
|
410
|
-
if (r.status !== "rejected")
|
|
411
|
-
continue;
|
|
412
|
-
const reason = r.reason;
|
|
413
|
-
const isAborted = typeof reason === "object" &&
|
|
414
|
-
reason !== null &&
|
|
415
|
-
reason.pipelineAborted ===
|
|
416
|
-
true;
|
|
417
|
-
if (!isAborted)
|
|
418
|
-
return reason;
|
|
419
|
-
}
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
|
-
async end() {
|
|
423
|
-
// Send a terminating Sync so the connection settles back to idle.
|
|
424
|
-
this.conn.startExtendedBatch();
|
|
425
|
-
const finalSync = this.conn.enqueueSync();
|
|
426
|
-
this.conn.writeRaw(Sync());
|
|
427
|
-
// Wait for the final Sync's RfQ, but tolerate a sticky pipeline
|
|
428
|
-
// error: enqueueSync rejects when the pipeline ended in error
|
|
429
|
-
// state. `\endpipeline` still needs to harvest the queued
|
|
430
|
-
// ResultSets, so we capture rather than propagate immediately.
|
|
431
|
-
let fatal = null;
|
|
432
|
-
await finalSync.catch((err) => {
|
|
433
|
-
fatal = err;
|
|
434
|
-
});
|
|
435
|
-
this.conn._extPipelineActive = false;
|
|
436
|
-
// Drain pending ops too — Parse/Bind/Describe/Close that rejected
|
|
437
|
-
// (e.g. bind-param-count mismatch) would otherwise surface as
|
|
438
|
-
// process-level UnhandledPromiseRejection. We only need the
|
|
439
|
-
// settlement; the results aren't surfaced here.
|
|
440
|
-
await Promise.allSettled(this.pending.splice(0));
|
|
441
|
-
const settled = await Promise.allSettled(this.results);
|
|
442
|
-
const out = [];
|
|
443
|
-
for (const r of settled) {
|
|
444
|
-
if (r.status === "fulfilled")
|
|
445
|
-
out.push(r.value);
|
|
446
|
-
}
|
|
447
|
-
// FATAL-class pipeline aborts (e.g. "COPY in a pipeline is not
|
|
448
|
-
// supported, aborting connection") must surface to the caller so
|
|
449
|
-
// `\endpipeline` can render the diagnostic on stderr. Plain
|
|
450
|
-
// per-op errors stay swallowed so a partial pipeline still
|
|
451
|
-
// returns its successful results — but we stash them on
|
|
452
|
-
// `_lastError` so the cmd layer can print them inline.
|
|
453
|
-
if (fatal !== null) {
|
|
454
|
-
const sev = fatal.severity;
|
|
455
|
-
if (sev === "FATAL") {
|
|
456
|
-
if (fatal instanceof Error)
|
|
457
|
-
throw fatal;
|
|
458
|
-
const msg = fatal.message ??
|
|
459
|
-
"pipeline aborted";
|
|
460
|
-
throw Object.assign(new Error(msg), fatal);
|
|
461
|
-
}
|
|
462
|
-
// Non-FATAL (typically ERROR) — keep for the cmd layer, unless
|
|
463
|
-
// `\getresults` already consumed the diagnostic (see
|
|
464
|
-
// `clearLastError()` / `_errorConsumedExternally`). In that case
|
|
465
|
-
// the same rejection is mirrored on the final Sync — re-stashing
|
|
466
|
-
// it here would cause `\endpipeline` to double-print.
|
|
467
|
-
if (!this._errorConsumedExternally) {
|
|
468
|
-
this._lastError = fatal;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
else if (this._lastError === null) {
|
|
472
|
-
// Also scan the settled per-op promises: a ParseError /
|
|
473
|
-
// BindError that arrived BEFORE the final Sync rejects its own
|
|
474
|
-
// op promise but the Sync may still resolve cleanly (the
|
|
475
|
-
// server marks subsequent ops as "Pipeline aborted, command
|
|
476
|
-
// did not run" instead of erroring). The conformance corpus
|
|
477
|
-
// expects the first ErrorResponse to surface at `\endpipeline`
|
|
478
|
-
// time, so we hunt for the first rejected per-op promise — but
|
|
479
|
-
// skip entries that `\getresults` already inspected and surfaced
|
|
480
|
-
// inline (tracked via `markDrained`). Without this, the same
|
|
481
|
-
// rejection would re-stash on `_lastError` and `\endpipeline`
|
|
482
|
-
// would double-print the diagnostic.
|
|
483
|
-
//
|
|
484
|
-
// Don't OVERWRITE an existing `_lastError`: a prior `sync()`
|
|
485
|
-
// (via `\syncpipeline`) may have already stashed the real
|
|
486
|
-
// ConnectError for an earlier batch, and the second batch's
|
|
487
|
-
// results carry only `pipelineAborted` markers which would
|
|
488
|
-
// shadow the real diagnostic if blindly assigned here. The
|
|
489
|
-
// outer `if (this._lastError === null)` guard makes this a
|
|
490
|
-
// first-wins capture.
|
|
491
|
-
for (let i = this._externalDrained; i < settled.length; i++) {
|
|
492
|
-
const r = settled[i];
|
|
493
|
-
if (r.status === "rejected") {
|
|
494
|
-
this._lastError = r.reason;
|
|
495
|
-
break;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return out;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
3
|
+
//#region src/psql/wire/pipeline.ts
|
|
4
|
+
/**
|
|
5
|
+
* PipelineSession — high-level wrapper around the extended-protocol pipeline
|
|
6
|
+
* driver in {@link PgConnection} (WP-21).
|
|
7
|
+
*
|
|
8
|
+
* In pipeline mode (libpq's `PQpipelineSync`), the client streams multiple
|
|
9
|
+
* extended-protocol commands (Parse/Bind/Describe/Execute/Close) without
|
|
10
|
+
* waiting for results. The server processes each in order and queues replies.
|
|
11
|
+
* `sync()` inserts a Sync into the stream — that's the error boundary: any
|
|
12
|
+
* failure before the next Sync causes the server to skip subsequent commands
|
|
13
|
+
* until that Sync. `end()` finalizes the session.
|
|
14
|
+
*
|
|
15
|
+
* Design:
|
|
16
|
+
*
|
|
17
|
+
* - Each method writes the wire frame *and* enqueues a matching op record
|
|
18
|
+
* on `PgConnection.extDriver.queue`. The connection's dispatch loop
|
|
19
|
+
* resolves ops in order as replies arrive.
|
|
20
|
+
* - `execute()` records its own ResultSet promise; callers can `await` each
|
|
21
|
+
* individually or use the implicit ordering via the resolution sequence.
|
|
22
|
+
* - `flush()` sends `Flush`, which forces the server to send any queued
|
|
23
|
+
* replies without committing a Sync barrier. `sync()` sends `Sync` (and
|
|
24
|
+
* resolves on the corresponding ReadyForQuery).
|
|
25
|
+
* - `end()` sends a final Sync, waits for ReadyForQuery, and returns the
|
|
26
|
+
* concatenated ResultSets in execute-order. After end() the connection
|
|
27
|
+
* drops back to `idle`.
|
|
28
|
+
*/
|
|
29
|
+
var PipelineSession = class PipelineSession {
|
|
30
|
+
constructor(conn) {
|
|
31
|
+
this.conn = conn;
|
|
32
|
+
this.results = [];
|
|
33
|
+
this.cmdSlots = [];
|
|
34
|
+
this.pending = [];
|
|
35
|
+
this.currentGroup = [];
|
|
36
|
+
this._lastError = null;
|
|
37
|
+
this._errorConsumedExternally = false;
|
|
38
|
+
this._externalDrained = 0;
|
|
39
|
+
this.conn._extPipelineActive = true;
|
|
40
|
+
this.conn.startExtendedBatch();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Attach a no-op rejection handler so an unhandled rejection on `p`
|
|
44
|
+
* doesn't bubble up. The original promise is also retained in
|
|
45
|
+
* `this.results` / `this.pending` so callers (end(), getresults) can
|
|
46
|
+
* still observe the outcome — we only swallow the unhandled-rejection
|
|
47
|
+
* warning Node would otherwise emit at process scope.
|
|
48
|
+
*/
|
|
49
|
+
static silenceUnhandled(p) {
|
|
50
|
+
p.catch(() => void 0);
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Push a synthetic ResultSet slot onto `cmdSlots`. The slot's
|
|
55
|
+
* effective rejection is taken from the FIRST wire-op rejection in
|
|
56
|
+
* the current user-command group ({@link currentGroup}) followed by
|
|
57
|
+
* the final ResultSet promise — mirroring libpq's per-PGresult
|
|
58
|
+
* shape, where the real `ConnectError` lands on the failing wire op
|
|
59
|
+
* (often a Bind / Parse on `pending`, NOT the trailing Execute on
|
|
60
|
+
* `results`). Without this combining, the slot would inherit the
|
|
61
|
+
* cascaded `pipelineAborted` marker from the Execute and the real
|
|
62
|
+
* diagnostic would never reach the cmd layer.
|
|
63
|
+
*
|
|
64
|
+
* The group is reset after consumption so the next user command
|
|
65
|
+
* starts fresh.
|
|
66
|
+
*/
|
|
67
|
+
pushCmdSlot(p) {
|
|
68
|
+
const group = this.currentGroup.splice(0);
|
|
69
|
+
if (group.length === 0) {
|
|
70
|
+
this.cmdSlots.push(PipelineSession.silenceUnhandled(p));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const combined = Promise.all([...group, p]).then((arr) => arr[arr.length - 1]);
|
|
74
|
+
this.cmdSlots.push(PipelineSession.silenceUnhandled(combined));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Map a wire-op `Promise<void>` to a `Promise<ResultSet>` that
|
|
78
|
+
* resolves with a silent zero-field placeholder when the op
|
|
79
|
+
* completes, or rejects with the same reason if it fails. Used by
|
|
80
|
+
* `parseSlot` / `closeSlot` so a successful Parse / Close walks the
|
|
81
|
+
* `\getresults` drain loop without emitting any output (matching
|
|
82
|
+
* upstream `PQgetResult` for PGRES_COMMAND_OK on Parse / Close).
|
|
83
|
+
*/
|
|
84
|
+
static voidToPlaceholderResult(p) {
|
|
85
|
+
return p.then(() => ({
|
|
86
|
+
command: "",
|
|
87
|
+
rowCount: null,
|
|
88
|
+
oid: null,
|
|
89
|
+
fields: [],
|
|
90
|
+
rows: [],
|
|
91
|
+
notices: []
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
parse(name, sql, paramTypes) {
|
|
95
|
+
this.parseRaw(name, sql, paramTypes);
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Enqueue a Parse op AND register it as a USER-level command slot
|
|
100
|
+
* (one entry on `cmdSlots`). Mirrors {@link parse} — RESOLVES
|
|
101
|
+
* IMMEDIATELY; the underlying wire-op promise's outcome surfaces
|
|
102
|
+
* via `cmdSlots` (drained by `\getresults` / `\endpipeline`).
|
|
103
|
+
* Returning a slot-bound promise would deadlock callers that await
|
|
104
|
+
* it before Sync.
|
|
105
|
+
*
|
|
106
|
+
* Used by `\parse NAME` (standalone Parse). Do NOT use from inside
|
|
107
|
+
* `\sendpipeline` — that path issues an internal `parse('')` for the
|
|
108
|
+
* SQL buffer; the Execute downstream is the user-visible command.
|
|
109
|
+
*/
|
|
110
|
+
async parseSlot(name, sql, paramTypes) {
|
|
111
|
+
const p = this.parseRaw(name, sql, paramTypes);
|
|
112
|
+
const slot = PipelineSession.voidToPlaceholderResult(p);
|
|
113
|
+
this.pushCmdSlot(slot);
|
|
114
|
+
await Promise.resolve();
|
|
115
|
+
}
|
|
116
|
+
parseRaw(name, sql, paramTypes) {
|
|
117
|
+
this.conn.startExtendedBatch();
|
|
118
|
+
const p = this.conn.enqueueParse();
|
|
119
|
+
this.conn.writeRaw(Parse(name, sql, paramTypes ?? []));
|
|
120
|
+
const silenced = PipelineSession.silenceUnhandled(p);
|
|
121
|
+
this.pending.push(silenced);
|
|
122
|
+
this.currentGroup.push(silenced);
|
|
123
|
+
return p;
|
|
124
|
+
}
|
|
125
|
+
bind(name, values) {
|
|
126
|
+
this.conn.startExtendedBatch();
|
|
127
|
+
const p = this.conn.enqueueBind();
|
|
128
|
+
const encoded = values.map(toBindValue);
|
|
129
|
+
this.conn.writeRaw(Bind("", name, [], encoded, [0]));
|
|
130
|
+
const silenced = PipelineSession.silenceUnhandled(p);
|
|
131
|
+
this.pending.push(silenced);
|
|
132
|
+
this.currentGroup.push(silenced);
|
|
133
|
+
return Promise.resolve();
|
|
134
|
+
}
|
|
135
|
+
execute(name, maxRows) {
|
|
136
|
+
this.conn.startExtendedBatch();
|
|
137
|
+
const dp = this.conn.enqueueDescribePortalIntoNextExecute();
|
|
138
|
+
const silencedDp = PipelineSession.silenceUnhandled(dp);
|
|
139
|
+
this.pending.push(silencedDp);
|
|
140
|
+
this.currentGroup.push(silencedDp);
|
|
141
|
+
this.conn.writeRaw(Describe("P", name));
|
|
142
|
+
const ep = this.conn.enqueueExecute();
|
|
143
|
+
this.results.push(PipelineSession.silenceUnhandled(ep));
|
|
144
|
+
this.pushCmdSlot(ep);
|
|
145
|
+
this.conn.writeRaw(Execute(name, maxRows ?? 0));
|
|
146
|
+
return Promise.resolve();
|
|
147
|
+
}
|
|
148
|
+
describe(name) {
|
|
149
|
+
this.conn.startExtendedBatch();
|
|
150
|
+
const p = this.conn.enqueueDescribePortal();
|
|
151
|
+
this.conn.writeRaw(Describe("P", name));
|
|
152
|
+
this.pending.push(PipelineSession.silenceUnhandled(p));
|
|
153
|
+
return Promise.resolve();
|
|
154
|
+
}
|
|
155
|
+
close(name) {
|
|
156
|
+
this.closeRaw(name);
|
|
157
|
+
return Promise.resolve();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Enqueue a Close op AND register it as a user-level command slot.
|
|
161
|
+
* Resolves immediately (see {@link parseSlot} for rationale); the
|
|
162
|
+
* slot's outcome surfaces via `cmdSlots`.
|
|
163
|
+
*/
|
|
164
|
+
async closeSlot(name) {
|
|
165
|
+
const p = this.closeRaw(name);
|
|
166
|
+
const slot = PipelineSession.voidToPlaceholderResult(p);
|
|
167
|
+
this.pushCmdSlot(slot);
|
|
168
|
+
await Promise.resolve();
|
|
169
|
+
}
|
|
170
|
+
closeRaw(name) {
|
|
171
|
+
this.conn.startExtendedBatch();
|
|
172
|
+
const p = this.conn.enqueueClose();
|
|
173
|
+
this.conn.writeRaw(Close("S", name));
|
|
174
|
+
const silenced = PipelineSession.silenceUnhandled(p);
|
|
175
|
+
this.pending.push(silenced);
|
|
176
|
+
this.currentGroup.push(silenced);
|
|
177
|
+
return p;
|
|
178
|
+
}
|
|
179
|
+
async flush() {
|
|
180
|
+
this.conn.writeRaw(Flush());
|
|
181
|
+
await Promise.resolve();
|
|
182
|
+
}
|
|
183
|
+
async sync() {
|
|
184
|
+
this.conn.startExtendedBatch();
|
|
185
|
+
const p = this.conn.enqueueSync();
|
|
186
|
+
this.conn.writeRaw(Sync());
|
|
187
|
+
const syncMarker = {
|
|
188
|
+
command: "",
|
|
189
|
+
rowCount: null,
|
|
190
|
+
oid: null,
|
|
191
|
+
fields: [],
|
|
192
|
+
rows: [],
|
|
193
|
+
notices: []
|
|
194
|
+
};
|
|
195
|
+
this.results.push(Promise.resolve(syncMarker));
|
|
196
|
+
this.currentGroup.splice(0);
|
|
197
|
+
this.cmdSlots.push(Promise.resolve(syncMarker));
|
|
198
|
+
await p.catch((err) => {
|
|
199
|
+
if (this._lastError === null) this._lastError = err;
|
|
200
|
+
});
|
|
201
|
+
const settled = await Promise.allSettled(this.pending.splice(0));
|
|
202
|
+
for (const r of settled) if (r.status === "rejected" && this._lastError === null) this._lastError = r.reason;
|
|
203
|
+
}
|
|
204
|
+
get lastError() {
|
|
205
|
+
return this._lastError;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Drop any sticky error stashed via `_lastError = err` AND latch a
|
|
209
|
+
* "do not re-stash" sentinel so the next `end()` won't overwrite the
|
|
210
|
+
* cleared state from its final-Sync `fatal` capture or its per-op
|
|
211
|
+
* rejection scan. Used by `\getresults` when it surfaced a rejection
|
|
212
|
+
* inline so the follow-on `\endpipeline` doesn't re-emit the same
|
|
213
|
+
* diagnostic.
|
|
214
|
+
*
|
|
215
|
+
* The sentinel only applies to the IMMEDIATELY-FOLLOWING `end()`
|
|
216
|
+
* call: if a fresh error arrives on the wire AFTER clearLastError
|
|
217
|
+
* (e.g. a `\sendpipeline` queued after the `\getresults` that
|
|
218
|
+
* itself triggers a server-side ERROR), `_lastError` is re-armed
|
|
219
|
+
* normally because the per-op rejection scan only skips entries
|
|
220
|
+
* the cmd layer has already inspected (see `_externalDrained`).
|
|
221
|
+
*/
|
|
222
|
+
clearLastError() {
|
|
223
|
+
this._lastError = null;
|
|
224
|
+
this._errorConsumedExternally = true;
|
|
225
|
+
}
|
|
226
|
+
markDrained(count) {
|
|
227
|
+
if (count > this._externalDrained) this._externalDrained = count;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Peek at the FIRST real (non-`pipelineAborted`) rejection anywhere
|
|
231
|
+
* in `pending` ∪ `results`, INSPECTING ONLY promises that are already
|
|
232
|
+
* settled. Used by `\getresults` / `\endpipeline` to find the
|
|
233
|
+
* originating diagnostic when the wire layer's cascade-reject landed
|
|
234
|
+
* the real `ConnectError` on a `pending` Parse/Bind/Close (not visible
|
|
235
|
+
* to `results`-only walks) and stamped `pipelineAborted` onto the
|
|
236
|
+
* visible Execute slots.
|
|
237
|
+
*
|
|
238
|
+
* Each candidate is raced against a microtask-resolved sentinel so a
|
|
239
|
+
* still-pending promise (e.g. the next Execute waiting for its
|
|
240
|
+
* Sync round-trip) doesn't block the peek. Returns `null` when
|
|
241
|
+
* nothing has rejected yet — or when the only rejections so far are
|
|
242
|
+
* the synthetic `pipelineAborted` marker.
|
|
243
|
+
*/
|
|
244
|
+
async peekRealError() {
|
|
245
|
+
const allP = [...this.pending, ...this.results];
|
|
246
|
+
const sentinel = Symbol("pending");
|
|
247
|
+
const deferred = () => new Promise((resolve) => setImmediate(() => {
|
|
248
|
+
resolve(sentinel);
|
|
249
|
+
}));
|
|
250
|
+
for (const p of allP) {
|
|
251
|
+
const r = await Promise.race([p.then((v) => ({
|
|
252
|
+
status: "fulfilled",
|
|
253
|
+
value: v
|
|
254
|
+
}), (e) => ({
|
|
255
|
+
status: "rejected",
|
|
256
|
+
reason: e
|
|
257
|
+
})), deferred()]);
|
|
258
|
+
if (r === sentinel) continue;
|
|
259
|
+
if (r.status !== "rejected") continue;
|
|
260
|
+
const reason = r.reason;
|
|
261
|
+
if (!(typeof reason === "object" && reason !== null && reason.pipelineAborted === true)) return reason;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
async end() {
|
|
266
|
+
this.conn.startExtendedBatch();
|
|
267
|
+
const finalSync = this.conn.enqueueSync();
|
|
268
|
+
this.conn.writeRaw(Sync());
|
|
269
|
+
let fatal = null;
|
|
270
|
+
await finalSync.catch((err) => {
|
|
271
|
+
fatal = err;
|
|
272
|
+
});
|
|
273
|
+
this.conn._extPipelineActive = false;
|
|
274
|
+
await Promise.allSettled(this.pending.splice(0));
|
|
275
|
+
const settled = await Promise.allSettled(this.results);
|
|
276
|
+
const out = [];
|
|
277
|
+
for (const r of settled) if (r.status === "fulfilled") out.push(r.value);
|
|
278
|
+
if (fatal !== null) {
|
|
279
|
+
if (fatal.severity === "FATAL") {
|
|
280
|
+
if (fatal instanceof Error) throw fatal;
|
|
281
|
+
const msg = fatal.message ?? "pipeline aborted";
|
|
282
|
+
throw Object.assign(new Error(msg), fatal);
|
|
283
|
+
}
|
|
284
|
+
if (!this._errorConsumedExternally) this._lastError = fatal;
|
|
285
|
+
} else if (this._lastError === null) for (let i = this._externalDrained; i < settled.length; i++) {
|
|
286
|
+
const r = settled[i];
|
|
287
|
+
if (r.status === "rejected") {
|
|
288
|
+
this._lastError = r.reason;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
502
295
|
function toBindValue(v) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
// Objects, arrays, etc: JSON-stringify deterministically. Falls back to ''
|
|
514
|
-
// if the value is not representable.
|
|
515
|
-
try {
|
|
516
|
-
return JSON.stringify(v);
|
|
517
|
-
}
|
|
518
|
-
catch {
|
|
519
|
-
return "";
|
|
520
|
-
}
|
|
296
|
+
if (v === null || v === void 0) return null;
|
|
297
|
+
if (Buffer.isBuffer(v)) return v;
|
|
298
|
+
if (typeof v === "string") return v;
|
|
299
|
+
if (typeof v === "boolean") return v ? "t" : "f";
|
|
300
|
+
if (typeof v === "number" || typeof v === "bigint") return v.toString();
|
|
301
|
+
try {
|
|
302
|
+
return JSON.stringify(v);
|
|
303
|
+
} catch {
|
|
304
|
+
return "";
|
|
305
|
+
}
|
|
521
306
|
}
|
|
307
|
+
//#endregion
|
|
308
|
+
export { PipelineSession };
|