@enbox/dwn-sdk-js 0.2.2 → 0.3.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/dist/browser.mjs +6 -6
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/generated/precompiled-validators.js +704 -342
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +1 -0
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/event-stream/event-emitter-event-log.js +151 -20
- package/dist/esm/src/event-stream/event-emitter-event-log.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +7 -0
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/messages-sync.js +6 -3
- package/dist/esm/src/handlers/messages-sync.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +1 -1
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +7 -0
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +33 -5
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/store/storage-controller.js +1 -1
- package/dist/esm/src/store/storage-controller.js.map +1 -1
- package/dist/esm/src/utils/messages.js +41 -1
- package/dist/esm/src/utils/messages.js.map +1 -1
- package/dist/esm/tests/event-emitter-event-log.spec.js +214 -81
- package/dist/esm/tests/event-emitter-event-log.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +288 -0
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts +50 -34
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +1 -0
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/event-stream/event-emitter-event-log.d.ts +34 -4
- package/dist/types/src/event-stream/event-emitter-event-log.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts +4 -3
- package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +4 -3
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +14 -4
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +8 -4
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +73 -20
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/messages.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/utils/test-data-generator.d.ts +5 -4
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/dwn-error.ts +1 -0
- package/src/event-stream/event-emitter-event-log.ts +169 -21
- package/src/handlers/messages-subscribe.ts +8 -1
- package/src/handlers/messages-sync.ts +6 -3
- package/src/handlers/protocols-configure.ts +1 -1
- package/src/handlers/records-subscribe.ts +8 -1
- package/src/handlers/records-write.ts +34 -5
- package/src/index.ts +1 -1
- package/src/interfaces/messages-subscribe.ts +4 -3
- package/src/interfaces/records-subscribe.ts +4 -3
- package/src/store/storage-controller.ts +1 -1
- package/src/types/messages-types.ts +12 -4
- package/src/types/records-types.ts +6 -4
- package/src/types/subscriptions.ts +78 -20
- package/src/utils/messages.ts +47 -1
|
@@ -4,6 +4,7 @@ import type { HandlerDependencies, MethodHandler } from '../types/method-handler
|
|
|
4
4
|
import type { MessagesSyncDiffEntry, MessagesSyncMessage, MessagesSyncReply } from '../types/messages-types.js';
|
|
5
5
|
|
|
6
6
|
import { authenticate } from '../core/auth.js';
|
|
7
|
+
import { DwnConstant } from '../core/dwn-constant.js';
|
|
7
8
|
import { Encoder } from '../utils/encoder.js';
|
|
8
9
|
import { hashToHex } from '../smt/smt-utils.js';
|
|
9
10
|
import { messageReplyFromError } from '../core/message-reply.js';
|
|
@@ -13,11 +14,13 @@ import { PermissionsProtocol } from '../protocols/permissions.js';
|
|
|
13
14
|
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
+
* Maximum inline data size for diff responses — aligned with the
|
|
18
|
+
* {@link DwnConstant.maxDataSizeAllowedToBeEncoded} threshold (30 KB).
|
|
17
19
|
* RecordsWrite data payloads smaller than this are base64url-encoded and
|
|
18
|
-
* included directly in the diff reply
|
|
20
|
+
* included directly in the diff reply. Larger payloads must be fetched
|
|
21
|
+
* separately via MessagesRead.
|
|
19
22
|
*/
|
|
20
|
-
const DEFAULT_MAX_INLINE_DATA_SIZE =
|
|
23
|
+
const DEFAULT_MAX_INLINE_DATA_SIZE = DwnConstant.maxDataSizeAllowedToBeEncoded;
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
export class MessagesSyncHandler implements MethodHandler {
|
|
@@ -83,7 +83,7 @@ export class ProtocolsConfigureHandler implements MethodHandler {
|
|
|
83
83
|
|
|
84
84
|
// only emit if the event log is set
|
|
85
85
|
if (this.deps.eventLog !== undefined) {
|
|
86
|
-
await this.deps.eventLog.emit(tenant, { message }, indexes);
|
|
86
|
+
await this.deps.eventLog.emit(tenant, { message }, indexes, messageCid);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
messageReply = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { CoreProtocolRegistry } from '../core/core-protocol.js';
|
|
2
2
|
import type { MessageSort } from '../types/message-types.js';
|
|
3
3
|
import type { MessageStore } from '../types//message-store.js';
|
|
4
|
-
import type { SubscriptionListener } from '../types/subscriptions.js';
|
|
5
4
|
import type { Filter, PaginationCursor } from '../types/query-types.js';
|
|
6
5
|
import type { HandlerDependencies, MethodHandler } from '../types/method-handler.js';
|
|
6
|
+
import type { ProgressGapInfo, SubscriptionListener } from '../types/subscriptions.js';
|
|
7
7
|
import type { RecordsQueryReplyEntry, RecordsSubscribeMessage, RecordsSubscribeReply } from '../types/records-types.js';
|
|
8
8
|
|
|
9
9
|
import { authenticate } from '../core/auth.js';
|
|
@@ -93,6 +93,13 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
93
93
|
subscription,
|
|
94
94
|
};
|
|
95
95
|
} catch (error) {
|
|
96
|
+
if (error instanceof DwnError && error.code === DwnErrorCode.EventLogProgressGap) {
|
|
97
|
+
const gapInfo = (error as any).gapInfo as ProgressGapInfo | undefined;
|
|
98
|
+
return {
|
|
99
|
+
status : { code: 410, detail: 'Progress token gap' },
|
|
100
|
+
error : gapInfo !== undefined ? { code: 'ProgressGap' as const, ...gapInfo } : undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
96
103
|
return messageReplyFromError(error, 500);
|
|
97
104
|
}
|
|
98
105
|
}
|
|
@@ -91,9 +91,32 @@ export class RecordsWriteHandler implements MethodHandler {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
if (!incomingMessageIsNewest) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// Allow re-processing when the existing record was stored as an
|
|
95
|
+
// initial write without data (isLatestBaseState = false, status 204)
|
|
96
|
+
// and the incoming message now supplies data. This happens during
|
|
97
|
+
// sync when a live pull initially stores the message without data
|
|
98
|
+
// and a subsequent poll or retry delivers the same message with data.
|
|
99
|
+
//
|
|
100
|
+
// We detect the incomplete state by checking whether the existing
|
|
101
|
+
// message is an initial write that lacks both inline encodedData and
|
|
102
|
+
// DataStore data — indicating it was stored without data.
|
|
103
|
+
let existingLacksData = false;
|
|
104
|
+
if (newestExistingMessage !== undefined && dataStream !== undefined) {
|
|
105
|
+
const isInitial = await RecordsWrite.isInitialWrite(newestExistingMessage);
|
|
106
|
+
if (isInitial) {
|
|
107
|
+
const hasInlineData = !!(newestExistingMessage as any).encodedData;
|
|
108
|
+
const hasStoredData = this.deps.dataStore
|
|
109
|
+
? !!(await this.deps.dataStore.get(tenant, recordsWrite.message.recordId, message.descriptor.dataCid!))
|
|
110
|
+
: false;
|
|
111
|
+
existingLacksData = !hasInlineData && !hasStoredData;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!existingLacksData) {
|
|
116
|
+
return {
|
|
117
|
+
status: { code: 409, detail: 'Conflict' }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
// Look up the core protocol (if any) for the incoming message so that lifecycle hooks
|
|
@@ -142,13 +165,19 @@ export class RecordsWriteHandler implements MethodHandler {
|
|
|
142
165
|
|
|
143
166
|
const indexes = await recordsWrite.constructIndexes(isLatestBaseState);
|
|
144
167
|
await this.deps.messageStore.put(tenant, messageWithOptionalEncodedData, indexes);
|
|
145
|
-
|
|
168
|
+
const messageCid = await Message.getCid(message);
|
|
169
|
+
await this.deps.stateIndex!.insert(tenant, messageCid, indexes);
|
|
146
170
|
|
|
147
171
|
// NOTE: We only emit a `RecordsWrite` when the message is the latest base state.
|
|
148
172
|
// Because we allow a `RecordsWrite` which is not the latest state to be written, but not queried, we shouldn't emit it either.
|
|
149
173
|
// It will be emitted as a part of a subsequent next write, if it is the latest base state.
|
|
174
|
+
//
|
|
175
|
+
// We emit `messageWithOptionalEncodedData` (not the raw `message`) so
|
|
176
|
+
// that WebSocket subscribers receive inline `encodedData` for small
|
|
177
|
+
// records (<= 30 KB). This allows live sync to store the record
|
|
178
|
+
// immediately without a separate MessagesRead round-trip.
|
|
150
179
|
if (this.deps.eventLog !== undefined && isLatestBaseState) {
|
|
151
|
-
await this.deps.eventLog.emit(tenant, { message, initialWrite }, indexes);
|
|
180
|
+
await this.deps.eventLog.emit(tenant, { message: messageWithOptionalEncodedData, initialWrite }, indexes, messageCid);
|
|
152
181
|
}
|
|
153
182
|
} catch (error) {
|
|
154
183
|
if (error instanceof DwnError) {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// export everything that we want to be consumable
|
|
2
2
|
export type { DwnConfig } from './dwn.js';
|
|
3
|
-
export type { EventListener, EventLog, EventLogEntry, EventLogReadOptions, EventLogReadResult, EventLogSubscribeOptions, EventSubscription, MessageEvent, SubscriptionEose, SubscriptionEvent, SubscriptionListener, SubscriptionMessage, SubscriptionReply } from './types/subscriptions.js';
|
|
3
|
+
export type { EventListener, EventLog, EventLogEntry, EventLogReadOptions, EventLogReadResult, EventLogSubscribeOptions, EventSubscription, MessageEvent, ProgressGapInfo, ProgressGapReason, ProgressToken, SubscriptionEose, SubscriptionEvent, SubscriptionListener, SubscriptionMessage, SubscriptionReply } from './types/subscriptions.js';
|
|
4
4
|
export type { AuthorizationModel, Descriptor, DelegatedGrantRecordsWriteMessage, GenericMessage, GenericMessageReply, GenericSignaturePayload, MessageSort, MessageSubscription, Pagination, QueryResultEntry, Status } from './types/message-types.js';
|
|
5
5
|
export type { MessagesFilter, MessagesReadMessage as MessagesReadMessage, MessagesReadReply as MessagesReadReply, MessagesReadReplyEntry as MessagesReadReplyEntry, MessagesReadDescriptor, MessagesSubscribeDescriptor, MessagesSubscribeMessage, MessagesSubscribeReply, MessagesSubscribeMessageOptions, MessagesSyncAction, MessagesSyncDescriptor, MessagesSyncDiffEntry, MessagesSyncMessage, MessagesSyncReply } from './types/messages-types.js';
|
|
6
6
|
export type { GT, LT, Filter, FilterValue, KeyValues, EqualFilter, OneOfFilter, RangeFilter, RangeCriterion, PaginationCursor, QueryOptions, RangeValue, StartsWithFilter } from './types/query-types.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MessagesFilter } from '../types/messages-types.js';
|
|
2
2
|
import type { MessageSigner } from '../types/signer.js';
|
|
3
|
+
import type { ProgressToken } from '../types/subscriptions.js';
|
|
3
4
|
import type { MessagesSubscribeDescriptor, MessagesSubscribeMessage } from '../types/messages-types.js';
|
|
4
5
|
|
|
5
6
|
import { AbstractMessage } from '../core/abstract-message.js';
|
|
@@ -16,10 +17,10 @@ export type MessagesSubscribeOptions = {
|
|
|
16
17
|
filters?: MessagesFilter[];
|
|
17
18
|
permissionGrantId?: string;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
20
|
+
* Progress token to resume from. When provided, catch-up events are replayed
|
|
21
|
+
* from the EventLog and an EOSE marker is delivered before live events.
|
|
21
22
|
*/
|
|
22
|
-
cursor?:
|
|
23
|
+
cursor?: ProgressToken;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
export class MessagesSubscribe extends AbstractMessage<MessagesSubscribeMessage> {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MessageSigner } from '../types/signer.js';
|
|
2
2
|
import type { MessageStore } from '../types/message-store.js';
|
|
3
3
|
import type { Pagination } from '../types/message-types.js';
|
|
4
|
+
import type { ProgressToken } from '../types/subscriptions.js';
|
|
4
5
|
import type { DataEncodedRecordsWriteMessage, DateSort, RecordsFilter, RecordsSubscribeDescriptor, RecordsSubscribeMessage } from '../types/records-types.js';
|
|
5
6
|
|
|
6
7
|
import { AbstractMessage } from '../core/abstract-message.js';
|
|
@@ -23,10 +24,10 @@ export type RecordsSubscribeOptions = {
|
|
|
23
24
|
protocolRole?: string;
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* Progress token to resume from. When provided, catch-up events are replayed
|
|
28
|
+
* from the EventLog and an EOSE marker is delivered before live events.
|
|
28
29
|
*/
|
|
29
|
-
cursor?:
|
|
30
|
+
cursor?: ProgressToken;
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* The delegated grant to sign on behalf of the logical author, which is the grantor (`grantedBy`) of the delegated grant.
|
|
@@ -75,7 +75,7 @@ export class StorageController {
|
|
|
75
75
|
|
|
76
76
|
// only emit if the event log is set
|
|
77
77
|
if (this.eventLog !== undefined) {
|
|
78
|
-
await this.eventLog.emit(tenant, { message, initialWrite }, indexes);
|
|
78
|
+
await this.eventLog.emit(tenant, { message, initialWrite }, indexes, messageCid);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (message.descriptor.prune) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RangeCriterion } from './query-types.js';
|
|
2
|
-
import type { SubscriptionListener } from './subscriptions.js';
|
|
3
2
|
import type { AuthorizationModel, GenericMessage, GenericMessageReply, MessageSubscription } from './message-types.js';
|
|
4
3
|
import type { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
4
|
+
import type { ProgressGapInfo, ProgressToken, SubscriptionListener } from './subscriptions.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* filters used when filtering for any type of Message across interfaces
|
|
@@ -10,6 +10,12 @@ export type MessagesFilter = {
|
|
|
10
10
|
interface?: string;
|
|
11
11
|
method?: string;
|
|
12
12
|
protocol?: string;
|
|
13
|
+
/** Prefix filter for protocolPath. Matches records whose protocolPath equals
|
|
14
|
+
* the prefix or starts with the prefix followed by '/'. */
|
|
15
|
+
protocolPathPrefix?: string;
|
|
16
|
+
/** Prefix filter for contextId. Matches records whose contextId equals
|
|
17
|
+
* the prefix or starts with the prefix followed by '/'. */
|
|
18
|
+
contextIdPrefix?: string;
|
|
13
19
|
messageTimestamp?: RangeCriterion;
|
|
14
20
|
};
|
|
15
21
|
|
|
@@ -100,6 +106,8 @@ export type MessagesSubscribeMessage = {
|
|
|
100
106
|
|
|
101
107
|
export type MessagesSubscribeReply = GenericMessageReply & {
|
|
102
108
|
subscription?: MessageSubscription;
|
|
109
|
+
/** Present when status.code is 410 — structured gap metadata. */
|
|
110
|
+
error?: { code: 'ProgressGap' } & ProgressGapInfo;
|
|
103
111
|
};
|
|
104
112
|
|
|
105
113
|
export type MessagesSubscribeDescriptor = {
|
|
@@ -109,9 +117,9 @@ export type MessagesSubscribeDescriptor = {
|
|
|
109
117
|
filters: MessagesFilter[];
|
|
110
118
|
permissionGrantId?: string;
|
|
111
119
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
120
|
+
* Progress token to resume from. When provided, the handler replays events
|
|
121
|
+
* from the EventLog starting after this position instead of returning no
|
|
114
122
|
* initial snapshot. An EOSE marker is sent after catch-up.
|
|
115
123
|
*/
|
|
116
|
-
cursor?:
|
|
124
|
+
cursor?: ProgressToken;
|
|
117
125
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { GeneralJws } from './jws-types.js';
|
|
2
2
|
import type { JweEncryption } from '../utils/encryption.js';
|
|
3
|
-
import type { SubscriptionListener } from './subscriptions.js';
|
|
4
3
|
import type { AuthorizationModel, GenericMessage, GenericMessageReply, GenericSignaturePayload, MessageSubscription, Pagination } from './message-types.js';
|
|
5
4
|
import type { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
6
5
|
import type { PaginationCursor, RangeCriterion, RangeFilter, StartsWithFilter } from './query-types.js';
|
|
6
|
+
import type { ProgressGapInfo, ProgressToken, SubscriptionListener } from './subscriptions.js';
|
|
7
7
|
|
|
8
8
|
export enum DateSort {
|
|
9
9
|
CreatedAscending = 'createdAscending',
|
|
@@ -132,11 +132,11 @@ export type RecordsSubscribeDescriptor = {
|
|
|
132
132
|
dateSort?: DateSort;
|
|
133
133
|
pagination?: Pagination;
|
|
134
134
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
135
|
+
* Progress token to resume from. When provided, the handler replays events
|
|
136
|
+
* from the EventLog starting after this position instead of querying the
|
|
137
137
|
* MessageStore for an initial snapshot. An EOSE marker is sent after catch-up.
|
|
138
138
|
*/
|
|
139
|
-
cursor?:
|
|
139
|
+
cursor?: ProgressToken;
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
export type RecordsFilter = {
|
|
@@ -203,6 +203,8 @@ export type RecordsSubscribeReply = GenericMessageReply & {
|
|
|
203
203
|
subscription?: MessageSubscription;
|
|
204
204
|
entries?: RecordsQueryReplyEntry[];
|
|
205
205
|
cursor?: PaginationCursor;
|
|
206
|
+
/** Present when status.code is 410 — structured gap metadata. */
|
|
207
|
+
error?: { code: 'ProgressGap' } & ProgressGapInfo;
|
|
206
208
|
};
|
|
207
209
|
|
|
208
210
|
export type RecordsReadMessage = {
|
|
@@ -2,6 +2,54 @@ import type { RecordsWriteMessage } from './records-types.js';
|
|
|
2
2
|
import type { Filter, KeyValues } from './query-types.js';
|
|
3
3
|
import type { GenericMessage, GenericMessageReply, MessageSubscription } from './message-types.js';
|
|
4
4
|
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// ProgressToken — structured cursor for EventLog replay/resume
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Structured cursor for EventLog replay and resume. Replaces the previous
|
|
11
|
+
* opaque `string` cursor to provide explicit ordering semantics required
|
|
12
|
+
* by causal frontier progression in multi-master sync.
|
|
13
|
+
*
|
|
14
|
+
* Comparisons are valid only when `streamId` and `epoch` are equal.
|
|
15
|
+
* `position` is compared numerically (BigInt-safe), never lexicographically.
|
|
16
|
+
* Progress tokens are source-local: they MUST NOT be reused across different
|
|
17
|
+
* remote providers or EventLog instances.
|
|
18
|
+
*/
|
|
19
|
+
export type ProgressToken = {
|
|
20
|
+
/** Stable identity of the source event stream domain. */
|
|
21
|
+
streamId : string;
|
|
22
|
+
/** Stream generation/version; changes on non-compatible reset. */
|
|
23
|
+
epoch : string;
|
|
24
|
+
/** Monotonic decimal string within `(streamId, epoch)`. Compared numerically. */
|
|
25
|
+
position : string;
|
|
26
|
+
/** The CID of the message associated with this event. */
|
|
27
|
+
messageCid : string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Reason code for a {@link ProgressGapInfo} — explains why the cursor
|
|
32
|
+
* cannot be resumed.
|
|
33
|
+
*/
|
|
34
|
+
export type ProgressGapReason = 'token_too_old' | 'epoch_mismatch' | 'stream_mismatch';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Metadata attached to a `DwnError(DwnErrorCode.EventLogProgressGap, ...)`
|
|
38
|
+
* when an EventLog implementation cannot resume from a given cursor.
|
|
39
|
+
*
|
|
40
|
+
* Subscribe handlers translate this into a 410 response.
|
|
41
|
+
*/
|
|
42
|
+
export type ProgressGapInfo = {
|
|
43
|
+
/** The cursor the consumer requested. */
|
|
44
|
+
requested : ProgressToken;
|
|
45
|
+
/** The oldest token still available for replay. */
|
|
46
|
+
oldestAvailable : ProgressToken;
|
|
47
|
+
/** The latest token available. */
|
|
48
|
+
latestAvailable : ProgressToken;
|
|
49
|
+
/** Why the cursor is no longer valid. */
|
|
50
|
+
reason : ProgressGapReason;
|
|
51
|
+
};
|
|
52
|
+
|
|
5
53
|
/**
|
|
6
54
|
* Internal listener type used by {@link EventLog.emit} to notify in-process
|
|
7
55
|
* subscribers. Not intended for direct consumer use — consumers should use
|
|
@@ -32,17 +80,16 @@ export type SubscriptionReply = GenericMessageReply & {
|
|
|
32
80
|
// ---------------------------------------------------------------------------
|
|
33
81
|
|
|
34
82
|
/**
|
|
35
|
-
* A regular subscription event carrying a message and its EventLog
|
|
83
|
+
* A regular subscription event carrying a message and its EventLog progress token.
|
|
36
84
|
*/
|
|
37
85
|
export type SubscriptionEvent = {
|
|
38
86
|
type : 'event';
|
|
39
87
|
/**
|
|
40
|
-
*
|
|
41
|
-
* persist this value and pass it back to `subscribe()` or `read()` to
|
|
42
|
-
* from this point.
|
|
43
|
-
* for in-memory, Redis stream ID, NATS stream sequence, etc.).
|
|
88
|
+
* Structured progress token assigned by the EventLog implementation. Clients
|
|
89
|
+
* should persist this value and pass it back to `subscribe()` or `read()` to
|
|
90
|
+
* resume from this point.
|
|
44
91
|
*/
|
|
45
|
-
cursor :
|
|
92
|
+
cursor : ProgressToken;
|
|
46
93
|
/** The event payload (message + optional initialWrite). */
|
|
47
94
|
event : MessageEvent;
|
|
48
95
|
};
|
|
@@ -57,10 +104,10 @@ export type SubscriptionEvent = {
|
|
|
57
104
|
export type SubscriptionEose = {
|
|
58
105
|
type : 'eose';
|
|
59
106
|
/**
|
|
60
|
-
*
|
|
107
|
+
* Progress token of the last stored event that was replayed.
|
|
61
108
|
* Echoes the input cursor when no stored events matched (i.e. already caught up).
|
|
62
109
|
*/
|
|
63
|
-
cursor :
|
|
110
|
+
cursor : ProgressToken;
|
|
64
111
|
};
|
|
65
112
|
|
|
66
113
|
/**
|
|
@@ -80,15 +127,15 @@ export type SubscriptionListener = (message: SubscriptionMessage) => void;
|
|
|
80
127
|
*/
|
|
81
128
|
export type EventLogSubscribeOptions = {
|
|
82
129
|
/**
|
|
83
|
-
*
|
|
130
|
+
* Progress token to resume from (exclusive — events after this position
|
|
84
131
|
* are replayed). When provided, stored events are replayed first, followed by
|
|
85
132
|
* an EOSE marker, then live events. When omitted, only live events are delivered.
|
|
86
133
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
134
|
+
* Tokens must be obtained from a prior interaction with the same EventLog
|
|
135
|
+
* instance (e.g. `SubscriptionEvent.cursor`, `EventLogReadResult.cursor`,
|
|
136
|
+
* or the return value of `emit()`).
|
|
90
137
|
*/
|
|
91
|
-
cursor? :
|
|
138
|
+
cursor? : ProgressToken;
|
|
92
139
|
|
|
93
140
|
/**
|
|
94
141
|
* Filters evaluated against event indexes. Events must match at least one
|
|
@@ -119,8 +166,8 @@ export type EventLogEntry = {
|
|
|
119
166
|
* Options accepted by {@link EventLog.read}.
|
|
120
167
|
*/
|
|
121
168
|
export type EventLogReadOptions = {
|
|
122
|
-
/**
|
|
123
|
-
cursor? :
|
|
169
|
+
/** Progress token to resume from (exclusive — returns events after this position). */
|
|
170
|
+
cursor? : ProgressToken;
|
|
124
171
|
|
|
125
172
|
/** Maximum number of events to return. */
|
|
126
173
|
limit? : number;
|
|
@@ -137,14 +184,14 @@ export type EventLogReadResult = {
|
|
|
137
184
|
events : EventLogEntry[];
|
|
138
185
|
|
|
139
186
|
/**
|
|
140
|
-
*
|
|
187
|
+
* Progress token for resuming subsequent reads or subscriptions.
|
|
141
188
|
*
|
|
142
|
-
* - When events are returned:
|
|
189
|
+
* - When events are returned: token of the last event.
|
|
143
190
|
* - When no events are returned but a cursor was provided: the input cursor
|
|
144
191
|
* (meaning "you are caught up, nothing new since this point").
|
|
145
192
|
* - When no events exist and no cursor was provided: `undefined`.
|
|
146
193
|
*/
|
|
147
|
-
cursor? :
|
|
194
|
+
cursor? : ProgressToken;
|
|
148
195
|
};
|
|
149
196
|
|
|
150
197
|
/**
|
|
@@ -162,9 +209,13 @@ export type EventLogReadResult = {
|
|
|
162
209
|
export interface EventLog {
|
|
163
210
|
/**
|
|
164
211
|
* Persist an event and notify in-process subscribers.
|
|
165
|
-
* @
|
|
212
|
+
* @param tenant The tenant DID.
|
|
213
|
+
* @param event The event payload.
|
|
214
|
+
* @param indexes Index values for the event.
|
|
215
|
+
* @param messageCid The CID of the message being emitted — embedded in the returned token.
|
|
216
|
+
* @returns A {@link ProgressToken} assigned to the event, or `undefined` on failure.
|
|
166
217
|
*/
|
|
167
|
-
emit(tenant: string, event: MessageEvent, indexes: KeyValues): Promise<
|
|
218
|
+
emit(tenant: string, event: MessageEvent, indexes: KeyValues, messageCid: string): Promise<ProgressToken | undefined>;
|
|
168
219
|
|
|
169
220
|
/**
|
|
170
221
|
* Read events from the log starting after `cursor`, optionally filtered.
|
|
@@ -188,6 +239,13 @@ export interface EventLog {
|
|
|
188
239
|
*/
|
|
189
240
|
subscribe(tenant: string, id: string, listener: SubscriptionListener, options?: EventLogSubscribeOptions): Promise<EventSubscription>;
|
|
190
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Returns the oldest and latest available progress tokens for a tenant,
|
|
244
|
+
* or `undefined` if the tenant has no events. Used to construct
|
|
245
|
+
* `ProgressGap` metadata when a consumer's cursor is no longer replayable.
|
|
246
|
+
*/
|
|
247
|
+
getReplayBounds(tenant: string): Promise<{ oldest: ProgressToken; latest: ProgressToken } | undefined>;
|
|
248
|
+
|
|
191
249
|
/**
|
|
192
250
|
* Delete events older than the given sequence number or ISO-8601 timestamp.
|
|
193
251
|
*/
|
package/src/utils/messages.ts
CHANGED
|
@@ -67,6 +67,29 @@ export class Messages {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
messagesQueryFilters.push(this.convertFilter(filter));
|
|
70
|
+
|
|
71
|
+
// When protocolPathPrefix is used with a protocol, inject a shadow filter
|
|
72
|
+
// for ProtocolsConfigure events. Without this, protocol metadata updates
|
|
73
|
+
// would be excluded (ProtocolsConfigure indexes have no protocolPath).
|
|
74
|
+
// This mirrors the existing core-protocol additional-filter pattern above.
|
|
75
|
+
// The messageTimestamp constraint is carried over so time-bounded queries
|
|
76
|
+
// (including cursor-based subscriptions) also apply to the shadow filter.
|
|
77
|
+
if ((filter.protocolPathPrefix !== undefined || filter.contextIdPrefix !== undefined) && filter.protocol !== undefined) {
|
|
78
|
+
const metadataFilter: Filter = {
|
|
79
|
+
interface : 'Protocols',
|
|
80
|
+
method : 'Configure',
|
|
81
|
+
protocol : filter.protocol,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (filter.messageTimestamp !== undefined) {
|
|
85
|
+
const timestampFilter = FilterUtility.convertRangeCriterion(filter.messageTimestamp);
|
|
86
|
+
if (timestampFilter) {
|
|
87
|
+
metadataFilter.messageTimestamp = timestampFilter;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
messagesQueryFilters.push(metadataFilter);
|
|
92
|
+
}
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
return messagesQueryFilters;
|
|
@@ -78,12 +101,35 @@ export class Messages {
|
|
|
78
101
|
private static convertFilter(filter: MessagesFilter): Filter {
|
|
79
102
|
const filterCopy = { ...filter } as Filter;
|
|
80
103
|
|
|
81
|
-
const { messageTimestamp } = filter;
|
|
104
|
+
const { messageTimestamp, protocolPathPrefix, contextIdPrefix } = filter;
|
|
82
105
|
const messageTimestampFilter = messageTimestamp ? FilterUtility.convertRangeCriterion(messageTimestamp) : undefined;
|
|
83
106
|
if (messageTimestampFilter) {
|
|
84
107
|
filterCopy.messageTimestamp = messageTimestampFilter;
|
|
85
108
|
delete filterCopy.dateUpdated;
|
|
86
109
|
}
|
|
110
|
+
|
|
111
|
+
// Convert protocolPathPrefix into a protocolPath range filter.
|
|
112
|
+
// The range gte: prefix, lt: prefix + '/\uffff' matches:
|
|
113
|
+
// - exact: 'post' (prefix itself)
|
|
114
|
+
// - children: 'post/attachment', 'post/comment', etc.
|
|
115
|
+
// - NOT siblings: 'poster', 'postfix' (excluded because '/' < any alphanumeric)
|
|
116
|
+
if (protocolPathPrefix !== undefined) {
|
|
117
|
+
delete (filterCopy as any).protocolPathPrefix;
|
|
118
|
+
filterCopy.protocolPath = {
|
|
119
|
+
gte : protocolPathPrefix,
|
|
120
|
+
lt : protocolPathPrefix + '/\uffff',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Convert contextIdPrefix into a contextId range filter (same pattern).
|
|
125
|
+
if (contextIdPrefix !== undefined) {
|
|
126
|
+
delete (filterCopy as any).contextIdPrefix;
|
|
127
|
+
filterCopy.contextId = {
|
|
128
|
+
gte : contextIdPrefix,
|
|
129
|
+
lt : contextIdPrefix + '/\uffff',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
87
133
|
return filterCopy as Filter;
|
|
88
134
|
}
|
|
89
135
|
}
|