@kronos-ts/messaging 0.7.0 → 0.9.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.
- package/dist/event-processor.d.ts +32 -2
- package/dist/event-processor.d.ts.map +1 -1
- package/dist/event-processor.js +10 -0
- package/dist/event-processor.js.map +1 -1
- package/dist/event-source.d.ts +17 -0
- package/dist/event-source.d.ts.map +1 -1
- package/dist/event-source.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/streaming-event-processor.d.ts.map +1 -1
- package/dist/streaming-event-processor.js +10 -5
- package/dist/streaming-event-processor.js.map +1 -1
- package/dist/subscribing-event-processor.js +2 -2
- package/dist/subscribing-event-processor.js.map +1 -1
- package/dist/tracking-event-processor.d.ts +14 -6
- package/dist/tracking-event-processor.d.ts.map +1 -1
- package/dist/tracking-event-processor.js +39 -16
- package/dist/tracking-event-processor.js.map +1 -1
- package/dist/tracking-token.d.ts +54 -0
- package/dist/tracking-token.d.ts.map +1 -1
- package/dist/tracking-token.js +71 -8
- package/dist/tracking-token.js.map +1 -1
- package/package.json +2 -2
- package/src/event-processor.ts +34 -2
- package/src/event-source.ts +17 -0
- package/src/index.ts +9 -4
- package/src/streaming-event-processor.ts +10 -4
- package/src/subscribing-event-processor.ts +2 -2
- package/src/tracking-event-processor.ts +46 -20
- package/src/tracking-token.ts +112 -9
package/src/tracking-token.ts
CHANGED
|
@@ -77,6 +77,50 @@ export function globalSequenceToken(sequence: bigint): GlobalSequenceToken {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// GapAwareToken
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export interface GapAwareToken extends TrackingToken {
|
|
85
|
+
readonly kind: "gap-aware"
|
|
86
|
+
/** The `sequence_position` of the last consumed event — the `position()`. */
|
|
87
|
+
readonly sequence: bigint
|
|
88
|
+
/**
|
|
89
|
+
* An opaque, store-defined commit-order key that, paired with `sequence`,
|
|
90
|
+
* forms a gap-free tailing cursor. For the Postgres engine this is the
|
|
91
|
+
* event's `transaction_id` (xid8): the durable token MUST carry it because
|
|
92
|
+
* gap-free tailing orders by `(transaction_id, sequence_position)` and only
|
|
93
|
+
* `transaction_id` has a commit watermark (`pg_snapshot_xmin`). A position
|
|
94
|
+
* alone cannot resume the stream without permanently skipping events whose
|
|
95
|
+
* `sequence_position` is lower but whose `transaction_id` is higher (the
|
|
96
|
+
* xid/seq inversion that happens when a transaction writes other rows —
|
|
97
|
+
* stamping its xid — before appending its event).
|
|
98
|
+
*/
|
|
99
|
+
readonly gapKey: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates a token for a gap-free tailing engine: a `sequence` position paired
|
|
104
|
+
* with an opaque `gapKey` (the store's commit-order key, e.g. Postgres xid8).
|
|
105
|
+
* `position()` returns the sequence so replay/`covers` semantics are unchanged;
|
|
106
|
+
* the `gapKey` rides along so the engine can resume the `(gapKey, sequence)`
|
|
107
|
+
* cursor exactly on reopen instead of falling back to a lossy position filter.
|
|
108
|
+
*/
|
|
109
|
+
export function gapAwareToken(sequence: bigint, gapKey: string): GapAwareToken {
|
|
110
|
+
return {
|
|
111
|
+
kind: "gap-aware",
|
|
112
|
+
sequence,
|
|
113
|
+
gapKey,
|
|
114
|
+
position: () => sequence,
|
|
115
|
+
covers: (other) => sequence >= other.position(),
|
|
116
|
+
lowerBound: (other) =>
|
|
117
|
+
sequence <= other.position() ? gapAwareToken(sequence, gapKey) : globalSequenceToken(other.position()),
|
|
118
|
+
upperBound: (other) =>
|
|
119
|
+
sequence >= other.position() ? gapAwareToken(sequence, gapKey) : globalSequenceToken(other.position()),
|
|
120
|
+
samePositionAs: (other) => sequence === other.position(),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
80
124
|
/**
|
|
81
125
|
* Sentinel token representing the beginning of the event stream.
|
|
82
126
|
* A processor starting with FIRST_TOKEN will read from position 0.
|
|
@@ -171,28 +215,87 @@ export function isGlobalSequenceToken(token: TrackingToken): token is GlobalSequ
|
|
|
171
215
|
return token.kind === "global-sequence"
|
|
172
216
|
}
|
|
173
217
|
|
|
218
|
+
export function isGapAwareToken(token: TrackingToken): token is GapAwareToken {
|
|
219
|
+
return token.kind === "gap-aware"
|
|
220
|
+
}
|
|
221
|
+
|
|
174
222
|
// ---------------------------------------------------------------------------
|
|
175
223
|
// Token operations
|
|
176
224
|
// ---------------------------------------------------------------------------
|
|
177
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Advance a token to the position represented by `next`, preserving replay
|
|
228
|
+
* wrapping. Generalises {@link advanceToken} to any TrackingToken — used when
|
|
229
|
+
* the event source supplies its own cursor token (e.g. a {@link GapAwareToken}
|
|
230
|
+
* carrying a commit-order key) that must be persisted verbatim rather than
|
|
231
|
+
* collapsed to a bare position.
|
|
232
|
+
*/
|
|
233
|
+
export function advanceTokenTo(token: TrackingToken, next: TrackingToken): TrackingToken {
|
|
234
|
+
if (!isReplayToken(token)) {
|
|
235
|
+
return next
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if replay is complete
|
|
239
|
+
if (next.covers(token.tokenAtReset)) {
|
|
240
|
+
return next
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Still replaying — wrap the advanced token
|
|
244
|
+
return replayToken(token.tokenAtReset, next, token.resetContext)
|
|
245
|
+
}
|
|
246
|
+
|
|
178
247
|
/**
|
|
179
248
|
* Advance a token to a new position. If the token is a ReplayToken and
|
|
180
249
|
* the new position covers the reset point, unwraps to a plain token.
|
|
181
250
|
*/
|
|
182
251
|
export function advanceToken(token: TrackingToken, newPosition: bigint): TrackingToken {
|
|
183
|
-
|
|
252
|
+
return advanceTokenTo(token, globalSequenceToken(newPosition))
|
|
253
|
+
}
|
|
184
254
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Serialization
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
188
258
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
259
|
+
/** Wire shape for a persisted token: a `kind` discriminant + a JSON body. */
|
|
260
|
+
export interface SerializedToken {
|
|
261
|
+
readonly type: string
|
|
262
|
+
readonly data: string
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Serialize a token for durable storage. The JSON body always carries
|
|
267
|
+
* `position`; when the token (or, for a ReplayToken, its innermost token) is a
|
|
268
|
+
* {@link GapAwareToken}, the `gapKey` is preserved so the cursor resumes
|
|
269
|
+
* exactly on reload. ReplayTokens still flatten to their current position on
|
|
270
|
+
* the wire (replay-in-progress state does not survive a restart, as before),
|
|
271
|
+
* but the gapKey survives so live tailing resumes without skipping events.
|
|
272
|
+
*/
|
|
273
|
+
export function serializeToken(token: TrackingToken): SerializedToken {
|
|
274
|
+
const inner = unwrapToken(token)
|
|
275
|
+
const payload: { position: string; gapKey?: string } = {
|
|
276
|
+
position: token.position().toString(),
|
|
192
277
|
}
|
|
278
|
+
if (isGapAwareToken(inner)) {
|
|
279
|
+
payload.gapKey = inner.gapKey
|
|
280
|
+
}
|
|
281
|
+
return { type: token.kind, data: JSON.stringify(payload) }
|
|
282
|
+
}
|
|
193
283
|
|
|
194
|
-
|
|
195
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Reconstruct a token from its persisted form. A body carrying a `gapKey`
|
|
286
|
+
* rehydrates as a {@link GapAwareToken}; otherwise a {@link GlobalSequenceToken}.
|
|
287
|
+
* Returns undefined when there is no stored token.
|
|
288
|
+
*/
|
|
289
|
+
export function deserializeToken(
|
|
290
|
+
type: string | null | undefined,
|
|
291
|
+
data: string | null | undefined,
|
|
292
|
+
): TrackingToken | undefined {
|
|
293
|
+
if (!data) return undefined
|
|
294
|
+
const parsed = JSON.parse(data) as { position: string; gapKey?: string }
|
|
295
|
+
if (parsed.gapKey !== undefined) {
|
|
296
|
+
return gapAwareToken(BigInt(parsed.position), parsed.gapKey)
|
|
297
|
+
}
|
|
298
|
+
return globalSequenceToken(BigInt(parsed.position))
|
|
196
299
|
}
|
|
197
300
|
|
|
198
301
|
/**
|