eventsourcingdb 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +77 -6
  2. package/dist/Client.d.ts +7 -6
  3. package/dist/Client.js +189 -204
  4. package/dist/ClientOptions.d.ts +4 -0
  5. package/dist/ClientOptions.js +1 -0
  6. package/dist/ConnectionStalledError.d.ts +4 -0
  7. package/dist/ConnectionStalledError.js +13 -0
  8. package/dist/Event.d.ts +2 -1
  9. package/dist/Event.js +58 -11
  10. package/dist/HashVerificationFailedError.d.ts +4 -0
  11. package/dist/HashVerificationFailedError.js +7 -0
  12. package/dist/HeartbeatTimeoutError.d.ts +1 -1
  13. package/dist/HeartbeatTimeoutError.js +4 -2
  14. package/dist/SignatureVerificationFailedError.d.ts +4 -0
  15. package/dist/SignatureVerificationFailedError.js +7 -0
  16. package/dist/UnsupportedHashVersionError.d.ts +4 -0
  17. package/dist/UnsupportedHashVersionError.js +7 -0
  18. package/dist/convertCloudEventToEvent.d.ts +1 -1
  19. package/dist/convertCloudEventToEvent.js +6 -1
  20. package/dist/getImageTag.d.ts +2 -0
  21. package/dist/getImageTag.js +13 -0
  22. package/dist/index.d.ts +7 -2
  23. package/dist/index.js +5 -1
  24. package/dist/json/getRawJsonElements.d.ts +2 -0
  25. package/dist/json/getRawJsonElements.js +22 -0
  26. package/dist/json/getRawJsonMember.d.ts +2 -0
  27. package/dist/json/getRawJsonMember.js +29 -0
  28. package/dist/json/scanJson.d.ts +4 -0
  29. package/dist/json/scanJson.js +65 -0
  30. package/dist/ndjson/readNdJsonStream.d.ts +7 -1
  31. package/dist/ndjson/readNdJsonStream.js +7 -28
  32. package/dist/stall/StallGuard.d.ts +14 -0
  33. package/dist/stall/StallGuard.js +83 -0
  34. package/dist/stall/createFetchTimeoutError.d.ts +2 -0
  35. package/dist/stall/createFetchTimeoutError.js +16 -0
  36. package/dist/stall/defaultStallTimeout.d.ts +3 -0
  37. package/dist/stall/defaultStallTimeout.js +6 -0
  38. package/dist/stall/formatDuration.d.ts +2 -0
  39. package/dist/stall/formatDuration.js +9 -0
  40. package/dist/stall/readResponseText.d.ts +3 -0
  41. package/dist/stall/readResponseText.js +26 -0
  42. package/dist/stall/runWithStallGuard.d.ts +6 -0
  43. package/dist/stall/runWithStallGuard.js +18 -0
  44. package/package.json +4 -4
  45. package/dist/heartbeatTimeout.d.ts +0 -4
  46. package/dist/heartbeatTimeout.js +0 -6
package/README.md CHANGED
@@ -41,6 +41,60 @@ If you want to verify the API token, call `verifyApiToken`. If the token is inva
41
41
  await client.verifyApiToken();
