eventsourcingdb 1.10.0 → 2.0.1
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 +198 -24
- package/dist/Client.d.ts +2 -1
- package/dist/Client.js +79 -439
- package/dist/DbApiError.d.ts +17 -0
- package/dist/DbApiError.js +24 -0
- package/dist/ErrorCode.d.ts +68 -0
- package/dist/ErrorCode.js +70 -0
- package/dist/EventType.d.ts +2 -2
- package/dist/OutcomeUnknownError.d.ts +6 -0
- package/dist/OutcomeUnknownError.js +9 -0
- package/dist/ReadSubjectsOptions.d.ts +4 -0
- package/dist/Transport.d.ts +25 -0
- package/dist/detectApiVersion.d.ts +7 -0
- package/dist/detectApiVersion.js +47 -0
- package/dist/http/assertEventSourcingDb.d.ts +2 -0
- package/dist/http/assertEventSourcingDb.js +14 -0
- package/dist/http/createDbApiErrorFromResponse.d.ts +4 -0
- package/dist/http/createDbApiErrorFromResponse.js +20 -0
- package/dist/http/createStreamEndedError.d.ts +2 -0
- package/dist/http/createStreamEndedError.js +6 -0
- package/dist/http/isAbortError.d.ts +2 -0
- package/dist/http/isAbortError.js +2 -0
- package/dist/http/parseRetryAfter.d.ts +2 -0
- package/dist/http/parseRetryAfter.js +14 -0
- package/dist/http/runWrite.d.ts +6 -0
- package/dist/http/runWrite.js +25 -0
- package/dist/http/trackSending.d.ts +7 -0
- package/dist/http/trackSending.js +46 -0
- package/dist/http/waitUnlessAborted.d.ts +2 -0
- package/dist/http/waitUnlessAborted.js +21 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -2
- package/dist/stall/StallGuard.d.ts +0 -1
- package/dist/stall/StallGuard.js +3 -9
- package/dist/stall/streamWithStallGuard.d.ts +6 -0
- package/dist/stall/streamWithStallGuard.js +25 -0
- package/dist/v1/V1Transport.d.ts +23 -0
- package/dist/v1/V1Transport.js +346 -0
- package/dist/v1/getV1ErrorCode.d.ts +4 -0
- package/dist/v1/getV1ErrorCode.js +65 -0
- package/dist/v1/isV1Refusal.d.ts +3 -0
- package/dist/v1/isV1Refusal.js +8 -0
- package/dist/v1/normalizeSubject.d.ts +2 -0
- package/dist/v1/normalizeSubject.js +6 -0
- package/dist/v1/stream/StreamRow.js +1 -0
- package/dist/v1/stream/StreamSubject.js +1 -0
- package/dist/{stream → v1/stream}/isStreamCloudEvent.js +5 -5
- package/dist/{stream → v1/stream}/isStreamError.js +2 -2
- package/dist/{stream → v1/stream}/isStreamEventType.js +3 -3
- package/dist/{stream → v1/stream}/isStreamHeartbeat.js +1 -1
- package/dist/{stream → v1/stream}/isStreamRow.js +1 -1
- package/dist/{stream → v1/stream}/isStreamSubject.js +2 -2
- package/dist/v2/V2Event.d.ts +16 -0
- package/dist/v2/V2Event.js +1 -0
- package/dist/v2/V2Item.d.ts +6 -0
- package/dist/v2/V2Item.js +1 -0
- package/dist/v2/V2Transport.d.ts +23 -0
- package/dist/v2/V2Transport.js +302 -0
- package/dist/v2/convertV2Item.d.ts +6 -0
- package/dist/v2/convertV2Item.js +52 -0
- package/dist/v2/isV2Refusal.d.ts +3 -0
- package/dist/v2/isV2Refusal.js +12 -0
- package/dist/v2/parseV2Item.d.ts +3 -0
- package/dist/v2/parseV2Item.js +30 -0
- package/dist/v2/readV2Answer.d.ts +4 -0
- package/dist/v2/readV2Answer.js +14 -0
- package/dist/v2/readV2Stream.d.ts +8 -0
- package/dist/v2/readV2Stream.js +34 -0
- package/dist/v2/toV2RequestBody.d.ts +8 -0
- package/dist/v2/toV2RequestBody.js +46 -0
- package/package.json +1 -1
- package/dist/HeartbeatTimeoutError.d.ts +0 -4
- package/dist/HeartbeatTimeoutError.js +0 -9
- /package/dist/{stream/StreamCloudEvent.js → ReadSubjectsOptions.js} +0 -0
- /package/dist/{stream/StreamError.js → Transport.js} +0 -0
- /package/dist/{stream → v1/stream}/StreamCloudEvent.d.ts +0 -0
- /package/dist/{stream/StreamEventType.js → v1/stream/StreamCloudEvent.js} +0 -0
- /package/dist/{stream → v1/stream}/StreamError.d.ts +0 -0
- /package/dist/{stream/StreamHeartbeat.js → v1/stream/StreamError.js} +0 -0
- /package/dist/{stream → v1/stream}/StreamEventType.d.ts +0 -0
- /package/dist/{stream/StreamRow.js → v1/stream/StreamEventType.js} +0 -0
- /package/dist/{stream → v1/stream}/StreamHeartbeat.d.ts +0 -0
- /package/dist/{stream/StreamSubject.js → v1/stream/StreamHeartbeat.js} +0 -0
- /package/dist/{stream → v1/stream}/StreamRow.d.ts +0 -0
- /package/dist/{stream → v1/stream}/StreamSubject.d.ts +0 -0
- /package/dist/{stream → v1/stream}/isStreamCloudEvent.d.ts +0 -0
- /package/dist/{stream → v1/stream}/isStreamError.d.ts +0 -0
- /package/dist/{stream → v1/stream}/isStreamEventType.d.ts +0 -0
- /package/dist/{stream → v1/stream}/isStreamHeartbeat.d.ts +0 -0
- /package/dist/{stream → v1/stream}/isStreamRow.d.ts +0 -0
- /package/dist/{stream → v1/stream}/isStreamSubject.d.ts +0 -0
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ For more information on EventSourcingDB, see its [official documentation](https:
|
|
|
8
8
|
|
|
9
9
|
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).
|
|
10
10
|
|
|
11
|
+
If you are upgrading from version 1 of this client SDK, see [Upgrading From Version 1](#upgrading-from-version-1).
|
|
12
|
+
|
|
11
13
|
## Getting Started
|
|
12
14
|
|
|
13
15
|
Install the client SDK:
|
|
@@ -41,6 +43,12 @@ If you want to verify the API token, call `verifyApiToken`. If the token is inva
|
|
|
41
43
|
await client.verifyApiToken();
|
|
42
44
|
```
|
|
43
45
|
|
|
46
|
+
### Choosing the API
|
|
47
|
+
|
|
48
|
+
EventSourcingDB offers its v2 API since version 1.3, while older versions offer only the v1 API. This client SDK works with both. Creating a client does not send a request. Instead, the first request of a client asks the server whether it offers the v2 API, by sending a `GET` request to `/api/v2/system/ping`, and the client uses the v2 API if it does, and the v1 API otherwise. The client keeps the answer for its lifetime, so every later request goes straight to the API it found. If the client cannot find out, for example because the server is not reachable, the first request fails with that error, and the next request asks again.
|
|
49
|
+
|
|
50
|
+
Through the v2 API, all functions that read use the HTTP method `QUERY` ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)), which, unlike `GET`, carries a request body. If there is a proxy or a load balancer in front of EventSourcingDB, it has to let `QUERY` requests through. There is no fallback to `POST`.
|
|
51
|
+
|
|
44
52
|
### Handling Stalled Connections
|
|
45
53
|
|
|
46
54
|
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.
|
|
@@ -69,19 +77,17 @@ const client = new Client(url, apiToken, {
|
|
|
69
77
|
|
|
70
78
|
*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
79
|
|
|
72
|
-
*Note that `
|
|
80
|
+
*Note that a write that stalls, a call of `writeEvents` or `registerEventSchema`, throws an `OutcomeUnknownError` whose `cause` is the `ConnectionStalledError`, since the write may have been stored nonetheless, see [Knowing Whether a Write Was Stored](#knowing-whether-a-write-was-stored).*
|
|
73
81
|
|
|
74
82
|
### Aborting a Request
|
|
75
83
|
|
|
76
|
-
To abort a call of `ping`, `verifyApiToken`,
|
|
84
|
+
Every function that sends a request takes an abort signal as last argument. To abort a call of `ping`, `verifyApiToken`, or `readEventType`, hand over an abort signal, 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
85
|
|
|
78
86
|
```typescript
|
|
79
87
|
const controller = new AbortController();
|
|
80
88
|
|
|
81
89
|
try {
|
|
82
|
-
await client.
|
|
83
|
-
// events
|
|
84
|
-
], [], controller.signal);
|
|
90
|
+
await client.readEventType('io.eventsourcingdb.library.book-acquired', controller.signal);
|
|
85
91
|
} catch (error) {
|
|
86
92
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
87
93
|
// ...
|
|
@@ -89,12 +95,67 @@ try {
|
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
// Somewhere else, abort the controller, which will cause
|
|
92
|
-
//
|
|
98
|
+
// readEventType to throw.
|
|
93
99
|
controller.abort();
|
|
94
100
|
```
|
|
95
101
|
|
|
102
|
+
Aborting `writeEvents` or `registerEventSchema` once the write has been sent does not undo it, so the write may have been stored nonetheless. The call then throws an `OutcomeUnknownError` whose `cause` is the reason of the abort, see [Knowing Whether a Write Was Stored](#knowing-whether-a-write-was-stored). If the write has not been sent yet, because the signal was aborted before or while the client SDK still establishes the connection, the call throws the reason of the abort itself.
|
|
103
|
+
|
|
96
104
|
*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
105
|
|
|
106
|
+
### Handling Errors
|
|
107
|
+
|
|
108
|
+
Every error that the server reports throws a `DbApiError`, whether the server describes it in its answer, answers with an HTTP status other than `200`, or reports it while a stream is running. A `DbApiError` has the following fields:
|
|
109
|
+
|
|
110
|
+
- `code` is the URL that names the error, exactly as the server sends it, for example `https://docs.eventsourcingdb.io/errors/precondition-violated`. It doubles as a link to the documentation of the error. It is `undefined` for an error without a code.
|
|
111
|
+
- `description` is the text that describes the error.
|
|
112
|
+
- `details` is an object with details on the error, such as the index of the precondition that does not hold. It is empty for an error without details.
|
|
113
|
+
- `status` is the HTTP status of the answer, which is `200` for an error that the server describes in its answer.
|
|
114
|
+
- `retryAfter` is the number of seconds after which the server accepts requests again, which only an answer with the HTTP status `429` may name. The client SDK never repeats a request on its own.
|
|
115
|
+
|
|
116
|
+
To tell errors apart, compare the `code` with the constants of `ErrorCode`, which has a constant for each code that the server knows:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { DbApiError, ErrorCode, isSubjectPristine } from 'eventsourcingdb';
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await client.writeEvents([
|
|
123
|
+
// events
|
|
124
|
+
], [
|
|
125
|
+
isSubjectPristine('/books/42')
|
|
126
|
+
]);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof DbApiError && error.code === ErrorCode.preconditionViolated) {
|
|
129
|
+
// ...
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The v1 API does not send codes. For the errors of the v1 API that clearly match a code of the v2 API, the client SDK sets that code, so that the same code works with both APIs: `preconditionViolated`, `capacityExceeded`, `eventSchemaViolated`, and `licenseInvalid` for writing events, `eventSchemaAlreadyRegistered` and `eventSchemaViolated` for registering an event schema, `rangeUnsatisfiable` for reading events, `eventTypeUnknown` for reading an event type, and `storeReadOnly` for every request. Every other error of the v1 API has no code.
|
|
135
|
+
|
|
136
|
+
A stream that the server ends with an end item, which every stream of the v2 API except observing does, throws an error if it breaks off before that item. Observing never ends on its own, so a stream of observed events that the server closes without reporting an error throws the same error, with both APIs.
|
|
137
|
+
|
|
138
|
+
#### Knowing Whether a Write Was Stored
|
|
139
|
+
|
|
140
|
+
EventSourcingDB never stops a write halfway, so a write whose answer is lost may have been stored nonetheless. If `writeEvents` or `registerEventSchema` fails once the write may have reached the server, it throws an `OutcomeUnknownError`, whose `cause` tells what went wrong, for example a `ConnectionStalledError`, a broken connection, an abort after sending, a stream that broke off, or an answer with the HTTP status `500`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { OutcomeUnknownError } from 'eventsourcingdb';
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await client.writeEvents([
|
|
147
|
+
// events
|
|
148
|
+
]);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (error instanceof OutcomeUnknownError) {
|
|
151
|
+
// Find out whether the events were stored, for example by
|
|
152
|
+
// reading them, before writing them again.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
A write certainly was not stored, and throws the error itself, only if its request never went out, for example since the client SDK could not establish a connection, or since the write was aborted or stalled while connecting, or if the server refused the write before storing anything. A request counts as gone out once its headers have been sent. The server refuses a write with every `DbApiError` except one with the code `ErrorCode.storeFailed`, and, through the v2 API, with the HTTP status `400`, `401`, `408`, `413`, `429`, or `503`, or, through the v1 API, with an HTTP status of `4xx`, `503`, or `507`. A write that the server refuses with the HTTP status `429` may be sent again after `retryAfter` seconds.
|
|
158
|
+
|
|
98
159
|
### Writing Events
|
|
99
160
|
|
|
100
161
|
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.
|
|
@@ -316,10 +377,10 @@ controller.abort();
|
|
|
316
377
|
|
|
317
378
|
#### Detecting a Stalled Connection
|
|
318
379
|
|
|
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 `
|
|
380
|
+
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 `ConnectionStalledError`, see [Handling Stalled Connections](#handling-stalled-connections):
|
|
320
381
|
|
|
321
382
|
```typescript
|
|
322
|
-
import {
|
|
383
|
+
import { ConnectionStalledError } from 'eventsourcingdb';
|
|
323
384
|
|
|
324
385
|
try {
|
|
325
386
|
for await (const row of client.runEventQlQuery(`
|
|
@@ -329,13 +390,13 @@ try {
|
|
|
329
390
|
// ...
|
|
330
391
|
}
|
|
331
392
|
} catch (error) {
|
|
332
|
-
if (error instanceof
|
|
393
|
+
if (error instanceof ConnectionStalledError) {
|
|
333
394
|
// ...
|
|
334
395
|
}
|
|
335
396
|
}
|
|
336
397
|
```
|
|
337
398
|
|
|
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 `
|
|
399
|
+
*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 `ConnectionStalledError`.*
|
|
339
400
|
|
|
340
401
|
### Observing Events
|
|
341
402
|
|
|
@@ -351,6 +412,8 @@ for await (const event of client.observeEvents('/books/42', {
|
|
|
351
412
|
}
|
|
352
413
|
```
|
|
353
414
|
|
|
415
|
+
*Note that observing never ends on its own. If the server closes the stream without reporting an error, the iterator throws an error, so that a failure does not go unnoticed. To stop observing, use `break`, `return`, or an abort signal, see [Aborting Observing](#aborting-observing).*
|
|
416
|
+
|
|
354
417
|
#### Observing From Subjects Recursively
|
|
355
418
|
|
|
356
419
|
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`:
|
|
@@ -423,10 +486,10 @@ controller.abort();
|
|
|
423
486
|
|
|
424
487
|
#### Detecting a Stalled Connection
|
|
425
488
|
|
|
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 `
|
|
489
|
+
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 `ConnectionStalledError`, see [Handling Stalled Connections](#handling-stalled-connections):
|
|
427
490
|
|
|
428
491
|
```typescript
|
|
429
|
-
import {
|
|
492
|
+
import { ConnectionStalledError } from 'eventsourcingdb';
|
|
430
493
|
|
|
431
494
|
try {
|
|
432
495
|
for await (const event of client.observeEvents('/books/42', {
|
|
@@ -435,13 +498,13 @@ try {
|
|
|
435
498
|
// ...
|
|
436
499
|
}
|
|
437
500
|
} catch (error) {
|
|
438
|
-
if (error instanceof
|
|
501
|
+
if (error instanceof ConnectionStalledError) {
|
|
439
502
|
// ...
|
|
440
503
|
}
|
|
441
504
|
}
|
|
442
505
|
```
|
|
443
506
|
|
|
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 `
|
|
507
|
+
*Note that the time you spend handling an event does not count towards the stall timeout, and that aborting observing does not cause a `ConnectionStalledError`.*
|
|
445
508
|
|
|
446
509
|
### Registering an Event Schema
|
|
447
510
|
|
|
@@ -464,20 +527,36 @@ await client.registerEventSchema('io.eventsourcingdb.library.book-acquired', {
|
|
|
464
527
|
});
|
|
465
528
|
```
|
|
466
529
|
|
|
530
|
+
If a schema is already registered for the event type, the function throws a `DbApiError` with the code `ErrorCode.eventSchemaAlreadyRegistered`, since a schema can be neither replaced nor removed.
|
|
531
|
+
|
|
467
532
|
### Listing Subjects
|
|
468
533
|
|
|
469
|
-
To list
|
|
534
|
+
To list subjects, call the `readSubjects` function with a subject as the first argument and an options object as the second argument. The `recursive` option is required. Set it to `true` to list the subject together with all its nested subjects, so that `/` lists all subjects. The function returns an asynchronous iterator, which you can use e.g. inside a `for await` loop:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
for await (const subject of client.readSubjects('/', {
|
|
538
|
+
recursive: true
|
|
539
|
+
})) {
|
|
540
|
+
// ...
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
If you only want to list subjects within a specific branch, provide the desired subject instead:
|
|
470
545
|
|
|
471
546
|
```typescript
|
|
472
|
-
for await (const subject of client.readSubjects('/'
|
|
547
|
+
for await (const subject of client.readSubjects('/books', {
|
|
548
|
+
recursive: true
|
|
549
|
+
})) {
|
|
473
550
|
// ...
|
|
474
551
|
}
|
|
475
552
|
```
|
|
476
553
|
|
|
477
|
-
|
|
554
|
+
To find out whether a subject exists, set the `recursive` option to `false`. The iterator then returns only the subject itself, if it exists:
|
|
478
555
|
|
|
479
556
|
```typescript
|
|
480
|
-
for await (const subject of client.readSubjects('/books'
|
|
557
|
+
for await (const subject of client.readSubjects('/books/42', {
|
|
558
|
+
recursive: false
|
|
559
|
+
})) {
|
|
481
560
|
// ...
|
|
482
561
|
}
|
|
483
562
|
```
|
|
@@ -486,14 +565,14 @@ for await (const subject of client.readSubjects('/books')) {
|
|
|
486
565
|
|
|
487
566
|
If you need to abort listing use `break` or `return` within the `for await` loop. However, this only works if there is currently an iteration going on.
|
|
488
567
|
|
|
489
|
-
To abort listing independently of that, hand over an abort signal as
|
|
568
|
+
To abort listing independently of that, hand over an abort signal as third argument when calling `readSubjects`, and abort the appropriate `AbortController`:
|
|
490
569
|
|
|
491
570
|
```typescript
|
|
492
571
|
const controller = new AbortController();
|
|
493
572
|
|
|
494
|
-
for await (const subject of client.readSubjects(
|
|
495
|
-
|
|
496
|
-
) {
|
|
573
|
+
for await (const subject of client.readSubjects('/', {
|
|
574
|
+
recursive: true
|
|
575
|
+
}, controller.signal)) {
|
|
497
576
|
// ...
|
|
498
577
|
}
|
|
499
578
|
|
|
@@ -512,6 +591,8 @@ for await (const eventType of client.readEventTypes()) {
|
|
|
512
591
|
}
|
|
513
592
|
```
|
|
514
593
|
|
|
594
|
+
Each event type has the fields `type`, which names it, `hasEvents`, which tells whether there are events of this type, and `schema`, which is the registered schema, if there is one.
|
|
595
|
+
|
|
515
596
|
#### Aborting Listing
|
|
516
597
|
|
|
517
598
|
If you need to abort listing use `break` or `return` within the `for await` loop. However, this only works if there is currently an iteration going on.
|
|
@@ -521,7 +602,7 @@ To abort listing independently of that, hand over an abort signal as argument wh
|
|
|
521
602
|
```typescript
|
|
522
603
|
const controller = new AbortController();
|
|
523
604
|
|
|
524
|
-
for await (const eventType of client.readEventTypes()) {
|
|
605
|
+
for await (const eventType of client.readEventTypes(controller.signal)) {
|
|
525
606
|
// ...
|
|
526
607
|
}
|
|
527
608
|
|
|
@@ -535,9 +616,11 @@ controller.abort();
|
|
|
535
616
|
To list a specific event type, call the `readEventType` function with the event type as an argument. The function returns the detailed event type, which includes the schema:
|
|
536
617
|
|
|
537
618
|
```typescript
|
|
538
|
-
eventType = await client.readEventType(
|
|
619
|
+
const eventType = await client.readEventType('io.eventsourcingdb.library.book-acquired');
|
|
539
620
|
```
|
|
540
621
|
|
|
622
|
+
If there are neither events of this type nor a registered schema, the function throws a `DbApiError` with the code `ErrorCode.eventTypeUnknown`.
|
|
623
|
+
|
|
541
624
|
### Verifying an Event's Hash
|
|
542
625
|
|
|
543
626
|
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`:
|
|
@@ -641,3 +724,94 @@ In case you need to set up the client yourself, use the following functions to g
|
|
|
641
724
|
- `getMappedPort()` returns the port
|
|
642
725
|
- `getBaseUrl()` returns the full URL of the container
|
|
643
726
|
- `getApiToken()` returns the API token
|
|
727
|
+
|
|
728
|
+
## Upgrading From Version 1
|
|
729
|
+
|
|
730
|
+
Version 2 of this client SDK speaks the v2 API of EventSourcingDB 1.3 as well as the v1 API of older versions, see [Choosing the API](#choosing-the-api). It brings the following breaking changes:
|
|
731
|
+
|
|
732
|
+
- **Event types:** An event type names its type in the field `type` instead of `eventType`, and tells whether it has events in the field `hasEvents` instead of `isPhantom`, which meant the opposite.
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
// Before
|
|
736
|
+
if (!eventType.isPhantom) {
|
|
737
|
+
console.log(eventType.eventType);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// After
|
|
741
|
+
if (eventType.hasEvents) {
|
|
742
|
+
console.log(eventType.type);
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
- **Listing subjects:** `readSubjects` takes the subject and an options object with the required `recursive` option, instead of a base subject alone. Calling it without the option throws an error. To keep the previous behavior, set `recursive` to `true`. The abort signal moves from the second to the third argument.
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
// Before
|
|
750
|
+
client.readSubjects('/books', signal);
|
|
751
|
+
|
|
752
|
+
// After
|
|
753
|
+
client.readSubjects('/books', { recursive: true }, signal);
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
- **Errors of the server:** Every error that the server reports throws a `DbApiError` with `code`, `description`, `details`, `status`, and `retryAfter`, instead of an `Error` whose message names the HTTP status, see [Handling Errors](#handling-errors).
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
// Before
|
|
760
|
+
if (error instanceof Error && error.message.includes("'409'")) {
|
|
761
|
+
// ...
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// After
|
|
765
|
+
if (error instanceof DbApiError && error.code === ErrorCode.preconditionViolated) {
|
|
766
|
+
// ...
|
|
767
|
+
}
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
- **Stalled streams:** `HeartbeatTimeoutError` is gone. A stream in which neither an item nor a heartbeat arrives in time throws a `ConnectionStalledError`, as every other request that stalls does, whose `cause` has the `code` `UND_ERR_BODY_TIMEOUT`.
|
|
771
|
+
|
|
772
|
+
```typescript
|
|
773
|
+
// Before
|
|
774
|
+
if (error instanceof HeartbeatTimeoutError) {
|
|
775
|
+
// ...
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// After
|
|
779
|
+
if (error instanceof ConnectionStalledError) {
|
|
780
|
+
// ...
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
- **Failed writes:** `writeEvents` and `registerEventSchema` throw an `OutcomeUnknownError`, whose `cause` is the original error, whenever the write may have been stored nonetheless, see [Knowing Whether a Write Was Stored](#knowing-whether-a-write-was-stored). This includes aborting the signal once the write has been sent, which threw the reason of the abort before, and a write that stalls, which threw a `ConnectionStalledError` before.
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// Before
|
|
788
|
+
if (error instanceof ConnectionStalledError) {
|
|
789
|
+
// ...
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// After
|
|
793
|
+
if (error instanceof OutcomeUnknownError && error.cause instanceof ConnectionStalledError) {
|
|
794
|
+
// ...
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
- **Observing that ends:** A stream of observed events that the server closes without reporting an error throws an error, instead of ending the iteration quietly. Stopping with `break`, `return`, or an abort signal still ends it without an error.
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
// Before
|
|
802
|
+
for await (const event of client.observeEvents('/books', { recursive: true })) {
|
|
803
|
+
// ...
|
|
804
|
+
}
|
|
805
|
+
// Reached once the server closed the stream.
|
|
806
|
+
|
|
807
|
+
// After
|
|
808
|
+
try {
|
|
809
|
+
for await (const event of client.observeEvents('/books', { recursive: true })) {
|
|
810
|
+
// ...
|
|
811
|
+
}
|
|
812
|
+
} catch (error) {
|
|
813
|
+
// Reached once the server closed the stream.
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
- **Reading through a proxy:** Against EventSourcingDB 1.3 and later, the functions that read use the HTTP method `QUERY` instead of `POST`, which a proxy in front of the database has to let through, see [Choosing the API](#choosing-the-api).
|
package/dist/Client.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { EventType } from './EventType.js';
|
|
|
5
5
|
import type { ObserveEventsOptions } from './ObserveEventsOptions.js';
|
|
6
6
|
import type { Precondition } from './Precondition.js';
|
|
7
7
|
import type { ReadEventsOptions } from './ReadEventsOptions.js';
|
|
8
|
+
import type { ReadSubjectsOptions } from './ReadSubjectsOptions.js';
|
|
8
9
|
declare class Client {
|
|
9
10
|
#private;
|
|
10
11
|
constructor(url: URL, apiToken: string, options?: ClientOptions);
|
|
@@ -15,7 +16,7 @@ declare class Client {
|
|
|
15
16
|
runEventQlQuery(query: string, signal?: AbortSignal): AsyncGenerator<unknown, void, void>;
|
|
16
17
|
observeEvents(subject: string, options: ObserveEventsOptions, signal?: AbortSignal): AsyncGenerator<Event, void, void>;
|
|
17
18
|
registerEventSchema(eventType: string, schema: Record<string, unknown>, signal?: AbortSignal): Promise<void>;
|
|
18
|
-
readSubjects(
|
|
19
|
+
readSubjects(subject: string, options: ReadSubjectsOptions, signal?: AbortSignal): AsyncGenerator<string, void, void>;
|
|
19
20
|
readEventType(eventType: string, signal?: AbortSignal): Promise<EventType>;
|
|
20
21
|
readEventTypes(signal?: AbortSignal): AsyncGenerator<EventType, void, void>;
|
|
21
22
|
}
|