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.
- package/README.md +163 -31
- package/lib/index.d.ts +12 -0
- package/lib/pool.js +10 -1
- package/lib/srp.js +12 -1
- package/lib/wire/connection.js +39 -7
- package/lib/wire/socket.js +13 -1
- package/lib/wire/xsqlvar.js +69 -6
- package/package.json +1 -1
- package/poc/node_modules/.package-lock.json +14 -0
- package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
- package/poc/node_modules/node-firebird/LICENSE +373 -0
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
- package/poc/node_modules/node-firebird/README.md +794 -0
- package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
- package/poc/node_modules/node-firebird/lib/callback.js +38 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
- package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
- package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
- package/poc/node_modules/node-firebird/lib/index.js +128 -0
- package/poc/node_modules/node-firebird/lib/messages.js +162 -0
- package/poc/node_modules/node-firebird/lib/pool.js +108 -0
- package/poc/node_modules/node-firebird/lib/srp.js +299 -0
- package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
- package/poc/node_modules/node-firebird/lib/utils.js +164 -0
- package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
- package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
- package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
- package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
- package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
- package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
- package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
- package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
- package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
- package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
- package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
- package/poc/node_modules/node-firebird/package.json +38 -0
- package/poc/node_modules/node-firebird/vitest.config.js +24 -0
- 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;
|