Skip to content

v2.18.0

Choose a tag to compare

@mariuz mariuz released this 23 Sep 17:33
· 24 commits to master since this release

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:


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, yes or on, case-insensitive, enable it; any other value disables it).
  • Declared in the public TypeScript Options interface.

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)
  • baseline receives 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 in src/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 new baseline containing 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 receive Event 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.eventBaseline entry and a new section describing baseline semantics.
  • The README events example now installs post_event (and optionally baseline) listeners before registerEvent(), 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 eventBaseline should re-read their database state on baseline rather than assume every change produces a later post_event.
  • post_event's count is 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. eventBaseline defaults to false.
  • To opt in, set eventBaseline: true, add a baseline listener that refreshes your state, and move listener installation before registerEvent().

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 as post_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
  • Integration test (test/index.js, real Firebird): a pre-existing non-zero counter produces a baseline with no post_event, the next committed event produces a post_event with a higher count, and registering an additional event produces a second baseline containing both events.
  • URI test (test/uri.js): eventBaseline=true / eventBaseline=false parse 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