@amritk/nish-x86_64-linux 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +45 -4
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/host.d.ts +12 -0
  6. package/runtime/core/host.mjs +74 -2
  7. package/runtime/nish.d.ts +148 -0
  8. package/runtime/nish.h +59 -0
  9. package/runtime/nish.mjs +25 -0
  10. package/runtime/node.mjs +136 -6
  11. package/runtime/runtime-net.c +44 -3
  12. package/runtime/runtime-os.c +351 -18
  13. package/scripts/build.sh +13 -3
  14. package/std/README.md +27 -10
  15. package/std/cli-args.ts +1216 -0
  16. package/std/cli-flags.ts +585 -0
  17. package/std/cli.ts +308 -19
  18. package/std/compress/checksum.ts +132 -20
  19. package/std/compress/deflate.ts +638 -248
  20. package/std/compress/gzip.ts +20 -6
  21. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  22. package/std/crypto/p256.ts +63 -1
  23. package/std/crypto/p384.ts +6044 -0
  24. package/std/crypto/pki-roots.ts +322 -0
  25. package/std/crypto/pki.ts +1207 -0
  26. package/std/crypto/rsa.ts +663 -0
  27. package/std/crypto/x509.ts +1054 -58
  28. package/std/net/dns-config.ts +734 -0
  29. package/std/net/dns-message.ts +517 -0
  30. package/std/net/dns-resolver.ts +1015 -0
  31. package/std/net/dns.ts +478 -0
  32. package/std/net/http1-client.ts +1227 -0
  33. package/std/net/http1-server.ts +7 -2
  34. package/std/net/http1.ts +9 -9
  35. package/std/net/postgres.ts +259 -26
  36. package/std/net/tls/client.ts +1094 -0
  37. package/std/net/tls/codec.ts +585 -3
  38. package/std/net/tls/record-client.ts +1192 -0
  39. package/std/net/tls/verify.ts +221 -0
  40. package/std/net/tls-tcp.ts +642 -11
  41. package/std/net/tls.ts +2 -2
  42. package/std/net/url.ts +559 -4
  43. package/std/yaml-write.ts +415 -0
@@ -1272,7 +1272,10 @@ export class Http1Connection {
1272
1272
  enc.identityFields(names, values)
1273
1273
  }
1274
1274
  // `respond` and the body together, or neither: what `respond` sets is
1275
- // put back when the body does not fit behind the head.
1275
+ // put back when the body does not fit behind the head. `room` first, so
1276
+ // that what was sent is already moved out of the way and the `outputEnd`
1277
+ // put back is one `respond` did not move.
1278
+ this.room()
1276
1279
  const outputEnd: i32 = this.outputEnd
1277
1280
  const closeAfter: boolean = this.closeAfter
1278
1281
  const r: i32 = this.respond(status, enc.names, enc.values, n)
