node-firebird 2.4.2 → 2.6.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.
package/README.md CHANGED
@@ -10,10 +10,11 @@
10
10
 
11
11
  - [Installation](#installation)
12
12
  - [Usage](#usage) — including [developing the driver](#developing-the-driver)
13
+ - [Promises and async/await](#promises-and-asyncawait) — the `*Async` API plus `withConnection` / `withTransaction` helpers
13
14
  - [Connection types](#connection-types) — connection options, classic connections, pooling
14
15
  - [Database object (db)](#database-object-db) — database, transaction and statement methods/options
15
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
- - [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
17
+ - [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, query cancellation (AbortSignal), statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
17
18
  - [Using node-firebird with Express.js](#using-node-firebird-with-expressjs)
18
19
  - [FAQ](#faq)
19
20
  - [Contributing](#contributing) · [Contributors](#contributors)
@@ -98,6 +99,55 @@ npm test # build + run the vitest suite (unit + integration)
98
99
  - `Firebird.create(options, function(err, db))` create a database
99
100
  - `Firebird.attachOrCreate(options, function(err, db))` attach or create database
100
101
  - `Firebird.pool(max, options) -> return {Object}` create a connection pooling
102
+ - `Firebird.attachAsync(options) -> Promise<Database>`, `createAsync`, `attachOrCreateAsync`, `dropAsync` — promise counterparts, see [Promises and async/await](#promises-and-asyncawait)
103
+
104
+ ## Promises and async/await
105
+
106
+ Every callback API has a promise-returning counterpart with an `Async`
107
+ suffix, plus two higher-level helpers: `pool.withConnection()` and
108
+ `db.withTransaction()`. The callback API is unchanged and the two styles can
109
+ be mixed freely, though sticking to one per project keeps code readable.
110
+
111
+ ```js
112
+ const Firebird = require('node-firebird');
113
+
114
+ const pool = Firebird.pool(5, options);
115
+
116
+ // acquire → work → always release, even when `work` throws
117
+ const users = await pool.withConnection((db) =>
118
+ db.queryAsync('SELECT id, name FROM users WHERE plan = ?', ['pro'])
119
+ );
120
+
121
+ // commit on success, rollback on error
122
+ await pool.withConnection((db) =>
123
+ db.withTransaction(async (transaction) => {
124
+ await transaction.executeAsync('INSERT INTO audit (msg) VALUES (?)', ['signup']);
125
+ await transaction.executeAsync('UPDATE stats SET signups = signups + 1');
126
+ })
127
+ );
128
+
129
+ await pool.destroyAsync();
130
+ ```
131
+
132
+ Available wrappers:
133
+
134
+ - **module** — `Firebird.attachAsync(options)`, `createAsync`, `attachOrCreateAsync`, `dropAsync`; resolve with a `Database` (or a `ServiceManager` when `options.manager` is `true`)
135
+ - **pool** — `pool.getAsync()`, `pool.destroyAsync()`, `pool.withConnection(work)`
136
+ - **database** — `db.queryAsync(sql, params?, options?)`, `executeAsync`, `sequentiallyAsync(sql, params?, onRow, options?)`, `transactionAsync(options?)`, `newStatementAsync(sql)`, `attachEventAsync()`, `detachAsync()`, `dropAsync()`, `db.withTransaction(work, options?)`
137
+ - **transaction** — `queryAsync`, `executeAsync`, `sequentiallyAsync`, `newStatementAsync`, `commitAsync`, `rollbackAsync`, `commitRetainingAsync`, `rollbackRetainingAsync`
138
+ - **statement** — `executeAsync(transaction, params?, options?)`, `fetchAsync`, `fetchScrollAsync`, `fetchAllAsync`, `closeAsync`, `dropAsync`, `releaseAsync`
139
+
140
+ Notes:
141
+
142
+ - Rejections are always `Error` instances carrying the usual Firebird
143
+ properties (`err.gdscode`, `err.gdsparams`) — see [Using GDS codes](#using-gds-codes).
144
+ - `queryAsync` / `executeAsync` resolve with the rows only; column metadata
145
+ is currently available through the callback API only.
146
+ - An un-`await`ed rejected promise becomes an unhandled rejection instead of
147
+ a callback error — prefer the `withConnection` / `withTransaction` helpers,
148
+ which guarantee cleanup on every path.
149
+ - TypeScript: the async methods accept a row-shape generic, e.g.
150
+ `db.queryAsync<User>(sql, params)` returns `Promise<User[]>`.
101
151
 
102
152
  ## Connection types
103
153
 
@@ -1101,6 +1151,47 @@ Firebird.attach({
1101
1151
  });
1102
1152
  ```
1103
1153
 
1154
+ ### Query Cancellation with AbortSignal (Firebird 2.5+)
1155
+
1156
+ Any query can be cancelled while it is executing on the server. Pass an
1157
+ `AbortSignal` in the query options — when it fires, the driver sends an
1158
+ out-of-band `op_cancel` packet and the running statement fails with
1159
+ `err.gdscode === GDSCode.CANCELLED`. The connection itself stays healthy and
1160
+ can run further queries immediately.
1161
+
1162
+ ```js
1163
+ const { GDSCode } = require('node-firebird/lib/gdscodes');
1164
+
1165
+ const controller = new AbortController();
1166
+ setTimeout(() => controller.abort(), 5000); // or req.on('close', ...) in Express
1167
+
1168
+ try {
1169
+ const rows = await db.queryAsync('SELECT /* expensive */ ...', [], { signal: controller.signal });
1170
+ } catch (err) {
1171
+ if (err.gdscode === GDSCode.CANCELLED) {
1172
+ // cancelled server-side; the connection remains usable
1173
+ } else if (err.name === 'AbortError') {
1174
+ // the signal was already aborted — the query was never sent
1175
+ } else {
1176
+ throw err;
1177
+ }
1178
+ }
1179
+ ```
1180
+
1181
+ The option works with the callback API as well (`db.query(sql, params, cb,
1182
+ { signal })`) and on transaction-level queries. A running operation can also
1183
+ be cancelled manually from anywhere with `db.cancel()` / `await
1184
+ db.cancelAsync()`.
1185
+
1186
+ Notes:
1187
+
1188
+ - Cancellation is **per attachment** (that is how the Firebird protocol
1189
+ defines it): it cancels whatever is currently executing on that
1190
+ connection. With a pool this is naturally scoped to the request holding
1191
+ the connection.
1192
+ - A signal that is already aborted rejects immediately with an `AbortError`
1193
+ without contacting the server; cancelling an idle connection is harmless.
1194
+
1104
1195
  ### Statement Timeouts (Firebird 4.0+)
1105
1196
  Setting a statement timeout allows the client to automatically abort queries that take too long on the server.
1106
1197
  ```js
@@ -1230,6 +1321,8 @@ process.on('SIGTERM', function() {
1230
1321
 
1231
1322
  node-firebird works well with Express, but because the driver is connection/pool based rather than an ORM with automatic connection management, a few request-lifecycle patterns keep connections from leaking under load. This section documents the recommended architecture, referenced from the [roadmap](ROADMAP.md#2-expressjs-support-first-class-integration).
1232
1323
 
1324
+ > The driver now ships native `pool.withConnection()` and `db.withTransaction()` helpers (see [Promises and async/await](#promises-and-asyncawait)) that supersede the hand-rolled `withConnection` / `transactional` helpers shown in the examples below. The examples remain valid and show what the helpers do under the hood.
1325
+
1233
1326
  ### Recommended architecture: one pool per app, created at startup
1234
1327
 
1235
1328
  Create a single `Firebird.pool(...)` when the app boots and reuse it for the lifetime of the process. Do **not** call `Firebird.pool()` or `Firebird.attach()` inside a request handler — that opens a new socket (or an entire new pool) on every request and will exhaust server and database resources under load.
package/lib/callback.d.ts CHANGED
@@ -16,5 +16,18 @@ export interface FbError extends Error {
16
16
  }
17
17
  export type SimpleCallback = (err?: any) => void;
18
18
  export type Callback<T = any> = (err?: any, result?: T) => void;
19
+ /**
20
+ * Normalize the values the driver passes as the callback error argument.
21
+ * Most code paths already deliver Error instances, but a few older ones
22
+ * pass plain objects (status vectors, `{error, message}` wrappers). A
23
+ * Promise must reject with an Error, so wrap those while preserving all
24
+ * their properties (gdscode, gdsparams, status, sqlcode, ...).
25
+ */
26
+ export declare function toError(err: any): Error;
27
+ /**
28
+ * Run a callback-style operation and return a Promise for its result.
29
+ * Usage: fromCallback<Database>(cb => attach(options, cb))
30
+ */
31
+ export declare function fromCallback<T = any>(executor: (cb: Callback<T>) => void): Promise<T>;
19
32
  export declare function doError(obj: any, callback?: (...args: any[]) => void): void;
20
33
  export declare function doCallback<T>(obj: T, callback?: Callback<T>): void;
package/lib/callback.js CHANGED
@@ -1,7 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toError = toError;
4
+ exports.fromCallback = fromCallback;
3
5
  exports.doError = doError;
4
6
  exports.doCallback = doCallback;
7
+ /**
8
+ * Normalize the values the driver passes as the callback error argument.
9
+ * Most code paths already deliver Error instances, but a few older ones
10
+ * pass plain objects (status vectors, `{error, message}` wrappers). A
11
+ * Promise must reject with an Error, so wrap those while preserving all
12
+ * their properties (gdscode, gdsparams, status, sqlcode, ...).
13
+ */
14
+ function toError(err) {
15
+ if (err instanceof Error)
16
+ return err;
17
+ var error = new Error(err != null && typeof err === 'object' && err.message ? err.message : String(err));
18
+ if (err != null && typeof err === 'object')
19
+ Object.assign(error, err);
20
+ return error;
21
+ }
22
+ /**
23
+ * Run a callback-style operation and return a Promise for its result.
24
+ * Usage: fromCallback<Database>(cb => attach(options, cb))
25
+ */
26
+ function fromCallback(executor) {
27
+ return new Promise(function (resolve, reject) {
28
+ executor(function (err, result) {
29
+ if (err)
30
+ reject(toError(err));
31
+ else
32
+ resolve(result);
33
+ });
34
+ });
35
+ }
5
36
  function doError(obj, callback) {
6
37
  if (callback)
7
38
  callback(obj);
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import Connection from './wire/connection';
2
2
  import { escape as escapeValue } from './utils';
3
- import type { Options, SvcMgrOptions, DatabaseCallback, ServiceManagerCallback, SimpleCallback, ConnectionPool } from './types';
3
+ import type { Options, SvcMgrOptions, DatabaseCallback, ServiceManagerCallback, SimpleCallback, ConnectionPool, Database, ServiceManager } from './types';
4
4
  export * from './types';
5
5
  export { GDSCode } from './gdscodes';
6
6
  export declare const AUTH_PLUGIN_LEGACY: string;
@@ -35,3 +35,8 @@ export declare function drop(options: Options, callback: SimpleCallback): void;
35
35
  export declare function create(options: Options, callback: DatabaseCallback): void;
36
36
  export declare function attachOrCreate(options: Options, callback: DatabaseCallback): void;
37
37
  export declare function pool(max: number, options: Options): ConnectionPool;
38
+ 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>;
package/lib/index.js CHANGED
@@ -23,6 +23,10 @@ exports.drop = drop;
23
23
  exports.create = create;
24
24
  exports.attachOrCreate = attachOrCreate;
25
25
  exports.pool = pool;
26
+ exports.attachAsync = attachAsync;
27
+ exports.createAsync = createAsync;
28
+ exports.attachOrCreateAsync = attachOrCreateAsync;
29
+ exports.dropAsync = dropAsync;
26
30
  const const_1 = __importDefault(require("./wire/const"));
27
31
  const callback_1 = require("./callback");
28
32
  const connection_1 = __importDefault(require("./wire/connection"));
@@ -137,3 +141,15 @@ function attachOrCreate(options, callback) {
137
141
  function pool(max, options) {
138
142
  return new pool_1.default(attach, max, Object.assign({}, options, { isPool: true }));
139
143
  }
144
+ function attachAsync(options) {
145
+ return (0, callback_1.fromCallback)(function (cb) { attach(options, cb); });
146
+ }
147
+ function createAsync(options) {
148
+ return (0, callback_1.fromCallback)(function (cb) { create(options, cb); });
149
+ }
150
+ function attachOrCreateAsync(options) {
151
+ return (0, callback_1.fromCallback)(function (cb) { attachOrCreate(options, cb); });
152
+ }
153
+ function dropAsync(options) {
154
+ return (0, callback_1.fromCallback)(function (cb) { drop(options, cb); });
155
+ }
package/lib/pool.d.ts CHANGED
@@ -19,5 +19,12 @@ declare class Pool {
19
19
  get(callback: Callback): this;
20
20
  check(): this;
21
21
  destroy(callback?: (err?: any) => void): void;
22
+ getAsync(): Promise<any>;
23
+ destroyAsync(): Promise<void>;
24
+ /**
25
+ * Run `work` with a connection from the pool, returning it to the pool
26
+ * (detach) when the returned promise settles — success or failure.
27
+ */
28
+ withConnection<T>(work: (db: any) => Promise<T> | T): Promise<T>;
22
29
  }
23
30
  export = Pool;
package/lib/pool.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * Simple Pooling
5
5
  *
6
6
  ***************************************/
7
+ const callback_1 = require("./callback");
7
8
  class Pool {
8
9
  constructor(attach, max, options) {
9
10
  this.attach = attach;
@@ -178,5 +179,29 @@ class Pool {
178
179
  }
179
180
  });
180
181
  }
182
+ /* Promise / async-await API — wrappers over the callback methods above. */
183
+ getAsync() {
184
+ var self = this;
185
+ return (0, callback_1.fromCallback)(function (cb) { self.get(cb); });
186
+ }
187
+ destroyAsync() {
188
+ var self = this;
189
+ return (0, callback_1.fromCallback)(function (cb) { self.destroy(cb); });
190
+ }
191
+ /**
192
+ * Run `work` with a connection from the pool, returning it to the pool
193
+ * (detach) when the returned promise settles — success or failure.
194
+ */
195
+ async withConnection(work) {
196
+ const db = await this.getAsync();
197
+ try {
198
+ return await work(db);
199
+ }
200
+ finally {
201
+ // A pooled detach only returns the connection to the pool; do not
202
+ // let a detach hiccup mask the outcome of `work`.
203
+ await new Promise(function (resolve) { db.detach(function () { resolve(); }); });
204
+ }
205
+ }
181
206
  }
182
207
  module.exports = Pool;
package/lib/types.d.ts CHANGED
@@ -67,6 +67,15 @@ export type TransactionOptions = {
67
67
  export type QueryOptions = {
68
68
  timeout?: number;
69
69
  scrollable?: boolean;
70
+ /**
71
+ * Abort the query when the signal fires (Firebird 2.5+ / protocol 12+).
72
+ * If the signal is already aborted the query is not sent at all and the
73
+ * callback/promise fails with an `AbortError`. If it fires mid-flight an
74
+ * out-of-band op_cancel is sent and the query fails with
75
+ * `err.gdscode === GDSCode.CANCELLED`. Cancellation is per-attachment:
76
+ * it cancels whatever is currently executing on the connection.
77
+ */
78
+ signal?: AbortSignal;
70
79
  };
71
80
  export interface Database {
72
81
  detach(callback?: SimpleCallback): Database;
@@ -82,6 +91,26 @@ export interface Database {
82
91
  alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
83
92
  dropTablespace(name: string, callback?: QueryCallback): Database;
84
93
  createSchema(schemaName: string, tablespaceName?: string | QueryCallback, callback?: QueryCallback): Database;
94
+ queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
95
+ executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
96
+ sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
97
+ sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
98
+ transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
99
+ startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
100
+ newStatementAsync(query: string): Promise<Statement>;
101
+ detachAsync(force?: boolean): Promise<void>;
102
+ dropAsync(): Promise<void>;
103
+ attachEventAsync(): Promise<any>;
104
+ /** Starts a transaction, commits when `work` resolves, rolls back when it rejects. */
105
+ withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T>;
106
+ /**
107
+ * Cancel the operation currently executing on this connection
108
+ * (Firebird 2.5+ / protocol 12+). The cancelled operation fails through
109
+ * its own callback/promise with `err.gdscode === GDSCode.CANCELLED`.
110
+ */
111
+ cancel(callback?: SimpleCallback): Database;
112
+ cancel(kind: number, callback?: SimpleCallback): Database;
113
+ cancelAsync(kind?: number): Promise<void>;
85
114
  }
86
115
  export interface Transaction {
87
116
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
@@ -92,6 +121,15 @@ export interface Transaction {
92
121
  commitRetaining(callback?: SimpleCallback): void;
93
122
  rollback(callback?: SimpleCallback): void;
94
123
  rollbackRetaining(callback?: SimpleCallback): void;
124
+ queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
125
+ executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
126
+ sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
127
+ sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
128
+ newStatementAsync(query: string): Promise<Statement>;
129
+ commitAsync(): Promise<void>;
130
+ commitRetainingAsync(): Promise<void>;
131
+ rollbackAsync(): Promise<void>;
132
+ rollbackRetainingAsync(): Promise<void>;
95
133
  }
96
134
  export interface Statement {
97
135
  close(callback?: SimpleCallback): void;
@@ -101,6 +139,13 @@ export interface Statement {
101
139
  fetch(transaction: Transaction, count: number, callback: QueryCallback): void;
102
140
  fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
103
141
  fetchAll(transaction: Transaction, callback: QueryCallback): void;
142
+ executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
143
+ fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
144
+ fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
145
+ fetchAllAsync(transaction: Transaction): Promise<any>;
146
+ closeAsync(): Promise<void>;
147
+ dropAsync(): Promise<void>;
148
+ releaseAsync(): Promise<void>;
104
149
  }
105
150
  export type SupportedCharacterSet = 'NONE' | 'CP943C' | 'DOS737' | 'DOS775' | 'DOS858' | 'DOS862' | 'DOS864' | 'DOS866' | 'DOS869' | 'GB18030' | 'GBK' | 'ISO8859_1' | 'ISO8859_2' | 'ISO8859_3' | 'ISO8859_4' | 'ISO8859_5' | 'ISO8859_6' | 'ISO8859_7' | 'ISO8859_8' | 'ISO8859_9' | 'ISO8859_13' | 'KOI8R' | 'KOI8U' | 'TIS620' | 'UTF8' | 'WIN1251' | 'WIN1252' | 'WIN1253' | 'WIN1254' | 'WIN1255' | 'WIN1256' | 'WIN1257' | 'WIN1258' | 'WIN_1258';
106
151
  export interface Options {
@@ -188,6 +233,10 @@ export interface SvcMgrOptions extends Options {
188
233
  export interface ConnectionPool {
189
234
  get(callback: DatabaseCallback): void;
190
235
  destroy(callback?: SimpleCallback): void;
236
+ getAsync(): Promise<Database>;
237
+ destroyAsync(): Promise<void>;
238
+ /** Acquire a connection, run `work`, always return the connection to the pool. */
239
+ withConnection<T>(work: (db: Database) => Promise<T> | T): Promise<T>;
191
240
  }
192
241
  export interface ReadableOptions {
193
242
  optread?: 'byline' | 'buffer';
@@ -57,6 +57,13 @@ declare class Connection {
57
57
  sendOpContAuth(authData: any, authDataEnc: any, pluginName: any): void;
58
58
  sendOpCrypt(encryptPlugin: any): void;
59
59
  sendOpCryptKeyCallback(pluginData: any): void;
60
+ /**
61
+ * Send an out-of-band op_cancel packet (protocol 12+ / Firebird 2.5+).
62
+ * The server reads it asynchronously while an operation is executing and
63
+ * makes that operation fail with isc_cancelled (GDSCode.CANCELLED); the
64
+ * op_cancel packet itself has no response, so nothing is queued here.
65
+ */
66
+ cancelOperation(kind: any, callback: any): this;
60
67
  _queueEvent(callback: any, defer?: boolean): void;
61
68
  connect(options: any, callback: any): void;
62
69
  attach(options: any, callback?: any, db?: any): void;
@@ -294,6 +294,32 @@ class Connection {
294
294
  msg.addBlr(pluginData); // Send the callback response data as a buffer
295
295
  this._socket.write(msg.getData());
296
296
  }
297
+ /**
298
+ * Send an out-of-band op_cancel packet (protocol 12+ / Firebird 2.5+).
299
+ * The server reads it asynchronously while an operation is executing and
300
+ * makes that operation fail with isc_cancelled (GDSCode.CANCELLED); the
301
+ * op_cancel packet itself has no response, so nothing is queued here.
302
+ */
303
+ cancelOperation(kind, callback) {
304
+ if (typeof kind === 'function') {
305
+ callback = kind;
306
+ kind = undefined;
307
+ }
308
+ kind = kind || const_1.default.fb_cancel_raise;
309
+ if (this._isClosed)
310
+ return this.throwClosed(callback);
311
+ if (!this.accept || this.accept.protocolVersion < const_1.default.PROTOCOL_VERSION12) {
312
+ (0, callback_1.doError)(new Error('Query cancellation requires protocol 12+ (Firebird 2.5 or newer)'), callback);
313
+ return;
314
+ }
315
+ var msg = this._msg;
316
+ msg.pos = 0;
317
+ msg.addInt(const_1.default.op_cancel);
318
+ msg.addInt(kind);
319
+ this._socket.write(msg.getData());
320
+ if (callback)
321
+ callback();
322
+ }
297
323
  _queueEvent(callback, defer = false) {
298
324
  var self = this;
299
325
  if (self._isClosed) {
@@ -1823,16 +1849,20 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
1823
1849
  public: BigInt('0x' + d.buffer.slice(keyStart, d.buffer.length).toString('utf8')),
1824
1850
  pluginName: accept.pluginName
1825
1851
  };
1826
- console.log('--- DEBUG SRP Handshake ---');
1827
- console.log('salt:', cnx.serverKeys.salt);
1828
- console.log('server public key:', cnx.serverKeys.public.toString(16));
1829
- console.log('client public key:', cnx.clientKeys.public.toString(16));
1830
- console.log('client private key:', cnx.clientKeys.private.toString(16));
1831
- console.log('hashAlgo:', accept.srpAlgo);
1852
+ if (process.env.FIREBIRD_DEBUG) {
1853
+ console.log('--- DEBUG SRP Handshake ---');
1854
+ console.log('salt:', cnx.serverKeys.salt);
1855
+ console.log('server public key:', cnx.serverKeys.public.toString(16));
1856
+ console.log('client public key:', cnx.clientKeys.public.toString(16));
1857
+ console.log('hashAlgo:', accept.srpAlgo);
1858
+ }
1832
1859
  const _t1 = Date.now();
1833
1860
  var proof = srp.clientProof(cnx.options.user.toUpperCase(), cnx.options.password, cnx.serverKeys.salt, cnx.clientKeys.public, cnx.serverKeys.public, cnx.clientKeys.private, accept.srpAlgo);
1834
- console.log('client proof M1:', proof.authData.toString(16));
1835
- console.log('client session key K:', proof.clientSessionKey.toString(16));
1861
+ if (process.env.FIREBIRD_DEBUG) {
1862
+ // Never log the private key or the session key: the
1863
+ // session key is the wire-encryption key material.
1864
+ console.log('client proof M1:', proof.authData.toString(16));
1865
+ }
1836
1866
  if (process.env.FIREBIRD_DEBUG) {
1837
1867
  console.log('[fb-debug] srp.clientProof(%s): %dms', accept.srpAlgo, Date.now() - _t1);
1838
1868
  }
@@ -1917,12 +1947,13 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
1917
1947
  Srp512: 'sha512'
1918
1948
  };
1919
1949
  var srpAlgo = crypto[pluginName];
1920
- console.log('--- DEBUG SRP Handshake ---');
1921
- console.log('salt:', cnx.serverKeys.salt);
1922
- console.log('server public key:', cnx.serverKeys.public.toString(16));
1923
- console.log('client public key:', cnx.clientKeys.public.toString(16));
1924
- console.log('client private key:', cnx.clientKeys.private.toString(16));
1925
- console.log('hashAlgo:', srpAlgo);
1950
+ if (process.env.FIREBIRD_DEBUG) {
1951
+ console.log('--- DEBUG SRP Handshake ---');
1952
+ console.log('salt:', cnx.serverKeys.salt);
1953
+ console.log('server public key:', cnx.serverKeys.public.toString(16));
1954
+ console.log('client public key:', cnx.clientKeys.public.toString(16));
1955
+ console.log('hashAlgo:', srpAlgo);
1956
+ }
1926
1957
  const _t1 = Date.now();
1927
1958
  var proof = srp.clientProof(cnx.options.user.toUpperCase(), cnx.options.password, cnx.serverKeys.salt, cnx.clientKeys.public, cnx.serverKeys.public, cnx.clientKeys.private, srpAlgo);
1928
1959
  if (process.env.FIREBIRD_DEBUG) {
@@ -104,6 +104,10 @@ declare const Const: Readonly<{
104
104
  DSQL_close: number;
105
105
  DSQL_drop: number;
106
106
  DSQL_unprepare: number;
107
+ fb_cancel_disable: number;
108
+ fb_cancel_enable: number;
109
+ fb_cancel_raise: number;
110
+ fb_cancel_abort: number;
107
111
  fetch_next: number;
108
112
  fetch_prior: number;
109
113
  fetch_first: number;
package/lib/wire/const.js CHANGED
@@ -117,6 +117,13 @@ const dsql = {
117
117
  DSQL_drop: 2,
118
118
  DSQL_unprepare: 4, // >: 2.5
119
119
  };
120
+ // fb_cancel_operation kinds sent with op_cancel (protocol 12+ / Firebird 2.5+)
121
+ const cancelKind = {
122
+ fb_cancel_disable: 1, // disable execution of fb_cancel_raise requests
123
+ fb_cancel_enable: 2, // re-enable delivery of cancel requests
124
+ fb_cancel_raise: 3, // cancel the operation currently executing on the attachment
125
+ fb_cancel_abort: 4, // forcibly abort the attachment's current activity
126
+ };
120
127
  const fetchOp = {
121
128
  fetch_next: 0,
122
129
  fetch_prior: 1,
@@ -794,6 +801,7 @@ const Const = Object.freeze({
794
801
  ...blr,
795
802
  ...blobType,
796
803
  ...buffer,
804
+ ...cancelKind,
797
805
  ...cnct,
798
806
  ...common,
799
807
  ...connect,
@@ -12,6 +12,14 @@ declare class Database extends Events.EventEmitter {
12
12
  sequentially(query: string, params?: any, on?: any, callback?: any, options?: any): this;
13
13
  query(query: string, params?: any, callback?: any, options?: any): this;
14
14
  drop(callback?: (err?: any) => void): void;
15
+ /**
16
+ * Cancel the operation currently executing on this connection by sending
17
+ * an out-of-band op_cancel (Firebird 2.5+ / protocol 12+). The cancelled
18
+ * operation fails through its own callback with err.gdscode ===
19
+ * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
20
+ * per-attachment, not per-statement.
21
+ */
22
+ cancel(kind?: number | ((err?: any) => void), callback?: (err?: any) => void): this;
15
23
  attachEvent(callback: (err: any, evt?: any) => void): this;
16
24
  /**
17
25
  * Create a physical tablespace.
@@ -53,5 +61,21 @@ declare class Database extends Events.EventEmitter {
53
61
  * @returns {Database}
54
62
  */
55
63
  createSchema(schemaName: string, tablespaceName?: string | ((err?: any) => void), callback?: any): this;
64
+ queryAsync(query: string, params?: any, options?: any): Promise<any[]>;
65
+ executeAsync(query: string, params?: any, options?: any): Promise<any[]>;
66
+ sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void>;
67
+ transactionAsync(options?: any): Promise<any>;
68
+ startTransactionAsync(options?: any): Promise<any>;
69
+ newStatementAsync(query: string): Promise<any>;
70
+ detachAsync(force?: boolean): Promise<void>;
71
+ dropAsync(): Promise<void>;
72
+ attachEventAsync(): Promise<any>;
73
+ cancelAsync(kind?: number): Promise<void>;
74
+ /**
75
+ * Run `work` inside a transaction: commits when the returned promise
76
+ * resolves, rolls back when it rejects (the original error is rethrown,
77
+ * even if the rollback itself fails).
78
+ */
79
+ withTransaction<T>(work: (transaction: any) => Promise<T> | T, options?: any): Promise<T>;
56
80
  }
57
81
  export = Database;
@@ -273,6 +273,21 @@ class Database extends events_1.default.EventEmitter {
273
273
  drop(callback) {
274
274
  return this.connection.dropDatabase(callback);
275
275
  }
276
+ /**
277
+ * Cancel the operation currently executing on this connection by sending
278
+ * an out-of-band op_cancel (Firebird 2.5+ / protocol 12+). The cancelled
279
+ * operation fails through its own callback with err.gdscode ===
280
+ * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
281
+ * per-attachment, not per-statement.
282
+ */
283
+ cancel(kind, callback) {
284
+ if (typeof kind === 'function') {
285
+ callback = kind;
286
+ kind = undefined;
287
+ }
288
+ this.connection.cancelOperation(kind, callback);
289
+ return this;
290
+ }
276
291
  attachEvent(callback) {
277
292
  var self = this;
278
293
  const eventid = self.eventid++;
@@ -377,5 +392,75 @@ class Database extends events_1.default.EventEmitter {
377
392
  }
378
393
  return this.execute(sql, [], callback);
379
394
  }
395
+ /*
396
+ * Promise / async-await API.
397
+ * Each *Async method wraps its callback counterpart; the callback API
398
+ * stays untouched. Result metadata is only available through the
399
+ * callback API — the promises resolve with the rows alone.
400
+ */
401
+ queryAsync(query, params, options) {
402
+ var self = this;
403
+ return (0, callback_1.fromCallback)(function (cb) { self.query(query, params, cb, options); });
404
+ }
405
+ executeAsync(query, params, options) {
406
+ var self = this;
407
+ return (0, callback_1.fromCallback)(function (cb) { self.execute(query, params, cb, options); });
408
+ }
409
+ sequentiallyAsync(query, params, on, options) {
410
+ if (params instanceof Function) {
411
+ options = on;
412
+ on = params;
413
+ params = undefined;
414
+ }
415
+ var self = this;
416
+ return (0, callback_1.fromCallback)(function (cb) { self.sequentially(query, params, on, cb, options); });
417
+ }
418
+ transactionAsync(options) {
419
+ var self = this;
420
+ return (0, callback_1.fromCallback)(function (cb) { self.startTransaction(options, cb); });
421
+ }
422
+ startTransactionAsync(options) {
423
+ return this.transactionAsync(options);
424
+ }
425
+ newStatementAsync(query) {
426
+ var self = this;
427
+ return (0, callback_1.fromCallback)(function (cb) { self.newStatement(query, cb); });
428
+ }
429
+ detachAsync(force) {
430
+ var self = this;
431
+ return (0, callback_1.fromCallback)(function (cb) { self.detach(cb, force); });
432
+ }
433
+ dropAsync() {
434
+ var self = this;
435
+ return (0, callback_1.fromCallback)(function (cb) { self.drop(cb); });
436
+ }
437
+ attachEventAsync() {
438
+ var self = this;
439
+ return (0, callback_1.fromCallback)(function (cb) { self.attachEvent(cb); });
440
+ }
441
+ cancelAsync(kind) {
442
+ var self = this;
443
+ return (0, callback_1.fromCallback)(function (cb) { self.cancel(kind, cb); });
444
+ }
445
+ /**
446
+ * Run `work` inside a transaction: commits when the returned promise
447
+ * resolves, rolls back when it rejects (the original error is rethrown,
448
+ * even if the rollback itself fails).
449
+ */
450
+ async withTransaction(work, options) {
451
+ const transaction = await this.transactionAsync(options);
452
+ try {
453
+ const result = await work(transaction);
454
+ await (0, callback_1.fromCallback)(function (cb) { transaction.commit(cb); });
455
+ return result;
456
+ }
457
+ catch (err) {
458
+ try {
459
+ await (0, callback_1.fromCallback)(function (cb) { transaction.rollback(cb); });
460
+ }
461
+ catch { /* surface the original error, not the rollback failure */ }
462
+ throw err;
463
+ }
464
+ }
380
465
  }
381
466
  module.exports = Database;
@@ -21,5 +21,12 @@ declare class Statement {
21
21
  fetch(transaction: any, count: number | string, callback: (err: any, result?: any) => void): void;
22
22
  fetchScroll(transaction: any, direction: string | number, offset?: any, count?: any, callback?: any): void;
23
23
  fetchAll(transaction: any, callback: (err: any, result?: any) => void): void;
24
+ executeAsync(transaction: any, params?: any, options?: any): Promise<any>;
25
+ fetchAsync(transaction: any, count: number | string): Promise<any>;
26
+ fetchScrollAsync(transaction: any, direction: string | number, offset?: any, count?: any): Promise<any>;
27
+ fetchAllAsync(transaction: any): Promise<any>;
28
+ closeAsync(): Promise<void>;
29
+ dropAsync(): Promise<void>;
30
+ releaseAsync(): Promise<void>;
24
31
  }
25
32
  export = Statement;