@relaybus/core 0.3.0 → 0.4.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/README.md +6 -1
- package/dist/{chunk-3OHMEMZA.cjs → chunk-J76M7XTS.cjs} +127 -9
- package/dist/chunk-J76M7XTS.cjs.map +1 -0
- package/dist/{chunk-WS274DL3.js → chunk-NOR5JHVN.js} +121 -9
- package/dist/chunk-NOR5JHVN.js.map +1 -0
- package/dist/index.cjs +2301 -1903
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -475
- package/dist/index.d.ts +88 -475
- package/dist/index.js +2316 -1924
- package/dist/index.js.map +1 -1
- package/dist/internal/index.cjs +492 -140
- package/dist/internal/index.cjs.map +1 -1
- package/dist/internal/index.d.cts +321 -9
- package/dist/internal/index.d.ts +321 -9
- package/dist/internal/index.js +450 -124
- package/dist/internal/index.js.map +1 -1
- package/dist/transport-DIFdeXQY.d.cts +536 -0
- package/dist/transport-DIFdeXQY.d.ts +536 -0
- package/package.json +1 -1
- package/dist/chunk-3OHMEMZA.cjs.map +0 -1
- package/dist/chunk-WS274DL3.js.map +0 -1
- package/dist/event-DCRTuYKI.d.cts +0 -142
- package/dist/event-DCRTuYKI.d.ts +0 -142
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/** Source of the current time. Replace it in tests to control retries and leases. */
|
|
2
|
+
interface Clock {
|
|
3
|
+
now(): Date;
|
|
4
|
+
/**
|
|
5
|
+
* Runs `task.run` after `task.delayMs` milliseconds, again every `delayMs` when `repeat` is set, and
|
|
6
|
+
* returns a function that cancels it. `unref` marks a timer that must not keep the process alive.
|
|
7
|
+
* Optional: without it relaybus uses the platform timers. Implement it to control timers in tests
|
|
8
|
+
* (`FakeClock` does with `controlTimers: true`).
|
|
9
|
+
*/
|
|
10
|
+
schedule?(task: {
|
|
11
|
+
readonly run: () => void;
|
|
12
|
+
readonly delayMs: number;
|
|
13
|
+
readonly repeat?: boolean;
|
|
14
|
+
readonly unref?: boolean;
|
|
15
|
+
}): () => void;
|
|
16
|
+
}
|
|
17
|
+
declare const systemClock: Clock;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A duration in milliseconds, or a string such as `'250ms'`, `'30s'`, `'5m'`,
|
|
21
|
+
* `'1h'` or `'7d'`.
|
|
22
|
+
*/
|
|
23
|
+
type Duration = number | `${number}${DurationUnit}`;
|
|
24
|
+
type DurationUnit = 'ms' | 's' | 'm' | 'h' | 'd';
|
|
25
|
+
declare function toMilliseconds(value: Duration, name: string): number;
|
|
26
|
+
/** Largest delay Node.js timers support. Longer values fire after 1ms instead. */
|
|
27
|
+
declare const MAX_TIMER_DELAY_MS = 2147483647;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The Standard Schema interface (https://standardschema.dev), copied as the
|
|
31
|
+
* specification recommends so relaybus needs no runtime dependency. Zod,
|
|
32
|
+
* Valibot, ArkType and other libraries implement it.
|
|
33
|
+
*/
|
|
34
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
35
|
+
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
|
|
36
|
+
}
|
|
37
|
+
declare namespace StandardSchemaV1 {
|
|
38
|
+
interface Props<Input = unknown, Output = Input> {
|
|
39
|
+
readonly version: 1;
|
|
40
|
+
readonly vendor: string;
|
|
41
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
42
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
43
|
+
}
|
|
44
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
45
|
+
interface SuccessResult<Output> {
|
|
46
|
+
readonly value: Output;
|
|
47
|
+
readonly issues?: undefined;
|
|
48
|
+
}
|
|
49
|
+
interface FailureResult {
|
|
50
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
51
|
+
}
|
|
52
|
+
interface Issue {
|
|
53
|
+
readonly message: string;
|
|
54
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
55
|
+
}
|
|
56
|
+
interface PathSegment {
|
|
57
|
+
readonly key: PropertyKey;
|
|
58
|
+
}
|
|
59
|
+
interface Types<Input = unknown, Output = Input> {
|
|
60
|
+
readonly input: Input;
|
|
61
|
+
readonly output: Output;
|
|
62
|
+
}
|
|
63
|
+
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];
|
|
64
|
+
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** String metadata carried with an event, such as `traceparent`. */
|
|
68
|
+
type EventHeaders = Readonly<Record<string, string>>;
|
|
69
|
+
/**
|
|
70
|
+
* An event as stored and delivered. Field names follow CloudEvents where they
|
|
71
|
+
* overlap (`id`, `type`, `source`, `time` as `occurredAt`, `subject` as `key`).
|
|
72
|
+
*/
|
|
73
|
+
interface EventEnvelope<TPayload = unknown> {
|
|
74
|
+
/** Globally unique id. Handlers and transports deduplicate on it. */
|
|
75
|
+
readonly id: string;
|
|
76
|
+
/** Event type, for example `order.created.v1`. */
|
|
77
|
+
readonly type: string;
|
|
78
|
+
/** Logical name of the publishing service. */
|
|
79
|
+
readonly source: string;
|
|
80
|
+
/** Ordering and partition key. Events sharing a key are handled in publish order. */
|
|
81
|
+
readonly key: string | null;
|
|
82
|
+
readonly payload: TPayload;
|
|
83
|
+
readonly headers: EventHeaders;
|
|
84
|
+
/** Id shared by every event of one business flow. */
|
|
85
|
+
readonly correlationId: string | null;
|
|
86
|
+
/** Id of the event whose handler published this one. */
|
|
87
|
+
readonly causationId: string | null;
|
|
88
|
+
readonly occurredAt: Date;
|
|
89
|
+
}
|
|
90
|
+
/** An instance of a class created with {@link defineEvent}. */
|
|
91
|
+
interface DomainEvent<TPayload = unknown> {
|
|
92
|
+
readonly type: string;
|
|
93
|
+
readonly payload: TPayload;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Derives the ordering key of an event from its validated payload. Declared bivariantly so an
|
|
97
|
+
* event class stays assignable wherever its payload type is widened.
|
|
98
|
+
*/
|
|
99
|
+
type EventKeyFunction<TPayload> = {
|
|
100
|
+
bivarianceHack(payload: TPayload): string;
|
|
101
|
+
}['bivarianceHack'];
|
|
102
|
+
/** A class created with {@link defineEvent}. */
|
|
103
|
+
interface EventClass<TPayload = unknown> {
|
|
104
|
+
new (payload: TPayload): DomainEvent<TPayload>;
|
|
105
|
+
readonly type: string;
|
|
106
|
+
readonly schema: StandardSchemaV1<unknown, TPayload> | undefined;
|
|
107
|
+
/** The `key` given to {@link defineEvent}; undefined for events without one. */
|
|
108
|
+
readonly key?: EventKeyFunction<TPayload> | undefined;
|
|
109
|
+
}
|
|
110
|
+
/** Payload type of an event class: `PayloadOf<typeof OrderPlaced>`. */
|
|
111
|
+
type PayloadOf<TEvent> = TEvent extends EventClass<infer TPayload> ? TPayload : never;
|
|
112
|
+
/** Anything that names an event type: an event class or the type string itself. */
|
|
113
|
+
type EventReference = {
|
|
114
|
+
readonly type: string;
|
|
115
|
+
} | string;
|
|
116
|
+
interface DefineEventOptions<TSchema extends StandardSchemaV1> {
|
|
117
|
+
/** Validates the payload on publish and before every handler call. */
|
|
118
|
+
readonly schema: TSchema;
|
|
119
|
+
/** Ordering key of every event of this type, from its validated payload. See {@link DefineEventKeyOptions}. */
|
|
120
|
+
readonly key?: (payload: StandardSchemaV1.InferOutput<TSchema>) => string;
|
|
121
|
+
}
|
|
122
|
+
interface DefineEventKeyOptions<TPayload> {
|
|
123
|
+
/**
|
|
124
|
+
* Ordering key of every event of this type, from its payload: `(payload) => payload.orderId`.
|
|
125
|
+
* Typed from the payload, so a missing field or a non-string fails to compile. `publish` uses it
|
|
126
|
+
* when it is given no `key`; an explicit `key` option wins (for `publishAll`, the one key of the
|
|
127
|
+
* call applies to every event). It must return a non-empty string.
|
|
128
|
+
*/
|
|
129
|
+
readonly key: (payload: TPayload) => string;
|
|
130
|
+
}
|
|
131
|
+
declare function assertEventType(type: string): void;
|
|
132
|
+
/**
|
|
133
|
+
* Defines an event type. Extend the returned class to name it:
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* export class OrderPlaced extends defineEvent<{ orderId: string }>('order.placed.v1') {}
|
|
137
|
+
* export class OrderShipped extends defineEvent('order.shipped.v1', { schema: orderShippedSchema }) {}
|
|
138
|
+
* export class OrderPaid extends defineEvent('order.paid.v1', {
|
|
139
|
+
* schema: orderPaidSchema,
|
|
140
|
+
* key: (payload) => payload.orderId,
|
|
141
|
+
* }) {}
|
|
142
|
+
* export class OrderCancelled extends defineEvent<{ orderId: string }>('order.cancelled.v1', {
|
|
143
|
+
* key: (payload) => payload.orderId,
|
|
144
|
+
* }) {}
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
declare function defineEvent<TSchema extends StandardSchemaV1>(type: string, options: DefineEventOptions<TSchema>): EventClass<StandardSchemaV1.InferOutput<TSchema>>;
|
|
148
|
+
declare function defineEvent<TPayload>(type: string, options?: DefineEventKeyOptions<TPayload>): EventClass<TPayload>;
|
|
149
|
+
/** Runs a Standard Schema and returns its output, or throws {@link EventValidationError}. */
|
|
150
|
+
declare function validatePayload<TPayload>(type: string, schema: StandardSchemaV1<unknown, TPayload> | undefined, payload: unknown): Promise<TPayload>;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Base class of every error thrown by relaybus.
|
|
154
|
+
*/
|
|
155
|
+
declare class RelayBusError extends Error {
|
|
156
|
+
name: string;
|
|
157
|
+
/**
|
|
158
|
+
* Stable, machine-readable identifier of the error class, such as `RELAYBUS_LEASE_LOST`.
|
|
159
|
+
* Unlike `name` and `message`, a code is never renamed once released; branch on it in
|
|
160
|
+
* alerts and dashboards. Subclasses declared outside relaybus inherit `RELAYBUS_ERROR`.
|
|
161
|
+
*/
|
|
162
|
+
readonly code: string;
|
|
163
|
+
}
|
|
164
|
+
/** Invalid options passed to a relaybus component. */
|
|
165
|
+
declare class ConfigurationError extends RelayBusError {
|
|
166
|
+
name: string;
|
|
167
|
+
readonly code: string;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Thrown by `publish` when no transaction can be found and the bus runs with
|
|
171
|
+
* `transactionMode: 'required'` (the default). Publishing outside a transaction
|
|
172
|
+
* would reintroduce the dual-write problem the outbox exists to prevent.
|
|
173
|
+
*/
|
|
174
|
+
declare class TransactionRequiredError extends RelayBusError {
|
|
175
|
+
name: string;
|
|
176
|
+
readonly code: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A `bus.transaction` (or `@RelayBusTransaction()` method) ran past its `transactionTimeout` and
|
|
180
|
+
* was rolled back. Its `cause` is the error the database raised when it ended the transaction,
|
|
181
|
+
* when there was one. Usually a slow call to another service made inside the transaction.
|
|
182
|
+
*/
|
|
183
|
+
declare class TransactionTimeoutError extends RelayBusError {
|
|
184
|
+
name: string;
|
|
185
|
+
readonly code: string;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Throw from a handler to dead-letter the delivery immediately, skipping the
|
|
189
|
+
* remaining retry attempts.
|
|
190
|
+
*/
|
|
191
|
+
declare class NonRetryableError extends RelayBusError {
|
|
192
|
+
name: string;
|
|
193
|
+
readonly code: string;
|
|
194
|
+
}
|
|
195
|
+
/** A payload failed its event schema. Never retried: the payload will not change. */
|
|
196
|
+
declare class EventValidationError extends NonRetryableError {
|
|
197
|
+
readonly issues: readonly ValidationIssue[];
|
|
198
|
+
name: string;
|
|
199
|
+
readonly code: string;
|
|
200
|
+
constructor(message: string, issues: readonly ValidationIssue[]);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A message received from a broker is not a valid event. Never retried:
|
|
204
|
+
* transports should dead-letter it instead of redelivering it forever.
|
|
205
|
+
*/
|
|
206
|
+
declare class InvalidEnvelopeError extends NonRetryableError {
|
|
207
|
+
name: string;
|
|
208
|
+
readonly code: string;
|
|
209
|
+
}
|
|
210
|
+
/** The worker no longer owns the delivery it was processing. */
|
|
211
|
+
declare class LeaseLostError extends RelayBusError {
|
|
212
|
+
name: string;
|
|
213
|
+
readonly code: string;
|
|
214
|
+
/**
|
|
215
|
+
* Whether the store said so (a fenced write returned false). A worker that only noticed
|
|
216
|
+
* locally that its lease ran out may still own the delivery in the store and has to give it back.
|
|
217
|
+
*/
|
|
218
|
+
readonly storeConfirmed: boolean;
|
|
219
|
+
constructor(message: string, options?: {
|
|
220
|
+
readonly storeConfirmed?: boolean;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** A handler did not settle within its configured timeout. Retried like any failure. */
|
|
224
|
+
declare class HandlerTimeoutError extends RelayBusError {
|
|
225
|
+
name: string;
|
|
226
|
+
readonly code: string;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Reason of a handler's abort signal when the bus stops before the handler
|
|
230
|
+
* finished. The delivery is released and runs again without losing an attempt.
|
|
231
|
+
*/
|
|
232
|
+
declare class DispatcherStoppedError extends RelayBusError {
|
|
233
|
+
name: string;
|
|
234
|
+
readonly code: string;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A delivery came back after its lease expired on its last attempt, so it is
|
|
238
|
+
* dead-lettered without running its handler again.
|
|
239
|
+
*/
|
|
240
|
+
declare class AttemptsExhaustedError extends RelayBusError {
|
|
241
|
+
name: string;
|
|
242
|
+
readonly code: string;
|
|
243
|
+
}
|
|
244
|
+
/** A lifecycle method was called in a state that does not allow it, such as starting a bus twice. */
|
|
245
|
+
declare class LifecycleError extends RelayBusError {
|
|
246
|
+
name: string;
|
|
247
|
+
readonly code: string;
|
|
248
|
+
}
|
|
249
|
+
/** `drain` still found due deliveries after its maximum number of batches. */
|
|
250
|
+
declare class DrainIncompleteError extends RelayBusError {
|
|
251
|
+
name: string;
|
|
252
|
+
readonly code: string;
|
|
253
|
+
}
|
|
254
|
+
interface ValidationIssue {
|
|
255
|
+
readonly message: string;
|
|
256
|
+
readonly path?: readonly PropertyKey[] | undefined;
|
|
257
|
+
}
|
|
258
|
+
/** Serializable snapshot of an error, stored with failed deliveries. */
|
|
259
|
+
interface ErrorInfo {
|
|
260
|
+
readonly name: string;
|
|
261
|
+
readonly message: string;
|
|
262
|
+
readonly stack?: string | undefined;
|
|
263
|
+
/** The {@link RelayBusError.code} of a relaybus error; absent for any other error. */
|
|
264
|
+
readonly code?: string | undefined;
|
|
265
|
+
}
|
|
266
|
+
declare function toErrorInfo(error: unknown): ErrorInfo;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Lifecycle of one handler's delivery of one event.
|
|
270
|
+
* `pending` (waiting or scheduled for retry) -> `processing` (leased by a worker)
|
|
271
|
+
* -> `completed` | `pending` (retry) | `dead` (exhausted or non-retryable).
|
|
272
|
+
* `dead` -> `pending` only through an explicit requeue.
|
|
273
|
+
*
|
|
274
|
+
* A completed delivery is deleted. `completed` records exist only in stores
|
|
275
|
+
* configured to keep a completion history (`retainCompleted` /
|
|
276
|
+
* `completionHistory`), where they are listed, counted and purged like before.
|
|
277
|
+
*/
|
|
278
|
+
type DeliveryStatus = 'pending' | 'processing' | 'completed' | 'dead';
|
|
279
|
+
declare const DELIVERY_STATUSES: readonly DeliveryStatus[];
|
|
280
|
+
interface NewDelivery {
|
|
281
|
+
readonly id: string;
|
|
282
|
+
/** Stable handler id. Together with the event id it is unique. */
|
|
283
|
+
readonly handler: string;
|
|
284
|
+
readonly availableAt: Date;
|
|
285
|
+
}
|
|
286
|
+
interface AppendEntry {
|
|
287
|
+
readonly event: EventEnvelope;
|
|
288
|
+
readonly deliveries: readonly NewDelivery[];
|
|
289
|
+
}
|
|
290
|
+
interface AppendResult {
|
|
291
|
+
/** Events written by this call. */
|
|
292
|
+
readonly appendedEventIds: readonly string[];
|
|
293
|
+
/** Events that already existed. Their missing deliveries were still added. */
|
|
294
|
+
readonly duplicateEventIds: readonly string[];
|
|
295
|
+
/** Deliveries written by this call. Existing (event, handler) pairs are skipped. */
|
|
296
|
+
readonly appendedDeliveryCount: number;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Lease expiry is compared with the `now` the caller passes, taken from the
|
|
300
|
+
* bus clock. Keep worker clocks synchronized (NTP): a skew larger than a third
|
|
301
|
+
* of the lease duration lets another worker reclaim a delivery that is still running.
|
|
302
|
+
*
|
|
303
|
+
* Stores may grant the lease on a group of deliveries (a lane) instead of on
|
|
304
|
+
* each delivery. The group is exclusive to `leaseOwner` while the lease is
|
|
305
|
+
* live, so other owners do not see its pending deliveries until it expires or
|
|
306
|
+
* is relinquished; every delivery of the group carries the group's fencing token.
|
|
307
|
+
*/
|
|
308
|
+
interface ClaimRequest {
|
|
309
|
+
/** Only deliveries of these handlers are claimed. */
|
|
310
|
+
readonly handlers: readonly string[];
|
|
311
|
+
readonly limit: number;
|
|
312
|
+
readonly leaseOwner: string;
|
|
313
|
+
readonly leaseExpiresAt: Date;
|
|
314
|
+
readonly now: Date;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Proof of ownership. Every state change after a claim must present it
|
|
318
|
+
* (fencing). A lease stays valid until another claim replaces it, even after
|
|
319
|
+
* its expiry time; expiry only makes the delivery claimable again.
|
|
320
|
+
*/
|
|
321
|
+
interface LeaseReference {
|
|
322
|
+
readonly deliveryId: string;
|
|
323
|
+
readonly leaseToken: string;
|
|
324
|
+
}
|
|
325
|
+
interface ClaimedDelivery extends LeaseReference {
|
|
326
|
+
readonly handler: string;
|
|
327
|
+
/** Attempt number of this claim, starting at 1. */
|
|
328
|
+
readonly attempt: number;
|
|
329
|
+
readonly leaseExpiresAt: Date;
|
|
330
|
+
readonly event: EventEnvelope;
|
|
331
|
+
}
|
|
332
|
+
interface DeliveryRecord {
|
|
333
|
+
readonly id: string;
|
|
334
|
+
readonly handler: string;
|
|
335
|
+
readonly status: DeliveryStatus;
|
|
336
|
+
readonly attempts: number;
|
|
337
|
+
readonly availableAt: Date;
|
|
338
|
+
readonly leaseOwner: string | null;
|
|
339
|
+
readonly leaseExpiresAt: Date | null;
|
|
340
|
+
readonly lastError: ErrorInfo | null;
|
|
341
|
+
readonly createdAt: Date;
|
|
342
|
+
readonly updatedAt: Date;
|
|
343
|
+
readonly completedAt: Date | null;
|
|
344
|
+
readonly event: EventEnvelope;
|
|
345
|
+
}
|
|
346
|
+
interface DeliveryQuery {
|
|
347
|
+
readonly status?: DeliveryStatus;
|
|
348
|
+
readonly handler?: string;
|
|
349
|
+
readonly eventType?: string;
|
|
350
|
+
readonly eventId?: string;
|
|
351
|
+
/** Default 100. */
|
|
352
|
+
readonly limit?: number;
|
|
353
|
+
}
|
|
354
|
+
interface PurgeRequest {
|
|
355
|
+
/** Delete completed deliveries completed before this instant. */
|
|
356
|
+
readonly completedBefore?: Date;
|
|
357
|
+
/** Delete dead deliveries last updated before this instant. */
|
|
358
|
+
readonly deadBefore?: Date;
|
|
359
|
+
}
|
|
360
|
+
interface PurgeResult {
|
|
361
|
+
readonly deletedDeliveries: number;
|
|
362
|
+
/** Events deleted because no delivery referenced them any more. */
|
|
363
|
+
readonly deletedEvents: number;
|
|
364
|
+
}
|
|
365
|
+
interface EventStoreStatistics {
|
|
366
|
+
readonly pending: number;
|
|
367
|
+
readonly processing: number;
|
|
368
|
+
readonly completed: number;
|
|
369
|
+
readonly dead: number;
|
|
370
|
+
/** `availableAt` of the oldest delivery that is due but not yet processed. */
|
|
371
|
+
readonly oldestDueAt: Date | null;
|
|
372
|
+
}
|
|
373
|
+
/** How `stop` closes a transport or a store. */
|
|
374
|
+
interface CloseOptions {
|
|
375
|
+
/**
|
|
376
|
+
* Aborted when the shutdown deadline passes (`bus.stop({ timeout })`), possibly before `close` is
|
|
377
|
+
* called. From then on, close what you opened yourself at once (destroy connections, cancel
|
|
378
|
+
* running queries) instead of waiting for it to finish; the bus no longer waits for `close`.
|
|
379
|
+
* Leave alone what the application passed in.
|
|
380
|
+
*/
|
|
381
|
+
readonly signal?: AbortSignal;
|
|
382
|
+
}
|
|
383
|
+
/** How a store runs one transaction of `bus.transaction`. */
|
|
384
|
+
interface EventStoreTransactionOptions {
|
|
385
|
+
/**
|
|
386
|
+
* Time budget of the whole transaction, in milliseconds. A store makes the database enforce it
|
|
387
|
+
* where it can (for example a statement or idle-in-transaction timeout), so a transaction that
|
|
388
|
+
* waits on something slow is ended by the server and its locks are released. The bus rolls back
|
|
389
|
+
* a transaction that still ran past the budget, so a store may also ignore it.
|
|
390
|
+
*/
|
|
391
|
+
readonly timeoutMs: number;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Storage port. Implementations persist events and deliveries in the same
|
|
395
|
+
* database as the application's business data so that both commit atomically.
|
|
396
|
+
*
|
|
397
|
+
* Every implementation must pass the contract suite in `@relaybus/testing`.
|
|
398
|
+
*/
|
|
399
|
+
interface EventStore<TTransaction = unknown> {
|
|
400
|
+
/** Creates tables, collections and indexes when they do not exist. */
|
|
401
|
+
setup?(): Promise<void>;
|
|
402
|
+
/**
|
|
403
|
+
* Runs `work` inside a new database transaction and commits when it resolves. `options` come
|
|
404
|
+
* from `bus.transaction`; the store's own transactions and the dispatcher's pass none.
|
|
405
|
+
*/
|
|
406
|
+
transaction<T>(work: (transaction: TTransaction) => Promise<T>, options?: EventStoreTransactionOptions): Promise<T>;
|
|
407
|
+
/**
|
|
408
|
+
* Writes events and their deliveries inside `transaction`. Idempotent on
|
|
409
|
+
* event id and on (event id, handler). Assigns a per-key sequence to keyed
|
|
410
|
+
* events in commit order.
|
|
411
|
+
*/
|
|
412
|
+
append(transaction: TTransaction, entries: readonly AppendEntry[], now: Date): Promise<AppendResult>;
|
|
413
|
+
/**
|
|
414
|
+
* Leases up to `limit` deliveries that are due: pending with `availableAt <= now`,
|
|
415
|
+
* or processing with an expired lease. A keyed delivery is only claimable when
|
|
416
|
+
* no earlier delivery of the same handler and key is pending or processing.
|
|
417
|
+
* Counts the attempt (`attempt` of the result; a store may persist the count
|
|
418
|
+
* only when the attempt fails or its lease is lost). Safe to call from many
|
|
419
|
+
* processes at once.
|
|
420
|
+
*/
|
|
421
|
+
claim(request: ClaimRequest): Promise<ClaimedDelivery[]>;
|
|
422
|
+
/** Extends a lease the caller still holds. Returns false when the lease was lost. */
|
|
423
|
+
extendLease(lease: LeaseReference, leaseExpiresAt: Date, now: Date): Promise<boolean>;
|
|
424
|
+
/**
|
|
425
|
+
* Marks a leased delivery completed by deleting it (or moving it to the
|
|
426
|
+
* completion history, when the store keeps one). With `transaction` the change
|
|
427
|
+
* commits together with the handler's own writes. Returns false when the lease
|
|
428
|
+
* was lost.
|
|
429
|
+
*/
|
|
430
|
+
complete(lease: LeaseReference, now: Date, transaction?: TTransaction): Promise<boolean>;
|
|
431
|
+
/** Returns a leased delivery to pending, due at `availableAt`. */
|
|
432
|
+
reschedule(lease: LeaseReference, availableAt: Date, error: ErrorInfo, now: Date): Promise<boolean>;
|
|
433
|
+
/**
|
|
434
|
+
* Gives a leased delivery back without counting the attempt: pending, due
|
|
435
|
+
* now, attempts decremented. Used when a handler is aborted at shutdown.
|
|
436
|
+
*/
|
|
437
|
+
release(lease: LeaseReference, now: Date): Promise<boolean>;
|
|
438
|
+
/**
|
|
439
|
+
* Marks many leased deliveries completed with one round trip. Returns, for
|
|
440
|
+
* every lease in the same order, whether it was completed (false: lease lost
|
|
441
|
+
* or already finished). Each lease is fenced individually, exactly as in
|
|
442
|
+
* {@link EventStore.complete}. Optional: the dispatcher falls back to
|
|
443
|
+
* `complete` when a store does not implement it.
|
|
444
|
+
*/
|
|
445
|
+
completeMany?(leases: readonly LeaseReference[], now: Date): Promise<boolean[]>;
|
|
446
|
+
/** Moves a leased delivery to dead. */
|
|
447
|
+
bury(lease: LeaseReference, error: ErrorInfo, now: Date): Promise<boolean>;
|
|
448
|
+
listDeliveries(query: DeliveryQuery): Promise<DeliveryRecord[]>;
|
|
449
|
+
/** Moves dead deliveries back to pending with a fresh attempt budget. Returns how many moved. */
|
|
450
|
+
requeue(deliveryIds: readonly string[], now: Date): Promise<number>;
|
|
451
|
+
purge(request: PurgeRequest): Promise<PurgeResult>;
|
|
452
|
+
statistics(now: Date): Promise<EventStoreStatistics>;
|
|
453
|
+
/**
|
|
454
|
+
* Hands back every lease group that `leaseOwner` holds without running work,
|
|
455
|
+
* so other workers can take them at once instead of after lease expiry.
|
|
456
|
+
* Called at graceful shutdown after the owner's deliveries settled. Groups
|
|
457
|
+
* with running deliveries stay leased. Optional.
|
|
458
|
+
*/
|
|
459
|
+
relinquish?(leaseOwner: string, now: Date): Promise<void>;
|
|
460
|
+
/**
|
|
461
|
+
* Calls `listener` soon after a transaction that added deliveries due at once has committed, in this
|
|
462
|
+
* process or in any other one, so an idle dispatcher claims them without waiting for its next poll.
|
|
463
|
+
* A hint, never a guarantee: a report may be late, merged with others, or lost (while a connection
|
|
464
|
+
* is re-established, for instance), and polling still finds every delivery. Resolves with a function
|
|
465
|
+
* that stops listening, or with `undefined` when this store cannot listen. Optional.
|
|
466
|
+
*/
|
|
467
|
+
watchNewDeliveries?(listener: () => void): Promise<(() => Promise<void>) | undefined>;
|
|
468
|
+
/** Releases connections the store opened itself; forced once `options.signal` aborts. */
|
|
469
|
+
close?(options?: CloseOptions): Promise<void>;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Where an incoming message came from, for observers and traces. */
|
|
473
|
+
interface TransportMessage {
|
|
474
|
+
/** Topic, routing key or queue the message arrived on. */
|
|
475
|
+
readonly destination: string;
|
|
476
|
+
}
|
|
477
|
+
/** A message the transport could not turn into an event, so it never reaches the bus. */
|
|
478
|
+
interface InvalidTransportMessage {
|
|
479
|
+
/** Topic, routing key or queue the message arrived on. */
|
|
480
|
+
readonly destination: string;
|
|
481
|
+
/** What the codec or decoder threw. */
|
|
482
|
+
readonly error: unknown;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* What the bus offers a transport at `subscribe`, beyond the message handler. Present only when
|
|
486
|
+
* an observer implements the matching hook, so a transport can skip the work otherwise.
|
|
487
|
+
*/
|
|
488
|
+
interface TransportSubscriptionHooks {
|
|
489
|
+
/**
|
|
490
|
+
* Call once for each inbound message the transport rejected as undecodable (dead-lettered or
|
|
491
|
+
* skipped) before it reached `onMessage`. Messages the bus rejected itself are reported by
|
|
492
|
+
* `onReceiveFinished` and must not be reported here. Never throws.
|
|
493
|
+
*/
|
|
494
|
+
readonly onInvalidMessage: (message: InvalidTransportMessage) => void;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Receives an event from a broker. Resolve only after it is durably stored. Transports pass
|
|
498
|
+
* `message` to tell where the event arrived; it is optional for transports written before it.
|
|
499
|
+
*/
|
|
500
|
+
type TransportMessageHandler = (envelope: EventEnvelope, message?: TransportMessage) => Promise<void>;
|
|
501
|
+
/**
|
|
502
|
+
* Broker port used to exchange events between services.
|
|
503
|
+
*
|
|
504
|
+
* Outbound: the bus calls `publish` from a delivery, so a broker outage only
|
|
505
|
+
* delays events; the outbox keeps them. `publish` must resolve only after the
|
|
506
|
+
* broker acknowledged the message.
|
|
507
|
+
*
|
|
508
|
+
* Inbound: `subscribe` must acknowledge a message only after `onMessage`
|
|
509
|
+
* resolved, and redeliver it when `onMessage` rejects (at-least-once).
|
|
510
|
+
*/
|
|
511
|
+
interface Transport {
|
|
512
|
+
/** Unique name referenced by routes and subscriptions. */
|
|
513
|
+
readonly name: string;
|
|
514
|
+
/**
|
|
515
|
+
* The messaging system, as OpenTelemetry names it (`kafka`, `rabbitmq`), reported to observers
|
|
516
|
+
* and traces. Default: the transport's name.
|
|
517
|
+
*/
|
|
518
|
+
readonly system?: string;
|
|
519
|
+
connect?(): Promise<void>;
|
|
520
|
+
publish(envelope: EventEnvelope, destination: string): Promise<void>;
|
|
521
|
+
subscribe?(destinations: readonly string[], onMessage: TransportMessageHandler, hooks?: TransportSubscriptionHooks): Promise<void>;
|
|
522
|
+
/**
|
|
523
|
+
* Whether the transport can currently publish and, when subscribed, is
|
|
524
|
+
* consuming. Health checks use it to report a dead consumer or a lost
|
|
525
|
+
* connection. Transports without it count as ready.
|
|
526
|
+
*/
|
|
527
|
+
isReady?(): boolean;
|
|
528
|
+
/**
|
|
529
|
+
* Releases connections. A closed transport is not reopened. Once `options.signal` aborts (the
|
|
530
|
+
* shutdown deadline passed), stop waiting and drop the connections it opened itself.
|
|
531
|
+
*/
|
|
532
|
+
close(options?: CloseOptions): Promise<void>;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export { toMilliseconds as $, LeaseLostError as F, LifecycleError as G, HandlerTimeoutError as H, InvalidEnvelopeError as I, NonRetryableError as J, TransactionRequiredError as K, MAX_TIMER_DELAY_MS as M, TransactionTimeoutError as O, RelayBusError as R, StandardSchemaV1 as S, assertEventType as X, defineEvent as Y, systemClock as Z, toErrorInfo as _, validatePayload as a0, AttemptsExhaustedError as o, ConfigurationError as q, DELIVERY_STATUSES as r, DispatcherStoppedError as v, DrainIncompleteError as w, EventValidationError as z };
|
|
536
|
+
export type { AppendEntry as A, InvalidTransportMessage as B, ClaimedDelivery as C, Duration as D, EventEnvelope as E, LeaseReference as L, NewDelivery as N, PayloadOf as P, TransportMessage as Q, Transport as T, TransportMessageHandler as U, TransportSubscriptionHooks as V, ValidationIssue as W, DomainEvent as a, EventHeaders as b, EventStoreStatistics as c, ErrorInfo as d, PurgeResult as e, EventStore as f, EventReference as g, Clock as h, AppendResult as i, ClaimRequest as j, DeliveryQuery as k, DeliveryRecord as l, PurgeRequest as m, EventClass as n, CloseOptions as p, DefineEventKeyOptions as s, DefineEventOptions as t, DeliveryStatus as u, EventKeyFunction as x, EventStoreTransactionOptions as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relaybus/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Reliable event bus for Node.js: a transactional outbox with per-handler deliveries, retries with jitter, dead letters and per-key ordering on top of your own database.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Berkcan Şavur <berkcansavur@gmail.com>",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/event.ts","../src/internal/timers.ts","../src/internal/validation.ts"],"names":[],"mappings":";;;AAGO,IAAM,aAAA,GAAN,cAA4B,KAAA,CAAM;AAAA;AAAA,EAE9B,IAAA,GAAO,eAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMP,IAAA,GAAe,gBAAA;AAC1B;AAGO,IAAM,kBAAA,GAAN,cAAiC,aAAA,CAAc;AAAA,EAC3C,IAAA,GAAO,oBAAA;AAAA,EACE,IAAA,GAAe,wBAAA;AACnC;AAOO,IAAM,wBAAA,GAAN,cAAuC,aAAA,CAAc;AAAA,EACjD,IAAA,GAAO,0BAAA;AAAA,EACE,IAAA,GAAe,+BAAA;AACnC;AAOO,IAAM,uBAAA,GAAN,cAAsC,aAAA,CAAc;AAAA,EAChD,IAAA,GAAO,yBAAA;AAAA,EACE,IAAA,GAAe,8BAAA;AACnC;AAMO,IAAM,iBAAA,GAAN,cAAgC,aAAA,CAAc;AAAA,EAC1C,IAAA,GAAO,mBAAA;AAAA,EACE,IAAA,GAAe,wBAAA;AACnC;AAGO,IAAM,oBAAA,GAAN,cAAmC,iBAAA,CAAkB;AAAA,EAI1D,WAAA,CACE,SACS,MAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAFJ,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAGX;AAAA,EAHW,MAAA;AAAA,EALF,IAAA,GAAO,sBAAA;AAAA,EACE,IAAA,GAAe,2BAAA;AAQnC;AAMO,IAAM,oBAAA,GAAN,cAAmC,iBAAA,CAAkB;AAAA,EACjD,IAAA,GAAO,sBAAA;AAAA,EACE,IAAA,GAAe,2BAAA;AACnC;AAGO,IAAM,cAAA,GAAN,cAA6B,aAAA,CAAc;AAAA,EACvC,IAAA,GAAO,gBAAA;AAAA,EACE,IAAA,GAAe,qBAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKxB,cAAA;AAAA,EAET,WAAA,CAAY,OAAA,EAAiB,OAAA,GAAiD,EAAC,EAAG;AAChF,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAQ,cAAA,IAAkB,KAAA;AAAA,EAClD;AACF;AAGO,IAAM,mBAAA,GAAN,cAAkC,aAAA,CAAc;AAAA,EAC5C,IAAA,GAAO,qBAAA;AAAA,EACE,IAAA,GAAe,0BAAA;AACnC;AAMO,IAAM,sBAAA,GAAN,cAAqC,aAAA,CAAc;AAAA,EAC/C,IAAA,GAAO,wBAAA;AAAA,EACE,IAAA,GAAe,6BAAA;AACnC;AAMO,IAAM,sBAAA,GAAN,cAAqC,aAAA,CAAc;AAAA,EAC/C,IAAA,GAAO,wBAAA;AAAA,EACE,IAAA,GAAe,6BAAA;AACnC;AAGO,IAAM,cAAA,GAAN,cAA6B,aAAA,CAAc;AAAA,EACvC,IAAA,GAAO,gBAAA;AAAA,EACE,IAAA,GAAe,oBAAA;AACnC;AAGO,IAAM,oBAAA,GAAN,cAAmC,aAAA,CAAc;AAAA,EAC7C,IAAA,GAAO,sBAAA;AAAA,EACE,IAAA,GAAe,2BAAA;AACnC;AAgBO,SAAS,YAAY,KAAA,EAA2B;AACrD,EAAA,IAAI,iBAAiB,aAAA,EAAe;AAClC,IAAA,OAAO,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,OAAA,EAAS,KAAA,CAAM,OAAA,EAAS,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,IAAA,EAAM,KAAA,CAAM,IAAA,EAAK;AAAA,EAC1F;AACA,EAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,IAAA,OAAO,EAAE,MAAM,KAAA,CAAM,IAAA,EAAM,SAAS,KAAA,CAAM,OAAA,EAAS,KAAA,EAAO,KAAA,CAAM,KAAA,EAAM;AAAA,EACxE;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,aAAA,CAAc,KAAK,CAAA,EAAE;AAC3D;AAEA,SAAS,cAAc,KAAA,EAAwB;AAC7C,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,IAAK,OAAO,KAAK,CAAA;AAAA,EAC9C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAO,KAAK,CAAA;AAAA,EACrB;AACF;;;AClFA,IAAM,kBAAA,GAAqB,sCAAA;AAEpB,SAAS,gBAAgB,IAAA,EAAoB;AAClD,EAAA,IAAI,CAAC,kBAAA,CAAmB,IAAA,CAAK,IAAI,CAAA,EAAG;AAClC,IAAA,MAAM,IAAI,kBAAA;AAAA,MACR,eAAe,IAAI,CAAA,yGAAA;AAAA,KACrB;AAAA,EACF;AACF;AAyBO,SAAS,WAAA,CACd,MACA,OAAA,EACqB;AACrB,EAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,EAAA,MAAM,SAAS,OAAA,EAAS,MAAA;AACxB,EAAA,MAAM,MAAM,OAAA,EAAS,GAAA;AACrB,EAAA,OAAO,MAAM,YAAA,CAA6C;AAAA,IAKxD,YAAqB,OAAA,EAAkB;AAAlB,MAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,IAAmB;AAAA,IAAnB,OAAA;AAAA,IAJrB,OAAgB,IAAA,GAAO,IAAA;AAAA,IACvB,OAAgB,MAAA,GAAS,MAAA;AAAA,IACzB,OAAgB,GAAA,GAAM,GAAA;AAAA,IACb,IAAA,GAAO,IAAA;AAAA,GAElB;AACF;AAIA,eAAsB,eAAA,CACpB,IAAA,EACA,MAAA,EACA,OAAA,EACmB;AACnB,EAAA,IAAI,WAAW,MAAA,EAAW;AACxB,IAAA,OAAO,OAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAS,MAAM,MAAA,CAAO,WAAW,CAAA,CAAE,SAAS,OAAO,CAAA;AACzD,EAAA,IAAI,MAAA,CAAO,WAAW,MAAA,EAAW;AAC/B,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,MAC3C,SAAS,KAAA,CAAM,OAAA;AAAA,MACf,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,GAAA,CAAI,CAAC,OAAA,KAAa,OAAO,OAAA,KAAY,QAAA,GAAW,OAAA,CAAQ,GAAA,GAAM,OAAQ;AAAA,KAC1F,CAAE,CAAA;AACF,IAAA,MAAM,IAAI,oBAAA;AAAA,MACR,CAAA,YAAA,EAAe,IAAI,CAAA,qBAAA,EAAwB,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,KAAA,CAAM,OAAO,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAAA,MAC1F;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,MAAA,CAAO,KAAA;AAChB;AAQO,SAAS,iBAAA,CAAkB,OAAoB,OAAA,EAAiC;AACrF,EAAA,MAAM,KAAA,GAAS,MAAM,WAAA,CAAoC,GAAA;AACzD,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,GAAA,GAAe,MAAM,OAAO,CAAA;AAClC,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,WAAW,CAAA,EAAG;AAC/C,IAAA,MAAM,IAAI,kBAAA;AAAA,MACR,CAAA,qBAAA,EAAwB,MAAM,IAAI,CAAA,4CAAA,EAA+C,OAAO,GAAA,KAAQ,QAAA,GAAW,IAAA,GAAO,OAAO,GAAG,CAAA;AAAA,KAC9H;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;ACzJO,SAAS,QAAA,CAAS,OAAc,IAAA,EAAkC;AACvE,EAAA,OAAO,KAAA,CAAM,aAAa,MAAA,GAAY,kBAAA,CAAmB,IAAI,CAAA,GAAI,KAAA,CAAM,SAAS,IAAI,CAAA;AACtF;AAGO,SAAS,KAAA,CAAM,OAAc,YAAA,EAAqC;AACvE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC9B,IAAA,QAAA,CAAS,OAAO,EAAE,GAAA,EAAK,OAAA,EAAS,OAAA,EAAS,cAAc,CAAA;AAAA,EACzD,CAAC,CAAA;AACH;AAEA,SAAS,mBAAmB,IAAA,EAAkC;AAC5D,EAAA,IAAI,IAAA,CAAK,WAAW,IAAA,EAAM;AACxB,IAAA,MAAM,QAAA,GAAW,WAAA,CAAY,IAAA,CAAK,GAAA,EAAK,KAAK,OAAO,CAAA;AACnD,IAAA,IAAI,IAAA,CAAK,UAAU,IAAA,EAAM;AACvB,MAAA,QAAA,CAAS,KAAA,EAAM;AAAA,IACjB;AACA,IAAA,OAAO,MAAM,cAAc,QAAQ,CAAA;AAAA,EACrC;AACA,EAAA,MAAM,OAAA,GAAU,UAAA,CAAW,IAAA,CAAK,GAAA,EAAK,KAAK,OAAO,CAAA;AACjD,EAAA,IAAI,IAAA,CAAK,UAAU,IAAA,EAAM;AACvB,IAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,EAChB;AACA,EAAA,OAAO,MAAM,aAAa,OAAO,CAAA;AACnC;;;ACjCO,SAAS,qBAAA,CAAsB,OAAe,IAAA,EAAsB;AACzE,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,IAAK,QAAQ,CAAA,EAAG;AACzC,IAAA,MAAM,IAAI,mBAAmB,CAAA,EAAG,IAAI,yCAAyC,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,CAAA;AAAA,EAC9F;AACA,EAAA,OAAO,KAAA;AACT","file":"chunk-3OHMEMZA.cjs","sourcesContent":["/**\n * Base class of every error thrown by relaybus.\n */\nexport class RelayBusError extends Error {\n // Explicit names survive minification, unlike `new.target.name`.\n override name = 'RelayBusError';\n /**\n * Stable, machine-readable identifier of the error class, such as `RELAYBUS_LEASE_LOST`.\n * Unlike `name` and `message`, a code is never renamed once released; branch on it in\n * alerts and dashboards. Subclasses declared outside relaybus inherit `RELAYBUS_ERROR`.\n */\n readonly code: string = 'RELAYBUS_ERROR';\n}\n\n/** Invalid options passed to a relaybus component. */\nexport class ConfigurationError extends RelayBusError {\n override name = 'ConfigurationError';\n override readonly code: string = 'RELAYBUS_CONFIGURATION';\n}\n\n/**\n * Thrown by `publish` when no transaction can be found and the bus runs with\n * `transactionMode: 'required'` (the default). Publishing outside a transaction\n * would reintroduce the dual-write problem the outbox exists to prevent.\n */\nexport class TransactionRequiredError extends RelayBusError {\n override name = 'TransactionRequiredError';\n override readonly code: string = 'RELAYBUS_TRANSACTION_REQUIRED';\n}\n\n/**\n * A `bus.transaction` (or `@RelayBusTransaction()` method) ran past its `transactionTimeout` and\n * was rolled back. Its `cause` is the error the database raised when it ended the transaction,\n * when there was one. Usually a slow call to another service made inside the transaction.\n */\nexport class TransactionTimeoutError extends RelayBusError {\n override name = 'TransactionTimeoutError';\n override readonly code: string = 'RELAYBUS_TRANSACTION_TIMEOUT';\n}\n\n/**\n * Throw from a handler to dead-letter the delivery immediately, skipping the\n * remaining retry attempts.\n */\nexport class NonRetryableError extends RelayBusError {\n override name = 'NonRetryableError';\n override readonly code: string = 'RELAYBUS_NON_RETRYABLE';\n}\n\n/** A payload failed its event schema. Never retried: the payload will not change. */\nexport class EventValidationError extends NonRetryableError {\n override name = 'EventValidationError';\n override readonly code: string = 'RELAYBUS_EVENT_VALIDATION';\n\n constructor(\n message: string,\n readonly issues: readonly ValidationIssue[],\n ) {\n super(message);\n }\n}\n\n/**\n * A message received from a broker is not a valid event. Never retried:\n * transports should dead-letter it instead of redelivering it forever.\n */\nexport class InvalidEnvelopeError extends NonRetryableError {\n override name = 'InvalidEnvelopeError';\n override readonly code: string = 'RELAYBUS_INVALID_ENVELOPE';\n}\n\n/** The worker no longer owns the delivery it was processing. */\nexport class LeaseLostError extends RelayBusError {\n override name = 'LeaseLostError';\n override readonly code: string = 'RELAYBUS_LEASE_LOST';\n /**\n * Whether the store said so (a fenced write returned false). A worker that only noticed\n * locally that its lease ran out may still own the delivery in the store and has to give it back.\n */\n readonly storeConfirmed: boolean;\n\n constructor(message: string, options: { readonly storeConfirmed?: boolean } = {}) {\n super(message);\n this.storeConfirmed = options.storeConfirmed ?? false;\n }\n}\n\n/** A handler did not settle within its configured timeout. Retried like any failure. */\nexport class HandlerTimeoutError extends RelayBusError {\n override name = 'HandlerTimeoutError';\n override readonly code: string = 'RELAYBUS_HANDLER_TIMEOUT';\n}\n\n/**\n * Reason of a handler's abort signal when the bus stops before the handler\n * finished. The delivery is released and runs again without losing an attempt.\n */\nexport class DispatcherStoppedError extends RelayBusError {\n override name = 'DispatcherStoppedError';\n override readonly code: string = 'RELAYBUS_DISPATCHER_STOPPED';\n}\n\n/**\n * A delivery came back after its lease expired on its last attempt, so it is\n * dead-lettered without running its handler again.\n */\nexport class AttemptsExhaustedError extends RelayBusError {\n override name = 'AttemptsExhaustedError';\n override readonly code: string = 'RELAYBUS_ATTEMPTS_EXHAUSTED';\n}\n\n/** A lifecycle method was called in a state that does not allow it, such as starting a bus twice. */\nexport class LifecycleError extends RelayBusError {\n override name = 'LifecycleError';\n override readonly code: string = 'RELAYBUS_LIFECYCLE';\n}\n\n/** `drain` still found due deliveries after its maximum number of batches. */\nexport class DrainIncompleteError extends RelayBusError {\n override name = 'DrainIncompleteError';\n override readonly code: string = 'RELAYBUS_DRAIN_INCOMPLETE';\n}\n\nexport interface ValidationIssue {\n readonly message: string;\n readonly path?: readonly PropertyKey[] | undefined;\n}\n\n/** Serializable snapshot of an error, stored with failed deliveries. */\nexport interface ErrorInfo {\n readonly name: string;\n readonly message: string;\n readonly stack?: string | undefined;\n /** The {@link RelayBusError.code} of a relaybus error; absent for any other error. */\n readonly code?: string | undefined;\n}\n\nexport function toErrorInfo(error: unknown): ErrorInfo {\n if (error instanceof RelayBusError) {\n return { name: error.name, message: error.message, stack: error.stack, code: error.code };\n }\n if (error instanceof Error) {\n return { name: error.name, message: error.message, stack: error.stack };\n }\n return { name: 'NonError', message: safeStringify(error) };\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n","import { ConfigurationError, EventValidationError } from './errors.js';\nimport type { StandardSchemaV1 } from './standard-schema.js';\n\n/** String metadata carried with an event, such as `traceparent`. */\nexport type EventHeaders = Readonly<Record<string, string>>;\n\n/**\n * An event as stored and delivered. Field names follow CloudEvents where they\n * overlap (`id`, `type`, `source`, `time` as `occurredAt`, `subject` as `key`).\n */\nexport interface EventEnvelope<TPayload = unknown> {\n /** Globally unique id. Handlers and transports deduplicate on it. */\n readonly id: string;\n /** Event type, for example `order.created.v1`. */\n readonly type: string;\n /** Logical name of the publishing service. */\n readonly source: string;\n /** Ordering and partition key. Events sharing a key are handled in publish order. */\n readonly key: string | null;\n readonly payload: TPayload;\n readonly headers: EventHeaders;\n /** Id shared by every event of one business flow. */\n readonly correlationId: string | null;\n /** Id of the event whose handler published this one. */\n readonly causationId: string | null;\n readonly occurredAt: Date;\n}\n\n/** An instance of a class created with {@link defineEvent}. */\nexport interface DomainEvent<TPayload = unknown> {\n readonly type: string;\n readonly payload: TPayload;\n}\n\n/**\n * Derives the ordering key of an event from its validated payload. Declared bivariantly so an\n * event class stays assignable wherever its payload type is widened.\n */\nexport type EventKeyFunction<TPayload> = {\n bivarianceHack(payload: TPayload): string;\n}['bivarianceHack'];\n\n/** A class created with {@link defineEvent}. */\nexport interface EventClass<TPayload = unknown> {\n new (payload: TPayload): DomainEvent<TPayload>;\n readonly type: string;\n readonly schema: StandardSchemaV1<unknown, TPayload> | undefined;\n /** The `key` given to {@link defineEvent}; undefined for events without one. */\n readonly key?: EventKeyFunction<TPayload> | undefined;\n}\n\n/** Payload type of an event class: `PayloadOf<typeof OrderPlaced>`. */\nexport type PayloadOf<TEvent> = TEvent extends EventClass<infer TPayload> ? TPayload : never;\n\n/** Anything that names an event type: an event class or the type string itself. */\nexport type EventReference = { readonly type: string } | string;\n\nexport interface DefineEventOptions<TSchema extends StandardSchemaV1> {\n /** Validates the payload on publish and before every handler call. */\n readonly schema: TSchema;\n /** Ordering key of every event of this type, from its validated payload. See {@link DefineEventKeyOptions}. */\n readonly key?: (payload: StandardSchemaV1.InferOutput<TSchema>) => string;\n}\n\nexport interface DefineEventKeyOptions<TPayload> {\n /**\n * Ordering key of every event of this type, from its payload: `(payload) => payload.orderId`.\n * Typed from the payload, so a missing field or a non-string fails to compile. `publish` uses it\n * when it is given no `key`; an explicit `key` option wins (for `publishAll`, the one key of the\n * call applies to every event). It must return a non-empty string.\n */\n readonly key: (payload: TPayload) => string;\n}\n\nconst EVENT_TYPE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;\n\nexport function assertEventType(type: string): void {\n if (!EVENT_TYPE_PATTERN.test(type)) {\n throw new ConfigurationError(\n `Event type '${type}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-', starting with a letter or digit`,\n );\n }\n}\n\n/**\n * Defines an event type. Extend the returned class to name it:\n *\n * ```ts\n * export class OrderPlaced extends defineEvent<{ orderId: string }>('order.placed.v1') {}\n * export class OrderShipped extends defineEvent('order.shipped.v1', { schema: orderShippedSchema }) {}\n * export class OrderPaid extends defineEvent('order.paid.v1', {\n * schema: orderPaidSchema,\n * key: (payload) => payload.orderId,\n * }) {}\n * export class OrderCancelled extends defineEvent<{ orderId: string }>('order.cancelled.v1', {\n * key: (payload) => payload.orderId,\n * }) {}\n * ```\n */\nexport function defineEvent<TSchema extends StandardSchemaV1>(\n type: string,\n options: DefineEventOptions<TSchema>,\n): EventClass<StandardSchemaV1.InferOutput<TSchema>>;\nexport function defineEvent<TPayload>(\n type: string,\n options?: DefineEventKeyOptions<TPayload>,\n): EventClass<TPayload>;\nexport function defineEvent(\n type: string,\n options?: Partial<DefineEventOptions<StandardSchemaV1>>,\n): EventClass<unknown> {\n assertEventType(type);\n const schema = options?.schema;\n const key = options?.key;\n return class DefinedEvent implements DomainEvent<unknown> {\n static readonly type = type;\n static readonly schema = schema;\n static readonly key = key;\n readonly type = type;\n constructor(readonly payload: unknown) {}\n };\n}\n\n/** Runs a Standard Schema and returns its output, or throws {@link EventValidationError}. */\n// eslint-disable-next-line relaybus/max-two-params -- exported API validatePayload keeps its public signature\nexport async function validatePayload<TPayload>(\n type: string,\n schema: StandardSchemaV1<unknown, TPayload> | undefined,\n payload: unknown,\n): Promise<TPayload> {\n if (schema === undefined) {\n return payload as TPayload;\n }\n const result = await schema['~standard'].validate(payload);\n if (result.issues !== undefined) {\n const issues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((segment) => (typeof segment === 'object' ? segment.key : segment)),\n }));\n throw new EventValidationError(\n `Payload of '${type}' failed validation: ${issues.map((issue) => issue.message).join('; ')}`,\n issues,\n );\n }\n return result.value;\n}\n\n/**\n * The ordering key an event of `event`'s class takes from its definition, or null when the class\n * defines no key.\n *\n * @throws ConfigurationError when the key function returns an empty string or no string.\n */\nexport function keyFromDefinition(event: DomainEvent, payload: unknown): string | null {\n const keyOf = (event.constructor as Partial<EventClass>).key;\n if (keyOf === undefined) {\n return null;\n }\n const key: unknown = keyOf(payload);\n if (typeof key !== 'string' || key.length === 0) {\n throw new ConfigurationError(\n `The key function of '${event.type}' must return a non-empty string | returned=${typeof key === 'string' ? \"''\" : typeof key}`,\n );\n }\n return key;\n}\n","import type { Clock } from '../clock.js';\n\n/** A task for {@link schedule}: what runs, after how long, whether it repeats and keeps the process alive. */\nexport type ScheduledTask = Parameters<NonNullable<Clock['schedule']>>[0];\n\n/** Cancels a scheduled task; harmless once it ran or was cancelled. */\nexport type CancelTimer = () => void;\n\n/**\n * The one place relaybus starts timers: through `clock.schedule` when the clock controls timers,\n * else with the platform timers.\n */\nexport function schedule(clock: Clock, task: ScheduledTask): CancelTimer {\n return clock.schedule === undefined ? scheduleOnPlatform(task) : clock.schedule(task);\n}\n\n/** Resolves after `milliseconds` on the timers of `clock`. */\nexport function sleep(clock: Clock, milliseconds: number): Promise<void> {\n return new Promise((resolve) => {\n schedule(clock, { run: resolve, delayMs: milliseconds });\n });\n}\n\nfunction scheduleOnPlatform(task: ScheduledTask): CancelTimer {\n if (task.repeat === true) {\n const interval = setInterval(task.run, task.delayMs);\n if (task.unref === true) {\n interval.unref();\n }\n return () => clearInterval(interval);\n }\n const timeout = setTimeout(task.run, task.delayMs);\n if (task.unref === true) {\n timeout.unref();\n }\n return () => clearTimeout(timeout);\n}\n","import { ConfigurationError } from '../errors.js';\n\n/** Returns `value` when it is an integer of at least 1, else throws a {@link ConfigurationError} naming the option. */\nexport function assertPositiveInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new ConfigurationError(`${name} must be a positive integer, received ${String(value)}`);\n }\n return value;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/event.ts","../src/internal/timers.ts","../src/internal/validation.ts"],"names":[],"mappings":"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","file":"chunk-WS274DL3.js","sourcesContent":["/**\n * Base class of every error thrown by relaybus.\n */\nexport class RelayBusError extends Error {\n // Explicit names survive minification, unlike `new.target.name`.\n override name = 'RelayBusError';\n /**\n * Stable, machine-readable identifier of the error class, such as `RELAYBUS_LEASE_LOST`.\n * Unlike `name` and `message`, a code is never renamed once released; branch on it in\n * alerts and dashboards. Subclasses declared outside relaybus inherit `RELAYBUS_ERROR`.\n */\n readonly code: string = 'RELAYBUS_ERROR';\n}\n\n/** Invalid options passed to a relaybus component. */\nexport class ConfigurationError extends RelayBusError {\n override name = 'ConfigurationError';\n override readonly code: string = 'RELAYBUS_CONFIGURATION';\n}\n\n/**\n * Thrown by `publish` when no transaction can be found and the bus runs with\n * `transactionMode: 'required'` (the default). Publishing outside a transaction\n * would reintroduce the dual-write problem the outbox exists to prevent.\n */\nexport class TransactionRequiredError extends RelayBusError {\n override name = 'TransactionRequiredError';\n override readonly code: string = 'RELAYBUS_TRANSACTION_REQUIRED';\n}\n\n/**\n * A `bus.transaction` (or `@RelayBusTransaction()` method) ran past its `transactionTimeout` and\n * was rolled back. Its `cause` is the error the database raised when it ended the transaction,\n * when there was one. Usually a slow call to another service made inside the transaction.\n */\nexport class TransactionTimeoutError extends RelayBusError {\n override name = 'TransactionTimeoutError';\n override readonly code: string = 'RELAYBUS_TRANSACTION_TIMEOUT';\n}\n\n/**\n * Throw from a handler to dead-letter the delivery immediately, skipping the\n * remaining retry attempts.\n */\nexport class NonRetryableError extends RelayBusError {\n override name = 'NonRetryableError';\n override readonly code: string = 'RELAYBUS_NON_RETRYABLE';\n}\n\n/** A payload failed its event schema. Never retried: the payload will not change. */\nexport class EventValidationError extends NonRetryableError {\n override name = 'EventValidationError';\n override readonly code: string = 'RELAYBUS_EVENT_VALIDATION';\n\n constructor(\n message: string,\n readonly issues: readonly ValidationIssue[],\n ) {\n super(message);\n }\n}\n\n/**\n * A message received from a broker is not a valid event. Never retried:\n * transports should dead-letter it instead of redelivering it forever.\n */\nexport class InvalidEnvelopeError extends NonRetryableError {\n override name = 'InvalidEnvelopeError';\n override readonly code: string = 'RELAYBUS_INVALID_ENVELOPE';\n}\n\n/** The worker no longer owns the delivery it was processing. */\nexport class LeaseLostError extends RelayBusError {\n override name = 'LeaseLostError';\n override readonly code: string = 'RELAYBUS_LEASE_LOST';\n /**\n * Whether the store said so (a fenced write returned false). A worker that only noticed\n * locally that its lease ran out may still own the delivery in the store and has to give it back.\n */\n readonly storeConfirmed: boolean;\n\n constructor(message: string, options: { readonly storeConfirmed?: boolean } = {}) {\n super(message);\n this.storeConfirmed = options.storeConfirmed ?? false;\n }\n}\n\n/** A handler did not settle within its configured timeout. Retried like any failure. */\nexport class HandlerTimeoutError extends RelayBusError {\n override name = 'HandlerTimeoutError';\n override readonly code: string = 'RELAYBUS_HANDLER_TIMEOUT';\n}\n\n/**\n * Reason of a handler's abort signal when the bus stops before the handler\n * finished. The delivery is released and runs again without losing an attempt.\n */\nexport class DispatcherStoppedError extends RelayBusError {\n override name = 'DispatcherStoppedError';\n override readonly code: string = 'RELAYBUS_DISPATCHER_STOPPED';\n}\n\n/**\n * A delivery came back after its lease expired on its last attempt, so it is\n * dead-lettered without running its handler again.\n */\nexport class AttemptsExhaustedError extends RelayBusError {\n override name = 'AttemptsExhaustedError';\n override readonly code: string = 'RELAYBUS_ATTEMPTS_EXHAUSTED';\n}\n\n/** A lifecycle method was called in a state that does not allow it, such as starting a bus twice. */\nexport class LifecycleError extends RelayBusError {\n override name = 'LifecycleError';\n override readonly code: string = 'RELAYBUS_LIFECYCLE';\n}\n\n/** `drain` still found due deliveries after its maximum number of batches. */\nexport class DrainIncompleteError extends RelayBusError {\n override name = 'DrainIncompleteError';\n override readonly code: string = 'RELAYBUS_DRAIN_INCOMPLETE';\n}\n\nexport interface ValidationIssue {\n readonly message: string;\n readonly path?: readonly PropertyKey[] | undefined;\n}\n\n/** Serializable snapshot of an error, stored with failed deliveries. */\nexport interface ErrorInfo {\n readonly name: string;\n readonly message: string;\n readonly stack?: string | undefined;\n /** The {@link RelayBusError.code} of a relaybus error; absent for any other error. */\n readonly code?: string | undefined;\n}\n\nexport function toErrorInfo(error: unknown): ErrorInfo {\n if (error instanceof RelayBusError) {\n return { name: error.name, message: error.message, stack: error.stack, code: error.code };\n }\n if (error instanceof Error) {\n return { name: error.name, message: error.message, stack: error.stack };\n }\n return { name: 'NonError', message: safeStringify(error) };\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n","import { ConfigurationError, EventValidationError } from './errors.js';\nimport type { StandardSchemaV1 } from './standard-schema.js';\n\n/** String metadata carried with an event, such as `traceparent`. */\nexport type EventHeaders = Readonly<Record<string, string>>;\n\n/**\n * An event as stored and delivered. Field names follow CloudEvents where they\n * overlap (`id`, `type`, `source`, `time` as `occurredAt`, `subject` as `key`).\n */\nexport interface EventEnvelope<TPayload = unknown> {\n /** Globally unique id. Handlers and transports deduplicate on it. */\n readonly id: string;\n /** Event type, for example `order.created.v1`. */\n readonly type: string;\n /** Logical name of the publishing service. */\n readonly source: string;\n /** Ordering and partition key. Events sharing a key are handled in publish order. */\n readonly key: string | null;\n readonly payload: TPayload;\n readonly headers: EventHeaders;\n /** Id shared by every event of one business flow. */\n readonly correlationId: string | null;\n /** Id of the event whose handler published this one. */\n readonly causationId: string | null;\n readonly occurredAt: Date;\n}\n\n/** An instance of a class created with {@link defineEvent}. */\nexport interface DomainEvent<TPayload = unknown> {\n readonly type: string;\n readonly payload: TPayload;\n}\n\n/**\n * Derives the ordering key of an event from its validated payload. Declared bivariantly so an\n * event class stays assignable wherever its payload type is widened.\n */\nexport type EventKeyFunction<TPayload> = {\n bivarianceHack(payload: TPayload): string;\n}['bivarianceHack'];\n\n/** A class created with {@link defineEvent}. */\nexport interface EventClass<TPayload = unknown> {\n new (payload: TPayload): DomainEvent<TPayload>;\n readonly type: string;\n readonly schema: StandardSchemaV1<unknown, TPayload> | undefined;\n /** The `key` given to {@link defineEvent}; undefined for events without one. */\n readonly key?: EventKeyFunction<TPayload> | undefined;\n}\n\n/** Payload type of an event class: `PayloadOf<typeof OrderPlaced>`. */\nexport type PayloadOf<TEvent> = TEvent extends EventClass<infer TPayload> ? TPayload : never;\n\n/** Anything that names an event type: an event class or the type string itself. */\nexport type EventReference = { readonly type: string } | string;\n\nexport interface DefineEventOptions<TSchema extends StandardSchemaV1> {\n /** Validates the payload on publish and before every handler call. */\n readonly schema: TSchema;\n /** Ordering key of every event of this type, from its validated payload. See {@link DefineEventKeyOptions}. */\n readonly key?: (payload: StandardSchemaV1.InferOutput<TSchema>) => string;\n}\n\nexport interface DefineEventKeyOptions<TPayload> {\n /**\n * Ordering key of every event of this type, from its payload: `(payload) => payload.orderId`.\n * Typed from the payload, so a missing field or a non-string fails to compile. `publish` uses it\n * when it is given no `key`; an explicit `key` option wins (for `publishAll`, the one key of the\n * call applies to every event). It must return a non-empty string.\n */\n readonly key: (payload: TPayload) => string;\n}\n\nconst EVENT_TYPE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;\n\nexport function assertEventType(type: string): void {\n if (!EVENT_TYPE_PATTERN.test(type)) {\n throw new ConfigurationError(\n `Event type '${type}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-', starting with a letter or digit`,\n );\n }\n}\n\n/**\n * Defines an event type. Extend the returned class to name it:\n *\n * ```ts\n * export class OrderPlaced extends defineEvent<{ orderId: string }>('order.placed.v1') {}\n * export class OrderShipped extends defineEvent('order.shipped.v1', { schema: orderShippedSchema }) {}\n * export class OrderPaid extends defineEvent('order.paid.v1', {\n * schema: orderPaidSchema,\n * key: (payload) => payload.orderId,\n * }) {}\n * export class OrderCancelled extends defineEvent<{ orderId: string }>('order.cancelled.v1', {\n * key: (payload) => payload.orderId,\n * }) {}\n * ```\n */\nexport function defineEvent<TSchema extends StandardSchemaV1>(\n type: string,\n options: DefineEventOptions<TSchema>,\n): EventClass<StandardSchemaV1.InferOutput<TSchema>>;\nexport function defineEvent<TPayload>(\n type: string,\n options?: DefineEventKeyOptions<TPayload>,\n): EventClass<TPayload>;\nexport function defineEvent(\n type: string,\n options?: Partial<DefineEventOptions<StandardSchemaV1>>,\n): EventClass<unknown> {\n assertEventType(type);\n const schema = options?.schema;\n const key = options?.key;\n return class DefinedEvent implements DomainEvent<unknown> {\n static readonly type = type;\n static readonly schema = schema;\n static readonly key = key;\n readonly type = type;\n constructor(readonly payload: unknown) {}\n };\n}\n\n/** Runs a Standard Schema and returns its output, or throws {@link EventValidationError}. */\n// eslint-disable-next-line relaybus/max-two-params -- exported API validatePayload keeps its public signature\nexport async function validatePayload<TPayload>(\n type: string,\n schema: StandardSchemaV1<unknown, TPayload> | undefined,\n payload: unknown,\n): Promise<TPayload> {\n if (schema === undefined) {\n return payload as TPayload;\n }\n const result = await schema['~standard'].validate(payload);\n if (result.issues !== undefined) {\n const issues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((segment) => (typeof segment === 'object' ? segment.key : segment)),\n }));\n throw new EventValidationError(\n `Payload of '${type}' failed validation: ${issues.map((issue) => issue.message).join('; ')}`,\n issues,\n );\n }\n return result.value;\n}\n\n/**\n * The ordering key an event of `event`'s class takes from its definition, or null when the class\n * defines no key.\n *\n * @throws ConfigurationError when the key function returns an empty string or no string.\n */\nexport function keyFromDefinition(event: DomainEvent, payload: unknown): string | null {\n const keyOf = (event.constructor as Partial<EventClass>).key;\n if (keyOf === undefined) {\n return null;\n }\n const key: unknown = keyOf(payload);\n if (typeof key !== 'string' || key.length === 0) {\n throw new ConfigurationError(\n `The key function of '${event.type}' must return a non-empty string | returned=${typeof key === 'string' ? \"''\" : typeof key}`,\n );\n }\n return key;\n}\n","import type { Clock } from '../clock.js';\n\n/** A task for {@link schedule}: what runs, after how long, whether it repeats and keeps the process alive. */\nexport type ScheduledTask = Parameters<NonNullable<Clock['schedule']>>[0];\n\n/** Cancels a scheduled task; harmless once it ran or was cancelled. */\nexport type CancelTimer = () => void;\n\n/**\n * The one place relaybus starts timers: through `clock.schedule` when the clock controls timers,\n * else with the platform timers.\n */\nexport function schedule(clock: Clock, task: ScheduledTask): CancelTimer {\n return clock.schedule === undefined ? scheduleOnPlatform(task) : clock.schedule(task);\n}\n\n/** Resolves after `milliseconds` on the timers of `clock`. */\nexport function sleep(clock: Clock, milliseconds: number): Promise<void> {\n return new Promise((resolve) => {\n schedule(clock, { run: resolve, delayMs: milliseconds });\n });\n}\n\nfunction scheduleOnPlatform(task: ScheduledTask): CancelTimer {\n if (task.repeat === true) {\n const interval = setInterval(task.run, task.delayMs);\n if (task.unref === true) {\n interval.unref();\n }\n return () => clearInterval(interval);\n }\n const timeout = setTimeout(task.run, task.delayMs);\n if (task.unref === true) {\n timeout.unref();\n }\n return () => clearTimeout(timeout);\n}\n","import { ConfigurationError } from '../errors.js';\n\n/** Returns `value` when it is an integer of at least 1, else throws a {@link ConfigurationError} naming the option. */\nexport function assertPositiveInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new ConfigurationError(`${name} must be a positive integer, received ${String(value)}`);\n }\n return value;\n}\n"]}
|