@enbox/dwn-sdk-js 0.3.0 → 0.3.2

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 (69) hide show
  1. package/dist/browser.mjs +6 -6
  2. package/dist/browser.mjs.map +3 -3
  3. package/dist/esm/generated/precompiled-validators.js +704 -342
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/src/core/dwn-error.js +1 -0
  6. package/dist/esm/src/core/dwn-error.js.map +1 -1
  7. package/dist/esm/src/event-stream/event-emitter-event-log.js +152 -22
  8. package/dist/esm/src/event-stream/event-emitter-event-log.js.map +1 -1
  9. package/dist/esm/src/handlers/messages-subscribe.js +7 -0
  10. package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
  11. package/dist/esm/src/handlers/protocols-configure.js +1 -1
  12. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  13. package/dist/esm/src/handlers/records-subscribe.js +7 -0
  14. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  15. package/dist/esm/src/handlers/records-write.js +3 -2
  16. package/dist/esm/src/handlers/records-write.js.map +1 -1
  17. package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
  18. package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
  19. package/dist/esm/src/store/storage-controller.js +1 -1
  20. package/dist/esm/src/store/storage-controller.js.map +1 -1
  21. package/dist/esm/src/utils/messages.js +41 -1
  22. package/dist/esm/src/utils/messages.js.map +1 -1
  23. package/dist/esm/tests/event-emitter-event-log.spec.js +278 -84
  24. package/dist/esm/tests/event-emitter-event-log.spec.js.map +1 -1
  25. package/dist/esm/tests/handlers/messages-subscribe.spec.js +288 -0
  26. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  27. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  28. package/dist/types/generated/precompiled-validators.d.ts +50 -34
  29. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  30. package/dist/types/src/core/dwn-error.d.ts +1 -0
  31. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  32. package/dist/types/src/event-stream/event-emitter-event-log.d.ts +34 -4
  33. package/dist/types/src/event-stream/event-emitter-event-log.d.ts.map +1 -1
  34. package/dist/types/src/handlers/messages-subscribe.d.ts +1 -1
  35. package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
  36. package/dist/types/src/handlers/records-subscribe.d.ts +1 -1
  37. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  38. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  39. package/dist/types/src/index.d.ts +1 -1
  40. package/dist/types/src/index.d.ts.map +1 -1
  41. package/dist/types/src/interfaces/messages-subscribe.d.ts +4 -3
  42. package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
  43. package/dist/types/src/interfaces/records-subscribe.d.ts +4 -3
  44. package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
  45. package/dist/types/src/types/messages-types.d.ts +14 -4
  46. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  47. package/dist/types/src/types/records-types.d.ts +8 -4
  48. package/dist/types/src/types/records-types.d.ts.map +1 -1
  49. package/dist/types/src/types/subscriptions.d.ts +80 -20
  50. package/dist/types/src/types/subscriptions.d.ts.map +1 -1
  51. package/dist/types/src/utils/messages.d.ts.map +1 -1
  52. package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
  53. package/dist/types/tests/utils/test-data-generator.d.ts +5 -4
  54. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/core/dwn-error.ts +1 -0
  57. package/src/event-stream/event-emitter-event-log.ts +174 -27
  58. package/src/handlers/messages-subscribe.ts +8 -1
  59. package/src/handlers/protocols-configure.ts +1 -1
  60. package/src/handlers/records-subscribe.ts +8 -1
  61. package/src/handlers/records-write.ts +3 -2
  62. package/src/index.ts +1 -1
  63. package/src/interfaces/messages-subscribe.ts +4 -3
  64. package/src/interfaces/records-subscribe.ts +4 -3
  65. package/src/store/storage-controller.ts +1 -1
  66. package/src/types/messages-types.ts +12 -4
  67. package/src/types/records-types.ts +6 -4
  68. package/src/types/subscriptions.ts +86 -20
  69. package/src/utils/messages.ts +47 -1
