postgrejs 3.9.0 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/connection/connection.js +6 -2
  3. package/connection/intl-connection.d.ts +18 -1
  4. package/connection/intl-connection.js +104 -11
  5. package/data-types/circle-type.js +5 -2
  6. package/data-types/classes/geometric.d.ts +49 -7
  7. package/data-types/classes/geometric.js +59 -11
  8. package/data-types/classes/interval.d.ts +40 -16
  9. package/data-types/classes/interval.js +68 -40
  10. package/data-types/classes/numeric.d.ts +6 -0
  11. package/data-types/classes/numeric.js +8 -0
  12. package/data-types/classes/range.d.ts +6 -1
  13. package/data-types/classes/range.js +6 -1
  14. package/data-types/interval-type.js +1 -1
  15. package/data-types/money-type.js +11 -2
  16. package/data-types/numeric-type.js +21 -10
  17. package/data-types/temporal-types.d.ts +19 -0
  18. package/data-types/temporal-types.js +520 -0
  19. package/index.d.ts +2 -0
  20. package/index.js +2 -0
  21. package/interfaces/data-mapping-options.d.ts +99 -4
  22. package/interfaces/database-connection-params.d.ts +10 -0
  23. package/package.json +1 -1
  24. package/util/connection-config.js +65 -1
  25. package/util/decimal-as-string.d.ts +11 -0
  26. package/util/decimal-as-string.js +30 -0
  27. package/util/fetch-as-string.d.ts +33 -0
  28. package/util/fetch-as-string.js +122 -0
  29. package/util/get-parsers.js +18 -1
  30. package/util/resolve-column-formats.d.ts +5 -11
  31. package/util/resolve-column-formats.js +24 -24
  32. package/util/stringify-for-sql.js +6 -5
  33. package/util/temporal.d.ts +47 -0
  34. package/util/temporal.js +61 -0
  35. package/util/unspecified-param.d.ts +18 -3
  36. package/util/unspecified-param.js +21 -4
package/CHANGELOG.md CHANGED
@@ -1,6 +1,37 @@
1
1
  ## Changelog
2
2
 
3
- ### [v3.9.0](https://github.com/panates/postgrejs/compare/v3.8.0...v3.9.0) -
3
+ ### [v3.10.1](https://github.com/panates/postgrejs/compare/v3.10.0...v3.10.1) -
4
+
5
+ #### 🚀 New Features
6
+
7
+ - feat: accept `connectionString`, and answer a key from another client @Eray Hanoğlu
8
+
9
+ ### [v3.10.0](https://github.com/panates/postgrejs/compare/v3.9.0...v3.10.0) - 22 September 2026
10
+
11
+ #### Breaking Changes :warning:
12
+
13
+ - feat!: serialise the value classes as their fields @Eray Hanoğlu
14
+
15
+ #### 🚀 New Features
16
+
17
+ - feat: decode the date/time types into Temporal values on request @Eray Hanoğlu
18
+ - feat: let fetchAsString ask for a scalar without its arrays @Eray Hanoğlu
19
+ - feat: decode money and numeric as their exact decimal string on request @Eray Hanoğlu
20
+ - feat: let fetchAsString name an array column by its element type @Eray Hanoğlu
21
+ - feat: report a lost connection on 'error', not only on 'close' @Eray Hanoğlu
22
+ - feat: name Circle's radius `radius`, as pg does @Eray Hanoğlu
23
+
24
+ #### 🪲 Fixes
25
+
26
+ - fix: stop declaring an element type for an array of numbers @Eray Hanoğlu
27
+ - fix: sign only the interval components that have one, and add toPostgres() @Eray Hanoğlu
28
+ - fix: read the database out of a `postgresql://` URL @Eray Hanoğlu
29
+
30
+ #### 📖 Documentation Changes
31
+
32
+ - docs: name the migration note after the release it is for @Eray Hanoğlu
33
+
34
+ ### [v3.9.0](https://github.com/panates/postgrejs/compare/v3.8.0...v3.9.0) - 22 September 2026
4
35
 
5
36
  #### 🚀 New Features
6
37
 