42
42
  ```
43
43
 
44
+ ### Handling Stalled Connections
45
+
46
+ Every request fails once nothing arrives from the server for 30 seconds, for example because the server does not respond, or because a proxy keeps the connection open but no longer passes anything through. The time counts from sending the request until it ends, including connecting and waiting for the response. Everything that arrives starts it anew, so a long stream that keeps receiving events, rows, or heartbeats is never cut off, and the time you spend handling an item of a stream does not count.
47
+
48
+ A request that stalls throws a `ConnectionStalledError`. It extends `TypeError`, since `fetch` throws a `TypeError` when its own timeouts run out, and its `cause` has the `code` that the cause of that `TypeError` has, `UND_ERR_HEADERS_TIMEOUT` while waiting for the response headers and `UND_ERR_BODY_TIMEOUT` afterwards:
49
+
50
+ ```typescript
51
+ import { ConnectionStalledError } from 'eventsourcingdb';
52
+
53
+ try {
54
+ await client.ping();
55
+ } catch (error) {
56
+ if (error instanceof ConnectionStalledError) {
57
+ // ...
58
+ }
59
+ }
60
+ ```
61
+
62
+ To change the 30 seconds, hand over the `stallTimeout` option in milliseconds as third argument when creating the client:
63
+
64
+ ```typescript
65
+ const client = new Client(url, apiToken, {
66
+ stallTimeout: 10_000
67
+ });
68
+ ```
69
+
70
+ *Note that the `fetch` built into Node.js has timeouts of its own, which keep applying: 10 seconds for connecting, 300 seconds for waiting for the response headers, and 300 seconds without data in the response body. If one of them runs out first, the request throws the `TypeError` of `fetch`, with the message `fetch failed` or `terminated`. A `stallTimeout` above these values therefore cannot extend them.*
71
+
72
+ *Note that `runEventQlQuery` and `observeEvents` throw a `HeartbeatTimeoutError` instead once the server has started to respond, see [Running EventQL Queries](#running-eventql-queries) and [Observing Events](#observing-events).*
73
+
74
+ ### Aborting a Request
75
+
76
+ To abort a call of `ping`, `verifyApiToken`, `writeEvents`, `registerEventSchema`, or `readEventType`, hand over an abort signal as last argument, and abort the appropriate `AbortController`. The call then throws the reason of the abort, which is an `AbortError` unless you provide a reason of your own:
77
+
78
+ ```typescript
79
+ const controller = new AbortController();
80
+
81
+ try {
82
+ await client.writeEvents([
83
+ // events
84
+ ], [], controller.signal);
85
+ } catch (error) {
86
+ if (error instanceof DOMException && error.name === 'AbortError') {
87
+ // ...
88
+ }
89
+ }
90
+
91
+ // Somewhere else, abort the controller, which will cause
92
+ // writeEvents to throw.
93
+ controller.abort();
94
+ ```
95
+
96
+ *Note that aborting a function that returns an asynchronous iterator, such as `readEvents`, does not throw, but ends the iteration, see [Aborting Reading](#aborting-reading).*
97
+
44
98
  ### Writing Events
45
99
 
46
100
  Call the `writeEvents` function and hand over an array with one or more events. You do not have to provide all event fields – some are automatically added by the server.
@@ -262,7 +316,7 @@ controller.abort();
262
316
 
263
317
  #### Detecting a Stalled Connection
264
318
 
265
- While a query is running, EventSourcingDB sends a heartbeat every second whenever there is no row to send. If neither a row nor a heartbeat arrives for 30 seconds, for example because a proxy keeps the connection open but no longer passes anything through, the client SDK closes the connection and the iterator throws a `HeartbeatTimeoutError`:
319
+ While a query is running, EventSourcingDB sends a heartbeat every second whenever there is no row to send. If neither a row nor a heartbeat arrives for the stall timeout, 30 seconds by default, for example because a proxy keeps the connection open but no longer passes anything through, the client SDK closes the connection and the iterator throws a `HeartbeatTimeoutError`:
266
320
 
267
321
  ```typescript
268
322
  import { HeartbeatTimeoutError } from 'eventsourcingdb';
@@ -281,7 +335,7 @@ try {
281
335
  }
282
336
  ```
283
337
 
284
- *Note that the time you spend handling a row does not count towards the 30 seconds, and that aborting the query does not cause a `HeartbeatTimeoutError`.*
338
+ *Note that the time you spend handling a row does not count towards the stall timeout, and that aborting the query does not cause a `HeartbeatTimeoutError`. If the server does not start to respond in time, the iterator throws a `ConnectionStalledError`, see [Handling Stalled Connections](#handling-stalled-connections).*
285
339
 
286
340
  ### Observing Events
287
341
 
@@ -369,7 +423,7 @@ controller.abort();
369
423
 
370
424
  #### Detecting a Stalled Connection
371
425
 
372
- While observing, EventSourcingDB sends a heartbeat every second whenever there is no event to send. If neither an event nor a heartbeat arrives for 30 seconds, for example because a proxy keeps the connection open but no longer passes anything through, the client SDK closes the connection and the iterator throws a `HeartbeatTimeoutError`:
426
+ While observing, EventSourcingDB sends a heartbeat every second whenever there is no event to send. If neither an event nor a heartbeat arrives for the stall timeout, 30 seconds by default, for example because a proxy keeps the connection open but no longer passes anything through, the client SDK closes the connection and the iterator throws a `HeartbeatTimeoutError`:
373
427
 
374
428
  ```typescript
375
429
  import { HeartbeatTimeoutError } from 'eventsourcingdb';
@@ -387,7 +441,7 @@ try {
387
441
  }
