@effect/sql-pg 4.0.0-rc.111 → 4.0.0-rc.113

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 (60) hide show
  1. package/AGENTS.md +24 -9
  2. package/CLAUDE.md +24 -9
  3. package/ai-docs/package.json +2 -2
  4. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +18 -5
  5. package/ai-docs/src/01_effect/01_basics/index.md +5 -3
  6. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +1 -1
  7. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +2 -2
  8. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +2 -2
  9. package/ai-docs/src/03_stream/30_encoding.ts +5 -7
  10. package/ai-docs/src/08_observability/10_logging.ts +1 -1
  11. package/ai-docs/src/70_cli/10_basics.ts +7 -7
  12. package/ai-docs/src/71_ai/10_language-model.ts +2 -2
  13. package/ai-docs/src/71_ai/20_tools.ts +1 -1
  14. package/ai-docs/src/71_ai/30_chat.ts +1 -1
  15. package/dist/PgAuth.d.ts +93 -0
  16. package/dist/PgAuth.d.ts.map +1 -0
  17. package/dist/PgAuth.js +229 -0
  18. package/dist/PgAuth.js.map +1 -0
  19. package/dist/PgClient.d.ts +49 -82
  20. package/dist/PgClient.d.ts.map +1 -1
  21. package/dist/PgClient.js +58 -510
  22. package/dist/PgClient.js.map +1 -1
  23. package/dist/PgConnection.d.ts +177 -0
  24. package/dist/PgConnection.d.ts.map +1 -0
  25. package/dist/PgConnection.js +1786 -0
  26. package/dist/PgConnection.js.map +1 -0
  27. package/dist/PgPool.d.ts +107 -0
  28. package/dist/PgPool.d.ts.map +1 -0
  29. package/dist/PgPool.js +124 -0
  30. package/dist/PgPool.js.map +1 -0
  31. package/dist/PgProtocol.d.ts +783 -0
  32. package/dist/PgProtocol.d.ts.map +1 -0
  33. package/dist/PgProtocol.js +1202 -0
  34. package/dist/PgProtocol.js.map +1 -0
  35. package/dist/PgTypes.d.ts +429 -0
  36. package/dist/PgTypes.d.ts.map +1 -0
  37. package/dist/PgTypes.js +1511 -0
  38. package/dist/PgTypes.js.map +1 -0
  39. package/dist/index.d.ts +20 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +20 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal/connection.d.ts +2 -0
  44. package/dist/internal/connection.d.ts.map +1 -0
  45. package/dist/internal/connection.js +5 -0
  46. package/dist/internal/connection.js.map +1 -0
  47. package/dist/internal/sqlError.d.ts +10 -0
  48. package/dist/internal/sqlError.d.ts.map +1 -0
  49. package/dist/internal/sqlError.js +57 -0
  50. package/dist/internal/sqlError.js.map +1 -0
  51. package/package.json +8 -12
  52. package/src/PgAuth.ts +318 -0
  53. package/src/PgClient.ts +138 -681
  54. package/src/PgConnection.ts +2294 -0
  55. package/src/PgPool.ts +231 -0
  56. package/src/PgProtocol.ts +1863 -0
  57. package/src/PgTypes.ts +1998 -0
  58. package/src/index.ts +25 -0
  59. package/src/internal/connection.ts +26 -0
  60. package/src/internal/sqlError.ts +75 -0
