node-firebird 2.9.0 → 2.10.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 (49) hide show
  1. package/README.md +166 -6
  2. package/lib/index.d.ts +5 -0
  3. package/lib/index.js +32 -1
  4. package/lib/pool.js +1 -1
  5. package/lib/srp.d.ts +3 -3
  6. package/lib/types.d.ts +143 -5
  7. package/lib/uri.js +2 -2
  8. package/lib/wire/connection.d.ts +92 -59
  9. package/lib/wire/connection.js +267 -53
  10. package/lib/wire/const.d.ts +9 -1
  11. package/lib/wire/const.js +21 -9
  12. package/lib/wire/database.d.ts +51 -26
  13. package/lib/wire/database.js +26 -8
  14. package/lib/wire/eventConnection.js +5 -3
  15. package/lib/wire/query-stream.d.ts +18 -0
  16. package/lib/wire/query-stream.js +73 -0
  17. package/lib/wire/serialize.d.ts +18 -2
  18. package/lib/wire/serialize.js +7 -0
  19. package/lib/wire/service.d.ts +42 -0
  20. package/lib/wire/service.js +145 -0
  21. package/lib/wire/socket.d.ts +3 -1
  22. package/lib/wire/socket.js +5 -2
  23. package/lib/wire/statement.d.ts +31 -19
  24. package/lib/wire/statement.js +1 -5
  25. package/lib/wire/transaction.d.ts +30 -18
  26. package/lib/wire/transaction.js +23 -4
  27. package/lib/wire/wire-types.d.ts +116 -0
  28. package/lib/wire/wire-types.js +10 -0
  29. package/lib/wire/xsqlvar.d.ts +18 -18
  30. package/package.json +1 -1
  31. package/src/index.ts +36 -4
  32. package/src/messages.ts +1 -1
  33. package/src/pool.ts +1 -1
  34. package/src/srp.ts +6 -6
  35. package/src/types.ts +140 -5
  36. package/src/unix-crypt.ts +9 -9
  37. package/src/uri.ts +2 -2
  38. package/src/wire/connection.ts +464 -232
  39. package/src/wire/const.ts +21 -9
  40. package/src/wire/database.ts +75 -43
  41. package/src/wire/eventConnection.ts +8 -5
  42. package/src/wire/query-stream.ts +80 -0
  43. package/src/wire/serialize.ts +29 -0
  44. package/src/wire/service.ts +188 -6
  45. package/src/wire/socket.ts +17 -8
  46. package/src/wire/statement.ts +37 -29
  47. package/src/wire/transaction.ts +57 -32
  48. package/src/wire/wire-types.ts +127 -0
  49. package/src/wire/xsqlvar.ts +9 -7
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - [Promises and async/await](#promises-and-asyncawait) — the `*Async` API plus `withConnection` / `withTransaction` helpers
14
14
  - [Connection types](#connection-types) — connection options, `firebird://` URIs and traditional connection strings, classic connections, pooling
15
15
  - [Database object (db)](#database-object-db) — database, transaction and statement methods/options
16
- - [Examples](#examples) — parametrized queries, named placeholders, BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
16
+ - [Examples](#examples) — parametrized queries, named placeholders, custom type parsers (typeCast), BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
17
17
  - [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, query cancellation (AbortSignal), batch execution (bulk inserts), statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
18
18
  - [Using node-firebird with Express.js](#using-node-firebird-with-expressjs)
19
19
  - [FAQ](#faq)
@@ -136,6 +136,7 @@ Available wrappers:
136
136
  - **database** — `db.queryAsync(sql, params?, options?)`, `executeAsync`, `executeBatchAsync(sql, rows, options?)`, `sequentiallyAsync(sql, params?, onRow, options?)`, `transactionAsync(options?)`, `newStatementAsync(sql)`, `attachEventAsync()`, `detachAsync()`, `dropAsync()`, `db.withTransaction(work, options?)`
137
137
  - **transaction** — `queryAsync`, `executeAsync`, `executeBatchAsync`, `sequentiallyAsync`, `newStatementAsync`, `commitAsync`, `rollbackAsync`, `commitRetainingAsync`, `rollbackRetainingAsync`
138
138
  - **statement** — `executeAsync(transaction, params?, options?)`, `executeBatchAsync(transaction, rows, options?)`, `fetchAsync`, `fetchScrollAsync`, `fetchAllAsync`, `closeAsync`, `dropAsync`, `releaseAsync`
139
+ - **service manager** — every [Service Manager function](#service-manager-functions) has an `*Async` counterpart (`backupAsync`, `restoreAsync`, `getUsersAsync`, `addUserAsync`, `getFbserverInfosAsync`, `startTraceAsync`, …); stream-producing functions resolve with the `Readable`, info functions with the info object
139
140
 
140
141
  Notes:
141
142
 
@@ -174,13 +175,18 @@ options.wireCrypt = Firebird.WIRE_CRYPT_ENABLE; // default; set to Firebird.WIRE
174
175
  options.pluginName = undefined; // optional, auto-negotiated; can be set to Firebird.AUTH_PLUGIN_SRP256, Firebird.AUTH_PLUGIN_SRP, or Firebird.AUTH_PLUGIN_LEGACY
175
176
  options.dbCryptConfig = undefined; // optional; database encryption key for encrypted databases. Use 'base64:<value>' for base64-encoded keys or plain text
176
177
  options.connectTimeout = 10000; // optional; timeout in ms for a single pool.get() attach operation (default: no timeout)
178
+ options.enableKeepAlive = true; // TCP keepalive probing to detect dead/stale connections (same option names as mysql2); set to false to disable
179
+ options.keepAliveInitialDelay = 60000; // ms a socket must be idle before the first keepalive probe (ignored when enableKeepAlive is false)
177
180
  options.parallelWorkers = undefined; // optional; request multiple thread workers for maintenance/index tasks (FB >= 5)
178
181
  options.maxInlineBlobSize = undefined; // optional; threshold size in bytes for inline blob transmission (default 65535, FB >= 5.0.3)
179
- options.maxNegotiatedProtocols = 10; // optional; limit maximum protocol versions negotiated (default 10 for compatibility, set to 11 for FB >= 6.0)
180
- options.defaultSchema = undefined; // optional; sets session CURRENT_SCHEMA at connect time (FB >= 6.0)
182
+ options.maxNegotiatedProtocols = undefined; // optional; cap how many protocol versions are offered, oldest first (default: all, up to Protocol 20; set to 10 to stop at Protocol 19)
183
+ options.defaultSchema = undefined; // optional; sets session CURRENT_SCHEMA at connect time by putting the schema first in the search path (FB >= 6.0)
181
184
  options.searchPath = undefined; // optional; ordered list/array of schemas to resolve unqualified object references (FB >= 6.0)
185
+ options.owner = undefined; // optional; owner of a newly created database — lets a superuser create a database for another user (create only, FB >= 6.0)
182
186
  options.jsonAsObject = false; // optional; automatically stringify parameters and parse query results that contain JSON (FB >= 6.0)
183
187
  options.namedPlaceholders = false; // set to true to allow :name placeholders in SQL with a { name: value } params object (see Named placeholders)
188
+ options.typeCast = undefined; // optional; custom type parser called for every result column value (see Custom type parsers)
189
+ options.statementCacheSize = 0; // optional; per-connection LRU cache of prepared statements, 0 = disabled (see Prepared-statement cache)
184
190
  ```
185
191
 
186
192
  ### Connection URI strings
@@ -486,6 +492,134 @@ the per-query option:
486
492
  await db.queryAsync(execBlockSql, [], { namedPlaceholders: false });
487
493
  ```
488
494
 
495
+ ### Custom type parsers (typeCast)
496
+
497
+ The `typeCast` connection option lets you override how column values are
498
+ decoded, per SQL type or per column — the same idea as mysql2's `typeCast`
499
+ and pg's `setTypeParser`. The hook is called for **every column value of
500
+ every result row** (including `NULL`s); whatever it returns becomes the
501
+ value in the row. Call `next()` to get the value the driver would produce
502
+ by default (after `blobAsText` / `jsonAsObject` are applied).
503
+
504
+ ```js
505
+ const Firebird = require('node-firebird');
506
+
507
+ Firebird.attach({
508
+ ...options,
509
+ typeCast: (column, next) => {
510
+ // dates as ISO strings instead of Date objects
511
+ if (column.typeName === 'DATE') {
512
+ const v = next();
513
+ return v === null ? null : v.toISOString().slice(0, 10);
514
+ }
515
+ // BIGINT columns as strings
516
+ if (column.type === Firebird.SQL_TYPES.SQL_INT64 && !column.scale) {
517
+ return String(next());
518
+ }
519
+ return next(); // everything else: default decoding
520
+ },
521
+ }, (err, db) => { /* ... */ });
522
+ ```
523
+
524
+ `column` describes the result column:
525
+
526
+ | Property | Meaning |
527
+ | :--------- | :------------------------------------------------------------------ |
528
+ | `type` | Firebird SQL type code — compare against `Firebird.SQL_TYPES.*` |
529
+ | `typeName` | Friendly name: `'VARYING'`, `'INT64'`, `'DATE'`, `'BLOB'`, ... |
530
+ | `subType` | Column subtype (`1` = text for BLOBs) |
531
+ | `scale` | Negative decimal scale for `NUMERIC`/`DECIMAL` (e.g. `-2`) |
532
+ | `length` | Declared length in bytes |
533
+ | `field` | Column name in the table |
534
+ | `relation` | Table name |
535
+ | `alias` | SELECT-list alias (the row key for object rows) |
536
+
537
+ Notes:
538
+
539
+ - Non-text BLOB columns reach the hook as the usual asynchronous fetch
540
+ function; text BLOBs with `blobAsText: true` reach it as the resolved
541
+ string.
542
+ - The hook must be a **pure function** of its inputs: when a response
543
+ spans multiple TCP packets the affected rows can be decoded more than
544
+ once, calling the hook again for the same value.
545
+ - The hook runs for every value on the hot row-decoding path — keep it
546
+ cheap, and prefer dispatching on `column.type`/`column.typeName` early.
547
+ - Exceptions thrown by the hook are caught: the default value is used and
548
+ a warning is printed. A throw cannot be allowed to escape into the wire
549
+ decoder, so validate inside the hook and encode failures in the value.
550
+
551
+ ### Prepared-statement cache
552
+
553
+ Setting `statementCacheSize` keeps a per-connection LRU cache of prepared
554
+ statements (like mysql2's statement cache): running the same SQL string
555
+ again transparently reuses the already-prepared server-side statement,
556
+ skipping the prepare round-trip. No API changes are needed — `db.query`,
557
+ `tx.query`, `sequentially`, `executeBatch` and the `*Async` wrappers all
558
+ benefit automatically.
559
+
560
+ ```js
561
+ Firebird.attach({ ...options, statementCacheSize: 100 }, (err, db) => {
562
+ // the second identical query reuses the prepared statement
563
+ db.query('SELECT * FROM t WHERE id = ?', [1], () => {
564
+ db.query('SELECT * FROM t WHERE id = ?', [2], () => { /* ... */ });
565
+ });
566
+ });
567
+ ```
568
+
569
+ How it works:
570
+
571
+ - The number is the maximum of **idle** statements kept per connection;
572
+ the least-recently-used statement is dropped when the limit is exceeded.
573
+ - A cached statement leaves the cache while in use, so concurrent runs of
574
+ the same SQL never share a server-side cursor — extra preparations run
575
+ in parallel and only one goes back into the cache.
576
+ - Statements that failed and DDL statements are never cached.
577
+ - Cache keys are exact SQL strings (after the `namedPlaceholders`
578
+ rewrite), so use parametrized queries to get hits.
579
+ - The legacy `cacheQuery: true` / `maxCachedQuery` options remain
580
+ supported and now map onto the same LRU cache (with a default limit of
581
+ 100 instead of the old unbounded map).
582
+
583
+ > **Note (DDL):** a statement prepared before a metadata change (e.g.
584
+ > `ALTER TABLE`) may fail when reused. If you mix DDL with hot queries on
585
+ > the same connection, keep the cache small or disabled.
586
+
587
+ ### Streaming rows with queryStream
588
+
589
+ `db.queryStream(sql, params, options)` returns an **object-mode
590
+ `Readable`** emitting one row per chunk — the counterpart of
591
+ `pg-query-stream` and mysql2's `.stream()`. It is built on
592
+ `sequentially()`'s backpressure: fetching from the server pauses while
593
+ the stream's buffer is full and resumes as the consumer drains it, so
594
+ constant memory is used regardless of the result size.
595
+
596
+ ```js
597
+ const { pipeline } = require('stream/promises');
598
+
599
+ // async iteration
600
+ for await (const row of db.queryStream('SELECT * FROM big_table')) {
601
+ console.log(row.ID);
602
+ }
603
+
604
+ // or piping into any Writable/Transform (HTTP response, CSV encoder, ...)
605
+ await pipeline(
606
+ db.queryStream('SELECT * FROM big_table WHERE grp = ?', [42]),
607
+ myCsvTransform,
608
+ res);
609
+ ```
610
+
611
+ - `db.queryStream` runs in its own transaction (like `db.query`);
612
+ `transaction.queryStream` runs inside your transaction, which is *not*
613
+ committed when the stream ends.
614
+ - Destroying the stream early — including an error mid-`pipeline()` —
615
+ aborts the fetch and releases the statement; the connection stays
616
+ usable.
617
+ - Options: everything `query` accepts (e.g. `signal`), plus
618
+ `highWaterMark` (rows buffered before fetching pauses, default 16) and
619
+ `asObject: false` for array rows.
620
+ - Rows go through the regular decode path, so `typeCast`, `blobAsText`
621
+ and `jsonAsObject` all apply.
622
+
489
623
  ### Tablespaces and Schema Partitioning (Firebird 6.0+)
490
624
 
491
625
  For Firebird 6.0+ (Protocol 20+), you can create and manage physical tablespace locations and logical schema namespaces, optionally partitioning schemas into specific physical tablespaces.
@@ -1020,6 +1154,26 @@ var fbsvc = {
1020
1154
 
1021
1155
  ```
1022
1156
 
1157
+ Every function also has a promise-returning `*Async` counterpart (no
1158
+ callback argument): stream-producing functions resolve with the
1159
+ `Readable`, info functions with the info object.
1160
+
1161
+ ```js
1162
+ const svc = await Firebird.attachAsync({ ...options, manager: true });
1163
+ try {
1164
+ const info = await svc.getFbserverInfosAsync();
1165
+ console.log(info.fbversion);
1166
+
1167
+ const backup = await svc.backupAsync({
1168
+ database: '/DB/MYDB.FDB',
1169
+ files: [{ filename: '/DB/MYDB.FBK' }]
1170
+ });
1171
+ for await (const line of backup) console.log(line);
1172
+ } finally {
1173
+ await svc.detachAsync();
1174
+ }
1175
+ ```
1176
+
1023
1177
  ### Backup Service example
1024
1178
 
1025
1179
  ```js
@@ -1758,13 +1912,13 @@ db.transaction(function (err, tx) {
1758
1912
 
1759
1913
  #### Is the wire protocol version hard-coded?
1760
1914
 
1761
- No. node-firebird negotiates the highest protocol version both the client and server support, up to `options.maxNegotiatedProtocols` (default `10`, i.e. Protocol 19 see [Protocol Implementation Status](ROADMAP.md#4-protocol-implementation-status) in the roadmap for the full version table). Raise it if you're on Firebird 6.0 and want to attempt Protocol 20:
1915
+ No. node-firebird negotiates the highest protocol version both the client and server support up to Protocol 20 (Firebird 6.0) by default; see [Protocol Implementation Status](ROADMAP.md#4-protocol-implementation-status) in the roadmap for the full version table. Servers ignore protocol versions they do not know, so offering the full list is safe on old servers too. To cap negotiation at an older protocol, limit how many versions are offered (the list is ordered oldest first):
1762
1916
 
1763
1917
  ```js
1764
- options.maxNegotiatedProtocols = 11; // offers Protocol 20 as well as 19
1918
+ options.maxNegotiatedProtocols = 10; // stop at Protocol 19 (pre-Firebird 6 behavior)
1765
1919
  ```
1766
1920
 
1767
- The default is capped at 10 (Protocol 19) rather than the full list because Protocol 20 has a known query-preparation hang on some Firebird 6.0 builds see the "Firebird 6 and Beyond" note in [ROADMAP.md](ROADMAP.md#4-protocol-implementation-status).
1921
+ Historical note: Protocol 20 used to be excluded by default because of a query-preparation hang the client did not send the `p_sqlst_flags` field that protocol 20 added to `op_prepare_statement`, leaving the server blocked mid-packet. The field is now sent and Protocol 20 is fully negotiated.
1768
1922
 
1769
1923
  #### BLOB reads/writes are very slow, especially for large files over a remote connection
1770
1924
 
@@ -1821,6 +1975,12 @@ db.query('SELECT * FROM ACTORS WHERE NAME LIKE ?', ['James Wick%'], function (er
1821
1975
  });
1822
1976
  ```
1823
1977
 
1978
+ #### attach() *sometimes* fails with gdscode 335544472 ("Your user name and password are not defined") even though the credentials are correct
1979
+
1980
+ If the failure is intermittent — the same code with the same credentials succeeds on most attempts and fails on others — update the driver: versions before 2.8.1 had two serialization mismatches in the SRP (Srp/Srp256/384/512) proof-of-password computation that made roughly 1 in 80 attaches fail with exactly this error (whenever the ephemeral SRP values happened to have a leading zero byte). Fixed in 2.8.1; see [issue #421](https://github.com/hgourvest/node-firebird/issues/421) and [issue #347](https://github.com/hgourvest/node-firebird/issues/347). Switching the user to `Legacy_UserManager`/`Legacy_Auth` "fixed" it on older versions only because that avoids the SRP code path entirely — with 2.8.1+ this workaround is no longer needed.
1981
+
1982
+ If the failure is consistent, the credentials really don't match an account for the authentication plugin in use: check `AuthServer`/`UserManager` in `firebird.conf` and remember that SRP and Legacy user managers keep separate password stores — a user created under one plugin does not automatically exist for the other.
1983
+
1824
1984
  ## Contributing
1825
1985
 
1826
1986
  Contributions are welcome — code, documentation, and bug reports alike.
package/lib/index.d.ts CHANGED
@@ -24,6 +24,11 @@ export declare const ISOLATION_REPEATABLE_READ: number[];
24
24
  export declare const ISOLATION_SERIALIZABLE: number[];
25
25
  export declare const ISOLATION_READ_COMMITTED_READ_ONLY: number[];
26
26
  export declare const escape: typeof escapeValue;
27
+ /**
28
+ * Firebird SQL type codes, as seen in `column.type` inside a `typeCast`
29
+ * hook (each code also has a friendly `column.typeName`).
30
+ */
31
+ export declare const SQL_TYPES: Readonly<Record<string, number>>;
27
32
  /**
28
33
  * The most recent Connection created by attach()/create()/attachOrCreate().
29
34
  * Kept for backwards compatibility with the previous CommonJS module where
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.parseNamedPlaceholders = exports.parseConnectionString = exports.parseConnectionUri = exports.connection = exports.escape = exports.ISOLATION_READ_COMMITTED_READ_ONLY = exports.ISOLATION_SERIALIZABLE = exports.ISOLATION_REPEATABLE_READ = exports.ISOLATION_READ_COMMITTED = exports.ISOLATION_READ_UNCOMMITTED = exports.WIRE_CRYPT_ENABLE = exports.WIRE_CRYPT_DISABLE = exports.AUTH_PLUGIN_SRP512 = exports.AUTH_PLUGIN_SRP384 = exports.AUTH_PLUGIN_SRP256 = exports.AUTH_PLUGIN_SRP = exports.AUTH_PLUGIN_LEGACY = exports.GDSCode = void 0;
20
+ exports.parseNamedPlaceholders = exports.parseConnectionString = exports.parseConnectionUri = exports.connection = exports.SQL_TYPES = exports.escape = exports.ISOLATION_READ_COMMITTED_READ_ONLY = exports.ISOLATION_SERIALIZABLE = exports.ISOLATION_REPEATABLE_READ = exports.ISOLATION_READ_COMMITTED = exports.ISOLATION_READ_UNCOMMITTED = exports.WIRE_CRYPT_ENABLE = exports.WIRE_CRYPT_DISABLE = exports.AUTH_PLUGIN_SRP512 = exports.AUTH_PLUGIN_SRP384 = exports.AUTH_PLUGIN_SRP256 = exports.AUTH_PLUGIN_SRP = exports.AUTH_PLUGIN_LEGACY = exports.GDSCode = void 0;
21
21
  exports.attach = attach;
22
22
  exports.drop = drop;
23
23
  exports.create = create;
@@ -63,6 +63,35 @@ exports.ISOLATION_REPEATABLE_READ = const_1.default.ISOLATION_REPEATABLE_READ;
63
63
  exports.ISOLATION_SERIALIZABLE = const_1.default.ISOLATION_SERIALIZABLE;
64
64
  exports.ISOLATION_READ_COMMITTED_READ_ONLY = const_1.default.ISOLATION_READ_COMMITTED_READ_ONLY;
65
65
  exports.escape = utils_1.escape;
66
+ /**
67
+ * Firebird SQL type codes, as seen in `column.type` inside a `typeCast`
68
+ * hook (each code also has a friendly `column.typeName`).
69
+ */
70
+ exports.SQL_TYPES = Object.freeze({
71
+ SQL_TEXT: const_1.default.SQL_TEXT,
72
+ SQL_VARYING: const_1.default.SQL_VARYING,
73
+ SQL_SHORT: const_1.default.SQL_SHORT,
74
+ SQL_LONG: const_1.default.SQL_LONG,
75
+ SQL_FLOAT: const_1.default.SQL_FLOAT,
76
+ SQL_DOUBLE: const_1.default.SQL_DOUBLE,
77
+ SQL_D_FLOAT: const_1.default.SQL_D_FLOAT,
78
+ SQL_TIMESTAMP: const_1.default.SQL_TIMESTAMP,
79
+ SQL_BLOB: const_1.default.SQL_BLOB,
80
+ SQL_ARRAY: const_1.default.SQL_ARRAY,
81
+ SQL_QUAD: const_1.default.SQL_QUAD,
82
+ SQL_TYPE_TIME: const_1.default.SQL_TYPE_TIME,
83
+ SQL_TYPE_DATE: const_1.default.SQL_TYPE_DATE,
84
+ SQL_INT64: const_1.default.SQL_INT64,
85
+ SQL_INT128: const_1.default.SQL_INT128,
86
+ SQL_TIMESTAMP_TZ: const_1.default.SQL_TIMESTAMP_TZ,
87
+ SQL_TIMESTAMP_TZ_EX: const_1.default.SQL_TIMESTAMP_TZ_EX,
88
+ SQL_TIME_TZ: const_1.default.SQL_TIME_TZ,
89
+ SQL_TIME_TZ_EX: const_1.default.SQL_TIME_TZ_EX,
90
+ SQL_DEC16: const_1.default.SQL_DEC16,
91
+ SQL_DEC34: const_1.default.SQL_DEC34,
92
+ SQL_BOOLEAN: const_1.default.SQL_BOOLEAN,
93
+ SQL_NULL: const_1.default.SQL_NULL,
94
+ });
66
95
  function attach(options, callback) {
67
96
  options = (0, uri_1.normalizeOptions)(options);
68
97
  var host = options.host || const_1.default.DEFAULT_HOST;
@@ -135,6 +164,8 @@ function attachOrCreate(options, callback) {
135
164
  if (!err) {
136
165
  if (self.db)
137
166
  self.db.emit('connect', ret);
167
+ // DatabaseCallback stays permissive (db non-optional) for
168
+ // API users; internally the error path passes no db
138
169
  (0, callback_1.doCallback)(ret, callback);
139
170
  return;
140
171
  }
package/lib/pool.js CHANGED
@@ -141,7 +141,7 @@ class Pool extends events_1.default.EventEmitter {
141
141
  return self;
142
142
  if ((self.dbinuse + self._creating) >= self.max)
143
143
  return self;
144
- var cb = self.pending.shift();
144
+ const cb = self.pending.shift();
145
145
  if (!cb)
146
146
  return self;
147
147
  if (self.pooldb.length) {
package/lib/srp.d.ts CHANGED
@@ -22,7 +22,7 @@ export declare function clientSeed(a?: bigint): KeyPair;
22
22
  * @param b BigInt Server private key.
23
23
  * @returns {{private: BigInt, public: BigInt}}
24
24
  */
25
- export declare function serverSeed(user: string, password: string, salt: Buffer, b?: bigint | string, hashAlgo?: string): KeyPair;
25
+ export declare function serverSeed(user: string, password: string, salt: Buffer | string, b?: bigint | string, hashAlgo?: string): KeyPair;
26
26
  /**
27
27
  * Server session secret.
28
28
  *
@@ -34,11 +34,11 @@ export declare function serverSeed(user: string, password: string, salt: Buffer,
34
34
  * @param b BigInt Server private key.
35
35
  * @returns {BigInt}
36
36
  */
37
- export declare function serverSession(user: string, password: string, salt: Buffer, A: bigint, B: bigint, b: bigint, hashAlgo?: string): bigint;
37
+ export declare function serverSession(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, b: bigint, hashAlgo?: string): bigint;
38
38
  /**
39
39
  * M = H(H(N) xor H(g), H(I), s, A, B, K)
40
40
  */
41
- export declare function clientProof(user: string, password: string, salt: Buffer, A: bigint, B: bigint, a: bigint, hashAlgo?: string): ClientProof;
41
+ export declare function clientProof(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, a: bigint, hashAlgo?: string): ClientProof;
42
42
  /**
43
43
  * Pad hex string.
44
44
  */
package/lib/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Readable } from 'stream';
1
2
  export type DatabaseCallback = (err: any, db: Database) => void;
2
3
  export type TransactionCallback = (err: any, transaction: Transaction) => void;
3
4
  export type QueryCallback = (err: any, result: any[]) => void;
@@ -115,6 +116,15 @@ export type QueryOptions = {
115
116
  */
116
117
  signal?: AbortSignal;
117
118
  };
119
+ export type QueryStreamOptions = QueryOptions & {
120
+ /**
121
+ * Rows buffered internally before fetching pauses (object-mode
122
+ * Readable highWaterMark, default 16).
123
+ */
124
+ highWaterMark?: number;
125
+ /** Emit array rows instead of objects (like db.execute). */
126
+ asObject?: boolean;
127
+ };
118
128
  export interface Database {
119
129
  detach(callback?: SimpleCallback): Database;
120
130
  transaction(options: TransactionOptions | Isolation | TransactionCallback, callback?: TransactionCallback): Database;
@@ -124,6 +134,13 @@ export interface Database {
124
134
  /** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
125
135
  executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
126
136
  sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
137
+ /**
138
+ * Run `query` and return an object-mode Readable emitting one row per
139
+ * chunk, with backpressure (fetching pauses while the buffer is full).
140
+ * Runs in its own transaction. Destroying the stream early aborts the
141
+ * fetch and releases the statement.
142
+ */
143
+ queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): Readable;
127
144
  drop(callback: SimpleCallback): void;
128
145
  escape(value: any): string;
129
146
  attachEvent(callback: any): this;
@@ -160,6 +177,12 @@ export interface Transaction {
160
177
  /** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
161
178
  executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
162
179
  sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
180
+ /**
181
+ * Run `query` inside this transaction and return an object-mode
182
+ * Readable emitting one row per chunk, with backpressure. The
183
+ * transaction is NOT committed when the stream ends.
184
+ */
185
+ queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): Readable;
163
186
  commit(callback?: SimpleCallback): void;
164
187
  commitRetaining(callback?: SimpleCallback): void;
165
188
  rollback(callback?: SimpleCallback): void;
@@ -231,6 +254,16 @@ export interface Options {
231
254
  * per-query `namedPlaceholders: false` override.
232
255
  */
233
256
  namedPlaceholders?: boolean;
257
+ /**
258
+ * TCP keepalive probing to detect dead/stale connections (same option
259
+ * names as mysql2). On by default; set false to disable.
260
+ */
261
+ enableKeepAlive?: boolean;
262
+ /**
263
+ * Milliseconds a socket must be idle before the first TCP keepalive
264
+ * probe is sent (default 60000). Ignored when enableKeepAlive is false.
265
+ */
266
+ keepAliveInitialDelay?: number;
234
267
  pluginName?: string;
235
268
  parallelWorkers?: number;
236
269
  maxInlineBlobSize?: number;
@@ -262,11 +295,11 @@ export interface Options {
262
295
  /**
263
296
  * **Firebird 6.0+ only (Protocol 20+)**
264
297
  *
265
- * Sets the session's current schema at connection time. Equivalent to
266
- * executing `SET SCHEMA <name>` immediately after connecting.
267
- *
268
- * Unqualified object references (tables, procedures, etc.) that do not
269
- * match any schema in the `searchPath` fall back to `PUBLIC`.
298
+ * Sets the session's current schema at connection time. `CURRENT_SCHEMA`
299
+ * in Firebird is the first existing schema of the search path, so this
300
+ * option is implemented by putting the schema at the front of the
301
+ * `searchPath` sent to the server (with `PUBLIC` kept as a fallback when
302
+ * no explicit `searchPath` is given).
270
303
  *
271
304
  * Example: `defaultSchema: 'myapp'`
272
305
  */
@@ -288,6 +321,18 @@ export interface Options {
288
321
  * (typically `PUBLIC` then `SYSTEM`).
289
322
  */
290
323
  searchPath?: string | string[];
324
+ /**
325
+ * **Firebird 6.0+ only**
326
+ *
327
+ * Owner of a newly created database (`isc_dpb_owner`), allowing a
328
+ * superuser to create a database owned by another user
329
+ * ([firebird#7718](https://github.com/FirebirdSQL/firebird/issues/7718)).
330
+ * Only honored by `create`/`attachOrCreate` when the database is
331
+ * created; ignored on plain attach and by older servers.
332
+ *
333
+ * Example: `owner: 'APP_OWNER'`
334
+ */
335
+ owner?: string;
291
336
  /**
292
337
  * **Firebird 6.0+ only (Protocol 20+)**
293
338
  *
@@ -296,7 +341,55 @@ export interface Options {
296
341
  * text/BLOB columns back into JavaScript objects/arrays.
297
342
  */
298
343
  jsonAsObject?: boolean;
344
+ /**
345
+ * Custom type parser (mysql2-style). Called for every column value of
346
+ * every result row (including NULLs); whatever it returns becomes the
347
+ * value in the row. Call `next()` to get the value the driver would
348
+ * produce by default (after `blobAsText`/`jsonAsObject` are applied).
349
+ *
350
+ * ```js
351
+ * typeCast: (column, next) =>
352
+ * column.typeName === 'INT64' ? Number(next()) : next()
353
+ * ```
354
+ *
355
+ * Non-text BLOB columns reach the hook as the usual fetch function;
356
+ * text BLOBs with `blobAsText` reach it as the resolved string. The
357
+ * hook must be a pure function: a row can be decoded more than once
358
+ * when a response spans TCP packets.
359
+ */
360
+ typeCast?: TypeCastFunction;
361
+ /**
362
+ * Per-connection LRU cache of prepared statements (like mysql2's
363
+ * statement cache). `db.query`/`tx.query` and friends transparently
364
+ * reuse the prepared handle for a repeated SQL string, skipping the
365
+ * prepare round-trip on hot paths. The number is the maximum of idle
366
+ * cached statements; least-recently-used ones are dropped over the
367
+ * limit. 0 / unset = disabled. Statements that failed and DDL are
368
+ * never cached; concurrent runs of the same SQL never share a
369
+ * statement (extra preparations are simply not cached).
370
+ */
371
+ statementCacheSize?: number;
299
372
  }
373
+ /** Column metadata passed to the {@link Options.typeCast} hook. */
374
+ export interface TypeCastColumn {
375
+ /** Firebird SQL type code (see the exported `SQL_TYPES` map). */
376
+ type: number;
377
+ /** Friendly name of the type code: 'VARYING', 'INT64', 'BLOB', ... */
378
+ typeName: string;
379
+ /** Column subtype (e.g. 1 = text for BLOBs; charset id for strings). */
380
+ subType?: number;
381
+ /** Negative decimal scale for NUMERIC/DECIMAL columns (e.g. -2). */
382
+ scale?: number;
383
+ /** Declared length in bytes. */
384
+ length?: number;
385
+ /** Column name in the table. */
386
+ field?: string;
387
+ /** Table (relation) name. */
388
+ relation?: string;
389
+ /** Alias used in the SELECT list (the row key for object rows). */
390
+ alias?: string;
391
+ }
392
+ export type TypeCastFunction = (column: TypeCastColumn, next: () => any) => any;
300
393
  export interface SvcMgrOptions extends Options {
301
394
  manager: true;
302
395
  }
@@ -506,4 +599,49 @@ export interface ServiceManager {
506
599
  hasRunningAction(options: ReadableOptions, callback: ReadableCallback): void;
507
600
  readusers(options: ReadableOptions, callback: ReadableCallback): void;
508
601
  readlimbo(options: ReadableOptions, callback: ReadableCallback): void;
602
+ detachAsync(force?: boolean): Promise<void>;
603
+ backupAsync(options: BackupOptions): Promise<NodeJS.ReadableStream>;
604
+ nbackupAsync(options: BackupOptions): Promise<NodeJS.ReadableStream>;
605
+ restoreAsync(options: RestoreOptions): Promise<NodeJS.ReadableStream>;
606
+ nrestoreAsync(options: NRestoreOptions): Promise<NodeJS.ReadableStream>;
607
+ setDialectAsync(db: string, dialect: 1 | 3): Promise<NodeJS.ReadableStream>;
608
+ setSweepintervalAsync(db: string, interval: number): Promise<any>;
609
+ setCachebufferAsync(db: string, nbpages: any): Promise<NodeJS.ReadableStream>;
610
+ BringOnlineAsync(db: string): Promise<NodeJS.ReadableStream>;
611
+ ShutdownAsync(db: string, kind: ShutdownKind, delay: number, mode?: ShutdownMode): Promise<NodeJS.ReadableStream>;
612
+ setShadowAsync(db: string, val: boolean): Promise<NodeJS.ReadableStream>;
613
+ setForcewriteAsync(db: string, val: boolean): Promise<NodeJS.ReadableStream>;
614
+ setReservespaceAsync(db: string, val: boolean): Promise<NodeJS.ReadableStream>;
615
+ setReadonlyModeAsync(db: string): Promise<NodeJS.ReadableStream>;
616
+ setReadwriteModeAsync(db: string): Promise<NodeJS.ReadableStream>;
617
+ validateAsync(options: ValidateOptions): Promise<NodeJS.ReadableStream>;
618
+ commitAsync(db: string, transactid: number): Promise<NodeJS.ReadableStream>;
619
+ rollbackAsync(db: string, transactid: number): Promise<NodeJS.ReadableStream>;
620
+ recoverAsync(db: string, transactid: number): Promise<NodeJS.ReadableStream>;
621
+ getStatsAsync(options: StatsOptions): Promise<NodeJS.ReadableStream>;
622
+ getLogAsync(options: ReadableOptions): Promise<NodeJS.ReadableStream>;
623
+ getUsersAsync(username?: string | null): Promise<ServerInfo>;
624
+ addUserAsync(username: string, password: string, info?: UserInfo): Promise<NodeJS.ReadableStream>;
625
+ editUserAsync(username: string, info: UserInfo): Promise<NodeJS.ReadableStream>;
626
+ removeUserAsync(username: string, rolename?: string | null): Promise<NodeJS.ReadableStream>;
627
+ getFbserverInfosAsync(infos?: ServerInfoReq, options?: {
628
+ buffersize?: number;
629
+ timeout?: number;
630
+ }): Promise<ServerInfo>;
631
+ startTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
632
+ suspendTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
633
+ resumeTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
634
+ stopTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
635
+ getTraceListAsync(options?: ReadableOptions): Promise<NodeJS.ReadableStream>;
636
+ readlineAsync(options?: ReadableOptions): Promise<{
637
+ result: number;
638
+ line: string;
639
+ }>;
640
+ readeofAsync(options?: ReadableOptions): Promise<{
641
+ result: number;
642
+ line: string;
643
+ }>;
644
+ hasRunningActionAsync(options?: ReadableOptions): Promise<any>;
645
+ readusersAsync(options?: ReadableOptions): Promise<any>;
646
+ readlimboAsync(options?: ReadableOptions): Promise<any>;
509
647
  }
package/lib/uri.js CHANGED
@@ -15,14 +15,14 @@ exports.normalizeOptions = normalizeOptions;
15
15
  */
16
16
  const BOOLEAN_KEYS = new Set([
17
17
  'lowercase_keys', 'blobAsText', 'wireCompression', 'manager',
18
- 'namedPlaceholders',
18
+ 'namedPlaceholders', 'enableKeepAlive',
19
19
  ]);
20
20
  /** Option keys coerced to number when they arrive as URI query parameters. */
21
21
  const NUMBER_KEYS = new Set([
22
22
  'port', 'pageSize', 'timeout', 'retryConnectionInterval',
23
23
  'blobChunkSize', 'blobReadChunkSize', 'wireCrypt', 'parallelWorkers',
24
24
  'maxInlineBlobSize', 'maxNegotiatedProtocols', 'connectTimeout',
25
- 'min', 'idleTimeoutMillis',
25
+ 'min', 'idleTimeoutMillis', 'keepAliveInitialDelay',
26
26
  ]);
27
27
  function coerce(key, value) {
28
28
  if (BOOLEAN_KEYS.has(key)) {