node-firebird 2.3.3 → 2.4.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 +544 -6
- package/lib/callback.d.ts +20 -0
- package/lib/callback.js +6 -16
- package/lib/firebird.msg.json +1370 -1370
- package/lib/gdscodes.d.ts +1519 -1520
- package/lib/gdscodes.js +5 -5
- package/lib/ieee754-decimal.d.ts +38 -0
- package/lib/ieee754-decimal.js +71 -118
- package/lib/index.d.ts +37 -338
- package/lib/index.js +97 -86
- package/lib/messages.d.ts +6 -0
- package/lib/messages.js +135 -162
- package/lib/pool.d.ts +23 -0
- package/lib/pool.js +34 -47
- package/lib/srp.d.ts +51 -0
- package/lib/srp.js +62 -72
- package/lib/types.d.ts +375 -0
- package/lib/types.js +22 -0
- package/lib/unix-crypt.d.ts +1 -0
- package/lib/unix-crypt.js +126 -152
- package/lib/utils.d.ts +16 -0
- package/lib/utils.js +24 -48
- package/lib/wire/connection.d.ts +100 -0
- package/lib/wire/connection.js +1029 -1009
- package/lib/wire/const.d.ts +567 -0
- package/lib/wire/const.js +620 -599
- package/lib/wire/database.d.ts +57 -0
- package/lib/wire/database.js +111 -108
- package/lib/wire/eventConnection.d.ts +16 -0
- package/lib/wire/eventConnection.js +29 -31
- package/lib/wire/fbEventManager.d.ts +45 -0
- package/lib/wire/fbEventManager.js +38 -57
- package/lib/wire/serialize.d.ts +97 -0
- package/lib/wire/serialize.js +42 -141
- package/lib/wire/service.d.ts +66 -0
- package/lib/wire/service.js +374 -401
- package/lib/wire/socket.d.ts +59 -0
- package/lib/wire/socket.js +79 -40
- package/lib/wire/statement.d.ts +25 -0
- package/lib/wire/statement.js +13 -9
- package/lib/wire/transaction.d.ts +21 -0
- package/lib/wire/transaction.js +33 -61
- package/lib/wire/xsqlvar.d.ts +175 -0
- package/lib/wire/xsqlvar.js +169 -269
- package/package.json +12 -4
- package/src/callback.ts +55 -0
- package/{poc/node_modules/node-firebird/lib/gdscodes.js → src/gdscodes.ts} +3 -3
- package/{poc/node_modules/node-firebird/lib/ieee754-decimal.js → src/ieee754-decimal.ts} +7 -14
- package/src/index.ts +157 -0
- package/{poc/node_modules/node-firebird/lib/messages.js → src/messages.ts} +163 -162
- package/src/pool.ts +209 -0
- package/{poc/node_modules/node-firebird/lib/srp.js → src/srp.ts} +74 -33
- package/src/types.ts +419 -0
- package/{poc/node_modules/node-firebird/lib/unix-crypt.js → src/unix-crypt.ts} +6 -12
- package/{poc/node_modules/node-firebird/lib/utils.js → src/utils.ts} +11 -20
- package/{poc/node_modules/node-firebird/lib/wire/connection.js → src/wire/connection.ts} +611 -129
- package/{poc/node_modules/node-firebird/lib/wire/const.js → src/wire/const.ts} +96 -9
- package/{poc/node_modules/node-firebird/lib/wire/database.js → src/wire/database.ts} +110 -44
- package/{poc/node_modules/node-firebird/lib/wire/eventConnection.js → src/wire/eventConnection.ts} +29 -14
- package/{poc/node_modules/node-firebird/lib/wire/fbEventManager.js → src/wire/fbEventManager.ts} +39 -30
- package/{poc/node_modules/node-firebird/lib/wire/serialize.js → src/wire/serialize.ts} +78 -59
- package/{poc/node_modules/node-firebird/lib/wire/service.js → src/wire/service.ts} +90 -88
- package/src/wire/socket.ts +262 -0
- package/src/wire/statement.ts +71 -0
- package/{poc/node_modules/node-firebird/lib/wire/transaction.js → src/wire/transaction.ts} +25 -19
- package/{poc/node_modules/node-firebird/lib/wire/xsqlvar.js → src/wire/xsqlvar.ts} +244 -143
- package/.eslintrc.json +0 -12
- package/.github/workflows/codeql.yml +0 -76
- package/.github/workflows/node.js.yml +0 -95
- package/BIGINT_MIGRATION.md +0 -374
- package/CI_DEBUGGING_GUIDE.md +0 -148
- package/ENCRYPTION_CALLBACK.md +0 -152
- package/FIREBIRD_LOG_FEATURE.md +0 -145
- package/MINIMAL_CHANGES_SUMMARY.md +0 -136
- package/PR_SUMMARY.md +0 -96
- package/ROADMAP.md +0 -223
- package/SRP_PROTOCOL.md +0 -482
- package/poc/README.md +0 -160
- package/poc/helpers.js +0 -59
- package/poc/node_modules/.package-lock.json +0 -14
- package/poc/node_modules/node-firebird/.eslintrc.json +0 -12
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +0 -76
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +0 -95
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +0 -374
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +0 -148
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +0 -152
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +0 -145
- package/poc/node_modules/node-firebird/LICENSE +0 -373
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +0 -136
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +0 -96
- package/poc/node_modules/node-firebird/README.md +0 -794
- package/poc/node_modules/node-firebird/ROADMAP.md +0 -223
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +0 -482
- package/poc/node_modules/node-firebird/lib/callback.js +0 -38
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +0 -1524
- package/poc/node_modules/node-firebird/lib/index.d.ts +0 -316
- package/poc/node_modules/node-firebird/lib/index.js +0 -128
- package/poc/node_modules/node-firebird/lib/pool.js +0 -108
- package/poc/node_modules/node-firebird/lib/wire/socket.js +0 -175
- package/poc/node_modules/node-firebird/lib/wire/statement.js +0 -48
- package/poc/node_modules/node-firebird/package.json +0 -38
- package/poc/node_modules/node-firebird/vitest.config.js +0 -24
- package/poc/package-lock.json +0 -21
- package/poc/package.json +0 -12
- package/poc/reproduce-fixed.js +0 -150
- package/poc/reproduce.js +0 -133
- package/vitest.config.js +0 -27
- /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg +0 -0
- /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg.json +0 -0
package/README.md
CHANGED
|
@@ -42,6 +42,29 @@ npm install node-firebird
|
|
|
42
42
|
var Firebird = require('node-firebird');
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
TypeScript is fully supported — the driver itself is written in TypeScript and
|
|
46
|
+
ships its own type declarations:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import * as Firebird from 'node-firebird';
|
|
50
|
+
import type { Options, Database } from 'node-firebird';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Development
|
|
54
|
+
|
|
55
|
+
The sources live in `src/` (TypeScript) and are compiled to `lib/` (CommonJS +
|
|
56
|
+
`.d.ts`) by the native TypeScript 7 compiler:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install # installs deps and builds lib/ via the prepare script
|
|
60
|
+
npm run build # compile src/ -> lib/
|
|
61
|
+
npm run typecheck # type-check sources and unit tests without emitting
|
|
62
|
+
npm test # build + run the vitest suite (unit + integration)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The integration tests expect a Firebird server on `127.0.0.1:3050`
|
|
66
|
+
(SYSDBA/masterkey); the unit tests under `test/unit/` run without a server.
|
|
67
|
+
|
|
45
68
|
### Methods
|
|
46
69
|
|
|
47
70
|
- `Firebird.escape(value) -> return {String}` - prevent for SQL Injections
|
|
@@ -75,6 +98,12 @@ options.wireCrypt = Firebird.WIRE_CRYPT_ENABLE; // default; set to Firebird.WIRE
|
|
|
75
98
|
options.pluginName = undefined; // optional, auto-negotiated; can be set to Firebird.AUTH_PLUGIN_SRP256, Firebird.AUTH_PLUGIN_SRP, or Firebird.AUTH_PLUGIN_LEGACY
|
|
76
99
|
options.dbCryptConfig = undefined; // optional; database encryption key for encrypted databases. Use 'base64:<value>' for base64-encoded keys or plain text
|
|
77
100
|
options.connectTimeout = 10000; // optional; timeout in ms for a single pool.get() attach operation (default: no timeout)
|
|
101
|
+
options.parallelWorkers = undefined; // optional; request multiple thread workers for maintenance/index tasks (FB >= 5)
|
|
102
|
+
options.maxInlineBlobSize = undefined; // optional; threshold size in bytes for inline blob transmission (default 65535, FB >= 5.0.3)
|
|
103
|
+
options.maxNegotiatedProtocols = 10; // optional; limit maximum protocol versions negotiated (default 10 for compatibility, set to 11 for FB >= 6.0)
|
|
104
|
+
options.defaultSchema = undefined; // optional; sets session CURRENT_SCHEMA at connect time (FB >= 6.0)
|
|
105
|
+
options.searchPath = undefined; // optional; ordered list/array of schemas to resolve unqualified object references (FB >= 6.0)
|
|
106
|
+
options.jsonAsObject = false; // optional; automatically stringify parameters and parse query results that contain JSON (FB >= 6.0)
|
|
78
107
|
```
|
|
79
108
|
|
|
80
109
|
### Classic
|
|
@@ -175,6 +204,10 @@ sequenceDiagram
|
|
|
175
204
|
- `db.sequentially(query, [params], function(row, index), function(err), options)` - sequentially query
|
|
176
205
|
- `db.detach(function(err))` detach a database
|
|
177
206
|
- `db.transaction(options, function(err, transaction))` create transaction
|
|
207
|
+
- `db.createTablespace(name, filePath, function(err, result))` - Create a physical tablespace (FB >= 6.0)
|
|
208
|
+
- `db.alterTablespace(name, filePath, function(err, result))` - Alter an existing tablespace physical location (FB >= 6.0)
|
|
209
|
+
- `db.dropTablespace(name, function(err, result))` - Drop a tablespace (FB >= 6.0)
|
|
210
|
+
- `db.createSchema(schemaName, [tablespaceName], function(err, result))` - Create a schema/namespace, optionally binding it to a tablespace (FB >= 6.0)
|
|
178
211
|
|
|
179
212
|
### Transaction options
|
|
180
213
|
|
|
@@ -235,6 +268,93 @@ Firebird.attach(options, function (err, db) {
|
|
|
235
268
|
});
|
|
236
269
|
```
|
|
237
270
|
|
|
271
|
+
### Tablespaces and Schema Partitioning (Firebird 6.0+)
|
|
272
|
+
|
|
273
|
+
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.
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
Firebird.attach(options, function (err, db) {
|
|
277
|
+
if (err) throw err;
|
|
278
|
+
|
|
279
|
+
// 1. Create a physical tablespace mapping to a physical storage location
|
|
280
|
+
db.createTablespace('FAST_TS', '/ssd/fast_data.ts', function (err, result) {
|
|
281
|
+
if (err) throw err;
|
|
282
|
+
console.log('Tablespace FAST_TS created successfully');
|
|
283
|
+
|
|
284
|
+
// 2. Create a schema namespace and partition it into the FAST_TS tablespace
|
|
285
|
+
db.createSchema('MYSCHEMA', 'FAST_TS', function (err, result) {
|
|
286
|
+
if (err) throw err;
|
|
287
|
+
console.log('Schema MYSCHEMA partitioned to FAST_TS');
|
|
288
|
+
|
|
289
|
+
// 3. Drop tablespace when no longer needed
|
|
290
|
+
// db.dropTablespace('FAST_TS', function (err, result) { ... });
|
|
291
|
+
|
|
292
|
+
db.detach();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Native JSON Data Type Support (Firebird 6.0+)
|
|
299
|
+
|
|
300
|
+
By enabling the `jsonAsObject` connection parameter, the driver will automatically serialize JavaScript objects/arrays passed as query parameters to JSON strings, and automatically parse returned JSON text/BLOB columns back into JavaScript objects/arrays.
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
const options = {
|
|
304
|
+
// ...other connection options
|
|
305
|
+
jsonAsObject: true,
|
|
306
|
+
blobAsText: true // recommended to read text BLOBs as strings
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
Firebird.attach(options, function (err, db) {
|
|
310
|
+
if (err) throw err;
|
|
311
|
+
|
|
312
|
+
const data = { name: 'Alice', age: 30, roles: ['admin', 'user'] };
|
|
313
|
+
|
|
314
|
+
db.query(
|
|
315
|
+
'INSERT INTO USERS (ID, PROFILE_JSON) VALUES (?, ?)',
|
|
316
|
+
[1, data],
|
|
317
|
+
function (err, result) {
|
|
318
|
+
if (err) throw err;
|
|
319
|
+
|
|
320
|
+
db.query(
|
|
321
|
+
'SELECT PROFILE_JSON FROM USERS WHERE ID = ?',
|
|
322
|
+
[1],
|
|
323
|
+
function (err, result) {
|
|
324
|
+
if (err) throw err;
|
|
325
|
+
// PROFILE_JSON is automatically parsed back to a JavaScript object
|
|
326
|
+
console.log(result[0].profile_json); // { name: 'Alice', age: 30, roles: ['admin', 'user'] }
|
|
327
|
+
db.detach();
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### SQL-Standard ROW Type (Firebird 6.0+)
|
|
336
|
+
|
|
337
|
+
Firebird 6.0+ supports the SQL-standard `ROW` type representing composite records / tuples (e.g. `ROW(id INT, name VARCHAR(20))`). Since the database server compiles row value expressions into individual scalar columns/parameters at the wire interface, you can pass individual parameters or tuple arrays natively:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
Firebird.attach(options, function (err, db) {
|
|
341
|
+
if (err) throw err;
|
|
342
|
+
|
|
343
|
+
// Use a row value expression / tuple comparison
|
|
344
|
+
db.query(
|
|
345
|
+
'SELECT * FROM USERS WHERE (ID, NAME) = (ROW(?, ?))',
|
|
346
|
+
[1, 'Alice'],
|
|
347
|
+
function (err, rows) {
|
|
348
|
+
if (err) throw err;
|
|
349
|
+
console.log(rows);
|
|
350
|
+
db.detach();
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
For PSQL block declarations (triggers, procedures), you can declare and use `ROW`/`RECORD` variables (such as `DECLARE VARIABLE myrow ROW(id INT, name VARCHAR(20))`) directly within the compiled SQL strings executed via `db.query` or `db.execute`.
|
|
357
|
+
|
|
238
358
|
### BLOB (stream)
|
|
239
359
|
|
|
240
360
|
```js
|
|
@@ -404,6 +524,8 @@ Firebird.attach(options, function (err, db) {
|
|
|
404
524
|
|
|
405
525
|
### Streaming a big data
|
|
406
526
|
|
|
527
|
+
`db.query` / `db.execute` buffer the **entire** result set into memory as an array before your callback runs — fine for small/medium results, but a poor fit for big tables or unbounded exports (this is what causes "Sequential heap limit / allocation failed"-style errors on very large result sets). `db.sequentially` / `transaction.sequentially` stream rows one at a time to an `on(row, index)` callback instead: node-firebird never accumulates the rows itself, so memory use stays flat regardless of table size (the `rows` argument passed to the completion callback is always `[]`).
|
|
528
|
+
|
|
407
529
|
```js
|
|
408
530
|
Firebird.attach(options, function (err, db) {
|
|
409
531
|
if (err) throw err;
|
|
@@ -424,6 +546,34 @@ Firebird.attach(options, function (err, db) {
|
|
|
424
546
|
});
|
|
425
547
|
```
|
|
426
548
|
|
|
549
|
+
#### Backpressure
|
|
550
|
+
|
|
551
|
+
Rows are still fetched from the server in batches (200 rows per round-trip) as fast as your `on` callback returns. If you're forwarding each row to something that can fall behind (an HTTP response, a file write stream, a rate-limited API), declare `on` with a third `next` parameter — or return a `Promise` — and node-firebird will wait for you to call it (or for the promise to resolve) before fetching or processing the next row:
|
|
552
|
+
|
|
553
|
+
```js
|
|
554
|
+
db.sequentially(
|
|
555
|
+
'SELECT * FROM BIGTABLE',
|
|
556
|
+
function (row, index, next) {
|
|
557
|
+
// Only ask for the next row once the downstream write has drained.
|
|
558
|
+
if (outputStream.write(JSON.stringify(row) + '\n')) {
|
|
559
|
+
next();
|
|
560
|
+
} else {
|
|
561
|
+
outputStream.once('drain', next);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
function (err) {
|
|
565
|
+
db.detach();
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
#### Do / Don't
|
|
571
|
+
|
|
572
|
+
- **Do** use `sequentially` for large tables, full-table exports, or any query whose row count you can't bound in advance.
|
|
573
|
+
- **Do** use the 3-arg `on(row, index, next)` form (or return a `Promise` from `on`) when writing rows to something that applies its own backpressure, so unprocessed rows can't pile up faster than they're consumed.
|
|
574
|
+
- **Don't** use `db.query` / `db.execute` for big or unbounded result sets — both build the full array in memory before your callback ever runs.
|
|
575
|
+
- **Don't** assume the 2-arg `on(row, index)` form throttles you — it only guarantees node-firebird itself won't buffer rows; if your handler does async work without waiting on it (e.g. fire-and-forget writes), buffering can still build up on the *consumer* side.
|
|
576
|
+
|
|
427
577
|
### Transactions
|
|
428
578
|
|
|
429
579
|
**Transaction types:**
|
|
@@ -519,9 +669,9 @@ Firebird.attach(options, function (err, db) {
|
|
|
519
669
|
|
|
520
670
|
### Firebird Database Events (POST_EVENT)
|
|
521
671
|
|
|
522
|
-
Firebird database events are **asynchronous** notifications triggered by `POST_EVENT` inside PSQL
|
|
523
|
-
|
|
524
|
-
|
|
672
|
+
Firebird database events are **asynchronous** notifications triggered by `POST_EVENT` inside PSQL
|
|
673
|
+
triggers or stored procedures. They travel over a separate "aux" connection (opened via
|
|
674
|
+
`db.attachEvent()`) and are managed through a `FbEventManager` instance.
|
|
525
675
|
|
|
526
676
|
```js
|
|
527
677
|
Firebird.attach(options, function (err, db) {
|
|
@@ -531,7 +681,8 @@ Firebird.attach(options, function (err, db) {
|
|
|
531
681
|
db.attachEvent(function (err, evtmgr) {
|
|
532
682
|
if (err) throw err;
|
|
533
683
|
|
|
534
|
-
// 2. Subscribe to one or more named events
|
|
684
|
+
// 2. Subscribe to one or more named events (names must match POST_EVENT('name') in your
|
|
685
|
+
// PSQL triggers/procedures). Resolves once op_que_events is acknowledged by the server.
|
|
535
686
|
evtmgr.registerEvent(['MY_EVENT'], function (err) {
|
|
536
687
|
if (err) throw err;
|
|
537
688
|
|
|
@@ -542,10 +693,20 @@ Firebird.attach(options, function (err, db) {
|
|
|
542
693
|
});
|
|
543
694
|
});
|
|
544
695
|
|
|
545
|
-
// 4. Unsubscribe from events
|
|
696
|
+
// 4. Unsubscribe from one or more events. Passing all currently registered names cancels
|
|
697
|
+
// the subscription (sends op_cancel_events); the manager re-subscribes automatically if
|
|
698
|
+
// other event names remain registered.
|
|
546
699
|
// evtmgr.unregisterEvent(['MY_EVENT'], function (err) { ... });
|
|
547
700
|
|
|
548
|
-
// 5.
|
|
701
|
+
// 5. Inspect the current subscription state for debugging: returns
|
|
702
|
+
// { state, hasActiveSubscription, registeredEvents, eventId,
|
|
703
|
+
// isEventConnectionOpen, isDatabaseConnectionClosed }.
|
|
704
|
+
// state is one of 'IDLE' (aux connection open, no active subscription),
|
|
705
|
+
// 'SUBSCRIBED' (op_que_events acknowledged) or 'CLOSED'.
|
|
706
|
+
// const state = evtmgr.getState();
|
|
707
|
+
|
|
708
|
+
// 6. Release the aux connection when done. Cancels any active subscription first, then
|
|
709
|
+
// gracefully closes the aux socket.
|
|
549
710
|
// evtmgr.close(function (err) { ... });
|
|
550
711
|
});
|
|
551
712
|
});
|
|
@@ -939,6 +1100,77 @@ Firebird.attach(options, function(err, db) {
|
|
|
939
1100
|
});
|
|
940
1101
|
```
|
|
941
1102
|
|
|
1103
|
+
### Bidirectional Scrollable Cursors (Firebird 5.0+)
|
|
1104
|
+
Firebird 5.0 introduced native support for scrollable cursors, enabling bi-directional result set traversal on the server side. You can execute a statement with `{ scrollable: true }` and navigate with `statement.fetchScroll()`.
|
|
1105
|
+
|
|
1106
|
+
```js
|
|
1107
|
+
db.transaction(function(err, tx) {
|
|
1108
|
+
tx.newStatement('SELECT ID, VAL FROM MY_TABLE ORDER BY ID', function(err, statement) {
|
|
1109
|
+
if (err) throw err;
|
|
1110
|
+
|
|
1111
|
+
// Execute the statement and request a scrollable cursor on the server
|
|
1112
|
+
statement.execute(tx, [], function(err) {
|
|
1113
|
+
if (err) throw err;
|
|
1114
|
+
|
|
1115
|
+
// 1. Fetch the first row
|
|
1116
|
+
statement.fetchScroll(tx, 'FIRST', 0, 1, function(err, res) {
|
|
1117
|
+
console.log('First:', res.data); // Row 1
|
|
1118
|
+
|
|
1119
|
+
// 2. Fetch the next row
|
|
1120
|
+
statement.fetchScroll(tx, 'NEXT', 0, 1, function(err, res) {
|
|
1121
|
+
console.log('Next:', res.data); // Row 2
|
|
1122
|
+
|
|
1123
|
+
// 3. Fetch the prior row
|
|
1124
|
+
statement.fetchScroll(tx, 'PRIOR', 0, 1, function(err, res) {
|
|
1125
|
+
console.log('Prior:', res.data); // Row 1 again
|
|
1126
|
+
|
|
1127
|
+
// 4. Fetch the absolute 3rd row
|
|
1128
|
+
statement.fetchScroll(tx, 'ABSOLUTE', 3, 1, function(err, res) {
|
|
1129
|
+
console.log('Absolute 3rd:', res.data); // Row 3
|
|
1130
|
+
|
|
1131
|
+
statement.release();
|
|
1132
|
+
tx.commit();
|
|
1133
|
+
db.detach();
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
}, { scrollable: true });
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
Supported directions are: `'NEXT'` (0), `'PRIOR'` (1), `'FIRST'` (2), `'LAST'` (3), `'ABSOLUTE'` (4), and `'RELATIVE'` (5).
|
|
1144
|
+
|
|
1145
|
+
### DML RETURNING Multiple Rows (Firebird 5.0+)
|
|
1146
|
+
In Firebird 5.0, DML statements like `UPDATE`, `DELETE`, and `INSERT ... SELECT` with a `RETURNING` clause can return multiple rows. When executing these statements, the driver receives an array of objects representing all the affected rows:
|
|
1147
|
+
|
|
1148
|
+
```js
|
|
1149
|
+
db.query(
|
|
1150
|
+
'UPDATE MY_TABLE SET VAL = VAL || \'!\' WHERE ID > 1 RETURNING ID, VAL',
|
|
1151
|
+
[],
|
|
1152
|
+
function(err, rows) {
|
|
1153
|
+
if (err) throw err;
|
|
1154
|
+
console.log(rows); // Array of updated rows: [{ id: 2, val: 'two!' }, { id: 3, val: 'three!' }]
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
### SKIP LOCKED (Firebird 5.0+)
|
|
1160
|
+
Firebird 5.0 supports the `SKIP LOCKED` clause with `SELECT ... WITH LOCK`, `UPDATE`, and `DELETE` statements. This allows transactions to skip rows currently locked by other transactions instead of waiting or raising lock conflict errors, making it ideal for concurrency queues:
|
|
1161
|
+
|
|
1162
|
+
```js
|
|
1163
|
+
// Selects unlocked rows, skipping any locked by concurrent processes
|
|
1164
|
+
db.query(
|
|
1165
|
+
'SELECT * FROM QUEUE_TASK WHERE STATUS = \'PENDING\' WITH LOCK SKIP LOCKED',
|
|
1166
|
+
[],
|
|
1167
|
+
function(err, result) {
|
|
1168
|
+
if (err) throw err;
|
|
1169
|
+
console.log(result);
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
```
|
|
1173
|
+
|
|
942
1174
|
### Advanced Connection Pooling & Life-cycle
|
|
943
1175
|
```js
|
|
944
1176
|
var pool = Firebird.pool(10, {
|
|
@@ -968,6 +1200,312 @@ process.on('SIGTERM', function() {
|
|
|
968
1200
|
});
|
|
969
1201
|
```
|
|
970
1202
|
|
|
1203
|
+
## Using node-firebird with Express.js
|
|
1204
|
+
|
|
1205
|
+
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).
|
|
1206
|
+
|
|
1207
|
+
### Recommended architecture: one pool per app, created at startup
|
|
1208
|
+
|
|
1209
|
+
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.
|
|
1210
|
+
|
|
1211
|
+
```js
|
|
1212
|
+
// db.js
|
|
1213
|
+
const Firebird = require('node-firebird');
|
|
1214
|
+
|
|
1215
|
+
const options = {
|
|
1216
|
+
host: '127.0.0.1',
|
|
1217
|
+
database: '/var/lib/firebird/data/app.fdb',
|
|
1218
|
+
user: 'SYSDBA',
|
|
1219
|
+
password: 'masterkey',
|
|
1220
|
+
connectTimeout: 5000,
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// Size the pool to your expected concurrency, not to the number of requests.
|
|
1224
|
+
const pool = Firebird.pool(10, options);
|
|
1225
|
+
|
|
1226
|
+
module.exports = pool;
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
```js
|
|
1230
|
+
// app.js
|
|
1231
|
+
const express = require('express');
|
|
1232
|
+
const pool = require('./db');
|
|
1233
|
+
|
|
1234
|
+
const app = express();
|
|
1235
|
+
|
|
1236
|
+
// ...routes...
|
|
1237
|
+
|
|
1238
|
+
const server = app.listen(3000);
|
|
1239
|
+
|
|
1240
|
+
// Close the pool (and reject any in-flight pool.get() calls) on shutdown.
|
|
1241
|
+
process.on('SIGTERM', function () {
|
|
1242
|
+
server.close(function () {
|
|
1243
|
+
pool.destroy();
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
### Request lifecycle: acquire → query → always release
|
|
1249
|
+
|
|
1250
|
+
Every request that touches the database must release its connection back to the pool exactly once, on every code path: success, query error, or a synchronous throw. There is no native `finally` across async callbacks, so a small helper plays that role:
|
|
1251
|
+
|
|
1252
|
+
```js
|
|
1253
|
+
// withConnection.js
|
|
1254
|
+
function withConnection(pool, handler) {
|
|
1255
|
+
return function (req, res, next) {
|
|
1256
|
+
pool.get(function (err, db) {
|
|
1257
|
+
if (err) return next(err);
|
|
1258
|
+
|
|
1259
|
+
var finished = false;
|
|
1260
|
+
|
|
1261
|
+
// Safe to call more than once; only the first call has any effect.
|
|
1262
|
+
function done(err) {
|
|
1263
|
+
if (finished) return;
|
|
1264
|
+
finished = true;
|
|
1265
|
+
db.detach();
|
|
1266
|
+
if (err) next(err);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
try {
|
|
1270
|
+
handler(db, req, res, done);
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
done(err);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
module.exports = withConnection;
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
```js
|
|
1282
|
+
app.get('/users', withConnection(pool, function (db, req, res, done) {
|
|
1283
|
+
db.query('SELECT ID, ALIAS FROM USERS', function (err, rows) {
|
|
1284
|
+
if (err) return done(err);
|
|
1285
|
+
res.json(rows);
|
|
1286
|
+
done();
|
|
1287
|
+
});
|
|
1288
|
+
}));
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
### Transaction middleware: commit on success, rollback on error
|
|
1292
|
+
|
|
1293
|
+
For write endpoints, acquire a connection and start a transaction in middleware, expose it as `req.tx`, and let the route handler decide whether to commit or roll back:
|
|
1294
|
+
|
|
1295
|
+
```js
|
|
1296
|
+
function transactional(pool) {
|
|
1297
|
+
return function (req, res, next) {
|
|
1298
|
+
pool.get(function (err, db) {
|
|
1299
|
+
if (err) return next(err);
|
|
1300
|
+
|
|
1301
|
+
db.transaction(function (err, tx) {
|
|
1302
|
+
if (err) {
|
|
1303
|
+
db.detach();
|
|
1304
|
+
return next(err);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
req.tx = tx;
|
|
1308
|
+
|
|
1309
|
+
// Call on success — commits, releases the connection, then responds.
|
|
1310
|
+
req.commit = function (payload) {
|
|
1311
|
+
tx.commit(function (err) {
|
|
1312
|
+
db.detach();
|
|
1313
|
+
if (err) return next(err);
|
|
1314
|
+
res.json(payload);
|
|
1315
|
+
});
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
// Call on failure — rolls back, releases the connection, then
|
|
1319
|
+
// forwards the error to Express's error-handling middleware.
|
|
1320
|
+
req.rollbackOnError = function (err) {
|
|
1321
|
+
tx.rollback(function () {
|
|
1322
|
+
db.detach();
|
|
1323
|
+
next(err);
|
|
1324
|
+
});
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
next();
|
|
1328
|
+
});
|
|
1329
|
+
});
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
app.post('/orders', transactional(pool), function (req, res, next) {
|
|
1334
|
+
req.tx.query(
|
|
1335
|
+
'INSERT INTO ORDERS (CUSTOMER, TOTAL) VALUES (?, ?) RETURNING ID',
|
|
1336
|
+
[req.body.customer, req.body.total],
|
|
1337
|
+
function (err, result) {
|
|
1338
|
+
if (err) return req.rollbackOnError(err);
|
|
1339
|
+
req.commit(result);
|
|
1340
|
+
}
|
|
1341
|
+
);
|
|
1342
|
+
});
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
### Error handling: map Firebird errors to HTTP status codes
|
|
1346
|
+
|
|
1347
|
+
Firebird errors surface as a plain `Error` with `.message`, `.gdscode`, and `.gdsparams` (see `lib/callback.js`). Map the codes you care about — exported as named constants from `node-firebird/lib/gdscodes` — to HTTP status codes in your error-handling middleware, and log the original error server-side only. Don't forward `err.message` as-is to clients: it can contain SQL text, table/column names, or file paths.
|
|
1348
|
+
|
|
1349
|
+
```js
|
|
1350
|
+
const { GDSCode } = require('node-firebird/lib/gdscodes');
|
|
1351
|
+
|
|
1352
|
+
function mapFirebirdErrorToStatus(err) {
|
|
1353
|
+
switch (err.gdscode) {
|
|
1354
|
+
case GDSCode.UNIQUE_KEY_VIOLATION:
|
|
1355
|
+
return 409; // Conflict
|
|
1356
|
+
case GDSCode.LOCK_CONFLICT:
|
|
1357
|
+
return 423; // Locked
|
|
1358
|
+
default:
|
|
1359
|
+
return 500;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Express error-handling middleware (register last, with 4 arguments).
|
|
1364
|
+
app.use(function (err, req, res, next) {
|
|
1365
|
+
console.error(err); // full detail goes to server logs only
|
|
1366
|
+
|
|
1367
|
+
res.status(mapFirebirdErrorToStatus(err)).json({
|
|
1368
|
+
error: err.gdscode === GDSCode.UNIQUE_KEY_VIOLATION ? 'Duplicate record' : 'Request failed',
|
|
1369
|
+
});
|
|
1370
|
+
});
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
### BLOB streaming: pipe a BLOB column straight to the response
|
|
1374
|
+
|
|
1375
|
+
Blob columns come back from `db.query`/`transaction.query` as functions (see [Reading Blobs](#reading-blobs-asynchronous)). Call the column function to get an `EventEmitter` you can `.pipe()` straight into `res`, and release the connection once streaming ends, errors, *or* the client disconnects early.
|
|
1376
|
+
|
|
1377
|
+
```js
|
|
1378
|
+
app.get('/users/:id/picture', withConnection(pool, function (db, req, res, done) {
|
|
1379
|
+
db.query(
|
|
1380
|
+
'SELECT USERPICTURE FROM USERS WHERE ID = ?',
|
|
1381
|
+
[req.params.id],
|
|
1382
|
+
function (err, rows) {
|
|
1383
|
+
if (err) return done(err);
|
|
1384
|
+
if (!rows.length) {
|
|
1385
|
+
res.sendStatus(404);
|
|
1386
|
+
return done();
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
rows[0].userpicture(function (err, name, blobStream) {
|
|
1390
|
+
if (err) return done(err);
|
|
1391
|
+
|
|
1392
|
+
res.type('application/octet-stream');
|
|
1393
|
+
|
|
1394
|
+
// done() is idempotent — whichever of these fires first releases
|
|
1395
|
+
// the connection; the rest are no-ops.
|
|
1396
|
+
blobStream.once('end', function () { done(); });
|
|
1397
|
+
blobStream.once('error', done);
|
|
1398
|
+
res.once('close', function () { done(); });
|
|
1399
|
+
|
|
1400
|
+
blobStream.pipe(res);
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
);
|
|
1404
|
+
}));
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
## FAQ
|
|
1408
|
+
|
|
1409
|
+
Answers to recurring questions from the [issue tracker](https://github.com/hgourvest/node-firebird/issues).
|
|
1410
|
+
|
|
1411
|
+
#### Can I use aggregate functions like `LIST()`? I get "no database to handle" when I call the result.
|
|
1412
|
+
|
|
1413
|
+
Yes — `LIST()` is plain SQL and needs no special driver support. The error happens because `LIST()` returns a text blob (subtype 1), and blob columns come back from `db.query`/`transaction.query` as **async reader functions** bound to the transaction the query ran in (see [Reading Blobs](#reading-blobs-asynchronous)). Calling that function without a transaction — or with a different one — is what throws "no database to handle".
|
|
1414
|
+
|
|
1415
|
+
The simplest fix is `blobAsText: true`, which returns subtype-1 blobs as plain strings automatically, no callback needed:
|
|
1416
|
+
|
|
1417
|
+
```js
|
|
1418
|
+
const options = { /* ...other options... */ blobAsText: true };
|
|
1419
|
+
|
|
1420
|
+
Firebird.attach(options, function (err, db) {
|
|
1421
|
+
if (err) throw err;
|
|
1422
|
+
db.query('SELECT LIST(RDB$FIELD_NAME) AS COLUMNS FROM RDB$RELATION_FIELDS', function (err, rows) {
|
|
1423
|
+
if (err) throw err;
|
|
1424
|
+
console.log(rows[0].columns); // plain string, not a function
|
|
1425
|
+
db.detach();
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
If you need to read it manually instead, pass the **same** transaction the query used:
|
|
1431
|
+
|
|
1432
|
+
```js
|
|
1433
|
+
db.transaction(function (err, tx) {
|
|
1434
|
+
tx.query('SELECT LIST(RDB$FIELD_NAME) AS COLUMNS FROM RDB$RELATION_FIELDS', function (err, rows) {
|
|
1435
|
+
rows[0].columns(tx, function (err, name, e) {
|
|
1436
|
+
let collected = '';
|
|
1437
|
+
e.on('data', (chunk) => (collected += chunk));
|
|
1438
|
+
e.on('end', () => console.log(collected));
|
|
1439
|
+
});
|
|
1440
|
+
});
|
|
1441
|
+
});
|
|
1442
|
+
```
|
|
1443
|
+
|
|
1444
|
+
#### Is the wire protocol version hard-coded?
|
|
1445
|
+
|
|
1446
|
+
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:
|
|
1447
|
+
|
|
1448
|
+
```js
|
|
1449
|
+
options.maxNegotiatedProtocols = 11; // offers Protocol 20 as well as 19
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
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).
|
|
1453
|
+
|
|
1454
|
+
#### BLOB reads/writes are very slow, especially for large files over a remote connection
|
|
1455
|
+
|
|
1456
|
+
This is almost always network round-trips, not the database. By default, blobs are streamed in small (1024-byte) segments; for a multi-megabyte file over a non-local connection that's thousands of round-trips. Raise `blobChunkSize` / `blobReadChunkSize` (up to the protocol max of `65535`) to fetch/send far larger segments per round-trip — see [Optimizing BLOB Read/Write Chunk Sizes](#optimizing-blob-readwrite-chunk-sizes):
|
|
1457
|
+
|
|
1458
|
+
```js
|
|
1459
|
+
options.blobChunkSize = 65535;
|
|
1460
|
+
options.blobReadChunkSize = 65535;
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
If your server and client are on the same host, this won't matter much — the slowdown is latency-bound, not throughput-bound.
|
|
1464
|
+
|
|
1465
|
+
#### How do I use an encoding other than UTF-8 (e.g. WIN1252/Latin1)?
|
|
1466
|
+
|
|
1467
|
+
Set `options.encoding` — no source changes required (see [Character Set & Encoding Support](#character-set--encoding-support) for the full mapping table):
|
|
1468
|
+
|
|
1469
|
+
```js
|
|
1470
|
+
options.encoding = 'WIN1252'; // or 'ISO8859_1', 'LATIN1', 'ASCII', 'NONE'
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
`options.encoding` must match the character set the *database itself* was created with. If you set an encoding that doesn't match the database's charset, Firebird will reject transliteration of characters that don't exist in both charsets with an error like `Cannot transliterate character between character sets` — that's the server protecting you from silent data corruption, not a driver bug. When in doubt, check the database's `RDB$CHARACTER_SETS` / connection charset rather than guessing.
|
|
1474
|
+
|
|
1475
|
+
If your database uses `charset NONE` (no transliteration at all — the server stores whatever bytes it's given) and you need full control over the byte encoding yourself, pass a `Buffer` instead of a string for text-column parameters. node-firebird writes `Buffer` values through unchanged, bypassing Node's UTF-8 string handling entirely — combine with a transliteration library like [`iconv-lite`](https://www.npmjs.com/package/iconv-lite) to produce the bytes:
|
|
1476
|
+
|
|
1477
|
+
```js
|
|
1478
|
+
const iconv = require('iconv-lite');
|
|
1479
|
+
options.encoding = 'NONE';
|
|
1480
|
+
|
|
1481
|
+
db.query(
|
|
1482
|
+
'INSERT INTO NOTES (ID, BODY) VALUES (?, ?)',
|
|
1483
|
+
[1, iconv.encode('Café', 'win1252')], // pre-encoded bytes, written as-is
|
|
1484
|
+
function (err) { /* ... */ }
|
|
1485
|
+
);
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
#### I get "Dynamic SQL Error" (string truncation) using `LIKE` with a pattern longer than the column
|
|
1489
|
+
|
|
1490
|
+
```
|
|
1491
|
+
SELECT * FROM ACTORS WHERE NAME LIKE 'James Wick%' -- NAME is VARCHAR(10), literal is 11 chars
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
On some Firebird server versions, the optimizer infers a literal's type from the column it's compared against and can raise a truncation error at compile time if an inline string literal is longer than the column's declared length — even though the same query works fine as a parameterized query. This is server-side SQL compilation behavior, not something node-firebird controls; it did not reproduce against the Firebird 6.0 server used by this project's own test suite, so it appears to be limited to certain older engine versions/optimizer paths.
|
|
1495
|
+
|
|
1496
|
+
The reliable fix, which also happens to be the general best practice (see [Parametrized Queries](#parametrized-queries)), is to pass the pattern as a parameter instead of inlining it:
|
|
1497
|
+
|
|
1498
|
+
```js
|
|
1499
|
+
// Instead of embedding the literal in the SQL text:
|
|
1500
|
+
db.query("SELECT * FROM ACTORS WHERE NAME LIKE 'James Wick%'", ...);
|
|
1501
|
+
|
|
1502
|
+
// Bind it as a parameter:
|
|
1503
|
+
db.query('SELECT * FROM ACTORS WHERE NAME LIKE ?', ['James Wick%'], function (err, rows) {
|
|
1504
|
+
if (err) throw err;
|
|
1505
|
+
console.log(rows);
|
|
1506
|
+
});
|
|
1507
|
+
```
|
|
1508
|
+
|
|
971
1509
|
## Contributors
|
|
972
1510
|
|
|
973
1511
|
- Henri Gourvest, <https://github.com/hgourvest>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A single Firebird status-vector entry (gds code plus optional parameters). */
|
|
2
|
+
export interface FbStatusItem {
|
|
3
|
+
gdscode: number;
|
|
4
|
+
params?: any[];
|
|
5
|
+
}
|
|
6
|
+
/** Error-like object produced by the wire protocol layer. */
|
|
7
|
+
export interface FbStatusObject {
|
|
8
|
+
status?: FbStatusItem[];
|
|
9
|
+
message?: string;
|
|
10
|
+
sqlcode?: number;
|
|
11
|
+
}
|
|
12
|
+
/** An Error enriched with the Firebird gds code and its parameters. */
|
|
13
|
+
export interface FbError extends Error {
|
|
14
|
+
gdscode?: number;
|
|
15
|
+
gdsparams?: any[];
|
|
16
|
+
}
|
|
17
|
+
export type SimpleCallback = (err?: any) => void;
|
|
18
|
+
export type Callback<T = any> = (err?: any, result?: T) => void;
|
|
19
|
+
export declare function doError(obj: any, callback?: (...args: any[]) => void): void;
|
|
20
|
+
export declare function doCallback<T>(obj: T, callback?: Callback<T>): void;
|
package/lib/callback.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doError = doError;
|
|
4
|
+
exports.doCallback = doCallback;
|
|
1
5
|
function doError(obj, callback) {
|
|
2
6
|
if (callback)
|
|
3
|
-
callback(obj)
|
|
7
|
+
callback(obj);
|
|
4
8
|
}
|
|
5
|
-
|
|
6
9
|
function isError(obj) {
|
|
7
|
-
return Boolean(
|
|
8
|
-
obj != null && typeof obj === "object" && !Array.isArray(obj) && obj.status
|
|
9
|
-
);
|
|
10
|
+
return Boolean(obj != null && typeof obj === "object" && !Array.isArray(obj) && obj.status);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
12
|
function doCallback(obj, callback) {
|
|
13
|
-
|
|
14
13
|
if (!callback)
|
|
15
14
|
return;
|
|
16
|
-
|
|
17
15
|
if (obj instanceof Error) {
|
|
18
16
|
callback(obj);
|
|
19
17
|
return;
|
|
20
18
|
}
|
|
21
|
-
|
|
22
19
|
if (isError(obj)) {
|
|
23
20
|
var error = new Error(obj.message);
|
|
24
21
|
var status = obj.status && obj.status.length && obj.status[0] || {};
|
|
@@ -27,12 +24,5 @@ function doCallback(obj, callback) {
|
|
|
27
24
|
callback(error);
|
|
28
25
|
return;
|
|
29
26
|
}
|
|
30
|
-
|
|
31
27
|
callback(undefined, obj);
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
module.exports = {
|
|
36
|
-
doError,
|
|
37
|
-
doCallback
|
|
38
28
|
}
|