eventsourcingdb 0.1.0-beta.1 copy "eventsourcingdb: ^0.1.0-beta.1" to clipboard
eventsourcingdb: ^0.1.0-beta.1 copied to clipboard

Community Dart client for EventSourcingDB with typed HTTP and NDJSON APIs.

eventsourcingdb #

An independent, community-maintained Dart client for EventSourcingDB, a purpose-built database for event sourcing.

This project is not an official SDK from the native web GmbH. It targets Dart VM and Flutter mobile/desktop. Browser support is experimental and is not part of the compatibility test matrix.

The integration suite targets the immutable EventSourcingDB preview snapshot sha256:169073c2e0231aed5d9a7acadddb70ca222a5f0cc18874c2671b5b6efe6c50d7 from 2026-09-06. Compatibility claims are limited to that exact server build.

For more information on EventSourcingDB, see its official documentation.

Getting Started #

Add the client SDK to your pubspec.yaml:

dart pub add eventsourcingdb

Import the Client class and create an instance by providing the URL of your EventSourcingDB instance and the API token to use:

import 'package:eventsourcingdb/eventsourcingdb.dart';

final client = Client(Uri.parse('http://localhost:3000'), 'secret');

Then call ping to check whether the instance is reachable. If it is not, the call throws:

await client.ping();

Note that ping does not require authentication, so the call may succeed even if the API token is invalid.

If you want to verify the API token, call verifyApiToken. If the token is invalid, the call throws:

await client.verifyApiToken();

Writing Events #

Call writeEvents and pass one or more EventCandidates. data is any JSON-encodable value (a Map, List, String, num, bool, or null):

final writtenEvents = await client.writeEvents([
  EventCandidate(
    source: 'https://www.eventsourcingdb.io',
    subject: '/books/42',
    type: 'io.eventsourcingdb.library.book-acquired',
    data: {'title': 'Dune', 'author': 'Frank Herbert'},
  ),
]);

writeEvents writes either all or none of the given events, depending on optional preconditions:

await client.writeEvents(
  [eventCandidate],
  preconditions: [
    Precondition.isSubjectPristine('/books/42'),
    // or: Precondition.isSubjectPopulated('/books/42'),
    // or: Precondition.isSubjectOnEventId('/books/42', '0'),
    // or: Precondition.isEventQlQueryTrue('FROM e IN events PROJECT INTO COUNT() == 0'),
  ],
);

Reading Events #

Call readEvents with a subject and options. It returns a Stream<Event> that ends once all matching events have been read:

await for (final event in client.readEvents(
  '/books/42',
  const ReadEventsOptions(recursive: false),
)) {
  print(event.id);
}

recursive controls whether events of nested subjects are included. Further options let you control ordering and bounds:

const ReadEventsOptions(
  recursive: true,
  order: Order.antichronological,
  lowerBound: Bound(id: '0', type: BoundType.inclusive),
  upperBound: Bound(id: '10', type: BoundType.exclusive),
  fromLatestEvent: ReadFromLatestEvent(
    subject: '/books/42',
    type: 'io.eventsourcingdb.library.book-borrowed',
    ifEventIsMissing: ReadIfEventIsMissing.readEverything,
  ),
);

Reading Subjects #

Call readSubjects with a base subject to read all subjects recursively below it (streamed as they are found):

await for (final subject in client.readSubjects('/books')) {
  print(subject);
}

Observing Events #

Call observeEvents to keep reading events as they are written. The returned Stream<Event> only ends when you stop listening (e.g. by cancelling the subscription) or the connection is lost:

final subscription = client
    .observeEvents('/books/42', const ObserveEventsOptions(recursive: false))
    .listen((event) => print(event.id));

// ... later:
await subscription.cancel();

Reading and Registering Event Types #

await for (final eventType in client.readEventTypes()) {
  print('${eventType.type} (phantom: ${eventType.isPhantom})');
}

final eventType = await client.readEventType(
  'io.eventsourcingdb.library.book-acquired',
);

await client.registerEventSchema(
  'io.eventsourcingdb.library.book-acquired',
  {
    'type': 'object',
    'properties': {
      'title': {'type': 'string'},
      'author': {'type': 'string'},
    },
    'required': ['title', 'author'],
  },
);

