@relaybus/core 0.2.0 → 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/README.md +120 -8
- package/dist/chunk-J76M7XTS.cjs +323 -0
- package/dist/chunk-J76M7XTS.cjs.map +1 -0
- package/dist/chunk-NOR5JHVN.js +295 -0
- package/dist/chunk-NOR5JHVN.js.map +1 -0
- package/dist/index.cjs +2775 -1625
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +321 -471
- package/dist/index.d.ts +321 -471
- package/dist/index.js +2712 -1625
- package/dist/index.js.map +1 -1
- package/dist/internal/index.cjs +662 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.d.cts +438 -0
- package/dist/internal/index.d.ts +438 -0
- package/dist/internal/index.js +617 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/transport-BlfzIBZD.d.cts +535 -0
- package/dist/transport-BlfzIBZD.d.ts +535 -0
- package/package.json +18 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ pnpm add @relaybus/core @relaybus/nestjs @relaybus/postgres pg
|
|
|
13
13
|
|
|
14
14
|
| Package | Use it for |
|
|
15
15
|
| ------------------------------------------------------------------------ | ------------------------------------------------------------- |
|
|
16
|
-
| [`@relaybus/nestjs`](https://www.npmjs.com/package/@relaybus/nestjs) | `RelayBusModule` and the `@
|
|
16
|
+
| [`@relaybus/nestjs`](https://www.npmjs.com/package/@relaybus/nestjs) | `RelayBusModule` and the `@RelayBusConsumer` decorator |
|
|
17
17
|
| [`@relaybus/postgres`](https://www.npmjs.com/package/@relaybus/postgres) | PostgreSQL store (pg, Prisma, TypeORM, Knex, Kysely, Drizzle) |
|
|
18
18
|
| [`@relaybus/mongodb`](https://www.npmjs.com/package/@relaybus/mongodb) | MongoDB store (driver or Mongoose sessions) |
|
|
19
19
|
| [`@relaybus/kafka`](https://www.npmjs.com/package/@relaybus/kafka) | Kafka transport between services |
|
|
@@ -33,7 +33,7 @@ export class AppModule {}
|
|
|
33
33
|
// shipping.handlers.ts: consume with a decorator
|
|
34
34
|
@Injectable()
|
|
35
35
|
export class ShippingHandlers {
|
|
36
|
-
@
|
|
36
|
+
@RelayBusConsumer(OrderPlaced, { retry: { attempts: 5 } })
|
|
37
37
|
async ship(event: EventEnvelope<{ orderId: string }>) {
|
|
38
38
|
/* runs after the order transaction commits */
|
|
39
39
|
}
|
|
@@ -84,6 +84,7 @@ Use a database store in production: [`@relaybus/postgres`](../postgres) or
|
|
|
84
84
|
| `source` | `'relaybus'` | Service name recorded on every event. |
|
|
85
85
|
| `transactionMode` | `'required'` | `'optional'` lets `publish` open its own transaction. |
|
|
86
86
|
| `transactionResolver` | — | Returns the caller's active transaction, e.g. from `@nestjs-cls/transactional`. |
|
|
87
|
+
| `transactionTimeout` | `'5s'` | Budget of every `bus.transaction`; the database enforces it where it can, an overrun rolls back with `TransactionTimeoutError`. |
|
|
87
88
|
| `unroutedEvents` | `'warn'` | `'throw'` rejects events without any handler or route. |
|
|
88
89
|
| `retry` | 10 attempts, `1s`, ×2, max `10m`, full jitter | Default handler retry policy. `attempts: Infinity` retries forever. |
|
|
89
90
|
| `handlerTimeout` | `false` | Default handler timeout. |
|
|
@@ -92,7 +93,7 @@ Use a database store in production: [`@relaybus/postgres`](../postgres) or
|
|
|
92
93
|
| `dispatcher.batchSize` | concurrency | Deliveries claimed per query. |
|
|
93
94
|
| `dispatcher.pollInterval` | `'1s'` | Idle wait between claims; retries are picked up on this cadence. |
|
|
94
95
|
| `dispatcher.leaseDuration` | `'30s'` | Lease length, renewed every third while a handler runs. |
|
|
95
|
-
| `dispatcher.shutdownTimeout` | `'30s'` |
|
|
96
|
+
| `dispatcher.shutdownTimeout` | `'30s'` | Longest `stop` takes in all (default of `stop({ timeout })`); handlers are aborted and released at four fifths of it. |
|
|
96
97
|
| `dispatcher.completionBatchWindow` | `'2ms'` | Deliveries that finish within this window are completed with one store call; `0` completes one by one. |
|
|
97
98
|
| `dispatcher.completionBatchSize` | `200` | Most deliveries completed in one store call. |
|
|
98
99
|
| `retention.completed` | `'7d'` | Delete events whose deliveries all completed after this; also the deduplication horizon. A completed delivery is deleted at once (see below). |
|
|
@@ -100,9 +101,11 @@ Use a database store in production: [`@relaybus/postgres`](../postgres) or
|
|
|
100
101
|
| `retention.interval` | `'1h'` | Cleanup cadence. |
|
|
101
102
|
| `transports` / `routes` / `subscriptions` | — | Broker integration, see below. |
|
|
102
103
|
| `setupStore` | `true` | Create tables and indexes on `start`. |
|
|
103
|
-
| `observers` | `[]` | Lifecycle hooks for logs, metrics and tracing.
|
|
104
|
+
| `observers` | `[]` | Lifecycle hooks for logs, metrics and tracing (see Observability below). |
|
|
105
|
+
| `tracer` | none | Spans around publish, send and handler runs; writes `traceparent`/`tracestate` into published events. `@relaybus/otel` provides one. |
|
|
106
|
+
| `backlogInterval` | off | Reads `statistics()` this often while running and reports it to `onBacklog`. |
|
|
104
107
|
| `logger` | `console` | `{ log, warn, error }`. |
|
|
105
|
-
| `clock`, `generateIdentifier`, `random` | system | Replace in tests.
|
|
108
|
+
| `clock`, `generateIdentifier`, `random` | system | Replace in tests. A clock with `schedule` also runs the bus's timers. |
|
|
106
109
|
|
|
107
110
|
Durations are milliseconds or strings such as `'250ms'`, `'30s'`, `'5m'`, `'1h'`, `'7d'`.
|
|
108
111
|
|
|
@@ -123,6 +126,11 @@ order: `options.transaction`, the transaction of `bus.transaction()`, then
|
|
|
123
126
|
and `id` on `publish` for idempotent publishing. Register every handler in each
|
|
124
127
|
process that publishes the event: deliveries are created at publish time.
|
|
125
128
|
|
|
129
|
+
Declare the ordering key on the event to apply it to every publish: `defineEvent('order.placed.v1',
|
|
130
|
+
{ schema, key: (payload) => payload.orderId })` (or `defineEvent<Payload>(type, { key })`). The
|
|
131
|
+
function is typed from the payload, so a wrong field fails to compile. An explicit `key` option
|
|
132
|
+
overrides it; in `publishAll` an explicit key applies to every event of the call.
|
|
133
|
+
|
|
126
134
|
## Handlers
|
|
127
135
|
|
|
128
136
|
`subscribe(event, handler, { id, retry, timeout, concurrency, transactional })`.
|
|
@@ -248,9 +256,11 @@ class ShipmentHandlers implements HandlersOf<typeof OrderEvents, 'OrderPlaced'>
|
|
|
248
256
|
- Received events (`bus.receive`, called by Kafka and RabbitMQ consumers) are matched to the
|
|
249
257
|
catalog handlers by event type, idempotently on the event id, and the payload is validated
|
|
250
258
|
against the event's schema before the handler runs; a payload that fails it is dead-lettered.
|
|
251
|
-
- Per-key order holds **per
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
- Per-key order on the wire holds **per destination**: every route to one destination of one
|
|
260
|
+
transport shares one queue per key, so `OrderPlaced` and `OrderCancelled` of one key reach the
|
|
261
|
+
broker in publish order even when a send of the first is retried (Kafka: same partition;
|
|
262
|
+
RabbitMQ: `ordered: true`). On the consuming side order holds **per handler**: two handlers of
|
|
263
|
+
two types run independently and can interleave.
|
|
254
264
|
|
|
255
265
|
## Transports
|
|
256
266
|
|
|
@@ -268,11 +278,73 @@ Routes are deliveries like any handler: retried until the broker accepts them
|
|
|
268
278
|
`bus.receive(envelope)`, which stores the event once by id for local handlers;
|
|
269
279
|
transports acknowledge only after it resolves and dead-letter `InvalidEnvelopeError`.
|
|
270
280
|
|
|
281
|
+
Every route to one destination of one transport is **one handler**, with the id
|
|
282
|
+
`relaybus.transport:<transport>:<destination>` (`handlers()` lists it once per routed
|
|
283
|
+
event type). Its deliveries of one key are sent one at a time in commit order, whatever
|
|
284
|
+
their event types: while the oldest is retried, the later ones of that key wait until it
|
|
285
|
+
is sent or dead-lettered (a dead-lettered head releases them, as for any handler). Other
|
|
286
|
+
keys and unkeyed events are not held back. Because the routes share one queue, they must
|
|
287
|
+
resolve to the same `retry` and `timeout`; `new RelayBus` throws a `ConfigurationError`
|
|
288
|
+
when they differ, and when one event type is routed to the same destination twice. Each
|
|
289
|
+
delivery is one transport call: a hot key is sent one event per round trip (sending a run
|
|
290
|
+
of one key's deliveries in one ordered call is a possible follow-up, not implemented).
|
|
291
|
+
|
|
292
|
+
### Upgrading from 0.2.0
|
|
293
|
+
|
|
294
|
+
0.2.0 registered one route per event type, with the id
|
|
295
|
+
`relaybus.transport:<transport>:<destination>:<eventType>`, so events of one key but two
|
|
296
|
+
types could reach the broker out of order. 0.3.0 groups them under
|
|
297
|
+
`relaybus.transport:<transport>:<destination>`. Nothing has to be migrated: deliveries
|
|
298
|
+
stored under a 0.2.0 id, including those written by 0.2.0 processes during a rolling
|
|
299
|
+
deploy, are still claimed and sent by the new route; the store rows keep their old id.
|
|
300
|
+
|
|
301
|
+
- **Order during the upgrade.** A delivery under a 0.2.0 id and one under the new id are
|
|
302
|
+
two queues, so one key's backlog from 0.2.0 is not ordered against events published
|
|
303
|
+
after the upgrade. Where that matters, let the route backlog drain first
|
|
304
|
+
(`listDeliveries({ handler: '<0.2.0 id>', status: 'pending' })` returns nothing) and
|
|
305
|
+
stop 0.2.0 publishers before new ones start.
|
|
306
|
+
- **Routes to one destination with different `retry` or `timeout`** now fail at
|
|
307
|
+
construction; give them the same settings.
|
|
308
|
+
- Code or dashboards that match route ids (`handlers()`, `listDeliveries({ handler })`,
|
|
309
|
+
`HandlerContext.handler`, observer events) see the new id for new deliveries.
|
|
310
|
+
|
|
271
311
|
## Operations
|
|
272
312
|
|
|
273
313
|
`listDeliveries(query)`, `requeue(ids)`, `statistics()`, `purgeExpired()`,
|
|
274
314
|
`handlers()`, `runOnce()` and `drain()` (tests), `start()` and `stop()`.
|
|
275
315
|
|
|
316
|
+
**`stop({ timeout })` is bounded.** The whole shutdown (stop claiming, wait for running
|
|
317
|
+
handlers, hand back leases, close transports and the store) takes at most `timeout`, default
|
|
318
|
+
`dispatcher.shutdownTimeout` (30 s). Running handlers get four fifths of it; then their
|
|
319
|
+
`context.signal` aborts and their deliveries are released for another process. If the deadline
|
|
320
|
+
itself passes (a hung query, a broker that does not answer), `stop` gives up waiting, force-closes
|
|
321
|
+
what relaybus opened itself (the pool a store created from `connectionString`, its listening
|
|
322
|
+
connection, a RabbitMQ transport's connection) and resolves; it never closes a pool or client you
|
|
323
|
+
passed in, and never rejects because of the deadline. It resolves with a `StopResult`:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
const { graceful, abandonedDeliveries, durationMs } = await bus.stop({ timeout: '25s' });
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`graceful` is false when the deadline passed, and a warning is logged
|
|
330
|
+
(`[RelayBus](stop) Shutdown deadline passed, forced close | abandoned=1 | timeoutMs=25000`).
|
|
331
|
+
Abandoned deliveries keep their lease until it expires (`dispatcher.leaseDuration`), then another
|
|
332
|
+
process runs them again: at-least-once still holds, so keep handlers idempotent. Kafka's
|
|
333
|
+
`disconnect` has no forced variant; past the deadline the bus simply stops waiting for it.
|
|
334
|
+
|
|
335
|
+
**Observability.** Besides the lifecycle hooks, observers can implement timed hooks:
|
|
336
|
+
`onPublishFinished` (event types, duration, outcome), `onClaimFinished` (requested, claimed,
|
|
337
|
+
lanes, duration), `onHandlerStarted` (end-to-end latency from `occurredAt`),
|
|
338
|
+
`onHandlerFinished` (duration, outcome, error with its code), `onTransportSent` (transport,
|
|
339
|
+
system, destination, duration, outcome), `onReceiveFinished` (transport, destination, source,
|
|
340
|
+
trace context; `accepted`, `duplicate`, `ignored`, `invalid` or `error`), `onInvalidMessage`
|
|
341
|
+
(a transport could not decode an inbound message, so it never reached the bus: transport,
|
|
342
|
+
destination, reason) and `onBacklog`
|
|
343
|
+
(pending deliveries and age of the oldest due one, every `backlogInterval`). A hook no observer
|
|
344
|
+
implements costs nothing, not even a clock read. Hooks run synchronously and must not throw; a
|
|
345
|
+
throwing hook is reported to `onFailure` with stage `observer`. `traceContextOf(event.headers)`
|
|
346
|
+
returns the W3C trace context an event carries. `@relaybus/otel` maps all of it to OpenTelemetry.
|
|
347
|
+
|
|
276
348
|
**Completed deliveries are deleted.** Completing a delivery removes its row instead
|
|
277
349
|
of updating it, which is what keeps the write cost of a delivery constant (see
|
|
278
350
|
`docs/performance/write-amplification.md`). `listDeliveries({ status: 'completed' })`
|
|
@@ -283,6 +355,46 @@ on it: an event remembers every handler it ever had a delivery for, so a
|
|
|
283
355
|
redelivered or republished event never runs a completed handler again, for as
|
|
284
356
|
long as the event is kept (`retention.completed`).
|
|
285
357
|
|
|
358
|
+
## Error codes
|
|
359
|
+
|
|
360
|
+
Every relaybus error has a stable, machine-readable `code` next to its `name`. Codes are
|
|
361
|
+
never renamed or reused once released, so branch on them in alerts and retry rules. The
|
|
362
|
+
`lastError` of a dead or retried delivery and the `error` of observer events carry the
|
|
363
|
+
`code` of relaybus errors; other errors keep `name`, `message` and `stack` only.
|
|
364
|
+
|
|
365
|
+
| Code | Error class | Package |
|
|
366
|
+
| ------------------------------------------ | ------------------------------- | -------------------- |
|
|
367
|
+
| `RELAYBUS_ERROR` | `RelayBusError` | `@relaybus/core` |
|
|
368
|
+
| `RELAYBUS_CONFIGURATION` | `ConfigurationError` | `@relaybus/core` |
|
|
369
|
+
| `RELAYBUS_TRANSACTION_REQUIRED` | `TransactionRequiredError` | `@relaybus/core` |
|
|
370
|
+
| `RELAYBUS_TRANSACTION_TIMEOUT` | `TransactionTimeoutError` | `@relaybus/core` |
|
|
371
|
+
| `RELAYBUS_NON_RETRYABLE` | `NonRetryableError` | `@relaybus/core` |
|
|
372
|
+
| `RELAYBUS_EVENT_VALIDATION` | `EventValidationError` | `@relaybus/core` |
|
|
373
|
+
| `RELAYBUS_INVALID_ENVELOPE` | `InvalidEnvelopeError` | `@relaybus/core` |
|
|
374
|
+
| `RELAYBUS_LEASE_LOST` | `LeaseLostError` | `@relaybus/core` |
|
|
375
|
+
| `RELAYBUS_HANDLER_TIMEOUT` | `HandlerTimeoutError` | `@relaybus/core` |
|
|
376
|
+
| `RELAYBUS_DISPATCHER_STOPPED` | `DispatcherStoppedError` | `@relaybus/core` |
|
|
377
|
+
| `RELAYBUS_ATTEMPTS_EXHAUSTED` | `AttemptsExhaustedError` | `@relaybus/core` |
|
|
378
|
+
| `RELAYBUS_LIFECYCLE` | `LifecycleError` | `@relaybus/core` |
|
|
379
|
+
| `RELAYBUS_DRAIN_INCOMPLETE` | `DrainIncompleteError` | `@relaybus/core` |
|
|
380
|
+
| `RELAYBUS_KAFKA_INVALID_MESSAGE` | `InvalidKafkaMessageError` | `@relaybus/kafka` |
|
|
381
|
+
| `RELAYBUS_KAFKA_UNENCODABLE_EVENT` | `UnencodableEventError` | `@relaybus/kafka` |
|
|
382
|
+
| `RELAYBUS_KAFKA_PUBLISH_TIMEOUT` | `PublishTimeoutError` | `@relaybus/kafka` |
|
|
383
|
+
| `RELAYBUS_KAFKA_HANDLER_FAILED` | `HandlerFailedError` | `@relaybus/kafka` |
|
|
384
|
+
| `RELAYBUS_KAFKA_TRANSPORT_CLOSED` | `TransportClosedError` | `@relaybus/kafka` |
|
|
385
|
+
| `RELAYBUS_KAFKA_GROUP_JOIN_TIMEOUT` | `GroupJoinTimeoutError` | `@relaybus/kafka` |
|
|
386
|
+
| `RELAYBUS_POSTGRES_RECONCILE_FAILED` | `ReconcileFailedError` | `@relaybus/postgres` |
|
|
387
|
+
| `RELAYBUS_RABBITMQ_UNAVAILABLE` | `RabbitMqUnavailableError` | `@relaybus/rabbitmq` |
|
|
388
|
+
| `RELAYBUS_RABBITMQ_PUBLISH_REJECTED` | `RabbitMqPublishRejectedError` | `@relaybus/rabbitmq` |
|
|
389
|
+
| `RELAYBUS_RABBITMQ_UNROUTABLE_MESSAGE` | `UnroutableMessageError` | `@relaybus/rabbitmq` |
|
|
390
|
+
| `RELAYBUS_RABBITMQ_MALFORMED_MESSAGE` | `MalformedMessageError` | `@relaybus/rabbitmq` |
|
|
391
|
+
| `RELAYBUS_RABBITMQ_UNSERIALIZABLE_PAYLOAD` | `UnserializablePayloadError` | `@relaybus/rabbitmq` |
|
|
392
|
+
| `RELAYBUS_RABBITMQ_UNENCODABLE_EVENT` | `RabbitMqUnencodableEventError` | `@relaybus/rabbitmq` |
|
|
393
|
+
| `RELAYBUS_RABBITMQ_AMQP_LIMIT_EXCEEDED` | `AmqpLimitExceededError` | `@relaybus/rabbitmq` |
|
|
394
|
+
|
|
395
|
+
An error class you derive from `RelayBusError` inherits `RELAYBUS_ERROR` unless it sets
|
|
396
|
+
its own `code`. See [Error codes](https://berkcansavur.github.io/relaybus/concepts/error-codes) for what each one means.
|
|
397
|
+
|
|
286
398
|
## Writing a store
|
|
287
399
|
|
|
288
400
|
Implement `EventStore` and run `eventStoreContract` from
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var RelayBusError = class extends Error {
|
|
5
|
+
// Explicit names survive minification, unlike `new.target.name`.
|
|
6
|
+
name = "RelayBusError";
|
|
7
|
+
/**
|
|
8
|
+
* Stable, machine-readable identifier of the error class, such as `RELAYBUS_LEASE_LOST`.
|
|
9
|
+
* Unlike `name` and `message`, a code is never renamed once released; branch on it in
|
|
10
|
+
* alerts and dashboards. Subclasses declared outside relaybus inherit `RELAYBUS_ERROR`.
|
|
11
|
+
*/
|
|
12
|
+
code = "RELAYBUS_ERROR";
|
|
13
|
+
};
|
|
14
|
+
var ConfigurationError = class extends RelayBusError {
|
|
15
|
+
name = "ConfigurationError";
|
|
16
|
+
code = "RELAYBUS_CONFIGURATION";
|
|
17
|
+
};
|
|
18
|
+
var TransactionRequiredError = class extends RelayBusError {
|
|
19
|
+
name = "TransactionRequiredError";
|
|
20
|
+
code = "RELAYBUS_TRANSACTION_REQUIRED";
|
|
21
|
+
};
|
|
22
|
+
var TransactionTimeoutError = class extends RelayBusError {
|
|
23
|
+
name = "TransactionTimeoutError";
|
|
24
|
+
code = "RELAYBUS_TRANSACTION_TIMEOUT";
|
|
25
|
+
};
|
|
26
|
+
var NonRetryableError = class extends RelayBusError {
|
|
27
|
+
name = "NonRetryableError";
|
|
28
|
+
code = "RELAYBUS_NON_RETRYABLE";
|
|
29
|
+
};
|
|
30
|
+
var EventValidationError = class extends NonRetryableError {
|
|
31
|
+
constructor(message, issues) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.issues = issues;
|
|
34
|
+
}
|
|
35
|
+
issues;
|
|
36
|
+
name = "EventValidationError";
|
|
37
|
+
code = "RELAYBUS_EVENT_VALIDATION";
|
|
38
|
+
};
|
|
39
|
+
var InvalidEnvelopeError = class extends NonRetryableError {
|
|
40
|
+
name = "InvalidEnvelopeError";
|
|
41
|
+
code = "RELAYBUS_INVALID_ENVELOPE";
|
|
42
|
+
};
|
|
43
|
+
var LeaseLostError = class extends RelayBusError {
|
|
44
|
+
name = "LeaseLostError";
|
|
45
|
+
code = "RELAYBUS_LEASE_LOST";
|
|
46
|
+
/**
|
|
47
|
+
* Whether the store said so (a fenced write returned false). A worker that only noticed
|
|
48
|
+
* locally that its lease ran out may still own the delivery in the store and has to give it back.
|
|
49
|
+
*/
|
|
50
|
+
storeConfirmed;
|
|
51
|
+
constructor(message, options = {}) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.storeConfirmed = options.storeConfirmed ?? false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var HandlerTimeoutError = class extends RelayBusError {
|
|
57
|
+
name = "HandlerTimeoutError";
|
|
58
|
+
code = "RELAYBUS_HANDLER_TIMEOUT";
|
|
59
|
+
};
|
|
60
|
+
var DispatcherStoppedError = class extends RelayBusError {
|
|
61
|
+
name = "DispatcherStoppedError";
|
|
62
|
+
code = "RELAYBUS_DISPATCHER_STOPPED";
|
|
63
|
+
};
|
|
64
|
+
var AttemptsExhaustedError = class extends RelayBusError {
|
|
65
|
+
name = "AttemptsExhaustedError";
|
|
66
|
+
code = "RELAYBUS_ATTEMPTS_EXHAUSTED";
|
|
67
|
+
};
|
|
68
|
+
var LifecycleError = class extends RelayBusError {
|
|
69
|
+
name = "LifecycleError";
|
|
70
|
+
code = "RELAYBUS_LIFECYCLE";
|
|
71
|
+
};
|
|
72
|
+
var DrainIncompleteError = class extends RelayBusError {
|
|
73
|
+
name = "DrainIncompleteError";
|
|
74
|
+
code = "RELAYBUS_DRAIN_INCOMPLETE";
|
|
75
|
+
};
|
|
76
|
+
function toErrorInfo(error) {
|
|
77
|
+
if (error instanceof RelayBusError) {
|
|
78
|
+
return { name: error.name, message: error.message, stack: error.stack, code: error.code };
|
|
79
|
+
}
|
|
80
|
+
if (error instanceof Error) {
|
|
81
|
+
return { name: error.name, message: error.message, stack: error.stack };
|
|
82
|
+
}
|
|
83
|
+
return { name: "NonError", message: safeStringify(error) };
|
|
84
|
+
}
|
|
85
|
+
function safeStringify(value) {
|
|
86
|
+
if (typeof value === "string") {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return JSON.stringify(value) ?? String(value);
|
|
91
|
+
} catch {
|
|
92
|
+
return String(value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/duration.ts
|
|
97
|
+
var MILLISECONDS_PER_UNIT = {
|
|
98
|
+
ms: 1,
|
|
99
|
+
s: 1e3,
|
|
100
|
+
m: 6e4,
|
|
101
|
+
h: 36e5,
|
|
102
|
+
d: 864e5
|
|
103
|
+
};
|
|
104
|
+
var DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
|
|
105
|
+
function toMilliseconds(value, name) {
|
|
106
|
+
if (typeof value === "number") {
|
|
107
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
108
|
+
throw new ConfigurationError(
|
|
109
|
+
`${name} must be a non-negative finite number of milliseconds, received ${value}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
const match = DURATION_PATTERN.exec(value);
|
|
115
|
+
if (match === null) {
|
|
116
|
+
throw new ConfigurationError(
|
|
117
|
+
`${name} must look like '500ms', '30s', '5m', '1h' or '7d', received '${value}'`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const [, amount, unit] = match;
|
|
121
|
+
return Number(amount) * MILLISECONDS_PER_UNIT[unit];
|
|
122
|
+
}
|
|
123
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
124
|
+
|
|
125
|
+
// src/event.ts
|
|
126
|
+
var EVENT_TYPE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
|
127
|
+
function assertEventType(type) {
|
|
128
|
+
if (!EVENT_TYPE_PATTERN.test(type)) {
|
|
129
|
+
throw new ConfigurationError(
|
|
130
|
+
`Event type '${type}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-', starting with a letter or digit`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function defineEvent(type, options) {
|
|
135
|
+
assertEventType(type);
|
|
136
|
+
const schema = options?.schema;
|
|
137
|
+
const key = options?.key;
|
|
138
|
+
return class DefinedEvent {
|
|
139
|
+
constructor(payload) {
|
|
140
|
+
this.payload = payload;
|
|
141
|
+
}
|
|
142
|
+
payload;
|
|
143
|
+
static type = type;
|
|
144
|
+
static schema = schema;
|
|
145
|
+
static key = key;
|
|
146
|
+
type = type;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function validatePayload(type, schema, payload) {
|
|
150
|
+
if (schema === void 0) {
|
|
151
|
+
return payload;
|
|
152
|
+
}
|
|
153
|
+
const result = await schema["~standard"].validate(payload);
|
|
154
|
+
if (result.issues !== void 0) {
|
|
155
|
+
const issues = result.issues.map((issue) => ({
|
|
156
|
+
message: issue.message,
|
|
157
|
+
path: issue.path?.map((segment) => typeof segment === "object" ? segment.key : segment)
|
|
158
|
+
}));
|
|
159
|
+
throw new EventValidationError(
|
|
160
|
+
`Payload of '${type}' failed validation: ${issues.map((issue) => issue.message).join("; ")}`,
|
|
161
|
+
issues
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return result.value;
|
|
165
|
+
}
|
|
166
|
+
function keyFromDefinition(event, payload) {
|
|
167
|
+
const keyOf = event.constructor.key;
|
|
168
|
+
if (keyOf === void 0) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const key = keyOf(payload);
|
|
172
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
173
|
+
throw new ConfigurationError(
|
|
174
|
+
`The key function of '${event.type}' must return a non-empty string | returned=${typeof key === "string" ? "''" : typeof key}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return key;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/store-kit/lease-settlement.ts
|
|
181
|
+
var LeaseSettlement = class {
|
|
182
|
+
/** Renews the lease when it is still held; false otherwise. */
|
|
183
|
+
extendLease(extension) {
|
|
184
|
+
const held = this.findHeld(extension.lease);
|
|
185
|
+
return held === void 0 ? false : this.writeExtension(held, extension);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Applies `change` when the lease is still held. Once the store answered (either way) the lease
|
|
189
|
+
* is forgotten as handed out; when the write throws it stays registered, since the caller may
|
|
190
|
+
* retry with the same lease.
|
|
191
|
+
*/
|
|
192
|
+
changeClaim(request) {
|
|
193
|
+
const held = this.findHeld(request.lease);
|
|
194
|
+
if (held === void 0) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const changed = this.writeChange(held, request);
|
|
198
|
+
if (typeof changed === "boolean") {
|
|
199
|
+
this.forgetHandedOut(request.lease);
|
|
200
|
+
return changed;
|
|
201
|
+
}
|
|
202
|
+
return this.#forgetOnceAnswered(request.lease, changed);
|
|
203
|
+
}
|
|
204
|
+
async #forgetOnceAnswered(lease, answer) {
|
|
205
|
+
const changed = await answer;
|
|
206
|
+
this.forgetHandedOut(lease);
|
|
207
|
+
return changed;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/values/composite-key.ts
|
|
212
|
+
var SEPARATOR = "\0";
|
|
213
|
+
var CompositeKey = class {
|
|
214
|
+
static of(first, second) {
|
|
215
|
+
return `${first}${SEPARATOR}${second}`;
|
|
216
|
+
}
|
|
217
|
+
/** The start every key of `first` shares, to find them all by prefix. */
|
|
218
|
+
static startOf(first) {
|
|
219
|
+
return `${first}${SEPARATOR}`;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// src/internal/timers.ts
|
|
224
|
+
function schedule(clock, task) {
|
|
225
|
+
return clock.schedule === void 0 ? scheduleOnPlatform(task) : clock.schedule(task);
|
|
226
|
+
}
|
|
227
|
+
function sleep(clock, milliseconds) {
|
|
228
|
+
return new Promise((resolve) => {
|
|
229
|
+
schedule(clock, { run: resolve, delayMs: milliseconds });
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function scheduleOnPlatform(task) {
|
|
233
|
+
if (task.repeat === true) {
|
|
234
|
+
const interval = setInterval(task.run, task.delayMs);
|
|
235
|
+
if (task.unref === true) {
|
|
236
|
+
interval.unref();
|
|
237
|
+
}
|
|
238
|
+
return () => clearInterval(interval);
|
|
239
|
+
}
|
|
240
|
+
const timeout = setTimeout(task.run, task.delayMs);
|
|
241
|
+
if (task.unref === true) {
|
|
242
|
+
timeout.unref();
|
|
243
|
+
}
|
|
244
|
+
return () => clearTimeout(timeout);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/values/milliseconds.ts
|
|
248
|
+
var Milliseconds = class _Milliseconds {
|
|
249
|
+
/** Zero or more milliseconds. */
|
|
250
|
+
static of(value, name) {
|
|
251
|
+
return toMilliseconds(value, name);
|
|
252
|
+
}
|
|
253
|
+
/** More than zero milliseconds: a wait, a timeout or an age that must pass at all. */
|
|
254
|
+
static positive(value, name) {
|
|
255
|
+
const milliseconds = toMilliseconds(value, name);
|
|
256
|
+
if (milliseconds === 0) {
|
|
257
|
+
throw new ConfigurationError(`${name} must be greater than zero`);
|
|
258
|
+
}
|
|
259
|
+
return milliseconds;
|
|
260
|
+
}
|
|
261
|
+
/** A duration that drives a timer: more than zero, and no longer than timers can represent. */
|
|
262
|
+
static forTimer(value, name) {
|
|
263
|
+
const milliseconds = _Milliseconds.positive(value, name);
|
|
264
|
+
if (milliseconds > MAX_TIMER_DELAY_MS) {
|
|
265
|
+
throw new ConfigurationError(
|
|
266
|
+
`${name} must not exceed ${MAX_TIMER_DELAY_MS}ms (about 24.8 days), received ${milliseconds}ms`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return milliseconds;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/values/delivery-lease-identity.ts
|
|
274
|
+
var DeliveryLeaseIdentity = class {
|
|
275
|
+
static of(deliveryId, leaseToken) {
|
|
276
|
+
return CompositeKey.of(deliveryId, leaseToken);
|
|
277
|
+
}
|
|
278
|
+
/** The start of the identity of every claim of `deliveryId`. */
|
|
279
|
+
static prefixOf(deliveryId) {
|
|
280
|
+
return CompositeKey.startOf(deliveryId);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/values/positive-integer.ts
|
|
285
|
+
var PositiveInteger = class {
|
|
286
|
+
/** Returns `value` when it is an integer of at least 1, else throws a {@link ConfigurationError} naming the option. */
|
|
287
|
+
static of(value, name) {
|
|
288
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
289
|
+
throw new ConfigurationError(`${name} must be a positive integer, received ${String(value)}`);
|
|
290
|
+
}
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
exports.AttemptsExhaustedError = AttemptsExhaustedError;
|
|
296
|
+
exports.CompositeKey = CompositeKey;
|
|
297
|
+
exports.ConfigurationError = ConfigurationError;
|
|
298
|
+
exports.DeliveryLeaseIdentity = DeliveryLeaseIdentity;
|
|
299
|
+
exports.DispatcherStoppedError = DispatcherStoppedError;
|
|
300
|
+
exports.DrainIncompleteError = DrainIncompleteError;
|
|
301
|
+
exports.EventValidationError = EventValidationError;
|
|
302
|
+
exports.HandlerTimeoutError = HandlerTimeoutError;
|
|
303
|
+
exports.InvalidEnvelopeError = InvalidEnvelopeError;
|
|
304
|
+
exports.LeaseLostError = LeaseLostError;
|
|
305
|
+
exports.LeaseSettlement = LeaseSettlement;
|
|
306
|
+
exports.LifecycleError = LifecycleError;
|
|
307
|
+
exports.MAX_TIMER_DELAY_MS = MAX_TIMER_DELAY_MS;
|
|
308
|
+
exports.Milliseconds = Milliseconds;
|
|
309
|
+
exports.NonRetryableError = NonRetryableError;
|
|
310
|
+
exports.PositiveInteger = PositiveInteger;
|
|
311
|
+
exports.RelayBusError = RelayBusError;
|
|
312
|
+
exports.TransactionRequiredError = TransactionRequiredError;
|
|
313
|
+
exports.TransactionTimeoutError = TransactionTimeoutError;
|
|
314
|
+
exports.assertEventType = assertEventType;
|
|
315
|
+
exports.defineEvent = defineEvent;
|
|
316
|
+
exports.keyFromDefinition = keyFromDefinition;
|
|
317
|
+
exports.schedule = schedule;
|
|
318
|
+
exports.sleep = sleep;
|
|
319
|
+
exports.toErrorInfo = toErrorInfo;
|
|
320
|
+
exports.toMilliseconds = toMilliseconds;
|
|
321
|
+
exports.validatePayload = validatePayload;
|
|
322
|
+
//# sourceMappingURL=chunk-J76M7XTS.cjs.map
|
|
323
|
+
//# sourceMappingURL=chunk-J76M7XTS.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/duration.ts","../src/event.ts","../src/store-kit/lease-settlement.ts","../src/values/composite-key.ts","../src/internal/timers.ts","../src/values/milliseconds.ts","../src/values/delivery-lease-identity.ts","../src/values/positive-integer.ts"],"names":[],"mappings":"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","file":"chunk-J76M7XTS.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 } from './errors.js';\n\n/**\n * A duration in milliseconds, or a string such as `'250ms'`, `'30s'`, `'5m'`,\n * `'1h'` or `'7d'`.\n */\nexport type Duration = number | `${number}${DurationUnit}`;\n\ntype DurationUnit = 'ms' | 's' | 'm' | 'h' | 'd';\n\nconst MILLISECONDS_PER_UNIT: Record<DurationUnit, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nconst DURATION_PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/;\n\nexport function toMilliseconds(value: Duration, name: string): number {\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || value < 0) {\n throw new ConfigurationError(\n `${name} must be a non-negative finite number of milliseconds, received ${value}`,\n );\n }\n return value;\n }\n const match = DURATION_PATTERN.exec(value);\n if (match === null) {\n throw new ConfigurationError(\n `${name} must look like '500ms', '30s', '5m', '1h' or '7d', received '${value}'`,\n );\n }\n const [, amount, unit] = match as unknown as [string, string, DurationUnit];\n return Number(amount) * MILLISECONDS_PER_UNIT[unit];\n}\n\n/** Largest delay Node.js timers support. Longer values fire after 1ms instead. */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\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 { ErrorInfo } from '../errors.js';\nimport type { LeaseReference } from '../event-store.js';\n\n/** How a claim ends without completing: retried later, given back, or dead-lettered. */\nexport type SettlementChange =\n | {\n readonly kind: 'reschedule';\n readonly availableAt: Date;\n readonly error: ErrorInfo;\n readonly now: Date;\n }\n | { readonly kind: 'release'; readonly now: Date }\n | { readonly kind: 'bury'; readonly error: ErrorInfo; readonly now: Date };\n\n/** A change of the claim a lease names. */\nexport type LeaseChange = { readonly lease: LeaseReference; readonly change: SettlementChange };\n\n/** A lease renewal; `now` for a store that records when it happened. */\nexport type LeaseExtension = {\n readonly lease: LeaseReference;\n readonly leaseExpiresAt: Date;\n readonly now?: Date;\n};\n\n/**\n * What a store answers about a lease: at once when it answers from memory, later when it asks a\n * database. Answering at once keeps the in-memory store as fast (in microtasks) as it always was.\n */\nexport type StoreAnswer = boolean | Promise<boolean>;\n\n/**\n * The fenced settlement skeleton every event store shares: find the claim a lease names (a lease\n * this store cannot have issued, or whose claim ended, is not held and changes nothing), apply the\n * change fenced by the lease, then forget the lease as handed out once the store answered.\n * A store supplies only how it finds a held claim, how it writes a change and a renewal, and its\n * bookkeeping of handed-out leases.\n */\nexport abstract class LeaseSettlement<THeld> {\n /** Renews the lease when it is still held; false otherwise. */\n extendLease(extension: LeaseExtension): StoreAnswer {\n const held = this.findHeld(extension.lease);\n return held === undefined ? false : this.writeExtension(held, extension);\n }\n\n /**\n * Applies `change` when the lease is still held. Once the store answered (either way) the lease\n * is forgotten as handed out; when the write throws it stays registered, since the caller may\n * retry with the same lease.\n */\n changeClaim(request: LeaseChange): StoreAnswer {\n const held = this.findHeld(request.lease);\n if (held === undefined) {\n return false;\n }\n const changed = this.writeChange(held, request);\n if (typeof changed === 'boolean') {\n this.forgetHandedOut(request.lease);\n return changed;\n }\n return this.#forgetOnceAnswered(request.lease, changed);\n }\n\n /** The claim `lease` names, when this store issued the lease; undefined otherwise. */\n protected abstract findHeld(lease: LeaseReference): THeld | undefined;\n\n protected abstract writeChange(held: THeld, request: LeaseChange): StoreAnswer;\n\n protected abstract writeExtension(held: THeld, extension: LeaseExtension): StoreAnswer;\n\n /** Drops `lease` from the store's handed-out bookkeeping; nothing for a store that keeps none. */\n protected abstract forgetHandedOut(lease: LeaseReference): void;\n\n async #forgetOnceAnswered(lease: LeaseReference, answer: Promise<boolean>): Promise<boolean> {\n const changed = await answer;\n this.forgetHandedOut(lease);\n return changed;\n }\n}\n","/** Separates the parts of a composite key: no identifier, handler id or ordering key contains it. */\nconst SEPARATOR = '\\u0000';\n\n/**\n * Two identifiers joined into one map or set key, such as an event id and a handler id, or a\n * handler id and an ordering key. The NUL separator keeps `('a:b', 'c')` and `('a', 'b:c')` apart.\n * Keys are plain strings, so they cost nothing more than the template literal they replace.\n */\nexport abstract class CompositeKey {\n static of(first: string, second: string): string {\n return `${first}${SEPARATOR}${second}`;\n }\n\n /** The start every key of `first` shares, to find them all by prefix. */\n static startOf(first: string): string {\n return `${first}${SEPARATOR}`;\n }\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 { MAX_TIMER_DELAY_MS, toMilliseconds, type Duration } from '../duration.js';\nimport { ConfigurationError } from '../errors.js';\n\n/**\n * A configured length of time, converted once from a {@link Duration} option. Each factory names\n * the option, so its refusal says which one was wrong.\n */\nexport abstract class Milliseconds {\n /** Zero or more milliseconds. */\n static of(value: Duration, name: string): number {\n return toMilliseconds(value, name);\n }\n\n /** More than zero milliseconds: a wait, a timeout or an age that must pass at all. */\n static positive(value: Duration, name: string): number {\n const milliseconds = toMilliseconds(value, name);\n if (milliseconds === 0) {\n throw new ConfigurationError(`${name} must be greater than zero`);\n }\n return milliseconds;\n }\n\n /** A duration that drives a timer: more than zero, and no longer than timers can represent. */\n static forTimer(value: Duration, name: string): number {\n const milliseconds = Milliseconds.positive(value, name);\n if (milliseconds > MAX_TIMER_DELAY_MS) {\n throw new ConfigurationError(\n `${name} must not exceed ${MAX_TIMER_DELAY_MS}ms (about 24.8 days), received ${milliseconds}ms`,\n );\n }\n return milliseconds;\n }\n}\n","import { CompositeKey } from './composite-key.js';\n\n/**\n * Identifies one claim of a delivery: the delivery id with the exact lease token it was handed out\n * with. Two claims of the same delivery (an expired one and the current one) have different\n * identities, so bookkeeping of the older can never replace or remove the newer one's.\n */\nexport abstract class DeliveryLeaseIdentity {\n static of(deliveryId: string, leaseToken: string): string {\n return CompositeKey.of(deliveryId, leaseToken);\n }\n\n /** The start of the identity of every claim of `deliveryId`. */\n static prefixOf(deliveryId: string): string {\n return CompositeKey.startOf(deliveryId);\n }\n}\n","import { ConfigurationError } from '../errors.js';\n\n/** A count option that must be a whole number of at least 1: a batch size, attempts, a limit. */\nexport abstract class PositiveInteger {\n /** Returns `value` when it is an integer of at least 1, else throws a {@link ConfigurationError} naming the option. */\n static of(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}\n"]}
|