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.
Files changed (109) hide show
  1. package/README.md +544 -6
  2. package/lib/callback.d.ts +20 -0
  3. package/lib/callback.js +6 -16
  4. package/lib/firebird.msg.json +1370 -1370
  5. package/lib/gdscodes.d.ts +1519 -1520
  6. package/lib/gdscodes.js +5 -5
  7. package/lib/ieee754-decimal.d.ts +38 -0
  8. package/lib/ieee754-decimal.js +71 -118
  9. package/lib/index.d.ts +37 -338
  10. package/lib/index.js +97 -86
  11. package/lib/messages.d.ts +6 -0
  12. package/lib/messages.js +135 -162
  13. package/lib/pool.d.ts +23 -0
  14. package/lib/pool.js +34 -47
  15. package/lib/srp.d.ts +51 -0
  16. package/lib/srp.js +62 -72
  17. package/lib/types.d.ts +375 -0
  18. package/lib/types.js +22 -0
  19. package/lib/unix-crypt.d.ts +1 -0
  20. package/lib/unix-crypt.js +126 -152
  21. package/lib/utils.d.ts +16 -0
  22. package/lib/utils.js +24 -48
  23. package/lib/wire/connection.d.ts +100 -0
  24. package/lib/wire/connection.js +1029 -1009
  25. package/lib/wire/const.d.ts +567 -0
  26. package/lib/wire/const.js +620 -599
  27. package/lib/wire/database.d.ts +57 -0
  28. package/lib/wire/database.js +111 -108
  29. package/lib/wire/eventConnection.d.ts +16 -0
  30. package/lib/wire/eventConnection.js +29 -31
  31. package/lib/wire/fbEventManager.d.ts +45 -0
  32. package/lib/wire/fbEventManager.js +38 -57
  33. package/lib/wire/serialize.d.ts +97 -0
  34. package/lib/wire/serialize.js +42 -141
  35. package/lib/wire/service.d.ts +66 -0
  36. package/lib/wire/service.js +374 -401
  37. package/lib/wire/socket.d.ts +59 -0
  38. package/lib/wire/socket.js +79 -40
  39. package/lib/wire/statement.d.ts +25 -0
  40. package/lib/wire/statement.js +13 -9
  41. package/lib/wire/transaction.d.ts +21 -0
  42. package/lib/wire/transaction.js +33 -61
  43. package/lib/wire/xsqlvar.d.ts +175 -0
  44. package/lib/wire/xsqlvar.js +169 -269
  45. package/package.json +12 -4
  46. package/src/callback.ts +55 -0
  47. package/{poc/node_modules/node-firebird/lib/gdscodes.js → src/gdscodes.ts} +3 -3
  48. package/{poc/node_modules/node-firebird/lib/ieee754-decimal.js → src/ieee754-decimal.ts} +7 -14
  49. package/src/index.ts +157 -0
  50. package/{poc/node_modules/node-firebird/lib/messages.js → src/messages.ts} +163 -162
  51. package/src/pool.ts +209 -0
  52. package/{poc/node_modules/node-firebird/lib/srp.js → src/srp.ts} +74 -33
  53. package/src/types.ts +419 -0
  54. package/{poc/node_modules/node-firebird/lib/unix-crypt.js → src/unix-crypt.ts} +6 -12
  55. package/{poc/node_modules/node-firebird/lib/utils.js → src/utils.ts} +11 -20
  56. package/{poc/node_modules/node-firebird/lib/wire/connection.js → src/wire/connection.ts} +611 -129
  57. package/{poc/node_modules/node-firebird/lib/wire/const.js → src/wire/const.ts} +96 -9
  58. package/{poc/node_modules/node-firebird/lib/wire/database.js → src/wire/database.ts} +110 -44
  59. package/{poc/node_modules/node-firebird/lib/wire/eventConnection.js → src/wire/eventConnection.ts} +29 -14
  60. package/{poc/node_modules/node-firebird/lib/wire/fbEventManager.js → src/wire/fbEventManager.ts} +39 -30
  61. package/{poc/node_modules/node-firebird/lib/wire/serialize.js → src/wire/serialize.ts} +78 -59
  62. package/{poc/node_modules/node-firebird/lib/wire/service.js → src/wire/service.ts} +90 -88
  63. package/src/wire/socket.ts +262 -0
  64. package/src/wire/statement.ts +71 -0
  65. package/{poc/node_modules/node-firebird/lib/wire/transaction.js → src/wire/transaction.ts} +25 -19
  66. package/{poc/node_modules/node-firebird/lib/wire/xsqlvar.js → src/wire/xsqlvar.ts} +244 -143
  67. package/.eslintrc.json +0 -12
  68. package/.github/workflows/codeql.yml +0 -76
  69. package/.github/workflows/node.js.yml +0 -95
  70. package/BIGINT_MIGRATION.md +0 -374
  71. package/CI_DEBUGGING_GUIDE.md +0 -148
  72. package/ENCRYPTION_CALLBACK.md +0 -152
  73. package/FIREBIRD_LOG_FEATURE.md +0 -145
  74. package/MINIMAL_CHANGES_SUMMARY.md +0 -136
  75. package/PR_SUMMARY.md +0 -96
  76. package/ROADMAP.md +0 -223
  77. package/SRP_PROTOCOL.md +0 -482
  78. package/poc/README.md +0 -160
  79. package/poc/helpers.js +0 -59
  80. package/poc/node_modules/.package-lock.json +0 -14
  81. package/poc/node_modules/node-firebird/.eslintrc.json +0 -12
  82. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +0 -76
  83. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +0 -95
  84. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +0 -374
  85. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +0 -148
  86. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +0 -152
  87. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +0 -145
  88. package/poc/node_modules/node-firebird/LICENSE +0 -373
  89. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +0 -136
  90. package/poc/node_modules/node-firebird/PR_SUMMARY.md +0 -96
  91. package/poc/node_modules/node-firebird/README.md +0 -794
  92. package/poc/node_modules/node-firebird/ROADMAP.md +0 -223
  93. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +0 -482
  94. package/poc/node_modules/node-firebird/lib/callback.js +0 -38
  95. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +0 -1524
  96. package/poc/node_modules/node-firebird/lib/index.d.ts +0 -316
  97. package/poc/node_modules/node-firebird/lib/index.js +0 -128
  98. package/poc/node_modules/node-firebird/lib/pool.js +0 -108
  99. package/poc/node_modules/node-firebird/lib/wire/socket.js +0 -175
  100. package/poc/node_modules/node-firebird/lib/wire/statement.js +0 -48
  101. package/poc/node_modules/node-firebird/package.json +0 -38
  102. package/poc/node_modules/node-firebird/vitest.config.js +0 -24
  103. package/poc/package-lock.json +0 -21
  104. package/poc/package.json +0 -12
  105. package/poc/reproduce-fixed.js +0 -150
  106. package/poc/reproduce.js +0 -133
  107. package/vitest.config.js +0 -27
  108. /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg +0 -0
  109. /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg.json +0 -0