388
442
  ```
389
443
 
390
- *Note that the time you spend handling an event does not count towards the 30 seconds, and that aborting observing does not cause a `HeartbeatTimeoutError`.*
444
+ *Note that the time you spend handling an event does not count towards the stall timeout, and that aborting observing does not cause a `HeartbeatTimeoutError`. If the server does not start to respond in time, the iterator throws a `ConnectionStalledError`, see [Handling Stalled Connections](#handling-stalled-connections).*
391
445
 
392
446
  ### Registering an Event Schema
393
447
 
@@ -486,19 +540,36 @@ eventType = await client.readEventType("io.eventsourcingdb.library.book-acquired
486
540
 
487
541
  ### Verifying an Event's Hash
488
542
 
489
- To verify the integrity of an event, call the `verifyHash` 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:
543
+ To verify the integrity of an event, call the `verifyHash` 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, or if the event's `data` has been changed since it was received, the function throws a `HashVerificationFailedError`:
490
544
 
491
545
  ```typescript
492
546
  event.verifyHash();
493
547
  ```
494
548
 
549
+ The hash of an event tells its version. A hash that starts with `esdb:hash:v2:` is of version 2, which EventSourcingDB writes since version 1.3. A hash without a prefix is of version 1, which events written before keep. `verifyHash` supports both. If the hash is of a version the client SDK does not know, the function throws an `UnsupportedHashVersionError` that names the version, since such a hash cannot be verified, rather than being invalid:
550
+
551
+ ```typescript
552
+ import { HashVerificationFailedError, UnsupportedHashVersionError } from 'eventsourcingdb';
553
+
554
+ try {
555
+ event.verifyHash();
556
+ } catch (error) {
557
+ if (error instanceof HashVerificationFailedError) {
558
+ // ...
559
+ }
560
+ if (error instanceof UnsupportedHashVersionError) {
561
+ // ...
562
+ }
563
+ }
564
+ ```
565
+
495
566
  *Note that this only verifies the hash. If you also want to verify the signature, you can skip this step and call `verifySignature` directly, which performs a hash verification internally.*
496
567
 
497
568
  ### Verifying an Event's Signature
498
569
 
499
570
  To verify the authenticity of an event, call the `verifySignature` function on the event instance. This requires the public key that matches the private key used for signing on the server.
500
571
 
501
- The function first verifies the event's hash, and then checks the signature. If any verification step fails, it returns an error:
572
+ The function first verifies the event's hash, and then checks the signature. If any verification step fails, it throws an error, for example a `SignatureVerificationFailedError` if the signature is invalid:
502
573
 
503
574
  ```typescript
504
575
  const verificationKey = // an ed25519 public key
package/dist/Client.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ClientOptions } from './ClientOptions.js';
1
2
  import type { Event } from './Event.js';
2
3
  import type { EventCandidate } from './EventCandidate.js';
3
4
  import type { EventType } from './EventType.js';
@@ -6,16 +7,16 @@ import type { Precondition } from './Precondition.js';
6
7
  import type { ReadEventsOptions } from './ReadEventsOptions.js';
7
8
  declare class Client {
8
9
  #private;
9
- constructor(url: URL, apiToken: string);
10
- ping(): Promise<void>;
11
- verifyApiToken(): Promise<void>;
12
- writeEvents(events: EventCandidate[], preconditions?: Precondition[]): Promise<Event[]>;
10
+ constructor(url: URL, apiToken: string, options?: ClientOptions);
11
+ ping(signal?: AbortSignal): Promise<void>;
12
+ verifyApiToken(signal?: AbortSignal): Promise<void>;
13
+ writeEvents(events: EventCandidate[], preconditions?: Precondition[], signal?: AbortSignal): Promise<Event[]>;
13
14
  readEvents(subject: string, options: ReadEventsOptions, signal?: AbortSignal): AsyncGenerator<Event, void, void>;
14
15
  runEventQlQuery(query: string, signal?: AbortSignal): AsyncGenerator<unknown, void, void>;
15
16
  observeEvents(subject: string, options: ObserveEventsOptions, signal?: AbortSignal): AsyncGenerator<Event, void, void>;
16
- registerEventSchema(eventType: string, schema: Record<string, unknown>): Promise<void>;
17
+ registerEventSchema(eventType: string, schema: Record<string, unknown>, signal?: AbortSignal): Promise<void>;
17
18
  readSubjects(baseSubject: string, signal?: AbortSignal): AsyncGenerator<string, void, void>;
18
- readEventType(eventType: string): Promise<EventType>;
19
+ readEventType(eventType: string, signal?: AbortSignal): Promise<EventType>;
19
20
  readEventTypes(signal?: AbortSignal): AsyncGenerator<EventType, void, void>;
20
21
  }
21
22
  export { Client };