Running EventQL Queries #

runEventQlQuery yields each row's decoded JSON payload as-is (a Map, a scalar, or null, depending on what the query projects):

final rows = await client
    .runEventQlQuery('FROM e IN events PROJECT INTO COUNT()')
    .toList();

Use runEventQlQueryAs for a typed result, or runEventQlQueryForEvents when the query projects whole events:

final counts = client.runEventQlQueryAs<int>(
  'FROM e IN events PROJECT INTO COUNT()',
  (value) => value as int,
);
await for (final event in client.runEventQlQueryForEvents(
  'FROM e IN events PROJECT INTO e',
)) {
  print(event.id);
}

Reading Event Data #

Event.data is already JSON-decoded (a Map, List, String, num, bool, or null). Use getData with your own decode function for a typed value:

final title = event.getData(
  (json) => (json as Map<String, Object?>)['title'] as String,
);

Verifying Hashes and Signatures #

Every event carries a SHA-256 hash over its metadata and data, tamper-evident by construction. Call verifyHash to recompute and compare it; it throws HashVerificationException on a mismatch:

event.verifyHash();

If the server was started with a signing key, events also carry an Ed25519 signature. Call verifySignature with the server's PKIX/SPKI-encoded public key; it throws SignatureVerificationException if the event is unsigned, malformed, or the signature does not verify:

event.verifySignature(verificationKeySpki);

Using a Custom HTTP Client #

Provide your own package:http client, e.g. to control timeouts or add custom headers:

import 'package:http/http.dart' as http;

final client = Client(
  Uri.parse('http://localhost:3000'),
  'secret',
  httpClient: http.Client(),
);

Injected HTTP clients remain owned by the caller and are not closed by Client.close(). Network methods also accept an optional abortTrigger:

import 'dart:async';

final abort = Completer<void>();
final pending = client.ping(abortTrigger: abort.future);
abort.complete();
await pending;

The constructor also accepts a JsonCodec dataCodec for custom event payload conversion and a package:logging Logger. Logs contain operation names and paths, never API tokens or event payloads. Depend on the EventSourcingDbClient interface when the client needs to be mocked.

Docker Test Container #

VM tests can import the optional container helper without adding a separate dependency:

import 'package:eventsourcingdb/eventsourcingdb_test.dart';

final container = EventSourcingDbContainer()
    .withImageTag('preview@sha256:169073c2e0231aed5d9a7acadddb70ca222a5f0cc18874c2671b5b6efe6c50d7')
    .withPort(4000)
    .withSigningKey();
await container.start();
final client = container.getClient();
addTearDown(container.stop);

Error Handling #

All exceptions this package throws extend EventSourcingDbException:

  • EventSourcingDbHttpException – the server responded with a non-200 status; carries statusCode and the response body.
  • ServerErrorException – a streamed response reported an error line.
  • InvalidValueException – a response violated the protocol this client expects.
  • HashVerificationException / SignatureVerificationExceptionEvent.verifyHash / Event.verifySignature failed.

Connection-level failures (e.g. the server being unreachable) surface as whatever package:http throws (typically a SocketException or http.ClientException), not one of the types above.

Running the Example #

See example/example.dart for a runnable end-to-end walkthrough (ping, write, read, EventQL) against a local EventSourcingDB instance.

Development #

dart pub get          # install dependencies
dart format .          # format
dart analyze           # static analysis
dart test test/unit    # unit tests (no Docker required)
dart test --concurrency=2 --tags docker test/integration

make qa runs formatting, analysis, and the unit test suite in one go.

Integration tests spin up a real, digest-pinned EventSourcingDB container per test through EventSourcingDbContainer, so they require a running Docker daemon. See doc/architecture.md for the design model.

0
likes
160
points
41
downloads

Documentation

API reference

Publisher

verified publisherdclimber.com

Weekly Downloads

Community Dart client for EventSourcingDB with typed HTTP and NDJSON APIs.

Homepage
Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

collection, crypto, ed25519_edwards, http, logging

More

Packages that depend on eventsourcingdb