@@ -1954,7 +1957,9 @@ export class Http1TlsServer {
1954
1957
  readable(slot: i32): i32 {
1955
1958
  const live: boolean = this.live(slot)
1956
1959
  const now: i64 = monotonicNanos()
1957
- if (live) {
1960
+ // What a lingering slot drains is not activity, so `idleTimeout` bounds
1961
+ // how long a client that trickles bytes after the close can hold it.
1962
+ if (live && !this.tls.lingering(slot)) {
1958
1963
  this.connections[slot].lastActive = now
1959
1964
  }
1960
1965
  this.tls.readable(slot)
package/std/net/http1.ts CHANGED
@@ -524,7 +524,7 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
524
524
  }
525
525
 
526
526
  /** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
527
- const http1IsTokenByte = (c: i32): boolean =>
527
+ export const http1IsTokenByte = (c: i32): boolean =>
528
528
  (c >= 97 && c <= 122) ||
529
529
  (c >= 65 && c <= 90) ||
530
530
  (c >= 48 && c <= 57) ||
@@ -541,10 +541,10 @@ const http1IsTokenByte = (c: i32): boolean =>
541
541
  c === 126
542
542
 
543
543
  /** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
544
- const http1IsValueByte = (c: i32): boolean => c === 9 || (c >= 32 && c !== 127)
544
+ export const http1IsValueByte = (c: i32): boolean => c === 9 || (c >= 32 && c !== 127)
545
545
 
546
546
  /** `buf[from .. to)` as a string, each upper-case ASCII letter lowered when `lower`. */
547
- const http1Text = (buf: u8[], from: i32, to: i32, lower: boolean): string => {
547
+ export const http1Text = (buf: u8[], from: i32, to: i32, lower: boolean): string => {
548
548
  const parts: string[] = []
549
549
  for (let k: i32 = from; k >= 0 && k < to && k < toI32(buf.length); k += 1) {
550
550
  let c: i32 = toI32(buf[k])
@@ -557,10 +557,10 @@ const http1Text = (buf: u8[], from: i32, to: i32, lower: boolean): string => {
557
557
  }
558
558
 
559
559
  /** Whether `c` is optional whitespace, a space or a tab (OWS, RFC 9110 §5.6.3). */
560
- const http1IsOws = (c: i32): boolean => c === 32 || c === 9
560
+ export const http1IsOws = (c: i32): boolean => c === 32 || c === 9
561
561
 
562
562
  /** Whether `text[a .. b)` is `word`, which is lowercase, in any ASCII case. */
563
- const http1SameWord = (text: string, a: i32, b: i32, word: string): boolean => {
563
+ export const http1SameWord = (text: string, a: i32, b: i32, word: string): boolean => {
564
564
  const n: i32 = toI32(word.length)
565
565
  if (a < 0 || b - a !== n || b > toI32(text.length)) {
566
566
  return false
@@ -989,7 +989,7 @@ const http1ContentLength = (bytes: u8[], from: i32, to: i32, limit: i32): i32 =>
989
989
  }
990
990
 
991
991
  /** The value of the hex digit `c`, or -1. */
992
- const http1HexValue = (c: i32): i32 => {
992
+ export const http1HexValue = (c: i32): i32 => {
993
993
  if (c >= 48 && c <= 57) {
994
994
  return c - 48
995
995
  }
@@ -1190,7 +1190,7 @@ const http1BytesOf = (text: string): u8[] => {
1190
1190
  }
1191
1191
 
1192
1192
  /** Whether every byte of `text` is a tchar, and there is at least one. */
1193
- const http1IsToken = (text: string): boolean => {
1193
+ export const http1IsToken = (text: string): boolean => {
1194
1194
  const n: i32 = toI32(text.length)
1195
1195
  if (n === 0) {
1196
1196
  return false
@@ -1204,7 +1204,7 @@ const http1IsToken = (text: string): boolean => {
1204
1204
  }
1205
1205
 
1206
1206
  /** Whether every byte of `text` may stand in a field value or a reason phrase. */
1207
- const http1IsFieldText = (text: string): boolean => {
1207
+ export const http1IsFieldText = (text: string): boolean => {
1208
1208
  const n: i32 = toI32(text.length)
1209
1209
  for (let k: i32 = 0; k < n; k += 1) {
1210
1210
  if (!http1IsValueByte(toI32(text.charCodeAt(k)))) {
@@ -1395,7 +1395,7 @@ const http1DecimalDigits = (n: i32): i32 => {
1395
1395
  }
1396
1396
 
1397
1397
  /** Writes `text` at `out[at ..)`, which the caller has made sure holds it, and answers where it ends. */
1398
- const http1PutText = (out: u8[], at: i32, text: string): i32 => {
1398
+ export const http1PutText = (out: u8[], at: i32, text: string): i32 => {
1399
1399
  const n: i32 = toI32(text.length)
1400
1400
  for (let k: i32 = 0; k < n && at + k < toI32(out.length); k += 1) {
1401
1401
  if (at + k >= 0) {
@@ -17,7 +17,9 @@
17
17
  * // PG_READY: authenticated; prepare statements and send queries
18
18
  * // conn.prepare("by_id", "select id, name from users where id = $1", [23]); conn.sync()
19
19
  * // conn.bindStart("by_id"); conn.paramInt(7); conn.bindEnd(); conn.sync()
20
+ * // or conn.bindStartBinary("by_id") for binary result columns
20
21
  * // PG_ROW: conn.int(0), conn.colStart(1) .. + conn.colLength(1) in conn.inputBuffer()
22
+ * // after bindStartBinary: conn.binaryInt(0), the text column as before
21
23
  * // PG_COMPLETE: conn.tagRows(); PG_ERROR: conn.lastError.code
22
24
  * // PG_GROUP_DONE: one sync()'s group has been answered
23
25
  * event = conn.next();
@@ -57,18 +59,29 @@
57
59
  * does not need: a program prepares a statement and executes it in the same
58
60
  * flight.
59
61
  *
60
- * **Text results, no copies.** Every parameter and every result column is in
61
- * text format. A row is read where it lies in the input buffer: `next()` frames
62
- * each message once, validates its layout, and records each column's start and
63
- * length in two reused arrays, so a row costs no allocation. `int`, `bool`,
64
- * `isNull` and `PgText.timestamp` decode a column in place; `PgArrayWalker`
65
- * walks a one-dimensional `text[]` in place. `columnText(c)` is the one that
66
- * allocates, a string. An array parameter is a text array literal too:
67
- * `paramIntArray` writes an `int8[]` from an `i64[]` window, and `arrayStart`,
68
- * `arrayElement`, `arrayInt`, `arrayNull` and `arrayEnd` build an array one
69
- * element at a time into the Bind, so a list needs no string built first. A
70
- * program whose element was refused for room can drop the whole bind,
71
- * unsent, with `bindCancel`.
62
+ * **Text results, no copies.** Every parameter is in text format, and so is
63
+ * every result column unless the bind asks otherwise. A row is read where it
64
+ * lies in the input buffer: `next()` frames each message once, validates its
65
+ * layout, and records each column's start and length in two reused arrays, so
66
+ * a row costs no allocation. `int`, `bool`, `isNull` and `PgText.timestamp`
67
+ * decode a column in place; `PgArrayWalker` walks a one-dimensional `text[]`
68
+ * in place. `columnText(c)` is the one that allocates, a string. An array
69
+ * parameter is a text array literal too: `paramIntArray` writes an `int8[]`
70
+ * from an `i64[]` window, and `arrayStart`, `arrayElement`, `arrayInt`,
71
+ * `arrayNull` and `arrayEnd` build an array one element at a time into the
72
+ * Bind, so a list needs no string built first. A program whose element was
73
+ * refused for room can drop the whole bind, unsent, with `bindCancel`.
74
+ *
75
+ * **Binary results.** A bind opened with `bindStartBinary` instead of
76
+ * `bindStart` asks for every result column in binary format: an `int8` is
77
+ * eight bytes, a `timestamptz` eight bytes of microseconds since 2000, a
78
+ * `bool` one byte, so there is nothing to parse and less on the wire.
79
+ * `binaryInt`, `binaryBool` and `binaryFloat` read them in place, and
80
+ * `PgBinary`'s `timestamp` and `iso` write a time as `PgText.timestamp` does.
81
+ * A `text` column is the same bytes in either format, so `columnText` and the
82
+ * column windows read it unchanged. The program knows which of its binds
83
+ * asked for which, and reads each execution's rows with the matching
84
+ * decoders.
72
85
  *
73
86
  * **Authentication.** SCRAM-SHA-256 (RFC 5802, RFC 7677) or none (`trust`).
74
87
  * The password arrives as a `Secret<u8[]>`; the connection keeps a copy in a
@@ -100,8 +113,9 @@
100
113
  * that needs a socket, `connect(slot, nonce)` starts its connection, and the
101
114
  * program moves its bytes as above through `connection(slot)`. A request is a
102
115
  * ticket from `request()`; `dispatch()` gives the oldest to the ready
103
- * connection with the fewest groups in flight, the program builds its group and
104
- * ends it with `send()`, and `next(slot)` reads that connection's events with
116
+ * connection with the fewest groups in flight (or, with `PgConfig.pack`, to the
117
+ * first ready one with room, so a burst fills few connections in fuller
118
+ * writes), the program builds its group and ends it with `send()`, and `next(slot)` reads that connection's events with
105
119
  * `ticket` naming the request. Statements given to `register` are prepared on
106
120
  * each connection ahead of its first request. A connection that fails reports
107
121
  * `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
@@ -127,7 +141,7 @@
127
141
  * Written from PostgreSQL's "Frontend/Backend Protocol" chapter (version 18)
128
142
  * and RFC 5802 and RFC 7677; not ported from another implementation.
129
143
  */
130
- import { copyBytes, getI32BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
144
+ import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
131
145
  import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
132
146
  import { timingSafeEqual } from "nish/crypto/ct"
133
147
  import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
@@ -218,6 +232,11 @@ const PG_NONCE_BYTES: i32 = 18
218
232
  // take: kept free under `maxOutput` by every other builder call, so that
219
233
  // `bindEnd()` and `sync()` are never refused for room.
220
234
  const PG_TAIL: i32 = 17
235
+ // What a binary bind's end takes beyond `PG_TAIL`: its one result format code,
236
+ // kept free from `bindStartBinary` on.
237
+ const PG_BINARY_TAIL: i32 = 2
238
+ // The result format code for binary.
239
+ const PG_FORMAT_BINARY: i32 = 1
221
240
  // The largest `maxMessage` and `maxOutput`. The input buffer doubles up to one
222
241
  // past them; the output buffer doubles only while twice its size fits in an i32.
223
242
  const PG_MAX_CAP: i32 = 1073741823
@@ -238,6 +257,15 @@ export class PgConfig {
238
257
  maxMessage: i32 = 1048576
239
258
  /** The most groups waiting for their answers at once. */
240
259
  maxInFlight: i32 = 16
260
+ /**
261
+ * Read by `PgPool` only: whether `dispatch()` packs requests onto the
262
+ * first ready connection with room, filling it to `maxInFlight` before the
263
+ * next, rather than spreading them over every connection, fewest in flight
264
+ * first. Fewer connections in use send fuller writes, fewer packets, which
265
+ * pays on a lossy link to the server; a connection is still wanted only once
266
+ * every ready one is full.
267
+ */
268
+ pack: boolean = false
241
269
  /** The input buffer's first size, in bytes; it grows to a message's length, up to `maxMessage` + 1. */
242
270
  inputSize: i32 = 8192
243
271
  /** The widest row; past it, `PG_ERR_TOO_BIG`. PostgreSQL's own limit is 1664. */
@@ -677,6 +705,159 @@ export class PgText {
677
705
  }
678
706
  }
679
707
 
708
+ // The days from 0000-03-01, where the civil calendar's year starts for the
709
+ // conversion below, to 2000-01-01, where PostgreSQL's binary times count from.
710
+ const PG_EPOCH_DAYS: i64 = 730425
711
+ // The first and last days `PgBinary.iso` writes, counted from 2000-01-01:
712
+ // 0001-01-01 and 9999-12-31, the years four digits spell.
713
+ const PG_FIRST_DAY: i64 = -730119
714
+ const PG_LAST_DAY: i64 = 2921939
715
+
716
+ /** `value`'s last `width` decimal digits into `out[at, at + width)`. */
717
+ const pgDigits = (out: u8[], at: i32, value: i32, width: i32): void => {
718
+ let v: i32 = value
719
+ for (let i: i32 = width - 1; i >= 0; i--) {
720
+ out[at + i] = toU8((v % 10) + 48)
721
+ v = v / 10
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Decoders for PostgreSQL's binary format, in place, for the rows of a bind
727
+ * that `bindStartBinary` opened; a bad input sets `failed`, which stays set.
728
+ * A `text` or `varchar` column's bytes are the same in either format, so
729
+ * `columnText`, `colStart` and `colLength` read it as they read a text one.
730
+ */
731
+ export class PgBinary {
732
+ /** Whether a decode since the last `reset()` was given something it could not read. */
733
+ failed: boolean = false
734
+
735
+ /** Clears `failed`. */
736
+ reset(): void {
737
+ this.failed = false
738
+ }
739
+
740
+ // Whether `[start, start + length)` is inside `b`; sets `failed` when not.
741
+ private inside(b: u8[], start: i32, length: i32): boolean {
742
+ if (start < 0 || length < 0 || start > toI32(b.length) - length) {
743
+ this.failed = true
744
+ return false
745
+ }
746
+ return true
747
+ }
748
+
749
+ /**
750
+ * An `int2`, `int4` or `int8` column, told apart by its length (2, 4 or 8
751
+ * bytes, big-endian two's complement); 0 and `failed` for any other length,
752
+ * NULL's -1 among them. A `timestamptz` or `timestamp` reads as an `int8`:
753
+ * microseconds since 2000-01-01 00:00:00 UTC, which `iso` writes out.
754
+ */
755
+ int(b: u8[], start: i32, length: i32): i64 {
756
+ if ((length !== 2 && length !== 4 && length !== 8) || !this.inside(b, start, length)) {
757
+ this.failed = true
758
+ return 0
759
+ }
760
+ if (length === 8) {
761
+ return getI64BE(b, start)
762
+ }
763
+ if (length === 4) {
764
+ return toI64(getI32BE(b, start))
765
+ }
766
+ const v: i32 = toI32(getU16BE(b, start))
767
+ return toI64(v >= 32768 ? v - 65536 : v)
768
+ }
769
+
770
+ /** A `bool` column: one byte, 1 or 0; false and `failed` otherwise. */
771
+ bool(b: u8[], start: i32, length: i32): boolean {
772
+ if (length === 1 && this.inside(b, start, length)) {
773
+ const c: i32 = toI32(b[start])
774
+ if (c === 1) {
775
+ return true
776
+ }
777
+ if (c === 0) {
778
+ return false
779
+ }
780
+ }
781
+ this.failed = true
782
+ return false
783
+ }
784
+
785
+ /** A `float8` column: eight bytes, IEEE 754 big-endian; 0 and `failed` otherwise. */
786
+ float(b: u8[], start: i32, length: i32): f64 {
787
+ if (length !== 8 || !this.inside(b, start, length)) {
788
+ this.failed = true
789
+ return 0
790
+ }
791
+ return getF64BE(b, start)
792
+ }
793
+
794
+ /**
795
+ * A `timestamptz` or `timestamp` column, written to `out[at, at + 24)` as
796
+ * `iso` writes it. Answers 24, or -1 and `failed` for a column that is not
797
+ * eight bytes or a time `iso` refuses.
798
+ */
799
+ timestamp(b: u8[], start: i32, length: i32, out: u8[], at: i32): i32 {
800
+ if (length !== 8 || !this.inside(b, start, length)) {
801
+ this.failed = true
802
+ return -1
803
+ }
804
+ return this.iso(getI64BE(b, start), out, at)
805
+ }
806
+
807
+ /**
808
+ * `micros`, microseconds since 2000-01-01 00:00:00 UTC, written to
809
+ * `out[at, at + 24)` as `2024-01-02T03:04:05.123Z`: the microseconds cut,
810
+ * not rounded, to milliseconds, as `PgText.timestamp` cuts the same time.
811
+ * Answers 24, or -1 and `failed` with `out` untouched for a year outside
812
+ * 0001–9999 (`infinity` and `-infinity` among them) or no room in `out`.
813
+ */
814
+ iso(micros: i64, out: u8[], at: i32): i32 {
815
+ if (at < 0 || at > toI32(out.length) - 24) {
816
+ this.failed = true
817
+ return -1
818
+ }
819
+ // Floored, so that a time before 2000 keeps its day and is cut toward the past.
820
+ const dayMicros: i64 = toI64(86400000) * 1000
821
+ let days: i64 = micros / dayMicros
822
+ let rest: i64 = micros % dayMicros
823
+ if (rest < 0) {
824
+ days -= 1
825
+ rest += dayMicros
826
+ }
827
+ if (days < PG_FIRST_DAY || days > PG_LAST_DAY) {
828
+ this.failed = true
829
+ return -1
830
+ }
831
+ // The civil date of a day count: eras of 400 years (146097 days), each
832
+ // year starting on 1 March so that a leap day is its last.
833
+ const z: i64 = days + PG_EPOCH_DAYS
834
+ const era: i64 = z / 146097
835
+ const doe: i64 = z - era * 146097
836
+ const yoe: i64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
837
+ const doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100)
838
+ const mp: i64 = (5 * doy + 2) / 153
839
+ const day: i64 = doy - (153 * mp + 2) / 5 + 1
840
+ const month: i64 = mp < 10 ? mp + 3 : mp - 9
841
+ const year: i64 = yoe + era * 400 + (month <= 2 ? 1 : 0)
842
+ const millis: i32 = toI32(rest / 1000)
843
+ pgDigits(out, at, toI32(year), 4)
844
+ out[at + 4] = toU8(45)
845
+ pgDigits(out, at + 5, toI32(month), 2)
846
+ out[at + 7] = toU8(45)
847
+ pgDigits(out, at + 8, toI32(day), 2)
848
+ out[at + 10] = toU8(84)
849
+ pgDigits(out, at + 11, millis / 3600000, 2)
850
+ out[at + 13] = toU8(58)
851
+ pgDigits(out, at + 14, (millis / 60000) % 60, 2)
852
+ out[at + 16] = toU8(58)
853
+ pgDigits(out, at + 17, (millis / 1000) % 60, 2)
854
+ out[at + 19] = toU8(46)
855
+ pgDigits(out, at + 20, millis % 1000, 3)
856
+ out[at + 23] = toU8(90)
857
+ return 24
858
+ }
859
+ }
860
+
680
861
  /**
681
862
  * A walk over a one-dimensional `text[]` column in place: `start` the
682
863
  * column, then `next()` until `PG_ARRAY_END` or `PG_ARRAY_BAD`. Each element
@@ -842,10 +1023,11 @@ export class PgConnection {
842
1023
  lastNotice: PgError | null = null
843
1024
  /** The decoders `int`, `bool` and `isNull` use; `text.failed` says whether one was given a column it could not read. */
844
1025
  readonly text: PgText
1026
+ /** The decoders `binaryInt`, `binaryBool` and `binaryFloat` use, for a bind `bindStartBinary` opened; `binary.failed` as `text.failed`. */
1027
+ readonly binary: PgBinary
845
1028
 
846
1029
  private readonly config: PgConfig
847
1030
  private readonly password: PgPassword
848
- private state: i32 = 0
849
1031
  private input: u8[]
850
1032
  private inStart: i32 = 0
851
1033
  private inEnd: i32 = 0
@@ -863,12 +1045,15 @@ export class PgConnection {
863
1045
  groupFailed: boolean = false
864
1046
  // Whether the open bind opened its group, which `bindCancel` closes again.
865
1047
  private bindOpened: boolean = false
1048
+ // Whether the open bind asks for its result columns in binary.
1049
+ private bindBinary: boolean = false
866
1050
  private bindAt: i32 = -1
867
1051
  private paramCountAt: i32 = 0
868
1052
  private paramCount: i32 = 0
869
1053
  // The open array parameter's length word, -1 when none is open, and its elements so far.
870
1054
  private arrayAt: i32 = -1
871
1055
  private arrayCount: i32 = 0
1056
+ private state: i32 = 0
872
1057
  private clientNonce: string = ""
873
1058
  private serverSignature: u8[]
874
1059
 
@@ -885,6 +1070,7 @@ export class PgConnection {
885
1070
  pgCheckConfig(config)
886
1071
  this.config = config
887
1072
  this.text = new PgText()
1073
+ this.binary = new PgBinary()
888
1074
  this.password = new PgPassword(password)
889
1075
  const inputSize: i32 = config.inputSize < 64 ? 64 : config.inputSize
890
1076
  this.input = new Array<u8>(inputSize)
@@ -1574,7 +1760,8 @@ export class PgConnection {
1574
1760
  */
1575
1761
  private room(size: i32): boolean {
1576
1762
  const queued: i32 = this.outEnd - this.outStart
1577
- if (size < 0 || size > this.config.maxOutput - PG_TAIL - queued) {
1763
+ const tail: i32 = this.bindAt >= 0 && this.bindBinary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL
1764
+ if (size < 0 || size > this.config.maxOutput - tail - queued) {
1578
1765
  return false
1579
1766
  }
1580
1767
  if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
@@ -1740,11 +1927,31 @@ export class PgConnection {
1740
1927
  * order, then `bindEnd()`. False as `prepare` is.
1741
1928
  */
1742
1929
  bindStart(name: string): boolean {
1930
+ return this.openBind(name, false)
1931
+ }
1932
+
1933
+ /**
1934
+ * `bindStart`, asking for every result column of the execution in binary
1935
+ * format rather than text: fixed-size integers, booleans, floats and
1936
+ * times, read in place by `binaryInt`, `binaryBool`, `binaryFloat` and
1937
+ * `binary.timestamp`. A `text` column's bytes are the same in either
1938
+ * format, so `columnText` and the column windows read it as before; a
1939
+ * `text[]` or `numeric` column comes in PostgreSQL's binary layout, which
1940
+ * no decoder here reads. Its `bindEnd()` writes two bytes more, kept free
1941
+ * under `maxOutput` from here on. False as `prepare` is.
1942
+ */
1943
+ bindStartBinary(name: string): boolean {
1944
+ return this.openBind(name, true)
1945
+ }
1946
+
1947
+ private openBind(name: string, binary: boolean): boolean {
1743
1948
  const opens: boolean = !this.groupOpen
1744
- if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(11 + toI32(name.length))) {
1949
+ const extra: i32 = binary ? PG_BINARY_TAIL : 0
1950
+ if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(11 + extra + toI32(name.length))) {
1745
1951
  return false
1746
1952
  }
1747
1953
  this.bindOpened = opens
1954
+ this.bindBinary = binary
1748
1955
  this.bindAt = this.startMessage(PG_FE_BIND)
1749
1956
  this.putByte(PG_ZERO)
1750
1957
  this.putCString(name)
@@ -2058,9 +2265,10 @@ export class PgConnection {
2058
2265
  }
2059
2266
 
2060
2267
  /**
2061
- * Ends the Bind (every result column in text) and queues its Execute, for
2062
- * every row. False, and nothing written, when no bind is open; never for
2063
- * room, which the calls before it kept.
2268
+ * Ends the Bind (every result column in text, or in binary after
2269
+ * `bindStartBinary`) and queues its Execute, for every row. False, and
2270
+ * nothing written, when no bind is open; never for room, which the calls
2271
+ * before it kept.
2064
2272
  */
2065
2273
  bindEnd(): boolean {
2066
2274
  if (this.bindAt < 0) {
@@ -2069,10 +2277,17 @@ export class PgConnection {
2069
2277
  if (this.arrayAt >= 0) {
2070
2278
  panic("nish/net/postgres: bindEnd() while an array is open")
2071
2279
  }
2072
- this.tailRoom(PG_TAIL)
2280
+ const binary: boolean = this.bindBinary
2281
+ this.tailRoom(binary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL)
2073
2282
  const at: i32 = this.bindAt
2074
2283
  setU16BE(this.out, this.paramCountAt, toU16(this.paramCount))
2075
- this.putI16(PG_ZERO)
2284
+ if (binary) {
2285
+ // One format code, which the server applies to every column.
2286
+ this.putI16(toI32(1))
2287
+ this.putI16(PG_FORMAT_BINARY)
2288
+ } else {
2289
+ this.putI16(PG_ZERO)
2290
+ }
2076
2291
  this.endMessage(at)
2077
2292
  this.bindAt = -1
2078
2293
  const execute: i32 = this.startMessage(PG_FE_EXECUTE)
@@ -2170,6 +2385,21 @@ export class PgConnection {
2170
2385
  return this.text.bool(this.input, this.colStarts[c], this.colLengths[c])
2171
2386
  }
2172
2387
 
2388
+ /** Binary column `c`, an `int2`, `int4`, `int8` or a time's microseconds, as an integer; NULL or another length sets `binary.failed`. */
2389
+ binaryInt(c: i32): i64 {
2390
+ return this.binary.int(this.input, this.colStarts[c], this.colLengths[c])
2391
+ }
2392
+
2393
+ /** Binary column `c` as a boolean; NULL or a bad value sets `binary.failed`. */
2394
+ binaryBool(c: i32): boolean {
2395
+ return this.binary.bool(this.input, this.colStarts[c], this.colLengths[c])
2396
+ }
2397
+
2398
+ /** Binary column `c`, a `float8`, as an f64; NULL or another length sets `binary.failed`. */
2399
+ binaryFloat(c: i32): f64 {
2400
+ return this.binary.float(this.input, this.colStarts[c], this.colLengths[c])
2401
+ }
2402
+
2173
2403
  /** Whether the tag of the execution just completed starts with the word `word` (`INSERT`, `SELECT`, ...). */
2174
2404
  tagIs(word: string): boolean {
2175
2405
  const n: i32 = toI32(word.length)
@@ -2266,7 +2496,8 @@ class PgPoolSlot {
2266
2496
  *
2267
2497
  * A request is a ticket from `request()`, queued in FIFO order. `dispatch()`
2268
2498
  * hands the queue's head to the ready connection with the fewest groups in
2269
- * flight, never past `maxInFlight` on one; the program builds the request's
2499
+ * flight, or under `PgConfig.pack` the lowest-numbered ready one with room,
2500
+ * never past `maxInFlight` on one; the program builds the request's
2270
2501
  * group on `connection(slot)` and ends it with `send()`, not `sync()`.
2271
2502
  * `next(slot)` then reads that connection's events, `ticket` naming the
2272
2503
  * request each is about. A connection that fails reports `PG_LOST` for each
@@ -2367,7 +2598,8 @@ export class PgPool {
2367
2598
  }
2368
2599
 
2369
2600
  /**
2370
- * Hands the oldest waiting request to a ready connection with room, and
2601
+ * Hands the oldest waiting request to a ready connection with room (the
2602
+ * one with the fewest in flight, or under `PgConfig.pack` the first), and
2371
2603
  * answers its slot, `ticket` set to the request: build its group on
2372
2604
  * `connection(slot)`, then `send()`. `PG_POOL_NONE` when nothing waits, a
2373
2605
  * request is still being built, or no ready connection has room; then a
@@ -2381,6 +2613,7 @@ export class PgPool {
2381
2613
  return PG_POOL_NONE
2382
2614
  }
2383
2615
  const cap: i32 = this.config.maxInFlight
2616
+ const pack: boolean = this.config.pack
2384
2617
  let best: i32 = -1
2385
2618
  let load: i32 = 0
2386
2619
  let starting: i32 = 0
@@ -2389,7 +2622,7 @@ export class PgPool {
2389
2622
  const slot: PgPoolSlot = this.slots[s]
2390
2623
  const conn: PgConnection | null = slot.conn
2391
2624
  if (slot.state === PG_SLOT_READY && conn !== null) {
2392
- if (conn.inFlight < cap && (best < 0 || conn.inFlight < load)) {
2625
+ if (conn.inFlight < cap && (best < 0 || (!pack && conn.inFlight < load))) {
2393
2626
  best = s
2394
2627
  load = conn.inFlight
2395
2628
  }