@@ -1,5 +1,4 @@
1
1
  import { ConnectionState, DataTypeOIDs } from '../constants.js';
2
- import { GlobalTypeMap } from '../data-type-map.js';
3
2
  import { SafeEventEmitter } from '../safe-event-emitter.js';
4
3
  import { withAbortSignal } from '../util/abort-signal.js';
5
4
  import { normalizeChannelName } from '../util/channel-name.js';
@@ -665,7 +664,12 @@ export class Connection extends SafeEventEmitter {
665
664
  : this._intlCon.config.pipeline === false;
666
665
  }
667
666
  async _query(sql, options) {
668
- const typeMap = options?.typeMap || GlobalTypeMap;
667
+ // Only the type map, not withDefaults(): the parameter types are
668
+ // decided here, before the options are merged, and merging them here
669
+ // would change what the merge means - a fetchAsString list that came
670
+ // from the connection deliberately takes a cheaper route than one
671
+ // named on the call.
672
+ const typeMap = this._intlCon.resolveTypeMap(options);
669
673
  const paramTypes = options?.params?.map(prm => prm instanceof BindParam
670
674
  ? prm.oid
671
675
  : // A Date and a string go out with no declared type, so the
@@ -73,9 +73,14 @@ export declare class IntlConnection extends SafeEventEmitter {
73
73
  /** The session's DateStyle as last reported, and what it parsed to. */
74
74
  protected _dateStyleRaw?: string;
75
75
  protected _dateStyle?: PgDateStyle;
76
+ /** The session's TimeZone as last reported, once checked. */
77
+ protected _timeZoneRaw?: string;
78
+ protected _timeZone?: string;
76
79
  /** The server's own money rendering - see ensureMoneyFormat(). */
77
80
  protected _moneyFormat?: MoneyFormat;
78
81
  protected _moneyFormatPromise?: Promise<void>;
82
+ /** Whether the socket has been up, which is what makes a close a loss. */
83
+ protected _live: boolean;
79
84
  /** Statements currently holding the wire - see enterWire(). */
80
85
  protected _wireUsers: number;
81
86
  protected _wireIdleWaiters?: (() => void)[];
@@ -410,6 +415,12 @@ export declare class IntlConnection extends SafeEventEmitter {
410
415
  * loop is over and the question can be asked.
411
416
  */
412
417
  protected _decodeDeferredRows(deferred: DeferredRows[], rowDecoder: RowDecoder, options: QueryOptions): Promise<QueryOptions>;
418
+ /**
419
+ * The type map a statement is parsed and its parameters typed with:
420
+ * the call's, else the connection's, with whatever `temporalTypes`
421
+ * asks for registered on top of it.
422
+ */
423
+ resolveTypeMap(options?: ConnectionMappingDefaults): DataTypeMap;
413
424
  withDefaults<T extends DataMappingOptions>(options: T): T;
414
425
  /**
415
426
  * The session's DateStyle, when it is not ISO.
@@ -422,8 +433,14 @@ export declare class IntlConnection extends SafeEventEmitter {
422
433
  * compare while nothing changes - which is always, for the ISO
423
434
  * default that needs no parsing at all.
424
435
  */
436
+ /**
437
+ * The zone `timestamptz` values are given when they decode to a
438
+ * Temporal.ZonedDateTime. Checked when the server's answer changes, so
439
+ * that a zone nothing can resolve is reported against the setting it
440
+ * came from instead of from inside a decode.
441
+ */
442
+ protected _currentTimeZone(): Maybe<string>;
425
443
  protected _currentDateStyle(): Maybe<PgDateStyle>;
426
- protected _onError(err: Error): void;
427
444
  }
428
445
  export declare function getIntlConnection(connection: Connection): IntlConnection;
429
446
  export {};
@@ -2,6 +2,7 @@ import { performance } from 'node:perf_hooks';
2
2
  import { ConnectionState, DataTypeOIDs, DEFAULT_COLUMN_FORMAT, } from '../constants.js';
3
3
  import { GlobalTypeMap } from '../data-type-map.js';
4
4
  import { parseMoneyFormat } from '../data-types/money-type.js';
5
+ import { temporalTypeMap } from '../data-types/temporal-types.js';
5
6
  import { DatabaseError } from '../protocol/database-error.js';
6
7
  import { PgSocket } from '../protocol/pg-socket.js';
7
8
  import { Protocol } from '../protocol/protocol.js';
@@ -9,10 +10,13 @@ import { SafeEventEmitter } from '../safe-event-emitter.js';
9
10
  import { getConnectionConfig } from '../util/connection-config.js';
10
11
  import { buildCopySql, buildProbeSql, writeCopyBinaryRows, } from '../util/copy-from-rows.js';
11
12
  import { parseDateStyleSetting } from '../util/date-style.js';
13
+ import { validateDecimalAsString } from '../util/decimal-as-string.js';
12
14
  import { escapeLiteral } from '../util/escape-literal.js';
15
+ import { fetchAsStringEqual, fetchAsStringNamesElement, fetchAsStringNamesOid, validateFetchAsString, } from '../util/fetch-as-string.js';
13
16
  import { getParsers } from '../util/get-parsers.js';
14
- import { fetchAsStringEqual, resolveColumnFormats, } from '../util/resolve-column-formats.js';
17
+ import { resolveColumnFormats } from '../util/resolve-column-formats.js';
15
18
  import { resolveRowDecoder, resolveRowType, } from '../util/row-decoder.js';
19
+ import { requireTimeZone } from '../util/temporal.js';
16
20
  import { isTransactionCommand, refusesSavepoint, } from '../util/transaction-command.js';
17
21
  import { wrapRowDescription } from '../util/wrap-row-description.js';
18
22
  import { CopyFromStream, CopyToStream } from './copy-stream.js';
@@ -89,11 +93,14 @@ const PREPARE_AFTER_USES = 2;
89
93
  /** The connection-configuration keys that are per-statement defaults. */
90
94
  const MAPPING_DEFAULT_KEYS = [
91
95
  'typeMap',
96
+ 'temporalTypes',
97
+ 'timeZone',
92
98
  'columnFormat',
93
99
  'objectRows',
94
100
  'rowDecoder',
95
101
  'utcDates',
96
102
  'fetchAsString',
103
+ 'decimalAsString',
97
104
  'unknownTypesAsString',
98
105
  'moneyFormat',
99
106
  ];
@@ -197,9 +204,14 @@ export class IntlConnection extends SafeEventEmitter {
197
204
  /** The session's DateStyle as last reported, and what it parsed to. */
198
205
  _dateStyleRaw;
199
206
  _dateStyle;
207
+ /** The session's TimeZone as last reported, once checked. */
208
+ _timeZoneRaw;
209
+ _timeZone;
200
210
  /** The server's own money rendering - see ensureMoneyFormat(). */
201
211
  _moneyFormat;
202
212
  _moneyFormatPromise;
213
+ /** Whether the socket has been up, which is what makes a close a loss. */
214
+ _live = false;
203
215
  /** Statements currently holding the wire - see enterWire(). */
204
216
  _wireUsers = 0;
205
217
  _wireIdleWaiters;
@@ -223,11 +235,36 @@ export class IntlConnection extends SafeEventEmitter {
223
235
  super();
224
236
  this._config = Object.freeze(getConnectionConfig(config));
225
237
  this._mappingDefaults = pickMappingDefaults(this._config);
238
+ // Built here rather than at the first statement, so that a Temporal
239
+ // this runtime does not have, or an OID that is not one of the five,
240
+ // is reported before any I/O - and so the map every statement then
241
+ // reuses is made once.
242
+ if (this._mappingDefaults?.temporalTypes)
243
+ temporalTypeMap(this._mappingDefaults.temporalTypes, this._mappingDefaults.typeMap || GlobalTypeMap);
226
244
  this.socket = new PgSocket(this._config);
227
- this.socket.on('error', err => this._onError(err));
245
+ this.socket.on('ready', () => {
246
+ this._live = true;
247
+ });
228
248
  // The reason travels with it: 'close' on its own cannot tell an
229
249
  // ordinary shutdown from the backend being terminated under us.
230
- this.socket.on('close', (reason) => this.emit('close', reason));
250
+ //
251
+ // A loss is reported on 'error' as well, which is where `pg` puts it
252
+ // and where code ported from it listens - `client.on('error')` was
253
+ // attached and never fired, because a terminated backend closes
254
+ // cleanly at the socket level and Node gives 'close' without an
255
+ // 'error' of its own. The Pool already reports the same object the
256
+ // same way. Emitted before 'close': the loss is the cause and the
257
+ // close is the consequence. Safe to do unconditionally, since
258
+ // SafeEventEmitter drops an 'error' nobody is listening for instead
259
+ // of throwing - and only for a connection that was up, so a failed
260
+ // connect stays the business of the promise connect() returns.
261
+ this.socket.on('close', (reason) => {
262
+ const live = this._live;
263
+ this._live = false;
264
+ if (reason && live)
265
+ this.emit('error', reason);
266
+ this.emit('close', reason);
267
+ });
231
268
  this.socket.on('notification', payload => this.emit('notification', payload));
232
269
  // Covers a notice that arrives while no statement is running. One
233
270
  // raised *by* a statement is dispatched to that statement's own
@@ -618,8 +655,11 @@ export class IntlConnection extends SafeEventEmitter {
618
655
  if (oid !== DataTypeOIDs.money && oid !== DataTypeOIDs._money)
619
656
  continue;
620
657
  // Named by fetchAsString, so the value goes back exactly as the
621
- // server rendered it and no decoder asks what a minor unit is.
622
- if (asString?.includes(oid))
658
+ // server rendered it and no decoder asks what a minor unit is -
659
+ // which naming `money` does for a `money[]` column too.
660
+ if (fetchAsStringNamesOid(asString, oid) ||
661
+ (oid === DataTypeOIDs._money &&
662
+ fetchAsStringNamesElement(asString, DataTypeOIDs.money)))
623
663
  continue;
624
664
  return true;
625
665
  }
@@ -1815,11 +1855,27 @@ export class IntlConnection extends SafeEventEmitter {
1815
1855
  }
1816
1856
  return opts;
1817
1857
  }
1858
+ /**
1859
+ * The type map a statement is parsed and its parameters typed with:
1860
+ * the call's, else the connection's, with whatever `temporalTypes`
1861
+ * asks for registered on top of it.
1862
+ */
1863
+ resolveTypeMap(options) {
1864
+ const defaults = this._mappingDefaults;
1865
+ const base = options?.typeMap || defaults?.typeMap || GlobalTypeMap;
1866
+ const temporal = options?.temporalTypes ?? defaults?.temporalTypes;
1867
+ return temporal ? temporalTypeMap(temporal, base) : base;
1868
+ }
1818
1869
  withDefaults(options) {
1819
1870
  const defaults = this._mappingDefaults;
1820
1871
  const money = this._moneyFormat;
1821
1872
  const dateStyle = this._currentDateStyle();
1822
- if (!defaults && !money && !dateStyle)
1873
+ if (!defaults &&
1874
+ !money &&
1875
+ !dateStyle &&
1876
+ !options.temporalTypes &&
1877
+ !options.fetchAsString &&
1878
+ options.decimalAsString === undefined)
1823
1879
  return options;
1824
1880
  let out;
1825
1881
  if (defaults) {
@@ -1840,6 +1896,32 @@ export class IntlConnection extends SafeEventEmitter {
1840
1896
  out = out || { ...options };
1841
1897
  out.dateStyle = dateStyle;
1842
1898
  }
1899
+ // Every path into a statement passes through here, so a selector
1900
+ // that asks for something this does not do is refused once, at the
1901
+ // call that carries it, rather than in whichever of the three
1902
+ // readers of the list happens to reach it first.
1903
+ if ((out || options).fetchAsString)
1904
+ validateFetchAsString((out || options).fetchAsString, this.resolveTypeMap(out || options));
1905
+ if ((out || options).decimalAsString !== undefined)
1906
+ validateDecimalAsString((out || options).decimalAsString);
1907
+ if ((out || options).temporalTypes) {
1908
+ // On top of whatever type map is in effect, rather than instead of
1909
+ // it. The map is memoized per (base, selection), so this is two
1910
+ // lookups and not a copy.
1911
+ const base = (out || options).typeMap || GlobalTypeMap;
1912
+ const map = this.resolveTypeMap(out || options);
1913
+ if (map !== base) {
1914
+ out = out || { ...options };
1915
+ out.typeMap = map;
1916
+ }
1917
+ if (!(out || options).timeZone) {
1918
+ const tz = this._currentTimeZone();
1919
+ if (tz) {
1920
+ out = out || { ...options };
1921
+ out.timeZone = tz;
1922
+ }
1923
+ }
1924
+ }
1843
1925
  return out || options;
1844
1926
  }
1845
1927
  /**
@@ -1853,6 +1935,22 @@ export class IntlConnection extends SafeEventEmitter {
1853
1935
  * compare while nothing changes - which is always, for the ISO
1854
1936
  * default that needs no parsing at all.
1855
1937
  */
1938
+ /**
1939
+ * The zone `timestamptz` values are given when they decode to a
1940
+ * Temporal.ZonedDateTime. Checked when the server's answer changes, so
1941
+ * that a zone nothing can resolve is reported against the setting it
1942
+ * came from instead of from inside a decode.
1943
+ */
1944
+ _currentTimeZone() {
1945
+ const raw = this.socket.sessionParameters.TimeZone;
1946
+ if (raw !== this._timeZoneRaw) {
1947
+ this._timeZoneRaw = raw;
1948
+ this._timeZone = raw
1949
+ ? requireTimeZone(raw, 'the session TimeZone')
1950
+ : undefined;
1951
+ }
1952
+ return this._timeZone;
1953
+ }
1856
1954
  _currentDateStyle() {
1857
1955
  const raw = this.socket.sessionParameters.DateStyle;
1858
1956
  if (raw !== this._dateStyleRaw) {
@@ -1861,11 +1959,6 @@ export class IntlConnection extends SafeEventEmitter {
1861
1959
  }
1862
1960
  return this._dateStyle;
1863
1961
  }
1864
- _onError(err) {
1865
- if (this.socket.state !== ConnectionState.READY)
1866
- return;
1867
- this.emit('error', err);
1868
- }
1869
1962
  }
1870
1963
  export function getIntlConnection(connection) {
1871
1964
  return connection._intlCon;
@@ -8,13 +8,16 @@ export const CircleType = {
8
8
  name: 'circle',
9
9
  oid: DataTypeOIDs.circle,
10
10
  jsType: 'Circle',
11
+ // isType() accepts a plain object as well as a Circle, and that object
12
+ // may spell the radius either way - hence Circle.radiusOf() rather
13
+ // than the property.
11
14
  encodeText(v) {
12
- return `<(${v.x},${v.y}),${v.r}>`;
15
+ return `<(${v.x},${v.y}),${Circle.radiusOf(v)}>`;
13
16
  },
14
17
  encodeBinary(buf, v) {
15
18
  buf.writeDoubleBE(v.x);
16
19
  buf.writeDoubleBE(v.y);
17
- buf.writeDoubleBE(v.r);
20
+ buf.writeDoubleBE(Circle.radiusOf(v));
18
21
  },
19
22
  decodeBinary(v, offset = 0) {
20
23
  return new Circle(v.readDoubleBE(offset), v.readDoubleBE(offset + 8), v.readDoubleBE(offset + 16));
@@ -5,19 +5,46 @@ export declare class Point {
5
5
  constructor(x?: number, y?: number);
6
6
  /** As PostgreSQL prints it, so it casts back through `::point`. */
7
7
  toString(): string;
8
- toJSON(): string;
8
+ /**
9
+ * The literal PostgreSQL reads back, for an encoder that asks the value
10
+ * how to write itself - `pg`'s convention, and the reason a value this
11
+ * client decoded can be handed straight to one.
12
+ */
13
+ toPostgres(): string;
9
14
  static isPointLike(v: any): boolean;
10
15
  }
11
16
  /** A `circle`: a centre and a radius. */
12
17
  export declare class Circle {
13
18
  x: number;
14
19
  y: number;
15
- r: number;
16
- constructor(x?: number, y?: number, r?: number);
20
+ radius: number;
21
+ constructor(x?: number, y?: number, radius?: number);
22
+ /**
23
+ * The radius under the name this client used up to 3.9. `pg` spells it
24
+ * `radius`, and a value decoded here should be usable wherever one of
25
+ * its own is, so `radius` is what the property is now - but `r` was a
26
+ * released name, so it stays as an accessor onto the same number
27
+ * rather than silently reading back undefined.
28
+ *
29
+ * @deprecated use `radius`
30
+ */
31
+ get r(): number;
32
+ set r(value: number);
17
33
  /** As PostgreSQL prints it, so it casts back through `::circle`. */
18
34
  toString(): string;
19
- toJSON(): string;
35
+ /**
36
+ * The literal PostgreSQL reads back, for an encoder that asks the value
37
+ * how to write itself - `pg`'s convention, and the reason a value this
38
+ * client decoded can be handed straight to one.
39
+ */
40
+ toPostgres(): string;
20
41
  static isCircleLike(v: any): boolean;
42
+ /**
43
+ * The radius of anything isCircleLike() accepts: a Circle, the plain
44
+ * `{x, y, radius}` object `pg` hands back, or the `{x, y, r}` that was
45
+ * this client's own shape before the classes existed.
46
+ */
47
+ static radiusOf(v: any): number;
21
48
  }
22
49
  /**
23
50
  * Two corners, which is how both `box` and `lseg` are carried - and why
@@ -32,7 +59,12 @@ declare abstract class TwoPoints {
32
59
  x2: number;
33
60
  y2: number;
34
61
  constructor(x1?: number, y1?: number, x2?: number, y2?: number);
35
- toJSON(): string;
62
+ /**
63
+ * The literal PostgreSQL reads back, for an encoder that asks the value
64
+ * how to write itself - `pg`'s convention, and the reason a value this
65
+ * client decoded can be handed straight to one.
66
+ */
67
+ toPostgres(): string;
36
68
  static isTwoPointsLike(v: any): boolean;
37
69
  }
38
70
  /** A `box`: a rectangle given by two opposite corners. */
@@ -61,7 +93,12 @@ export declare class Line {
61
93
  constructor(a?: number, b?: number, c?: number);
62
94
  /** As PostgreSQL prints it, so it casts back through `::line`. */
63
95
  toString(): string;
64
- toJSON(): string;
96
+ /**
97
+ * The literal PostgreSQL reads back, for an encoder that asks the value
98
+ * how to write itself - `pg`'s convention, and the reason a value this
99
+ * client decoded can be handed straight to one.
100
+ */
101
+ toPostgres(): string;
65
102
  static isLineLike(v: any): boolean;
66
103
  }
67
104
  /**
@@ -77,7 +114,12 @@ declare abstract class PointList {
77
114
  x: number;
78
115
  y: number;
79
116
  })[]);
80
- toJSON(): string;
117
+ /**
118
+ * The literal PostgreSQL reads back, for an encoder that asks the value
119
+ * how to write itself - `pg`'s convention, and the reason a value this
120
+ * client decoded can be handed straight to one.
121
+ */
122
+ toPostgres(): string;
81
123
  protected join(): string;
82
124
  }
83
125
  /**
@@ -27,7 +27,12 @@ export class Point {
27
27
  toString() {
28
28
  return `(${this.x},${this.y})`;
29
29
  }
30
- toJSON() {
30
+ /**
31
+ * The literal PostgreSQL reads back, for an encoder that asks the value
32
+ * how to write itself - `pg`'s convention, and the reason a value this
33
+ * client decoded can be handed straight to one.
34
+ */
35
+ toPostgres() {
31
36
  return this.toString();
32
37
  }
33
38
  /* c8 ignore next 3 */
@@ -42,17 +47,37 @@ export class Point {
42
47
  export class Circle {
43
48
  x;
44
49
  y;
45
- r;
46
- constructor(x = 0, y = 0, r = 0) {
50
+ radius;
51
+ constructor(x = 0, y = 0, radius = 0) {
47
52
  this.x = x;
48
53
  this.y = y;
49
- this.r = r;
54
+ this.radius = radius;
55
+ }
56
+ /**
57
+ * The radius under the name this client used up to 3.9. `pg` spells it
58
+ * `radius`, and a value decoded here should be usable wherever one of
59
+ * its own is, so `radius` is what the property is now - but `r` was a
60
+ * released name, so it stays as an accessor onto the same number
61
+ * rather than silently reading back undefined.
62
+ *
63
+ * @deprecated use `radius`
64
+ */
65
+ get r() {
66
+ return this.radius;
67
+ }
68
+ set r(value) {
69
+ this.radius = value;
50
70
  }
51
71
  /** As PostgreSQL prints it, so it casts back through `::circle`. */
52
72
  toString() {
53
- return `<(${this.x},${this.y}),${this.r}>`;
54
- }
55
- toJSON() {
73
+ return `<(${this.x},${this.y}),${this.radius}>`;
74
+ }
75
+ /**
76
+ * The literal PostgreSQL reads back, for an encoder that asks the value
77
+ * how to write itself - `pg`'s convention, and the reason a value this
78
+ * client decoded can be handed straight to one.
79
+ */
80
+ toPostgres() {
56
81
  return this.toString();
57
82
  }
58
83
  static isCircleLike(v) {
@@ -60,7 +85,15 @@ export class Circle {
60
85
  Object.keys(v).length === 3 &&
61
86
  isNum(v.x) &&
62
87
  isNum(v.y) &&
63
- isNum(v.r));
88
+ (isNum(v.radius) || isNum(v.r)));
89
+ }
90
+ /**
91
+ * The radius of anything isCircleLike() accepts: a Circle, the plain
92
+ * `{x, y, radius}` object `pg` hands back, or the `{x, y, r}` that was
93
+ * this client's own shape before the classes existed.
94
+ */
95
+ static radiusOf(v) {
96
+ return v.radius ?? v.r;
64
97
  }
65
98
  }
66
99
  /**
@@ -81,7 +114,12 @@ class TwoPoints {
81
114
  this.x2 = x2;
82
115
  this.y2 = y2;
83
116
  }
84
- toJSON() {
117
+ /**
118
+ * The literal PostgreSQL reads back, for an encoder that asks the value
119
+ * how to write itself - `pg`'s convention, and the reason a value this
120
+ * client decoded can be handed straight to one.
121
+ */
122
+ toPostgres() {
85
123
  return this.toString();
86
124
  }
87
125
  static isTwoPointsLike(v) {
@@ -129,7 +167,12 @@ export class Line {
129
167
  toString() {
130
168
  return `{${this.a},${this.b},${this.c}}`;
131
169
  }
132
- toJSON() {
170
+ /**
171
+ * The literal PostgreSQL reads back, for an encoder that asks the value
172
+ * how to write itself - `pg`'s convention, and the reason a value this
173
+ * client decoded can be handed straight to one.
174
+ */
175
+ toPostgres() {
133
176
  return this.toString();
134
177
  }
135
178
  static isLineLike(v) {
@@ -152,7 +195,12 @@ class PointList {
152
195
  constructor(points = []) {
153
196
  this.points = points.map(p => p instanceof Point ? p : new Point(p.x, p.y));
154
197
  }
155
- toJSON() {
198
+ /**
199
+ * The literal PostgreSQL reads back, for an encoder that asks the value
200
+ * how to write itself - `pg`'s convention, and the reason a value this
201
+ * client decoded can be handed straight to one.
202
+ */
203
+ toPostgres() {
156
204
  return this.toString();
157
205
  }
158
206
  join() {
@@ -1,4 +1,4 @@
1
- /** The fields an Interval is built from. All optional, all defaulting to 0. */
1
+ /** The fields an Interval is built from; it keeps the ones that have a value. */
2
2
  export interface IntervalFields {
3
3
  years?: number;
4
4
  months?: number;
@@ -18,20 +18,29 @@ export interface IntervalFields {
18
18
  * zone, can add one to a timestamp. Anything here that answered "how many
19
19
  * milliseconds is this" would have to invent those lengths.
20
20
  *
21
- * Every field is always present, 0 when the value has none:
21
+ * It carries the fields that have a value and no others, which is what
22
+ * the wire format carries too - three quantities, and a zero among them
23
+ * is not a field:
22
24
  *
23
25
  * ```ts
24
26
  * const iv = new Interval({ days: 1, hours: 2 });
25
- * iv.minutes; // 0, not undefined
27
+ * Object.keys(iv); // ['days', 'hours']
28
+ * iv.minutes; // undefined
26
29
  * String(iv); // '1 day 02:00:00'
27
30
  * iv.toISOString(); // 'P0Y0M1DT2H0M0S'
28
31
  * ```
29
32
  *
30
- * The field names are `pg`'s (`postgres-interval`), so code ported from it
31
- * reads the same values from the same places. The difference is that
32
- * package's objects are sparse - `interval '1 day'` gives it `{days: 1}`
33
- * and nothing else, so `iv.hours + 1` is NaN and a zero interval is `{}` -
34
- * and that it has no `toString()`, so a log line gets `[object Object]`.
33
+ * The field names and the shape are `pg`'s (`postgres-interval`), so code
34
+ * ported from it reads the same values from the same places and a
35
+ * `JSON.stringify` of a row says the same thing. The difference is that
36
+ * this has a `toString()` - `pg`'s object gives `[object Object]` in a log
37
+ * line - and a `toPostgres()`, so the value can be sent back.
38
+ *
39
+ * The fields are optional, so `iv.hours + 1` does not compile without
40
+ * saying what an absent one means (`(iv.hours ?? 0) + 1`). That is the
41
+ * point: which fields a value has depends on the value, not on the type,
42
+ * and the older shape - every field present and 0 - hid that behind
43
+ * numbers nobody wrote.
35
44
  *
36
45
  * `milliseconds` carries the sub-second part and can be fractional, since
37
46
  * PostgreSQL stores microseconds: `interval '0.000001 seconds'` is
@@ -40,17 +49,27 @@ export interface IntervalFields {
40
49
  * A negative interval carries the sign on each field that has one, the way
41
50
  * the server prints it: `interval '-1 day -2 hours'` is
42
51
  * `{ days: -1, hours: -2 }`.
52
+ *
53
+ * `toPostgres()` is `pg`'s convention for "write yourself back", so a
54
+ * value read here can be passed to any encoder that follows it - this
55
+ * client's own parameter path never needed it, since it knows the class.
43
56
  */
44
57
  export declare class Interval implements IntervalFields {
45
- years: number;
46
- months: number;
47
- days: number;
48
- hours: number;
49
- minutes: number;
58
+ years?: number;
59
+ months?: number;
60
+ days?: number;
61
+ hours?: number;
62
+ minutes?: number;
50
63
  /** Whole seconds; the sub-second part is in `milliseconds`. */
51
- seconds: number;
64
+ seconds?: number;
52
65
  /** The sub-second part, fractional down to a microsecond. */
53
- milliseconds: number;
66
+ milliseconds?: number;
67
+ /**
68
+ * Keeps the fields that have a value. A zero is not one: it is what
69
+ * every field of every interval would otherwise be filled with, and
70
+ * `{ days: 1 }` says what `interval '1 day'` is far better than the
71
+ * same thing plus six zeroes nobody wrote.
72
+ */
54
73
  constructor(fields?: IntervalFields);
55
74
  /**
56
75
  * Builds one from the three quantities the wire format carries: a signed
@@ -69,5 +88,10 @@ export declare class Interval implements IntervalFields {
69
88
  toString(): string;
70
89
  /** The interval as an ISO 8601 duration. */
71
90
  toISOString(): string;
72
- toJSON(): string;
91
+ /**
92
+ * The literal PostgreSQL reads back, for an encoder that asks the value
93
+ * how to write itself - `pg`'s convention, and the reason a value this
94
+ * client decoded can be handed straight to one.
95
+ */
96
+ toPostgres(): string;
73
97
  }