node-firebird 2.3.2 → 2.3.4

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 (49) hide show
  1. package/README.md +163 -31
  2. package/lib/index.d.ts +12 -0
  3. package/lib/pool.js +10 -1
  4. package/lib/srp.js +12 -1
  5. package/lib/wire/connection.js +39 -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/node_modules/.package-lock.json +14 -0
  10. package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
  11. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
  12. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
  13. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
  14. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
  15. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
  16. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
  17. package/poc/node_modules/node-firebird/LICENSE +373 -0
  18. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
  19. package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
  20. package/poc/node_modules/node-firebird/README.md +794 -0
  21. package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
  22. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
  23. package/poc/node_modules/node-firebird/lib/callback.js +38 -0
  24. package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
  25. package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
  26. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
  27. package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
  28. package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
  29. package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
  30. package/poc/node_modules/node-firebird/lib/index.js +128 -0
  31. package/poc/node_modules/node-firebird/lib/messages.js +162 -0
  32. package/poc/node_modules/node-firebird/lib/pool.js +108 -0
  33. package/poc/node_modules/node-firebird/lib/srp.js +299 -0
  34. package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
  35. package/poc/node_modules/node-firebird/lib/utils.js +164 -0
  36. package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
  37. package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
  38. package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
  39. package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
  40. package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
  41. package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
  42. package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
  43. package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
  44. package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
  45. package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
  46. package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
  47. package/poc/node_modules/node-firebird/package.json +38 -0
  48. package/poc/node_modules/node-firebird/vitest.config.js +24 -0
  49. package/vitest.config.js +3 -1
