eventsourcingdb 1.1.0

A client library for the EventsourcingDB by the native web.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# eventsourcingdb

The official Rust client SDK for [EventSourcingDB](https://www.eventsourcingdb.io) – a purpose-built database for event sourcing.

EventSourcingDB enables you to build and operate event-driven applications with native support for writing, reading, and observing events. This client SDK provides convenient access to its capabilities in Rust.

For more information on EventSourcingDB, see its [official documentation](https://docs.eventsourcingdb.io/).

This client SDK includes support for [Testcontainers](https://testcontainers.com/) to spin up EventSourcingDB instances in integration tests. For details, see [Using Testcontainers](#using-testcontainers).

## Getting Started

Install the client SDK:

```shell
cargo add eventsourcingdb
```

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

```rust
use eventsourcingdb::client::Client;

// ...

let base_url: Url = "localhost:3000".parse().unwrap();
let api_token = "secret";
let client = Client::new(base_url, api_token);
```

Then call the `ping` function to check whether the instance is reachable. If it is not, the function will return an error:

```rust
let result = client.ping().await;
if let Err(err) = result {
  // handle error...
}
```

*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 `verify_api_token`. If the token is invalid, the function will return an error:

```rust
let result = client.verify_api_token().await;
if let Err(err) = result {
  // handle error...
}
```

### Writing Events

Call the `write_events` function and hand over a vector with one or more events. You do not have to provide all event fields – some are automatically added by the server.

Specify `source`, `subject`, `type` (using `ty`), and `data` according to the [CloudEvents](https://docs.eventsourcingdb.io/fundamentals/cloud-events/) format.

For `data` provide a JSON object using a `serde_json:Value`.

The function returns the written events, including the fields added by the server:

```rust
let event = EventCandidate::builder()
  .source("https://library.eventsourcingdb.io".to_string())
  .subject("/books/42".to_string())
  .ty("io.eventsourcingdb.library.book-acquired")
  .data(json!({
    "title": "2001 - A Space Odyssey",
    "author": "Arthur C. Clarke",
    "isbn": "978-0756906788",
  }))
  .build();

let result = client.write_events(vec![event.clone()], vec![]).await;
match result {
  Ok(written_events) => // ...
  Err(err) => // ...
}
```

#### Using the `IsSubjectPristine` precondition

If you only want to write events in case a subject (such as `/books/42`) does not yet have any events, use the `IsSubjectPristine` precondition to create a precondition and pass it in a vector as the second argument:

```rust
let result = client.write_events(
  vec![event.clone()],
  vec![Precondition::IsSubjectPristine {
    subject: "/books/42".to_string(),
  }],
).await;
match result {
  Ok(written_events) => // ...
  Err(err) => // ...
}
```

#### Using the `IsSubjectPopulated` precondition

If you only want to write events in case a subject (such as `/books/42`) already has at least one event, use the `IsSubjectPopulated` precondition to create a precondition and pass it in a vector as the second argument:

```rust
let result = client.write_events(
  vec![event.clone()],
  vec![Precondition::IsSubjectPopulated {
    subject: "/books/42".to_string(),
  }],
).await;
match result {
  Ok(written_events) => // ...
  Err(err) => // ...
}
```

#### Using the `IsSubjectOnEventId` precondition

If you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `0`), use the `IsSubjectOnEventId` precondition to create a precondition and pass it in a vector as the second argument:

```rust
let result = client.write_events(
  vec![event.clone()],
  vec![Precondition::IsSubjectOnEventId {
    subject: "/books/42".to_string(),
    event_id: "0".to_string(),
  }],
).await;
match result {
  Ok(written_events) => // ...
  Err(err) => // ...
}
```

*Note that according to the CloudEvents standard, event IDs must be of type string.*

#### Using the `IsEventQLQueryTrue` precondition

If you want to write events depending on an EventQL query, use the `IsEventQLQueryTrue` precondition to create a precondition and pass it in a vector as the second argument:

```rust
let result = client.write_events(
  vec![event.clone()],
  vec![Precondition::IsEventQLQueryTrue {
    query: "FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-borrowed' PROJECT INTO COUNT() < 10".to_string(),
  }],
).await;
match result {
  Ok(written_events) => // ...
  Err(err) => // ...
}
```

### Reading Events

To read all events of a subject, call the `read_events` function with the subject and an options object. Set the `recursive` option to `false`. This ensures that only events of the given subject are returned, not events of nested subjects.

The function returns a stream from which you can retrieve one event at a time:

```rust
let result = client
  .read_events("/books/42", Some(
    ReadEventsOptions {
      recursive: false,
      ..Default::default(),
    }
  ))
  .await;

match result {
  Err(err) => // ...
  Ok(mut stream) => {
    while let Some(event) = stream.next().await {
      // ...
    }
  }
}
```

#### Reading From Subjects Recursively

If you want to read not only all the events of a subject, but also the events of all nested subjects, set the `recursive` option to `true`:

```rust
let result = client
  .read_events("/books/42", Some(
    ReadEventsOptions {
      recursive: true,
      ..Default::default(),
    }
  ))
  .await;
```

This also allows you to read *all* events ever written. To do so, provide `/` as the subject and set `recursive` to `true`, since all subjects are nested under the root subject.

#### Reading in Anti-Chronological Order

By default, events are read in chronological order. To read in anti-chronological order, provide the `order` option and set it using the `Antichronological` ordering:

```rust
let result = client
  .read_events("/books/42", Some(
    ReadEventsOptions {
      recursive: false,
      order: Some(Ordering::Antichronological),
      ..Default::default(),
    }
  ))
  .await;
```

*Note that you can also use the `Chronological` ordering to explicitly enforce the default order.*

#### Specifying Bounds

Sometimes you do not want to read all events, but only a range of events. For that, you can specify the `lower_bound` and `upper_bound` options – either one of them or even both at the same time.

Specify the ID and whether to include or exclude it, for both the lower and upper bound:

```rust
let result = client
  .read_events("/books/42", Some(
    ReadEventsOptions {
      recursive: false,
      lower_bound: Some(Bound {
        bound_type: BoundType::Inclusive,
        id: "100",
      }),
      upper_bound: Some(Bound {
        bound_type: BoundType::Exclusive,
        id: "200",
      }),
      ..Default::default(),
    }
  ))
  .await;
```

#### Starting From the Latest Event of a Given Type

To read starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists.

Possible options are `ReadNothing`, which skips reading entirely, or `ReadEverything`, which effectively behaves as if `from_latest_event` was not specified:

```rust
let result = client
  .read_events("/books/42", Some(
    ReadEventsOptions {
      recursive: false,
      from_latest_event: Some(
        FromLatestEventOptions {
          subject: "/books/42",
          ty: "io.eventsourcingdb.library.book-borrowed",
          if_event_is_missing: ReadEventMissingStrategy::ReadEverything,
        }
      )
      ..Default::default(),
    }
  ))
  .await;
```

*Note that `from_latest_event` and `lower_bound` can not be provided at the same time.*

### Running EventQL Queries

To run an EventQL query, call the `run_eventql_query` function and provide the query as argument. The function returns a stream.

```rust
let result = client
  .run_eventql_query("FROM e IN events PROJECT INTO e")
  .await;

match result {
  Err(err) => // ...
  Ok(mut stream) => {
    while let Some(row) = stream.next().await {
      // ...
    }
  }
}
```

*Note that each row returned by the stream is of type `serde_json::Value` and matches the projection specified in your query.*

### Converting Events to Polars DataFrame

For data analysis and exploration, you can convert event streams to Polars DataFrames. To use this feature, add the SDK with the `polars` feature:

```shell
cargo add eventsourcingdb --features polars
```

Import the `events_to_dataframe` function and pass an event stream to it:

```rust
use eventsourcingdb::polars::events_to_dataframe;

let events = client
  .read_events("/books", Some(ReadEventsOptions {
    recursive: true,
    ..Default::default()
  }))
  .await?;

let df = events_to_dataframe(events).await?;
println!("{}", df);
```

The resulting DataFrame includes all event fields as columns: `event_id`, `time`, `source`, `subject`, `type`, `data`, `spec_version`, `data_content_type`, `predecessor_hash`, `hash`, `trace_parent`, `trace_state`, and `signature`.

The `data` field is stored as a JSON string. Use Polars' JSON functions to extract values:

```rust
use polars::prelude::*;

// Filter for a specific event type and extract data fields
let result = df.lazy()
  .filter(col("type").eq(lit("io.eventsourcingdb.library.book-acquired")))
  .with_columns([
    col("data").str().json_path_match("$.title")?.alias("title"),
    col("data").str().json_path_match("$.author")?.alias("author"),
  ])
  .collect()?;

println!("{}", result);
```

### Observing Events

To observe all events of a subject, call the `observe_events` function with the subject and an options object. Set the `recursive` option to `false`. This ensures that only events of the given subject are returned, not events of nested subjects.

The function returns a stream from which you can retrieve one event at a time:

```rust
let result = client
  .observe_events("/books/42", Some(
    ObserveEventsOptions {
      recursive: false,
      from_latest_event: None,
      lower_bound: None,
    }
  ))
  .await;

match result {
  Err(err) => // ...
  Ok(mut stream) => {
    while let Some(event) = stream.next().await {
      // ...
    }
  }
}
```

#### Observing From Subjects Recursively

If you want to observe not only all the events of a subject, but also the events of all nested subjects, set the `recursive` option to `true`:

```rust
let result = client
  .observe_events("/books/42", Some(
    ObserveEventsOptions {
      recursive: true,
      ..Default::default(),
    }
  ))
  .await
```

This also allows you to observe *all* events ever written. To do so, provide `/` as the subject and set `recursive` to `true`, since all subjects are nested under the root subject.

#### Specifying Bounds

Sometimes you do not want to observe all events, but only a range of events. For that, you can specify the `lower_bound` option.

Specify the ID and whether to include or exclude it:

```rust
let result = client
  .observe_events("/books/42", Some(
    ObserveEventsOptions {
      recursive: false,
      lower_bound: Some(Bound {
        bound_type: BoundType::Inclusive,
        id: "100",
      }),
      ..Default::default(),
    }
  ))
  .await
```

#### Starting From the Latest Event of a Given Type

To observe starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists.

Possible options are `WaitForEvent`, which waits for an event of the given type to happen, or `ObserveEverything`, which effectively behaves as if `from_latest_event` was not specified:

```rust
let result = client
  .observe_events("/books/42", Some(
    ObserveEventsOptions {
      recursive: false,
      from_latest_event: Some(
        ObserveFromLatestEventOptions {
          subject: "/books/42",
          ty: "io.eventsourcingdb.library.book-borrowed",
          if_event_is_missing: ObserveEventMissingStrategy::ObserveEverything,
        }
      )
      ..Default::default(),
    }
  ))
  .await
```

*Note that `from_latest_event` and `lower_bound` can not be provided at the same time.*

#### Aborting Observing

The observe will automatically be canceled if the stream is dropped from scope.

### Registering an Event Schema

To register an event schema, call the `register_event_schema` function and hand over an event type and the desired schema:

```rust
client.register_event_schema(
  "io.eventsourcingdb.library.book-acquired",
  &json!({
    "type": "object",
    "properties": {
      "title":  { "type": "string" },
      "author": { "type": "string" },
      "isbn":   { "type": "string" },
    },
    "required": [
      "title",
      "author",
      "isbn",
    ],
    "additionalProperties": false,
  }),
).await;
```

### Listing Subjects

To list all subjects, call the `list_subjects` function with `/` as the base subject. The function returns a stream from which you can retrieve one subject at a time:

```rust
let result = client.list_subjects(Some("/")).await;
match result {
  Ok(subjects) => // ...
  Err(err) => // ...
}
```

If you only want to list subjects within a specific branch, provide the desired base subject instead:

```rust
let result = client.list_subjects("/books").await;
```

### Listing Event Types

To list all event types, call the `list_event_types` function. The function returns a stream from which you can retrieve one event type at a time:

```rust
let result = client.list_event_types().await;
match result {
  Ok(event_types) => // ...
  Err(err) => // ...
}
```

### Listing a Specific Event Type

To list a specific event type, call the `read_event_type` function. The function returns the detailed event type, which includes the schema:

```rust
let event_type_name = "io.eventsourcingdb.library.book-acquired";
let result = client.read_event_type(event_type_name).await;
match result {
  Ok(event_type) => // ...
  Err(err) => // ...
}
```

### Verifying an Event's Hash

To verify the integrity of an event, call the `verify_hash` function on the event instance. This recomputes the event's hash locally and compares it to the hash stored in the event. If the hashes differ, the function returns an error:

```rust
let result = event.verify_hash();
match result {
  Ok(()) => // ...
  Err(err) => // ...
}
```

*Note that this only verifies the hash. If you also want to verify the signature, you can skip this step and call `verify_signature` directly, which performs a hash verification internally.*

### Verifying an Event's Signature

To verify the authenticity of an event, call the `verify_signature` function on the event instance. This requires the public key that matches the private key used for signing on the server.

The function first verifies the event's hash, and then checks the signature. If any verification step fails, it returns an error:

```rust
use ed25519_dalek::VerifyingKey;

// ...

let verification_key = // public key as VerifyingKey

let result = event.verify_signature(&verification_key);
match result {
  Ok(()) => // ...
  Err(err) => // ...
}
```

### Using Testcontainers

Call the `Container::start_default()` function, get a client, and run your test code:

```rust
let container  = Container::start_default().await.unwrap();
let client = container.get_client().await.unwrap();
```

#### Configuring the Container Instance

By default, `Container` uses the `latest` tag of the official EventSourcingDB Docker image. To change that use the provided builder and call the `with_image_tag` function.

```rust
let container = Container::builder()
  .with_image_tag("1.0.0")
  .build()
  .await.unwrap()
```

Similarly, you can configure the port to use and the API token. Call the `with_port` or the `with_api_token` function respectively:

```rust
let container = Container::builder()
  .with_port(4000)
  .with_api_token("secret")
  .build()
  .await.unwrap()
```

If you want to sign events, call the `with_signing_key` function. This generates a new signing and verification key pair inside the container:

```rust
let container = Container::builder()
  .with_signing_key()
  .build()
  .await.unwrap()
```

You can retrieve the private key (for signing) and the public key (for verifying signatures) once the container has been started:

```rust
let signing_key = container.get_signing_key().await?;
let verification_key = container.get_verification_key().await?;
```

The `signing_key` can be used when configuring the container to sign outgoing events. The `verification_key` can be passed to `verify_signature` when verifying events read from the database.

#### Configuring the Client Manually

In case you need to set up the client yourself, use the following functions to get details on the container:

- `get_host()` returns the host name
- `get_mapped_port()` returns the port
- `get_base_url()` returns the full URL of the container
- `get_api_token()` returns the API token