The conformance suite now checks that your store returns every digit of a double

18 minutes ago

Version 4.1.0 of the core event sourcing package adds one conformance case to its testing subpath, called payload-number-precision. It checks that a store hands back the doubles in a payload with every significant digit intact. The built-in stores already pass it. If you run the suites against your own event store or snapshot store, this release changes what they check.

Ask about this presentation

Chapters

  1. 0:00Every digit of a double
  2. 0:23What the old case missed
  3. 0:48The six doubles it writes
  4. 1:11Both suites, these reads
  5. 1:36How to fix a custom store
  6. 1:56Skip it with a reason
  7. 2:11Upgrade
Show transcript
  1. Version 4.1.0 of the core event sourcing package adds one conformance case to its testing subpath, called payload-number-precision. It checks that a store hands back the doubles in a payload with every significant digit intact. The built-in stores already pass it. If you run the suites against your own event store or snapshot store, this release changes what they check.

    Slide text

    The conformance suite now checks that your store returns every digit of a double

    0.1 + 0.2, as JavaScript stores it, with its 17th significant digit
    0. 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4
    Ocoda logo @ocoda/event-sourcing 4.1.0
  2. Until now, the closest check was payload-json-fidelity, and it only holds short decimals. So take a payload with 0.1 plus 0.2 in it. JavaScript stores that as a double with a 4 in the seventeenth significant digit. A store that parses or prints JSON numbers with only 15 digits gives back plain 0.3. The old case passed a store like that. The new case fails it.

    Slide text
    @ocoda/event-sourcing 4.1.0

    payload-json-fidelity only held short decimals, so a lost last digit passed

    Written: 0.1 + 0.2
    0. 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4
    Read back by a store that keeps 15 significant digits
    0. 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4
  3. Here's what the new case writes and reads back. That same sum, and its negative. The double just below 1. 2 to the power of minus 44. A 17-digit number a little over 123 million, and the largest double JavaScript can hold. Each one comes back unchanged through JSON, and each one changes when it's rounded to 15 digits.

    Slide text
    4.1.0

    Six doubles that need 16 or 17 digits to come back

    Each one survives JSON.stringify and JSON.parse. None survives rounding to 15 digits.

    packages/core/lib/testing/fixtures.ts
    createNumberPrecisionProbe()
    ({
      sum: 0.1 + 0.2,
      negativeSum: -(0.1 + 0.2),
      belowOne: 1 - Number.EPSILON / 2,
      small: 2 ** -44,
      large: 123_456_789.123_456_79,
      max: Number.MAX_VALUE,
    })
  4. The case runs in both suites. On the event store, the doubles have to come back unchanged from getEvent, getEvents, getEnvelope and getEnvelopes. On the snapshot store, from getSnapshot, getSnapshots, getLastSnapshot, getLastSnapshots and getEnvelope. Every built-in store passes it. The core package also keeps a deliberately lossy test store that rounds to 15 digits, to prove the case catches one.

    Slide text
    @ocoda/event-sourcing/testing

    Both suites check the doubles on these reads

    Event store suite

    • getEvent
    • getEvents
    • getEnvelope
    • getEnvelopes

    Snapshot store suite

    • getSnapshot
    • getSnapshots
    • getLastSnapshot
    • getLastSnapshots
    • getEnvelope
    Every built-in store passes the new case
  5. A custom store that runs the suites may now fail this case. The fix is in how the store keeps numbers, and how it prints them. Store the payload as JSON text, or as binary doubles. And when the store prints a number, use the shortest form that reads back as the same double. That's what JSON.stringify already does.

    Slide text
    @ocoda/event-sourcing 4.1.0

    If your own store fails the case, change how it keeps and prints numbers

    Keep
    Store the payload as JSON text or as binary doubles
    Print
    Use the shortest form that reads back the same, as JSON.stringify does
  6. Until the store is fixed, skip the case in the suite's skip option, and write down why. Once you think it's fixed, set CONFORMANCE_RUN_SKIPPED to true. The suites then run the skipped cases, so you can check whether the skip is still needed.

    Slide text
    4.1.0

    Skip the case with a reason until the store is fixed

    foo.event-store.conformance.spec.ts
    describeEventStoreConformance(
      'FooEventStore',
      async (context) => { … },
      {
        skip: {
          'payload-number-precision': '<why>',
        },
      },
    );
    CONFORMANCE_RUN_SKIPPED=true
    runs the skipped cases, to check whether a skip is still needed
  7. To upgrade, install the 4.x line of the core package. The change lives in the testing subpath, so it only shows up where your tests call the conformance suites. If you maintain your own store, run its suites first, and fix the store or skip the case with a reason. The PostgreSQL, MariaDB and MongoDB packages published their own 4.1.0 notes. For the core, the conformance suite now checks that your store returns every digit of a double.

    Slide text
    @ocoda/event-sourcing 4.1.0

    Upgrade the core package on the 4.x line

    $npm install @ocoda/event-sourcing@4
    Maintain your own store?
    Run its suites first. If payload-number-precision fails, fix the store or skip the case with a reason.
    Integrations release separately
    The PostgreSQL, MariaDB and MongoDB packages each published their own 4.1.0, with their own notes
    github.com/ocoda/event-sourcing/releases

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️