node-firebird 2.3.1 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +218 -31
  2. package/lib/index.d.ts +22 -0
  3. package/lib/pool.js +97 -10
  4. package/lib/srp.js +12 -1
  5. package/lib/wire/connection.js +40 -7
  6. package/lib/wire/socket.js +13 -1
  7. package/lib/wire/xsqlvar.js +69 -6
  8. package/package.json +1 -1
  9. package/poc/README.md +160 -0
  10. package/poc/helpers.js +59 -0
  11. package/poc/node_modules/.package-lock.json +14 -0
  12. package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
  13. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
  14. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
  15. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
  16. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
  17. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
  18. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
  19. package/poc/node_modules/node-firebird/LICENSE +373 -0
  20. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
  21. package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
  22. package/poc/node_modules/node-firebird/README.md +794 -0
  23. package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
  24. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
  25. package/poc/node_modules/node-firebird/lib/callback.js +38 -0
  26. package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
  27. package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
  28. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
  29. package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
  30. package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
  31. package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
  32. package/poc/node_modules/node-firebird/lib/index.js +128 -0
  33. package/poc/node_modules/node-firebird/lib/messages.js +162 -0
  34. package/poc/node_modules/node-firebird/lib/pool.js +108 -0
  35. package/poc/node_modules/node-firebird/lib/srp.js +299 -0
  36. package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
  37. package/poc/node_modules/node-firebird/lib/utils.js +164 -0
  38. package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
  39. package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
  40. package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
  41. package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
  42. package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
  43. package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
  44. package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
  45. package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
  46. package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
  47. package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
  48. package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
  49. package/poc/node_modules/node-firebird/package.json +38 -0
  50. package/poc/node_modules/node-firebird/vitest.config.js +24 -0
  51. package/poc/package-lock.json +21 -0
  52. package/poc/package.json +12 -0
  53. package/poc/reproduce-fixed.js +150 -0
  54. package/poc/reproduce.js +133 -0
  55. package/vitest.config.js +4 -1
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "node-firebird",
3
+ "version": "2.3.1",
4
+ "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
+ "keywords": [
6
+ "firebird",
7
+ "database",
8
+ "rdbms",
9
+ "sql"
10
+ ],
11
+ "homepage": "https://github.com/hgourvest/node-firebird",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/hgourvest/node-firebird.git"
15
+ },
16
+ "author": {
17
+ "name": "Henri Gourvest",
18
+ "email": "hgourvest@gmail.com"
19
+ },
20
+ "contributors": [
21
+ "Popa Marius Adrian <mapopa@gmail.com>",
22
+ "Peter Širka <petersirka@gmail.com>"
23
+ ],
24
+ "main": "./lib",
25
+ "types": "./lib/index.d.ts",
26
+ "license": "MPL-2.0",
27
+ "scripts": {
28
+ "test": "vitest run",
29
+ "lint": "oxlint"
30
+ },
31
+ "dependencies": {
32
+ },
33
+ "devDependencies": {
34
+ "coveralls-next": "^6.0.1",
35
+ "oxlint": "^0.15.11",
36
+ "vitest": "^4.0.18"
37
+ }
38
+ }
@@ -0,0 +1,24 @@
1
+ const { defineConfig } = require('vitest/config');
2
+
3
+ module.exports = defineConfig({
4
+ test: {
5
+ globals: true,
6
+ testTimeout: 10000,
7
+ hookTimeout: 30000,
8
+ fileParallelism: false,
9
+ maxWorkers: 1,
10
+ isolate: false,
11
+ include: [
12
+ 'test/arc4.js',
13
+ 'test/protocol.js',
14
+ 'test/srp.js',
15
+ 'test/service.js',
16
+ 'test/utf8-user-identification.js',
17
+ 'test/index.js',
18
+ 'test/db-crypt-config.js',
19
+ 'test/timezone.js',
20
+ 'test/decfloat.js',
21
+ 'test/mock-server.js'
22
+ ],
23
+ },
24
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "node-firebird-pool-poc",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "node-firebird-pool-poc",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "node-firebird": "^2.3.1"
12
+ }
13
+ },
14
+ "node_modules/node-firebird": {
15
+ "version": "2.3.1",
16
+ "resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-2.3.1.tgz",
17
+ "integrity": "sha512-3yTwiLHwgtLYRv+aS4frzsQEwHMDXkP5Z1SPM/W103TvMemVRoCIaxPYO7axnUrKhgTfPHGXzqC5umYlQail/w==",
18
+ "license": "MPL-2.0"
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "node-firebird-pool-poc",
3
+ "version": "1.0.0",
4
+ "description": "Proof of concept for node-firebird pool bugs: _creating stuck, pending leak, post-destroy get()",
5
+ "scripts": {
6
+ "bug": "node reproduce.js",
7
+ "fixed": "node reproduce-fixed.js"
8
+ },
9
+ "dependencies": {
10
+ "node-firebird": "^2.3.1"
11
+ }
12
+ }
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * reproduce-fixed.js — Validates all fixes in pool-patched.js.
5
+ *
6
+ * Uses the same fake TCP server from reproduce.js (accepts connections but never
7
+ * responds to the Firebird wire protocol). The patched Pool is instantiated
8
+ * directly with Firebird's original attach function, so no server-side changes
9
+ * are needed and the fix is purely in lib/pool.js.
10
+ *
11
+ * ─── Fix 1 · connectTimeout in check() ──────────────────────────────────────
12
+ *
13
+ * A new options.connectTimeout value (ms) wraps the attach() call with a
14
+ * setTimeout. If attach() does not complete in time:
15
+ * • _creating is decremented (slot freed)
16
+ * • cb(Error) is called immediately (no hanging Promise)
17
+ * • setImmediate(check) is triggered (next pending request can be served)
18
+ * If attach() eventually returns a db after the timeout, the connection is
19
+ * discarded via db.connection._pooled = false + db.detach() (no socket leak).
20
+ *
21
+ * ─── Fix 2 · _destroyed flag in get() and check() ──────────────────────────
22
+ *
23
+ * pool.get() called after pool.destroy() is immediately rejected with
24
+ * "Pool has been destroyed". No silent accumulation in pending.
25
+ *
26
+ * ─── Fix 3 · _destroyed guard inside the attach() callback ─────────────────
27
+ *
28
+ * If pool.destroy() is called while an attach() is in flight, the late-
29
+ * arriving connection is discarded and the caller is notified with an error.
30
+ *
31
+ * ─── Fix 4 · destroy() drains pending ───────────────────────────────────────
32
+ *
33
+ * All callbacks queued in this.pending when destroy() is called receive an
34
+ * immediate "Pool is being destroyed" error instead of hanging forever.
35
+ *
36
+ * ─── Fix 5 · destroy() counts in-use connections ────────────────────────────
37
+ *
38
+ * Connections currently in use (dbinuse) are now counted down so that the
39
+ * destroy() completion callback is not blocked when in-use connections exist.
40
+ *
41
+ * ─── How to run ──────────────────────────────────────────────────────────────
42
+ *
43
+ * npm install
44
+ * node reproduce-fixed.js
45
+ *
46
+ * Expected: all callbacks receive proper errors, _creating returns to 0,
47
+ * process exits cleanly with code 0.
48
+ */
49
+
50
+ const Firebird = require('node-firebird');
51
+ const PatchedPool = require('../lib/pool');
52
+ const { log, poolState, startFakeServer } = require('./helpers');
53
+
54
+ const FAKE_PORT = 13051; // different port from reproduce.js to avoid conflicts
55
+ const CONNECT_TIMEOUT = 1500; // 1.5 s — short for demo; use 5–10 s in production
56
+ const TICK_MS = 600;
57
+
58
+ startFakeServer(FAKE_PORT, (server) => {
59
+
60
+ const options = {
61
+ host: '127.0.0.1',
62
+ port: FAKE_PORT,
63
+ database: '/tmp/poc.fdb',
64
+ user: 'SYSDBA',
65
+ password: 'masterkey',
66
+ lowercase_keys: true,
67
+ isPool: true, // mirrors what exports.pool() adds internally
68
+ connectTimeout: CONNECT_TIMEOUT, // ← the new option (Fix 1)
69
+ };
70
+
71
+ // Instantiate the patched Pool directly with Firebird's original attach function.
72
+ // This is equivalent to what exports.pool() does internally:
73
+ // return new Pool(exports.attach, max, Object.assign({}, options, { isPool: true }));
74
+ const pool = new PatchedPool(Firebird.attach, 5, options);
75
+
76
+ console.log('\n══════════════════════════════════════════════════════════════════');
77
+ console.log(' node-firebird · pool fix validation (pool-patched.js)');
78
+ console.log(` connectTimeout = ${CONNECT_TIMEOUT} ms`);
79
+ console.log('══════════════════════════════════════════════════════════════════\n');
80
+
81
+ // ── Fix 1 ─────────────────────────────────────────────────────────────────
82
+ log('FIX-1', `pool.get() — attach() will hang, timeout fires after ${CONNECT_TIMEOUT} ms`);
83
+
84
+ pool.get((err, db) => {
85
+ if (err) log('FIX-1 ✓ OK ', `callback received error (expected): "${err.message}"`);
86
+ else log('FIX-1 ✗ WRONG ', `got db — should have timed out`);
87
+ });
88
+
89
+ let tick = 0;
90
+
91
+ const interval = setInterval(() => {
92
+ tick++;
93
+ log('pool-state', poolState(pool));
94
+
95
+ // ── Fix 2 + Fix 4 (setup) ─────────────────────────────────────────────
96
+ if (tick === 3) {
97
+ // At this point the connectTimeout for tick-1 has already fired
98
+ // (1500 ms < 3 × 600 ms = 1800 ms), so pool is idle again.
99
+ log('FIX-4', 'Queuing a get() in pending, then destroying pool immediately');
100
+
101
+ pool.get((err, db) => {
102
+ // [Fix 4] destroy() must drain this callback right away.
103
+ if (err) log('FIX-4 ✓ OK ', `pending callback received error: "${err.message}"`);
104
+ else log('FIX-4 ✗ WRONG ', `got db after destroy: ${!!db}`);
105
+ });
106
+
107
+ log('FIX-4', `Before destroy — ${poolState(pool)}`);
108
+
109
+ // Suspend check() by inflating _creating so our queued get() stays in pending.
110
+ // (In real usage a second concurrent pool.get() while one is stuck in attach()
111
+ // would naturally be in pending — this simulates that scenario without timing
112
+ // tricks that make the PoC fragile.)
113
+ pool._creating = 5; // fill all slots so check() won't serve the pending cb
114
+ pool.destroy();
115
+ pool._creating = 0; // reset (already orphaned pool, just for readable output)
116
+
117
+ log('FIX-4', `After destroy — ${poolState(pool)}`);
118
+ log('FIX-4', '↑ pending callback fired SYNCHRONOUSLY inside destroy()');
119
+ }
120
+
121
+ // ── Fix 2 ──────────────────────────────────────────────────────────────
122
+ if (tick === 4) {
123
+ log('FIX-2', 'pool.get() AFTER pool.destroy() — must be rejected immediately');
124
+
125
+ pool.get((err, db) => {
126
+ // [Fix 2] _destroyed guard in get() rejects this synchronously.
127
+ if (err) log('FIX-2 ✓ OK ', `immediately rejected: "${err.message}"`);
128
+ else log('FIX-2 ✗ WRONG ', `got db after destroy: ${!!db}`);
129
+ });
130
+
131
+ log('FIX-2', `After post-destroy get — ${poolState(pool)}`);
132
+ }
133
+
134
+ // ── Summary ────────────────────────────────────────────────────────────
135
+ if (tick === 5) {
136
+ clearInterval(interval);
137
+
138
+ console.log('\n══════════════════════════════════════════════════════════════════');
139
+ log('SUMMARY', `Final state : ${poolState(pool)}`);
140
+ log('SUMMARY', `Fix 1 confirmed : _creating returned to 0 after ${CONNECT_TIMEOUT} ms timeout`);
141
+ log('SUMMARY', 'Fix 2 confirmed : post-destroy get() rejected immediately (_destroyed flag)');
142
+ log('SUMMARY', 'Fix 4 confirmed : pending callback drained synchronously by destroy()');
143
+ log('SUMMARY', 'Process exits cleanly — no hanging sockets or unresolved callbacks');
144
+ console.log('══════════════════════════════════════════════════════════════════\n');
145
+
146
+ server.close();
147
+ process.exit(0); // exit(0) = all fixes validated
148
+ }
149
+ }, TICK_MS);
150
+ });
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * reproduce.js — Demonstrates three bugs in node-firebird's Pool class (lib/pool.js).
5
+ *
6
+ * No real Firebird server is required. A fake TCP server (see helpers.js) accepts
7
+ * connections but never responds to the Firebird wire protocol, triggering the bugs.
8
+ *
9
+ * ─── Bug 1 · _creating stuck forever ────────────────────────────────────────
10
+ *
11
+ * In Pool.check(), when no idle connection exists:
12
+ *
13
+ * self._creating++;
14
+ * this.attach(options, function(err, db) {
15
+ * self._creating--; // ← only here
16
+ * ...
17
+ * cb(err, db);
18
+ * });
19
+ *
20
+ * If attach() never calls back (TCP connects, but server stalls on Firebird
21
+ * protocol, e.g. SRP auth), _creating is never decremented. The pool slot is
22
+ * permanently locked. pool.get() callers wait forever.
23
+ *
24
+ * ─── Bug 2 · destroy() does not drain pending callbacks ─────────────────────
25
+ *
26
+ * pool.destroy() only iterates this.internaldb (connected databases). Callbacks
27
+ * queued in this.pending (waiting for a free slot) are silently abandoned — they
28
+ * are never called, so any awaiting Promise hangs until the process exits.
29
+ *
30
+ * Additionally, if any connection is currently in use (dbinuse > 0), the
31
+ * destroy() callback itself is never invoked because connectionCount never
32
+ * reached zero (the in-use branch falls through without calling detachCallback).
33
+ *
34
+ * ─── Bug 3 · pool.get() after pool.destroy() silently accumulates ────────────
35
+ *
36
+ * There is no _destroyed guard. Calling pool.get() on a destroyed pool pushes
37
+ * the callback into this.pending and calls check(), which returns early because
38
+ * _destroyed is never checked. The callback is never served.
39
+ *
40
+ * ─── How to run ──────────────────────────────────────────────────────────────
41
+ *
42
+ * npm install
43
+ * node reproduce.js
44
+ *
45
+ * Expected: process does NOT exit cleanly (hangs due to open sockets / pending
46
+ * callbacks). Exit code 1 is forced after the demo to confirm bugs are present.
47
+ */
48
+
49
+ const Firebird = require('node-firebird');
50
+ const { log, poolState, startFakeServer } = require('./helpers');
51
+
52
+ const FAKE_PORT = 13050;
53
+ const TICK_MS = 600; // interval between pool-state snapshots
54
+
55
+ startFakeServer(FAKE_PORT, (server) => {
56
+
57
+ const options = {
58
+ host: '127.0.0.1',
59
+ port: FAKE_PORT,
60
+ database: '/tmp/poc.fdb',
61
+ user: 'SYSDBA',
62
+ password: 'masterkey',
63
+ lowercase_keys: true,
64
+ isPool: true,
65
+ // No connectTimeout — original (buggy) behaviour
66
+ };
67
+
68
+ const pool = Firebird.pool(5, options);
69
+
70
+ console.log('\n══════════════════════════════════════════════════════════════════');
71
+ console.log(' node-firebird · pool bug reproduction (original lib/pool.js)');
72
+ console.log('══════════════════════════════════════════════════════════════════\n');
73
+
74
+ // ── Bug 1 ─────────────────────────────────────────────────────────────────
75
+ log('BUG-1', 'pool.get() → TCP connects, Firebird protocol hangs, callback NEVER fires');
76
+
77
+ pool.get((err, db) => {
78
+ // ← BUG: this line is never reached
79
+ log('BUG-1 ✗ WRONG', `callback fired (unexpected): err=${err?.message} db=${!!db}`);
80
+ });
81
+
82
+ let tick = 0;
83
+
84
+ const interval = setInterval(() => {
85
+ tick++;
86
+ log('pool-state', poolState(pool));
87
+
88
+ // ── Bug 2 (setup) ──────────────────────────────────────────────────────
89
+ if (tick === 2) {
90
+ log('BUG-2', 'Queuing a second get() in pending, then calling destroy()');
91
+
92
+ pool.get((err, db) => {
93
+ // ← BUG: destroy() never calls this; the Promise hangs forever
94
+ if (err) log('BUG-2 ✓ OK ', `pending callback received error: "${err.message}"`);
95
+ else log('BUG-2 ✗ WRONG ', `got db unexpectedly: ${!!db}`);
96
+ });
97
+
98
+ log('BUG-2', `Before destroy — ${poolState(pool)}`);
99
+ pool.destroy();
100
+ log('BUG-2', `After destroy — ${poolState(pool)}`);
101
+ log('BUG-2', '↑ pending callback was NEVER called — Promise hangs forever');
102
+ }
103
+
104
+ // ── Bug 3 ──────────────────────────────────────────────────────────────
105
+ if (tick === 3) {
106
+ log('BUG-3', 'pool.get() AFTER pool.destroy() — should be rejected immediately');
107
+
108
+ pool.get((err, db) => {
109
+ // ← BUG: no _destroyed guard; callback silently queued, never served
110
+ if (err) log('BUG-3 ✓ OK ', `rejected: "${err.message}"`);
111
+ else log('BUG-3 ✗ WRONG ', `got db after destroy: ${!!db}`);
112
+ });
113
+
114
+ log('BUG-3', `After post-destroy get — ${poolState(pool)}`);
115
+ }
116
+
117
+ // ── Summary ────────────────────────────────────────────────────────────
118
+ if (tick === 5) {
119
+ clearInterval(interval);
120
+
121
+ console.log('\n══════════════════════════════════════════════════════════════════');
122
+ log('SUMMARY', `Final state : ${poolState(pool)}`);
123
+ log('SUMMARY', 'Bug 1 confirmed : _creating=1, permanently stuck (slot lost)');
124
+ log('SUMMARY', 'Bug 2 confirmed : pending callback from before destroy never fired');
125
+ log('SUMMARY', 'Bug 3 confirmed : post-destroy get silently accumulated in pending');
126
+ log('SUMMARY', 'Process would hang forever without forced exit below');
127
+ console.log('══════════════════════════════════════════════════════════════════\n');
128
+
129
+ server.close();
130
+ process.exit(1); // exit(1) = bugs confirmed
131
+ }
132
+ }, TICK_MS);
133
+ });
package/vitest.config.js CHANGED
@@ -16,9 +16,12 @@ module.exports = defineConfig({
16
16
  'test/utf8-user-identification.js',
17
17
  'test/index.js',
18
18
  'test/db-crypt-config.js',
19
+ 'test/encoding.js',
19
20
  'test/timezone.js',
20
21
  'test/decfloat.js',
21
- 'test/mock-server.js'
22
+ 'test/mock-server.js',
23
+ 'test/pool-fixes.js',
24
+ 'test/blob-chunks.js'
22
25
  ],
23
26
  },
24
27
  });