postgrejs 2.23.1 → 3.0.2

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 (107) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +169 -16
  5. package/connection/connection.js +318 -56
  6. package/connection/copy-stream.d.ts +88 -0
  7. package/connection/copy-stream.js +244 -0
  8. package/connection/cursor.js +20 -13
  9. package/connection/intl-connection.d.ts +113 -5
  10. package/connection/intl-connection.js +500 -70
  11. package/connection/large-object.d.ts +65 -0
  12. package/connection/large-object.js +151 -0
  13. package/connection/logical-replication.d.ts +121 -0
  14. package/connection/logical-replication.js +279 -0
  15. package/connection/pool.d.ts +61 -4
  16. package/connection/pool.js +177 -29
  17. package/connection/portal.d.ts +18 -4
  18. package/connection/portal.js +53 -59
  19. package/connection/prepared-statement.d.ts +22 -1
  20. package/connection/prepared-statement.js +129 -109
  21. package/data-type-map.js +4 -2
  22. package/data-types/bool-type.js +14 -10
  23. package/data-types/box-type.js +15 -8
  24. package/data-types/bytea-type.js +9 -5
  25. package/data-types/char-type.js +11 -5
  26. package/data-types/circle-type.js +15 -7
  27. package/data-types/date-type.js +25 -19
  28. package/data-types/float4-type.js +10 -3
  29. package/data-types/float8-type.js +12 -3
  30. package/data-types/int2-type.js +9 -4
  31. package/data-types/int2-vector-type.js +9 -4
  32. package/data-types/int4-type.js +11 -5
  33. package/data-types/int8-type.js +22 -7
  34. package/data-types/json-type.js +17 -8
  35. package/data-types/jsonb-type.js +20 -14
  36. package/data-types/lseg-type.js +16 -8
  37. package/data-types/numeric-type.d.ts +8 -0
  38. package/data-types/numeric-type.js +118 -9
  39. package/data-types/oid-type.d.ts +0 -1
  40. package/data-types/oid-type.js +9 -10
  41. package/data-types/oid-vector-type.d.ts +9 -0
  42. package/data-types/oid-vector-type.js +51 -0
  43. package/data-types/point-type.js +14 -6
  44. package/data-types/time-type.js +20 -13
  45. package/data-types/timestamp-type.js +32 -25
  46. package/data-types/timestamptz-type.js +33 -21
  47. package/data-types/uuid-type.js +21 -11
  48. package/data-types/varchar-type.js +16 -4
  49. package/index.d.ts +5 -0
  50. package/index.js +5 -0
  51. package/interfaces/command-result.d.ts +2 -1
  52. package/interfaces/data-type.d.ts +5 -3
  53. package/interfaces/database-connection-params.d.ts +108 -0
  54. package/interfaces/query-options.d.ts +23 -0
  55. package/interfaces/script-execute-options.d.ts +23 -0
  56. package/interfaces/script-result.d.ts +3 -2
  57. package/package.json +2 -3
  58. package/protocol/backend.d.ts +6 -1
  59. package/protocol/backend.js +70 -45
  60. package/protocol/buffer-reader.d.ts +0 -2
  61. package/protocol/buffer-reader.js +3 -10
  62. package/protocol/cert-signature.d.ts +12 -0
  63. package/protocol/cert-signature.js +114 -0
  64. package/protocol/frontend.d.ts +16 -0
  65. package/protocol/frontend.js +70 -11
  66. package/protocol/pg-socket.d.ts +220 -9
  67. package/protocol/pg-socket.js +539 -102
  68. package/protocol/pgoutput.d.ts +76 -0
  69. package/protocol/pgoutput.js +155 -0
  70. package/protocol/protocol.d.ts +3 -4
  71. package/protocol/protocol.js +1 -1
  72. package/protocol/sasl.d.ts +11 -1
  73. package/protocol/sasl.js +36 -5
  74. package/protocol/smart-buffer.d.ts +0 -2
  75. package/protocol/smart-buffer.js +11 -16
  76. package/types.d.ts +5 -3
  77. package/util/abort-signal.d.ts +34 -0
  78. package/util/abort-signal.js +76 -0
  79. package/util/array-calculatedim.js +3 -1
  80. package/util/connection-config.js +77 -3
  81. package/util/decode-binaryarray.d.ts +1 -1
  82. package/util/decode-binaryarray.js +15 -6
  83. package/util/encode-binaryarray.js +7 -3
  84. package/util/escape-identifier.d.ts +14 -0
  85. package/util/escape-identifier.js +32 -0
  86. package/util/escape-literal.js +3 -0
  87. package/util/fast-parseint.d.ts +1 -0
  88. package/util/fast-parseint.js +12 -17
  89. package/util/format-datetime.d.ts +10 -0
  90. package/util/format-datetime.js +71 -0
  91. package/util/get-parsers.js +31 -10
  92. package/util/parse-array.js +2 -2
  93. package/util/parse-bytea.d.ts +2 -0
  94. package/util/parse-bytea.js +83 -0
  95. package/util/parse-datetime.d.ts +3 -1
  96. package/util/parse-datetime.js +86 -17
  97. package/util/parse-row.d.ts +3 -1
  98. package/util/parse-row.js +60 -5
  99. package/util/parse-time.js +8 -1
  100. package/util/sql-tag.d.ts +60 -0
  101. package/util/sql-tag.js +202 -0
  102. package/util/starts-transaction.d.ts +38 -0
  103. package/util/starts-transaction.js +148 -0
  104. package/util/stringify-arrayliteral.js +13 -1
  105. package/util/wrap-row-description.js +13 -4
  106. package/util/convert-row-to-object.d.ts +0 -2
  107. package/util/convert-row-to-object.js +0 -9