@@ -0,0 +1,326 @@
1
+ // FbEventManager – Firebird POST_EVENT subscription manager
2
+ //
3
+ // State machine overview
4
+ // ──────────────────────
5
+ //
6
+ // ┌──────────────────────────────────────────────────────────────────────┐
7
+ // │ FbEventManager states │
8
+ // └──────────────────────────────────────────────────────────────────────┘
9
+ //
10
+ // attachEvent()
11
+ // │
12
+ // ▼
13
+ // ┌─────────────────────────────────────────────────────────┐
14
+ // │ IDLE │
15
+ // │ _hasActiveSubscription = false │
16
+ // │ events = {} │
17
+ // │ eventcallback = loop fn (set but subscription absent) │
18
+ // └──────────────┬────────────────────────────┬────────────┘
19
+ // │ registerEvent([...]) │ close()
20
+ // ▼ ▼
21
+ // ┌──────────────────────────┐ ┌───────────────────────┐
22
+ // │ SUBSCRIBING │ │ CLOSING │
23
+ // │ queEvents() sent │ │ endAndWaitForClose() │
24
+ // │ waiting for op_response │ │ sock.end() + wait │
25
+ // └──────────┬───────────────┘ └───────────┬───────────┘
26
+ // │ op_response ok │ 'close' event
27
+ // ▼ ▼
28
+ // ┌──────────────────────────┐ ┌───────────────────────┐
29
+ // │ SUBSCRIBED │ │ CLOSED / DONE │
30
+ // │ _hasActiveSubscription │ │ eventconnection gone │
31
+ // │ = true │ └───────────────────────┘
32
+ // │ eventcallback active │
33
+ // └───┬───────────┬──────────┘
34
+ // │ │
35
+ // │ op_event │ unregisterEvent() (all removed) or close()
36
+ // │ received │
37
+ // │ ▼
38
+ // │ ┌──────────────────────────────────────────────────┐
39
+ // │ │ CANCELLING │
40
+ // │ │ closeEvents() sent (op_cancel_events) │
41
+ // │ │ waiting for op_response │
42
+ // │ └──────────┬───────────────────────────────────────┘
43
+ // │ │ op_response ok
44
+ // │ ▼
45
+ // │ ┌──────────────────────────────────────────────────┐
46
+ // │ │ IDLE (or CLOSING if called from close()) │
47
+ // │ └──────────────────────────────────────────────────┘
48
+ // │
49
+ // │ emit('post_event', name, count)
50
+ // └──────────────────────┐
51
+ // ▼
52
+ // loop() → SUBSCRIBING (re-subscribe)
53
+ //
54
+ // Wire-protocol messages on the MAIN connection
55
+ // ──────────────────────────────────────────────
56
+ // Client → Server : op_connect_request (attachEvent / auxConnection)
57
+ // Server → Client : op_response (socket address of AUX port)
58
+ // Client → Server : op_que_events (registerEvent / loop)
59
+ // Server → Client : op_response (confirms event ID)
60
+ // Client → Server : op_cancel_events (unregisterEvent / close)
61
+ // Server → Client : op_response
62
+ //
63
+ // Asynchronous notifications on the AUX (EventConnection) socket
64
+ // ───────────────────────────────────────────────────────────────
65
+ // Server → Client : op_event (fired by Firebird POST_EVENT trigger)
66
+
67
+ const Events = require('events');
68
+ const { doError } = require('../callback');
69
+
70
+ class FbEventManager extends Events.EventEmitter {
71
+ constructor(db, eventconnection, eventid, callback) {
72
+ super();
73
+ this.db = db;
74
+ this.eventconnection = eventconnection;
75
+ this.events = {};
76
+ this.eventid = eventid;
77
+ // True when an op_que_events subscription is currently active on the
78
+ // main connection (so close() and _changeEvent know whether to send
79
+ // op_cancel_events before tearing down or re-subscribing).
80
+ this._hasActiveSubscription = false;
81
+ this._createEventLoop(callback);
82
+ }
83
+
84
+ /**
85
+ * Returns a snapshot of the current state for debugging.
86
+ * Useful for tracing the state machine during development.
87
+ *
88
+ * Stable states: 'IDLE', 'SUBSCRIBED', 'CLOSED'.
89
+ * Transient states (SUBSCRIBING, CANCELLING, CLOSING) occur while waiting
90
+ * for op_response on the main connection or for the socket to close; they
91
+ * are not tracked with dedicated flags to keep the implementation simple,
92
+ * but they can be inferred: if the socket is open and _hasActiveSubscription
93
+ * disagrees with what the caller expects, a transitional operation is in
94
+ * progress.
95
+ *
96
+ * @returns {{
97
+ * state: string,
98
+ * hasActiveSubscription: boolean,
99
+ * registeredEvents: Object,
100
+ * eventId: number,
101
+ * isEventConnectionOpen: boolean,
102
+ * isDatabaseConnectionClosed: boolean
103
+ * }}
104
+ */
105
+ getState() {
106
+ const evtConnOpen = this.eventconnection
107
+ ? !this.eventconnection._isClosed
108
+ : false;
109
+ const dbConnClosed = this.db.connection
110
+ ? this.db.connection._isClosed
111
+ : true;
112
+
113
+ // Derive a human-readable stable-state label.
114
+ // Transitional states (SUBSCRIBING / CANCELLING / CLOSING) are not
115
+ // individually flagged; callers that need finer granularity can
116
+ // inspect hasActiveSubscription and isEventConnectionOpen together.
117
+ let state;
118
+ if (dbConnClosed || !evtConnOpen) {
119
+ state = 'CLOSED';
120
+ } else if (this._hasActiveSubscription) {
121
+ state = 'SUBSCRIBED';
122
+ } else {
123
+ state = 'IDLE';
124
+ }
125
+
126
+ return {
127
+ state,
128
+ hasActiveSubscription: this._hasActiveSubscription,
129
+ registeredEvents: Object.assign({}, this.events),
130
+ eventId: this.eventid,
131
+ isEventConnectionOpen: evtConnOpen,
132
+ isDatabaseConnectionClosed: dbConnClosed,
133
+ };
134
+ }
135
+
136
+ _createEventLoop(callback) {
137
+ var self = this;
138
+ var cnx = this.db.connection;
139
+ this.eventconnection.emgr = this;
140
+
141
+ if (process.env.FIREBIRD_DEBUG) {
142
+ console.log('[fb-debug] FbEventManager._createEventLoop: eventid=%d', self.eventid);
143
+ }
144
+
145
+ // Re-subscribe after each op_event notification so that further
146
+ // trigger fires continue to be delivered.
147
+ function loop() {
148
+ // Guard: do not send queEvents if the subscription has been
149
+ // cancelled (e.g. via unregisterEvent removing all events) or if
150
+ // there are no registered events. Without this check, a late
151
+ // op_event arriving on the event connection after closeEvents can
152
+ // trigger queEvents({}) which Firebird never acknowledges,
153
+ // permanently blocking the main connection queue.
154
+ if (!self._hasActiveSubscription || Object.keys(self.events).length === 0) {
155
+ return;
156
+ }
157
+ cnx.queEvents(self.events, self.eventid, function (err) {
158
+ if (err) {
159
+ doError(err, callback);
160
+ return;
161
+ }
162
+ // subscription renewed, nothing else to do
163
+ });
164
+ }
165
+
166
+ this.eventconnection.eventcallback = function (err, ret) {
167
+ if (err || (self.eventid !== ret.eventid)) {
168
+ doError(err || new Error('Bad eventid'), callback);
169
+ return;
170
+ }
171
+
172
+ ret.events.forEach(function (event) {
173
+ self.emit('post_event', event.name, event.count);
174
+ });
175
+
176
+ loop();
177
+ };
178
+
179
+ // Resolve attachEvent on the next tick – no subscription is needed
180
+ // until the caller registers at least one event name via registerEvent().
181
+ // process.nextTick ensures the outer `const evt = new FbEventManager(...)`
182
+ // assignment in database.js completes before this callback fires.
183
+ process.nextTick(function() { callback(null); });
184
+ }
185
+
186
+ _changeEvent(callback) {
187
+ var self = this;
188
+
189
+ // Temporarily suppress the event loop to prevent stale op_event
190
+ // notifications from pushing spurious queEvents onto the main
191
+ // connection queue while we're reconfiguring the subscription.
192
+ var savedCallback = self.eventconnection.eventcallback;
193
+ self.eventconnection.eventcallback = null;
194
+
195
+ function subscribe() {
196
+ // If no events remain, mark subscription as inactive and return.
197
+ // Sending queEvents with an empty EPB after op_cancel_events does
198
+ // not receive op_response from some Firebird versions, which would
199
+ // permanently block the main connection queue.
200
+ if (Object.keys(self.events).length === 0) {
201
+ self._hasActiveSubscription = false;
202
+ self.eventconnection.eventcallback = savedCallback;
203
+ callback(null);
204
+ return;
205
+ }
206
+
207
+ self.db.connection.queEvents(self.events, self.eventid, function (err, ret) {
208
+ self.eventconnection.eventcallback = savedCallback;
209
+ if (err) {
210
+ doError(err, callback);
211
+ return;
212
+ }
213
+ self._hasActiveSubscription = true;
214
+ callback(null, ret);
215
+ });
216
+ }
217
+
218
+ if (self._hasActiveSubscription) {
219
+ // Cancel the current subscription before setting up a new one.
220
+ self.db.connection.closeEvents(this.eventid, function (err) {
221
+ if (err) {
222
+ self.eventconnection.eventcallback = savedCallback;
223
+ doError(err, callback);
224
+ return;
225
+ }
226
+ self._hasActiveSubscription = false;
227
+ subscribe();
228
+ });
229
+ } else {
230
+ // No active subscription yet (first registerEvent call, or after
231
+ // all events were unregistered) – go straight to subscribing.
232
+ subscribe();
233
+ }
234
+ }
235
+
236
+ registerEvent(events, callback) {
237
+ var self = this;
238
+
239
+ if (self.db.connection._isClosed || self.eventconnection._isClosed)
240
+ return self.eventconnection.throwClosed(callback);
241
+
242
+ events.forEach((event) => self.events[event] = self.events[event] || 0);
243
+ self._changeEvent(callback);
244
+ }
245
+
246
+ unregisterEvent(events, callback) {
247
+ var self = this;
248
+
249
+ if (self.db.connection._isClosed || self.eventconnection._isClosed)
250
+ return self.eventconnection.throwClosed(callback);
251
+
252
+ events.forEach(function (event) { delete self.events[event] });
253
+ self._changeEvent(callback);
254
+ }
255
+
256
+ close(callback) {
257
+ var self = this;
258
+
259
+ if (process.env.FIREBIRD_DEBUG) {
260
+ console.log('[fb-debug] FbEventManager.close() called, _hasActiveSubscription=%s eventid=%d', self._hasActiveSubscription, self.eventid);
261
+ }
262
+
263
+ // Prevent the event loop from re-queuing on stale op_event notifications
264
+ // that may arrive between closeEvents and socket.end()
265
+ self.eventconnection.eventcallback = null;
266
+
267
+ // Gracefully close the event socket using a FIN (end()) rather than a RST
268
+ // (destroy()), then wait for the 'close' event which confirms both sides have
269
+ // exchanged FINs. This gives Firebird (all versions 3/4/5) time to fully
270
+ // process the previous event connection's teardown before the next
271
+ // op_connect_request or op_que_events arrives on the main connection.
272
+ // destroy() (RST) is faster but causes Firebird 3 to get confused on subsequent
273
+ // queEvents calls – the server internally fails on the RST error and does not
274
+ // clean up its event state in time for the next subscription request.
275
+ // A 200 ms safety timer fires as a fallback if Firebird never sends its FIN.
276
+ function endAndWaitForClose(cb) {
277
+ var sock = self.eventconnection && self.eventconnection._socket;
278
+ if (!sock || sock.destroyed) {
279
+ if (process.env.FIREBIRD_DEBUG) {
280
+ console.log('[fb-debug] endAndWaitForClose: socket already destroyed, calling back immediately');
281
+ }
282
+ if (cb) cb();
283
+ return;
284
+ }
285
+ var fired = false;
286
+ var timer;
287
+ function done(source) {
288
+ if (!fired) {
289
+ fired = true;
290
+ clearTimeout(timer);
291
+ if (process.env.FIREBIRD_DEBUG) {
292
+ console.log('[fb-debug] endAndWaitForClose done() via %s, eventid=%d', source, self.eventid);
293
+ }
294
+ if (cb) cb();
295
+ }
296
+ }
297
+ sock.once('close', function() { done('close-event'); });
298
+ if (process.env.FIREBIRD_DEBUG) {
299
+ console.log('[fb-debug] endAndWaitForClose: calling sock.end(), eventid=%d sock.destroyed=%s', self.eventid, sock.destroyed);
300
+ }
301
+ sock.end();
302
+ // Safety fallback: if Firebird never sends its FIN (e.g. an error
303
+ // occurs on the server side), resolve after 200 ms so tests don't hang.
304
+ timer = setTimeout(function() { done('200ms-timer'); }, 200);
305
+ }
306
+
307
+ if (!self._hasActiveSubscription) {
308
+ // No active subscription (attachEvent without registerEvent, or
309
+ // all events were unregistered) – nothing to cancel.
310
+ endAndWaitForClose(callback);
311
+ return;
312
+ }
313
+
314
+ self.db.connection.closeEvents(this.eventid, function (err) {
315
+ if (err) {
316
+ doError(err, callback);
317
+ return;
318
+ }
319
+
320
+ self._hasActiveSubscription = false;
321
+ endAndWaitForClose(callback);
322
+ });
323
+ }
324
+ }
325
+
326
+ module.exports = FbEventManager;