eventsourcingdb 1.9.1 → 2.0.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 (125) hide show
  1. package/README.md +265 -20
  2. package/dist/Client.d.ts +9 -7
  3. package/dist/Client.js +89 -464
  4. package/dist/ClientOptions.d.ts +4 -0
  5. package/dist/ConnectionStalledError.d.ts +4 -0
  6. package/dist/ConnectionStalledError.js +13 -0
  7. package/dist/DbApiError.d.ts +17 -0
  8. package/dist/DbApiError.js +24 -0
  9. package/dist/ErrorCode.d.ts +68 -0
  10. package/dist/ErrorCode.js +70 -0
  11. package/dist/Event.d.ts +2 -1
  12. package/dist/Event.js +58 -11
  13. package/dist/EventType.d.ts +2 -2
  14. package/dist/HashVerificationFailedError.d.ts +4 -0
  15. package/dist/HashVerificationFailedError.js +7 -0
  16. package/dist/OutcomeUnknownError.d.ts +6 -0
  17. package/dist/OutcomeUnknownError.js +9 -0
  18. package/dist/ReadSubjectsOptions.d.ts +4 -0
  19. package/dist/SignatureVerificationFailedError.d.ts +4 -0
  20. package/dist/SignatureVerificationFailedError.js +7 -0
  21. package/dist/Transport.d.ts +25 -0
  22. package/dist/UnsupportedHashVersionError.d.ts +4 -0
  23. package/dist/UnsupportedHashVersionError.js +7 -0
  24. package/dist/convertCloudEventToEvent.d.ts +1 -1
  25. package/dist/convertCloudEventToEvent.js +6 -1
  26. package/dist/detectApiVersion.d.ts +7 -0
  27. package/dist/detectApiVersion.js +47 -0
  28. package/dist/getImageTag.d.ts +2 -0
  29. package/dist/getImageTag.js +13 -0
  30. package/dist/http/assertEventSourcingDb.d.ts +2 -0
  31. package/dist/http/assertEventSourcingDb.js +14 -0
  32. package/dist/http/createDbApiErrorFromResponse.d.ts +4 -0
  33. package/dist/http/createDbApiErrorFromResponse.js +20 -0
  34. package/dist/http/createStreamEndedError.d.ts +2 -0
  35. package/dist/http/createStreamEndedError.js +6 -0
  36. package/dist/http/isAbortError.d.ts +2 -0
  37. package/dist/http/isAbortError.js +2 -0
  38. package/dist/http/parseRetryAfter.d.ts +2 -0
  39. package/dist/http/parseRetryAfter.js +14 -0
  40. package/dist/http/runWrite.d.ts +6 -0
  41. package/dist/http/runWrite.js +25 -0
  42. package/dist/http/trackSending.d.ts +7 -0
  43. package/dist/http/trackSending.js +46 -0
  44. package/dist/http/waitUnlessAborted.d.ts +2 -0
  45. package/dist/http/waitUnlessAborted.js +21 -0
  46. package/dist/index.d.ts +12 -3
  47. package/dist/index.js +8 -2
  48. package/dist/json/getRawJsonElements.d.ts +2 -0
  49. package/dist/json/getRawJsonElements.js +22 -0
  50. package/dist/json/getRawJsonMember.d.ts +2 -0
  51. package/dist/json/getRawJsonMember.js +29 -0
  52. package/dist/json/scanJson.d.ts +4 -0
  53. package/dist/json/scanJson.js +65 -0
  54. package/dist/ndjson/readNdJsonStream.d.ts +7 -1
  55. package/dist/ndjson/readNdJsonStream.js +7 -28
  56. package/dist/stall/StallGuard.d.ts +13 -0
  57. package/dist/stall/StallGuard.js +77 -0
  58. package/dist/stall/createFetchTimeoutError.d.ts +2 -0
  59. package/dist/stall/createFetchTimeoutError.js +16 -0
  60. package/dist/stall/defaultStallTimeout.d.ts +3 -0
  61. package/dist/stall/defaultStallTimeout.js +6 -0
  62. package/dist/stall/formatDuration.d.ts +2 -0
  63. package/dist/stall/formatDuration.js +9 -0
  64. package/dist/stall/readResponseText.d.ts +3 -0
  65. package/dist/stall/readResponseText.js +26 -0
  66. package/dist/stall/runWithStallGuard.d.ts +6 -0
  67. package/dist/stall/runWithStallGuard.js +18 -0
  68. package/dist/stall/streamWithStallGuard.d.ts +6 -0
  69. package/dist/stall/streamWithStallGuard.js +25 -0
  70. package/dist/v1/V1Transport.d.ts +23 -0
  71. package/dist/v1/V1Transport.js +339 -0
  72. package/dist/v1/getV1ErrorCode.d.ts +4 -0
  73. package/dist/v1/getV1ErrorCode.js +65 -0
  74. package/dist/v1/isV1Refusal.d.ts +3 -0
  75. package/dist/v1/isV1Refusal.js +8 -0
  76. package/dist/v1/stream/StreamHeartbeat.js +1 -0
  77. package/dist/v1/stream/StreamRow.js +1 -0
  78. package/dist/v1/stream/StreamSubject.js +1 -0
  79. package/dist/{stream → v1/stream}/isStreamCloudEvent.js +5 -5
  80. package/dist/{stream → v1/stream}/isStreamError.js +2 -2
  81. package/dist/{stream → v1/stream}/isStreamEventType.js +3 -3
  82. package/dist/{stream → v1/stream}/isStreamHeartbeat.js +1 -1
  83. package/dist/{stream → v1/stream}/isStreamRow.js +1 -1
  84. package/dist/{stream → v1/stream}/isStreamSubject.js +2 -2
  85. package/dist/v2/V2Event.d.ts +16 -0
  86. package/dist/v2/V2Event.js +1 -0
  87. package/dist/v2/V2Item.d.ts +6 -0
  88. package/dist/v2/V2Item.js +1 -0
  89. package/dist/v2/V2Transport.d.ts +23 -0
  90. package/dist/v2/V2Transport.js +302 -0
  91. package/dist/v2/convertV2Item.d.ts +6 -0
  92. package/dist/v2/convertV2Item.js +52 -0
  93. package/dist/v2/isV2Refusal.d.ts +3 -0
  94. package/dist/v2/isV2Refusal.js +12 -0
  95. package/dist/v2/parseV2Item.d.ts +3 -0
  96. package/dist/v2/parseV2Item.js +30 -0
  97. package/dist/v2/readV2Answer.d.ts +4 -0
  98. package/dist/v2/readV2Answer.js +14 -0
  99. package/dist/v2/readV2Stream.d.ts +8 -0
  100. package/dist/v2/readV2Stream.js +34 -0
  101. package/dist/v2/toV2RequestBody.d.ts +8 -0
  102. package/dist/v2/toV2RequestBody.js +46 -0
  103. package/package.json +3 -3
  104. package/dist/HeartbeatTimeoutError.d.ts +0 -4
  105. package/dist/HeartbeatTimeoutError.js +0 -7
  106. package/dist/heartbeatTimeout.d.ts +0 -4
  107. package/dist/heartbeatTimeout.js +0 -6
  108. /package/dist/{stream/StreamCloudEvent.js → ClientOptions.js} +0 -0
  109. /package/dist/{stream/StreamError.js → ReadSubjectsOptions.js} +0 -0
  110. /package/dist/{stream/StreamEventType.js → Transport.js} +0 -0
  111. /package/dist/{stream → v1/stream}/StreamCloudEvent.d.ts +0 -0
  112. /package/dist/{stream/StreamHeartbeat.js → v1/stream/StreamCloudEvent.js} +0 -0
  113. /package/dist/{stream → v1/stream}/StreamError.d.ts +0 -0
  114. /package/dist/{stream/StreamRow.js → v1/stream/StreamError.js} +0 -0
  115. /package/dist/{stream → v1/stream}/StreamEventType.d.ts +0 -0
  116. /package/dist/{stream/StreamSubject.js → v1/stream/StreamEventType.js} +0 -0
  117. /package/dist/{stream → v1/stream}/StreamHeartbeat.d.ts +0 -0
  118. /package/dist/{stream → v1/stream}/StreamRow.d.ts +0 -0
  119. /package/dist/{stream → v1/stream}/StreamSubject.d.ts +0 -0
  120. /package/dist/{stream → v1/stream}/isStreamCloudEvent.d.ts +0 -0
  121. /package/dist/{stream → v1/stream}/isStreamError.d.ts +0 -0
  122. /package/dist/{stream → v1/stream}/isStreamEventType.d.ts +0 -0
  123. /package/dist/{stream → v1/stream}/isStreamHeartbeat.d.ts +0 -0
  124. /package/dist/{stream → v1/stream}/isStreamRow.d.ts +0 -0
  125. /package/dist/{stream → v1/stream}/isStreamSubject.d.ts +0 -0
