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.
- package/README.md +77 -6
- package/dist/Client.d.ts +7 -6
- package/dist/Client.js +189 -204
- package/dist/ClientOptions.d.ts +4 -0
- package/dist/ClientOptions.js +1 -0
- package/dist/ConnectionStalledError.d.ts +4 -0
- package/dist/ConnectionStalledError.js +13 -0
- package/dist/Event.d.ts +2 -1
- package/dist/Event.js +58 -11
- package/dist/HashVerificationFailedError.d.ts +4 -0
- package/dist/HashVerificationFailedError.js +7 -0
- package/dist/HeartbeatTimeoutError.d.ts +1 -1
- package/dist/HeartbeatTimeoutError.js +4 -2
- package/dist/SignatureVerificationFailedError.d.ts +4 -0
- package/dist/SignatureVerificationFailedError.js +7 -0
- package/dist/UnsupportedHashVersionError.d.ts +4 -0
- package/dist/UnsupportedHashVersionError.js +7 -0
- package/dist/convertCloudEventToEvent.d.ts +1 -1
- package/dist/convertCloudEventToEvent.js +6 -1
- package/dist/getImageTag.d.ts +2 -0
- package/dist/getImageTag.js +13 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +5 -1
- package/dist/json/getRawJsonElements.d.ts +2 -0
- package/dist/json/getRawJsonElements.js +22 -0
- package/dist/json/getRawJsonMember.d.ts +2 -0
- package/dist/json/getRawJsonMember.js +29 -0
- package/dist/json/scanJson.d.ts +4 -0
- package/dist/json/scanJson.js +65 -0
- package/dist/ndjson/readNdJsonStream.d.ts +7 -1
- package/dist/ndjson/readNdJsonStream.js +7 -28
- package/dist/stall/StallGuard.d.ts +14 -0
- package/dist/stall/StallGuard.js +83 -0
- package/dist/stall/createFetchTimeoutError.d.ts +2 -0
- package/dist/stall/createFetchTimeoutError.js +16 -0
- package/dist/stall/defaultStallTimeout.d.ts +3 -0
- package/dist/stall/defaultStallTimeout.js +6 -0
- package/dist/stall/formatDuration.d.ts +2 -0
- package/dist/stall/formatDuration.js +9 -0
- package/dist/stall/readResponseText.d.ts +3 -0
- package/dist/stall/readResponseText.js +26 -0
- package/dist/stall/runWithStallGuard.d.ts +6 -0
- package/dist/stall/runWithStallGuard.js +18 -0
- package/package.json +4 -4
- package/dist/heartbeatTimeout.d.ts +0 -4
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 };
|