@@ -0,0 +1,1863 @@
1
+ /**
2
+ * Wire codec for the PostgreSQL frontend/backend protocol, version 3.0.
3
+ *
4
+ * The module encodes frontend messages and decodes backend messages. Every
5
+ * function is pure: bytes in, bytes or plain data out. Nothing here opens a
6
+ * socket, negotiates TLS, or tracks session state, and nothing here decodes
7
+ * column values - `DataRow` fields stay raw bytes for `PgTypes` to interpret.
8
+ *
9
+ * Typed messages are a type byte, an `int32` length that counts itself but not
10
+ * the type byte, and a payload. Integers are big-endian and strings are
11
+ * NUL-terminated UTF-8 unless they are explicitly length-prefixed.
12
+ *
13
+ * Encoded frames and decoded byte fields are views into pooled buffers that
14
+ * are written once and never rewritten. They stay valid for as long as they
15
+ * are held, but holding one keeps its whole pool buffer alive, so copy
16
+ * anything that has to outlive the message it came from.
17
+ *
18
+ * @since 4.0.0
19
+ */
20
+ import * as Data from "effect/Data"
21
+ import * as Result from "effect/Result"
22
+
23
+ /**
24
+ * Default `maxMessageSize` for `makeParser`: 16 MiB.
25
+ *
26
+ * @category constants
27
+ * @since 4.0.0
28
+ */
29
+ export const defaultMaxMessageSize = 16 * 1024 * 1024
30
+
31
+ /** Where a parser stops growing its buffer pool. */
32
+ const maxBufferSize = 64 * 1024
33
+
34
+ /**
35
+ * An incremental decoder for the post-startup backend message stream.
36
+ *
37
+ * @category models
38
+ * @since 4.0.0
39
+ */
40
+ export interface Parser<A = Uint8Array | null> {
41
+ /**
42
+ * Reads each `DataRow` field, for a parser built with one. A result's
43
+ * columns are only known from its `RowDescription`, which arrives on the
44
+ * same stream, so this is settable: replace it when the columns change.
45
+ */
46
+ readField: FieldReader<A> | undefined
47
+
48
+ /**
49
+ * Decodes a chunk and returns every complete message. A partial message is
50
+ * retained for the next call. Parse and field-reader errors are terminal and
51
+ * discard messages decoded earlier in the same call.
52
+ *
53
+ * **Details**
54
+ *
55
+ * `DataRow`, `CopyData`, and `Unknown` payloads are views into an internal
56
+ * buffer. Copy a payload that must outlive the current row, otherwise its
57
+ * entire buffer remains in memory.
58
+ */
59
+ readonly push: (chunk: Uint8Array) => ReadonlyArray<BackendMessage<A>>
60
+
61
+ /**
62
+ * Decodes a chunk and passes each complete message to `onMessage`
63
+ * immediately. This lets a `RowDescription` update `readField` before a
64
+ * `DataRow` later in the same chunk is decoded. The failure and
65
+ * buffer-lifetime rules match `push`, except messages
66
+ * delivered before a failure are not discarded.
67
+ */
68
+ readonly pushEach: (chunk: Uint8Array, onMessage: (message: BackendMessage<A>) => void) => void
69
+ }
70
+
71
+ /**
72
+ * Creates a `Parser`.
73
+ *
74
+ * **Details**
75
+ *
76
+ * Special pre-startup replies have no type byte. Use `decodeSslResponse` for
77
+ * those replies.
78
+ *
79
+ * @category constructors
80
+ * @since 4.0.0
81
+ */
82
+ export const makeParser = <A = Uint8Array | null>(options?: {
83
+ readonly maxMessageSize?: number | undefined
84
+ /**
85
+ * Reads each `DataRow` field as it is parsed, so a client that decodes its
86
+ * columns never needs a view per column. Without one every field is handed
87
+ * out as a view, which is the default.
88
+ */
89
+ readonly readField?: FieldReader<A> | undefined
90
+ }): Parser<A> => {
91
+ const maxMessageSize = options?.maxMessageSize ?? defaultMaxMessageSize
92
+ const reader = new Reader()
93
+ let bufferSize = 8192
94
+ let buffer = new Uint8Array(bufferSize)
95
+ let store = buffer.buffer
96
+ let start = 0
97
+ let end = 0
98
+ let failed = false
99
+
100
+ // Bytes already handed to the caller are never overwritten, so a full buffer
101
+ // is replaced rather than compacted in place. That lets `DataRow` fields be
102
+ // views instead of copies, which is the difference between one allocation per
103
+ // buffer and one per column.
104
+ //
105
+ // Every refill therefore allocates, so the pool doubles up to
106
+ // `maxBufferSize`: a busy connection spreads the allocation over more
107
+ // messages while a low-volume one stays small. A single oversized message
108
+ // grows its buffer beyond the pool without raising the pool itself.
109
+ const append = (chunk: Uint8Array): void => {
110
+ if (end + chunk.length > buffer.length) {
111
+ const pending = end - start
112
+ if (bufferSize < maxBufferSize) bufferSize *= 2
113
+ let capacity = bufferSize
114
+ while (capacity < pending + chunk.length) capacity *= 2
115
+ const next = new Uint8Array(capacity)
116
+ next.set(buffer.subarray(start, end))
117
+ buffer = next
118
+ store = next.buffer
119
+ start = 0
120
+ end = pending
121
+ }
122
+ buffer.set(chunk, end)
123
+ end += chunk.length
124
+ }
125
+
126
+ const parser: Parser<A> = {
127
+ readField: options?.readField,
128
+ push(chunk) {
129
+ const messages: Array<BackendMessage<A>> = []
130
+ parser.pushEach(chunk, (message) => {
131
+ messages.push(message)
132
+ })
133
+ return messages
134
+ },
135
+ pushEach(chunk, onMessage) {
136
+ if (failed) {
137
+ throw new ParseError({ message: "Parser cannot be reused after a failure" })
138
+ }
139
+ try {
140
+ append(chunk)
141
+ while (end - start >= 5) {
142
+ const length = (buffer[start + 1] << 24) | (buffer[start + 2] << 16) | (buffer[start + 3] << 8) |
143
+ buffer[start + 4]
144
+ if (length < 4) {
145
+ throw new ParseError({ message: `Invalid message length: ${length}` })
146
+ }
147
+ if (length > maxMessageSize) {
148
+ throw new ParseError({
149
+ message: `Message length ${length} exceeds maxMessageSize ${maxMessageSize}`
150
+ })
151
+ }
152
+ if (end - start < length + 1) break
153
+ const type = buffer[start]
154
+ const body = start + 5
155
+ const limit = start + 1 + length
156
+ start = limit
157
+ if (type === BackendType.DataRow) {
158
+ // `buffer` always starts at byte 0 of `store`, so offsets index both.
159
+ onMessage(decodeDataRow<A>(buffer, store, 0, body, limit, parser.readField))
160
+ } else {
161
+ reader.reset(buffer, body, limit)
162
+ const message = decodeBackend(type, reader)
163
+ if (reader.offset !== limit) {
164
+ throw new ParseError({ message: `Message has ${limit - reader.offset} trailing byte(s)` })
165
+ }
166
+ onMessage(message as BackendMessage<A>)
167
+ }
168
+ }
169
+ } catch (error) {
170
+ failed = true
171
+ throw error
172
+ }
173
+ }
174
+ }
175
+ return parser
176
+ }
177
+
178
+ // -----------------------------------------------------------------------------
179
+ // frontend messages
180
+ // -----------------------------------------------------------------------------
181
+
182
+ /**
183
+ * Prepares a named or unnamed statement.
184
+ *
185
+ * @category models
186
+ * @since 4.0.0
187
+ */
188
+ export interface Parse {
189
+ readonly _tag: "Parse"
190
+ readonly name: string
191
+ readonly query: string
192
+ readonly parameterTypes: ReadonlyArray<number>
193
+ }
194
+
195
+ /**
196
+ * Binds parameter values to a prepared statement and creates a portal.
197
+ * Parameters and results use the binary format.
198
+ *
199
+ * @category models
200
+ * @since 4.0.0
201
+ */
202
+ export interface Bind {
203
+ readonly _tag: "Bind"
204
+ readonly portal: string
205
+ readonly statement: string
206
+ readonly parameters: ReadonlyArray<Uint8Array | null>
207
+ }
208
+
209
+ /**
210
+ * Runs a portal, optionally limiting the number of rows returned.
211
+ *
212
+ * @category models
213
+ * @since 4.0.0
214
+ */
215
+ export interface Execute {
216
+ readonly _tag: "Execute"
217
+ readonly portal: string
218
+ readonly maxRows: number
219
+ }
220
+
221
+ /**
222
+ * Which kind of object a `Describe` or `Close` message names.
223
+ *
224
+ * @category models
225
+ * @since 4.0.0
226
+ */
227
+ export type DescribeTarget = "statement" | "portal"
228
+
229
+ /**
230
+ * Asks for the parameter and row shape of a statement or portal.
231
+ *
232
+ * @category models
233
+ * @since 4.0.0
234
+ */
235
+ export interface Describe {
236
+ readonly _tag: "Describe"
237
+ readonly target: DescribeTarget
238
+ readonly name: string
239
+ }
240
+
241
+ /**
242
+ * Drops a prepared statement or portal.
243
+ *
244
+ * @category models
245
+ * @since 4.0.0
246
+ */
247
+ export interface Close {
248
+ readonly _tag: "Close"
249
+ readonly target: DescribeTarget
250
+ readonly name: string
251
+ }
252
+
253
+ /**
254
+ * Closes the current transaction block and requests a `ReadyForQuery`.
255
+ *
256
+ * @category models
257
+ * @since 4.0.0
258
+ */
259
+ export interface Sync {
260
+ readonly _tag: "Sync"
261
+ }
262
+
263
+ /**
264
+ * Asks the backend to deliver buffered output without ending the transaction.
265
+ *
266
+ * @category models
267
+ * @since 4.0.0
268
+ */
269
+ export interface Flush {
270
+ readonly _tag: "Flush"
271
+ }
272
+
273
+ /**
274
+ * Ends the session.
275
+ *
276
+ * @category models
277
+ * @since 4.0.0
278
+ */
279
+ export interface Terminate {
280
+ readonly _tag: "Terminate"
281
+ }
282
+
283
+ /**
284
+ * Answers a cleartext or MD5 password request.
285
+ *
286
+ * @category models
287
+ * @since 4.0.0
288
+ */
289
+ export interface PasswordMessage {
290
+ readonly _tag: "PasswordMessage"
291
+ readonly password: string
292
+ }
293
+
294
+ /**
295
+ * Selects a SASL mechanism and carries its opaque initial response.
296
+ *
297
+ * @category models
298
+ * @since 4.0.0
299
+ */
300
+ export interface SASLInitialResponse {
301
+ readonly _tag: "SASLInitialResponse"
302
+ readonly mechanism: string
303
+ readonly initialResponse: Uint8Array | null
304
+ }
305
+
306
+ /**
307
+ * Carries an opaque SASL continuation payload.
308
+ *
309
+ * @category models
310
+ * @since 4.0.0
311
+ */
312
+ export interface SASLResponse {
313
+ readonly _tag: "SASLResponse"
314
+ readonly data: Uint8Array
315
+ }
316
+
317
+ /**
318
+ * Any message the client sends after startup.
319
+ *
320
+ * @category models
321
+ * @since 4.0.0
322
+ */
323
+ export type FrontendMessage =
324
+ | Parse
325
+ | Bind
326
+ | Execute
327
+ | Describe
328
+ | Close
329
+ | Sync
330
+ | Flush
331
+ | Terminate
332
+ | PasswordMessage
333
+ | SASLInitialResponse
334
+ | SASLResponse
335
+
336
+ /**
337
+ * Error produced when bytes cannot be interpreted as a protocol message.
338
+ *
339
+ * @category errors
340
+ * @since 4.0.0
341
+ */
342
+ export class ParseError extends Data.TaggedError("PgProtocolParseError")<{
343
+ readonly message: string
344
+ }> {}
345
+
346
+ /**
347
+ * Error returned when a frontend message cannot be encoded.
348
+ *
349
+ * @category errors
350
+ * @since 4.0.0
351
+ */
352
+ export class EncodeError extends Data.TaggedError("PgProtocolEncodeError")<{
353
+ readonly message: string
354
+ }> {}
355
+
356
+ const textEncoder = new TextEncoder()
357
+ const textDecoder = new TextDecoder("utf-8", { fatal: true })
358
+
359
+ /**
360
+ * Above this length `TextEncoder.encodeInto` beats a per-character loop, below
361
+ * it the call overhead dominates. Measured on V8: the loop runs at about a
362
+ * nanosecond per character and `encodeInto` costs about 50 ns whatever the
363
+ * length, so the crossover is around 50 characters.
364
+ */
365
+ const asciiEncodeLimit = 48
366
+
367
+ /**
368
+ * A view over part of a cached backing store. Slicing runs once per column of
369
+ * every row, and both obvious spellings are slower than this one: `subarray`
370
+ * consults the constructor's `Symbol.species` before it can allocate, and
371
+ * reading `.buffer` off a typed array is an accessor call rather than a field
372
+ * load. Callers hold the store and the array's byte offset instead.
373
+ */
374
+ const view = (store: ArrayBufferLike, offset: number, length: number): Uint8Array =>
375
+ new Uint8Array(store, offset, length)
376
+
377
+ /** Up to this many bytes a copy loop beats `Uint8Array.prototype.set`. */
378
+ const smallCopyLimit = 8
379
+
380
+ /**
381
+ * Writes messages back to back into a pooled buffer and hands out a view of
382
+ * each one, so encoding a message costs no allocation of its own. Bytes below
383
+ * `start` have already been handed out and are never rewritten; when the pool
384
+ * runs out it is replaced rather than reused.
385
+ */
386
+ class Writer {
387
+ readonly poolSize: number
388
+ bytes: Uint8Array
389
+ view: DataView
390
+ /** Where the message currently being written begins. */
391
+ start = 0
392
+ offset = 0
393
+ /** Set by `sqlNull`, read and cleared by the `makeBindEncoder` loop. */
394
+ isNull = false
395
+
396
+ constructor(poolSize: number) {
397
+ this.poolSize = poolSize
398
+ this.bytes = new Uint8Array(poolSize)
399
+ this.view = new DataView(this.bytes.buffer)
400
+ }
401
+
402
+ reserve(size: number): void {
403
+ if (this.offset + size <= this.bytes.length) return
404
+ const pending = this.offset - this.start
405
+ let capacity = this.poolSize
406
+ while (capacity < pending + size) capacity *= 2
407
+ const next = new Uint8Array(capacity)
408
+ next.set(this.bytes.subarray(this.start, this.offset))
409
+ this.bytes = next
410
+ this.view = new DataView(next.buffer)
411
+ this.start = 0
412
+ this.offset = pending
413
+ }
414
+
415
+ /** Starts a message, dropping anything a failed write left behind. */
416
+ begin(): void {
417
+ this.start = this.offset
418
+ }
419
+
420
+ uint8(value: number): void {
421
+ this.reserve(1)
422
+ this.bytes[this.offset++] = value
423
+ }
424
+
425
+ int16(value: number): void {
426
+ this.reserve(2)
427
+ const bytes = this.bytes
428
+ const offset = this.offset
429
+ bytes[offset] = value >>> 8
430
+ bytes[offset + 1] = value
431
+ this.offset = offset + 2
432
+ }
433
+
434
+ int32(value: number): void {
435
+ this.reserve(4)
436
+ this.setInt32(this.offset, value)
437
+ this.offset += 4
438
+ }
439
+
440
+ setInt32(offset: number, value: number): void {
441
+ const bytes = this.bytes
442
+ bytes[offset] = value >>> 24
443
+ bytes[offset + 1] = value >>> 16
444
+ bytes[offset + 2] = value >>> 8
445
+ bytes[offset + 3] = value
446
+ }
447
+
448
+ float32(value: number): void {
449
+ this.reserve(4)
450
+ this.view.setFloat32(this.offset, value)
451
+ this.offset += 4
452
+ }
453
+
454
+ float64(value: number): void {
455
+ this.reserve(8)
456
+ this.view.setFloat64(this.offset, value)
457
+ this.offset += 8
458
+ }
459
+
460
+ bigInt64(value: bigint): void {
461
+ this.reserve(8)
462
+ this.view.setBigInt64(this.offset, value)
463
+ this.offset += 8
464
+ }
465
+
466
+ raw(value: Uint8Array): void {
467
+ const length = value.length
468
+ this.reserve(length)
469
+ const bytes = this.bytes
470
+ const offset = this.offset
471
+ if (length <= smallCopyLimit) {
472
+ for (let index = 0; index < length; index++) bytes[offset + index] = value[index]
473
+ } else {
474
+ bytes.set(value, offset)
475
+ }
476
+ this.offset = offset + length
477
+ }
478
+
479
+ sqlNull(): void {
480
+ this.isNull = true
481
+ }
482
+
483
+ beginLength(): number {
484
+ // Relative to `start`, because a later write may move the message to a new
485
+ // pool buffer, which rebases `start` and `offset` but not the distance
486
+ // between them.
487
+ const token = this.offset - this.start
488
+ this.int32(0)
489
+ return token
490
+ }
491
+
492
+ endLength(token: number): void {
493
+ this.setInt32(this.start + token, this.offset - this.start - token - 4)
494
+ }
495
+
496
+ utf8(value: string, nul = false): void {
497
+ const length = value.length
498
+ if (length <= asciiEncodeLimit) {
499
+ this.reserve(length + (nul ? 1 : 0))
500
+ const bytes = this.bytes
501
+ const start = this.offset
502
+ let i = 0
503
+ for (; i < length; i++) {
504
+ const code = value.charCodeAt(i)
505
+ if (code > 0x7f) break
506
+ bytes[start + i] = code
507
+ }
508
+ if (i === length) {
509
+ const offset = start + length
510
+ if (nul) bytes[offset] = 0
511
+ this.offset = offset + (nul ? 1 : 0)
512
+ return
513
+ }
514
+ }
515
+ // UTF-8 takes at most three bytes per UTF-16 code unit, and four for the
516
+ // two units of a surrogate pair, so this covers any string.
517
+ this.reserve(length * 3 + (nul ? 1 : 0))
518
+ this.offset += textEncoder.encodeInto(value, this.bytes.subarray(this.offset)).written
519
+ if (nul) this.bytes[this.offset++] = 0
520
+ }
521
+
522
+ cString(value: string): void {
523
+ this.utf8(value, true)
524
+ }
525
+
526
+ finish(): Uint8Array {
527
+ const value = view(this.bytes.buffer, this.bytes.byteOffset + this.start, this.offset - this.start)
528
+ if (this.bytes.length > this.poolSize) {
529
+ // An oversized message grew the pool; do not keep the rest of it around.
530
+ this.bytes = new Uint8Array(this.poolSize)
531
+ this.view = new DataView(this.bytes.buffer)
532
+ this.start = 0
533
+ this.offset = 0
534
+ } else {
535
+ this.start = this.offset
536
+ }
537
+ return value
538
+ }
539
+ }
540
+
541
+ const emptyBytes = new Uint8Array(0)
542
+
543
+ /**
544
+ * A cursor over a message payload. The parser reuses one instance pointed at a
545
+ * window of its own buffer, so decoding a message allocates nothing beyond the
546
+ * message itself.
547
+ */
548
+ class Reader {
549
+ bytes: Uint8Array = emptyBytes
550
+ /** The backing store of `bytes` and its offset into it, resolved per message. */
551
+ store: ArrayBufferLike = emptyBytes.buffer
552
+ base = 0
553
+ offset = 0
554
+ limit = 0
555
+
556
+ reset(bytes: Uint8Array, offset: number, limit: number): void {
557
+ this.bytes = bytes
558
+ this.store = bytes.buffer
559
+ this.base = bytes.byteOffset
560
+ this.offset = offset
561
+ this.limit = limit
562
+ }
563
+
564
+ require(size: number): void {
565
+ if (size < 0) {
566
+ throw new ParseError({ message: `Invalid read of ${size} byte(s)` })
567
+ }
568
+ if (this.offset + size > this.limit) {
569
+ throw new ParseError({ message: `Truncated message: expected ${size} more byte(s)` })
570
+ }
571
+ }
572
+
573
+ uint8(): number {
574
+ this.require(1)
575
+ return this.bytes[this.offset++]
576
+ }
577
+
578
+ int16(): number {
579
+ this.require(2)
580
+ const bytes = this.bytes
581
+ const offset = this.offset
582
+ this.offset = offset + 2
583
+ return ((bytes[offset] << 8) | bytes[offset + 1]) << 16 >> 16
584
+ }
585
+
586
+ int32(): number {
587
+ this.require(4)
588
+ const bytes = this.bytes
589
+ const offset = this.offset
590
+ this.offset = offset + 4
591
+ return (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]
592
+ }
593
+
594
+ uint32(): number {
595
+ return this.int32() >>> 0
596
+ }
597
+
598
+ raw(size: number): Uint8Array {
599
+ this.require(size)
600
+ const value = view(this.store, this.base + this.offset, size)
601
+ this.offset += size
602
+ return value
603
+ }
604
+
605
+ rest(): Uint8Array {
606
+ return this.raw(this.limit - this.offset)
607
+ }
608
+
609
+ cString(): string {
610
+ const end = this.bytes.indexOf(0, this.offset)
611
+ if (end === -1 || end >= this.limit) {
612
+ throw new ParseError({ message: "Unterminated string" })
613
+ }
614
+ const value = decodeUtf8(this.bytes, this.offset, end - this.offset)
615
+ this.offset = end + 1
616
+ return value
617
+ }
618
+ }
619
+
620
+ /** Below this length a per-character loop beats `TextDecoder.decode`. */
621
+ const asciiDecodeLimit = 10
622
+
623
+ /**
624
+ * Node's own UTF-8 decoder; see the note on its `PgTypes` counterpart. A
625
+ * result containing a replacement character goes to the strict decoder, so
626
+ * invalid bytes still fail exactly as they did.
627
+ */
628
+ const utf8Slice: ((this: Uint8Array, start: number, end: number) => string) | undefined = (globalThis as any).Buffer
629
+ ?.prototype?.utf8Slice
630
+
631
+ const decodeUtf8 = (bytes: Uint8Array, offset: number, size: number): string => {
632
+ if (size <= asciiDecodeLimit) {
633
+ let text = ""
634
+ let index = 0
635
+ for (; index < size; index++) {
636
+ const code = bytes[offset + index]
637
+ if (code > 0x7f) break
638
+ text += String.fromCharCode(code)
639
+ }
640
+ if (index === size) return text
641
+ }
642
+ if (utf8Slice !== undefined) {
643
+ const text = utf8Slice.call(bytes, offset, offset + size)
644
+ if (text.indexOf("\ufffd") === -1) return text
645
+ }
646
+ try {
647
+ return textDecoder.decode(view(bytes.buffer, bytes.byteOffset + offset, size))
648
+ } catch {
649
+ throw new ParseError({ message: "Invalid UTF-8 in message" })
650
+ }
651
+ }
652
+
653
+ const sharedWriter = new Writer(8192)
654
+
655
+ /**
656
+ * Opens a typed message and leaves room for its length. Paired with `end`,
657
+ * which backfills it. A pair rather than a `write` callback, because a
658
+ * callback allocates a closure over the caller's options on every message.
659
+ */
660
+ const begin = (type: number): Writer => {
661
+ const writer = sharedWriter
662
+ writer.begin()
663
+ writer.reserve(5)
664
+ const bytes = writer.bytes
665
+ const offset = writer.offset
666
+ bytes[offset] = type
667
+ writer.offset = offset + 5
668
+ return writer
669
+ }
670
+
671
+ const end = (): Uint8Array => {
672
+ // Relative to `start`, because writing may have moved the message to a new
673
+ // pool buffer. The length counts itself but not the type byte.
674
+ sharedWriter.setInt32(sharedWriter.start + 1, sharedWriter.offset - sharedWriter.start - 1)
675
+ return sharedWriter.finish()
676
+ }
677
+
678
+ const empty = (type: number): Uint8Array => {
679
+ const writer = begin(type)
680
+ writer.setInt32(writer.start + 1, 4)
681
+ return writer.finish()
682
+ }
683
+
684
+ const targetByte = (target: DescribeTarget): number => target === "statement" ? 0x53 : 0x50
685
+
686
+ const requireInt16Count = (count: number, name: string): number => {
687
+ if (count > 0x7fff) throw new EncodeError({ message: `${name} count exceeds 32767: ${count}` })
688
+ return count
689
+ }
690
+
691
+ const encodeResult = <A>(evaluate: () => A): Result.Result<A, EncodeError> => {
692
+ try {
693
+ return Result.succeed(evaluate())
694
+ } catch (error) {
695
+ if (error instanceof EncodeError) return Result.fail(error)
696
+ throw error
697
+ }
698
+ }
699
+
700
+ /**
701
+ * Encodes a `Parse` message, returning `EncodeError` when its parameter count
702
+ * is outside the signed int16 wire range.
703
+ *
704
+ * @category encoding
705
+ * @since 4.0.0
706
+ */
707
+ const encodeParseUnsafe = (options: Omit<Parse, "_tag">): Uint8Array => {
708
+ const writer = begin(0x50)
709
+ writer.cString(options.name)
710
+ writer.cString(options.query)
711
+ const parameterTypes = options.parameterTypes
712
+ const count = requireInt16Count(parameterTypes.length, "Parse parameter type")
713
+ writer.reserve(2 + count * 4)
714
+ const bytes = writer.bytes
715
+ let offset = writer.offset
716
+ bytes[offset] = count >>> 8
717
+ bytes[offset + 1] = count
718
+ offset += 2
719
+ for (let index = 0; index < count; index++) {
720
+ const oid = parameterTypes[index]
721
+ bytes[offset] = oid >>> 24
722
+ bytes[offset + 1] = oid >>> 16
723
+ bytes[offset + 2] = oid >>> 8
724
+ bytes[offset + 3] = oid
725
+ offset += 4
726
+ }
727
+ writer.offset = offset
728
+ return end()
729
+ }
730
+
731
+ /**
732
+ * Encodes a `Parse` message.
733
+ *
734
+ * @category encoding
735
+ * @since 4.0.0
736
+ */
737
+ export const encodeParse = (options: Omit<Parse, "_tag">): Result.Result<Uint8Array, EncodeError> =>
738
+ encodeResult(() => encodeParseUnsafe(options))
739
+
740
+ /**
741
+ * Encodes a `Bind` message using the binary format code for parameters and
742
+ * results, returning `EncodeError` when its parameter count is outside the
743
+ * signed int16 wire range.
744
+ *
745
+ * @category encoding
746
+ * @since 4.0.0
747
+ */
748
+ const encodeBindUnsafe = (options: Omit<Bind, "_tag">): Uint8Array => {
749
+ const writer = begin(0x42)
750
+ writer.cString(options.portal)
751
+ writer.cString(options.statement)
752
+ const parameters = options.parameters
753
+ const count = requireInt16Count(parameters.length, "Bind parameter")
754
+ // Sizing the rest of the frame up front turns every remaining write into a
755
+ // plain store: one bounds check for the message instead of one per field.
756
+ let size = 10 + count * 4
757
+ for (let index = 0; index < count; index++) {
758
+ const parameter = parameters[index]
759
+ if (parameter !== null) size += parameter.length
760
+ }
761
+ writer.reserve(size)
762
+ const bytes = writer.bytes
763
+ let offset = writer.offset
764
+ // One parameter format code, binary, for every parameter.
765
+ bytes[offset] = 0
766
+ bytes[offset + 1] = 1
767
+ bytes[offset + 2] = 0
768
+ bytes[offset + 3] = 1
769
+ bytes[offset + 4] = count >>> 8
770
+ bytes[offset + 5] = count
771
+ offset += 6
772
+ for (let index = 0; index < count; index++) {
773
+ const parameter = parameters[index]
774
+ if (parameter === null) {
775
+ bytes[offset] = 0xff
776
+ bytes[offset + 1] = 0xff
777
+ bytes[offset + 2] = 0xff
778
+ bytes[offset + 3] = 0xff
779
+ offset += 4
780
+ } else {
781
+ const length = parameter.length
782
+ bytes[offset] = length >>> 24
783
+ bytes[offset + 1] = length >>> 16
784
+ bytes[offset + 2] = length >>> 8
785
+ bytes[offset + 3] = length
786
+ offset += 4
787
+ if (length <= smallCopyLimit) {
788
+ for (let byte = 0; byte < length; byte++) bytes[offset + byte] = parameter[byte]
789
+ } else {
790
+ bytes.set(parameter, offset)
791
+ }
792
+ offset += length
793
+ }
794
+ }
795
+ // One result format code, binary, for every column.
796
+ bytes[offset] = 0
797
+ bytes[offset + 1] = 1
798
+ bytes[offset + 2] = 0
799
+ bytes[offset + 3] = 1
800
+ writer.offset = offset + 4
801
+ return end()
802
+ }
803
+
804
+ /**
805
+ * Encodes a `Bind` message.
806
+ *
807
+ * @category encoding
808
+ * @since 4.0.0
809
+ */
810
+ export const encodeBind = (options: Omit<Bind, "_tag">): Result.Result<Uint8Array, EncodeError> =>
811
+ encodeResult(() => encodeBindUnsafe(options))
812
+
813
+ /**
814
+ * A sink for writing parameter bytes into a `Bind` frame. The frame reserves
815
+ * and backfills each parameter length. `PgTypes.writeParameter` supports
816
+ * OID-typed values.
817
+ *
818
+ * @category models
819
+ * @since 4.0.0
820
+ */
821
+ export interface ValueSink {
822
+ readonly uint8: (value: number) => void
823
+ readonly int16: (value: number) => void
824
+ readonly int32: (value: number) => void
825
+ readonly float32: (value: number) => void
826
+ readonly float64: (value: number) => void
827
+ readonly bigInt64: (value: bigint) => void
828
+ readonly raw: (value: Uint8Array) => void
829
+ readonly utf8: (value: string) => void
830
+ /** Writes SQL NULL. The value must write nothing else. */
831
+ readonly sqlNull: () => void
832
+ /**
833
+ * Leaves room for an int32 length and returns a token for `endLength`, so a
834
+ * value that contains other values can frame them without knowing their
835
+ * sizes up front. Tokens nest, and must be closed in the order they were
836
+ * opened.
837
+ */
838
+ readonly beginLength: () => number
839
+ /** Backfills the length of everything written since its `beginLength`. */
840
+ readonly endLength: (token: number) => void
841
+ }
842
+
843
+ const valueWriterUnsafe = Symbol.for("@effect/sql-pg/PgProtocol/ValueWriter/unsafe")
844
+
845
+ /**
846
+ * Creates a `Bind` encoder that writes parameters directly into the frame.
847
+ *
848
+ * **Details**
849
+ *
850
+ * `textFormat` identifies parameters encoded as text. All other parameters
851
+ * use the binary format.
852
+ *
853
+ * **Example** (Encoding a `Bind` message)
854
+ *
855
+ * ```ts
856
+ * import { PgProtocol, PgTypes } from "@effect/sql-pg"
857
+ *
858
+ * const encodeBind = PgProtocol.makeBindEncoder(PgTypes.writeParameter, PgTypes.isTextFormat)
859
+ * const frame = encodeBind({ portal: "", statement: "s1", parameters: [PgTypes.int4(1)] })
860
+ * ```
861
+ *
862
+ * @category encoding
863
+ * @since 4.0.0
864
+ */
865
+ export const makeBindEncoder = <A, E = never>(
866
+ writeParameter: (sink: ValueSink, value: A) => Result.Result<void, E>,
867
+ textFormat?: (value: A) => boolean
868
+ ) =>
869
+ (options: {
870
+ readonly portal: string
871
+ readonly statement: string
872
+ readonly parameters: ReadonlyArray<A>
873
+ }): Result.Result<Uint8Array, EncodeError | E> => {
874
+ try {
875
+ const writer = begin(0x42)
876
+ writer.cString(options.portal)
877
+ writer.cString(options.statement)
878
+ const parameters = options.parameters
879
+ const count = requireInt16Count(parameters.length, "Bind parameter")
880
+ let textCount = 0
881
+ if (textFormat !== undefined) {
882
+ for (let index = 0; index < count; index++) {
883
+ if (textFormat(parameters[index])) textCount++
884
+ }
885
+ }
886
+ if (textCount === 0 || textCount === count) {
887
+ // One format code covering every parameter: binary, or all-text.
888
+ const code = textCount === 0 ? 1 : 0
889
+ writer.reserve(6)
890
+ const header = writer.bytes
891
+ const headerOffset = writer.offset
892
+ header[headerOffset] = 0
893
+ header[headerOffset + 1] = 1
894
+ header[headerOffset + 2] = 0
895
+ header[headerOffset + 3] = code
896
+ header[headerOffset + 4] = count >>> 8
897
+ header[headerOffset + 5] = count
898
+ writer.offset = headerOffset + 6
899
+ } else {
900
+ writer.int16(count)
901
+ for (let index = 0; index < count; index++) {
902
+ writer.int16(textFormat!(parameters[index]) ? 0 : 1)
903
+ }
904
+ writer.int16(count)
905
+ }
906
+ const writeUnsafe = (writeParameter as any)[valueWriterUnsafe] as
907
+ | ((sink: ValueSink, value: A) => void)
908
+ | undefined
909
+ for (let index = 0; index < count; index++) {
910
+ const token = writer.beginLength()
911
+ writer.isNull = false
912
+ if (writeUnsafe === undefined) {
913
+ const written = writeParameter(writer, parameters[index])
914
+ if (Result.isFailure(written)) return Result.fail(written.failure)
915
+ } else {
916
+ writeUnsafe(writer, parameters[index])
917
+ }
918
+ if (writer.isNull) {
919
+ writer.isNull = false
920
+ writer.offset = writer.start + token
921
+ writer.int32(-1)
922
+ } else {
923
+ writer.endLength(token)
924
+ }
925
+ }
926
+ writer.reserve(4)
927
+ const trailer = writer.bytes
928
+ const trailerOffset = writer.offset
929
+ trailer[trailerOffset] = 0
930
+ trailer[trailerOffset + 1] = 1
931
+ trailer[trailerOffset + 2] = 0
932
+ trailer[trailerOffset + 3] = 1
933
+ writer.offset = trailerOffset + 4
934
+ return Result.succeed(end())
935
+ } catch (error) {
936
+ if (error instanceof EncodeError) return Result.fail(error)
937
+ throw error
938
+ }
939
+ }
940
+
941
+ /**
942
+ * Encodes an `Execute` message.
943
+ *
944
+ * @category encoding
945
+ * @since 4.0.0
946
+ */
947
+ export const encodeExecute = (options: Omit<Execute, "_tag">): Uint8Array => {
948
+ const writer = begin(0x45)
949
+ writer.cString(options.portal)
950
+ writer.int32(options.maxRows)
951
+ return end()
952
+ }
953
+
954
+ /**
955
+ * Encodes a `Describe` message.
956
+ *
957
+ * @category encoding
958
+ * @since 4.0.0
959
+ */
960
+ export const encodeDescribe = (options: Omit<Describe, "_tag">): Uint8Array => {
961
+ const writer = begin(0x44)
962
+ writer.uint8(targetByte(options.target))
963
+ writer.cString(options.name)
964
+ return end()
965
+ }
966
+
967
+ /**
968
+ * Encodes a `Close` message.
969
+ *
970
+ * @category encoding
971
+ * @since 4.0.0
972
+ */
973
+ export const encodeClose = (options: Omit<Close, "_tag">): Uint8Array => {
974
+ const writer = begin(0x43)
975
+ writer.uint8(targetByte(options.target))
976
+ writer.cString(options.name)
977
+ return end()
978
+ }
979
+
980
+ /**
981
+ * Encodes a `Sync` message.
982
+ *
983
+ * @category encoding
984
+ * @since 4.0.0
985
+ */
986
+ export const encodeSync = (): Uint8Array => empty(0x53)
987
+
988
+ /**
989
+ * Encodes a `Flush` message.
990
+ *
991
+ * @category encoding
992
+ * @since 4.0.0
993
+ */
994
+ export const encodeFlush = (): Uint8Array => empty(0x48)
995
+
996
+ /**
997
+ * Encodes a `Terminate` message.
998
+ *
999
+ * @category encoding
1000
+ * @since 4.0.0
1001
+ */
1002
+ export const encodeTerminate = (): Uint8Array => empty(0x58)
1003
+
1004
+ /**
1005
+ * Encodes a `PasswordMessage`. The password is sent verbatim, so MD5 hashing
1006
+ * belongs to the caller - see `PgAuth.md5Password`.
1007
+ *
1008
+ * @category encoding
1009
+ * @since 4.0.0
1010
+ */
1011
+ export const encodePasswordMessage = (options: Omit<PasswordMessage, "_tag">): Uint8Array => {
1012
+ const writer = begin(0x70)
1013
+ writer.cString(options.password)
1014
+ return end()
1015
+ }
1016
+
1017
+ /**
1018
+ * Encodes a `SASLInitialResponse` message.
1019
+ *
1020
+ * @category encoding
1021
+ * @since 4.0.0
1022
+ */
1023
+ export const encodeSASLInitialResponse = (options: Omit<SASLInitialResponse, "_tag">): Uint8Array => {
1024
+ const writer = begin(0x70)
1025
+ writer.cString(options.mechanism)
1026
+ if (options.initialResponse === null) {
1027
+ writer.int32(-1)
1028
+ } else {
1029
+ writer.int32(options.initialResponse.length)
1030
+ writer.raw(options.initialResponse)
1031
+ }
1032
+ return end()
1033
+ }
1034
+
1035
+ /**
1036
+ * Encodes a `SASLResponse` message.
1037
+ *
1038
+ * @category encoding
1039
+ * @since 4.0.0
1040
+ */
1041
+ export const encodeSASLResponse = (options: Omit<SASLResponse, "_tag">): Uint8Array => {
1042
+ const writer = begin(0x70)
1043
+ writer.raw(options.data)
1044
+ return end()
1045
+ }
1046
+
1047
+ /**
1048
+ * Encodes any frontend message.
1049
+ *
1050
+ * @category encoding
1051
+ * @since 4.0.0
1052
+ */
1053
+ export const encode = (message: FrontendMessage): Result.Result<Uint8Array, EncodeError> => {
1054
+ switch (message._tag) {
1055
+ case "Parse":
1056
+ return encodeParse(message)
1057
+ case "Bind":
1058
+ return encodeBind(message)
1059
+ case "Execute":
1060
+ return Result.succeed(encodeExecute(message))
1061
+ case "Describe":
1062
+ return Result.succeed(encodeDescribe(message))
1063
+ case "Close":
1064
+ return Result.succeed(encodeClose(message))
1065
+ case "Sync":
1066
+ return Result.succeed(encodeSync())
1067
+ case "Flush":
1068
+ return Result.succeed(encodeFlush())
1069
+ case "Terminate":
1070
+ return Result.succeed(encodeTerminate())
1071
+ case "PasswordMessage":
1072
+ return Result.succeed(encodePasswordMessage(message))
1073
+ case "SASLInitialResponse":
1074
+ return Result.succeed(encodeSASLInitialResponse(message))
1075
+ case "SASLResponse":
1076
+ return Result.succeed(encodeSASLResponse(message))
1077
+ }
1078
+ }
1079
+
1080
+ // -----------------------------------------------------------------------------
1081
+ // special messages
1082
+ // -----------------------------------------------------------------------------
1083
+
1084
+ const PROTOCOL_VERSION_3_0 = 196608
1085
+ const SSL_REQUEST_CODE = 80877103
1086
+ const CANCEL_REQUEST_CODE = 80877102
1087
+
1088
+ /**
1089
+ * Startup parameters. `user` is required; any other run-time parameter the
1090
+ * server accepts may be passed alongside it.
1091
+ *
1092
+ * @category models
1093
+ * @since 4.0.0
1094
+ */
1095
+ export interface StartupParameters {
1096
+ readonly [key: string]: string | undefined
1097
+ readonly user: string
1098
+ readonly database?: string | undefined
1099
+ readonly application_name?: string | undefined
1100
+ }
1101
+
1102
+ /**
1103
+ * Encodes an `SSLRequest`. It has no type byte and is only valid before
1104
+ * startup.
1105
+ *
1106
+ * @category encoding
1107
+ * @since 4.0.0
1108
+ */
1109
+ export const encodeSslRequest = (): Uint8Array => {
1110
+ sharedWriter.begin()
1111
+ sharedWriter.int32(8)
1112
+ sharedWriter.int32(SSL_REQUEST_CODE)
1113
+ return sharedWriter.finish()
1114
+ }
1115
+
1116
+ /**
1117
+ * Decodes the single byte the server sends in reply to an `SSLRequest`. `"S"`
1118
+ * means the server will speak TLS, `"N"` means it will not.
1119
+ *
1120
+ * @category decoding
1121
+ * @since 4.0.0
1122
+ */
1123
+ export const decodeSslResponse = (byte: number): Result.Result<"S" | "N", ParseError> =>
1124
+ byte === 0x53
1125
+ ? Result.succeed("S")
1126
+ : byte === 0x4e
1127
+ ? Result.succeed("N")
1128
+ : Result.fail(new ParseError({ message: `Invalid SSLRequest response byte: ${byte}` }))
1129
+
1130
+ /**
1131
+ * Encodes a `StartupMessage` for protocol 3.0. It has no type byte.
1132
+ * `client_encoding` defaults to `UTF8` because this codec always writes UTF-8.
1133
+ *
1134
+ * @category encoding
1135
+ * @since 4.0.0
1136
+ */
1137
+ export const encodeStartupMessage = (parameters: StartupParameters): Uint8Array => {
1138
+ const writer = sharedWriter
1139
+ writer.begin()
1140
+ writer.int32(0)
1141
+ writer.int32(PROTOCOL_VERSION_3_0)
1142
+ for (const [key, value] of Object.entries(parameters)) {
1143
+ if (value === undefined) continue
1144
+ writer.cString(key)
1145
+ writer.cString(value)
1146
+ }
1147
+ if (parameters.client_encoding === undefined) {
1148
+ writer.cString("client_encoding")
1149
+ writer.cString("UTF8")
1150
+ }
1151
+ writer.uint8(0)
1152
+ writer.setInt32(writer.start, writer.offset - writer.start)
1153
+ return writer.finish()
1154
+ }
1155
+
1156
+ /**
1157
+ * Encodes a `CancelRequest`. It has no type byte and is sent on a separate
1158
+ * connection, using the `pid` and `secret` from `BackendKeyData`.
1159
+ *
1160
+ * @category encoding
1161
+ * @since 4.0.0
1162
+ */
1163
+ export const encodeCancelRequest = (options: {
1164
+ readonly pid: number
1165
+ readonly secret: number
1166
+ }): Uint8Array => {
1167
+ const writer = sharedWriter
1168
+ writer.begin()
1169
+ writer.int32(16)
1170
+ writer.int32(CANCEL_REQUEST_CODE)
1171
+ writer.int32(options.pid)
1172
+ writer.int32(options.secret)
1173
+ return writer.finish()
1174
+ }
1175
+
1176
+ // -----------------------------------------------------------------------------
1177
+ // backend messages
1178
+ // -----------------------------------------------------------------------------
1179
+
1180
+ /**
1181
+ * Authentication succeeded.
1182
+ *
1183
+ * @category models
1184
+ * @since 4.0.0
1185
+ */
1186
+ export interface AuthenticationOk {
1187
+ readonly _tag: "AuthenticationOk"
1188
+ }
1189
+
1190
+ /**
1191
+ * The server wants the password in the clear.
1192
+ *
1193
+ * @category models
1194
+ * @since 4.0.0
1195
+ */
1196
+ export interface AuthenticationCleartextPassword {
1197
+ readonly _tag: "AuthenticationCleartextPassword"
1198
+ }
1199
+
1200
+ /**
1201
+ * The server wants an MD5-hashed password, salted with these four bytes.
1202
+ *
1203
+ * @category models
1204
+ * @since 4.0.0
1205
+ */
1206
+ export interface AuthenticationMD5Password {
1207
+ readonly _tag: "AuthenticationMD5Password"
1208
+ readonly salt: Uint8Array
1209
+ }
1210
+
1211
+ /**
1212
+ * The server offers these SASL mechanisms.
1213
+ *
1214
+ * @category models
1215
+ * @since 4.0.0
1216
+ */
1217
+ export interface AuthenticationSASL {
1218
+ readonly _tag: "AuthenticationSASL"
1219
+ readonly mechanisms: ReadonlyArray<string>
1220
+ }
1221
+
1222
+ /**
1223
+ * An opaque SASL challenge.
1224
+ *
1225
+ * @category models
1226
+ * @since 4.0.0
1227
+ */
1228
+ export interface AuthenticationSASLContinue {
1229
+ readonly _tag: "AuthenticationSASLContinue"
1230
+ readonly data: Uint8Array
1231
+ }
1232
+
1233
+ /**
1234
+ * The opaque final SASL payload, carrying the server signature.
1235
+ *
1236
+ * @category models
1237
+ * @since 4.0.0
1238
+ */
1239
+ export interface AuthenticationSASLFinal {
1240
+ readonly _tag: "AuthenticationSASLFinal"
1241
+ readonly data: Uint8Array
1242
+ }
1243
+
1244
+ /**
1245
+ * An authentication request this codec does not model, such as GSSAPI or
1246
+ * SSPI. The `method` is the raw sub-type integer.
1247
+ *
1248
+ * @category models
1249
+ * @since 4.0.0
1250
+ */
1251
+ export interface AuthenticationUnsupported {
1252
+ readonly _tag: "AuthenticationUnsupported"
1253
+ readonly method: number
1254
+ readonly payload: Uint8Array
1255
+ }
1256
+
1257
+ /**
1258
+ * Reports a run-time parameter value, at startup or whenever it changes.
1259
+ *
1260
+ * @category models
1261
+ * @since 4.0.0
1262
+ */
1263
+ export interface ParameterStatus {
1264
+ readonly _tag: "ParameterStatus"
1265
+ readonly name: string
1266
+ readonly value: string
1267
+ }
1268
+
1269
+ /**
1270
+ * The identity a `CancelRequest` needs.
1271
+ *
1272
+ * @category models
1273
+ * @since 4.0.0
1274
+ */
1275
+ export interface BackendKeyData {
1276
+ readonly _tag: "BackendKeyData"
1277
+ readonly pid: number
1278
+ readonly secret: number
1279
+ }
1280
+
1281
+ /**
1282
+ * Transaction status: idle, in a transaction block, or in a failed
1283
+ * transaction block.
1284
+ *
1285
+ * @category models
1286
+ * @since 4.0.0
1287
+ */
1288
+ export type TransactionStatus = "I" | "T" | "E"
1289
+
1290
+ /**
1291
+ * The backend is ready for a new query cycle.
1292
+ *
1293
+ * @category models
1294
+ * @since 4.0.0
1295
+ */
1296
+ export interface ReadyForQuery {
1297
+ readonly _tag: "ReadyForQuery"
1298
+ readonly status: TransactionStatus
1299
+ }
1300
+
1301
+ /**
1302
+ * One column of a `RowDescription`.
1303
+ *
1304
+ * @category models
1305
+ * @since 4.0.0
1306
+ */
1307
+ export interface FieldDescription {
1308
+ readonly name: string
1309
+ readonly tableOid: number
1310
+ readonly columnAttributeNumber: number
1311
+ readonly dataTypeOid: number
1312
+ readonly dataTypeSize: number
1313
+ readonly typeModifier: number
1314
+ readonly format: number
1315
+ }
1316
+
1317
+ /**
1318
+ * Describes the columns a portal will return.
1319
+ *
1320
+ * @category models
1321
+ * @since 4.0.0
1322
+ */
1323
+ export interface RowDescription {
1324
+ readonly _tag: "RowDescription"
1325
+ readonly fields: ReadonlyArray<FieldDescription>
1326
+ }
1327
+
1328
+ /**
1329
+ * One result row. Values stay raw bytes; `null` is SQL NULL. Decoding them
1330
+ * requires the OIDs from the matching `RowDescription`.
1331
+ *
1332
+ * @category models
1333
+ * @since 4.0.0
1334
+ */
1335
+ export interface DataRow<out A = Uint8Array | null> {
1336
+ readonly _tag: "DataRow"
1337
+ readonly values: ReadonlyArray<A>
1338
+ }
1339
+
1340
+ /**
1341
+ * Reads one `DataRow` field from the parser buffer.
1342
+ *
1343
+ * **Details**
1344
+ *
1345
+ * `size` is `-1` for SQL `NULL`, and `column` is the field index. Only bytes
1346
+ * from `offset` through `offset + size` belong to the field. A thrown error
1347
+ * permanently fails the parser.
1348
+ *
1349
+ * @category models
1350
+ * @since 4.0.0
1351
+ */
1352
+ export type FieldReader<A> = (bytes: Uint8Array, offset: number, size: number, column: number) => A
1353
+
1354
+ /**
1355
+ * A command finished, reporting its tag such as `SELECT 3`.
1356
+ *
1357
+ * @category models
1358
+ * @since 4.0.0
1359
+ */
1360
+ export interface CommandComplete {
1361
+ readonly _tag: "CommandComplete"
1362
+ readonly commandTag: string
1363
+ }
1364
+
1365
+ /**
1366
+ * The query string was empty.
1367
+ *
1368
+ * @category models
1369
+ * @since 4.0.0
1370
+ */
1371
+ export interface EmptyQueryResponse {
1372
+ readonly _tag: "EmptyQueryResponse"
1373
+ }
1374
+
1375
+ /**
1376
+ * The statement or portal returns no rows.
1377
+ *
1378
+ * @category models
1379
+ * @since 4.0.0
1380
+ */
1381
+ export interface NoData {
1382
+ readonly _tag: "NoData"
1383
+ }
1384
+
1385
+ /**
1386
+ * A `Parse` succeeded.
1387
+ *
1388
+ * @category models
1389
+ * @since 4.0.0
1390
+ */
1391
+ export interface ParseComplete {
1392
+ readonly _tag: "ParseComplete"
1393
+ }
1394
+
1395
+ /**
1396
+ * A `Bind` succeeded.
1397
+ *
1398
+ * @category models
1399
+ * @since 4.0.0
1400
+ */
1401
+ export interface BindComplete {
1402
+ readonly _tag: "BindComplete"
1403
+ }
1404
+
1405
+ /**
1406
+ * A `Close` succeeded.
1407
+ *
1408
+ * @category models
1409
+ * @since 4.0.0
1410
+ */
1411
+ export interface CloseComplete {
1412
+ readonly _tag: "CloseComplete"
1413
+ }
1414
+
1415
+ /**
1416
+ * An `Execute` stopped at its row limit; the portal can be executed again.
1417
+ *
1418
+ * @category models
1419
+ * @since 4.0.0
1420
+ */
1421
+ export interface PortalSuspended {
1422
+ readonly _tag: "PortalSuspended"
1423
+ }
1424
+
1425
+ /**
1426
+ * The parameter OIDs of a described statement.
1427
+ *
1428
+ * @category models
1429
+ * @since 4.0.0
1430
+ */
1431
+ export interface ParameterDescription {
1432
+ readonly _tag: "ParameterDescription"
1433
+ readonly parameterTypes: ReadonlyArray<number>
1434
+ }
1435
+
1436
+ /**
1437
+ * The fields of an `ErrorResponse` or `NoticeResponse`. Unrecognised field
1438
+ * codes are kept under their raw single-character key.
1439
+ *
1440
+ * @category models
1441
+ * @since 4.0.0
1442
+ */
1443
+ export interface ErrorFields {
1444
+ readonly [key: string]: string | undefined
1445
+ readonly severity?: string | undefined
1446
+ readonly severityUnlocalized?: string | undefined
1447
+ readonly code?: string | undefined
1448
+ readonly message?: string | undefined
1449
+ readonly detail?: string | undefined
1450
+ readonly hint?: string | undefined
1451
+ readonly position?: string | undefined
1452
+ readonly internalPosition?: string | undefined
1453
+ readonly internalQuery?: string | undefined
1454
+ readonly where?: string | undefined
1455
+ readonly schema?: string | undefined
1456
+ readonly table?: string | undefined
1457
+ readonly column?: string | undefined
1458
+ readonly dataType?: string | undefined
1459
+ readonly constraint?: string | undefined
1460
+ readonly file?: string | undefined
1461
+ readonly line?: string | undefined
1462
+ readonly routine?: string | undefined
1463
+ }
1464
+
1465
+ /**
1466
+ * An error. `code` is the SQLSTATE.
1467
+ *
1468
+ * @category models
1469
+ * @since 4.0.0
1470
+ */
1471
+ export interface ErrorResponse {
1472
+ readonly _tag: "ErrorResponse"
1473
+ readonly fields: ErrorFields
1474
+ }
1475
+
1476
+ /**
1477
+ * A warning or notice. Same field set as `ErrorResponse`.
1478
+ *
1479
+ * @category models
1480
+ * @since 4.0.0
1481
+ */
1482
+ export interface NoticeResponse {
1483
+ readonly _tag: "NoticeResponse"
1484
+ readonly fields: ErrorFields
1485
+ }
1486
+
1487
+ /**
1488
+ * A `LISTEN`/`NOTIFY` message.
1489
+ *
1490
+ * @category models
1491
+ * @since 4.0.0
1492
+ */
1493
+ export interface NotificationResponse {
1494
+ readonly _tag: "NotificationResponse"
1495
+ readonly pid: number
1496
+ readonly channel: string
1497
+ readonly payload: string
1498
+ }
1499
+
1500
+ /**
1501
+ * The server speaks an older minor protocol version, or did not recognise
1502
+ * some startup options.
1503
+ *
1504
+ * @category models
1505
+ * @since 4.0.0
1506
+ */
1507
+ export interface NegotiateProtocolVersion {
1508
+ readonly _tag: "NegotiateProtocolVersion"
1509
+ readonly minorVersion: number
1510
+ readonly unrecognizedOptions: ReadonlyArray<string>
1511
+ }
1512
+
1513
+ /**
1514
+ * The server is ready to receive `COPY` data.
1515
+ *
1516
+ * @category models
1517
+ * @since 4.0.0
1518
+ */
1519
+ export interface CopyInResponse {
1520
+ readonly _tag: "CopyInResponse"
1521
+ readonly format: number
1522
+ readonly columnFormats: ReadonlyArray<number>
1523
+ }
1524
+
1525
+ /**
1526
+ * The server is about to send `COPY` data.
1527
+ *
1528
+ * @category models
1529
+ * @since 4.0.0
1530
+ */
1531
+ export interface CopyOutResponse {
1532
+ readonly _tag: "CopyOutResponse"
1533
+ readonly format: number
1534
+ readonly columnFormats: ReadonlyArray<number>
1535
+ }
1536
+
1537
+ /**
1538
+ * The connection entered bidirectional `COPY` mode, as used by replication.
1539
+ *
1540
+ * @category models
1541
+ * @since 4.0.0
1542
+ */
1543
+ export interface CopyBothResponse {
1544
+ readonly _tag: "CopyBothResponse"
1545
+ readonly format: number
1546
+ readonly columnFormats: ReadonlyArray<number>
1547
+ }
1548
+
1549
+ /**
1550
+ * A chunk of `COPY` data.
1551
+ *
1552
+ * @category models
1553
+ * @since 4.0.0
1554
+ */
1555
+ export interface CopyData {
1556
+ readonly _tag: "CopyData"
1557
+ readonly data: Uint8Array
1558
+ }
1559
+
1560
+ /**
1561
+ * The `COPY` stream ended.
1562
+ *
1563
+ * @category models
1564
+ * @since 4.0.0
1565
+ */
1566
+ export interface CopyDone {
1567
+ readonly _tag: "CopyDone"
1568
+ }
1569
+
1570
+ /**
1571
+ * A message whose type byte this codec does not know. The payload excludes
1572
+ * the type byte and the length prefix.
1573
+ *
1574
+ * @category models
1575
+ * @since 4.0.0
1576
+ */
1577
+ export interface Unknown {
1578
+ readonly _tag: "Unknown"
1579
+ readonly type: number
1580
+ readonly payload: Uint8Array
1581
+ }
1582
+
1583
+ /**
1584
+ * Any message the server sends after startup.
1585
+ *
1586
+ * @category models
1587
+ * @since 4.0.0
1588
+ */
1589
+ export type BackendMessage<A = Uint8Array | null> =
1590
+ | AuthenticationOk
1591
+ | AuthenticationCleartextPassword
1592
+ | AuthenticationMD5Password
1593
+ | AuthenticationSASL
1594
+ | AuthenticationSASLContinue
1595
+ | AuthenticationSASLFinal
1596
+ | AuthenticationUnsupported
1597
+ | ParameterStatus
1598
+ | BackendKeyData
1599
+ | ReadyForQuery
1600
+ | RowDescription
1601
+ | DataRow<A>
1602
+ | CommandComplete
1603
+ | EmptyQueryResponse
1604
+ | NoData
1605
+ | ParseComplete
1606
+ | BindComplete
1607
+ | CloseComplete
1608
+ | PortalSuspended
1609
+ | ParameterDescription
1610
+ | ErrorResponse
1611
+ | NoticeResponse
1612
+ | NotificationResponse
1613
+ | NegotiateProtocolVersion
1614
+ | CopyInResponse
1615
+ | CopyOutResponse
1616
+ | CopyBothResponse
1617
+ | CopyData
1618
+ | CopyDone
1619
+ | Unknown
1620
+
1621
+ const BackendType = {
1622
+ NotificationResponse: 0x41, // A
1623
+ CommandComplete: 0x43, // C
1624
+ DataRow: 0x44, // D
1625
+ ErrorResponse: 0x45, // E
1626
+ CopyInResponse: 0x47, // G
1627
+ CopyOutResponse: 0x48, // H
1628
+ EmptyQueryResponse: 0x49, // I
1629
+ BackendKeyData: 0x4b, // K
1630
+ NoticeResponse: 0x4e, // N
1631
+ Authentication: 0x52, // R
1632
+ ParameterStatus: 0x53, // S
1633
+ RowDescription: 0x54, // T
1634
+ CopyBothResponse: 0x57, // W
1635
+ ReadyForQuery: 0x5a, // Z
1636
+ CopyDone: 0x63, // c
1637
+ CopyData: 0x64, // d
1638
+ NoData: 0x6e, // n
1639
+ PortalSuspended: 0x73, // s
1640
+ ParameterDescription: 0x74, // t
1641
+ NegotiateProtocolVersion: 0x76, // v
1642
+ ParseComplete: 0x31, // 1
1643
+ BindComplete: 0x32, // 2
1644
+ CloseComplete: 0x33 // 3
1645
+ } as const
1646
+
1647
+ const errorFieldNames: Record<string, string> = {
1648
+ S: "severity",
1649
+ V: "severityUnlocalized",
1650
+ C: "code",
1651
+ M: "message",
1652
+ D: "detail",
1653
+ H: "hint",
1654
+ P: "position",
1655
+ p: "internalPosition",
1656
+ q: "internalQuery",
1657
+ W: "where",
1658
+ s: "schema",
1659
+ t: "table",
1660
+ c: "column",
1661
+ d: "dataType",
1662
+ n: "constraint",
1663
+ F: "file",
1664
+ L: "line",
1665
+ R: "routine"
1666
+ }
1667
+
1668
+ const decodeAuthentication = (reader: Reader): BackendMessage => {
1669
+ const method = reader.int32()
1670
+ switch (method) {
1671
+ case 0:
1672
+ return { _tag: "AuthenticationOk" }
1673
+ case 3:
1674
+ return { _tag: "AuthenticationCleartextPassword" }
1675
+ case 5:
1676
+ return { _tag: "AuthenticationMD5Password", salt: reader.raw(4) }
1677
+ case 10: {
1678
+ const mechanisms: Array<string> = []
1679
+ for (;;) {
1680
+ const mechanism = reader.cString()
1681
+ if (mechanism === "") break
1682
+ mechanisms.push(mechanism)
1683
+ }
1684
+ return { _tag: "AuthenticationSASL", mechanisms }
1685
+ }
1686
+ case 11:
1687
+ return { _tag: "AuthenticationSASLContinue", data: reader.rest() }
1688
+ case 12:
1689
+ return { _tag: "AuthenticationSASLFinal", data: reader.rest() }
1690
+ default:
1691
+ return { _tag: "AuthenticationUnsupported", method, payload: reader.rest() }
1692
+ }
1693
+ }
1694
+
1695
+ const decodeErrorFields = (reader: Reader): ErrorFields => {
1696
+ const fields: Record<string, string> = {}
1697
+ for (;;) {
1698
+ const code = reader.uint8()
1699
+ if (code === 0) break
1700
+ const key = String.fromCharCode(code)
1701
+ fields[errorFieldNames[key] ?? key] = reader.cString()
1702
+ }
1703
+ return fields
1704
+ }
1705
+
1706
+ const requireNonNegativeCount = (count: number, name: string): number => {
1707
+ if (count < 0) {
1708
+ throw new ParseError({ message: `Invalid ${name} count: ${count}` })
1709
+ }
1710
+ return count
1711
+ }
1712
+
1713
+ const decodeCopyResponse = (
1714
+ reader: Reader
1715
+ ): { readonly format: number; readonly columnFormats: ReadonlyArray<number> } => {
1716
+ const format = reader.uint8()
1717
+ const count = requireNonNegativeCount(reader.int16(), "COPY column")
1718
+ const columnFormats: Array<number> = new Array(count)
1719
+ for (let i = 0; i < count; i++) {
1720
+ columnFormats[i] = reader.int16()
1721
+ }
1722
+ return { format, columnFormats }
1723
+ }
1724
+
1725
+ // The one message that arrives per result row, so it reads the frame directly
1726
+ // instead of going through `Reader`. Callers have already checked that the
1727
+ // whole frame is buffered, which turns every field read into one bounds check
1728
+ // against `limit`.
1729
+ const decodeDataRow = <A>(
1730
+ bytes: Uint8Array,
1731
+ store: ArrayBufferLike,
1732
+ base: number,
1733
+ offset: number,
1734
+ limit: number,
1735
+ readField: FieldReader<A> | undefined
1736
+ ): DataRow<A> => {
1737
+ if (offset + 2 > limit) {
1738
+ throw new ParseError({ message: "Truncated message: expected 2 more byte(s)" })
1739
+ }
1740
+ const count = ((bytes[offset] << 8) | bytes[offset + 1]) << 16 >> 16
1741
+ if (count < 0) {
1742
+ throw new ParseError({ message: `Invalid DataRow field count: ${count}` })
1743
+ }
1744
+ const values: Array<any> = new Array(count)
1745
+ let position = offset + 2
1746
+ for (let i = 0; i < count; i++) {
1747
+ if (position + 4 > limit) {
1748
+ throw new ParseError({ message: "Truncated message: expected 4 more byte(s)" })
1749
+ }
1750
+ const size = (bytes[position] << 24) | (bytes[position + 1] << 16) | (bytes[position + 2] << 8) |
1751
+ bytes[position + 3]
1752
+ position += 4
1753
+ if (size < 0) {
1754
+ if (size < -1) {
1755
+ throw new ParseError({ message: `Invalid DataRow field length: ${size}` })
1756
+ }
1757
+ values[i] = readField === undefined ? null : readField(bytes, position, -1, i)
1758
+ continue
1759
+ }
1760
+ const next = position + size
1761
+ if (next > limit) {
1762
+ throw new ParseError({ message: `Truncated message: expected ${size} more byte(s)` })
1763
+ }
1764
+ values[i] = readField === undefined
1765
+ ? view(store, base + position, size)
1766
+ : readField(bytes, position, size, i)
1767
+ position = next
1768
+ }
1769
+ if (position !== limit) {
1770
+ throw new ParseError({ message: `DataRow has ${limit - position} trailing byte(s)` })
1771
+ }
1772
+ return { _tag: "DataRow", values }
1773
+ }
1774
+
1775
+ const decodeBackend = (type: number, reader: Reader): BackendMessage => {
1776
+ switch (type) {
1777
+ case BackendType.Authentication:
1778
+ return decodeAuthentication(reader)
1779
+ case BackendType.ParameterStatus:
1780
+ return { _tag: "ParameterStatus", name: reader.cString(), value: reader.cString() }
1781
+ case BackendType.BackendKeyData:
1782
+ return { _tag: "BackendKeyData", pid: reader.int32(), secret: reader.int32() }
1783
+ case BackendType.ReadyForQuery: {
1784
+ const status = String.fromCharCode(reader.uint8())
1785
+ if (status !== "I" && status !== "T" && status !== "E") {
1786
+ throw new ParseError({ message: `Invalid ReadyForQuery status: ${status}` })
1787
+ }
1788
+ return { _tag: "ReadyForQuery", status }
1789
+ }
1790
+ case BackendType.RowDescription: {
1791
+ const count = requireNonNegativeCount(reader.int16(), "RowDescription field")
1792
+ const fields: Array<FieldDescription> = new Array(count)
1793
+ for (let i = 0; i < count; i++) {
1794
+ fields[i] = {
1795
+ name: reader.cString(),
1796
+ tableOid: reader.uint32(),
1797
+ columnAttributeNumber: reader.int16(),
1798
+ dataTypeOid: reader.uint32(),
1799
+ dataTypeSize: reader.int16(),
1800
+ typeModifier: reader.int32(),
1801
+ format: reader.int16()
1802
+ }
1803
+ }
1804
+ return { _tag: "RowDescription", fields }
1805
+ }
1806
+ case BackendType.DataRow:
1807
+ return decodeDataRow(reader.bytes, reader.store, reader.base, reader.offset, reader.limit, undefined)
1808
+ case BackendType.CommandComplete:
1809
+ return { _tag: "CommandComplete", commandTag: reader.cString() }
1810
+ case BackendType.EmptyQueryResponse:
1811
+ return { _tag: "EmptyQueryResponse" }
1812
+ case BackendType.NoData:
1813
+ return { _tag: "NoData" }
1814
+ case BackendType.ParseComplete:
1815
+ return { _tag: "ParseComplete" }
1816
+ case BackendType.BindComplete:
1817
+ return { _tag: "BindComplete" }
1818
+ case BackendType.CloseComplete:
1819
+ return { _tag: "CloseComplete" }
1820
+ case BackendType.PortalSuspended:
1821
+ return { _tag: "PortalSuspended" }
1822
+ case BackendType.ParameterDescription: {
1823
+ const count = requireNonNegativeCount(reader.int16(), "ParameterDescription parameter")
1824
+ const parameterTypes: Array<number> = new Array(count)
1825
+ for (let i = 0; i < count; i++) {
1826
+ parameterTypes[i] = reader.uint32()
1827
+ }
1828
+ return { _tag: "ParameterDescription", parameterTypes }
1829
+ }
1830
+ case BackendType.ErrorResponse:
1831
+ return { _tag: "ErrorResponse", fields: decodeErrorFields(reader) }
1832
+ case BackendType.NoticeResponse:
1833
+ return { _tag: "NoticeResponse", fields: decodeErrorFields(reader) }
1834
+ case BackendType.NotificationResponse:
1835
+ return {
1836
+ _tag: "NotificationResponse",
1837
+ pid: reader.int32(),
1838
+ channel: reader.cString(),
1839
+ payload: reader.cString()
1840
+ }
1841
+ case BackendType.NegotiateProtocolVersion: {
1842
+ const minorVersion = reader.int32()
1843
+ const count = requireNonNegativeCount(reader.int32(), "NegotiateProtocolVersion option")
1844
+ const unrecognizedOptions: Array<string> = new Array(count)
1845
+ for (let i = 0; i < count; i++) {
1846
+ unrecognizedOptions[i] = reader.cString()
1847
+ }
1848
+ return { _tag: "NegotiateProtocolVersion", minorVersion, unrecognizedOptions }
1849
+ }
1850
+ case BackendType.CopyInResponse:
1851
+ return { _tag: "CopyInResponse", ...decodeCopyResponse(reader) }
1852
+ case BackendType.CopyOutResponse:
1853
+ return { _tag: "CopyOutResponse", ...decodeCopyResponse(reader) }
1854
+ case BackendType.CopyBothResponse:
1855
+ return { _tag: "CopyBothResponse", ...decodeCopyResponse(reader) }
1856
+ case BackendType.CopyData:
1857
+ return { _tag: "CopyData", data: reader.rest() }
1858
+ case BackendType.CopyDone:
1859
+ return { _tag: "CopyDone" }
1860
+ default:
1861
+ return { _tag: "Unknown", type, payload: reader.rest() }
1862
+ }
1863
+ }