@@ -1,175 +0,0 @@
1
- const net = require("net");
2
- const zlib = require("zlib");
3
-
4
- /**
5
- * Arc4 stream cipher for Firebird wire encryption.
6
- * Uses the SRP session key to create RC4 encryption/decryption streams.
7
- */
8
- class Arc4 {
9
- constructor(key) {
10
- this._s = new Uint8Array(256);
11
- this._i = 0;
12
- this._j = 0;
13
-
14
- // KSA (Key-Scheduling Algorithm)
15
- for (var i = 0; i < 256; i++) {
16
- this._s[i] = i;
17
- }
18
-
19
- var j = 0;
20
- for (var i = 0; i < 256; i++) {
21
- j = (j + this._s[i] + key[i % key.length]) & 0xFF;
22
- // Swap
23
- var tmp = this._s[i];
24
- this._s[i] = this._s[j];
25
- this._s[j] = tmp;
26
- }
27
- }
28
-
29
- /**
30
- * Transform (encrypt/decrypt) data in place.
31
- * RC4 is symmetric - encrypt and decrypt are the same operation.
32
- */
33
- transform(data) {
34
- var out = Buffer.alloc(data.length);
35
- for (var n = 0; n < data.length; n++) {
36
- this._i = (this._i + 1) & 0xFF;
37
- this._j = (this._j + this._s[this._i]) & 0xFF;
38
-
39
- // Swap
40
- var tmp = this._s[this._i];
41
- this._s[this._i] = this._s[this._j];
42
- this._s[this._j] = tmp;
43
-
44
- var k = this._s[(this._s[this._i] + this._s[this._j]) & 0xFF];
45
- out[n] = data[n] ^ k;
46
- }
47
- return out;
48
- }
49
- }
50
-
51
- /**
52
- * Socket proxy.
53
- */
54
- class Socket {
55
- constructor(port, host) {
56
- this._socket = net.createConnection(port, host);
57
- this._socket.setNoDelay(true);
58
- this.compressor = null;
59
- this.compressorBuffer = [];
60
- this.decompressor = null;
61
- this.decompressorBuffer = [];
62
- this.buffer = null;
63
- this.encrypt = false;
64
- this.encryptCipher = null;
65
- this.decryptCipher = null;
66
-
67
- return new Proxy(this._socket, this);
68
- }
69
-
70
- /**
71
- * Decompress and/or decrypt data when received.
72
- * Override on data event.
73
- */
74
- on(event, cb) {
75
- if (event === 'data') {
76
- const mainCb = cb;
77
- cb = (data) => {
78
- // Decrypt first if encryption is enabled
79
- if (this.encrypt) {
80
- data = this.decryptCipher.transform(data);
81
- }
82
-
83
- if (this.compress) {
84
- this.decompressor.write(data, () => {
85
- mainCb(Buffer.concat(this.decompressorBuffer));
86
- this.decompressorBuffer = []; // Reset buffer
87
- });
88
- } else {
89
- mainCb(data);
90
- }
91
- };
92
- }
93
-
94
- this._socket.on(event, cb);
95
- }
96
-
97
- /**
98
- * Compress and/or encrypt data before sending to socket.
99
- */
100
- write(data, defer = false) {
101
- if (defer) {
102
- this.buffer = Buffer.from(data);
103
- return;
104
- }
105
-
106
- if (!defer && this.buffer) {
107
- data = Buffer.concat([this.buffer, data]);
108
- this.buffer = null;
109
- }
110
-
111
- if (this.compress) {
112
- this.compressor.write(data, () => {
113
- var compressedData = Buffer.concat(this.compressorBuffer);
114
- this.compressorBuffer = []; // Reset buffer
115
-
116
- // Encrypt after compression if encryption is enabled
117
- if (this.encrypt) {
118
- compressedData = this.encryptCipher.transform(compressedData);
119
- }
120
-
121
- this._socket.write(compressedData);
122
- });
123
- } else if (this.encrypt) {
124
- this._socket.write(this.encryptCipher.transform(data));
125
- } else {
126
- this._socket.write(data);
127
- }
128
- }
129
-
130
- /**
131
- * Enable compression/decompression on the fly.
132
- */
133
- enableCompression() {
134
- this.compress = true;
135
-
136
- // Create decompressor instance
137
- this.decompressor = zlib.createInflate();
138
- this.decompressor.on('data', (inflate) => {
139
- this.decompressorBuffer.push(inflate);
140
- });
141
-
142
- // Create compressor instance
143
- this.compressor = zlib.createDeflate({
144
- flush: zlib.constants.Z_FULL_FLUSH,
145
- finishFlush: zlib.constants.Z_SYNC_FLUSH
146
- });
147
- this.compressor.on('data', (deflate) => {
148
- this.compressorBuffer.push(deflate);
149
- });
150
- }
151
-
152
- /**
153
- * Enable encryption/decryption using Arc4 cipher.
154
- * @param {Buffer} sessionKey - The session key from SRP authentication.
155
- */
156
- enableEncryption(sessionKey) {
157
- this.encrypt = true;
158
- this.encryptCipher = new Arc4(sessionKey);
159
- this.decryptCipher = new Arc4(sessionKey);
160
- }
161
-
162
- /**
163
- * Proxy trap.
164
- */
165
- get(target, field) {
166
- if (field in this) {
167
- return this[field].bind(this);
168
- }
169
-
170
- return target[field];
171
- }
172
- }
173
-
174
- module.exports = Socket;
175
- module.exports.Arc4 = Arc4;
@@ -1,48 +0,0 @@
1
- /***************************************
2
- *
3
- * Statement
4
- *
5
- ***************************************/
6
-
7
- class Statement {
8
- constructor(connection) {
9
- this.connection = connection;
10
- }
11
-
12
- close(callback) {
13
- this.connection.closeStatement(this, callback);
14
- }
15
-
16
- drop(callback) {
17
- this.connection.dropStatement(this, callback);
18
- }
19
-
20
- release(callback) {
21
- var cache_query = this.connection.getCachedQuery(this.query);
22
- if (cache_query)
23
- this.connection.closeStatement(this, callback);
24
- else
25
- this.connection.dropStatement(this, callback);
26
- }
27
-
28
- execute(transaction, params, callback, options) {
29
- if (params instanceof Function) {
30
- options = callback;
31
- callback = params;
32
- params = undefined;
33
- }
34
-
35
- this.options = options;
36
- this.connection.executeStatement(transaction, this, params, callback, options);
37
- }
38
-
39
- fetch(transaction, count, callback) {
40
- this.connection.fetch(this, transaction, count, callback);
41
- }
42
-
43
- fetchAll(transaction, callback) {
44
- this.connection.fetchAll(this, transaction, callback);
45
- }
46
- }
47
-
48
- module.exports = Statement;
@@ -1,38 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
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
- });
@@ -1,21 +0,0 @@
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
- }
package/poc/package.json DELETED
@@ -1,12 +0,0 @@
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
- }
@@ -1,150 +0,0 @@
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
- });
package/poc/reproduce.js DELETED
@@ -1,133 +0,0 @@
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 DELETED
@@ -1,27 +0,0 @@
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/encoding.js',
20
- 'test/timezone.js',
21
- 'test/decfloat.js',
22
- 'test/mock-server.js',
23
- 'test/pool-fixes.js',
24
- 'test/blob-chunks.js'
25
- ],
26
- },
27
- });