@ddd-ts-kit/redis 0.0.0-stage → 0.1.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 +699 -2
- package/dist/cache/cache-codec.d.ts +6 -0
- package/dist/cache/cache-codec.d.ts.map +1 -0
- package/dist/cache/cache-codec.js +2 -0
- package/dist/cache/cache-codec.js.map +1 -0
- package/dist/cache/cache-operation.d.ts +3 -0
- package/dist/cache/cache-operation.d.ts.map +1 -0
- package/dist/cache/cache-operation.js +2 -0
- package/dist/cache/cache-operation.js.map +1 -0
- package/dist/cache/json-codec.d.ts +4 -0
- package/dist/cache/json-codec.d.ts.map +1 -0
- package/dist/cache/json-codec.js +9 -0
- package/dist/cache/json-codec.js.map +1 -0
- package/dist/cache/redis-cache.d.ts +65 -0
- package/dist/cache/redis-cache.d.ts.map +1 -0
- package/dist/cache/redis-cache.js +147 -0
- package/dist/cache/redis-cache.js.map +1 -0
- package/dist/connection/close-redis.d.ts +4 -0
- package/dist/connection/close-redis.d.ts.map +1 -0
- package/dist/connection/close-redis.js +20 -0
- package/dist/connection/close-redis.js.map +1 -0
- package/dist/connection/connect-redis.d.ts +10 -0
- package/dist/connection/connect-redis.d.ts.map +1 -0
- package/dist/connection/connect-redis.js +26 -0
- package/dist/connection/connect-redis.js.map +1 -0
- package/dist/connection/fail-fast.d.ts +9 -0
- package/dist/connection/fail-fast.d.ts.map +1 -0
- package/dist/connection/fail-fast.js +8 -0
- package/dist/connection/fail-fast.js.map +1 -0
- package/dist/connection/redis-connection-options.d.ts +7 -0
- package/dist/connection/redis-connection-options.d.ts.map +1 -0
- package/dist/connection/redis-connection-options.js +2 -0
- package/dist/connection/redis-connection-options.js.map +1 -0
- package/dist/connection/redis-health.d.ts +15 -0
- package/dist/connection/redis-health.d.ts.map +1 -0
- package/dist/connection/redis-health.js +31 -0
- package/dist/connection/redis-health.js.map +1 -0
- package/dist/connection/redis-source.d.ts +5 -0
- package/dist/connection/redis-source.d.ts.map +1 -0
- package/dist/connection/redis-source.js +2 -0
- package/dist/connection/redis-source.js.map +1 -0
- package/dist/event-bus.d.ts +22 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +10 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/keys/namespaced.d.ts +3 -0
- package/dist/keys/namespaced.d.ts.map +1 -0
- package/dist/keys/namespaced.js +5 -0
- package/dist/keys/namespaced.js.map +1 -0
- package/dist/keys/prefix-pattern.d.ts +3 -0
- package/dist/keys/prefix-pattern.d.ts.map +1 -0
- package/dist/keys/prefix-pattern.js +5 -0
- package/dist/keys/prefix-pattern.js.map +1 -0
- package/dist/lock/redis-lock.d.ts +31 -0
- package/dist/lock/redis-lock.d.ts.map +1 -0
- package/dist/lock/redis-lock.js +66 -0
- package/dist/lock/redis-lock.js.map +1 -0
- package/dist/rate-limiter/redis-rate-limiter.d.ts +35 -0
- package/dist/rate-limiter/redis-rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter/redis-rate-limiter.js +77 -0
- package/dist/rate-limiter/redis-rate-limiter.js.map +1 -0
- package/dist/relay/redis-relay-signal.d.ts +36 -0
- package/dist/relay/redis-relay-signal.d.ts.map +1 -0
- package/dist/relay/redis-relay-signal.js +72 -0
- package/dist/relay/redis-relay-signal.js.map +1 -0
- package/dist/relay/relay-wake-up-middleware.d.ts +11 -0
- package/dist/relay/relay-wake-up-middleware.d.ts.map +1 -0
- package/dist/relay/relay-wake-up-middleware.js +16 -0
- package/dist/relay/relay-wake-up-middleware.js.map +1 -0
- package/dist/replies/list-of.d.ts +3 -0
- package/dist/replies/list-of.d.ts.map +1 -0
- package/dist/replies/list-of.js +8 -0
- package/dist/replies/list-of.js.map +1 -0
- package/dist/secrecy/conceal.d.ts +8 -0
- package/dist/secrecy/conceal.d.ts.map +1 -0
- package/dist/secrecy/conceal.js +12 -0
- package/dist/secrecy/conceal.js.map +1 -0
- package/dist/streams/bury.d.ts +13 -0
- package/dist/streams/bury.d.ts.map +1 -0
- package/dist/streams/bury.js +14 -0
- package/dist/streams/bury.js.map +1 -0
- package/dist/streams/claim-due.d.ts +18 -0
- package/dist/streams/claim-due.d.ts.map +1 -0
- package/dist/streams/claim-due.js +41 -0
- package/dist/streams/claim-due.js.map +1 -0
- package/dist/streams/codec/decode.d.ts +5 -0
- package/dist/streams/codec/decode.d.ts.map +1 -0
- package/dist/streams/codec/decode.js +40 -0
- package/dist/streams/codec/decode.js.map +1 -0
- package/dist/streams/codec/encode.d.ts +7 -0
- package/dist/streams/codec/encode.d.ts.map +1 -0
- package/dist/streams/codec/encode.js +20 -0
- package/dist/streams/codec/encode.js.map +1 -0
- package/dist/streams/codec/entries-by-stream.d.ts +4 -0
- package/dist/streams/codec/entries-by-stream.d.ts.map +1 -0
- package/dist/streams/codec/entries-by-stream.js +14 -0
- package/dist/streams/codec/entries-by-stream.js.map +1 -0
- package/dist/streams/codec/entries-of.d.ts +4 -0
- package/dist/streams/codec/entries-of.d.ts.map +1 -0
- package/dist/streams/codec/entries-of.js +16 -0
- package/dist/streams/codec/entries-of.js.map +1 -0
- package/dist/streams/codec/entry-fields.d.ts +13 -0
- package/dist/streams/codec/entry-fields.d.ts.map +1 -0
- package/dist/streams/codec/entry-fields.js +13 -0
- package/dist/streams/codec/entry-fields.js.map +1 -0
- package/dist/streams/codec/entry.d.ts +6 -0
- package/dist/streams/codec/entry.d.ts.map +1 -0
- package/dist/streams/codec/entry.js +2 -0
- package/dist/streams/codec/entry.js.map +1 -0
- package/dist/streams/codec/fields-with.d.ts +4 -0
- package/dist/streams/codec/fields-with.d.ts.map +1 -0
- package/dist/streams/codec/fields-with.js +6 -0
- package/dist/streams/codec/fields-with.js.map +1 -0
- package/dist/streams/codec/pending-entry.d.ts +7 -0
- package/dist/streams/codec/pending-entry.d.ts.map +1 -0
- package/dist/streams/codec/pending-entry.js +2 -0
- package/dist/streams/codec/pending-entry.js.map +1 -0
- package/dist/streams/codec/pending-of.d.ts +4 -0
- package/dist/streams/codec/pending-of.d.ts.map +1 -0
- package/dist/streams/codec/pending-of.js +11 -0
- package/dist/streams/codec/pending-of.js.map +1 -0
- package/dist/streams/codec/replay-fields.d.ts +4 -0
- package/dist/streams/codec/replay-fields.d.ts.map +1 -0
- package/dist/streams/codec/replay-fields.js +15 -0
- package/dist/streams/codec/replay-fields.js.map +1 -0
- package/dist/streams/consumption.d.ts +22 -0
- package/dist/streams/consumption.d.ts.map +1 -0
- package/dist/streams/consumption.js +46 -0
- package/dist/streams/consumption.js.map +1 -0
- package/dist/streams/delivery.d.ts +8 -0
- package/dist/streams/delivery.d.ts.map +1 -0
- package/dist/streams/delivery.js +2 -0
- package/dist/streams/delivery.js.map +1 -0
- package/dist/streams/errors/broker-unavailable-error.d.ts +8 -0
- package/dist/streams/errors/broker-unavailable-error.d.ts.map +1 -0
- package/dist/streams/errors/broker-unavailable-error.js +12 -0
- package/dist/streams/errors/broker-unavailable-error.js.map +1 -0
- package/dist/streams/errors/consumer-name-required-error.d.ts +5 -0
- package/dist/streams/errors/consumer-name-required-error.d.ts.map +1 -0
- package/dist/streams/errors/consumer-name-required-error.js +8 -0
- package/dist/streams/errors/consumer-name-required-error.js.map +1 -0
- package/dist/streams/errors/event-bus-closed-error.d.ts +5 -0
- package/dist/streams/errors/event-bus-closed-error.d.ts.map +1 -0
- package/dist/streams/errors/event-bus-closed-error.js +8 -0
- package/dist/streams/errors/event-bus-closed-error.js.map +1 -0
- package/dist/streams/errors/failure-code.d.ts +7 -0
- package/dist/streams/errors/failure-code.d.ts.map +1 -0
- package/dist/streams/errors/failure-code.js +28 -0
- package/dist/streams/errors/failure-code.js.map +1 -0
- package/dist/streams/errors/publish-not-confirmed-error.d.ts +9 -0
- package/dist/streams/errors/publish-not-confirmed-error.d.ts.map +1 -0
- package/dist/streams/errors/publish-not-confirmed-error.js +14 -0
- package/dist/streams/errors/publish-not-confirmed-error.js.map +1 -0
- package/dist/streams/group-reader.d.ts +57 -0
- package/dist/streams/group-reader.d.ts.map +1 -0
- package/dist/streams/group-reader.js +187 -0
- package/dist/streams/group-reader.js.map +1 -0
- package/dist/streams/messaging-system.d.ts +3 -0
- package/dist/streams/messaging-system.d.ts.map +1 -0
- package/dist/streams/messaging-system.js +3 -0
- package/dist/streams/messaging-system.js.map +1 -0
- package/dist/streams/options/deduplication-options.d.ts +5 -0
- package/dist/streams/options/deduplication-options.d.ts.map +1 -0
- package/dist/streams/options/deduplication-options.js +2 -0
- package/dist/streams/options/deduplication-options.js.map +1 -0
- package/dist/streams/options/default-prefix.d.ts +2 -0
- package/dist/streams/options/default-prefix.d.ts.map +1 -0
- package/dist/streams/options/default-prefix.js +2 -0
- package/dist/streams/options/default-prefix.js.map +1 -0
- package/dist/streams/options/reconnect-delay.d.ts +4 -0
- package/dist/streams/options/reconnect-delay.d.ts.map +1 -0
- package/dist/streams/options/reconnect-delay.js +5 -0
- package/dist/streams/options/reconnect-delay.js.map +1 -0
- package/dist/streams/options/reconnect-options.d.ts +9 -0
- package/dist/streams/options/reconnect-options.d.ts.map +1 -0
- package/dist/streams/options/reconnect-options.js +2 -0
- package/dist/streams/options/reconnect-options.js.map +1 -0
- package/dist/streams/options/redis-streams-delivery.d.ts +13 -0
- package/dist/streams/options/redis-streams-delivery.d.ts.map +1 -0
- package/dist/streams/options/redis-streams-delivery.js +2 -0
- package/dist/streams/options/redis-streams-delivery.js.map +1 -0
- package/dist/streams/options/redis-streams-event-bus-options.d.ts +48 -0
- package/dist/streams/options/redis-streams-event-bus-options.d.ts.map +1 -0
- package/dist/streams/options/redis-streams-event-bus-options.js +2 -0
- package/dist/streams/options/redis-streams-event-bus-options.js.map +1 -0
- package/dist/streams/options/redis-streams-failure.d.ts +19 -0
- package/dist/streams/options/redis-streams-failure.d.ts.map +1 -0
- package/dist/streams/options/redis-streams-failure.js +2 -0
- package/dist/streams/options/redis-streams-failure.js.map +1 -0
- package/dist/streams/options/retention-options.d.ts +10 -0
- package/dist/streams/options/retention-options.d.ts.map +1 -0
- package/dist/streams/options/retention-options.js +2 -0
- package/dist/streams/options/retention-options.js.map +1 -0
- package/dist/streams/options/retry-options.d.ts +7 -0
- package/dist/streams/options/retry-options.d.ts.map +1 -0
- package/dist/streams/options/retry-options.js +2 -0
- package/dist/streams/options/retry-options.js.map +1 -0
- package/dist/streams/options/settings-of.d.ts +4 -0
- package/dist/streams/options/settings-of.d.ts.map +1 -0
- package/dist/streams/options/settings-of.js +47 -0
- package/dist/streams/options/settings-of.js.map +1 -0
- package/dist/streams/options/settings.d.ts +28 -0
- package/dist/streams/options/settings.d.ts.map +1 -0
- package/dist/streams/options/settings.js +2 -0
- package/dist/streams/options/settings.js.map +1 -0
- package/dist/streams/publications.d.ts +22 -0
- package/dist/streams/publications.d.ts.map +1 -0
- package/dist/streams/publications.js +75 -0
- package/dist/streams/publications.js.map +1 -0
- package/dist/streams/redis-streams-event-bus.d.ts +55 -0
- package/dist/streams/redis-streams-event-bus.d.ts.map +1 -0
- package/dist/streams/redis-streams-event-bus.js +120 -0
- package/dist/streams/redis-streams-event-bus.js.map +1 -0
- package/dist/streams/replay.d.ts +13 -0
- package/dist/streams/replay.d.ts.map +1 -0
- package/dist/streams/replay.js +26 -0
- package/dist/streams/replay.js.map +1 -0
- package/dist/streams/server-link.d.ts +45 -0
- package/dist/streams/server-link.d.ts.map +1 -0
- package/dist/streams/server-link.js +129 -0
- package/dist/streams/server-link.js.map +1 -0
- package/dist/streams/settlement.d.ts +46 -0
- package/dist/streams/settlement.d.ts.map +1 -0
- package/dist/streams/settlement.js +139 -0
- package/dist/streams/settlement.js.map +1 -0
- package/dist/streams/topology/consumer-binding.d.ts +5 -0
- package/dist/streams/topology/consumer-binding.d.ts.map +1 -0
- package/dist/streams/topology/consumer-binding.js +2 -0
- package/dist/streams/topology/consumer-binding.js.map +1 -0
- package/dist/streams/topology/create-group.d.ts +4 -0
- package/dist/streams/topology/create-group.d.ts.map +1 -0
- package/dist/streams/topology/create-group.js +13 -0
- package/dist/streams/topology/create-group.js.map +1 -0
- package/dist/streams/topology/dead-stream-of.d.ts +3 -0
- package/dist/streams/topology/dead-stream-of.d.ts.map +1 -0
- package/dist/streams/topology/dead-stream-of.js +5 -0
- package/dist/streams/topology/dead-stream-of.js.map +1 -0
- package/dist/streams/topology/declare-topology.d.ts +5 -0
- package/dist/streams/topology/declare-topology.d.ts.map +1 -0
- package/dist/streams/topology/declare-topology.js +8 -0
- package/dist/streams/topology/declare-topology.js.map +1 -0
- package/dist/streams/topology/group-description.d.ts +7 -0
- package/dist/streams/topology/group-description.d.ts.map +1 -0
- package/dist/streams/topology/group-description.js +2 -0
- package/dist/streams/topology/group-description.js.map +1 -0
- package/dist/streams/topology/group-of.d.ts +5 -0
- package/dist/streams/topology/group-of.d.ts.map +1 -0
- package/dist/streams/topology/group-of.js +4 -0
- package/dist/streams/topology/group-of.js.map +1 -0
- package/dist/streams/topology/redis-streams-topology.d.ts +8 -0
- package/dist/streams/topology/redis-streams-topology.d.ts.map +1 -0
- package/dist/streams/topology/redis-streams-topology.js +2 -0
- package/dist/streams/topology/redis-streams-topology.js.map +1 -0
- package/dist/streams/topology/stream-description.d.ts +12 -0
- package/dist/streams/topology/stream-description.d.ts.map +1 -0
- package/dist/streams/topology/stream-description.js +2 -0
- package/dist/streams/topology/stream-description.js.map +1 -0
- package/dist/streams/topology/stream-names-of.d.ts +5 -0
- package/dist/streams/topology/stream-names-of.d.ts.map +1 -0
- package/dist/streams/topology/stream-names-of.js +11 -0
- package/dist/streams/topology/stream-names-of.js.map +1 -0
- package/dist/streams/topology/stream-names.d.ts +5 -0
- package/dist/streams/topology/stream-names.d.ts.map +1 -0
- package/dist/streams/topology/stream-names.js +2 -0
- package/dist/streams/topology/stream-names.js.map +1 -0
- package/dist/streams/topology/topology-of.d.ts +5 -0
- package/dist/streams/topology/topology-of.d.ts.map +1 -0
- package/dist/streams/topology/topology-of.js +21 -0
- package/dist/streams/topology/topology-of.js.map +1 -0
- package/dist/streams/topology/topology-settings.d.ts +7 -0
- package/dist/streams/topology/topology-settings.d.ts.map +1 -0
- package/dist/streams/topology/topology-settings.js +2 -0
- package/dist/streams/topology/topology-settings.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -0
- package/package.json +51 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { encode } from './codec/encode.js';
|
|
2
|
+
import { failureCode } from './errors/failure-code.js';
|
|
3
|
+
import { PublishNotConfirmedError } from './errors/publish-not-confirmed-error.js';
|
|
4
|
+
/**
|
|
5
|
+
* Appends unless the event id was appended within the window, and answers the entry id either
|
|
6
|
+
* way. The append comes first: were the mark written first, a failed append would make the
|
|
7
|
+
* retry believe the event is there. KEYS: mark, stream. ARGV: window, then the arguments of
|
|
8
|
+
* `XADD` after the key.
|
|
9
|
+
*/
|
|
10
|
+
const GUARDED_APPEND = `
|
|
11
|
+
local known = redis.call('GET', KEYS[1])
|
|
12
|
+
if known then
|
|
13
|
+
return known
|
|
14
|
+
end
|
|
15
|
+
local entry = redis.call('XADD', KEYS[2], unpack(ARGV, 2))
|
|
16
|
+
redis.call('SET', KEYS[1], entry, 'PX', ARGV[1])
|
|
17
|
+
return entry`;
|
|
18
|
+
/**
|
|
19
|
+
* Publishing: one `XADD` per event, the whole batch in one `MULTI`. A publication succeeds
|
|
20
|
+
* only once the server answered an entry id for every event, which is what lets an outbox
|
|
21
|
+
* relay mark them published.
|
|
22
|
+
*/
|
|
23
|
+
export class Publications {
|
|
24
|
+
settings;
|
|
25
|
+
constructor(settings) {
|
|
26
|
+
this.settings = settings;
|
|
27
|
+
}
|
|
28
|
+
/** Resolves when every event has an entry; rejects with the first failure otherwise. */
|
|
29
|
+
async publish(client, events) {
|
|
30
|
+
const transaction = client.multi();
|
|
31
|
+
for (const event of events) {
|
|
32
|
+
this.append(transaction, event);
|
|
33
|
+
}
|
|
34
|
+
const answers = await executed(transaction, events);
|
|
35
|
+
events.forEach((event, index) => {
|
|
36
|
+
const [error, entryId] = answers[index] ?? [new Error('no answer'), undefined];
|
|
37
|
+
if (error !== null) {
|
|
38
|
+
throw new PublishNotConfirmedError(event.id, failureCode(error));
|
|
39
|
+
}
|
|
40
|
+
if (typeof entryId !== 'string') {
|
|
41
|
+
throw new PublishNotConfirmedError(event.id, 'NO_ENTRY_ID');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
append(transaction, event) {
|
|
46
|
+
const stream = this.settings.streamOf(event.type);
|
|
47
|
+
const entry = [...trimming(this.settings.retention), '*', ...encode(event)];
|
|
48
|
+
const { deduplication } = this.settings;
|
|
49
|
+
if (deduplication === undefined) {
|
|
50
|
+
transaction.call('XADD', stream, ...entry);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
transaction.eval(GUARDED_APPEND, 2, `${stream}:${event.id}:published`, stream, deduplication.windowMs, ...entry);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** A transaction the server did not answer, or refused as a whole, confirms none of its events. */
|
|
58
|
+
async function executed(transaction, events) {
|
|
59
|
+
try {
|
|
60
|
+
return (await transaction.exec()) ?? [];
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new PublishNotConfirmedError(events[0]?.id ?? '', failureCode(error));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function trimming(retention) {
|
|
67
|
+
if (retention === undefined) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
if ('maxLength' in retention) {
|
|
71
|
+
return ['MAXLEN', '~', String(retention.maxLength)];
|
|
72
|
+
}
|
|
73
|
+
return ['MINID', '~', `${String(Date.now() - retention.maxAgeMs)}-0`];
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=publications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publications.js","sourceRoot":"","sources":["../../src/streams/publications.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import type { EventBus, IntegrationEvent, IntegrationEventCallback, IntegrationEventSubscriber, Subscription } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import type { RedisStreamsEventBusOptions } from './options/redis-streams-event-bus-options.js';
|
|
4
|
+
import type { ConsumerBinding } from './topology/consumer-binding.js';
|
|
5
|
+
import type { RedisStreamsTopology } from './topology/redis-streams-topology.js';
|
|
6
|
+
/**
|
|
7
|
+
* The provider on Redis Streams, for Redis, Dragonfly and Valkey.
|
|
8
|
+
*
|
|
9
|
+
* Publishing: one stream per event type, one `XADD` per event, and `publish` resolves only
|
|
10
|
+
* when the server answered an entry id for every event, so it is a safe target for the outbox
|
|
11
|
+
* relay. Consuming: one consumer group per consumer name, a blocking read on a dedicated
|
|
12
|
+
* connection, `XACK` after the handlers resolved, a bounded number of delayed redeliveries
|
|
13
|
+
* from the pending entry list, then a dead stream. Delivery is at least once: consumers
|
|
14
|
+
* deduplicate.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RedisStreamsEventBus implements EventBus {
|
|
17
|
+
/** Read by what observes the bus from outside: the name of the transport. */
|
|
18
|
+
readonly messagingSystem = "redis";
|
|
19
|
+
private readonly settings;
|
|
20
|
+
private readonly link;
|
|
21
|
+
private readonly publications;
|
|
22
|
+
private readonly consumption;
|
|
23
|
+
private closed;
|
|
24
|
+
constructor(options: RedisStreamsEventBusOptions);
|
|
25
|
+
/** Entries delivered to this bus whose handlers have not finished yet. */
|
|
26
|
+
get deliveriesInFlight(): number;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves when the server acknowledged every event, rejects otherwise: nothing is buffered
|
|
29
|
+
* for later, so the caller (the outbox relay) knows what to retry.
|
|
30
|
+
*/
|
|
31
|
+
publish(events: readonly IntegrationEvent[]): Promise<void>;
|
|
32
|
+
/** Subscribes the consumer named by the `consumer` option. */
|
|
33
|
+
subscribe(type: string, handler: IntegrationEventCallback): Subscription;
|
|
34
|
+
/** A subscriber for one consuming context, with a reading connection of its own. */
|
|
35
|
+
forConsumer(consumer: string): IntegrationEventSubscriber;
|
|
36
|
+
/**
|
|
37
|
+
* Connects if needed and creates the streams and the groups of the subscriptions made so
|
|
38
|
+
* far. Idempotent: a group that exists keeps its position.
|
|
39
|
+
*/
|
|
40
|
+
assertTopology(): Promise<void>;
|
|
41
|
+
describeTopology(): RedisStreamsTopology;
|
|
42
|
+
/**
|
|
43
|
+
* Moves up to `limit` entries from the dead stream of a consumer back to the stream of
|
|
44
|
+
* their type, for that consumer only, with a fresh attempt budget. Returns how many moved.
|
|
45
|
+
*/
|
|
46
|
+
replayDead(binding: ConsumerBinding, limit?: number): Promise<number>;
|
|
47
|
+
/** Stops reading, waits for the handlers in progress to settle, disconnects what it opened. */
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
/** What a serialisation shows of the bus: its name, never its connection. */
|
|
50
|
+
toJSON(): string;
|
|
51
|
+
/** What `console.log` and `util.inspect` show of the bus: the same. */
|
|
52
|
+
[inspect.custom](): string;
|
|
53
|
+
private bind;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=redis-streams-event-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-streams-event-bus.d.ts","sourceRoot":"","sources":["../../src/streams/redis-streams-event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACb,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAOhG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAMjF;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,YAAW,QAAQ;IACnD,6EAA6E;IAC7E,QAAQ,CAAC,eAAe,WAAoB;IAE5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAElC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAE5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAE1C,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,OAAO,EAAE,2BAA2B,EAY/C;IAED,0EAA0E;IAC1E,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAED;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAchE;IAED,8DAA8D;IAC9D,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAwB,GAAG,YAAY,CAMvE;IAED,oFAAoF;IACpF,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,0BAA0B,CAMxD;IAED;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAEpC;IAED,gBAAgB,IAAI,oBAAoB,CAEvC;IAED;;;OAGG;IACG,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvE;IAED,+FAA+F;IACzF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3B;IAED,6EAA6E;IAC7E,MAAM,IAAI,MAAM,CAEf;IAED,uEAAuE;IACvE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,IAAI;CAOb"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import { replay } from './replay.js';
|
|
3
|
+
import { Consumption } from './consumption.js';
|
|
4
|
+
import { ConsumerNameRequiredError } from './errors/consumer-name-required-error.js';
|
|
5
|
+
import { EventBusClosedError } from './errors/event-bus-closed-error.js';
|
|
6
|
+
import { MESSAGING_SYSTEM } from './messaging-system.js';
|
|
7
|
+
import { settingsOf } from './options/settings-of.js';
|
|
8
|
+
import { conceal } from '../secrecy/conceal.js';
|
|
9
|
+
import { Publications } from './publications.js';
|
|
10
|
+
import { ServerLink } from './server-link.js';
|
|
11
|
+
import { declareTopology } from './topology/declare-topology.js';
|
|
12
|
+
import { streamNamesOf } from './topology/stream-names-of.js';
|
|
13
|
+
import { topologyOf } from './topology/topology-of.js';
|
|
14
|
+
const DESCRIPTION = 'RedisStreamsEventBus [connection concealed]';
|
|
15
|
+
/**
|
|
16
|
+
* The provider on Redis Streams, for Redis, Dragonfly and Valkey.
|
|
17
|
+
*
|
|
18
|
+
* Publishing: one stream per event type, one `XADD` per event, and `publish` resolves only
|
|
19
|
+
* when the server answered an entry id for every event, so it is a safe target for the outbox
|
|
20
|
+
* relay. Consuming: one consumer group per consumer name, a blocking read on a dedicated
|
|
21
|
+
* connection, `XACK` after the handlers resolved, a bounded number of delayed redeliveries
|
|
22
|
+
* from the pending entry list, then a dead stream. Delivery is at least once: consumers
|
|
23
|
+
* deduplicate.
|
|
24
|
+
*/
|
|
25
|
+
export class RedisStreamsEventBus {
|
|
26
|
+
/** Read by what observes the bus from outside: the name of the transport. */
|
|
27
|
+
messagingSystem = MESSAGING_SYSTEM;
|
|
28
|
+
settings;
|
|
29
|
+
link;
|
|
30
|
+
publications;
|
|
31
|
+
consumption;
|
|
32
|
+
closed = false;
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.settings = settingsOf(options);
|
|
35
|
+
const { reconnect, blockMs } = this.settings;
|
|
36
|
+
this.link = new ServerLink({ connection: options.connection, reconnect, blockMs }, (code) => {
|
|
37
|
+
this.settings.onFailure({ kind: 'connection', code });
|
|
38
|
+
});
|
|
39
|
+
this.publications = new Publications(this.settings);
|
|
40
|
+
this.consumption = new Consumption(this.link, this.settings);
|
|
41
|
+
conceal(this);
|
|
42
|
+
}
|
|
43
|
+
/** Entries delivered to this bus whose handlers have not finished yet. */
|
|
44
|
+
get deliveriesInFlight() {
|
|
45
|
+
return this.consumption.inFlight;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolves when the server acknowledged every event, rejects otherwise: nothing is buffered
|
|
49
|
+
* for later, so the caller (the outbox relay) knows what to retry.
|
|
50
|
+
*/
|
|
51
|
+
async publish(events) {
|
|
52
|
+
if (events.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (this.closed) {
|
|
56
|
+
throw new EventBusClosedError();
|
|
57
|
+
}
|
|
58
|
+
const client = await this.link.ready();
|
|
59
|
+
// The groups of this bus exist before it publishes: it never misses its own events.
|
|
60
|
+
await this.consumption.declare(client);
|
|
61
|
+
await this.publications.publish(client, events);
|
|
62
|
+
}
|
|
63
|
+
/** Subscribes the consumer named by the `consumer` option. */
|
|
64
|
+
subscribe(type, handler) {
|
|
65
|
+
if (this.settings.consumer === undefined) {
|
|
66
|
+
throw new ConsumerNameRequiredError();
|
|
67
|
+
}
|
|
68
|
+
return this.bind({ consumer: this.settings.consumer, type }, handler);
|
|
69
|
+
}
|
|
70
|
+
/** A subscriber for one consuming context, with a reading connection of its own. */
|
|
71
|
+
forConsumer(consumer) {
|
|
72
|
+
if (consumer.length === 0) {
|
|
73
|
+
throw new ConsumerNameRequiredError();
|
|
74
|
+
}
|
|
75
|
+
return { subscribe: (type, handler) => this.bind({ consumer, type }, handler) };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Connects if needed and creates the streams and the groups of the subscriptions made so
|
|
79
|
+
* far. Idempotent: a group that exists keeps its position.
|
|
80
|
+
*/
|
|
81
|
+
async assertTopology() {
|
|
82
|
+
await declareTopology(await this.link.ready(), this.describeTopology());
|
|
83
|
+
}
|
|
84
|
+
describeTopology() {
|
|
85
|
+
return topologyOf(this.settings, this.consumption.bindings());
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Moves up to `limit` entries from the dead stream of a consumer back to the stream of
|
|
89
|
+
* their type, for that consumer only, with a fresh attempt budget. Returns how many moved.
|
|
90
|
+
*/
|
|
91
|
+
async replayDead(binding, limit = 100) {
|
|
92
|
+
const { events, dead } = streamNamesOf(binding, this.settings.streamOf);
|
|
93
|
+
const client = await this.link.ready();
|
|
94
|
+
return replay(client, { stream: events, deadStream: dead, consumer: binding.consumer, limit });
|
|
95
|
+
}
|
|
96
|
+
/** Stops reading, waits for the handlers in progress to settle, disconnects what it opened. */
|
|
97
|
+
async close() {
|
|
98
|
+
if (this.closed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.closed = true;
|
|
102
|
+
await this.consumption.drain();
|
|
103
|
+
await this.link.close();
|
|
104
|
+
}
|
|
105
|
+
/** What a serialisation shows of the bus: its name, never its connection. */
|
|
106
|
+
toJSON() {
|
|
107
|
+
return DESCRIPTION;
|
|
108
|
+
}
|
|
109
|
+
/** What `console.log` and `util.inspect` show of the bus: the same. */
|
|
110
|
+
[inspect.custom]() {
|
|
111
|
+
return DESCRIPTION;
|
|
112
|
+
}
|
|
113
|
+
bind(binding, handler) {
|
|
114
|
+
if (this.closed) {
|
|
115
|
+
throw new EventBusClosedError();
|
|
116
|
+
}
|
|
117
|
+
return this.consumption.add(binding, handler);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=redis-streams-event-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-streams-event-bus.js","sourceRoot":"","sources":["../../src/streams/redis-streams-event-bus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
export interface Replay {
|
|
3
|
+
readonly stream: string;
|
|
4
|
+
readonly deadStream: string;
|
|
5
|
+
readonly consumer: string;
|
|
6
|
+
readonly limit: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Moves up to `limit` dead entries back, oldest first, and answers how many. The new entry
|
|
10
|
+
* names the consumer it is for: the other groups of the stream acknowledge it unseen.
|
|
11
|
+
*/
|
|
12
|
+
export declare function replay(client: Redis, { stream, deadStream, consumer, limit }: Replay): Promise<number>;
|
|
13
|
+
//# sourceMappingURL=replay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/streams/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIrC,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAcD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5G"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { entriesOf } from './codec/entries-of.js';
|
|
2
|
+
import { replayFields } from './codec/replay-fields.js';
|
|
3
|
+
/**
|
|
4
|
+
* Appends a dead entry to its stream again and deletes it from the dead stream, unless
|
|
5
|
+
* another replay took it meanwhile. KEYS: dead stream, stream. ARGV: dead entry id, then the
|
|
6
|
+
* fields of the new entry.
|
|
7
|
+
*/
|
|
8
|
+
const REPLAY = `
|
|
9
|
+
if #redis.call('XRANGE', KEYS[1], ARGV[1], ARGV[1]) == 0 then
|
|
10
|
+
return 0
|
|
11
|
+
end
|
|
12
|
+
redis.call('XADD', KEYS[2], '*', unpack(ARGV, 2))
|
|
13
|
+
return redis.call('XDEL', KEYS[1], ARGV[1])`;
|
|
14
|
+
/**
|
|
15
|
+
* Moves up to `limit` dead entries back, oldest first, and answers how many. The new entry
|
|
16
|
+
* names the consumer it is for: the other groups of the stream acknowledge it unseen.
|
|
17
|
+
*/
|
|
18
|
+
export async function replay(client, { stream, deadStream, consumer, limit }) {
|
|
19
|
+
const dead = entriesOf(await client.xrange(deadStream, '-', '+', 'COUNT', limit));
|
|
20
|
+
let replayed = 0;
|
|
21
|
+
for (const entry of dead) {
|
|
22
|
+
replayed += Number(await client.eval(REPLAY, 2, deadStream, stream, entry.id, ...replayFields(entry, consumer)));
|
|
23
|
+
}
|
|
24
|
+
return replayed;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/streams/replay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AASxD;;;;GAIG;AACH,MAAM,MAAM,GAAG;;;;;4CAK6B,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAa,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAU;IACzF,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAClF,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,QAAQ,IAAI,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import { Redis } from 'ioredis';
|
|
3
|
+
import type { RedisSource } from '../connection/redis-source.js';
|
|
4
|
+
import type { ReconnectOptions } from './options/reconnect-options.js';
|
|
5
|
+
export interface LinkSettings {
|
|
6
|
+
readonly connection: RedisSource;
|
|
7
|
+
readonly reconnect: Required<ReconnectOptions>;
|
|
8
|
+
readonly blockMs: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The connection the bus publishes and acknowledges on, and the source of the connections
|
|
12
|
+
* its readers block on. ioredis reconnects by itself; the link reports what happens to the
|
|
13
|
+
* connection as codes and answers at once, instead of queueing, while the server is away.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ServerLink {
|
|
16
|
+
private readonly connection;
|
|
17
|
+
private readonly owned;
|
|
18
|
+
private readonly blockMs;
|
|
19
|
+
private readonly failed;
|
|
20
|
+
private reason;
|
|
21
|
+
private wasReady;
|
|
22
|
+
private closed;
|
|
23
|
+
constructor(settings: LinkSettings, failed: (code: string) => void);
|
|
24
|
+
/** The connection for commands that answer at once. A command sent while the server is away fails by the rules of the client. */
|
|
25
|
+
get client(): Redis;
|
|
26
|
+
/**
|
|
27
|
+
* The live connection. Waits for the first connection in progress and fails at once between
|
|
28
|
+
* two reconnection attempts, so that a publisher never hangs on a server that is down.
|
|
29
|
+
*/
|
|
30
|
+
ready(): Promise<Redis>;
|
|
31
|
+
/**
|
|
32
|
+
* A connection of its own for a blocking reader. Its command timeout leaves the server the
|
|
33
|
+
* time to answer a read that found nothing; beyond it the connection is deemed dead.
|
|
34
|
+
*/
|
|
35
|
+
reader(): Redis;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
toJSON(): string;
|
|
38
|
+
[inspect.custom](): string;
|
|
39
|
+
private refuseOnceClosed;
|
|
40
|
+
private readonly onReady;
|
|
41
|
+
private readonly onError;
|
|
42
|
+
private readonly onClose;
|
|
43
|
+
private lost;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=server-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-link.d.ts","sourceRoot":"","sources":["../../src/streams/server-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAMjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAID;;;;GAIG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IAEnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAEhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAEhD,OAAO,CAAC,MAAM,CAAmB;IAEjC,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAUjE;IAED,iIAAiI;IACjI,IAAI,MAAM,IAAI,KAAK,CAElB;IAED;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAyB5B;IAED;;;OAGG;IACH,MAAM,IAAI,KAAK,CAQd;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3B;IAED,MAAM,IAAI,MAAM,CAEf;IAED,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAEtB;IAEF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAEtB;IAEF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAItB;IAEF,OAAO,CAAC,IAAI;CAQb"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import { Redis } from 'ioredis';
|
|
3
|
+
import { closeRedis } from '../connection/close-redis.js';
|
|
4
|
+
import { FAIL_FAST } from '../connection/fail-fast.js';
|
|
5
|
+
import { conceal } from '../secrecy/conceal.js';
|
|
6
|
+
import { BrokerUnavailableError } from './errors/broker-unavailable-error.js';
|
|
7
|
+
import { EventBusClosedError } from './errors/event-bus-closed-error.js';
|
|
8
|
+
import { failureCode } from './errors/failure-code.js';
|
|
9
|
+
import { reconnectDelay } from './options/reconnect-delay.js';
|
|
10
|
+
const DESCRIPTION = 'ServerLink [connection concealed]';
|
|
11
|
+
/**
|
|
12
|
+
* The connection the bus publishes and acknowledges on, and the source of the connections
|
|
13
|
+
* its readers block on. ioredis reconnects by itself; the link reports what happens to the
|
|
14
|
+
* connection as codes and answers at once, instead of queueing, while the server is away.
|
|
15
|
+
*/
|
|
16
|
+
export class ServerLink {
|
|
17
|
+
connection;
|
|
18
|
+
owned;
|
|
19
|
+
blockMs;
|
|
20
|
+
failed;
|
|
21
|
+
reason = 'NOT_CONNECTED';
|
|
22
|
+
wasReady;
|
|
23
|
+
closed = false;
|
|
24
|
+
constructor(settings, failed) {
|
|
25
|
+
const { connection } = settings;
|
|
26
|
+
this.owned = !(connection instanceof Redis);
|
|
27
|
+
this.connection = connection instanceof Redis ? connection : opened(connection, settings.reconnect);
|
|
28
|
+
this.blockMs = settings.blockMs;
|
|
29
|
+
this.failed = failed;
|
|
30
|
+
this.wasReady = this.connection.status === 'ready';
|
|
31
|
+
this.connection.on('ready', this.onReady).on('error', this.onError).on('close', this.onClose);
|
|
32
|
+
conceal(this);
|
|
33
|
+
}
|
|
34
|
+
/** The connection for commands that answer at once. A command sent while the server is away fails by the rules of the client. */
|
|
35
|
+
get client() {
|
|
36
|
+
return this.connection;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The live connection. Waits for the first connection in progress and fails at once between
|
|
40
|
+
* two reconnection attempts, so that a publisher never hangs on a server that is down.
|
|
41
|
+
*/
|
|
42
|
+
async ready() {
|
|
43
|
+
this.refuseOnceClosed();
|
|
44
|
+
const client = this.connection;
|
|
45
|
+
if (client.status === 'ready') {
|
|
46
|
+
return client;
|
|
47
|
+
}
|
|
48
|
+
if (client.status === 'wait') {
|
|
49
|
+
void client.connect().catch(ignore);
|
|
50
|
+
}
|
|
51
|
+
if (!CONNECTING.has(client.status)) {
|
|
52
|
+
throw new BrokerUnavailableError(this.reason);
|
|
53
|
+
}
|
|
54
|
+
await connected(client);
|
|
55
|
+
this.refuseOnceClosed();
|
|
56
|
+
if (!this.wasReady) {
|
|
57
|
+
throw new BrokerUnavailableError(this.reason);
|
|
58
|
+
}
|
|
59
|
+
return client;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A connection of its own for a blocking reader. Its command timeout leaves the server the
|
|
63
|
+
* time to answer a read that found nothing; beyond it the connection is deemed dead.
|
|
64
|
+
*/
|
|
65
|
+
reader() {
|
|
66
|
+
const patience = this.connection.options.commandTimeout ?? FAIL_FAST.commandTimeout ?? 0;
|
|
67
|
+
const reader = this.connection.duplicate({ commandTimeout: this.blockMs + patience });
|
|
68
|
+
// Without a handler ioredis prints connection errors; the reader reports its own failures.
|
|
69
|
+
reader.on('error', ignore);
|
|
70
|
+
return reader;
|
|
71
|
+
}
|
|
72
|
+
async close() {
|
|
73
|
+
this.closed = true;
|
|
74
|
+
this.connection.off('ready', this.onReady).off('error', this.onError).off('close', this.onClose);
|
|
75
|
+
if (this.owned) {
|
|
76
|
+
this.connection.on('error', ignore);
|
|
77
|
+
await closeRedis(this.connection);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
toJSON() {
|
|
81
|
+
return DESCRIPTION;
|
|
82
|
+
}
|
|
83
|
+
[inspect.custom]() {
|
|
84
|
+
return DESCRIPTION;
|
|
85
|
+
}
|
|
86
|
+
refuseOnceClosed() {
|
|
87
|
+
if (this.closed) {
|
|
88
|
+
throw new EventBusClosedError();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
onReady = () => {
|
|
92
|
+
this.wasReady = true;
|
|
93
|
+
};
|
|
94
|
+
onError = (error) => {
|
|
95
|
+
this.lost(failureCode(error));
|
|
96
|
+
};
|
|
97
|
+
onClose = () => {
|
|
98
|
+
if (this.wasReady) {
|
|
99
|
+
this.lost('CONNECTION_LOST');
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
lost(code) {
|
|
103
|
+
this.wasReady = false;
|
|
104
|
+
this.reason = code;
|
|
105
|
+
if (!this.closed) {
|
|
106
|
+
this.failed(code);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const CONNECTING = new Set(['wait', 'connecting', 'connect']);
|
|
111
|
+
function opened(source, reconnect) {
|
|
112
|
+
const retryStrategy = (attempt) => reconnectDelay(reconnect, attempt);
|
|
113
|
+
const defaults = { ...FAIL_FAST, retryStrategy };
|
|
114
|
+
return typeof source === 'string' ? new Redis(source, defaults) : new Redis({ ...defaults, ...source });
|
|
115
|
+
}
|
|
116
|
+
/** Resolves when the connection in progress succeeded or failed. */
|
|
117
|
+
function connected(client) {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
const settle = () => {
|
|
120
|
+
client.off('ready', settle).off('close', settle).off('end', settle);
|
|
121
|
+
resolve();
|
|
122
|
+
};
|
|
123
|
+
client.once('ready', settle).once('close', settle).once('end', settle);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function ignore() {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=server-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-link.js","sourceRoot":"","sources":["../../src/streams/server-link.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import type { IntegrationEventCallback } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import type { Delivery } from './delivery.js';
|
|
4
|
+
import type { RedisStreamsDelivery } from './options/redis-streams-delivery.js';
|
|
5
|
+
import type { RedisStreamsFailure } from './options/redis-streams-failure.js';
|
|
6
|
+
import type { RetryOptions } from './options/retry-options.js';
|
|
7
|
+
export interface SettlementSettings {
|
|
8
|
+
/** The consumer name, which is the name of its group. */
|
|
9
|
+
readonly consumer: string;
|
|
10
|
+
readonly instance: string;
|
|
11
|
+
readonly retry: Required<RetryOptions>;
|
|
12
|
+
readonly onFailure: (failure: RedisStreamsFailure) => void;
|
|
13
|
+
readonly onDelivery: (delivery: RedisStreamsDelivery) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare const POISON_ENTRY = "POISON_ENTRY";
|
|
16
|
+
/** The code of an entry that used its deliveries up without a handler answering: its consumers crashed on it. */
|
|
17
|
+
export declare const ATTEMPTS_EXHAUSTED = "ATTEMPTS_EXHAUSTED";
|
|
18
|
+
/**
|
|
19
|
+
* What becomes of a delivered entry: acknowledged once every handler resolved; left pending
|
|
20
|
+
* after a failure, to be claimed again after the delay; copied to the dead stream when its
|
|
21
|
+
* attempts are spent or when it does not hold an envelope.
|
|
22
|
+
*/
|
|
23
|
+
export declare class Settlement {
|
|
24
|
+
private readonly client;
|
|
25
|
+
private readonly settings;
|
|
26
|
+
constructor(client: Redis, settings: SettlementSettings);
|
|
27
|
+
/** Never rejects: an entry that could not be settled stays pending and is delivered again. */
|
|
28
|
+
settle(delivery: Delivery, handlers: ReadonlySet<IntegrationEventCallback>): Promise<void>;
|
|
29
|
+
private decide;
|
|
30
|
+
/** A replayed dead entry is addressed to the consumer it died for. */
|
|
31
|
+
private isForAnotherConsumer;
|
|
32
|
+
private acknowledge;
|
|
33
|
+
private buryPoison;
|
|
34
|
+
private handle;
|
|
35
|
+
private deliveryOf;
|
|
36
|
+
private fail;
|
|
37
|
+
private settled;
|
|
38
|
+
private bury;
|
|
39
|
+
/**
|
|
40
|
+
* The entry stays pending; claiming it for ourselves without reading it (`JUSTID`) restarts
|
|
41
|
+
* its idle time and leaves its delivery counter alone, so the retry delay runs from the
|
|
42
|
+
* failure, not from the delivery.
|
|
43
|
+
*/
|
|
44
|
+
private postpone;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=settlement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlement.d.ts","sourceRoot":"","sources":["../../src/streams/settlement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAmC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAYpG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG/D,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEvC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAE3D,QAAQ,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC/D;AAED,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C,iHAAiH;AACjH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD;;;;GAIG;AACH,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAF3B,YACmB,MAAM,EAAE,KAAK,EACb,QAAQ,EAAE,kBAAkB,EAC3C;IAEJ,8FAA8F;IACxF,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/F;YAEa,MAAM;IAkBpB,sEAAsE;IACtE,OAAO,CAAC,oBAAoB;YAMd,WAAW;YAIX,UAAU;YAQV,MAAM;IAepB,OAAO,CAAC,UAAU;YAMJ,IAAI;IAyBlB,OAAO,CAAC,OAAO;YAID,IAAI;IAoBlB;;;;OAIG;YACW,QAAQ;CAGvB"}
|