node-firebird 2.16.2 → 2.17.1
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 +26 -3
- package/lib/types.d.ts +26 -2
- package/lib/utils.d.ts +5 -0
- package/lib/utils.js +9 -0
- package/lib/wire/database.js +12 -7
- package/lib/wire/eventConnection.d.ts +2 -0
- package/lib/wire/eventConnection.js +36 -10
- package/lib/wire/fbEventManager.d.ts +10 -1
- package/lib/wire/fbEventManager.js +50 -10
- package/package.json +1 -1
- package/src/types.ts +29 -2
- package/src/utils.ts +7 -0
- package/src/wire/database.ts +12 -8
- package/src/wire/eventConnection.ts +35 -9
- package/src/wire/fbEventManager.ts +56 -10
package/README.md
CHANGED
|
@@ -181,6 +181,7 @@ same way `PGUSER`/`PGPASSWORD` work with pg.
|
|
|
181
181
|
var options = {};
|
|
182
182
|
|
|
183
183
|
options.host = '127.0.0.1';
|
|
184
|
+
options.eventHost = undefined; // optional; override the server-advertised host for the auxiliary event connection
|
|
184
185
|
options.port = 3050;
|
|
185
186
|
options.database = 'database.fdb';
|
|
186
187
|
options.user = 'SYSDBA';
|
|
@@ -1403,6 +1404,12 @@ Firebird.attach(options, function (err, db) {
|
|
|
1403
1404
|
db.attachEvent(function (err, evtmgr) {
|
|
1404
1405
|
if (err) throw err;
|
|
1405
1406
|
|
|
1407
|
+
// Auxiliary socket/protocol failures after attachment are reported here.
|
|
1408
|
+
// Install this listener before registering events.
|
|
1409
|
+
evtmgr.on('error', function (err) {
|
|
1410
|
+
console.error('event connection failed:', err);
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1406
1413
|
// 2. Subscribe to one or more named events (names must match POST_EVENT('name') in your
|
|
1407
1414
|
// PSQL triggers/procedures). Resolves once op_que_events is acknowledged by the server.
|
|
1408
1415
|
evtmgr.registerEvent(['MY_EVENT'], function (err) {
|
|
@@ -1455,6 +1462,11 @@ If the server reports `0.0.0.0` or `::` as the aux address — usual when it lis
|
|
|
1455
1462
|
interfaces — the driver dials the host from your connection options instead, so that host must
|
|
1456
1463
|
be the one reaching the aux port.
|
|
1457
1464
|
|
|
1465
|
+
Behind NAT, a tunnel, container networking, or a load balancer, the advertised
|
|
1466
|
+
address may not be reachable from the client. Set `options.eventHost` to override
|
|
1467
|
+
only the host used for the auxiliary event connection. The auxiliary port is
|
|
1468
|
+
still selected by Firebird; use `RemoteAuxPort` when it also needs to be fixed.
|
|
1469
|
+
|
|
1458
1470
|
Since **2.16.2** a failed dial is reported to the attachment callback as an `Error`, exactly
|
|
1459
1471
|
once, carrying Node's socket `code`. Earlier versions recorded it internally and never called
|
|
1460
1472
|
back, so `attachEvent()` and `attachEventAsync()` hung indefinitely:
|
|
@@ -1473,9 +1485,20 @@ db.attachEvent(function (err, evtmgr) {
|
|
|
1473
1485
|
});
|
|
1474
1486
|
```
|
|
1475
1487
|
|
|
1476
|
-
Errors on the aux socket *after* it connects are
|
|
1477
|
-
|
|
1478
|
-
|
|
1488
|
+
Errors on the aux socket *after* it connects are delivered through the manager's `error`
|
|
1489
|
+
event. Install the listener before calling `registerEvent()`:
|
|
1490
|
+
|
|
1491
|
+
```js
|
|
1492
|
+
evtmgr.on('error', function (err) {
|
|
1493
|
+
// The manager is now CLOSED. Reattach explicitly if the application wants
|
|
1494
|
+
// to resume event delivery; the driver does not reconnect automatically.
|
|
1495
|
+
console.error('event connection failed:', err);
|
|
1496
|
+
});
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
When no manager error listener is installed, the driver forwards the failure to guarded
|
|
1500
|
+
database `error` listeners when present. It never emits an unhandled EventEmitter `error`.
|
|
1501
|
+
The primary database attachment remains available for ordinary queries.
|
|
1479
1502
|
|
|
1480
1503
|
### Escaping Query values
|
|
1481
1504
|
|
package/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Readable, Writable } from 'stream';
|
|
2
|
+
import type { EventEmitter } from 'events';
|
|
2
3
|
import type { SqlTag } from './sql-template';
|
|
3
4
|
export type { SqlTag, SqlQuery, SqlIdentifier, CompiledQuery } from './sql-template';
|
|
4
5
|
export type DatabaseCallback = (err: any, db: Database) => void;
|
|
@@ -6,6 +7,27 @@ export type TransactionCallback = (err: any, transaction: Transaction) => void;
|
|
|
6
7
|
export type QueryCallback = (err: any, result: any[]) => void;
|
|
7
8
|
export type SimpleCallback = (err: any) => void;
|
|
8
9
|
export type SequentialCallback = (row: any, index: number, next?: (err?: any) => void) => void | Promise<void>;
|
|
10
|
+
export interface FbEventState {
|
|
11
|
+
state: 'IDLE' | 'SUBSCRIBED' | 'CLOSED';
|
|
12
|
+
hasActiveSubscription: boolean;
|
|
13
|
+
registeredEvents: Record<string, number>;
|
|
14
|
+
eventId: number;
|
|
15
|
+
isEventConnectionOpen: boolean;
|
|
16
|
+
isDatabaseConnectionClosed: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface FbEventManager extends EventEmitter {
|
|
19
|
+
readonly eventid: number;
|
|
20
|
+
readonly events: Record<string, number>;
|
|
21
|
+
registerEvent(events: string[], callback: SimpleCallback): void;
|
|
22
|
+
unregisterEvent(events: string[], callback: SimpleCallback): void;
|
|
23
|
+
close(callback?: SimpleCallback): void;
|
|
24
|
+
getState(): FbEventState;
|
|
25
|
+
on(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
26
|
+
on(event: 'error', listener: (error: Error) => void): this;
|
|
27
|
+
once(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
28
|
+
once(event: 'error', listener: (error: Error) => void): this;
|
|
29
|
+
}
|
|
30
|
+
export type FbEventManagerCallback = (err: any, manager?: FbEventManager) => void;
|
|
9
31
|
/**
|
|
10
32
|
* Describes a single column in a prepared statement's result set or
|
|
11
33
|
* parameter list. The properties here are populated by the
|
|
@@ -250,7 +272,7 @@ export interface Database {
|
|
|
250
272
|
batchStream(query: string, options?: BatchStreamOptions): BatchStream;
|
|
251
273
|
drop(callback: SimpleCallback): void;
|
|
252
274
|
escape(value: any): string;
|
|
253
|
-
attachEvent(callback:
|
|
275
|
+
attachEvent(callback: FbEventManagerCallback): this;
|
|
254
276
|
createTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
|
|
255
277
|
alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
|
|
256
278
|
dropTablespace(name: string, callback?: QueryCallback): Database;
|
|
@@ -271,7 +293,7 @@ export interface Database {
|
|
|
271
293
|
newStatementAsync(query: string): Promise<Statement>;
|
|
272
294
|
detachAsync(force?: boolean): Promise<void>;
|
|
273
295
|
dropAsync(): Promise<void>;
|
|
274
|
-
attachEventAsync(): Promise<
|
|
296
|
+
attachEventAsync(): Promise<FbEventManager>;
|
|
275
297
|
/** Starts a transaction, commits when `work` resolves, rolls back when it rejects. */
|
|
276
298
|
withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T>;
|
|
277
299
|
/**
|
|
@@ -352,6 +374,8 @@ export interface Statement {
|
|
|
352
374
|
export type SupportedCharacterSet = 'NONE' | 'CP943C' | 'DOS737' | 'DOS775' | 'DOS858' | 'DOS862' | 'DOS864' | 'DOS866' | 'DOS869' | 'GB18030' | 'GBK' | 'ISO8859_1' | 'ISO8859_2' | 'ISO8859_3' | 'ISO8859_4' | 'ISO8859_5' | 'ISO8859_6' | 'ISO8859_7' | 'ISO8859_8' | 'ISO8859_9' | 'ISO8859_13' | 'KOI8R' | 'KOI8U' | 'TIS620' | 'UTF8' | 'WIN1251' | 'WIN1252' | 'WIN1253' | 'WIN1254' | 'WIN1255' | 'WIN1256' | 'WIN1257' | 'WIN1258' | 'WIN_1258';
|
|
353
375
|
export interface Options {
|
|
354
376
|
host?: string;
|
|
377
|
+
/** Override the server-advertised auxiliary event host (for NAT, tunnels and load balancers). */
|
|
378
|
+
eventHost?: string;
|
|
355
379
|
port?: number;
|
|
356
380
|
database?: string;
|
|
357
381
|
user?: string;
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { FbStatusItem } from './callback';
|
|
2
|
+
/** Resolve the address used for Firebird's auxiliary event connection. */
|
|
3
|
+
export declare function resolveEventHost(options: {
|
|
4
|
+
host?: string;
|
|
5
|
+
eventHost?: string;
|
|
6
|
+
}, advertisedHost: string): string;
|
|
2
7
|
/**
|
|
3
8
|
* Parse date from string
|
|
4
9
|
*/
|
package/lib/utils.js
CHANGED
|
@@ -4,10 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.escape = exports.lookupMessages = exports.batchResultToError = exports.parseDate = void 0;
|
|
7
|
+
exports.resolveEventHost = resolveEventHost;
|
|
7
8
|
exports.noop = noop;
|
|
8
9
|
const firebird_msg_json_1 = __importDefault(require("./firebird.msg.json"));
|
|
9
10
|
const const_1 = __importDefault(require("./wire/const"));
|
|
10
11
|
const MessagesError = firebird_msg_json_1.default;
|
|
12
|
+
/** Resolve the address used for Firebird's auxiliary event connection. */
|
|
13
|
+
function resolveEventHost(options, advertisedHost) {
|
|
14
|
+
if (options.eventHost)
|
|
15
|
+
return options.eventHost;
|
|
16
|
+
if (advertisedHost === '0.0.0.0' || advertisedHost === '::')
|
|
17
|
+
return options.host || const_1.default.DEFAULT_HOST;
|
|
18
|
+
return advertisedHost;
|
|
19
|
+
}
|
|
11
20
|
/**
|
|
12
21
|
* Parse date from string
|
|
13
22
|
*/
|
package/lib/wire/database.js
CHANGED
|
@@ -390,6 +390,13 @@ class Database extends events_1.default.EventEmitter {
|
|
|
390
390
|
attachEvent(callback) {
|
|
391
391
|
var self = this;
|
|
392
392
|
const eventid = self.eventid++;
|
|
393
|
+
let completed = false;
|
|
394
|
+
const complete = function (err, manager) {
|
|
395
|
+
if (completed)
|
|
396
|
+
return;
|
|
397
|
+
completed = true;
|
|
398
|
+
callback(err, manager);
|
|
399
|
+
};
|
|
393
400
|
if (process.env.FIREBIRD_DEBUG) {
|
|
394
401
|
console.log('[fb-debug] Database.attachEvent: calling auxConnection, eventid=%d queue=%d', eventid, self.connection._queue.length);
|
|
395
402
|
}
|
|
@@ -398,21 +405,19 @@ class Database extends events_1.default.EventEmitter {
|
|
|
398
405
|
if (process.env.FIREBIRD_DEBUG) {
|
|
399
406
|
console.log('[fb-debug] Database.attachEvent: auxConnection error:', err.message);
|
|
400
407
|
}
|
|
401
|
-
(0, callback_1.doError)(err,
|
|
408
|
+
(0, callback_1.doError)(err, complete);
|
|
402
409
|
return;
|
|
403
410
|
}
|
|
404
411
|
if (process.env.FIREBIRD_DEBUG) {
|
|
405
412
|
console.log('[fb-debug] Database.attachEvent: auxConnection ok, connecting to aux port %s:%d', socket_info.host, socket_info.port);
|
|
406
413
|
}
|
|
407
|
-
const host = (
|
|
408
|
-
? self.connection.options.host
|
|
409
|
-
: socket_info.host;
|
|
414
|
+
const host = (0, utils_1.resolveEventHost)(self.connection.options, socket_info.host);
|
|
410
415
|
const eventConnection = new eventConnection_1.default(host, socket_info.port, function (err) {
|
|
411
416
|
if (err) {
|
|
412
417
|
if (process.env.FIREBIRD_DEBUG) {
|
|
413
418
|
console.log('[fb-debug] Database.attachEvent: EventConnection error:', err.message);
|
|
414
419
|
}
|
|
415
|
-
(0, callback_1.doError)(err,
|
|
420
|
+
(0, callback_1.doError)(err, complete);
|
|
416
421
|
return;
|
|
417
422
|
}
|
|
418
423
|
if (process.env.FIREBIRD_DEBUG) {
|
|
@@ -420,13 +425,13 @@ class Database extends events_1.default.EventEmitter {
|
|
|
420
425
|
}
|
|
421
426
|
const evt = new fbEventManager_1.default(self, eventConnection, eventid, function (err) {
|
|
422
427
|
if (err) {
|
|
423
|
-
(0, callback_1.doError)(err,
|
|
428
|
+
(0, callback_1.doError)(err, complete);
|
|
424
429
|
return;
|
|
425
430
|
}
|
|
426
431
|
if (process.env.FIREBIRD_DEBUG) {
|
|
427
432
|
console.log('[fb-debug] Database.attachEvent: FbEventManager ready, eventid=%d', evt.eventid);
|
|
428
433
|
}
|
|
429
|
-
|
|
434
|
+
complete(err, evt);
|
|
430
435
|
});
|
|
431
436
|
}, self);
|
|
432
437
|
});
|
|
@@ -10,6 +10,8 @@ declare class EventConnection {
|
|
|
10
10
|
error: any;
|
|
11
11
|
eventcallback: ((err: any, ret?: any) => void) | null;
|
|
12
12
|
_connectSettled: boolean;
|
|
13
|
+
_terminalErrorReported: boolean;
|
|
14
|
+
_intentionalClose: boolean;
|
|
13
15
|
constructor(host: string, port: number, callback: ((err?: Error) => void) | undefined, db: any);
|
|
14
16
|
_bind_events(host: string, port: number, callback?: (err?: Error) => void): void;
|
|
15
17
|
throwClosed(callback?: (err: any) => void): this;
|
|
@@ -14,6 +14,8 @@ class EventConnection {
|
|
|
14
14
|
this._isClosed = false;
|
|
15
15
|
this._isOpened = false;
|
|
16
16
|
this._connectSettled = false;
|
|
17
|
+
this._terminalErrorReported = false;
|
|
18
|
+
this._intentionalClose = false;
|
|
17
19
|
this._socket = net_1.default.createConnection(port, host);
|
|
18
20
|
this._bind_events(host, port, callback);
|
|
19
21
|
this.error = null;
|
|
@@ -28,19 +30,41 @@ class EventConnection {
|
|
|
28
30
|
if (callback)
|
|
29
31
|
callback(err);
|
|
30
32
|
}
|
|
33
|
+
function reportTerminalError(err) {
|
|
34
|
+
if (self._terminalErrorReported)
|
|
35
|
+
return;
|
|
36
|
+
self._terminalErrorReported = true;
|
|
37
|
+
const wasOpened = self._isOpened;
|
|
38
|
+
self.error = err;
|
|
39
|
+
self._isClosed = true;
|
|
40
|
+
self._isOpened = false;
|
|
41
|
+
// An error during a caller-initiated shutdown is part of tearing
|
|
42
|
+
// down the auxiliary socket, not a database error.
|
|
43
|
+
if (!self._intentionalClose && !wasOpened) {
|
|
44
|
+
finishConnect(err);
|
|
45
|
+
}
|
|
46
|
+
else if (!self._intentionalClose && self.eventcallback) {
|
|
47
|
+
self.eventcallback(err);
|
|
48
|
+
}
|
|
49
|
+
else if (!self._intentionalClose && self.db && self.db.connection &&
|
|
50
|
+
typeof self.db.connection._emitError === 'function') {
|
|
51
|
+
self.db.connection._emitError(err);
|
|
52
|
+
}
|
|
53
|
+
if (!self._socket.destroyed)
|
|
54
|
+
self._socket.destroy();
|
|
55
|
+
}
|
|
31
56
|
self._socket.on('close', function () {
|
|
32
57
|
self._isClosed = true;
|
|
33
58
|
if (!self._isOpened) {
|
|
34
59
|
finishConnect(self.error || new Error(`Event connection to ${host}:${port} closed before connecting.`));
|
|
35
60
|
}
|
|
61
|
+
else if (!self._intentionalClose && self.eventcallback && !self._terminalErrorReported) {
|
|
62
|
+
reportTerminalError(new Error(`Event connection to ${host}:${port} closed unexpectedly.`));
|
|
63
|
+
}
|
|
64
|
+
self._isOpened = false;
|
|
36
65
|
});
|
|
37
66
|
self._socket.on('error', function (e) {
|
|
38
|
-
|
|
39
|
-
if (!self._isOpened) {
|
|
40
|
-
if (!self._socket.destroyed)
|
|
41
|
-
self._socket.destroy();
|
|
42
|
-
finishConnect(e);
|
|
43
|
-
}
|
|
67
|
+
reportTerminalError(e);
|
|
44
68
|
});
|
|
45
69
|
self._socket.on('connect', function () {
|
|
46
70
|
self._isClosed = false;
|
|
@@ -64,6 +88,7 @@ class EventConnection {
|
|
|
64
88
|
try {
|
|
65
89
|
var tmp_event;
|
|
66
90
|
while (xdr.pos < xdr.buffer.length) {
|
|
91
|
+
op_pos = xdr.pos;
|
|
67
92
|
do {
|
|
68
93
|
var r = xdr.readInt();
|
|
69
94
|
} while (r === const_1.default.op_dummy);
|
|
@@ -80,7 +105,9 @@ class EventConnection {
|
|
|
80
105
|
var eventcount = 0;
|
|
81
106
|
var pos = 1;
|
|
82
107
|
while (pos < buf.length) {
|
|
83
|
-
var len = buf.
|
|
108
|
+
var len = buf.readUInt8(pos++);
|
|
109
|
+
if (pos + len + 4 > buf.length)
|
|
110
|
+
throw new RangeError('Incomplete event payload');
|
|
84
111
|
eventname = buf.toString(DEFAULT_ENCODING, pos, pos + len);
|
|
85
112
|
var prevcount = self.emgr.events[eventname] || 0;
|
|
86
113
|
pos += len;
|
|
@@ -105,7 +132,7 @@ class EventConnection {
|
|
|
105
132
|
self.eventcallback(null, { eventid: event_id, events: lst_event });
|
|
106
133
|
break;
|
|
107
134
|
default:
|
|
108
|
-
|
|
135
|
+
reportTerminalError(new Error('Unexpected event connection opcode: ' + r));
|
|
109
136
|
return;
|
|
110
137
|
}
|
|
111
138
|
}
|
|
@@ -117,14 +144,13 @@ class EventConnection {
|
|
|
117
144
|
self._xdr = xdr;
|
|
118
145
|
}
|
|
119
146
|
else {
|
|
120
|
-
|
|
147
|
+
reportTerminalError(err instanceof Error ? err : new Error(String(err)));
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
});
|
|
124
151
|
}
|
|
125
152
|
throwClosed(callback) {
|
|
126
153
|
var err = new Error('Event Connection is closed.');
|
|
127
|
-
this.db.emit('error', err);
|
|
128
154
|
if (callback)
|
|
129
155
|
callback(err);
|
|
130
156
|
return this;
|
|
@@ -6,7 +6,16 @@ declare class FbEventManager extends Events.EventEmitter {
|
|
|
6
6
|
eventid: number;
|
|
7
7
|
_subscriptionVersion: number;
|
|
8
8
|
_hasActiveSubscription: boolean;
|
|
9
|
+
_readySettled: boolean;
|
|
10
|
+
_terminalErrorReported: boolean;
|
|
11
|
+
_readyCallback: (err: any, ret?: any) => void;
|
|
9
12
|
constructor(db: any, eventconnection: any, eventid: number, callback: (err: any, ret?: any) => void);
|
|
13
|
+
on(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
14
|
+
on(event: 'error', listener: (error: Error) => void): this;
|
|
15
|
+
once(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
16
|
+
once(event: 'error', listener: (error: Error) => void): this;
|
|
17
|
+
_finishReady(err?: Error): void;
|
|
18
|
+
_handleAsyncError(err: any): void;
|
|
10
19
|
/**
|
|
11
20
|
* Returns a snapshot of the current state for debugging.
|
|
12
21
|
* Useful for tracing the state machine during development.
|
|
@@ -36,7 +45,7 @@ declare class FbEventManager extends Events.EventEmitter {
|
|
|
36
45
|
isEventConnectionOpen: boolean;
|
|
37
46
|
isDatabaseConnectionClosed: boolean;
|
|
38
47
|
};
|
|
39
|
-
_createEventLoop(
|
|
48
|
+
_createEventLoop(): void;
|
|
40
49
|
_changeEvent(callback: (err: any, ret?: any) => void): void;
|
|
41
50
|
registerEvent(events: string[], callback: (err: any, ret?: any) => void): any;
|
|
42
51
|
unregisterEvent(events: string[], callback: (err: any, ret?: any) => void): any;
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
// Asynchronous notifications on the AUX (EventConnection) socket
|
|
65
65
|
// ───────────────────────────────────────────────────────────────
|
|
66
66
|
// Server → Client : op_event (fired by Firebird POST_EVENT trigger)
|
|
67
|
+
// Error / unexpected close → CLOSED; emit manager 'error' once
|
|
67
68
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
68
69
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
69
70
|
};
|
|
@@ -83,7 +84,50 @@ class FbEventManager extends events_1.default.EventEmitter {
|
|
|
83
84
|
// main connection (so close() and _changeEvent know whether to send
|
|
84
85
|
// op_cancel_events before tearing down or re-subscribing).
|
|
85
86
|
this._hasActiveSubscription = false;
|
|
86
|
-
this.
|
|
87
|
+
this._readySettled = false;
|
|
88
|
+
this._terminalErrorReported = false;
|
|
89
|
+
this._readyCallback = callback;
|
|
90
|
+
this._createEventLoop();
|
|
91
|
+
process.nextTick(() => this._finishReady());
|
|
92
|
+
}
|
|
93
|
+
on(event, listener) {
|
|
94
|
+
return super.on(event, listener);
|
|
95
|
+
}
|
|
96
|
+
once(event, listener) {
|
|
97
|
+
return super.once(event, listener);
|
|
98
|
+
}
|
|
99
|
+
_finishReady(err) {
|
|
100
|
+
if (this._readySettled)
|
|
101
|
+
return;
|
|
102
|
+
this._readySettled = true;
|
|
103
|
+
if (err)
|
|
104
|
+
(0, callback_1.doError)(err, this._readyCallback);
|
|
105
|
+
else
|
|
106
|
+
this._readyCallback(null);
|
|
107
|
+
}
|
|
108
|
+
_handleAsyncError(err) {
|
|
109
|
+
if (this._terminalErrorReported)
|
|
110
|
+
return;
|
|
111
|
+
this._terminalErrorReported = true;
|
|
112
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
113
|
+
this._hasActiveSubscription = false;
|
|
114
|
+
this._subscriptionVersion++;
|
|
115
|
+
this.eventconnection._isClosed = true;
|
|
116
|
+
this.eventconnection._isOpened = false;
|
|
117
|
+
this.eventconnection.eventcallback = null;
|
|
118
|
+
if (this.eventconnection._socket && !this.eventconnection._socket.destroyed &&
|
|
119
|
+
typeof this.eventconnection._socket.destroy === 'function') {
|
|
120
|
+
this.eventconnection._socket.destroy();
|
|
121
|
+
}
|
|
122
|
+
if (!this._readySettled) {
|
|
123
|
+
this._finishReady(error);
|
|
124
|
+
}
|
|
125
|
+
else if (this.listenerCount('error') > 0) {
|
|
126
|
+
this.emit('error', error);
|
|
127
|
+
}
|
|
128
|
+
else if (this.db.connection && typeof this.db.connection._emitError === 'function') {
|
|
129
|
+
this.db.connection._emitError(error);
|
|
130
|
+
}
|
|
87
131
|
}
|
|
88
132
|
/**
|
|
89
133
|
* Returns a snapshot of the current state for debugging.
|
|
@@ -136,7 +180,7 @@ class FbEventManager extends events_1.default.EventEmitter {
|
|
|
136
180
|
isDatabaseConnectionClosed: dbConnClosed,
|
|
137
181
|
};
|
|
138
182
|
}
|
|
139
|
-
_createEventLoop(
|
|
183
|
+
_createEventLoop() {
|
|
140
184
|
var self = this;
|
|
141
185
|
var cnx = this.db.connection;
|
|
142
186
|
this.eventconnection.emgr = this;
|
|
@@ -157,15 +201,15 @@ class FbEventManager extends events_1.default.EventEmitter {
|
|
|
157
201
|
}
|
|
158
202
|
cnx.queEvents(self.events, self.eventid, function (err) {
|
|
159
203
|
if (err) {
|
|
160
|
-
|
|
204
|
+
self._handleAsyncError(err);
|
|
161
205
|
return;
|
|
162
206
|
}
|
|
163
207
|
// subscription renewed, nothing else to do
|
|
164
208
|
});
|
|
165
209
|
}
|
|
166
210
|
this.eventconnection.eventcallback = function (err, ret) {
|
|
167
|
-
if (err || (self.eventid !== ret.eventid)) {
|
|
168
|
-
|
|
211
|
+
if (err || !ret || (self.eventid !== ret.eventid)) {
|
|
212
|
+
self._handleAsyncError(err || new Error('Bad eventid'));
|
|
169
213
|
return;
|
|
170
214
|
}
|
|
171
215
|
ret.events.forEach(function (event) {
|
|
@@ -173,11 +217,6 @@ class FbEventManager extends events_1.default.EventEmitter {
|
|
|
173
217
|
});
|
|
174
218
|
loop();
|
|
175
219
|
};
|
|
176
|
-
// Resolve attachEvent on the next tick – no subscription is needed
|
|
177
|
-
// until the caller registers at least one event name via registerEvent().
|
|
178
|
-
// process.nextTick ensures the outer `const evt = new FbEventManager(...)`
|
|
179
|
-
// assignment in database.js completes before this callback fires.
|
|
180
|
-
process.nextTick(function () { callback(null); });
|
|
181
220
|
}
|
|
182
221
|
_changeEvent(callback) {
|
|
183
222
|
var self = this;
|
|
@@ -246,6 +285,7 @@ class FbEventManager extends events_1.default.EventEmitter {
|
|
|
246
285
|
}
|
|
247
286
|
// Prevent the event loop from re-queuing on stale op_event notifications
|
|
248
287
|
// that may arrive between closeEvents and socket.end()
|
|
288
|
+
self.eventconnection._intentionalClose = true;
|
|
249
289
|
self.eventconnection.eventcallback = null;
|
|
250
290
|
// Gracefully close the event socket using a FIN (end()) rather than a RST
|
|
251
291
|
// (destroy()), then wait for the 'close' event which confirms both sides have
|
package/package.json
CHANGED
package/src/types.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// published declaration files.
|
|
7
7
|
|
|
8
8
|
import type { Readable, Writable } from 'stream';
|
|
9
|
+
import type { EventEmitter } from 'events';
|
|
9
10
|
import type { SqlTag } from './sql-template';
|
|
10
11
|
|
|
11
12
|
export type { SqlTag, SqlQuery, SqlIdentifier, CompiledQuery } from './sql-template';
|
|
@@ -16,6 +17,30 @@ export type QueryCallback = (err: any, result: any[]) => void;
|
|
|
16
17
|
export type SimpleCallback = (err: any) => void;
|
|
17
18
|
export type SequentialCallback = (row: any, index: number, next?: (err?: any) => void) => void | Promise<void>;
|
|
18
19
|
|
|
20
|
+
export interface FbEventState {
|
|
21
|
+
state: 'IDLE' | 'SUBSCRIBED' | 'CLOSED';
|
|
22
|
+
hasActiveSubscription: boolean;
|
|
23
|
+
registeredEvents: Record<string, number>;
|
|
24
|
+
eventId: number;
|
|
25
|
+
isEventConnectionOpen: boolean;
|
|
26
|
+
isDatabaseConnectionClosed: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FbEventManager extends EventEmitter {
|
|
30
|
+
readonly eventid: number;
|
|
31
|
+
readonly events: Record<string, number>;
|
|
32
|
+
registerEvent(events: string[], callback: SimpleCallback): void;
|
|
33
|
+
unregisterEvent(events: string[], callback: SimpleCallback): void;
|
|
34
|
+
close(callback?: SimpleCallback): void;
|
|
35
|
+
getState(): FbEventState;
|
|
36
|
+
on(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
37
|
+
on(event: 'error', listener: (error: Error) => void): this;
|
|
38
|
+
once(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
39
|
+
once(event: 'error', listener: (error: Error) => void): this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type FbEventManagerCallback = (err: any, manager?: FbEventManager) => void;
|
|
43
|
+
|
|
19
44
|
/**
|
|
20
45
|
* Describes a single column in a prepared statement's result set or
|
|
21
46
|
* parameter list. The properties here are populated by the
|
|
@@ -272,7 +297,7 @@ export interface Database {
|
|
|
272
297
|
batchStream(query: string, options?: BatchStreamOptions): BatchStream;
|
|
273
298
|
drop(callback: SimpleCallback): void;
|
|
274
299
|
escape(value: any): string;
|
|
275
|
-
attachEvent(callback:
|
|
300
|
+
attachEvent(callback: FbEventManagerCallback): this;
|
|
276
301
|
createTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
|
|
277
302
|
alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
|
|
278
303
|
dropTablespace(name: string, callback?: QueryCallback): Database;
|
|
@@ -293,7 +318,7 @@ export interface Database {
|
|
|
293
318
|
newStatementAsync(query: string): Promise<Statement>;
|
|
294
319
|
detachAsync(force?: boolean): Promise<void>;
|
|
295
320
|
dropAsync(): Promise<void>;
|
|
296
|
-
attachEventAsync(): Promise<
|
|
321
|
+
attachEventAsync(): Promise<FbEventManager>;
|
|
297
322
|
/** Starts a transaction, commits when `work` resolves, rolls back when it rejects. */
|
|
298
323
|
withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T>;
|
|
299
324
|
/**
|
|
@@ -413,6 +438,8 @@ export type SupportedCharacterSet = |
|
|
|
413
438
|
|
|
414
439
|
export interface Options {
|
|
415
440
|
host?: string;
|
|
441
|
+
/** Override the server-advertised auxiliary event host (for NAT, tunnels and load balancers). */
|
|
442
|
+
eventHost?: string;
|
|
416
443
|
port?: number;
|
|
417
444
|
database?: string;
|
|
418
445
|
user?: string;
|
package/src/utils.ts
CHANGED
|
@@ -4,6 +4,13 @@ import type { FbStatusItem } from './callback';
|
|
|
4
4
|
|
|
5
5
|
const MessagesError = messagesJson as Record<string, string>;
|
|
6
6
|
|
|
7
|
+
/** Resolve the address used for Firebird's auxiliary event connection. */
|
|
8
|
+
export function resolveEventHost(options: { host?: string; eventHost?: string }, advertisedHost: string): string {
|
|
9
|
+
if (options.eventHost) return options.eventHost;
|
|
10
|
+
if (advertisedHost === '0.0.0.0' || advertisedHost === '::') return options.host || Const.DEFAULT_HOST;
|
|
11
|
+
return advertisedHost;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* Parse date from string
|
|
9
16
|
*/
|
package/src/wire/database.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Events from 'events';
|
|
2
2
|
import { doError, fromCallback, type Callback, type SimpleCallback } from '../callback';
|
|
3
|
-
import { batchResultToError, escape } from '../utils';
|
|
3
|
+
import { batchResultToError, escape, resolveEventHost } from '../utils';
|
|
4
4
|
import Const from './const';
|
|
5
5
|
import { makeSqlTag, type SqlTag } from '../sql-template';
|
|
6
6
|
import { computeColumnKeys, nestCell, resolveKeyTransform, resolveNestTables } from './xsqlvar';
|
|
@@ -464,6 +464,12 @@ class Database extends Events.EventEmitter {
|
|
|
464
464
|
attachEvent(callback: Callback<FbEventManager>): this {
|
|
465
465
|
var self = this;
|
|
466
466
|
const eventid = self.eventid++;
|
|
467
|
+
let completed = false;
|
|
468
|
+
const complete: Callback<FbEventManager> = function(err?: any, manager?: FbEventManager) {
|
|
469
|
+
if (completed) return;
|
|
470
|
+
completed = true;
|
|
471
|
+
callback(err, manager);
|
|
472
|
+
};
|
|
467
473
|
if (process.env.FIREBIRD_DEBUG) {
|
|
468
474
|
console.log('[fb-debug] Database.attachEvent: calling auxConnection, eventid=%d queue=%d', eventid, self.connection._queue.length);
|
|
469
475
|
}
|
|
@@ -473,7 +479,7 @@ class Database extends Events.EventEmitter {
|
|
|
473
479
|
if (process.env.FIREBIRD_DEBUG) {
|
|
474
480
|
console.log('[fb-debug] Database.attachEvent: auxConnection error:', err.message);
|
|
475
481
|
}
|
|
476
|
-
doError(err,
|
|
482
|
+
doError(err, complete);
|
|
477
483
|
return;
|
|
478
484
|
}
|
|
479
485
|
|
|
@@ -481,9 +487,7 @@ class Database extends Events.EventEmitter {
|
|
|
481
487
|
console.log('[fb-debug] Database.attachEvent: auxConnection ok, connecting to aux port %s:%d', socket_info.host, socket_info.port);
|
|
482
488
|
}
|
|
483
489
|
|
|
484
|
-
const host = (
|
|
485
|
-
? self.connection.options.host
|
|
486
|
-
: socket_info.host;
|
|
490
|
+
const host = resolveEventHost(self.connection.options, socket_info.host);
|
|
487
491
|
|
|
488
492
|
const eventConnection = new EventConnection(
|
|
489
493
|
host, socket_info.port, function(err?: any) {
|
|
@@ -491,7 +495,7 @@ class Database extends Events.EventEmitter {
|
|
|
491
495
|
if (process.env.FIREBIRD_DEBUG) {
|
|
492
496
|
console.log('[fb-debug] Database.attachEvent: EventConnection error:', err.message);
|
|
493
497
|
}
|
|
494
|
-
doError(err,
|
|
498
|
+
doError(err, complete);
|
|
495
499
|
return;
|
|
496
500
|
}
|
|
497
501
|
|
|
@@ -501,14 +505,14 @@ class Database extends Events.EventEmitter {
|
|
|
501
505
|
|
|
502
506
|
const evt = new FbEventManager(self, eventConnection, eventid, function (err: any) {
|
|
503
507
|
if (err) {
|
|
504
|
-
doError(err,
|
|
508
|
+
doError(err, complete);
|
|
505
509
|
return;
|
|
506
510
|
}
|
|
507
511
|
|
|
508
512
|
if (process.env.FIREBIRD_DEBUG) {
|
|
509
513
|
console.log('[fb-debug] Database.attachEvent: FbEventManager ready, eventid=%d', evt.eventid);
|
|
510
514
|
}
|
|
511
|
-
|
|
515
|
+
complete(err, evt);
|
|
512
516
|
});
|
|
513
517
|
}, self);
|
|
514
518
|
});
|
|
@@ -14,6 +14,8 @@ class EventConnection {
|
|
|
14
14
|
error: any;
|
|
15
15
|
eventcallback: ((err: any, ret?: any) => void) | null;
|
|
16
16
|
_connectSettled: boolean;
|
|
17
|
+
_terminalErrorReported: boolean;
|
|
18
|
+
_intentionalClose: boolean;
|
|
17
19
|
|
|
18
20
|
constructor(host: string, port: number, callback: ((err?: Error) => void) | undefined, db: any) {
|
|
19
21
|
var self = this;
|
|
@@ -22,6 +24,8 @@ class EventConnection {
|
|
|
22
24
|
this._isClosed = false;
|
|
23
25
|
this._isOpened = false;
|
|
24
26
|
this._connectSettled = false;
|
|
27
|
+
this._terminalErrorReported = false;
|
|
28
|
+
this._intentionalClose = false;
|
|
25
29
|
this._socket = net.createConnection(port, host);
|
|
26
30
|
this._bind_events(host, port, callback);
|
|
27
31
|
this.error = null;
|
|
@@ -37,19 +41,40 @@ class EventConnection {
|
|
|
37
41
|
if (callback) callback(err);
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
function reportTerminalError(err: Error) {
|
|
45
|
+
if (self._terminalErrorReported) return;
|
|
46
|
+
self._terminalErrorReported = true;
|
|
47
|
+
const wasOpened = self._isOpened;
|
|
48
|
+
self.error = err;
|
|
49
|
+
self._isClosed = true;
|
|
50
|
+
self._isOpened = false;
|
|
51
|
+
|
|
52
|
+
// An error during a caller-initiated shutdown is part of tearing
|
|
53
|
+
// down the auxiliary socket, not a database error.
|
|
54
|
+
if (!self._intentionalClose && !wasOpened) {
|
|
55
|
+
finishConnect(err);
|
|
56
|
+
} else if (!self._intentionalClose && self.eventcallback) {
|
|
57
|
+
self.eventcallback(err);
|
|
58
|
+
} else if (!self._intentionalClose && self.db && self.db.connection &&
|
|
59
|
+
typeof self.db.connection._emitError === 'function') {
|
|
60
|
+
self.db.connection._emitError(err);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!self._socket.destroyed) self._socket.destroy();
|
|
64
|
+
}
|
|
65
|
+
|
|
40
66
|
self._socket.on('close', function () {
|
|
41
67
|
self._isClosed = true;
|
|
42
68
|
if (!self._isOpened) {
|
|
43
69
|
finishConnect(self.error || new Error(`Event connection to ${host}:${port} closed before connecting.`));
|
|
70
|
+
} else if (!self._intentionalClose && self.eventcallback && !self._terminalErrorReported) {
|
|
71
|
+
reportTerminalError(new Error(`Event connection to ${host}:${port} closed unexpectedly.`));
|
|
44
72
|
}
|
|
73
|
+
self._isOpened = false;
|
|
45
74
|
})
|
|
46
75
|
|
|
47
76
|
self._socket.on('error', function (e) {
|
|
48
|
-
|
|
49
|
-
if (!self._isOpened) {
|
|
50
|
-
if (!self._socket.destroyed) self._socket.destroy();
|
|
51
|
-
finishConnect(e);
|
|
52
|
-
}
|
|
77
|
+
reportTerminalError(e);
|
|
53
78
|
})
|
|
54
79
|
|
|
55
80
|
self._socket.on('connect', function () {
|
|
@@ -78,6 +103,7 @@ class EventConnection {
|
|
|
78
103
|
|
|
79
104
|
var tmp_event: Record<string, number>;
|
|
80
105
|
while (xdr.pos < xdr.buffer.length) {
|
|
106
|
+
op_pos = xdr.pos;
|
|
81
107
|
do {
|
|
82
108
|
var r = xdr.readInt();
|
|
83
109
|
} while (r === Const.op_dummy);
|
|
@@ -95,7 +121,8 @@ class EventConnection {
|
|
|
95
121
|
var eventcount = 0;
|
|
96
122
|
var pos = 1;
|
|
97
123
|
while (pos < buf.length) {
|
|
98
|
-
var len = buf.
|
|
124
|
+
var len = buf.readUInt8(pos++);
|
|
125
|
+
if (pos + len + 4 > buf.length) throw new RangeError('Incomplete event payload');
|
|
99
126
|
eventname = buf.toString(DEFAULT_ENCODING, pos, pos + len);
|
|
100
127
|
var prevcount = self.emgr.events[eventname] || 0;
|
|
101
128
|
pos += len;
|
|
@@ -120,7 +147,7 @@ class EventConnection {
|
|
|
120
147
|
self.eventcallback(null, { eventid: event_id, events: lst_event });
|
|
121
148
|
break;
|
|
122
149
|
default:
|
|
123
|
-
|
|
150
|
+
reportTerminalError(new Error('Unexpected event connection opcode: ' + r));
|
|
124
151
|
return;
|
|
125
152
|
}
|
|
126
153
|
}
|
|
@@ -130,7 +157,7 @@ class EventConnection {
|
|
|
130
157
|
xdr.pos = 0;
|
|
131
158
|
self._xdr = xdr;
|
|
132
159
|
} else {
|
|
133
|
-
|
|
160
|
+
reportTerminalError(err instanceof Error ? err : new Error(String(err)));
|
|
134
161
|
}
|
|
135
162
|
}
|
|
136
163
|
})
|
|
@@ -138,7 +165,6 @@ class EventConnection {
|
|
|
138
165
|
|
|
139
166
|
throwClosed(callback?: (err: any) => void): this {
|
|
140
167
|
var err = new Error('Event Connection is closed.');
|
|
141
|
-
this.db.emit('error', err);
|
|
142
168
|
if (callback)
|
|
143
169
|
callback(err);
|
|
144
170
|
return this;
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
// Asynchronous notifications on the AUX (EventConnection) socket
|
|
64
64
|
// ───────────────────────────────────────────────────────────────
|
|
65
65
|
// Server → Client : op_event (fired by Firebird POST_EVENT trigger)
|
|
66
|
+
// Error / unexpected close → CLOSED; emit manager 'error' once
|
|
66
67
|
|
|
67
68
|
import Events from 'events';
|
|
68
69
|
import { doError } from '../callback';
|
|
@@ -74,6 +75,9 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
74
75
|
eventid: number;
|
|
75
76
|
_subscriptionVersion: number;
|
|
76
77
|
_hasActiveSubscription: boolean;
|
|
78
|
+
_readySettled: boolean;
|
|
79
|
+
_terminalErrorReported: boolean;
|
|
80
|
+
_readyCallback: (err: any, ret?: any) => void;
|
|
77
81
|
|
|
78
82
|
constructor(db: any, eventconnection: any, eventid: number, callback: (err: any, ret?: any) => void) {
|
|
79
83
|
super();
|
|
@@ -88,7 +92,53 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
88
92
|
// main connection (so close() and _changeEvent know whether to send
|
|
89
93
|
// op_cancel_events before tearing down or re-subscribing).
|
|
90
94
|
this._hasActiveSubscription = false;
|
|
91
|
-
this.
|
|
95
|
+
this._readySettled = false;
|
|
96
|
+
this._terminalErrorReported = false;
|
|
97
|
+
this._readyCallback = callback;
|
|
98
|
+
this._createEventLoop();
|
|
99
|
+
process.nextTick(() => this._finishReady());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
on(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
103
|
+
on(event: 'error', listener: (error: Error) => void): this;
|
|
104
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
105
|
+
return super.on(event, listener);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
once(event: 'post_event', listener: (name: string, count: number) => void): this;
|
|
109
|
+
once(event: 'error', listener: (error: Error) => void): this;
|
|
110
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
111
|
+
return super.once(event, listener);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_finishReady(err?: Error): void {
|
|
115
|
+
if (this._readySettled) return;
|
|
116
|
+
this._readySettled = true;
|
|
117
|
+
if (err) doError(err, this._readyCallback);
|
|
118
|
+
else this._readyCallback(null);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_handleAsyncError(err: any): void {
|
|
122
|
+
if (this._terminalErrorReported) return;
|
|
123
|
+
this._terminalErrorReported = true;
|
|
124
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
125
|
+
this._hasActiveSubscription = false;
|
|
126
|
+
this._subscriptionVersion++;
|
|
127
|
+
this.eventconnection._isClosed = true;
|
|
128
|
+
this.eventconnection._isOpened = false;
|
|
129
|
+
this.eventconnection.eventcallback = null;
|
|
130
|
+
if (this.eventconnection._socket && !this.eventconnection._socket.destroyed &&
|
|
131
|
+
typeof this.eventconnection._socket.destroy === 'function') {
|
|
132
|
+
this.eventconnection._socket.destroy();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!this._readySettled) {
|
|
136
|
+
this._finishReady(error);
|
|
137
|
+
} else if (this.listenerCount('error') > 0) {
|
|
138
|
+
this.emit('error', error);
|
|
139
|
+
} else if (this.db.connection && typeof this.db.connection._emitError === 'function') {
|
|
140
|
+
this.db.connection._emitError(error);
|
|
141
|
+
}
|
|
92
142
|
}
|
|
93
143
|
|
|
94
144
|
/**
|
|
@@ -143,7 +193,7 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
143
193
|
};
|
|
144
194
|
}
|
|
145
195
|
|
|
146
|
-
_createEventLoop(
|
|
196
|
+
_createEventLoop(): void {
|
|
147
197
|
var self = this;
|
|
148
198
|
var cnx = this.db.connection;
|
|
149
199
|
this.eventconnection.emgr = this;
|
|
@@ -166,7 +216,7 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
166
216
|
}
|
|
167
217
|
cnx.queEvents(self.events, self.eventid, function (err: any) {
|
|
168
218
|
if (err) {
|
|
169
|
-
|
|
219
|
+
self._handleAsyncError(err);
|
|
170
220
|
return;
|
|
171
221
|
}
|
|
172
222
|
// subscription renewed, nothing else to do
|
|
@@ -174,8 +224,8 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
174
224
|
}
|
|
175
225
|
|
|
176
226
|
this.eventconnection.eventcallback = function (err: any, ret?: any) {
|
|
177
|
-
if (err || (self.eventid !== ret.eventid)) {
|
|
178
|
-
|
|
227
|
+
if (err || !ret || (self.eventid !== ret.eventid)) {
|
|
228
|
+
self._handleAsyncError(err || new Error('Bad eventid'));
|
|
179
229
|
return;
|
|
180
230
|
}
|
|
181
231
|
|
|
@@ -186,11 +236,6 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
186
236
|
loop();
|
|
187
237
|
};
|
|
188
238
|
|
|
189
|
-
// Resolve attachEvent on the next tick – no subscription is needed
|
|
190
|
-
// until the caller registers at least one event name via registerEvent().
|
|
191
|
-
// process.nextTick ensures the outer `const evt = new FbEventManager(...)`
|
|
192
|
-
// assignment in database.js completes before this callback fires.
|
|
193
|
-
process.nextTick(function() { callback(null); });
|
|
194
239
|
}
|
|
195
240
|
|
|
196
241
|
_changeEvent(callback: (err: any, ret?: any) => void): void {
|
|
@@ -271,6 +316,7 @@ class FbEventManager extends Events.EventEmitter {
|
|
|
271
316
|
|
|
272
317
|
// Prevent the event loop from re-queuing on stale op_event notifications
|
|
273
318
|
// that may arrive between closeEvents and socket.end()
|
|
319
|
+
self.eventconnection._intentionalClose = true;
|
|
274
320
|
self.eventconnection.eventcallback = null;
|
|
275
321
|
|
|
276
322
|
// Gracefully close the event socket using a FIN (end()) rather than a RST
|