Repository navigation
v2.18.0
Minor release adding an opt-in event counter baseline for Firebird POST_EVENT subscriptions (attachEvent() / attachEventAsync()). It separates the counter snapshot the server sends when a subscription starts from real event changes. The default behavior is unchanged. Upgrading does not affect existing applications unless they set the new option.
Summary of Changes
When a client subscribes with op_que_events, Firebird compares the counters the client says it knows (initially 0) with the server's current counters. It answers with an op_event packet for any difference. If an event was posted at any point in the database's lifetime before the subscription, the first packet reports that historical count. The driver has always emitted that first packet as post_event, so it looked like a new event even though nothing changed after the subscription.
Some applications rely on that first post_event as a startup refresh signal, so it remains the default. Version 2.18.0 adds eventBaseline: true, which reports that first packet as a separate baseline event instead. This matches the ODBC/JDBC and Jaybird practice of treating the first callback as initialization.
References:
- Firebird wire protocol –
op_que_events - Firebird ODBC/JDBC manual – events listener
- Jaybird manual – events
Key Improvements
1. New connection option: eventBaseline (#437)
const options = {
host: '127.0.0.1',
database: 'database.fdb',
user: 'SYSDBA',
password: 'masterkey',
eventBaseline: true, // default: false
};- Also accepted in connection URIs as a boolean query parameter:
firebird://localhost/employee?eventBaseline=true(1,true,yesoron, case-insensitive, enable it; any other value disables it). - Declared in the public TypeScript
Optionsinterface.
2. New baseline event on FbEventManager (#437)
With eventBaseline: true:
| Packet | Default (eventBaseline: false) |
eventBaseline: true |
|---|---|---|
First op_event after a subscription (re)starts |
post_event(name, count) for each changed counter |
baseline(counts) once, no post_event |
Later op_event packets |
post_event(name, count) |
post_event(name, count) (unchanged) |
baselinereceives a frozen snapshot (Readonly<Record<string, number>>) of all currently registered event counters, e.g.{ MY_EVENT: 42 }. It is a copy; mutating the manager's internal state through it is impossible.- Typed
on('baseline', ...)/once('baseline', ...)overloads are included insrc/types.ts.
Recommended usage:
Firebird.attach({ ...options, eventBaseline: true }, (err, db) => {
if (err) throw err;
db.attachEvent((err, evtmgr) => {
if (err) throw err;
evtmgr.on('error', err => console.error('event connection failed:', err));
// Initialize from the current database state.
evtmgr.on('baseline', counts => refreshFromDatabase());
// Real changes after the baseline.
evtmgr.on('post_event', (name, count) => handleEvent(name, count));
// Install listeners BEFORE registering: the auxiliary notification can
// arrive before the registration callback on the main connection.
evtmgr.registerEvent(['MY_EVENT'], err => {
if (err) throw err;
});
});
});3. Reconfiguration starts a new baseline (#437)
- Every
registerEvent()/unregisterEvent()that changes the subscription cancels the active one (op_cancel_events) and queues a fresh subscription with a new wire event ID (p_event_rid). The next packet is emitted as a newbaselinecontaining all currently registered events. - Delayed packets from an older subscription are silently dropped. This applies even when the old and new subscriptions share event names, and to packets that arrive after the last event was unregistered. They never appear as a
post_event, and they never re-subscribe a removed event. - Overlapping register/unregister calls during an in-flight reconfiguration are coalesced. All their callbacks are resolved when the final subscription is acknowledged.
- An empty event set is never queued to the server (Firebird would never acknowledge it and the main connection queue would stall).
close()called while a baseline reconfiguration is in flight is deferred until that reconfiguration settles. Pending register/unregister callbacks then receiveEvent Connection is closed., and further register/unregister calls are rejected immediately.
4. Bounded tracking of retired event IDs (#439)
- Baseline mode now tracks retired subscription IDs with a single high-water mark instead of a set that grew by one entry per reconfiguration. Memory use is constant no matter how often an application re-registers events.
- Behavior is identical. Packets and re-queue errors for any retired ID are ignored, and an event ID that was never allocated is still reported as
Bad eventid.
5. Documentation
- README: new
options.eventBaselineentry and a new section describing baseline semantics. - The README events example now installs
post_event(and optionallybaseline) listeners beforeregisterEvent(), which fixes a race in the old example where the first notification could arrive before the listener existed.
Important Semantics
- A baseline can include a real event. An event committed while the subscription is being established can be folded into the baseline counts. Firebird can also combine several committed events into one notification. Applications using
eventBaselineshould re-read their database state onbaselinerather than assume every change produces a laterpost_event. post_event'scountis the server's cumulative counter for that event, as before.- Default mode keeps the existing parser-side counter updates and emission order exactly as in 2.17.x.
Upgrading
- No action required.
eventBaselinedefaults tofalse. - To opt in, set
eventBaseline: true, add abaselinelistener that refreshes your state, and move listener installation beforeregisterEvent().
Testing & Verification
- Unit tests (
test/unit/event-connection.test.ts):- Default mode still emits the first packet as
post_event - Opt-in mode emits one frozen
baseline, then delivers the first real change aspost_event - Register/unregister reconfiguration produces a new baseline each time and never queues an empty event set
- Late packet after the last event is unregistered is ignored
- Delayed packet from an older generation sharing an event name is ignored
- Changes made before cancellation completes are coalesced
- A subscription changed before its queue acknowledgement is replaced
close()during an in-flight cancellation does not subscribe again- Retired-ID state stays constant across 50 reconfigurations; unallocated IDs still raise
Bad eventid
- Default mode still emits the first packet as
- Integration test (
test/index.js, real Firebird): a pre-existing non-zero counter produces abaselinewith nopost_event, the next committed event produces apost_eventwith a higher count, and registering an additional event produces a second baseline containing both events. - URI test (
test/uri.js):eventBaseline=true/eventBaseline=falseparse to booleans. - CI matrix: Node 20 / 22 / 24 / 26 × Firebird 3 / 4 / 5 / 6-snapshot.
Pull Requests
- #437 feat(events): add opt-in counter baseline notifications — @Gobbo89
- #439 fix(events): bound retired baseline event-ID tracking
Full Changelog: v2.17.1...v2.18.0