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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { convertCloudEventToEvent } from '../convertCloudEventToEvent.js';
|
|
2
|
+
import { DbApiError } from '../DbApiError.js';
|
|
3
|
+
import { assertEventSourcingDb } from '../http/assertEventSourcingDb.js';
|
|
4
|
+
import { createDbApiErrorFromResponse } from '../http/createDbApiErrorFromResponse.js';
|
|
5
|
+
import { createStreamEndedError } from '../http/createStreamEndedError.js';
|
|
6
|
+
import { runWrite } from '../http/runWrite.js';
|
|
7
|
+
import { isCloudEvent } from '../isCloudEvent.js';
|
|
8
|
+
import { getRawJsonElements } from '../json/getRawJsonElements.js';
|
|
9
|
+
import { getRawJsonMember } from '../json/getRawJsonMember.js';
|
|
10
|
+
import { readNdJsonStream } from '../ndjson/readNdJsonStream.js';
|
|
11
|
+
import { readResponseText } from '../stall/readResponseText.js';
|
|
12
|
+
import { runWithStallGuard } from '../stall/runWithStallGuard.js';
|
|
13
|
+
import { streamWithStallGuard } from '../stall/streamWithStallGuard.js';
|
|
14
|
+
import { hasShapeOf } from '../types/hasShapeOf.js';
|
|
15
|
+
import { isBoolean } from '../types/isBoolean.js';
|
|
16
|
+
import { isString } from '../types/isString.js';
|
|
17
|
+
import { getV1ErrorCode } from './getV1ErrorCode.js';
|
|
18
|
+
import { isV1Refusal } from './isV1Refusal.js';
|
|
19
|
+
import { normalizeSubject } from './normalizeSubject.js';
|
|
20
|
+
import { isStreamCloudEvent } from './stream/isStreamCloudEvent.js';
|
|
21
|
+
import { isStreamError } from './stream/isStreamError.js';
|
|
22
|
+
import { isStreamEventType } from './stream/isStreamEventType.js';
|
|
23
|
+
import { isStreamHeartbeat } from './stream/isStreamHeartbeat.js';
|
|
24
|
+
import { isStreamRow } from './stream/isStreamRow.js';
|
|
25
|
+
import { isStreamSubject } from './stream/isStreamSubject.js';
|
|
26
|
+
// The v1 API names an event type that has no events a phantom.
|
|
27
|
+
const toEventType = ({ eventType, isPhantom, schema }) => ({
|
|
28
|
+
type: eventType,
|
|
29
|
+
hasEvents: !isPhantom,
|
|
30
|
+
...(schema === undefined ? {} : { schema }),
|
|
31
|
+
});
|
|
32
|
+
// Fails unless the server answered the call of the v1 API with the status 200.
|
|
33
|
+
const assertOk = async (response, stallGuard, call) => {
|
|
34
|
+
assertEventSourcingDb(response);
|
|
35
|
+
if (response.status !== 200) {
|
|
36
|
+
throw await createDbApiErrorFromResponse(response, stallGuard, (status, description) => getV1ErrorCode(call, status, description));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const getStreamBody = async (response, stallGuard, call, action) => {
|
|
40
|
+
await assertOk(response, stallGuard, call);
|
|
41
|
+
if (!response.body) {
|
|
42
|
+
throw new Error(`Failed to ${action}.`);
|
|
43
|
+
}
|
|
44
|
+
return response.body;
|
|
45
|
+
};
|
|
46
|
+
const createStreamError = (error) => new DbApiError({ status: 200, description: error });
|
|
47
|
+
// Sends the requests through the v1 API, which EventSourcingDB 1.2.0 offers.
|
|
48
|
+
class V1Transport {
|
|
49
|
+
#url;
|
|
50
|
+
#apiToken;
|
|
51
|
+
#stallTimeout;
|
|
52
|
+
#getUrl(path) {
|
|
53
|
+
return new URL(path, this.#url).toString();
|
|
54
|
+
}
|
|
55
|
+
constructor({ url, apiToken, stallTimeout }) {
|
|
56
|
+
this.#url = url;
|
|
57
|
+
this.#apiToken = apiToken;
|
|
58
|
+
this.#stallTimeout = stallTimeout;
|
|
59
|
+
}
|
|
60
|
+
async ping(signal) {
|
|
61
|
+
const url = this.#getUrl('/api/v1/ping');
|
|
62
|
+
const stallTimeout = this.#stallTimeout;
|
|
63
|
+
await runWithStallGuard({ stallTimeout, signal }, async (stallGuard) => {
|
|
64
|
+
const response = await stallGuard.send(url, {
|
|
65
|
+
method: 'GET',
|
|
66
|
+
});
|
|
67
|
+
await assertOk(response, stallGuard, 'ping');
|
|
68
|
+
const responseBody = JSON.parse(await readResponseText(response, stallGuard));
|
|
69
|
+
if (!hasShapeOf(responseBody, {
|
|
70
|
+
type: isString,
|
|
71
|
+
})) {
|
|
72
|
+
throw new Error('Failed to parse response.');
|
|
73
|
+
}
|
|
74
|
+
const eventType = 'io.eventsourcingdb.api.ping-received';
|
|
75
|
+
if (responseBody.type !== eventType) {
|
|
76
|
+
throw new Error('Failed to ping.');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async verifyApiToken(signal) {
|
|
81
|
+
const url = this.#getUrl('/api/v1/verify-api-token');
|
|
82
|
+
const stallTimeout = this.#stallTimeout;
|
|
83
|
+
await runWithStallGuard({ stallTimeout, signal }, async (stallGuard) => {
|
|
84
|
+
const response = await stallGuard.send(url, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
authorization: `Bearer ${this.#apiToken}`,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
await assertOk(response, stallGuard, 'verifyApiToken');
|
|
91
|
+
const responseBody = JSON.parse(await readResponseText(response, stallGuard));
|
|
92
|
+
if (!hasShapeOf(responseBody, {
|
|
93
|
+
type: isString,
|
|
94
|
+
})) {
|
|
95
|
+
throw new Error('Failed to parse response.');
|
|
96
|
+
}
|
|
97
|
+
const eventType = 'io.eventsourcingdb.api.api-token-verified';
|
|
98
|
+
if (responseBody.type !== eventType) {
|
|
99
|
+
throw new Error('Failed to verify API token.');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async writeEvents(events, preconditions, signal) {
|
|
104
|
+
const url = this.#getUrl('/api/v1/write-events');
|
|
105
|
+
const stallTimeout = this.#stallTimeout;
|
|
106
|
+
const body = JSON.stringify({
|
|
107
|
+
events,
|
|
108
|
+
preconditions,
|
|
109
|
+
});
|
|
110
|
+
return await runWrite({ signal, isRefusal: isV1Refusal }, async () => await runWithStallGuard({ stallTimeout, signal }, async (stallGuard) => {
|
|
111
|
+
const response = await stallGuard.send(url, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: {
|
|
114
|
+
authorization: `Bearer ${this.#apiToken}`,
|
|
115
|
+
'content-type': 'application/json',
|
|
116
|
+
},
|
|
117
|
+
body,
|
|
118
|
+
});
|
|
119
|
+
await assertOk(response, stallGuard, 'writeEvents');
|
|
120
|
+
const responseText = await readResponseText(response, stallGuard);
|
|
121
|
+
const responseBody = JSON.parse(responseText);
|
|
122
|
+
if (!Array.isArray(responseBody)) {
|
|
123
|
+
throw new Error('Failed to parse response.');
|
|
124
|
+
}
|
|
125
|
+
const responseItemTexts = getRawJsonElements(responseText);
|
|
126
|
+
return responseBody.map((item, index) => {
|
|
127
|
+
if (!isCloudEvent(item)) {
|
|
128
|
+
throw new Error('Failed to parse response item.');
|
|
129
|
+
}
|
|
130
|
+
return convertCloudEventToEvent(item, responseItemTexts[index]);
|
|
131
|
+
});
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
readEvents(subject, options, signal) {
|
|
135
|
+
const url = this.#getUrl('/api/v1/read-events');
|
|
136
|
+
const apiToken = this.#apiToken;
|
|
137
|
+
const stallTimeout = this.#stallTimeout;
|
|
138
|
+
return streamWithStallGuard({ stallTimeout, signal }, async function* (stallGuard) {
|
|
139
|
+
const response = await stallGuard.send(url, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: {
|
|
142
|
+
authorization: `Bearer ${apiToken}`,
|
|
143
|
+
'content-type': 'application/json',
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
subject,
|
|
147
|
+
options,
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
const body = await getStreamBody(response, stallGuard, 'readEvents', 'read events');
|
|
151
|
+
for await (const { value: line, text } of readNdJsonStream(body, stallGuard.signal, stallGuard)) {
|
|
152
|
+
if (isStreamError(line)) {
|
|
153
|
+
throw createStreamError(line.payload.error);
|
|
154
|
+
}
|
|
155
|
+
if (isStreamCloudEvent(line)) {
|
|
156
|
+
yield convertCloudEventToEvent(line.payload, getRawJsonMember(text, 'payload'));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
throw new Error('Failed to read events.');
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
runEventQlQuery(query, signal) {
|
|
164
|
+
const url = this.#getUrl('/api/v1/run-eventql-query');
|
|
165
|
+
const apiToken = this.#apiToken;
|
|
166
|
+
const stallTimeout = this.#stallTimeout;
|
|
167
|
+
return streamWithStallGuard({ stallTimeout, signal }, async function* (stallGuard) {
|
|
168
|
+
const response = await stallGuard.send(url, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: {
|
|
171
|
+
authorization: `Bearer ${apiToken}`,
|
|
172
|
+
'content-type': 'application/json',
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({ query }),
|
|
175
|
+
});
|
|
176
|
+
const body = await getStreamBody(response, stallGuard, 'runEventQlQuery', 'run EventQL query');
|
|
177
|
+
for await (const { value: line } of readNdJsonStream(body, stallGuard.signal, stallGuard)) {
|
|
178
|
+
if (isStreamHeartbeat(line)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (isStreamError(line)) {
|
|
182
|
+
throw createStreamError(line.payload.error);
|
|
183
|
+
}
|
|
184
|
+
if (isStreamRow(line)) {
|
|
185
|
+
yield line.payload;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
throw new Error('Failed to run EventQL query.');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
observeEvents(subject, options, signal) {
|
|
193
|
+
const url = this.#getUrl('/api/v1/observe-events');
|
|
194
|
+
const apiToken = this.#apiToken;
|
|
195
|
+
const stallTimeout = this.#stallTimeout;
|
|
196
|
+
return streamWithStallGuard({ stallTimeout, signal }, async function* (stallGuard) {
|
|
197
|
+
const response = await stallGuard.send(url, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
authorization: `Bearer ${apiToken}`,
|
|
201
|
+
'content-type': 'application/json',
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
subject,
|
|
205
|
+
options,
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
const body = await getStreamBody(response, stallGuard, 'observeEvents', 'observe events');
|
|
209
|
+
for await (const { value: line, text } of readNdJsonStream(body, stallGuard.signal, stallGuard)) {
|
|
210
|
+
if (isStreamHeartbeat(line)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (isStreamError(line)) {
|
|
214
|
+
throw createStreamError(line.payload.error);
|
|
215
|
+
}
|
|
216
|
+
if (isStreamCloudEvent(line)) {
|
|
217
|
+
yield convertCloudEventToEvent(line.payload, getRawJsonMember(text, 'payload'));
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
throw new Error('Failed to observe events.');
|
|
221
|
+
}
|
|
222
|
+
// Observing never ends on its own, so a stream that ends broke off,
|
|
223
|
+
// unless the caller aborted it.
|
|
224
|
+
stallGuard.throwIfStalled();
|
|
225
|
+
if (!signal?.aborted) {
|
|
226
|
+
throw createStreamEndedError('observe events');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async registerEventSchema(eventType, schema, signal) {
|
|
231
|
+
const url = this.#getUrl('/api/v1/register-event-schema');
|
|
232
|
+
const stallTimeout = this.#stallTimeout;
|
|
233
|
+
const body = JSON.stringify({
|
|
234
|
+
eventType,
|
|
235
|
+
schema,
|
|
236
|
+
});
|
|
237
|
+
await runWrite({ signal, isRefusal: isV1Refusal }, async () => await runWithStallGuard({ stallTimeout, signal }, async (stallGuard) => {
|
|
238
|
+
const response = await stallGuard.send(url, {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
headers: {
|
|
241
|
+
authorization: `Bearer ${this.#apiToken}`,
|
|
242
|
+
'content-type': 'application/json',
|
|
243
|
+
},
|
|
244
|
+
body,
|
|
245
|
+
});
|
|
246
|
+
await assertOk(response, stallGuard, 'registerEventSchema');
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
// The v1 API always reads the subjects below the subject as well. Reading
|
|
250
|
+
// only the subject itself keeps the subject alone, which the server lists
|
|
251
|
+
// at most once, so the request ends once it arrives. The server lists it
|
|
252
|
+
// as it keeps it, so it is compared as the server keeps it, which makes
|
|
253
|
+
// '/test/' keep '/test', as the v2 API does.
|
|
254
|
+
readSubjects(subject, { recursive }, signal) {
|
|
255
|
+
const url = this.#getUrl('/api/v1/read-subjects');
|
|
256
|
+
const apiToken = this.#apiToken;
|
|
257
|
+
const stallTimeout = this.#stallTimeout;
|
|
258
|
+
// A caller without types may pass a subject that is not a string, which
|
|
259
|
+
// the server refuses with an error of its own, so the client normalizes
|
|
260
|
+
// only a string, and only to read the subject alone.
|
|
261
|
+
const normalizedSubject = !recursive && isString(subject) ? normalizeSubject(subject) : subject;
|
|
262
|
+
return streamWithStallGuard({ stallTimeout, signal }, async function* (stallGuard) {
|
|
263
|
+
const response = await stallGuard.send(url, {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: {
|
|
266
|
+
authorization: `Bearer ${apiToken}`,
|
|
267
|
+
'content-type': 'application/json',
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
baseSubject: subject,
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
const body = await getStreamBody(response, stallGuard, 'readSubjects', 'read subjects');
|
|
274
|
+
for await (const { value: line } of readNdJsonStream(body, stallGuard.signal, stallGuard)) {
|
|
275
|
+
if (isStreamError(line)) {
|
|
276
|
+
throw createStreamError(line.payload.error);
|
|
277
|
+
}
|
|
278
|
+
if (isStreamSubject(line)) {
|
|
279
|
+
if (recursive) {
|
|
280
|
+
yield line.payload.subject;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (line.payload.subject === normalizedSubject) {
|
|
284
|
+
yield line.payload.subject;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
throw new Error('Failed to read subjects.');
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
async readEventType(eventType, signal) {
|
|
294
|
+
const url = this.#getUrl('/api/v1/read-event-type');
|
|
295
|
+
const stallTimeout = this.#stallTimeout;
|
|
296
|
+
return await runWithStallGuard({ stallTimeout, signal }, async (stallGuard) => {
|
|
297
|
+
const response = await stallGuard.send(url, {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: {
|
|
300
|
+
authorization: `Bearer ${this.#apiToken}`,
|
|
301
|
+
'content-type': 'application/json',
|
|
302
|
+
},
|
|
303
|
+
body: JSON.stringify({
|
|
304
|
+
eventType,
|
|
305
|
+
}),
|
|
306
|
+
});
|
|
307
|
+
await assertOk(response, stallGuard, 'readEventType');
|
|
308
|
+
const responseBody = JSON.parse(await readResponseText(response, stallGuard));
|
|
309
|
+
if (!hasShapeOf(responseBody, {
|
|
310
|
+
eventType: isString,
|
|
311
|
+
isPhantom: isBoolean,
|
|
312
|
+
})) {
|
|
313
|
+
throw new Error('Failed to parse response.');
|
|
314
|
+
}
|
|
315
|
+
return toEventType(responseBody);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
readEventTypes(signal) {
|
|
319
|
+
const url = this.#getUrl('/api/v1/read-event-types');
|
|
320
|
+
const apiToken = this.#apiToken;
|
|
321
|
+
const stallTimeout = this.#stallTimeout;
|
|
322
|
+
return streamWithStallGuard({ stallTimeout, signal }, async function* (stallGuard) {
|
|
323
|
+
const response = await stallGuard.send(url, {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
headers: {
|
|
326
|
+
authorization: `Bearer ${apiToken}`,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
const body = await getStreamBody(response, stallGuard, 'readEventTypes', 'read event types');
|
|
330
|
+
for await (const { value: line } of readNdJsonStream(body, stallGuard.signal, stallGuard)) {
|
|
331
|
+
if (isStreamHeartbeat(line)) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (isStreamError(line)) {
|
|
335
|
+
throw createStreamError(line.payload.error);
|
|
336
|
+
}
|
|
337
|
+
if (isStreamEventType(line)) {
|
|
338
|
+
yield toEventType(line.payload);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
throw new Error('Failed to read event types.');
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
export { V1Transport };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ErrorCode } from '../ErrorCode.js';
|
|
2
|
+
// The errors of the v1 API that clearly match a code of the v2 API, by the
|
|
3
|
+
// status and the text that EventSourcingDB 1.2.0 sends for them.
|
|
4
|
+
const mappings = [
|
|
5
|
+
{
|
|
6
|
+
call: 'writeEvents',
|
|
7
|
+
status: 409,
|
|
8
|
+
text: 'state conflict: precondition failed',
|
|
9
|
+
code: ErrorCode.preconditionViolated,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
call: 'writeEvents',
|
|
13
|
+
status: 409,
|
|
14
|
+
text: 'state conflict: exceeded max store capacity',
|
|
15
|
+
code: ErrorCode.capacityExceeded,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
call: 'writeEvents',
|
|
19
|
+
status: 409,
|
|
20
|
+
text: 'schema conflict: event candidate does not match schema',
|
|
21
|
+
code: ErrorCode.eventSchemaViolated,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
call: 'registerEventSchema',
|
|
25
|
+
status: 409,
|
|
26
|
+
text: 'schema conflict: schema already exists',
|
|
27
|
+
code: ErrorCode.eventSchemaAlreadyRegistered,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
call: 'registerEventSchema',
|
|
31
|
+
status: 409,
|
|
32
|
+
text: 'schema conflict: event with ID',
|
|
33
|
+
code: ErrorCode.eventSchemaViolated,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
call: 'readEvents',
|
|
37
|
+
status: 409,
|
|
38
|
+
text: 'state conflict: fromLatestEvent results in an event ID greater than upperBound ID',
|
|
39
|
+
code: ErrorCode.rangeUnsatisfiable,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
call: 'readEventType',
|
|
43
|
+
status: 404,
|
|
44
|
+
text: '',
|
|
45
|
+
code: ErrorCode.eventTypeUnknown,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
call: undefined,
|
|
49
|
+
status: 503,
|
|
50
|
+
text: 'the current operation mode conflicts with the given request',
|
|
51
|
+
code: ErrorCode.storeReadOnly,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
call: 'writeEvents',
|
|
55
|
+
status: 507,
|
|
56
|
+
text: '',
|
|
57
|
+
code: ErrorCode.licenseInvalid,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
// Assigns the code of the v2 API to an error of the v1 API that clearly
|
|
61
|
+
// matches one. Every other error of the v1 API has no code.
|
|
62
|
+
const getV1ErrorCode = (call, status, description) => mappings.find(mapping => (mapping.call === undefined || mapping.call === call) &&
|
|
63
|
+
mapping.status === status &&
|
|
64
|
+
description.startsWith(mapping.text))?.code;
|
|
65
|
+
export { getV1ErrorCode };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const statusServiceUnavailable = 503;
|
|
2
|
+
const statusInsufficientStorage = 507;
|
|
3
|
+
// Tells whether the v1 API refused a write before storing anything, which it
|
|
4
|
+
// does with a status of 4xx, 503, or 507.
|
|
5
|
+
const isV1Refusal = (error) => (error.status >= 400 && error.status < 500) ||
|
|
6
|
+
error.status === statusServiceUnavailable ||
|
|
7
|
+
error.status === statusInsufficientStorage;
|
|
8
|
+
export { isV1Refusal };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const leadingAndTrailingSlashes = /^\/+|\/+$/gu;
|
|
2
|
+
// Returns the subject as the server keeps it, which is a slash followed by
|
|
3
|
+
// the subject without its leading and trailing slashes, so '/test/' becomes
|
|
4
|
+
// '/test', and '/' stays '/'.
|
|
5
|
+
const normalizeSubject = (subject) => `/${subject.replaceAll(leadingAndTrailingSlashes, '')}`;
|
|
6
|
+
export { normalizeSubject };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { hasShapeOf } from '
|
|
2
|
-
import { isRecord } from '
|
|
3
|
-
import { isString } from '
|
|
4
|
-
import { isStringOrNull } from '
|
|
5
|
-
import { isStringOrUndefined } from '
|
|
1
|
+
import { hasShapeOf } from '../../types/hasShapeOf.js';
|
|
2
|
+
import { isRecord } from '../../types/isRecord.js';
|
|
3
|
+
import { isString } from '../../types/isString.js';
|
|
4
|
+
import { isStringOrNull } from '../../types/isStringOrNull.js';
|
|
5
|
+
import { isStringOrUndefined } from '../../types/isStringOrUndefined.js';
|
|
6
6
|
const isStreamCloudEvent = (line) => hasShapeOf(line, {
|
|
7
7
|
type: (value) => value === 'event',
|
|
8
8
|
payload: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { hasShapeOf } from '
|
|
2
|
-
import { isString } from '
|
|
1
|
+
import { hasShapeOf } from '../../types/hasShapeOf.js';
|
|
2
|
+
import { isString } from '../../types/isString.js';
|
|
3
3
|
const isStreamError = (line) => hasShapeOf(line, {
|
|
4
4
|
type: (value) => value === 'error',
|
|
5
5
|
payload: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { hasShapeOf } from '
|
|
2
|
-
import { isBoolean } from '
|
|
3
|
-
import { isString } from '
|
|
1
|
+
import { hasShapeOf } from '../../types/hasShapeOf.js';
|
|
2
|
+
import { isBoolean } from '../../types/isBoolean.js';
|
|
3
|
+
import { isString } from '../../types/isString.js';
|
|
4
4
|
const isStreamEventType = (line) => hasShapeOf(line, {
|
|
5
5
|
type: (value) => value === 'eventType',
|
|
6
6
|
payload: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { hasShapeOf } from '
|
|
2
|
-
import { isString } from '
|
|
1
|
+
import { hasShapeOf } from '../../types/hasShapeOf.js';
|
|
2
|
+
import { isString } from '../../types/isString.js';
|
|
3
3
|
const isStreamSubject = (line) => hasShapeOf(line, {
|
|
4
4
|
type: (value) => value === 'subject',
|
|
5
5
|
payload: {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface V2Event {
|
|
2
|
+
specversion: string;
|
|
3
|
+
id: string;
|
|
4
|
+
time: string;
|
|
5
|
+
source: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
type: string;
|
|
8
|
+
datacontenttype: string;
|
|
9
|
+
data: Record<string, unknown>;
|
|
10
|
+
hash: string;
|
|
11
|
+
predecessorhash: string;
|
|
12
|
+
traceparent: string | null;
|
|
13
|
+
tracestate: string | null;
|
|
14
|
+
signature: string | null;
|
|
15
|
+
}
|
|
16
|
+
export type { V2Event };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Event } from '../Event.js';
|
|
2
|
+
import type { EventCandidate } from '../EventCandidate.js';
|
|
3
|
+
import type { EventType } from '../EventType.js';
|
|
4
|
+
import type { ObserveEventsOptions } from '../ObserveEventsOptions.js';
|
|
5
|
+
import type { Precondition } from '../Precondition.js';
|
|
6
|
+
import type { ReadEventsOptions } from '../ReadEventsOptions.js';
|
|
7
|
+
import type { ReadSubjectsOptions } from '../ReadSubjectsOptions.js';
|
|
8
|
+
import type { Transport, TransportOptions } from '../Transport.js';
|
|
9
|
+
declare class V2Transport implements Transport {
|
|
10
|
+
#private;
|
|
11
|
+
constructor({ url, apiToken, stallTimeout }: TransportOptions);
|
|
12
|
+
ping(signal?: AbortSignal): Promise<void>;
|
|
13
|
+
verifyApiToken(signal?: AbortSignal): Promise<void>;
|
|
14
|
+
writeEvents(events: EventCandidate[], preconditions: Precondition[], signal?: AbortSignal): Promise<Event[]>;
|
|
15
|
+
readEvents(subject: string, options: ReadEventsOptions, signal?: AbortSignal): AsyncGenerator<Event, void, void>;
|
|
16
|
+
runEventQlQuery(query: string, signal?: AbortSignal): AsyncGenerator<unknown, void, void>;
|
|
17
|
+
observeEvents(subject: string, options: ObserveEventsOptions, signal?: AbortSignal): AsyncGenerator<Event, void, void>;
|
|
18
|
+
registerEventSchema(eventType: string, schema: Record<string, unknown>, signal?: AbortSignal): Promise<void>;
|
|
19
|
+
readSubjects(subject: string, { recursive }: ReadSubjectsOptions, signal?: AbortSignal): AsyncGenerator<string, void, void>;
|
|
20
|
+
readEventType(eventType: string, signal?: AbortSignal): Promise<EventType>;
|
|
21
|
+
readEventTypes(signal?: AbortSignal): AsyncGenerator<EventType, void, void>;
|
|
22
|
+
}
|
|
23
|
+
export { V2Transport };
|