@@ -0,0 +1,76 @@
1
+ import { BufferReader } from './buffer-reader.js';
2
+ export interface PgOutputColumn {
3
+ name: string;
4
+ dataTypeId: number;
5
+ typeModifier: number;
6
+ /** Part of the replica identity, so it can identify a row on its own. */
7
+ isKey: boolean;
8
+ }
9
+ export interface PgOutputRelation {
10
+ relationId: number;
11
+ schema: string;
12
+ name: string;
13
+ replicaIdentity: string;
14
+ columns: PgOutputColumn[];
15
+ }
16
+ export type PgOutputMessage = {
17
+ kind: 'begin';
18
+ finalLsn: bigint;
19
+ commitTime: Date;
20
+ xid: number;
21
+ } | {
22
+ kind: 'commit';
23
+ commitLsn: bigint;
24
+ endLsn: bigint;
25
+ commitTime: Date;
26
+ } | {
27
+ kind: 'relation';
28
+ relation: PgOutputRelation;
29
+ } | {
30
+ kind: 'type';
31
+ dataTypeId: number;
32
+ schema: string;
33
+ name: string;
34
+ } | {
35
+ kind: 'insert';
36
+ relation: PgOutputRelation;
37
+ row: TupleValues;
38
+ } | {
39
+ kind: 'update';
40
+ relation: PgOutputRelation;
41
+ row: TupleValues;
42
+ oldRow?: TupleValues;
43
+ } | {
44
+ kind: 'delete';
45
+ relation: PgOutputRelation;
46
+ oldRow: TupleValues;
47
+ } | {
48
+ kind: 'truncate';
49
+ relations: PgOutputRelation[];
50
+ } | {
51
+ kind: 'origin';
52
+ commitLsn: bigint;
53
+ name: string;
54
+ } | {
55
+ kind: 'message';
56
+ prefix: string;
57
+ content: Buffer;
58
+ lsn: bigint;
59
+ } | {
60
+ kind: 'unknown';
61
+ code: string;
62
+ };
63
+ /**
64
+ * A row as pgoutput sends it: column name to its raw text, or null. A value
65
+ * left out because it did not change and lives in TOAST storage is absent
66
+ * from the map entirely - which is not the same as being null.
67
+ */
68
+ export type TupleValues = Record<string, string | null>;
69
+ export declare class PgOutputDecoder {
70
+ protected readonly _relations: Map<number, PgOutputRelation>;
71
+ decode(data: Buffer): PgOutputMessage;
72
+ protected _relation(relationId: number): PgOutputRelation;
73
+ protected _tuple(io: BufferReader, relation: PgOutputRelation): TupleValues;
74
+ }
75
+ /** `0/16B3748`, the form PostgreSQL prints an LSN in. */
76
+ export declare function formatLsn(lsn: bigint): string;
@@ -0,0 +1,155 @@
1
+ import { BufferReader } from './buffer-reader.js';
2
+ /**
3
+ * Decoder for `pgoutput`, the logical decoding plugin PostgreSQL ships with,
4
+ * at protocol version 1.
5
+ *
6
+ * The server does not repeat a table's shape with every row: a Relation
7
+ * message describes it once and later Insert/Update/Delete messages refer to
8
+ * it by OID, so a decoder has to remember relations as it goes. That is why
9
+ * this is a class rather than a function.
10
+ */
11
+ /** Microseconds since 2000-01-01, which is how this protocol tells time. */
12
+ const PG_EPOCH_MS = Date.UTC(2000, 0, 1);
13
+ export class PgOutputDecoder {
14
+ _relations = new Map();
15
+ decode(data) {
16
+ const io = new BufferReader(data);
17
+ const code = String.fromCharCode(io.readUInt8());
18
+ switch (code) {
19
+ case 'B':
20
+ return {
21
+ kind: 'begin',
22
+ finalLsn: readLsn(io),
23
+ commitTime: readTime(io),
24
+ xid: io.readUInt32BE(),
25
+ };
26
+ case 'C': {
27
+ io.readUInt8(); // flags, unused at this version
28
+ return {
29
+ kind: 'commit',
30
+ commitLsn: readLsn(io),
31
+ endLsn: readLsn(io),
32
+ commitTime: readTime(io),
33
+ };
34
+ }
35
+ case 'O':
36
+ return {
37
+ kind: 'origin',
38
+ commitLsn: readLsn(io),
39
+ name: io.readCString(),
40
+ };
41
+ case 'R': {
42
+ const relation = {
43
+ relationId: io.readUInt32BE(),
44
+ schema: io.readCString(),
45
+ name: io.readCString(),
46
+ replicaIdentity: String.fromCharCode(io.readUInt8()),
47
+ columns: [],
48
+ };
49
+ const count = io.readUInt16BE();
50
+ for (let i = 0; i < count; i++) {
51
+ relation.columns.push({
52
+ isKey: io.readUInt8() === 1,
53
+ name: io.readCString(),
54
+ dataTypeId: io.readUInt32BE(),
55
+ typeModifier: io.readInt32BE(),
56
+ });
57
+ }
58
+ this._relations.set(relation.relationId, relation);
59
+ return { kind: 'relation', relation };
60
+ }
61
+ case 'Y':
62
+ return {
63
+ kind: 'type',
64
+ dataTypeId: io.readUInt32BE(),
65
+ schema: io.readCString(),
66
+ name: io.readCString(),
67
+ };
68
+ case 'I': {
69
+ const relation = this._relation(io.readUInt32BE());
70
+ io.readUInt8(); // 'N'
71
+ return { kind: 'insert', relation, row: this._tuple(io, relation) };
72
+ }
73
+ case 'U': {
74
+ const relation = this._relation(io.readUInt32BE());
75
+ let oldRow;
76
+ // 'K' is the old row's key columns, 'O' the whole old row; either
77
+ // appears only when the table's replica identity provides it, and
78
+ // the 'N' tag for the new row follows in every case.
79
+ const tag = String.fromCharCode(io.readUInt8());
80
+ if (tag === 'K' || tag === 'O') {
81
+ oldRow = this._tuple(io, relation);
82
+ io.readUInt8(); // 'N'
83
+ }
84
+ return {
85
+ kind: 'update',
86
+ relation,
87
+ row: this._tuple(io, relation),
88
+ oldRow,
89
+ };
90
+ }
91
+ case 'D': {
92
+ const relation = this._relation(io.readUInt32BE());
93
+ io.readUInt8(); // 'K' or 'O'
94
+ return { kind: 'delete', relation, oldRow: this._tuple(io, relation) };
95
+ }
96
+ case 'T': {
97
+ const count = io.readUInt32BE();
98
+ io.readUInt8(); // flags: cascade / restart identity
99
+ const relations = [];
100
+ for (let i = 0; i < count; i++)
101
+ relations.push(this._relation(io.readUInt32BE()));
102
+ return { kind: 'truncate', relations };
103
+ }
104
+ case 'M': {
105
+ io.readUInt8(); // flags
106
+ const lsn = readLsn(io);
107
+ const prefix = io.readCString();
108
+ const length = io.readUInt32BE();
109
+ return { kind: 'message', prefix, lsn, content: io.readBuffer(length) };
110
+ }
111
+ default:
112
+ return { kind: 'unknown', code };
113
+ }
114
+ }
115
+ _relation(relationId) {
116
+ const relation = this._relations.get(relationId);
117
+ if (!relation)
118
+ throw new Error(`Logical replication: a change arrived for relation ${relationId} before its description did`);
119
+ return relation;
120
+ }
121
+ _tuple(io, relation) {
122
+ const count = io.readUInt16BE();
123
+ const out = {};
124
+ for (let i = 0; i < count; i++) {
125
+ const column = relation.columns[i];
126
+ const kind = String.fromCharCode(io.readUInt8());
127
+ if (kind === 'n') {
128
+ out[column.name] = null;
129
+ }
130
+ else if (kind === 'u') {
131
+ // Unchanged and stored out of line: the server did not send it, and
132
+ // leaving the key out says so rather than claiming it is null.
133
+ continue;
134
+ }
135
+ else {
136
+ const length = io.readUInt32BE();
137
+ out[column.name] = io.readBuffer(length).toString('utf8');
138
+ }
139
+ }
140
+ return out;
141
+ }
142
+ }
143
+ function readLsn(io) {
144
+ const hi = BigInt(io.readUInt32BE());
145
+ const lo = BigInt(io.readUInt32BE());
146
+ return (hi << 32n) | lo;
147
+ }
148
+ function readTime(io) {
149
+ const micros = (BigInt(io.readUInt32BE()) << 32n) | BigInt(io.readUInt32BE());
150
+ return new Date(PG_EPOCH_MS + Number(micros / 1000n));
151
+ }
152
+ /** `0/16B3748`, the form PostgreSQL prints an LSN in. */
153
+ export function formatLsn(lsn) {
154
+ return `${(lsn >> 32n).toString(16).toUpperCase()}/${(lsn & 0xffffffffn).toString(16).toUpperCase()}`;
155
+ }
@@ -1,4 +1,3 @@
1
- import type { Nullable } from '../types.js';
2
1
  export declare namespace Protocol {
3
2
  const VERSION_MAJOR = 3;
4
3
  const VERSION_MINOR = 0;
@@ -10,7 +9,7 @@ export declare namespace Protocol {
10
9
  CommandComplete = 67,// C
11
10
  CopyData = 100,// d
12
11
  CopyDone = 99,// c
13
- CopyInResponse = 103,// G
12
+ CopyInResponse = 71,// G
14
13
  CopyOutResponse = 72,// H
15
14
  CopyBothResponse = 87,// W
16
15
  DataRow = 68,// D
@@ -29,7 +28,7 @@ export declare namespace Protocol {
29
28
  RowDescription = 84
30
29
  }
31
30
  enum FrontendMessageCode {
32
- Bind = 66,// R
31
+ Bind = 66,// B
33
32
  Close = 67,// C
34
33
  CopyData = 100,// d
35
34
  CopyDone = 99,// c
@@ -119,7 +118,7 @@ export declare namespace Protocol {
119
118
  }
120
119
  interface DataRowMessage {
121
120
  columnCount: number;
122
- columns: Nullable<Buffer>[];
121
+ data: Buffer;
123
122
  }
124
123
  interface ErrorResponseMessage {
125
124
  severity?: string;
@@ -12,7 +12,7 @@ export var Protocol;
12
12
  BackendMessageCode[BackendMessageCode["CommandComplete"] = 67] = "CommandComplete";
13
13
  BackendMessageCode[BackendMessageCode["CopyData"] = 100] = "CopyData";
14
14
  BackendMessageCode[BackendMessageCode["CopyDone"] = 99] = "CopyDone";
15
- BackendMessageCode[BackendMessageCode["CopyInResponse"] = 103] = "CopyInResponse";
15
+ BackendMessageCode[BackendMessageCode["CopyInResponse"] = 71] = "CopyInResponse";
16
16
  BackendMessageCode[BackendMessageCode["CopyOutResponse"] = 72] = "CopyOutResponse";
17
17
  BackendMessageCode[BackendMessageCode["CopyBothResponse"] = 87] = "CopyBothResponse";
18
18
  BackendMessageCode[BackendMessageCode["DataRow"] = 68] = "DataRow";
@@ -6,8 +6,18 @@ export declare namespace SASL {
6
6
  clientFirstMessage: string;
7
7
  clientFinalMessage: string;
8
8
  serverSignature: string;
9
+ /** The GS2 header this session announced; repeated in the final message. */
10
+ gs2Header: string;
11
+ /** tls-server-end-point data, present only for SCRAM-SHA-256-PLUS. */
12
+ bindingData?: Buffer;
9
13
  }
10
- function createSession(username: string, mechanism: string): Session;
14
+ /**
15
+ * @param bindingData - the server certificate's hash, for
16
+ * SCRAM-SHA-256-PLUS. Pass undefined for the plain mechanism, and
17
+ * `supportsBinding` to say whether the client could have done it, which
18
+ * is what tells the server a stripped -PLUS offer would be an attack.
19
+ */
20
+ function createSession(username: string, mechanism: string, bindingData?: Buffer, supportsBinding?: boolean): Session;
11
21
  function continueSession(session: Session, password: string, data: string): void;
12
22
  function finalizeSession(session: Session, data: string): void;
13
23
  }
package/protocol/sasl.js CHANGED
@@ -3,14 +3,36 @@ export var SASL;
3
3
  (function (SASL) {
4
4
  const CLIENT_KEY = 'Client Key';
5
5
  const SERVER_KEY = 'Server Key';
6
- const GS2_HEADER = 'n,,';
7
- function createSession(username, mechanism) {
6
+ /**
7
+ * The GS2 header states what the client can do about channel binding, and
8
+ * the server checks it against what it offered. `n` means the client does
9
+ * not support it; `y` means it does but saw no -PLUS mechanism offered -
10
+ * which is what lets the server notice an attacker having stripped it;
11
+ * `p=...` means it is in use.
12
+ */
13
+ const GS2_NONE = 'n,,';
14
+ const GS2_SUPPORTED_UNUSED = 'y,,';
15
+ const GS2_BOUND = 'p=tls-server-end-point,,';
16
+ /**
17
+ * @param bindingData - the server certificate's hash, for
18
+ * SCRAM-SHA-256-PLUS. Pass undefined for the plain mechanism, and
19
+ * `supportsBinding` to say whether the client could have done it, which
20
+ * is what tells the server a stripped -PLUS offer would be an attack.
21
+ */
22
+ function createSession(username, mechanism, bindingData, supportsBinding) {
8
23
  const nonce = crypto.randomBytes(18).toString('base64');
9
- const clientFirstMessage = `${GS2_HEADER}${firstMessageBare(username, nonce)}`;
24
+ const gs2Header = bindingData
25
+ ? GS2_BOUND
26
+ : supportsBinding
27
+ ? GS2_SUPPORTED_UNUSED
28
+ : GS2_NONE;
29
+ const clientFirstMessage = `${gs2Header}${firstMessageBare(username, nonce)}`;
10
30
  return {
11
31
  username,
12
32
  mechanism,
13
33
  nonce,
34
+ gs2Header,
35
+ bindingData,
14
36
  clientFirstMessage,
15
37
  };
16
38
  }
@@ -45,7 +67,16 @@ export var SASL;
45
67
  if (!nonce.startsWith(session.nonce))
46
68
  throw new Error('SASL: Server nonce does not start with client nonce');
47
69
  const serverFirstMessage = `r=${nonce},s=${salt},i=${iteration}`;
48
- const clientFinalMessageWithoutProof = `c=${encode64(GS2_HEADER)},r=${nonce}`;
70
+ // c= carries the header again, with the binding data appended when the
71
+ // channel is bound - so an attacker who relayed the exchange over a
72
+ // different TLS connection produces a different proof.
73
+ const cbind = session.bindingData
74
+ ? Buffer.concat([
75
+ Buffer.from(session.gs2Header, 'utf8'),
76
+ session.bindingData,
77
+ ]).toString('base64')
78
+ : encode64(session.gs2Header);
79
+ const clientFinalMessageWithoutProof = `c=${cbind},r=${nonce}`;
49
80
  const authMessage = `${firstMessageBare(session.username, session.nonce)},${serverFirstMessage},${clientFinalMessageWithoutProof}`;
50
81
  const saltPass = hi(password, salt, iteration);
51
82
  const clientKey = hmac(saltPass, CLIENT_KEY);
@@ -65,7 +96,7 @@ export var SASL;
65
96
  const arr = data.split(',');
66
97
  for (const s of arr) {
67
98
  if (s[0] === 'v')
68
- serverSignature = s.substr(2);
99
+ serverSignature = s.substring(2);
69
100
  }
70
101
  if (serverSignature !== session.serverSignature)
71
102
  throw new Error('SASL: Server signature does not match');
@@ -9,7 +9,6 @@ export declare class SmartBuffer extends BufferReader {
9
9
  static DEFAULT_MAX_SIZE: number;
10
10
  private readonly _houseKeepInterval;
11
11
  private _houseKeepTimer?;
12
- private _lastHouseKeep;
13
12
  private _stMaxPages;
14
13
  private _length;
15
14
  readonly pageSize: number;
@@ -20,7 +19,6 @@ export declare class SmartBuffer extends BufferReader {
20
19
  start(): this;
21
20
  flush(): Buffer;
22
21
  growSize(len: number): this;
23
- fill(value?: number, len?: number): this;
24
22
  writeCString(str: string, encoding?: BufferEncoding): this;
25
23
  writeLString(str?: string, encoding?: BufferEncoding): this;
26
24
  writeString(str: string, encoding?: BufferEncoding): this;
@@ -2,11 +2,10 @@ import * as os from 'os';
2
2
  import { writeBigUInt64BE } from '../util/bigint-methods.js';
3
3
  import { BufferReader } from './buffer-reader.js';
4
4
  export class SmartBuffer extends BufferReader {
5
- static DEFAULT_PAGE_SIZE = 4096;
5
+ static DEFAULT_PAGE_SIZE = 512;
6
6
  static DEFAULT_MAX_SIZE = Math.min(Math.floor(os.totalmem() / 2), 1024 * 1024 * 1024 * 2);
7
7
  _houseKeepInterval;
8
8
  _houseKeepTimer;
9
- _lastHouseKeep = 0;
10
9
  _stMaxPages = 1;
11
10
  _length = 0;
12
11
  pageSize;
@@ -38,15 +37,16 @@ export class SmartBuffer extends BufferReader {
38
37
  clearTimeout(this._houseKeepTimer);
39
38
  const length = this.length;
40
39
  this._length = 0;
41
- const out = this.buffer.slice(0, length);
40
+ const out = Buffer.from(this.buffer.subarray(0, length));
42
41
  const pages = length ? Math.ceil(length / this.pageSize) : 1;
43
42
  this._stMaxPages = Math.max(this._stMaxPages, pages);
44
- if (this._lastHouseKeep < Date.now() + this._houseKeepInterval)
45
- this._houseKeep();
46
- this._houseKeepTimer = setTimeout(() => {
47
- this._houseKeepTimer = undefined;
48
- this._houseKeep();
49
- }, this._houseKeepInterval).unref();
43
+ this._houseKeep();
44
+ if (this.buffer.length > this.pageSize) {
45
+ this._houseKeepTimer = setTimeout(() => {
46
+ this._houseKeepTimer = undefined;
47
+ this._houseKeep();
48
+ }, this._houseKeepInterval).unref();
49
+ }
50
50
  return out;
51
51
  }
52
52
  growSize(len) {
@@ -54,7 +54,8 @@ export class SmartBuffer extends BufferReader {
54
54
  if (this.capacity < endOffset) {
55
55
  if (endOffset > this.maxSize)
56
56
  throw new Error('Buffer limit exceeded.');
57
- const newSize = Math.ceil(endOffset / this.pageSize) * this.pageSize;
57
+ const byPage = Math.ceil(endOffset / this.pageSize) * this.pageSize;
58
+ const newSize = Math.min(Math.max(byPage, this.capacity * 2), this.maxSize);
58
59
  const newBuffer = Buffer.allocUnsafe(newSize);
59
60
  this.buffer.copy(newBuffer);
60
61
  this.buffer = newBuffer;
@@ -62,12 +63,6 @@ export class SmartBuffer extends BufferReader {
62
63
  this._length = Math.max(this.length, endOffset);
63
64
  return this;
64
65
  }
65
- fill(value = 0, len = 1) {
66
- this.ensureSize(len);
67
- this.buffer.fill(value, this.offset, this.offset + len);
68
- this.offset += len;
69
- return this;
70
- }
71
66
  writeCString(str, encoding) {
72
67
  const len = str ? Buffer.byteLength(str, encoding) : 0;
73
68
  this.ensureSize(len + 1);
package/types.d.ts CHANGED
@@ -5,11 +5,13 @@ export type Maybe<T> = T | undefined;
5
5
  export type Nullable<T> = T | null;
6
6
  export type Row = any;
7
7
  export type Callback = (err?: Error | null) => void;
8
- export type DecodeBinaryFunction = (buf: Buffer, options: DataMappingOptions & Record<string, any>) => any;
8
+ export type DecodeBinaryFunction = (buf: Buffer, offset: number, options: DataMappingOptions & Record<string, any>) => any;
9
+ export type DecodeTextBufferFunction = (buf: Buffer, offset: number, len: number, options: DataMappingOptions & Record<string, any>) => any;
9
10
  export type EncodeBinaryFunction = (buf: SmartBuffer, v: any, options: DataMappingOptions) => void;
10
11
  export type EncodeCalculateDimFunction = (v: any[]) => number[];
11
12
  export type EncodeAsNullFunction = (v: any, options: DataMappingOptions) => boolean;
12
- export type ParseTextFunction = (v: any, options: DataMappingOptions) => any;
13
+ export type DecodeTextFunction = (v: any, options: DataMappingOptions) => any;
13
14
  export type EncodeTextFunction = (v: any, options: DataMappingOptions) => string;
14
- export type AnyParseFunction = ParseTextFunction | DecodeBinaryFunction;
15
+ export type ResolvedColumnParser = (data: Buffer, offset: number, len: number, options: DataMappingOptions & Record<string, any>) => any;
16
+ export type AnyParseFunction = ResolvedColumnParser;
15
17
  export type DebugLogger = (namespace: string, format: any, ...args: any[]) => void;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The error an aborted operation rejects with.
3
+ *
4
+ * Prefers the signal's own reason, so `AbortSignal.timeout()` still reports
5
+ * itself as a TimeoutError and a caller's `controller.abort(myError)` still
6
+ * arrives intact. The database error that actually ended the query (SQLSTATE
7
+ * 57014) is attached as `cause` rather than replacing it: the reason is what
8
+ * callers branch on, the cause is what they need when reporting it.
9
+ */
10
+ export declare function abortError(signal: AbortSignal, cause?: unknown): Error;
11
+ /**
12
+ * Runs `run()` under an AbortSignal, asking `cancel()` to interrupt it if the
13
+ * signal fires.
14
+ *
15
+ * Cancelling a PostgreSQL query is a request, not a guarantee: it travels on
16
+ * its own connection and the statement may well finish first. So this does
17
+ * not settle early - it lets the query end the way the server ends it and
18
+ * only then reports the abort, which is also what keeps the connection
19
+ * usable, since its response still has to be read either way.
20
+ *
21
+ * The listener is always removed again: one long-lived signal shared by many
22
+ * queries would otherwise accumulate a listener per query.
23
+ *
24
+ * Not `async` itself: the overwhelmingly common case (no signal passed) just
25
+ * returns `run()`'s own promise straight through. Marking this function
26
+ * `async` would still be correct, but every `async function` return goes
27
+ * through the spec's promise-resolve machinery - a real extra microtask
28
+ * tick, even for a value that is already the right kind of promise - and
29
+ * this sits on every query's hot path, signal or not. The slow path (a
30
+ * signal actually given) is factored into its own async helper below, where
31
+ * the cost is negligible next to the abort-listener bookkeeping it already
32
+ * does.
33
+ */
34
+ export declare function withAbortSignal<T>(signal: AbortSignal | undefined, cancel: () => Promise<void>, run: () => Promise<T>): Promise<T>;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The error an aborted operation rejects with.
3
+ *
4
+ * Prefers the signal's own reason, so `AbortSignal.timeout()` still reports
5
+ * itself as a TimeoutError and a caller's `controller.abort(myError)` still
6
+ * arrives intact. The database error that actually ended the query (SQLSTATE
7
+ * 57014) is attached as `cause` rather than replacing it: the reason is what
8
+ * callers branch on, the cause is what they need when reporting it.
9
+ */
10
+ export function abortError(signal, cause) {
11
+ const reason = signal.reason;
12
+ const err = reason instanceof Error
13
+ ? reason
14
+ : Object.assign(new Error('The operation was aborted'), {
15
+ name: 'AbortError',
16
+ });
17
+ if (cause !== undefined && err.cause === undefined) {
18
+ try {
19
+ err.cause = cause;
20
+ }
21
+ catch {
22
+ // A reason the caller froze; the error itself is still correct.
23
+ }
24
+ }
25
+ return err;
26
+ }
27
+ /**
28
+ * Runs `run()` under an AbortSignal, asking `cancel()` to interrupt it if the
29
+ * signal fires.
30
+ *
31
+ * Cancelling a PostgreSQL query is a request, not a guarantee: it travels on
32
+ * its own connection and the statement may well finish first. So this does
33
+ * not settle early - it lets the query end the way the server ends it and
34
+ * only then reports the abort, which is also what keeps the connection
35
+ * usable, since its response still has to be read either way.
36
+ *
37
+ * The listener is always removed again: one long-lived signal shared by many
38
+ * queries would otherwise accumulate a listener per query.
39
+ *
40
+ * Not `async` itself: the overwhelmingly common case (no signal passed) just
41
+ * returns `run()`'s own promise straight through. Marking this function
42
+ * `async` would still be correct, but every `async function` return goes
43
+ * through the spec's promise-resolve machinery - a real extra microtask
44
+ * tick, even for a value that is already the right kind of promise - and
45
+ * this sits on every query's hot path, signal or not. The slow path (a
46
+ * signal actually given) is factored into its own async helper below, where
47
+ * the cost is negligible next to the abort-listener bookkeeping it already
48
+ * does.
49
+ */
50
+ export function withAbortSignal(signal, cancel, run) {
51
+ if (!signal)
52
+ return run();
53
+ // Already aborted: nothing has been sent, so nothing needs cancelling.
54
+ if (signal.aborted)
55
+ return Promise.reject(abortError(signal));
56
+ return runWithAbortSignal(signal, cancel, run);
57
+ }
58
+ async function runWithAbortSignal(signal, cancel, run) {
59
+ const onAbort = () => {
60
+ // A failed cancel (the session already gone, say) must not replace the
61
+ // error the query itself is about to report.
62
+ cancel().catch(() => undefined);
63
+ };
64
+ signal.addEventListener('abort', onAbort, { once: true });
65
+ try {
66
+ return await run();
67
+ }
68
+ catch (e) {
69
+ if (signal.aborted)
70
+ throw abortError(signal, e);
71
+ throw e;
72
+ }
73
+ finally {
74
+ signal.removeEventListener('abort', onAbort);
75
+ }
76
+ }
@@ -3,7 +3,9 @@ export function arrayCalculateDim(arr) {
3
3
  return [0];
4
4
  const dim = [arr.length];
5
5
  const iterate = (a, level) => {
6
- for (let i = 0; i < a.length; i++) {
6
+ let i;
7
+ const l = a.length;
8
+ for (i = 0; i < l; i++) {
7
9
  if (Array.isArray(a[i])) {
8
10
  dim[level] = Math.max(dim[level] || 0, a[i].length);
9
11
  iterate(a[i], level + 1);