@@ -0,0 +1,68 @@
1
+ declare const ErrorCode: {
2
+ readonly subjectMissing: 'https://docs.eventsourcingdb.io/errors/subject-missing';
3
+ readonly subjectMalformed: 'https://docs.eventsourcingdb.io/errors/subject-malformed';
4
+ readonly orderUnknown: 'https://docs.eventsourcingdb.io/errors/order-unknown';
5
+ readonly eventIdMissing: 'https://docs.eventsourcingdb.io/errors/event-id-missing';
6
+ readonly eventIdMalformed: 'https://docs.eventsourcingdb.io/errors/event-id-malformed';
7
+ readonly boundTypeMissing: 'https://docs.eventsourcingdb.io/errors/bound-type-missing';
8
+ readonly boundTypeUnknown: 'https://docs.eventsourcingdb.io/errors/bound-type-unknown';
9
+ readonly rangeUnsatisfiable: 'https://docs.eventsourcingdb.io/errors/range-unsatisfiable';
10
+ readonly fromLatestEventIncomplete: 'https://docs.eventsourcingdb.io/errors/from-latest-event-incomplete';
11
+ readonly ifEventIsMissingUnknown: 'https://docs.eventsourcingdb.io/errors/if-event-is-missing-unknown';
12
+ readonly selectionConflicting: 'https://docs.eventsourcingdb.io/errors/selection-conflicting';
13
+ readonly eventTypeMissing: 'https://docs.eventsourcingdb.io/errors/event-type-missing';
14
+ readonly eventTypeMalformed: 'https://docs.eventsourcingdb.io/errors/event-type-malformed';
15
+ readonly eventTypeUnknown: 'https://docs.eventsourcingdb.io/errors/event-type-unknown';
16
+ readonly sourceMissing: 'https://docs.eventsourcingdb.io/errors/source-missing';
17
+ readonly sourceMalformed: 'https://docs.eventsourcingdb.io/errors/source-malformed';
18
+ readonly dataMissing: 'https://docs.eventsourcingdb.io/errors/data-missing';
19
+ readonly traceparentMalformed: 'https://docs.eventsourcingdb.io/errors/traceparent-malformed';
20
+ readonly tracestateMalformed: 'https://docs.eventsourcingdb.io/errors/tracestate-malformed';
21
+ readonly tracestateWithoutTraceparent: 'https://docs.eventsourcingdb.io/errors/tracestate-without-traceparent';
22
+ readonly eventsMissing: 'https://docs.eventsourcingdb.io/errors/events-missing';
23
+ readonly preconditionTypeMissing: 'https://docs.eventsourcingdb.io/errors/precondition-type-missing';
24
+ readonly preconditionUnknown: 'https://docs.eventsourcingdb.io/errors/precondition-unknown';
25
+ readonly payloadMissing: 'https://docs.eventsourcingdb.io/errors/payload-missing';
26
+ readonly licenseInvalid: 'https://docs.eventsourcingdb.io/errors/license-invalid';
27
+ readonly capacityExceeded: 'https://docs.eventsourcingdb.io/errors/capacity-exceeded';
28
+ readonly eventSchemaViolated: 'https://docs.eventsourcingdb.io/errors/event-schema-violated';
29
+ readonly eventSchemaUnregistered: 'https://docs.eventsourcingdb.io/errors/event-schema-unregistered';
30
+ readonly eventSchemaMissing: 'https://docs.eventsourcingdb.io/errors/event-schema-missing';
31
+ readonly eventSchemaMalformed: 'https://docs.eventsourcingdb.io/errors/event-schema-malformed';
32
+ readonly eventSchemaNotObject: 'https://docs.eventsourcingdb.io/errors/event-schema-not-object';
33
+ readonly eventSchemaAlreadyRegistered: 'https://docs.eventsourcingdb.io/errors/event-schema-already-registered';
34
+ readonly lastAuditedEventMissing: 'https://docs.eventsourcingdb.io/errors/last-audited-event-missing';
35
+ readonly lastAuditedEventMismatched: 'https://docs.eventsourcingdb.io/errors/last-audited-event-mismatched';
36
+ readonly lastAuditedEventUnknown: 'https://docs.eventsourcingdb.io/errors/last-audited-event-unknown';
37
+ readonly eventHashMissing: 'https://docs.eventsourcingdb.io/errors/event-hash-missing';
38
+ readonly eventHashMalformed: 'https://docs.eventsourcingdb.io/errors/event-hash-malformed';
39
+ readonly eventChainBroken: 'https://docs.eventsourcingdb.io/errors/event-chain-broken';
40
+ readonly lastBackedUpEventMissing: 'https://docs.eventsourcingdb.io/errors/last-backed-up-event-missing';
41
+ readonly lastBackedUpEventMismatched: 'https://docs.eventsourcingdb.io/errors/last-backed-up-event-mismatched';
42
+ readonly lastBackedUpEventUnknown: 'https://docs.eventsourcingdb.io/errors/last-backed-up-event-unknown';
43
+ readonly storeNotEmpty: 'https://docs.eventsourcingdb.io/errors/store-not-empty';
44
+ readonly eventHashVersionUnknown: 'https://docs.eventsourcingdb.io/errors/event-hash-version-unknown';
45
+ readonly backupMalformed: 'https://docs.eventsourcingdb.io/errors/backup-malformed';
46
+ readonly backupIncomplete: 'https://docs.eventsourcingdb.io/errors/backup-incomplete';
47
+ readonly backupFormatUnknown: 'https://docs.eventsourcingdb.io/errors/backup-format-unknown';
48
+ readonly fullBackupMissing: 'https://docs.eventsourcingdb.io/errors/full-backup-missing';
49
+ readonly backupIncrementDisconnected: 'https://docs.eventsourcingdb.io/errors/backup-increment-disconnected';
50
+ readonly backupFailed: 'https://docs.eventsourcingdb.io/errors/backup-failed';
51
+ readonly backupUnreadable: 'https://docs.eventsourcingdb.io/errors/backup-unreadable';
52
+ readonly eventTooLarge: 'https://docs.eventsourcingdb.io/errors/event-too-large';
53
+ readonly dataMalformed: 'https://docs.eventsourcingdb.io/errors/data-malformed';
54
+ readonly queryMissing: 'https://docs.eventsourcingdb.io/errors/query-missing';
55
+ readonly queryMalformed: 'https://docs.eventsourcingdb.io/errors/query-malformed';
56
+ readonly queryUnevaluable: 'https://docs.eventsourcingdb.io/errors/query-unevaluable';
57
+ readonly queryNotBoolean: 'https://docs.eventsourcingdb.io/errors/query-not-boolean';
58
+ readonly preconditionViolated: 'https://docs.eventsourcingdb.io/errors/precondition-violated';
59
+ readonly storeReadOnly: 'https://docs.eventsourcingdb.io/errors/store-read-only';
60
+ readonly readOnlyModeAlreadyEnabled: 'https://docs.eventsourcingdb.io/errors/read-only-mode-already-enabled';
61
+ readonly diskFull: 'https://docs.eventsourcingdb.io/errors/disk-full';
62
+ readonly restoreInProgress: 'https://docs.eventsourcingdb.io/errors/restore-in-progress';
63
+ readonly storeFailed: 'https://docs.eventsourcingdb.io/errors/store-failed';
64
+ readonly indexesRebuilding: 'https://docs.eventsourcingdb.io/errors/indexes-rebuilding';
65
+ readonly serverShuttingDown: 'https://docs.eventsourcingdb.io/errors/server-shutting-down';
66
+ };
67
+ type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
68
+ export { ErrorCode };
@@ -0,0 +1,70 @@
1
+ // The codes of the errors that the server reports. A code is the URL of the
2
+ // page that documents the error, exactly as the server sends it, so a caller
3
+ // can tell errors apart by comparing the code of a DbApiError with these.
4
+ const ErrorCode = {
5
+ subjectMissing: 'https://docs.eventsourcingdb.io/errors/subject-missing',
6
+ subjectMalformed: 'https://docs.eventsourcingdb.io/errors/subject-malformed',
7
+ orderUnknown: 'https://docs.eventsourcingdb.io/errors/order-unknown',
8
+ eventIdMissing: 'https://docs.eventsourcingdb.io/errors/event-id-missing',
9
+ eventIdMalformed: 'https://docs.eventsourcingdb.io/errors/event-id-malformed',
10
+ boundTypeMissing: 'https://docs.eventsourcingdb.io/errors/bound-type-missing',
11
+ boundTypeUnknown: 'https://docs.eventsourcingdb.io/errors/bound-type-unknown',
12
+ rangeUnsatisfiable: 'https://docs.eventsourcingdb.io/errors/range-unsatisfiable',
13
+ fromLatestEventIncomplete: 'https://docs.eventsourcingdb.io/errors/from-latest-event-incomplete',
14
+ ifEventIsMissingUnknown: 'https://docs.eventsourcingdb.io/errors/if-event-is-missing-unknown',
15
+ selectionConflicting: 'https://docs.eventsourcingdb.io/errors/selection-conflicting',
16
+ eventTypeMissing: 'https://docs.eventsourcingdb.io/errors/event-type-missing',
17
+ eventTypeMalformed: 'https://docs.eventsourcingdb.io/errors/event-type-malformed',
18
+ eventTypeUnknown: 'https://docs.eventsourcingdb.io/errors/event-type-unknown',
19
+ sourceMissing: 'https://docs.eventsourcingdb.io/errors/source-missing',
20
+ sourceMalformed: 'https://docs.eventsourcingdb.io/errors/source-malformed',
21
+ dataMissing: 'https://docs.eventsourcingdb.io/errors/data-missing',
22
+ traceparentMalformed: 'https://docs.eventsourcingdb.io/errors/traceparent-malformed',
23
+ tracestateMalformed: 'https://docs.eventsourcingdb.io/errors/tracestate-malformed',
24
+ tracestateWithoutTraceparent: 'https://docs.eventsourcingdb.io/errors/tracestate-without-traceparent',
25
+ eventsMissing: 'https://docs.eventsourcingdb.io/errors/events-missing',
26
+ preconditionTypeMissing: 'https://docs.eventsourcingdb.io/errors/precondition-type-missing',
27
+ preconditionUnknown: 'https://docs.eventsourcingdb.io/errors/precondition-unknown',
28
+ payloadMissing: 'https://docs.eventsourcingdb.io/errors/payload-missing',
29
+ licenseInvalid: 'https://docs.eventsourcingdb.io/errors/license-invalid',
30
+ capacityExceeded: 'https://docs.eventsourcingdb.io/errors/capacity-exceeded',
31
+ eventSchemaViolated: 'https://docs.eventsourcingdb.io/errors/event-schema-violated',
32
+ eventSchemaUnregistered: 'https://docs.eventsourcingdb.io/errors/event-schema-unregistered',
33
+ eventSchemaMissing: 'https://docs.eventsourcingdb.io/errors/event-schema-missing',
34
+ eventSchemaMalformed: 'https://docs.eventsourcingdb.io/errors/event-schema-malformed',
35
+ eventSchemaNotObject: 'https://docs.eventsourcingdb.io/errors/event-schema-not-object',
36
+ eventSchemaAlreadyRegistered: 'https://docs.eventsourcingdb.io/errors/event-schema-already-registered',
37
+ lastAuditedEventMissing: 'https://docs.eventsourcingdb.io/errors/last-audited-event-missing',
38
+ lastAuditedEventMismatched: 'https://docs.eventsourcingdb.io/errors/last-audited-event-mismatched',
39
+ lastAuditedEventUnknown: 'https://docs.eventsourcingdb.io/errors/last-audited-event-unknown',
40
+ eventHashMissing: 'https://docs.eventsourcingdb.io/errors/event-hash-missing',
41
+ eventHashMalformed: 'https://docs.eventsourcingdb.io/errors/event-hash-malformed',
42
+ eventChainBroken: 'https://docs.eventsourcingdb.io/errors/event-chain-broken',
43
+ lastBackedUpEventMissing: 'https://docs.eventsourcingdb.io/errors/last-backed-up-event-missing',
44
+ lastBackedUpEventMismatched: 'https://docs.eventsourcingdb.io/errors/last-backed-up-event-mismatched',
45
+ lastBackedUpEventUnknown: 'https://docs.eventsourcingdb.io/errors/last-backed-up-event-unknown',
46
+ storeNotEmpty: 'https://docs.eventsourcingdb.io/errors/store-not-empty',
47
+ eventHashVersionUnknown: 'https://docs.eventsourcingdb.io/errors/event-hash-version-unknown',
48
+ backupMalformed: 'https://docs.eventsourcingdb.io/errors/backup-malformed',
49
+ backupIncomplete: 'https://docs.eventsourcingdb.io/errors/backup-incomplete',
50
+ backupFormatUnknown: 'https://docs.eventsourcingdb.io/errors/backup-format-unknown',
51
+ fullBackupMissing: 'https://docs.eventsourcingdb.io/errors/full-backup-missing',
52
+ backupIncrementDisconnected: 'https://docs.eventsourcingdb.io/errors/backup-increment-disconnected',
53
+ backupFailed: 'https://docs.eventsourcingdb.io/errors/backup-failed',
54
+ backupUnreadable: 'https://docs.eventsourcingdb.io/errors/backup-unreadable',
55
+ eventTooLarge: 'https://docs.eventsourcingdb.io/errors/event-too-large',
56
+ dataMalformed: 'https://docs.eventsourcingdb.io/errors/data-malformed',
57
+ queryMissing: 'https://docs.eventsourcingdb.io/errors/query-missing',
58
+ queryMalformed: 'https://docs.eventsourcingdb.io/errors/query-malformed',
59
+ queryUnevaluable: 'https://docs.eventsourcingdb.io/errors/query-unevaluable',
60
+ queryNotBoolean: 'https://docs.eventsourcingdb.io/errors/query-not-boolean',
61
+ preconditionViolated: 'https://docs.eventsourcingdb.io/errors/precondition-violated',
62
+ storeReadOnly: 'https://docs.eventsourcingdb.io/errors/store-read-only',
63
+ readOnlyModeAlreadyEnabled: 'https://docs.eventsourcingdb.io/errors/read-only-mode-already-enabled',
64
+ diskFull: 'https://docs.eventsourcingdb.io/errors/disk-full',
65
+ restoreInProgress: 'https://docs.eventsourcingdb.io/errors/restore-in-progress',
66
+ storeFailed: 'https://docs.eventsourcingdb.io/errors/store-failed',
67
+ indexesRebuilding: 'https://docs.eventsourcingdb.io/errors/indexes-rebuilding',
68
+ serverShuttingDown: 'https://docs.eventsourcingdb.io/errors/server-shutting-down',
69
+ };
70
+ export { ErrorCode };
package/dist/Event.d.ts CHANGED
@@ -14,7 +14,7 @@ declare class Event {
14
14
  traceparent?: string;
15
15
  tracestate?: string;
16
16
  signature: string | null;
17
- constructor({ specversion, id, time, timeFromServer, source, subject, type, datacontenttype, data, hash, predecessorhash, traceparent, tracestate, signature, }: {
17
+ constructor({ specversion, id, time, timeFromServer, source, subject, type, datacontenttype, data, dataFromServer, hash, predecessorhash, traceparent, tracestate, signature, }: {
18
18
  specversion: string;
19
19
  id: string;
20
20
  time: Date;
@@ -24,6 +24,7 @@ declare class Event {
24
24
  type: string;
25
25
  datacontenttype: string;
26
26
  data: Record<string, unknown>;
27
+ dataFromServer: string;
27
28
  hash: string;
28
29
  predecessorhash: string;
29
30
  traceparent?: string;
package/dist/Event.js CHANGED
@@ -1,4 +1,16 @@
1
1
  import crypto from 'node:crypto';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { HashVerificationFailedError } from './HashVerificationFailedError.js';
4
+ import { SignatureVerificationFailedError } from './SignatureVerificationFailedError.js';
5
+ import { UnsupportedHashVersionError } from './UnsupportedHashVersionError.js';
6
+ // A hash of version 2 starts with this prefix. A hash without a prefix is of
7
+ // version 1, which predates the prefix.
8
+ const hashVersion2Prefix = 'esdb:hash:v2:';
9
+ const hashVersionRegex = /^esdb:hash:(?<version>[^:]*):/u;
10
+ // The input of a hash of version 2 writes a field that the event does not
11
+ // have like this.
12
+ const missingField = '-';
13
+ const getSha256 = (input) => crypto.createHash('sha256').update(input).digest('hex');
2
14
  class Event {
3
15
  specversion;
4
16
  id;
@@ -9,12 +21,13 @@ class Event {
9
21
  type;
10
22
  datacontenttype;
11
23
  data;
24
+ #dataFromServer;
12
25
  hash;
13
26
  predecessorhash;
14
27
  traceparent;
15
28
  tracestate;
16
29
  signature;
17
- constructor({ specversion, id, time, timeFromServer, source, subject, type, datacontenttype, data, hash, predecessorhash, traceparent, tracestate, signature, }) {
30
+ constructor({ specversion, id, time, timeFromServer, source, subject, type, datacontenttype, data, dataFromServer, hash, predecessorhash, traceparent, tracestate, signature, }) {
18
31
  this.specversion = specversion;
19
32
  this.id = id;
20
33
  this.time = time;
@@ -24,22 +37,56 @@ class Event {
24
37
  this.type = type;
25
38
  this.datacontenttype = datacontenttype;
26
39
  this.data = data;
40
+ this.#dataFromServer = dataFromServer;
27
41
  this.hash = hash;
28
42
  this.predecessorhash = predecessorhash;
29
43
  this.traceparent = traceparent;
30
44
  this.tracestate = tracestate;
31
45
  this.signature = signature;
32
46
  }
33
- verifyHash() {
47
+ #computeHashOfVersion1() {
34
48
  const metadata = `${this.specversion}|${this.id}|${this.predecessorhash}|${this.#timeFromServer}|${this.source}|${this.subject}|${this.type}|${this.datacontenttype}`;
35
- const metadataHash = crypto.createHash('sha256').update(metadata).digest('hex');
36
- const dataHash = crypto.createHash('sha256').update(JSON.stringify(this.data)).digest('hex');
37
- const finalHash = crypto
38
- .createHash('sha256')
39
- .update(`${metadataHash}${dataHash}`)
40
- .digest('hex');
41
- if (finalHash !== this.hash) {
42
- throw new Error('Failed to verify hash.');
49
+ const metadataHash = getSha256(metadata);
50
+ const dataHash = getSha256(this.#dataFromServer);
51
+ return getSha256(`${metadataHash}${dataHash}`);
52
+ }
53
+ #computeHashOfVersion2() {
54
+ const input = [
55
+ hashVersion2Prefix,
56
+ this.specversion,
57
+ this.id,
58
+ this.predecessorhash,
59
+ this.#timeFromServer,
60
+ this.source,
61
+ this.subject,
62
+ this.type,
63
+ this.datacontenttype,
64
+ this.traceparent ?? missingField,
65
+ this.tracestate ?? missingField,
66
+ this.#dataFromServer,
67
+ ].join('\n');
68
+ return `${hashVersion2Prefix}${getSha256(input)}`;
69
+ }
70
+ // An event keeps the version of the hash it was written with, so the hash
71
+ // is computed in the version it names.
72
+ #computeHash() {
73
+ const version = hashVersionRegex.exec(this.hash)?.groups?.version;
74
+ if (version === undefined) {
75
+ return this.#computeHashOfVersion1();
76
+ }
77
+ if (`esdb:hash:${version}:` === hashVersion2Prefix) {
78
+ return this.#computeHashOfVersion2();
79
+ }
80
+ throw new UnsupportedHashVersionError(version);
81
+ }
82
+ verifyHash() {
83
+ if (this.#computeHash() !== this.hash) {
84
+ throw new HashVerificationFailedError();
85
+ }
86
+ // The hash covers the data as the server sent it, so data that has been
87
+ // changed since would go unnoticed without comparing the two.
88
+ if (!isDeepStrictEqual(this.data, JSON.parse(this.#dataFromServer))) {
89
+ throw new HashVerificationFailedError();
43
90
  }
44
91
  }
45
92
  verifySignature(verificationKey) {
@@ -56,7 +103,7 @@ class Event {
56
103
  const hashBytes = Buffer.from(this.hash, 'utf8');
57
104
  const isSignatureValid = crypto.verify(null, hashBytes, verificationKey, signatureBytes);
58
105
  if (!isSignatureValid) {
59
- throw new Error('Signature verification failed.');
106
+ throw new SignatureVerificationFailedError();
60
107
  }
61
108
  }
62
109
  }
@@ -1,6 +1,6 @@
1
1
  interface EventType {
2
- eventType: string;
3
- isPhantom: boolean;
2
+ type: string;
3
+ hasEvents: boolean;
4
4
  schema?: Record<string, unknown>;
5
5
  }
6
6
  export type { EventType };
@@ -0,0 +1,4 @@
1
+ declare class HashVerificationFailedError extends Error {
2
+ constructor();
3
+ }
4
+ export { HashVerificationFailedError };
@@ -0,0 +1,7 @@
1
+ class HashVerificationFailedError extends Error {
2
+ constructor() {
3
+ super('Failed to verify hash.');
4
+ this.name = 'HashVerificationFailedError';
5
+ }
6
+ }
7
+ export { HashVerificationFailedError };
@@ -0,0 +1,6 @@
1
+ declare class OutcomeUnknownError extends Error {
2
+ constructor({ cause }: {
3
+ cause: unknown;
4
+ });
5
+ }
6
+ export { OutcomeUnknownError };
@@ -0,0 +1,9 @@
1
+ // A write that failed once it may have reached the server, so it may have been
2
+ // stored nonetheless. Its cause tells what went wrong.
3
+ class OutcomeUnknownError extends Error {
4
+ constructor({ cause }) {
5
+ super('The write may or may not have been stored.', { cause });
6
+ this.name = 'OutcomeUnknownError';
7
+ }
8
+ }
9
+ export { OutcomeUnknownError };
@@ -0,0 +1,4 @@
1
+ interface ReadSubjectsOptions {
2
+ recursive: boolean;
3
+ }
4
+ export type { ReadSubjectsOptions };
@@ -0,0 +1,4 @@
1
+ declare class SignatureVerificationFailedError extends Error {
2
+ constructor();
3
+ }
4
+ export { SignatureVerificationFailedError };
@@ -0,0 +1,7 @@
1
+ class SignatureVerificationFailedError extends Error {
2
+ constructor() {
3
+ super('Signature verification failed.');
4
+ this.name = 'SignatureVerificationFailedError';
5
+ }
6
+ }
7
+ export { SignatureVerificationFailedError };
@@ -0,0 +1,25 @@
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
+ interface Transport {
9
+ ping: (signal?: AbortSignal) => Promise<void>;
10
+ verifyApiToken: (signal?: AbortSignal) => Promise<void>;
11
+ writeEvents: (events: EventCandidate[], preconditions: Precondition[], signal?: AbortSignal) => Promise<Event[]>;
12
+ readEvents: (subject: string, options: ReadEventsOptions, signal?: AbortSignal) => AsyncGenerator<Event, void, void>;
13
+ runEventQlQuery: (query: string, signal?: AbortSignal) => AsyncGenerator<unknown, void, void>;
14
+ observeEvents: (subject: string, options: ObserveEventsOptions, signal?: AbortSignal) => AsyncGenerator<Event, void, void>;
15
+ registerEventSchema: (eventType: string, schema: Record<string, unknown>, signal?: AbortSignal) => Promise<void>;
16
+ readSubjects: (subject: string, options: ReadSubjectsOptions, signal?: AbortSignal) => AsyncGenerator<string, void, void>;
17
+ readEventType: (eventType: string, signal?: AbortSignal) => Promise<EventType>;
18
+ readEventTypes: (signal?: AbortSignal) => AsyncGenerator<EventType, void, void>;
19
+ }
20
+ interface TransportOptions {
21
+ url: URL;
22
+ apiToken: string;
23
+ stallTimeout: number;
24
+ }
25
+ export type { Transport, TransportOptions };
@@ -0,0 +1,4 @@
1
+ declare class UnsupportedHashVersionError extends Error {
2
+ constructor(version: string);
3
+ }
4
+ export { UnsupportedHashVersionError };
@@ -0,0 +1,7 @@
1
+ class UnsupportedHashVersionError extends Error {
2
+ constructor(version) {
3
+ super(`Hash version '${version}' is not supported.`);
4
+ this.name = 'UnsupportedHashVersionError';
5
+ }
6
+ }
7
+ export { UnsupportedHashVersionError };
@@ -1,4 +1,4 @@
1
1
  import type { CloudEvent } from './CloudEvent.js';
2
2
  import { Event } from './Event.js';
3
- declare const convertCloudEventToEvent: (cloudEvent: CloudEvent) => Event;
3
+ declare const convertCloudEventToEvent: (cloudEvent: CloudEvent, cloudEventText?: string) => Event;
4
4
  export { convertCloudEventToEvent };
@@ -1,5 +1,8 @@
1
1
  import { Event } from './Event.js';
2
- const convertCloudEventToEvent = (cloudEvent) => new Event({
2
+ import { getRawJsonMember } from './json/getRawJsonMember.js';
3
+ // The hash of an event covers its data exactly as the server sent it, so the
4
+ // event keeps the data from the text of the cloud event, byte for byte.
5
+ const convertCloudEventToEvent = (cloudEvent, cloudEventText) => new Event({
3
6
  specversion: cloudEvent.specversion,
4
7
  id: cloudEvent.id,
5
8
  time: new Date(cloudEvent.time),
@@ -9,6 +12,8 @@ const convertCloudEventToEvent = (cloudEvent) => new Event({
9
12
  type: cloudEvent.type,
10
13
  datacontenttype: cloudEvent.datacontenttype,
11
14
  data: cloudEvent.data,
15
+ dataFromServer: (cloudEventText === undefined ? undefined : getRawJsonMember(cloudEventText, 'data')) ??
16
+ JSON.stringify(cloudEvent.data),
12
17
  hash: cloudEvent.hash,
13
18
  predecessorhash: cloudEvent.predecessorhash,
14
19
  traceparent: cloudEvent.traceparent,
@@ -0,0 +1,7 @@
1
+ type ApiVersion = 'v1' | 'v2';
2
+ declare const detectApiVersion: ({ url, stallTimeout, }: {
3
+ url: URL;
4
+ stallTimeout: number;
5
+ }) => Promise<ApiVersion>;
6
+ export type { ApiVersion };
7
+ export { detectApiVersion };
@@ -0,0 +1,47 @@
1
+ import { assertEventSourcingDb } from './http/assertEventSourcingDb.js';
2
+ import { createDbApiErrorFromResponse } from './http/createDbApiErrorFromResponse.js';
3
+ import { readResponseText } from './stall/readResponseText.js';
4
+ import { runWithStallGuard } from './stall/runWithStallGuard.js';
5
+ import { hasShapeOf } from './types/hasShapeOf.js';
6
+ import { isString } from './types/isString.js';
7
+ // An answer that is not JSON is not a ping, so it parses as null.
8
+ const parseJson = (text) => {
9
+ try {
10
+ return JSON.parse(text);
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ };
16
+ const statusNotFound = 404;
17
+ const statusMethodNotAllowed = 405;
18
+ // Finds out whether the server offers the v2 API, by pinging it there. A
19
+ // server that offers only the v1 API, as EventSourcingDB 1.2.0 does, answers
20
+ // a path of the v2 API with the status 404 or 405. Every other answer than
21
+ // these and a ping of the v2 API fails, so that the next request asks again.
22
+ const detectApiVersion = async ({ url, stallTimeout, }) => await runWithStallGuard({ stallTimeout }, async (stallGuard) => {
23
+ const response = await stallGuard.send(new URL('/api/v2/system/ping', url).toString(), {
24
+ method: 'GET',
25
+ });
26
+ if (response.status === statusNotFound || response.status === statusMethodNotAllowed) {
27
+ await readResponseText(response, stallGuard);
28
+ return 'v1';
29
+ }
30
+ assertEventSourcingDb(response);
31
+ if (response.status !== 200) {
32
+ throw await createDbApiErrorFromResponse(response, stallGuard);
33
+ }
34
+ const responseBody = parseJson(await readResponseText(response, stallGuard));
35
+ if (!hasShapeOf(responseBody, {
36
+ type: isString,
37
+ data: {
38
+ type: isString,
39
+ },
40
+ }) ||
41
+ responseBody.type !== 'event' ||
42
+ responseBody.data.type !== 'io.eventsourcingdb.api.ping-received') {
43
+ throw new Error('Failed to find out which API the server offers.');
44
+ }
45
+ return 'v2';
46
+ });
47
+ export { detectApiVersion };
@@ -0,0 +1,2 @@
1
+ declare const getImageTag: () => string;
2
+ export { getImageTag };
@@ -0,0 +1,13 @@
1
+ import process from 'node:process';
2
+ import { getImageVersionFromDockerfile } from './getImageVersionFromDockerfile.js';
3
+ // The tests run against the image tag that EVENTSOURCINGDB_IMAGE_TAG names, so
4
+ // that the pipeline can run them against several tags. Without the variable,
5
+ // they run against the tag that the Dockerfile names.
6
+ const getImageTag = () => {
7
+ const imageTag = process.env.EVENTSOURCINGDB_IMAGE_TAG;
8
+ if (imageTag !== undefined && imageTag !== '') {
9
+ return imageTag;
10
+ }
11
+ return getImageVersionFromDockerfile();
12
+ };
13
+ export { getImageTag };
@@ -0,0 +1,2 @@
1
+ declare const assertEventSourcingDb: (response: Response) => void;
2
+ export { assertEventSourcingDb };
@@ -0,0 +1,14 @@
1
+ import { isValidServerHeader } from '../isValidServerHeader.js';
2
+ // Fails unless the response comes from EventSourcingDB, which names itself in
3
+ // the Server header.
4
+ const assertEventSourcingDb = (response) => {
5
+ if (isValidServerHeader(response)) {
6
+ return;
7
+ }
8
+ const serverHeader = response.headers.get('Server');
9
+ if (!serverHeader) {
10
+ throw new Error('Server must be EventSourcingDB, but Server header is missing.');
11
+ }
12
+ throw new Error(`Server must be EventSourcingDB, got Server header: '${serverHeader}'.`);
13
+ };
14
+ export { assertEventSourcingDb };
@@ -0,0 +1,4 @@
1
+ import { DbApiError } from '../DbApiError.js';
2
+ import type { StallGuard } from '../stall/StallGuard.js';
3
+ declare const createDbApiErrorFromResponse: (response: Response, stallGuard: StallGuard, getCode?: (status: number, description: string) => string | undefined) => Promise<DbApiError>;
4
+ export { createDbApiErrorFromResponse };
@@ -0,0 +1,20 @@
1
+ import { DbApiError } from '../DbApiError.js';
2
+ import { readResponseText } from '../stall/readResponseText.js';
3
+ import { parseRetryAfter } from './parseRetryAfter.js';
4
+ const statusTooManyRequests = 429;
5
+ // Creates the error for a response with a status other than 200, whose body
6
+ // is a text that describes the error. The text ends with a line break, which
7
+ // is not part of the description.
8
+ const createDbApiErrorFromResponse = async (response, stallGuard, getCode) => {
9
+ const { status } = response;
10
+ const description = (await readResponseText(response, stallGuard)).trim();
11
+ return new DbApiError({
12
+ status,
13
+ description,
14
+ code: getCode?.(status, description),
15
+ retryAfter: status === statusTooManyRequests
16
+ ? parseRetryAfter(response.headers.get('Retry-After'))
17
+ : undefined,
18
+ });
19
+ };
20
+ export { createDbApiErrorFromResponse };
@@ -0,0 +1,2 @@
1
+ declare const createStreamEndedError: (action: string) => Error;
2
+ export { createStreamEndedError };
@@ -0,0 +1,6 @@
1
+ // Creates the error for a stream that the server closed before it was done: a
2
+ // finite stream that ends before its end item, or a stream of observed events,
3
+ // which never ends on its own. Either broke off because of a failure that the
4
+ // server did not report.
5
+ const createStreamEndedError = (action) => new Error(`Failed to ${action}, since the stream ended unexpectedly.`);
6
+ export { createStreamEndedError };
@@ -0,0 +1,2 @@
1
+ declare const isAbortError: (error: unknown) => boolean;
2
+ export { isAbortError };
@@ -0,0 +1,2 @@
1
+ const isAbortError = (error) => error instanceof DOMException && error.name === 'AbortError';
2
+ export { isAbortError };
@@ -0,0 +1,2 @@
1
+ declare const parseRetryAfter: (header: string | null) => number | undefined;
2
+ export { parseRetryAfter };
@@ -0,0 +1,14 @@
1
+ const secondsRegex = /^\d+$/u;
2
+ // Returns the seconds that a Retry-After header names, or undefined if there
3
+ // is no such header or it does not name seconds.
4
+ const parseRetryAfter = (header) => {
5
+ if (header === null) {
6
+ return;
7
+ }
8
+ const value = header.trim();
9
+ if (!secondsRegex.test(value)) {
10
+ return;
11
+ }
12
+ return Number(value);
13
+ };
14
+ export { parseRetryAfter };
@@ -0,0 +1,6 @@
1
+ import { DbApiError } from '../DbApiError.js';
2
+ declare const runWrite: <TResult>({ signal, isRefusal }: {
3
+ signal?: AbortSignal;
4
+ isRefusal: (error: DbApiError) => boolean;
5
+ }, write: () => Promise<TResult>) => Promise<TResult>;
6
+ export { runWrite };
@@ -0,0 +1,25 @@
1
+ import { DbApiError } from '../DbApiError.js';
2
+ import { OutcomeUnknownError } from '../OutcomeUnknownError.js';
3
+ import { trackSending } from './trackSending.js';
4
+ // Runs a write that has not been sent yet. A write certainly was not stored if
5
+ // it failed before its request went out, whether the caller aborted it, it
6
+ // stalled, or it could not establish a connection, or if the server refused
7
+ // it, which isRefusal tells for an error that the server reports. Every other
8
+ // failure may come after the server stored the write, so it fails with an
9
+ // OutcomeUnknownError that wraps it.
10
+ const runWrite = async ({ signal, isRefusal }, write) => {
11
+ signal?.throwIfAborted();
12
+ const outcome = await trackSending(write);
13
+ if ('result' in outcome) {
14
+ return outcome.result;
15
+ }
16
+ const { error, hasBeenSent } = outcome;
17
+ if (!hasBeenSent) {
18
+ throw error;
19
+ }
20
+ if (error instanceof DbApiError && isRefusal(error)) {
21
+ throw error;
22
+ }
23
+ throw new OutcomeUnknownError({ cause: error });
24
+ };
25
+ export { runWrite };
@@ -0,0 +1,7 @@
1
+ declare const trackSending: <TResult>(write: () => Promise<TResult>) => Promise<{
2
+ result: TResult;
3
+ } | {
4
+ error: unknown;
5
+ hasBeenSent: boolean;
6
+ }>;
7
+ export { trackSending };