@@ -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 cursor.
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
- * Opaque cursor string assigned by the EventLog implementation. Clients should
41
- * persist this value and pass it back to `subscribe()` or `read()` to resume
42
- * from this point. The format is implementation-defined (e.g. numeric sequence
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 : string;
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
- * Opaque cursor string of the last stored event that was replayed.
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 : string;
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
- * Opaque cursor string to resume from (exclusive — events after this cursor
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
- * Cursor values are implementation-defined and must be obtained from a prior
88
- * interaction with the same EventLog instance (e.g. `SubscriptionEvent.cursor`,
89
- * `EventLogReadResult.cursor`, or the return value of `emit()`).
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? : string;
138
+ cursor? : ProgressToken;
92
139
 
93
140
  /**
94
141
  * Filters evaluated against event indexes. Events must match at least one
@@ -113,14 +160,22 @@ export type EventLogEntry = {
113
160
 
114
161
  /** Indexes associated with the event (used for filter matching). */
115
162
  indexes: KeyValues;
163
+
164
+ /**
165
+ * The CID of the message that produced this event. Populated by
166
+ * implementations that track it (e.g. {@link EventEmitterEventLog}).
167
+ * Consumers should fall back to computing the CID from the message
168
+ * if this is absent.
169
+ */
170
+ messageCid?: string;
116
171
  };
117
172
 
118
173
  /**
119
174
  * Options accepted by {@link EventLog.read}.
120
175
  */
121
176
  export type EventLogReadOptions = {
122
- /** Opaque cursor string to resume from (exclusive — returns events after this cursor). */
123
- cursor? : string;
177
+ /** Progress token to resume from (exclusive — returns events after this position). */
178
+ cursor? : ProgressToken;
124
179
 
125
180
  /** Maximum number of events to return. */
126
181
  limit? : number;
@@ -137,14 +192,14 @@ export type EventLogReadResult = {
137
192
  events : EventLogEntry[];
138
193
 
139
194
  /**
140
- * Opaque cursor string for resuming subsequent reads or subscriptions.
195
+ * Progress token for resuming subsequent reads or subscriptions.
141
196
  *
142
- * - When events are returned: cursor of the last event.
197
+ * - When events are returned: token of the last event.
143
198
  * - When no events are returned but a cursor was provided: the input cursor
144
199
  * (meaning "you are caught up, nothing new since this point").
145
200
  * - When no events exist and no cursor was provided: `undefined`.
146
201
  */
147
- cursor? : string;
202
+ cursor? : ProgressToken;
148
203
  };
149
204
 
150
205
  /**
@@ -162,9 +217,13 @@ export type EventLogReadResult = {
162
217
  export interface EventLog {
163
218
  /**
164
219
  * Persist an event and notify in-process subscribers.
165
- * @returns The opaque cursor string assigned to the event, or empty string on failure.
220
+ * @param tenant The tenant DID.
221
+ * @param event The event payload.
222
+ * @param indexes Index values for the event.
223
+ * @param messageCid The CID of the message being emitted — embedded in the returned token.
224
+ * @returns A {@link ProgressToken} assigned to the event, or `undefined` on failure.
166
225
  */
167
- emit(tenant: string, event: MessageEvent, indexes: KeyValues): Promise<string>;
226
+ emit(tenant: string, event: MessageEvent, indexes: KeyValues, messageCid: string): Promise<ProgressToken | undefined>;
168
227
 
169
228
  /**
170
229
  * Read events from the log starting after `cursor`, optionally filtered.
@@ -188,6 +247,13 @@ export interface EventLog {
188
247
  */
189
248
  subscribe(tenant: string, id: string, listener: SubscriptionListener, options?: EventLogSubscribeOptions): Promise<EventSubscription>;
190
249
 
250
+ /**
251
+ * Returns the oldest and latest available progress tokens for a tenant,
252
+ * or `undefined` if the tenant has no events. Used to construct
253
+ * `ProgressGap` metadata when a consumer's cursor is no longer replayable.
254
+ */
255
+ getReplayBounds(tenant: string): Promise<{ oldest: ProgressToken; latest: ProgressToken } | undefined>;
256
+
191
257
  /**
192
258
  * Delete events older than the given sequence number or ISO-8601 timestamp.
193
259
  */
@@ -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
  }