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.
@@ -4,6 +4,7 @@
4
4
  * Statement
5
5
  *
6
6
  ***************************************/
7
+ const callback_1 = require("../callback");
7
8
  class Statement {
8
9
  constructor(connection) {
9
10
  this.connection = connection;
@@ -48,5 +49,34 @@ class Statement {
48
49
  fetchAll(transaction, callback) {
49
50
  this.connection.fetchAll(this, transaction, callback);
50
51
  }
52
+ /* Promise / async-await API — wrappers over the callback methods above. */
53
+ executeAsync(transaction, params, options) {
54
+ var self = this;
55
+ return (0, callback_1.fromCallback)(function (cb) { self.execute(transaction, params, cb, options); });
56
+ }
57
+ fetchAsync(transaction, count) {
58
+ var self = this;
59
+ return (0, callback_1.fromCallback)(function (cb) { self.fetch(transaction, count, cb); });
60
+ }
61
+ fetchScrollAsync(transaction, direction, offset, count) {
62
+ var self = this;
63
+ return (0, callback_1.fromCallback)(function (cb) { self.fetchScroll(transaction, direction, offset, count, cb); });
64
+ }
65
+ fetchAllAsync(transaction) {
66
+ var self = this;
67
+ return (0, callback_1.fromCallback)(function (cb) { self.fetchAll(transaction, cb); });
68
+ }
69
+ closeAsync() {
70
+ var self = this;
71
+ return (0, callback_1.fromCallback)(function (cb) { self.close(cb); });
72
+ }
73
+ dropAsync() {
74
+ var self = this;
75
+ return (0, callback_1.fromCallback)(function (cb) { self.drop(cb); });
76
+ }
77
+ releaseAsync() {
78
+ var self = this;
79
+ return (0, callback_1.fromCallback)(function (cb) { self.release(cb); });
80
+ }
51
81
  }
52
82
  module.exports = Statement;
@@ -1,8 +1,3 @@
1
- /***************************************
2
- *
3
- * Transaction
4
- *
5
- ***************************************/
6
1
  declare class Transaction {
7
2
  connection: any;
8
3
  db: any;
@@ -17,5 +12,13 @@ declare class Transaction {
17
12
  rollback(callback?: (err?: any) => void): void;
18
13
  commitRetaining(callback?: (err?: any) => void): void;
19
14
  rollbackRetaining(callback?: (err?: any) => void): void;
15
+ queryAsync(query: string, params?: any, options?: any): Promise<any[]>;
16
+ executeAsync(query: string, params?: any, options?: any): Promise<any[]>;
17
+ sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void>;
18
+ newStatementAsync(query: string): Promise<any>;
19
+ commitAsync(): Promise<void>;
20
+ rollbackAsync(): Promise<void>;
21
+ commitRetainingAsync(): Promise<void>;
22
+ rollbackRetainingAsync(): Promise<void>;
20
23
  }
21
24
  export = Transaction;
@@ -10,6 +10,36 @@ const const_1 = __importDefault(require("./const"));
10
10
  * Transaction
11
11
  *
12
12
  ***************************************/
13
+ /** The error delivered when options.signal was already aborted on entry. */
14
+ function abortError(signal) {
15
+ if (signal && signal.reason instanceof Error)
16
+ return signal.reason;
17
+ var err = new Error('The operation was aborted');
18
+ err.name = 'AbortError';
19
+ err.code = 'ABORT_ERR';
20
+ return err;
21
+ }
22
+ /**
23
+ * Wire an AbortSignal to a running statement: on abort, send an out-of-band
24
+ * op_cancel so the server fails the executing operation with isc_cancelled
25
+ * (surfaced through the statement's own callback as err.gdscode ===
26
+ * GDSCode.CANCELLED). Returns the wrapped callback that detaches the
27
+ * listener once the operation settles.
28
+ */
29
+ function hookAbortSignal(connection, signal, callback) {
30
+ var settled = false;
31
+ var onAbort = function () {
32
+ if (!settled)
33
+ connection.cancelOperation(const_1.default.fb_cancel_raise);
34
+ };
35
+ signal.addEventListener('abort', onAbort, { once: true });
36
+ return function (err, result, meta, isSelect) {
37
+ settled = true;
38
+ signal.removeEventListener('abort', onAbort);
39
+ if (callback)
40
+ callback(err, result, meta, isSelect);
41
+ };
42
+ }
13
43
  class Transaction {
14
44
  constructor(connection) {
15
45
  this.connection = connection;
@@ -32,6 +62,14 @@ class Transaction {
32
62
  callback = params;
33
63
  params = undefined;
34
64
  }
65
+ var signal = options && options.signal;
66
+ if (signal) {
67
+ if (signal.aborted) {
68
+ (0, callback_1.doError)(abortError(signal), callback);
69
+ return;
70
+ }
71
+ callback = hookAbortSignal(this.connection, signal, callback);
72
+ }
35
73
  var self = this;
36
74
  this.newStatement(query, function (err, statement) {
37
75
  if (err) {
@@ -174,5 +212,43 @@ class Transaction {
174
212
  rollbackRetaining(callback) {
175
213
  this.connection.rollbackRetaining(this, callback);
176
214
  }
215
+ /* Promise / async-await API — wrappers over the callback methods above. */
216
+ queryAsync(query, params, options) {
217
+ var self = this;
218
+ return (0, callback_1.fromCallback)(function (cb) { self.query(query, params, cb, options); });
219
+ }
220
+ executeAsync(query, params, options) {
221
+ var self = this;
222
+ return (0, callback_1.fromCallback)(function (cb) { self.execute(query, params, cb, options); });
223
+ }
224
+ sequentiallyAsync(query, params, on, options) {
225
+ if (params instanceof Function) {
226
+ options = on;
227
+ on = params;
228
+ params = undefined;
229
+ }
230
+ var self = this;
231
+ return (0, callback_1.fromCallback)(function (cb) { self.sequentially(query, params, on, cb, options); });
232
+ }
233
+ newStatementAsync(query) {
234
+ var self = this;
235
+ return (0, callback_1.fromCallback)(function (cb) { self.newStatement(query, cb); });
236
+ }
237
+ commitAsync() {
238
+ var self = this;
239
+ return (0, callback_1.fromCallback)(function (cb) { self.commit(cb); });
240
+ }
241
+ rollbackAsync() {
242
+ var self = this;
243
+ return (0, callback_1.fromCallback)(function (cb) { self.rollback(cb); });
244
+ }
245
+ commitRetainingAsync() {
246
+ var self = this;
247
+ return (0, callback_1.fromCallback)(function (cb) { self.commitRetaining(cb); });
248
+ }
249
+ rollbackRetainingAsync() {
250
+ var self = this;
251
+ return (0, callback_1.fromCallback)(function (cb) { self.rollbackRetaining(cb); });
252
+ }
177
253
  }
178
254
  module.exports = Transaction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "2.4.2",
3
+ "version": "2.6.0",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
package/src/callback.ts CHANGED
@@ -20,6 +20,42 @@ export interface FbError extends Error {
20
20
  export type SimpleCallback = (err?: any) => void;
21
21
  export type Callback<T = any> = (err?: any, result?: T) => void;
22
22
 
23
+ /**
24
+ * Normalize the values the driver passes as the callback error argument.
25
+ * Most code paths already deliver Error instances, but a few older ones
26
+ * pass plain objects (status vectors, `{error, message}` wrappers). A
27
+ * Promise must reject with an Error, so wrap those while preserving all
28
+ * their properties (gdscode, gdsparams, status, sqlcode, ...).
29
+ */
30
+ export function toError(err: any): Error {
31
+ if (err instanceof Error)
32
+ return err;
33
+
34
+ var error: FbError = new Error(
35
+ err != null && typeof err === 'object' && err.message ? err.message : String(err)
36
+ );
37
+
38
+ if (err != null && typeof err === 'object')
39
+ Object.assign(error, err);
40
+
41
+ return error;
42
+ }
43
+
44
+ /**
45
+ * Run a callback-style operation and return a Promise for its result.
46
+ * Usage: fromCallback<Database>(cb => attach(options, cb))
47
+ */
48
+ export function fromCallback<T = any>(executor: (cb: Callback<T>) => void): Promise<T> {
49
+ return new Promise<T>(function(resolve, reject) {
50
+ executor(function(err?: any, result?: T) {
51
+ if (err)
52
+ reject(toError(err));
53
+ else
54
+ resolve(result as T);
55
+ });
56
+ });
57
+ }
58
+
23
59
  export function doError(obj: any, callback?: (...args: any[]) => void): void {
24
60
  if (callback)
25
61
  callback(obj)
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import Const from './wire/const';
2
- import { doError, doCallback } from './callback';
2
+ import { doError, doCallback, fromCallback } from './callback';
3
3
  import Connection from './wire/connection';
4
4
  import Pool from './pool';
5
5
  import { escape as escapeValue } from './utils';
@@ -10,6 +10,8 @@ import type {
10
10
  ServiceManagerCallback,
11
11
  SimpleCallback,
12
12
  ConnectionPool,
13
+ Database,
14
+ ServiceManager,
13
15
  } from './types';
14
16
 
15
17
  export * from './types';
@@ -155,3 +157,28 @@ export function attachOrCreate(options: Options, callback: DatabaseCallback): vo
155
157
  export function pool(max: number, options: Options): ConnectionPool {
156
158
  return new Pool(attach, max, Object.assign({}, options, { isPool: true }));
157
159
  }
160
+
161
+ /*
162
+ * Promise / async-await API.
163
+ * Wrappers over the callback functions above; the callback API stays
164
+ * untouched. Rejections are always Error instances carrying the usual
165
+ * Firebird properties (gdscode, gdsparams, ...).
166
+ */
167
+
168
+ export function attachAsync(options: SvcMgrOptions): Promise<ServiceManager>;
169
+ export function attachAsync(options: Options): Promise<Database>;
170
+ export function attachAsync(options: any): Promise<any> {
171
+ return fromCallback(function(cb) { attach(options, cb); });
172
+ }
173
+
174
+ export function createAsync(options: Options): Promise<Database> {
175
+ return fromCallback(function(cb) { create(options, cb); });
176
+ }
177
+
178
+ export function attachOrCreateAsync(options: Options): Promise<Database> {
179
+ return fromCallback(function(cb) { attachOrCreate(options, cb); });
180
+ }
181
+
182
+ export function dropAsync(options: Options): Promise<void> {
183
+ return fromCallback(function(cb) { drop(options, cb); });
184
+ }
package/src/pool.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  *
5
5
  ***************************************/
6
6
 
7
+ import { fromCallback } from './callback';
7
8
  import type { Callback } from './callback';
8
9
 
9
10
  type AttachFn = (options: any, callback: Callback) => void;
@@ -204,6 +205,33 @@ class Pool {
204
205
  }
205
206
  });
206
207
  }
208
+
209
+ /* Promise / async-await API — wrappers over the callback methods above. */
210
+
211
+ getAsync(): Promise<any> {
212
+ var self = this;
213
+ return fromCallback(function(cb) { self.get(cb); });
214
+ }
215
+
216
+ destroyAsync(): Promise<void> {
217
+ var self = this;
218
+ return fromCallback(function(cb) { self.destroy(cb); });
219
+ }
220
+
221
+ /**
222
+ * Run `work` with a connection from the pool, returning it to the pool
223
+ * (detach) when the returned promise settles — success or failure.
224
+ */
225
+ async withConnection<T>(work: (db: any) => Promise<T> | T): Promise<T> {
226
+ const db = await this.getAsync();
227
+ try {
228
+ return await work(db);
229
+ } finally {
230
+ // A pooled detach only returns the connection to the pool; do not
231
+ // let a detach hiccup mask the outcome of `work`.
232
+ await new Promise<void>(function(resolve) { db.detach(function() { resolve(); }); });
233
+ }
234
+ }
207
235
  }
208
236
 
209
237
  export = Pool;
package/src/types.ts CHANGED
@@ -78,6 +78,15 @@ export type TransactionOptions = {
78
78
  export type QueryOptions = {
79
79
  timeout?: number;
80
80
  scrollable?: boolean;
81
+ /**
82
+ * Abort the query when the signal fires (Firebird 2.5+ / protocol 12+).
83
+ * If the signal is already aborted the query is not sent at all and the
84
+ * callback/promise fails with an `AbortError`. If it fires mid-flight an
85
+ * out-of-band op_cancel is sent and the query fails with
86
+ * `err.gdscode === GDSCode.CANCELLED`. Cancellation is per-attachment:
87
+ * it cancels whatever is currently executing on the connection.
88
+ */
89
+ signal?: AbortSignal;
81
90
  }
82
91
 
83
92
  export interface Database {
@@ -94,6 +103,29 @@ export interface Database {
94
103
  alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
95
104
  dropTablespace(name: string, callback?: QueryCallback): Database;
96
105
  createSchema(schemaName: string, tablespaceName?: string | QueryCallback, callback?: QueryCallback): Database;
106
+
107
+ // Promise / async-await API (see README § Promises / async–await).
108
+ // Result metadata is only available through the callback API.
109
+ queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
110
+ executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
111
+ sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
112
+ sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
113
+ transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
114
+ startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
115
+ newStatementAsync(query: string): Promise<Statement>;
116
+ detachAsync(force?: boolean): Promise<void>;
117
+ dropAsync(): Promise<void>;
118
+ attachEventAsync(): Promise<any>;
119
+ /** Starts a transaction, commits when `work` resolves, rolls back when it rejects. */
120
+ withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T>;
121
+ /**
122
+ * Cancel the operation currently executing on this connection
123
+ * (Firebird 2.5+ / protocol 12+). The cancelled operation fails through
124
+ * its own callback/promise with `err.gdscode === GDSCode.CANCELLED`.
125
+ */
126
+ cancel(callback?: SimpleCallback): Database;
127
+ cancel(kind: number, callback?: SimpleCallback): Database;
128
+ cancelAsync(kind?: number): Promise<void>;
97
129
  }
98
130
 
99
131
  export interface Transaction {
@@ -105,6 +137,17 @@ export interface Transaction {
105
137
  commitRetaining(callback?: SimpleCallback): void;
106
138
  rollback(callback?: SimpleCallback): void;
107
139
  rollbackRetaining(callback?: SimpleCallback): void;
140
+
141
+ // Promise / async-await API
142
+ queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
143
+ executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
144
+ sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
145
+ sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
146
+ newStatementAsync(query: string): Promise<Statement>;
147
+ commitAsync(): Promise<void>;
148
+ commitRetainingAsync(): Promise<void>;
149
+ rollbackAsync(): Promise<void>;
150
+ rollbackRetainingAsync(): Promise<void>;
108
151
  }
109
152
 
110
153
  export interface Statement {
@@ -115,6 +158,15 @@ export interface Statement {
115
158
  fetch(transaction: Transaction, count: number, callback: QueryCallback): void;
116
159
  fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
117
160
  fetchAll(transaction: Transaction, callback: QueryCallback): void;
161
+
162
+ // Promise / async-await API
163
+ executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
164
+ fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
165
+ fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
166
+ fetchAllAsync(transaction: Transaction): Promise<any>;
167
+ closeAsync(): Promise<void>;
168
+ dropAsync(): Promise<void>;
169
+ releaseAsync(): Promise<void>;
118
170
  }
119
171
 
120
172
  export type SupportedCharacterSet = |
@@ -240,6 +292,12 @@ export interface SvcMgrOptions extends Options {
240
292
  export interface ConnectionPool {
241
293
  get(callback: DatabaseCallback): void;
242
294
  destroy(callback?: SimpleCallback): void;
295
+
296
+ // Promise / async-await API
297
+ getAsync(): Promise<Database>;
298
+ destroyAsync(): Promise<void>;
299
+ /** Acquire a connection, run `work`, always return the connection to the pool. */
300
+ withConnection<T>(work: (db: Database) => Promise<T> | T): Promise<T>;
243
301
  }
244
302
 
245
303
  export interface ReadableOptions {
@@ -369,6 +369,38 @@ class Connection {
369
369
  }
370
370
 
371
371
 
372
+ /**
373
+ * Send an out-of-band op_cancel packet (protocol 12+ / Firebird 2.5+).
374
+ * The server reads it asynchronously while an operation is executing and
375
+ * makes that operation fail with isc_cancelled (GDSCode.CANCELLED); the
376
+ * op_cancel packet itself has no response, so nothing is queued here.
377
+ */
378
+ cancelOperation(kind, callback) {
379
+ if (typeof kind === 'function') {
380
+ callback = kind;
381
+ kind = undefined;
382
+ }
383
+ kind = kind || Const.fb_cancel_raise;
384
+
385
+ if (this._isClosed)
386
+ return this.throwClosed(callback);
387
+
388
+ if (!this.accept || this.accept.protocolVersion < Const.PROTOCOL_VERSION12) {
389
+ doError(new Error('Query cancellation requires protocol 12+ (Firebird 2.5 or newer)'), callback);
390
+ return;
391
+ }
392
+
393
+ var msg = this._msg;
394
+ msg.pos = 0;
395
+ msg.addInt(Const.op_cancel);
396
+ msg.addInt(kind);
397
+ this._socket.write(msg.getData());
398
+
399
+ if (callback)
400
+ callback();
401
+ }
402
+
403
+
372
404
  _queueEvent(callback, defer = false) {
373
405
  var self = this;
374
406
 
@@ -2185,12 +2217,13 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2185
2217
  pluginName: accept.pluginName
2186
2218
  };
2187
2219
 
2188
- console.log('--- DEBUG SRP Handshake ---');
2189
- console.log('salt:', cnx.serverKeys.salt);
2190
- console.log('server public key:', cnx.serverKeys.public.toString(16));
2191
- console.log('client public key:', cnx.clientKeys.public.toString(16));
2192
- console.log('client private key:', cnx.clientKeys.private.toString(16));
2193
- console.log('hashAlgo:', accept.srpAlgo);
2220
+ if (process.env.FIREBIRD_DEBUG) {
2221
+ console.log('--- DEBUG SRP Handshake ---');
2222
+ console.log('salt:', cnx.serverKeys.salt);
2223
+ console.log('server public key:', cnx.serverKeys.public.toString(16));
2224
+ console.log('client public key:', cnx.clientKeys.public.toString(16));
2225
+ console.log('hashAlgo:', accept.srpAlgo);
2226
+ }
2194
2227
 
2195
2228
  const _t1 = Date.now();
2196
2229
  var proof = srp.clientProof(
@@ -2203,8 +2236,11 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2203
2236
  accept.srpAlgo
2204
2237
  );
2205
2238
 
2206
- console.log('client proof M1:', proof.authData.toString(16));
2207
- console.log('client session key K:', proof.clientSessionKey.toString(16));
2239
+ if (process.env.FIREBIRD_DEBUG) {
2240
+ // Never log the private key or the session key: the
2241
+ // session key is the wire-encryption key material.
2242
+ console.log('client proof M1:', proof.authData.toString(16));
2243
+ }
2208
2244
 
2209
2245
  if (process.env.FIREBIRD_DEBUG) {
2210
2246
  console.log('[fb-debug] srp.clientProof(%s): %dms', accept.srpAlgo, Date.now() - _t1);
@@ -2309,12 +2345,13 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2309
2345
  };
2310
2346
  var srpAlgo = crypto[pluginName];
2311
2347
 
2312
- console.log('--- DEBUG SRP Handshake ---');
2313
- console.log('salt:', cnx.serverKeys.salt);
2314
- console.log('server public key:', cnx.serverKeys.public.toString(16));
2315
- console.log('client public key:', cnx.clientKeys.public.toString(16));
2316
- console.log('client private key:', cnx.clientKeys.private.toString(16));
2317
- console.log('hashAlgo:', srpAlgo);
2348
+ if (process.env.FIREBIRD_DEBUG) {
2349
+ console.log('--- DEBUG SRP Handshake ---');
2350
+ console.log('salt:', cnx.serverKeys.salt);
2351
+ console.log('server public key:', cnx.serverKeys.public.toString(16));
2352
+ console.log('client public key:', cnx.clientKeys.public.toString(16));
2353
+ console.log('hashAlgo:', srpAlgo);
2354
+ }
2318
2355
 
2319
2356
  const _t1 = Date.now();
2320
2357
  var proof = srp.clientProof(
package/src/wire/const.ts CHANGED
@@ -132,6 +132,14 @@ const dsql = {
132
132
  DSQL_unprepare : 4, // >: 2.5
133
133
  };
134
134
 
135
+ // fb_cancel_operation kinds sent with op_cancel (protocol 12+ / Firebird 2.5+)
136
+ const cancelKind = {
137
+ fb_cancel_disable : 1, // disable execution of fb_cancel_raise requests
138
+ fb_cancel_enable : 2, // re-enable delivery of cancel requests
139
+ fb_cancel_raise : 3, // cancel the operation currently executing on the attachment
140
+ fb_cancel_abort : 4, // forcibly abort the attachment's current activity
141
+ };
142
+
135
143
  const fetchOp = {
136
144
  fetch_next : 0,
137
145
  fetch_prior : 1,
@@ -859,6 +867,7 @@ const Const = Object.freeze({
859
867
  ...blr,
860
868
  ...blobType,
861
869
  ...buffer,
870
+ ...cancelKind,
862
871
  ...cnct,
863
872
  ...common,
864
873
  ...connect,
@@ -1,5 +1,5 @@
1
1
  import Events from 'events';
2
- import { doError } from '../callback';
2
+ import { doError, fromCallback } from '../callback';
3
3
  import { escape } from '../utils';
4
4
  import Const from './const';
5
5
  import EventConnection from './eventConnection';
@@ -322,6 +322,22 @@ class Database extends Events.EventEmitter {
322
322
  return this.connection.dropDatabase(callback);
323
323
  }
324
324
 
325
+ /**
326
+ * Cancel the operation currently executing on this connection by sending
327
+ * an out-of-band op_cancel (Firebird 2.5+ / protocol 12+). The cancelled
328
+ * operation fails through its own callback with err.gdscode ===
329
+ * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
330
+ * per-attachment, not per-statement.
331
+ */
332
+ cancel(kind?: number | ((err?: any) => void), callback?: (err?: any) => void): this {
333
+ if (typeof kind === 'function') {
334
+ callback = kind;
335
+ kind = undefined;
336
+ }
337
+ this.connection.cancelOperation(kind, callback);
338
+ return this;
339
+ }
340
+
325
341
  attachEvent(callback: (err: any, evt?: any) => void): this {
326
342
  var self = this;
327
343
  const eventid = self.eventid++;
@@ -439,6 +455,86 @@ class Database extends Events.EventEmitter {
439
455
  }
440
456
  return this.execute(sql, [], callback);
441
457
  }
458
+
459
+ /*
460
+ * Promise / async-await API.
461
+ * Each *Async method wraps its callback counterpart; the callback API
462
+ * stays untouched. Result metadata is only available through the
463
+ * callback API — the promises resolve with the rows alone.
464
+ */
465
+
466
+ queryAsync(query: string, params?: any, options?: any): Promise<any[]> {
467
+ var self = this;
468
+ return fromCallback(function(cb) { self.query(query, params, cb, options); });
469
+ }
470
+
471
+ executeAsync(query: string, params?: any, options?: any): Promise<any[]> {
472
+ var self = this;
473
+ return fromCallback(function(cb) { self.execute(query, params, cb, options); });
474
+ }
475
+
476
+ sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void> {
477
+ if (params instanceof Function) {
478
+ options = on;
479
+ on = params;
480
+ params = undefined;
481
+ }
482
+ var self = this;
483
+ return fromCallback(function(cb) { self.sequentially(query, params, on, cb, options); });
484
+ }
485
+
486
+ transactionAsync(options?: any): Promise<any> {
487
+ var self = this;
488
+ return fromCallback(function(cb) { self.startTransaction(options, cb); });
489
+ }
490
+
491
+ startTransactionAsync(options?: any): Promise<any> {
492
+ return this.transactionAsync(options);
493
+ }
494
+
495
+ newStatementAsync(query: string): Promise<any> {
496
+ var self = this;
497
+ return fromCallback(function(cb) { self.newStatement(query, cb); });
498
+ }
499
+
500
+ detachAsync(force?: boolean): Promise<void> {
501
+ var self = this;
502
+ return fromCallback(function(cb) { self.detach(cb, force); });
503
+ }
504
+
505
+ dropAsync(): Promise<void> {
506
+ var self = this;
507
+ return fromCallback(function(cb) { self.drop(cb); });
508
+ }
509
+
510
+ attachEventAsync(): Promise<any> {
511
+ var self = this;
512
+ return fromCallback(function(cb) { self.attachEvent(cb); });
513
+ }
514
+
515
+ cancelAsync(kind?: number): Promise<void> {
516
+ var self = this;
517
+ return fromCallback(function(cb) { self.cancel(kind, cb); });
518
+ }
519
+
520
+ /**
521
+ * Run `work` inside a transaction: commits when the returned promise
522
+ * resolves, rolls back when it rejects (the original error is rethrown,
523
+ * even if the rollback itself fails).
524
+ */
525
+ async withTransaction<T>(work: (transaction: any) => Promise<T> | T, options?: any): Promise<T> {
526
+ const transaction = await this.transactionAsync(options);
527
+ try {
528
+ const result = await work(transaction);
529
+ await fromCallback(function(cb) { transaction.commit(cb); });
530
+ return result;
531
+ } catch (err) {
532
+ try {
533
+ await fromCallback(function(cb) { transaction.rollback(cb); });
534
+ } catch { /* surface the original error, not the rollback failure */ }
535
+ throw err;
536
+ }
537
+ }
442
538
  }
443
539
 
444
540
  export = Database;
@@ -4,6 +4,8 @@
4
4
  *
5
5
  ***************************************/
6
6
 
7
+ import { fromCallback } from '../callback';
8
+
7
9
  class Statement {
8
10
  connection: any;
9
11
  query: string;
@@ -66,6 +68,43 @@ class Statement {
66
68
  fetchAll(transaction: any, callback: (err: any, result?: any) => void): void {
67
69
  this.connection.fetchAll(this, transaction, callback);
68
70
  }
71
+
72
+ /* Promise / async-await API — wrappers over the callback methods above. */
73
+
74
+ executeAsync(transaction: any, params?: any, options?: any): Promise<any> {
75
+ var self = this;
76
+ return fromCallback(function(cb) { self.execute(transaction, params, cb, options); });
77
+ }
78
+
79
+ fetchAsync(transaction: any, count: number | string): Promise<any> {
80
+ var self = this;
81
+ return fromCallback(function(cb) { self.fetch(transaction, count, cb); });
82
+ }
83
+
84
+ fetchScrollAsync(transaction: any, direction: string | number, offset?: any, count?: any): Promise<any> {
85
+ var self = this;
86
+ return fromCallback(function(cb) { self.fetchScroll(transaction, direction, offset, count, cb); });
87
+ }
88
+
89
+ fetchAllAsync(transaction: any): Promise<any> {
90
+ var self = this;
91
+ return fromCallback(function(cb) { self.fetchAll(transaction, cb); });
92
+ }
93
+
94
+ closeAsync(): Promise<void> {
95
+ var self = this;
96
+ return fromCallback(function(cb) { self.close(cb); });
97
+ }
98
+
99
+ dropAsync(): Promise<void> {
100
+ var self = this;
101
+ return fromCallback(function(cb) { self.drop(cb); });
102
+ }
103
+
104
+ releaseAsync(): Promise<void> {
105
+ var self = this;
106
+ return fromCallback(function(cb) { self.release(cb); });
107
+ }
69
108
  }
70
109
 
71
110
  export = Statement;