node-firebird 2.8.1 → 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 (53) hide show
  1. package/README.md +268 -7
  2. package/lib/index.d.ts +17 -9
  3. package/lib/index.js +42 -3
  4. package/lib/named-params.d.ts +42 -0
  5. package/lib/named-params.js +133 -0
  6. package/lib/pool.js +1 -1
  7. package/lib/srp.d.ts +3 -3
  8. package/lib/types.d.ts +185 -25
  9. package/lib/uri.d.ts +57 -0
  10. package/lib/uri.js +193 -0
  11. package/lib/wire/connection.d.ts +92 -59
  12. package/lib/wire/connection.js +286 -55
  13. package/lib/wire/const.d.ts +9 -1
  14. package/lib/wire/const.js +21 -9
  15. package/lib/wire/database.d.ts +51 -26
  16. package/lib/wire/database.js +26 -8
  17. package/lib/wire/eventConnection.js +5 -3
  18. package/lib/wire/query-stream.d.ts +18 -0
  19. package/lib/wire/query-stream.js +73 -0
  20. package/lib/wire/serialize.d.ts +18 -2
  21. package/lib/wire/serialize.js +7 -0
  22. package/lib/wire/service.d.ts +42 -0
  23. package/lib/wire/service.js +145 -0
  24. package/lib/wire/socket.d.ts +3 -1
  25. package/lib/wire/socket.js +5 -2
  26. package/lib/wire/statement.d.ts +40 -20
  27. package/lib/wire/statement.js +26 -6
  28. package/lib/wire/transaction.d.ts +32 -18
  29. package/lib/wire/transaction.js +50 -8
  30. package/lib/wire/wire-types.d.ts +116 -0
  31. package/lib/wire/wire-types.js +10 -0
  32. package/lib/wire/xsqlvar.d.ts +18 -18
  33. package/package.json +1 -1
  34. package/src/index.ts +54 -15
  35. package/src/messages.ts +1 -1
  36. package/src/named-params.ts +145 -0
  37. package/src/pool.ts +1 -1
  38. package/src/srp.ts +6 -6
  39. package/src/types.ts +183 -25
  40. package/src/unix-crypt.ts +9 -9
  41. package/src/uri.ts +204 -0
  42. package/src/wire/connection.ts +481 -234
  43. package/src/wire/const.ts +21 -9
  44. package/src/wire/database.ts +75 -43
  45. package/src/wire/eventConnection.ts +8 -5
  46. package/src/wire/query-stream.ts +80 -0
  47. package/src/wire/serialize.ts +29 -0
  48. package/src/wire/service.ts +188 -6
  49. package/src/wire/socket.ts +17 -8
  50. package/src/wire/statement.ts +68 -31
  51. package/src/wire/transaction.ts +85 -33
  52. package/src/wire/wire-types.ts +127 -0
  53. package/src/wire/xsqlvar.ts +9 -7
