@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 @@
|
|
|
1
|
+
{"version":3,"file":"prefix-pattern.d.ts","sourceRoot":"","sources":["../../src/keys/prefix-pattern.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefix-pattern.js","sourceRoot":"","sources":["../../src/keys/prefix-pattern.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
import { type AcquireOptions, type Lock, type LockHandle, type WaitingOptions } from '@ddd-ts-kit/kernel';
|
|
4
|
+
export interface RedisLockOptions extends WaitingOptions {
|
|
5
|
+
/** Prefix of the keys, typically the bounded context. Default `lock`. */
|
|
6
|
+
readonly namespace?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The single-instance lock of the Redis documentation: `SET key owner NX PX ttl`, released by
|
|
10
|
+
* compare-and-delete so that a holder whose lock expired cannot free the lock of the next one.
|
|
11
|
+
*
|
|
12
|
+
* What it guarantees: at most one holder at a time as long as the server keeps its data and
|
|
13
|
+
* the holder finishes within `ttlMs`. What it does not: it is not a consensus lock. A
|
|
14
|
+
* failover to a replica that had not received the key, a restart without persistence, an
|
|
15
|
+
* eviction, or a holder paused longer than the ttl each let two holders in. The fencing
|
|
16
|
+
* token narrows that, for a resource able to compare tokens; its counter lives in the same
|
|
17
|
+
* server and starts again if that server loses it.
|
|
18
|
+
*/
|
|
19
|
+
export declare class RedisLock implements Lock {
|
|
20
|
+
private readonly client;
|
|
21
|
+
private readonly namespace;
|
|
22
|
+
private readonly waiting;
|
|
23
|
+
constructor(client: Redis, options?: RedisLockOptions);
|
|
24
|
+
acquire(key: string, options: AcquireOptions): Promise<LockHandle | undefined>;
|
|
25
|
+
/** What a serialisation shows: a name, never the client, whose options carry the password. */
|
|
26
|
+
toJSON(): string;
|
|
27
|
+
/** What `console.log` and `util.inspect` show: the same. */
|
|
28
|
+
[inspect.custom](): string;
|
|
29
|
+
private attempt;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=redis-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-lock.d.ts","sourceRoot":"","sources":["../../src/lock/redis-lock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAa,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIrH,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAkBD;;;;;;;;;;GAUG;AACH,qBAAa,SAAU,YAAW,IAAI;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAE/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IAEzC,YAAY,MAAM,EAAE,KAAK,EAAE,OAAO,GAAE,gBAAqB,EAKxD;IAED,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAE7E;IAED,8FAA8F;IAC9F,MAAM,IAAI,MAAM,CAEf;IAED,4DAA4D;IAC5D,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;YAEa,OAAO;CAiBtB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { inspect } from 'node:util';
|
|
3
|
+
import { acquiring } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { namespaced } from '../keys/namespaced.js';
|
|
5
|
+
import { conceal } from '../secrecy/conceal.js';
|
|
6
|
+
/** Takes the lock if it is free and numbers the acquisition, in one step. KEYS: lock, counter. ARGV: owner, ttl. */
|
|
7
|
+
const ACQUIRE = `
|
|
8
|
+
if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then
|
|
9
|
+
return redis.call('INCR', KEYS[2])
|
|
10
|
+
end
|
|
11
|
+
return 0`;
|
|
12
|
+
/** Deletes the lock only while it still holds the value of this owner. KEYS: lock. ARGV: owner. */
|
|
13
|
+
const RELEASE = `
|
|
14
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
15
|
+
return redis.call('DEL', KEYS[1])
|
|
16
|
+
end
|
|
17
|
+
return 0`;
|
|
18
|
+
const DESCRIPTION = 'RedisLock [client concealed]';
|
|
19
|
+
/**
|
|
20
|
+
* The single-instance lock of the Redis documentation: `SET key owner NX PX ttl`, released by
|
|
21
|
+
* compare-and-delete so that a holder whose lock expired cannot free the lock of the next one.
|
|
22
|
+
*
|
|
23
|
+
* What it guarantees: at most one holder at a time as long as the server keeps its data and
|
|
24
|
+
* the holder finishes within `ttlMs`. What it does not: it is not a consensus lock. A
|
|
25
|
+
* failover to a replica that had not received the key, a restart without persistence, an
|
|
26
|
+
* eviction, or a holder paused longer than the ttl each let two holders in. The fencing
|
|
27
|
+
* token narrows that, for a resource able to compare tokens; its counter lives in the same
|
|
28
|
+
* server and starts again if that server loses it.
|
|
29
|
+
*/
|
|
30
|
+
export class RedisLock {
|
|
31
|
+
client;
|
|
32
|
+
namespace;
|
|
33
|
+
waiting;
|
|
34
|
+
constructor(client, options = {}) {
|
|
35
|
+
this.client = client;
|
|
36
|
+
this.namespace = options.namespace ?? 'lock';
|
|
37
|
+
this.waiting = options;
|
|
38
|
+
conceal(this);
|
|
39
|
+
}
|
|
40
|
+
acquire(key, options) {
|
|
41
|
+
return acquiring(() => this.attempt(key, options.ttlMs), options.waitMs ?? 0, this.waiting);
|
|
42
|
+
}
|
|
43
|
+
/** What a serialisation shows: a name, never the client, whose options carry the password. */
|
|
44
|
+
toJSON() {
|
|
45
|
+
return DESCRIPTION;
|
|
46
|
+
}
|
|
47
|
+
/** What `console.log` and `util.inspect` show: the same. */
|
|
48
|
+
[inspect.custom]() {
|
|
49
|
+
return DESCRIPTION;
|
|
50
|
+
}
|
|
51
|
+
async attempt(key, ttlMs) {
|
|
52
|
+
// The braces make both keys hash to one slot, which a cluster requires of a script.
|
|
53
|
+
const lockKey = namespaced(this.namespace, `{${key}}`);
|
|
54
|
+
const owner = randomBytes(16).toString('hex');
|
|
55
|
+
const ttl = String(Math.max(1, Math.ceil(ttlMs)));
|
|
56
|
+
const token = Number(await this.client.eval(ACQUIRE, 2, lockKey, `${lockKey}:fence`, owner, ttl));
|
|
57
|
+
if (token === 0) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const release = async () => {
|
|
61
|
+
await this.client.eval(RELEASE, 1, lockKey, owner);
|
|
62
|
+
};
|
|
63
|
+
return { key, fencingToken: token, release };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=redis-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-lock.js","sourceRoot":"","sources":["../../src/lock/redis-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAwE,MAAM,oBAAoB,CAAC;AACrH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAOhD,oHAAoH;AACpH,MAAM,OAAO,GAAG;;;;SAIP,CAAC;AAEV,mGAAmG;AACnG,MAAM,OAAO,GAAG;;;;SAIP,CAAC;AAEV,MAAM,WAAW,GAAG,8BAA8B,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,SAAS;IACH,MAAM,CAAQ;IAEd,SAAS,CAAS;IAElB,OAAO,CAAiB;IAEzC,YAAY,MAAa,EAAE,OAAO,GAAqB,EAAE;QACvD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,GAAW,EAAE,OAAuB;QAC1C,OAAO,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9F,CAAC;IAED,8FAA8F;IAC9F,MAAM;QACJ,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,4DAA4D;IAC5D,CAAC,OAAO,CAAC,MAAM,CAAC;QACd,OAAO,WAAW,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,KAAa;QAC9C,oFAAoF;QACpF,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,GAAG,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QAElG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;YACxC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC,CAAC;QAEF,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC/C,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
import { type Clock, type RateLimit, type RateLimitDecision, type RateLimiter } from '@ddd-ts-kit/kernel';
|
|
4
|
+
export interface RedisRateLimiterOptions {
|
|
5
|
+
/** Prefix of the keys. Default `rate`. */
|
|
6
|
+
readonly namespace?: string;
|
|
7
|
+
/**
|
|
8
|
+
* The instants come from the caller, not from the server: every instance must run on a
|
|
9
|
+
* synchronized clock, and a skew between two of them shifts the window by as much.
|
|
10
|
+
*/
|
|
11
|
+
readonly clock?: Clock;
|
|
12
|
+
/**
|
|
13
|
+
* What to answer while the server is away. `throw` (default) lets the caller decide;
|
|
14
|
+
* `allow` keeps the application serving without a limit; `deny` refuses every call.
|
|
15
|
+
*/
|
|
16
|
+
readonly onError?: 'throw' | 'allow' | 'deny';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* An exact sliding window, as a sorted set of the instants of the allowed calls: one member
|
|
20
|
+
* per call, so a key costs memory in proportion to its limit. For limits in the tens of
|
|
21
|
+
* thousands per window, count in a coarser key instead.
|
|
22
|
+
*/
|
|
23
|
+
export declare class RedisRateLimiter implements RateLimiter {
|
|
24
|
+
private readonly client;
|
|
25
|
+
private readonly namespace;
|
|
26
|
+
private readonly clock;
|
|
27
|
+
private readonly onError;
|
|
28
|
+
constructor(client: Redis, options?: RedisRateLimiterOptions);
|
|
29
|
+
consume(key: string, limit: RateLimit): Promise<RateLimitDecision>;
|
|
30
|
+
/** What a serialisation shows: a name, never the client, whose options carry the password. */
|
|
31
|
+
toJSON(): string;
|
|
32
|
+
/** What `console.log` and `util.inspect` show: the same. */
|
|
33
|
+
[inspect.custom](): string;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=redis-rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limiter/redis-rate-limiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAK5B,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IAEvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;CAC/C;AAuBD;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAE/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAErD,YAAY,MAAM,EAAE,KAAK,EAAE,OAAO,GAAE,uBAA4B,EAM/D;IAEK,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBvE;IAED,8FAA8F;IAC9F,MAAM,IAAI,MAAM,CAEf;IAED,4DAA4D;IAC5D,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;CACF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { inspect } from 'node:util';
|
|
3
|
+
import { SystemClock, rateLimitDecisionOf, } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { namespaced } from '../keys/namespaced.js';
|
|
5
|
+
import { listOf } from '../replies/list-of.js';
|
|
6
|
+
import { conceal } from '../secrecy/conceal.js';
|
|
7
|
+
/**
|
|
8
|
+
* Forgets the calls that left the window, then counts and records this one in the same step:
|
|
9
|
+
* two concurrent calls cannot both take the last place. Lua is kept to comparisons, the
|
|
10
|
+
* arithmetic is done by the caller. KEYS: calls. ARGV: cutoff, limit, now, member, window.
|
|
11
|
+
* Answers allowed, calls in the window, score of the oldest.
|
|
12
|
+
*/
|
|
13
|
+
const CONSUME = `
|
|
14
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[1])
|
|
15
|
+
local calls = redis.call('ZCARD', KEYS[1])
|
|
16
|
+
local allowed = 0
|
|
17
|
+
if calls < tonumber(ARGV[2]) then
|
|
18
|
+
redis.call('ZADD', KEYS[1], ARGV[3], ARGV[4])
|
|
19
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[5])
|
|
20
|
+
calls = calls + 1
|
|
21
|
+
allowed = 1
|
|
22
|
+
end
|
|
23
|
+
local oldest = redis.call('ZRANGE', KEYS[1], 0, 0, 'WITHSCORES')
|
|
24
|
+
return {allowed, calls, oldest[2]}`;
|
|
25
|
+
const DESCRIPTION = 'RedisRateLimiter [client concealed]';
|
|
26
|
+
/**
|
|
27
|
+
* An exact sliding window, as a sorted set of the instants of the allowed calls: one member
|
|
28
|
+
* per call, so a key costs memory in proportion to its limit. For limits in the tens of
|
|
29
|
+
* thousands per window, count in a coarser key instead.
|
|
30
|
+
*/
|
|
31
|
+
export class RedisRateLimiter {
|
|
32
|
+
client;
|
|
33
|
+
namespace;
|
|
34
|
+
clock;
|
|
35
|
+
onError;
|
|
36
|
+
constructor(client, options = {}) {
|
|
37
|
+
this.client = client;
|
|
38
|
+
this.namespace = options.namespace ?? 'rate';
|
|
39
|
+
this.clock = options.clock ?? new SystemClock();
|
|
40
|
+
this.onError = options.onError ?? 'throw';
|
|
41
|
+
conceal(this);
|
|
42
|
+
}
|
|
43
|
+
async consume(key, limit) {
|
|
44
|
+
const now = this.clock.now().getTime();
|
|
45
|
+
try {
|
|
46
|
+
const answer = await this.client.eval(CONSUME, 1, namespaced(this.namespace, key), ...argumentsOf(now, limit));
|
|
47
|
+
const [allowed, calls, oldest] = listOf(answer);
|
|
48
|
+
return rateLimitDecisionOf({ allowed: Number(allowed) === 1, calls: Number(calls), oldest: Number(oldest ?? now), now }, limit);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (this.onError === 'throw') {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
const allowed = this.onError === 'allow';
|
|
55
|
+
return { allowed, remaining: 0, retryAfterMs: allowed ? 0 : limit.windowMs };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** What a serialisation shows: a name, never the client, whose options carry the password. */
|
|
59
|
+
toJSON() {
|
|
60
|
+
return DESCRIPTION;
|
|
61
|
+
}
|
|
62
|
+
/** What `console.log` and `util.inspect` show: the same. */
|
|
63
|
+
[inspect.custom]() {
|
|
64
|
+
return DESCRIPTION;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function argumentsOf(now, limit) {
|
|
68
|
+
const member = `${now}-${randomBytes(8).toString('hex')}`;
|
|
69
|
+
return [
|
|
70
|
+
String(now - limit.windowMs),
|
|
71
|
+
String(limit.limit),
|
|
72
|
+
String(now),
|
|
73
|
+
member,
|
|
74
|
+
String(Math.max(1, Math.ceil(limit.windowMs))),
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=redis-rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-rate-limiter.js","sourceRoot":"","sources":["../../src/rate-limiter/redis-rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACL,WAAW,EACX,mBAAmB,GAKpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAmBhD;;;;;GAKG;AACH,MAAM,OAAO,GAAG;;;;;;;;;;;mCAWmB,CAAC;AAEpC,MAAM,WAAW,GAAG,qCAAqC,CAAC;AAE1D;;;;GAIG;AACH,MAAM,OAAO,gBAAgB;IACV,MAAM,CAAQ;IAEd,SAAS,CAAS;IAElB,KAAK,CAAQ;IAEb,OAAO,CAA6B;IAErD,YAAY,MAAa,EAAE,OAAO,GAA4B,EAAE;QAC9D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,WAAW,EAAE,CAAC;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,KAAgB;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QAEvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YAC/G,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAEhD,OAAO,mBAAmB,CACxB,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EAAE,GAAG,EAAE,EAC5F,KAAK,CACN,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;gBAC7B,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC;YAEzC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,8FAA8F;IAC9F,MAAM;QACJ,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,4DAA4D;IAC5D,CAAC,OAAO,CAAC,MAAM,CAAC;QACd,OAAO,WAAW,CAAC;IACrB,CAAC;CACF;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,KAAgB;IAChD,MAAM,MAAM,GAAG,GAAG,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAE1D,OAAO;QACL,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;QACnB,MAAM,CAAC,GAAG,CAAC;QACX,MAAM;QACN,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;KAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
export interface RedisRelaySignalOptions {
|
|
4
|
+
/** One channel per outbox. Default `ddd-ts-kit:outbox`. */
|
|
5
|
+
readonly channel?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The `RelaySignal` of `@ddd-ts-kit/events` between processes, over publish/subscribe: the
|
|
9
|
+
* process that appended to the outbox calls `notify`, and a relay built with `wakeOn` in any
|
|
10
|
+
* other process ticks at once instead of at its next interval.
|
|
11
|
+
*
|
|
12
|
+
* Same semantics as the in-memory signal: a hint, never a delivery. Publish/subscribe keeps
|
|
13
|
+
* nothing for a subscriber that is away, so a notification sent while the relay reconnects
|
|
14
|
+
* is lost, and the event goes out at the next poll. Keep the interval of the relay.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RedisRelaySignal {
|
|
17
|
+
private readonly client;
|
|
18
|
+
private readonly channel;
|
|
19
|
+
private readonly listeners;
|
|
20
|
+
private subscriber;
|
|
21
|
+
constructor(client: Redis, options?: RedisRelaySignalOptions);
|
|
22
|
+
subscribe(listener: () => void): {
|
|
23
|
+
unsubscribe(): void;
|
|
24
|
+
};
|
|
25
|
+
/** Never fails: a notification that cannot be sent only delays the publication. */
|
|
26
|
+
notify(): Promise<void>;
|
|
27
|
+
/** Closes the connection this signal opened to listen. The client it was given stays open. */
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
/** What a serialisation shows: a name, never the client, whose options carry the password. */
|
|
30
|
+
toJSON(): string;
|
|
31
|
+
/** What `console.log` and `util.inspect` show: the same. */
|
|
32
|
+
[inspect.custom](): string;
|
|
33
|
+
/** A connection in subscriber mode accepts no other command, hence one of its own. */
|
|
34
|
+
private listen;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=redis-relay-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-relay-signal.d.ts","sourceRoot":"","sources":["../../src/relay/redis-relay-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIrC,MAAM,WAAW,uBAAuB;IACtC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAE/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IAEnD,OAAO,CAAC,UAAU,CAAoB;IAEtC,YAAY,MAAM,EAAE,KAAK,EAAE,OAAO,GAAE,uBAA4B,EAI/D;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG;QAAE,WAAW,IAAI,IAAI,CAAA;KAAE,CAKvD;IAED,mFAAmF;IAC7E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAM5B;IAED,8FAA8F;IACxF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3B;IAED,8FAA8F;IAC9F,MAAM,IAAI,MAAM,CAEf;IAED,4DAA4D;IAC5D,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;IAED,sFAAsF;IACtF,OAAO,CAAC,MAAM;CAmBf"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import { closeRedis } from '../connection/close-redis.js';
|
|
3
|
+
import { conceal } from '../secrecy/conceal.js';
|
|
4
|
+
const DESCRIPTION = 'RedisRelaySignal [client concealed]';
|
|
5
|
+
/**
|
|
6
|
+
* The `RelaySignal` of `@ddd-ts-kit/events` between processes, over publish/subscribe: the
|
|
7
|
+
* process that appended to the outbox calls `notify`, and a relay built with `wakeOn` in any
|
|
8
|
+
* other process ticks at once instead of at its next interval.
|
|
9
|
+
*
|
|
10
|
+
* Same semantics as the in-memory signal: a hint, never a delivery. Publish/subscribe keeps
|
|
11
|
+
* nothing for a subscriber that is away, so a notification sent while the relay reconnects
|
|
12
|
+
* is lost, and the event goes out at the next poll. Keep the interval of the relay.
|
|
13
|
+
*/
|
|
14
|
+
export class RedisRelaySignal {
|
|
15
|
+
client;
|
|
16
|
+
channel;
|
|
17
|
+
listeners = new Set();
|
|
18
|
+
subscriber;
|
|
19
|
+
constructor(client, options = {}) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
this.channel = options.channel ?? 'ddd-ts-kit:outbox';
|
|
22
|
+
conceal(this);
|
|
23
|
+
}
|
|
24
|
+
subscribe(listener) {
|
|
25
|
+
this.listeners.add(listener);
|
|
26
|
+
this.subscriber ??= this.listen();
|
|
27
|
+
return { unsubscribe: () => void this.listeners.delete(listener) };
|
|
28
|
+
}
|
|
29
|
+
/** Never fails: a notification that cannot be sent only delays the publication. */
|
|
30
|
+
async notify() {
|
|
31
|
+
try {
|
|
32
|
+
await this.client.publish(this.channel, '');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Closes the connection this signal opened to listen. The client it was given stays open. */
|
|
39
|
+
async close() {
|
|
40
|
+
this.listeners.clear();
|
|
41
|
+
const subscriber = this.subscriber;
|
|
42
|
+
this.subscriber = undefined;
|
|
43
|
+
if (subscriber !== undefined) {
|
|
44
|
+
await closeRedis(subscriber);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** What a serialisation shows: a name, never the client, whose options carry the password. */
|
|
48
|
+
toJSON() {
|
|
49
|
+
return DESCRIPTION;
|
|
50
|
+
}
|
|
51
|
+
/** What `console.log` and `util.inspect` show: the same. */
|
|
52
|
+
[inspect.custom]() {
|
|
53
|
+
return DESCRIPTION;
|
|
54
|
+
}
|
|
55
|
+
/** A connection in subscriber mode accepts no other command, hence one of its own. */
|
|
56
|
+
listen() {
|
|
57
|
+
const subscriber = this.client.duplicate();
|
|
58
|
+
subscriber.on('message', (channel) => {
|
|
59
|
+
if (channel !== this.channel) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for (const listener of [...this.listeners]) {
|
|
63
|
+
listener();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// Without a handler ioredis prints connection errors; it reconnects and subscribes again by itself.
|
|
67
|
+
subscriber.on('error', () => undefined);
|
|
68
|
+
subscriber.subscribe(this.channel).catch(() => undefined);
|
|
69
|
+
return subscriber;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=redis-relay-signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-relay-signal.js","sourceRoot":"","sources":["../../src/relay/redis-relay-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAOhD,MAAM,WAAW,GAAG,qCAAqC,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IACV,MAAM,CAAQ;IAEd,OAAO,CAAS;IAEhB,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IAE3C,UAAU,CAAoB;IAEtC,YAAY,MAAa,EAAE,OAAO,GAA4B,EAAE;QAC9D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,mBAAmB,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,SAAS,CAAC,QAAoB;QAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAElC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IACrE,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,MAAM;QACV,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;IACH,CAAC;IAED,8FAA8F;IAC9F,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAEnC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAE5B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,8FAA8F;IAC9F,MAAM;QACJ,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,4DAA4D;IAC5D,CAAC,OAAO,CAAC,MAAM,CAAC;QACd,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,sFAAsF;IAC9E,MAAM;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAE3C,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC7B,OAAO;YACT,CAAC;YAED,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3C,QAAQ,EAAE,CAAC;YACb,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,oGAAoG;QACpG,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACxC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAE1D,OAAO,UAAU,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BusMiddleware } from '@ddd-ts-kit/kernel';
|
|
2
|
+
/**
|
|
3
|
+
* Notifies after each command that succeeded. Install it before `unitOfWorkMiddleware`, so
|
|
4
|
+
* that the notification leaves once the transaction that appended to the outbox has
|
|
5
|
+
* committed. A command that published nothing wakes the relay for nothing, which costs one
|
|
6
|
+
* empty claim.
|
|
7
|
+
*/
|
|
8
|
+
export declare function relayWakeUpMiddleware(signal: {
|
|
9
|
+
notify(): Promise<void>;
|
|
10
|
+
}): BusMiddleware;
|
|
11
|
+
//# sourceMappingURL=relay-wake-up-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-wake-up-middleware.d.ts","sourceRoot":"","sources":["../../src/relay/relay-wake-up-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAAE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAAG,aAAa,CAUxF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifies after each command that succeeded. Install it before `unitOfWorkMiddleware`, so
|
|
3
|
+
* that the notification leaves once the transaction that appended to the outbox has
|
|
4
|
+
* committed. A command that published nothing wakes the relay for nothing, which costs one
|
|
5
|
+
* empty claim.
|
|
6
|
+
*/
|
|
7
|
+
export function relayWakeUpMiddleware(signal) {
|
|
8
|
+
return async (_message, next, info) => {
|
|
9
|
+
const result = await next();
|
|
10
|
+
if (info.kind === 'command') {
|
|
11
|
+
void signal.notify().catch(() => undefined);
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=relay-wake-up-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-wake-up-middleware.js","sourceRoot":"","sources":["../../src/relay/relay-wake-up-middleware.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAmC;IACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAE5B,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-of.d.ts","sourceRoot":"","sources":["../../src/replies/list-of.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,CAEzD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A reply of the server read as a list: anything else, `null` included, is an empty one. */
|
|
2
|
+
export function listOf(reply) {
|
|
3
|
+
return isList(reply) ? reply : [];
|
|
4
|
+
}
|
|
5
|
+
function isList(value) {
|
|
6
|
+
return Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=list-of.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-of.js","sourceRoot":"","sources":["../../src/replies/list-of.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,MAAM,UAAU,MAAM,CAAC,KAAc;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes the fields an instance holds so far out of enumeration: `Object.keys`, a spread and a
|
|
3
|
+
* comparison no longer reach the client or the connection behind them, whose options carry
|
|
4
|
+
* credentials. Called last in a constructor, next to a `toJSON` and an inspection method that
|
|
5
|
+
* answer a name: nothing the instance is shown through leads to what it holds.
|
|
6
|
+
*/
|
|
7
|
+
export declare function conceal(instance: object): void;
|
|
8
|
+
//# sourceMappingURL=conceal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conceal.d.ts","sourceRoot":"","sources":["../../src/secrecy/conceal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAI9C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes the fields an instance holds so far out of enumeration: `Object.keys`, a spread and a
|
|
3
|
+
* comparison no longer reach the client or the connection behind them, whose options carry
|
|
4
|
+
* credentials. Called last in a constructor, next to a `toJSON` and an inspection method that
|
|
5
|
+
* answer a name: nothing the instance is shown through leads to what it holds.
|
|
6
|
+
*/
|
|
7
|
+
export function conceal(instance) {
|
|
8
|
+
for (const field of Object.keys(instance)) {
|
|
9
|
+
Object.defineProperty(instance, field, { enumerable: false });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=conceal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conceal.js","sourceRoot":"","sources":["../../src/secrecy/conceal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import type { Entry } from './codec/entry.js';
|
|
3
|
+
export interface DeadLetter {
|
|
4
|
+
/** The stream the entry was read from, and the group it is pending in. */
|
|
5
|
+
readonly stream: string;
|
|
6
|
+
readonly group: string;
|
|
7
|
+
readonly deadStream: string;
|
|
8
|
+
readonly entry: Entry;
|
|
9
|
+
/** Why it died, as fields added to the copy: codes only. */
|
|
10
|
+
readonly annotations: Readonly<Record<string, string>>;
|
|
11
|
+
}
|
|
12
|
+
export declare function bury(client: Redis, letter: DeadLetter): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=bury.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bury.d.ts","sourceRoot":"","sources":["../../src/streams/bury.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAEtB,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD;AAWD,wBAAsB,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAI3E"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { fieldsWith } from './codec/fields-with.js';
|
|
2
|
+
/**
|
|
3
|
+
* Copies the entry to the dead stream, then acknowledges it. A script, not a `MULTI`: a
|
|
4
|
+
* transaction would acknowledge even if the copy failed. KEYS: dead stream, stream.
|
|
5
|
+
* ARGV: group, entry id, then the fields of the copy.
|
|
6
|
+
*/
|
|
7
|
+
const BURY = `
|
|
8
|
+
redis.call('XADD', KEYS[1], '*', unpack(ARGV, 3))
|
|
9
|
+
return redis.call('XACK', KEYS[2], ARGV[1], ARGV[2])`;
|
|
10
|
+
export async function bury(client, letter) {
|
|
11
|
+
const { stream, group, deadStream, entry } = letter;
|
|
12
|
+
await client.eval(BURY, 2, deadStream, stream, group, entry.id, ...fieldsWith(entry, letter.annotations));
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=bury.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bury.js","sourceRoot":"","sources":["../../src/streams/bury.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAgBpD;;;;GAIG;AACH,MAAM,IAAI,GAAG;;qDAEwC,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,MAAa,EAAE,MAAkB;IAC1D,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAEpD,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAC5G,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import type { Delivery } from './delivery.js';
|
|
3
|
+
export interface RedeliverySettings {
|
|
4
|
+
/** The consumer name, which is the name of its group. */
|
|
5
|
+
readonly consumer: string;
|
|
6
|
+
readonly instance: string;
|
|
7
|
+
readonly batchSize: number;
|
|
8
|
+
readonly retryDelayMs: number;
|
|
9
|
+
readonly visibilityTimeoutMs: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Takes back what is due in the pending entry list of the group for one stream: the entries
|
|
13
|
+
* this instance failed, once the retry delay has passed, and those another instance has left
|
|
14
|
+
* idle for longer than the visibility timeout. `XCLAIM` counts a delivery; the attempt is the
|
|
15
|
+
* counter of the pending entry list after it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function claimDue(reader: Redis, stream: string, settings: RedeliverySettings): Promise<Delivery[]>;
|
|
18
|
+
//# sourceMappingURL=claim-due.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-due.d.ts","sourceRoot":"","sources":["../../src/streams/claim-due.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC;AAQD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAe/G"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { entriesOf } from './codec/entries-of.js';
|
|
2
|
+
import { pendingOf } from './codec/pending-of.js';
|
|
3
|
+
/**
|
|
4
|
+
* Takes back what is due in the pending entry list of the group for one stream: the entries
|
|
5
|
+
* this instance failed, once the retry delay has passed, and those another instance has left
|
|
6
|
+
* idle for longer than the visibility timeout. `XCLAIM` counts a delivery; the attempt is the
|
|
7
|
+
* counter of the pending entry list after it.
|
|
8
|
+
*/
|
|
9
|
+
export async function claimDue(reader, stream, settings) {
|
|
10
|
+
const { consumer, instance, batchSize, retryDelayMs, visibilityTimeoutMs } = settings;
|
|
11
|
+
const [failedHere, idle] = await Promise.all([
|
|
12
|
+
reader.xpending(stream, consumer, 'IDLE', retryDelayMs, '-', '+', batchSize, instance),
|
|
13
|
+
reader.xpending(stream, consumer, 'IDLE', visibilityTimeoutMs, '-', '+', batchSize),
|
|
14
|
+
]);
|
|
15
|
+
const abandoned = pendingOf(idle).filter((entry) => entry.consumer !== instance);
|
|
16
|
+
const claimant = new Claimant(reader, settings);
|
|
17
|
+
return [
|
|
18
|
+
...(await claimant.claim({ stream, pending: pendingOf(failedHere), minIdleMs: retryDelayMs })),
|
|
19
|
+
...(await claimant.claim({ stream, pending: abandoned, minIdleMs: visibilityTimeoutMs })),
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
class Claimant {
|
|
23
|
+
reader;
|
|
24
|
+
settings;
|
|
25
|
+
constructor(reader, settings) {
|
|
26
|
+
this.reader = reader;
|
|
27
|
+
this.settings = settings;
|
|
28
|
+
}
|
|
29
|
+
/** An entry another instance claimed in the meantime is no longer idle, and is not answered. */
|
|
30
|
+
async claim({ stream, pending, minIdleMs }) {
|
|
31
|
+
if (pending.length === 0) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const { consumer, instance } = this.settings;
|
|
35
|
+
const ids = pending.map((entry) => entry.id);
|
|
36
|
+
const claimed = entriesOf(await this.reader.call('XCLAIM', stream, consumer, instance, minIdleMs, ...ids));
|
|
37
|
+
const deliveries = new Map(pending.map((entry) => [entry.id, entry.deliveries]));
|
|
38
|
+
return claimed.map((entry) => ({ stream, entry, attempt: (deliveries.get(entry.id) ?? 0) + 1 }));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=claim-due.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-due.js","sourceRoot":"","sources":["../../src/streams/claim-due.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAsBlD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAa,EAAE,MAAc,EAAE,QAA4B;IACxF,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,mBAAmB,EAAE,GAAG,QAAQ,CAAC;IAEtF,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC;QACtF,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC;KACpF,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEhD,OAAO;QACL,GAAG,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9F,GAAG,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAC;KAC1F,CAAC;AACJ,CAAC;AAED,MAAM,QAAQ;IAEO,MAAM;IACN,QAAQ;IAF3B,YACmB,MAAa,EACb,QAA4B;sBAD5B,MAAM;wBACN,QAAQ;IACxB,CAAC;IAEJ,gGAAgG;IAChG,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAS;QAC/C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC3G,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAEjF,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC;CACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IntegrationEvent } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { Entry } from './entry.js';
|
|
3
|
+
/** The envelope exactly as published, or `undefined` for an entry that does not hold one (poison). */
|
|
4
|
+
export declare function decode(entry: Entry): IntegrationEvent | undefined;
|
|
5
|
+
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../../src/streams/codec/decode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAaxC,sGAAsG;AACtG,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,gBAAgB,GAAG,SAAS,CAgBjE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ENVELOPE_FIELD } from './entry-fields.js';
|
|
2
|
+
/** The envelope exactly as published, or `undefined` for an entry that does not hold one (poison). */
|
|
3
|
+
export function decode(entry) {
|
|
4
|
+
const body = parsed(entry.fields.get(ENVELOPE_FIELD));
|
|
5
|
+
if (!isEnvelope(body)) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
id: body.id,
|
|
10
|
+
type: body.type,
|
|
11
|
+
version: body.version,
|
|
12
|
+
occurredAt: body.occurredAt,
|
|
13
|
+
payload: body.payload,
|
|
14
|
+
...(typeof body.traceparent === 'string' ? { traceparent: body.traceparent } : {}),
|
|
15
|
+
...(typeof body.correlationId === 'string' ? { correlationId: body.correlationId } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function parsed(envelope) {
|
|
19
|
+
try {
|
|
20
|
+
return envelope === undefined ? undefined : JSON.parse(envelope);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isEnvelope(body) {
|
|
27
|
+
if (typeof body !== 'object' || body === null) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return (hasText(body, 'id') &&
|
|
31
|
+
hasText(body, 'type') &&
|
|
32
|
+
hasText(body, 'occurredAt') &&
|
|
33
|
+
'version' in body &&
|
|
34
|
+
typeof body.version === 'number');
|
|
35
|
+
}
|
|
36
|
+
function hasText(body, field) {
|
|
37
|
+
const value = Reflect.get(body, field);
|
|
38
|
+
return typeof value === 'string' && value.length > 0;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=decode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.js","sourceRoot":"","sources":["../../../src/streams/codec/decode.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAYnD,sGAAsG;AACtG,MAAM,UAAU,MAAM,CAAC,KAAY;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;IAEtD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,CAAC,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,GAAG,CAAC,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzF,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,QAA4B;IAC1C,IAAI,CAAC;QACH,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAa;IAC/B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CACL,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;QACnB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC;QAC3B,SAAS,IAAI,IAAI;QACjB,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CACjC,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,KAAa;IAC1C,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEhD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC"}
|