package/README.md CHANGED
@@ -11,9 +11,9 @@
11
11
  - [Installation](#installation)
12
12
  - [Usage](#usage) — including [developing the driver](#developing-the-driver)
13
13
  - [Promises and async/await](#promises-and-asyncawait) — the `*Async` API plus `withConnection` / `withTransaction` helpers
14
- - [Connection types](#connection-types) — connection options, classic connections, pooling
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, 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,14 +175,80 @@ 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)
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)
183
190
  ```
184
191
 
192
+ ### Connection URI strings
193
+
194
+ Everywhere an options object is accepted — `attach`, `create`,
195
+ `attachOrCreate`, `drop`, `Firebird.pool()` and their `*Async`
196
+ counterparts — a `firebird://` URI string works too, which is handy for
197
+ 12-factor apps and containers that configure the database via a single
198
+ environment variable:
199
+
200
+ ```js
201
+ const db = await Firebird.attachAsync(process.env.DATABASE_URL);
202
+ // e.g. DATABASE_URL=firebird://SYSDBA:masterkey@db.example.com:3050//var/fb/prod.fdb?encoding=UTF8
203
+
204
+ const pool = Firebird.pool(10,
205
+ 'firebird://app:secret@localhost/appdb?lowercase_keys=true&idleTimeoutMillis=30000');
206
+ ```
207
+
208
+ The database part after `host[:port]/` can be:
209
+
210
+ | URI | database |
211
+ | :--- | :--- |
212
+ | `firebird://host/employee` | the alias `employee` |
213
+ | `firebird://host//var/fb/prod.fdb` | `/var/fb/prod.fdb` (explicit absolute path) |
214
+ | `firebird://host/var/fb/prod.fdb` | `/var/fb/prod.fdb` (a database part with `/` is a path — aliases cannot contain slashes) |
215
+ | `firebird://host/C:/fbdata/prod.fdb` | the Windows path `C:/fbdata/prod.fdb` |
216
+
217
+ Query parameters map 1:1 onto the connection options above and are coerced
218
+ to the right type (`?pageSize=8192&lowercase_keys=true&wireCompression=1`).
219
+ Credentials and paths are URL-decoded, so reserved characters can be
220
+ percent-encoded (`p%40ss` for `p@ss`); `user`/`password` may alternatively
221
+ be passed as query parameters. IPv6 hosts use brackets:
222
+ `firebird://[::1]:3050/employee`. The parser is exported as
223
+ `Firebird.parseConnectionUri(uri)` if you need the resulting options object.
224
+
225
+ ### Traditional connection strings (old style)
226
+
227
+ The classic Firebird connection string format — the same
228
+ `[host[/port]:]{path | alias}` strings isql and the other Firebird tools
229
+ use — is accepted everywhere too:
230
+
231
+ ```js
232
+ const db = await Firebird.attachAsync('db.example.com/3051:/var/fb/prod.fdb');
233
+ ```
234
+
235
+ | Connection string | meaning |
236
+ | :--- | :--- |
237
+ | `employee` | the alias `employee` on `127.0.0.1:3050` |
238
+ | `/var/fb/prod.fdb` | a path on `127.0.0.1:3050` |
239
+ | `db.example.com:employee` | the alias `employee` on `db.example.com:3050` |
240
+ | `db.example.com/3051:/var/fb/prod.fdb` | host and explicit port |
241
+ | `myserver:C:\fbdata\prod.fdb` | a Windows path behind a host |
242
+ | `C:\fbdata\prod.fdb` | a single character before `:` is a drive letter, not a host (same rule as Firebird) |
243
+ | `[::1]/3050:employee` | IPv6 hosts use brackets |
244
+
245
+ Unlike `firebird://` URIs, traditional strings carry no credentials or
246
+ options — the driver defaults apply (`SYSDBA`/`masterkey`, port 3050), and
247
+ the port must be numeric (`/etc/services` names are not resolved). Use the
248
+ URI form or an options object when you need to set anything else.
249
+ `Firebird.parseConnectionString(str)` parses both forms and is what
250
+ `attach`/`create`/`pool` use internally for string arguments.
251
+
185
252
  ### Classic
186
253
 
187
254
  ```js
@@ -385,6 +452,174 @@ Firebird.attach(options, function (err, db) {
385
452
  });
386
453
  ```
387
454
 
455
+ ### Named placeholders
456
+
457
+ With the `namedPlaceholders: true` connection option, SQL may use `:name`
458
+ markers and parameters may be passed as a values-by-name object instead of
459
+ a positional array. The rewrite happens client-side before the statement is
460
+ prepared, so it works on every Firebird version; positional `?` arrays keep
461
+ working unchanged on the same connection.
462
+
463
+ ```js
464
+ const db = await Firebird.attachAsync({ ...options, namedPlaceholders: true });
465
+ // or: firebird://user:pass@host/db?namedPlaceholders=true
466
+
467
+ const rows = await db.queryAsync(
468
+ 'SELECT * FROM USERS WHERE ALIAS = :alias AND CREATED > :since',
469
+ { alias: 'Peter', since: new Date(2026, 0, 1) });
470
+
471
+ // A name may repeat — it binds once per occurrence:
472
+ await db.queryAsync(
473
+ 'SELECT * FROM T WHERE A = :v OR B = :v', { v: 42 });
474
+
475
+ // Batch rows can be objects too (Firebird 4.0+):
476
+ await db.executeBatchAsync(
477
+ 'INSERT INTO USERS (ID, ALIAS) VALUES (:id, :alias)',
478
+ [{ id: 1, alias: 'a' }, { id: 2, alias: 'b' }]);
479
+ ```
480
+
481
+ Placeholders inside string literals (`'...'`), quoted identifiers (`"..."`),
482
+ comments and `q'{...}'` alternative literals are left untouched. A key
483
+ present with value `null` binds SQL `NULL`; a *missing* key raises
484
+ `Missing value for named placeholder(s): ...`.
485
+
486
+ The scanner has no SQL grammar, so inside an `EXECUTE BLOCK` body every
487
+ PSQL `:variable` reference looks like a placeholder too. The option is
488
+ therefore off by default — and can be disabled for a single statement with
489
+ the per-query option:
490
+
491
+ ```js
492
+ await db.queryAsync(execBlockSql, [], { namedPlaceholders: false });
493
+ ```
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
+
388
623
  ### Tablespaces and Schema Partitioning (Firebird 6.0+)
389
624
 
390
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.
@@ -919,6 +1154,26 @@ var fbsvc = {
919
1154
 
920
1155
  ```
921
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
+
922
1177
  ### Backup Service example
923
1178
 
924
1179
  ```js
@@ -1657,13 +1912,13 @@ db.transaction(function (err, tx) {
1657
1912
 
1658
1913
  #### Is the wire protocol version hard-coded?
1659
1914
 
1660
- 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):
1661
1916
 
1662
1917
  ```js
1663
- options.maxNegotiatedProtocols = 11; // offers Protocol 20 as well as 19
1918
+ options.maxNegotiatedProtocols = 10; // stop at Protocol 19 (pre-Firebird 6 behavior)
1664
1919
  ```
1665
1920
 
1666
- 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.
1667
1922
 
1668
1923
  #### BLOB reads/writes are very slow, especially for large files over a remote connection
1669
1924
 
@@ -1720,6 +1975,12 @@ db.query('SELECT * FROM ACTORS WHERE NAME LIKE ?', ['James Wick%'], function (er
1720
1975
  });
1721
1976
  ```
1722
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
+
1723
1984
  ## Contributing
1724
1985
 
1725
1986
  Contributions are welcome — code, documentation, and bug reports alike.
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import Connection from './wire/connection';
2
2
  import { escape as escapeValue } from './utils';
3
+ import { parseConnectionUri, parseConnectionString } from './uri';
3
4
  import type { Options, SvcMgrOptions, DatabaseCallback, ServiceManagerCallback, SimpleCallback, ConnectionPool, Database, ServiceManager } from './types';
4
5
  export * from './types';
5
6
  export { GDSCode } from './gdscodes';
@@ -23,20 +24,27 @@ export declare const ISOLATION_REPEATABLE_READ: number[];
23
24
  export declare const ISOLATION_SERIALIZABLE: number[];
24
25
  export declare const ISOLATION_READ_COMMITTED_READ_ONLY: number[];
25
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>>;
26
32
  /**
27
33
  * The most recent Connection created by attach()/create()/attachOrCreate().
28
34
  * Kept for backwards compatibility with the previous CommonJS module where
29
35
  * the connection was stored on the module object itself.
30
36
  */
31
37
  export declare let connection: Connection | undefined;
32
- export declare function attach(options: Options, callback: DatabaseCallback): void;
38
+ export declare function attach(options: Options | string, callback: DatabaseCallback): void;
33
39
  export declare function attach(options: SvcMgrOptions, callback: ServiceManagerCallback): void;
34
- export declare function drop(options: Options, callback: SimpleCallback): void;
35
- export declare function create(options: Options, callback: DatabaseCallback): void;
36
- export declare function attachOrCreate(options: Options, callback: DatabaseCallback): void;
37
- export declare function pool(max: number, options: Options): ConnectionPool;
40
+ export declare function drop(options: Options | string, callback: SimpleCallback): void;
41
+ export declare function create(options: Options | string, callback: DatabaseCallback): void;
42
+ export declare function attachOrCreate(options: Options | string, callback: DatabaseCallback): void;
43
+ export declare function pool(max: number, options: Options | string): ConnectionPool;
44
+ export { parseConnectionUri, parseConnectionString };
45
+ export { parseNamedPlaceholders } from './named-params';
38
46
  export declare function attachAsync(options: SvcMgrOptions): Promise<ServiceManager>;
39
- export declare function attachAsync(options: Options): Promise<Database>;
40
- export declare function createAsync(options: Options): Promise<Database>;
41
- export declare function attachOrCreateAsync(options: Options): Promise<Database>;
42
- export declare function dropAsync(options: Options): Promise<void>;
47
+ export declare function attachAsync(options: Options | string): Promise<Database>;
48
+ export declare function createAsync(options: Options | string): Promise<Database>;
49
+ export declare function attachOrCreateAsync(options: Options | string): Promise<Database>;
50
+ export declare function dropAsync(options: Options | string): Promise<void>;
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.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;
@@ -32,6 +32,9 @@ const callback_1 = require("./callback");
32
32
  const connection_1 = __importDefault(require("./wire/connection"));
33
33
  const pool_1 = __importDefault(require("./pool"));
34
34
  const utils_1 = require("./utils");
35
+ const uri_1 = require("./uri");
36
+ Object.defineProperty(exports, "parseConnectionUri", { enumerable: true, get: function () { return uri_1.parseConnectionUri; } });
37
+ Object.defineProperty(exports, "parseConnectionString", { enumerable: true, get: function () { return uri_1.parseConnectionString; } });
35
38
  __exportStar(require("./types"), exports);
36
39
  var gdscodes_1 = require("./gdscodes");
37
40
  Object.defineProperty(exports, "GDSCode", { enumerable: true, get: function () { return gdscodes_1.GDSCode; } });
@@ -60,7 +63,37 @@ exports.ISOLATION_REPEATABLE_READ = const_1.default.ISOLATION_REPEATABLE_READ;
60
63
  exports.ISOLATION_SERIALIZABLE = const_1.default.ISOLATION_SERIALIZABLE;
61
64
  exports.ISOLATION_READ_COMMITTED_READ_ONLY = const_1.default.ISOLATION_READ_COMMITTED_READ_ONLY;
62
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
+ });
63
95
  function attach(options, callback) {
96
+ options = (0, uri_1.normalizeOptions)(options);
64
97
  var host = options.host || const_1.default.DEFAULT_HOST;
65
98
  var port = options.port || const_1.default.DEFAULT_PORT;
66
99
  var manager = options.manager || false;
@@ -83,7 +116,7 @@ function attach(options, callback) {
83
116
  }, options);
84
117
  }
85
118
  function drop(options, callback) {
86
- attach(options, function (err, db) {
119
+ attach((0, uri_1.normalizeOptions)(options), function (err, db) {
87
120
  if (err) {
88
121
  callback({ error: err, message: "Drop error" });
89
122
  return;
@@ -92,6 +125,7 @@ function drop(options, callback) {
92
125
  });
93
126
  }
94
127
  function create(options, callback) {
128
+ options = (0, uri_1.normalizeOptions)(options);
95
129
  var host = options.host || const_1.default.DEFAULT_HOST;
96
130
  var port = options.port || const_1.default.DEFAULT_PORT;
97
131
  var cnx = exports.connection = new connection_1.default(host, port, function (err) {
@@ -112,6 +146,7 @@ function create(options, callback) {
112
146
  }, options);
113
147
  }
114
148
  function attachOrCreate(options, callback) {
149
+ options = (0, uri_1.normalizeOptions)(options);
115
150
  var host = options.host || const_1.default.DEFAULT_HOST;
116
151
  var port = options.port || const_1.default.DEFAULT_PORT;
117
152
  var cnx = exports.connection = new connection_1.default(host, port, function (err) {
@@ -129,6 +164,8 @@ function attachOrCreate(options, callback) {
129
164
  if (!err) {
130
165
  if (self.db)
131
166
  self.db.emit('connect', ret);
167
+ // DatabaseCallback stays permissive (db non-optional) for
168
+ // API users; internally the error path passes no db
132
169
  (0, callback_1.doCallback)(ret, callback);
133
170
  return;
134
171
  }
@@ -139,8 +176,10 @@ function attachOrCreate(options, callback) {
139
176
  }
140
177
  // Pooling
141
178
  function pool(max, options) {
142
- return new pool_1.default(attach, max, Object.assign({}, options, { isPool: true }));
179
+ return new pool_1.default(attach, max, Object.assign({}, (0, uri_1.normalizeOptions)(options), { isPool: true }));
143
180
  }
181
+ var named_params_1 = require("./named-params");
182
+ Object.defineProperty(exports, "parseNamedPlaceholders", { enumerable: true, get: function () { return named_params_1.parseNamedPlaceholders; } });
144
183
  function attachAsync(options) {
145
184
  return (0, callback_1.fromCallback)(function (cb) { attach(options, cb); });
146
185
  }
@@ -0,0 +1,42 @@
1
+ /***************************************
2
+ *
3
+ * Named placeholders (:name → ?)
4
+ *
5
+ ***************************************/
6
+ /**
7
+ * Result of scanning a SQL string for named placeholders.
8
+ */
9
+ export interface ParsedNamedPlaceholders {
10
+ /** SQL with every named placeholder replaced by a positional "?". */
11
+ sql: string;
12
+ /**
13
+ * Placeholder names in positional order (a repeated name appears once
14
+ * per occurrence), or null when the SQL contains none.
15
+ */
16
+ names: string[] | null;
17
+ }
18
+ /**
19
+ * Scan `sql` for named placeholders (`:name`) and rewrite them to positional
20
+ * `?` markers, returning the rewritten SQL and the names in positional
21
+ * order. Placeholders inside string literals ('...'), quoted identifiers
22
+ * ("..."), line comments (--), block comments and Firebird alternative
23
+ * string literals (q'{...}') are left untouched.
24
+ *
25
+ * Note: the scanner has no SQL grammar — inside an EXECUTE BLOCK body every
26
+ * `:variable` reference looks like a placeholder too. Use positional params
27
+ * (or per-call `namedPlaceholders: false`) for EXECUTE BLOCK.
28
+ */
29
+ export declare function parseNamedPlaceholders(sql: string): ParsedNamedPlaceholders;
30
+ /**
31
+ * True when `params` is a plain values-by-name object (and not one of the
32
+ * values the driver accepts as a single positional parameter, like Date or
33
+ * Buffer).
34
+ */
35
+ export declare function isNamedParamsObject(params: any): params is Record<string, any>;
36
+ /**
37
+ * Map a values-by-name object onto the positional order collected by
38
+ * parseNamedPlaceholders. A name may be bound multiple times; every name
39
+ * must be an own property of `params` (a present key holding null is a
40
+ * NULL parameter, a missing key is an error).
41
+ */
42
+ export declare function bindNamedParams(names: string[], params: Record<string, any>): any[];
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /***************************************
3
+ *
4
+ * Named placeholders (:name → ?)
5
+ *
6
+ ***************************************/
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseNamedPlaceholders = parseNamedPlaceholders;
9
+ exports.isNamedParamsObject = isNamedParamsObject;
10
+ exports.bindNamedParams = bindNamedParams;
11
+ const IDENT_START = /[A-Za-z_]/;
12
+ const IDENT_PART = /[A-Za-z0-9_$]/;
13
+ // Parsing is pure string work, so identical SQL (the common case with
14
+ // query builders and hot paths) is scanned only once.
15
+ const CACHE_MAX = 100;
16
+ const cache = new Map();
17
+ /**
18
+ * Scan `sql` for named placeholders (`:name`) and rewrite them to positional
19
+ * `?` markers, returning the rewritten SQL and the names in positional
20
+ * order. Placeholders inside string literals ('...'), quoted identifiers
21
+ * ("..."), line comments (--), block comments and Firebird alternative
22
+ * string literals (q'{...}') are left untouched.
23
+ *
24
+ * Note: the scanner has no SQL grammar — inside an EXECUTE BLOCK body every
25
+ * `:variable` reference looks like a placeholder too. Use positional params
26
+ * (or per-call `namedPlaceholders: false`) for EXECUTE BLOCK.
27
+ */
28
+ function parseNamedPlaceholders(sql) {
29
+ var cached = cache.get(sql);
30
+ if (cached)
31
+ return cached;
32
+ var out = '';
33
+ var names = [];
34
+ var i = 0;
35
+ var n = sql.length;
36
+ while (i < n) {
37
+ var c = sql[i];
38
+ if (c === "'" || c === '"') {
39
+ // string literal or quoted identifier; doubled quotes escape
40
+ var quote = c;
41
+ var end = i + 1;
42
+ while (end < n) {
43
+ if (sql[end] === quote) {
44
+ if (sql[end + 1] === quote) {
45
+ end += 2;
46
+ continue;
47
+ }
48
+ end++;
49
+ break;
50
+ }
51
+ end++;
52
+ }
53
+ out += sql.slice(i, end);
54
+ i = end;
55
+ }
56
+ else if (c === '-' && sql[i + 1] === '-') {
57
+ var eol = sql.indexOf('\n', i);
58
+ if (eol === -1)
59
+ eol = n;
60
+ out += sql.slice(i, eol);
61
+ i = eol;
62
+ }
63
+ else if (c === '/' && sql[i + 1] === '*') {
64
+ var close = sql.indexOf('*/', i + 2);
65
+ close = close === -1 ? n : close + 2;
66
+ out += sql.slice(i, close);
67
+ i = close;
68
+ }
69
+ else if ((c === 'q' || c === 'Q') && sql[i + 1] === "'" && i + 2 < n &&
70
+ (i === 0 || !IDENT_PART.test(sql[i - 1]))) {
71
+ // Firebird 3+ alternative string literal: q'{...}' / q'!...!'
72
+ var open = sql[i + 2];
73
+ var closer = open === '(' ? ')'
74
+ : open === '[' ? ']'
75
+ : open === '{' ? '}'
76
+ : open === '<' ? '>'
77
+ : open;
78
+ var stop = sql.indexOf(closer + "'", i + 3);
79
+ stop = stop === -1 ? n : stop + 2;
80
+ out += sql.slice(i, stop);
81
+ i = stop;
82
+ }
83
+ else if (c === ':' && i + 1 < n && IDENT_START.test(sql[i + 1])) {
84
+ var end2 = i + 2;
85
+ while (end2 < n && IDENT_PART.test(sql[end2]))
86
+ end2++;
87
+ names.push(sql.slice(i + 1, end2));
88
+ out += '?';
89
+ i = end2;
90
+ }
91
+ else {
92
+ out += c;
93
+ i++;
94
+ }
95
+ }
96
+ var result = names.length
97
+ ? { sql: out, names: names }
98
+ : { sql: sql, names: null };
99
+ if (cache.size >= CACHE_MAX) {
100
+ cache.delete(cache.keys().next().value);
101
+ }
102
+ cache.set(sql, result);
103
+ return result;
104
+ }
105
+ /**
106
+ * True when `params` is a plain values-by-name object (and not one of the
107
+ * values the driver accepts as a single positional parameter, like Date or
108
+ * Buffer).
109
+ */
110
+ function isNamedParamsObject(params) {
111
+ return params !== null &&
112
+ typeof params === 'object' &&
113
+ !Array.isArray(params) &&
114
+ !Buffer.isBuffer(params) &&
115
+ !(params instanceof Date);
116
+ }
117
+ /**
118
+ * Map a values-by-name object onto the positional order collected by
119
+ * parseNamedPlaceholders. A name may be bound multiple times; every name
120
+ * must be an own property of `params` (a present key holding null is a
121
+ * NULL parameter, a missing key is an error).
122
+ */
123
+ function bindNamedParams(names, params) {
124
+ var missing = [];
125
+ var values = names.map(function (name) {
126
+ if (!Object.prototype.hasOwnProperty.call(params, name))
127
+ missing.push(name);
128
+ return params[name];
129
+ });
130
+ if (missing.length)
131
+ throw new Error('Missing value for named placeholder(s): ' + missing.join(', '));
132
+ return values;
133
+ }
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) {