youeduc-sdk-messaging 0.0.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/admin.d.ts +61 -0
- package/dist/admin.js +143 -0
- package/dist/admin.js.map +1 -0
- package/dist/config.d.ts +74 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/consumer-client.d.ts +155 -0
- package/dist/consumer-client.js +299 -0
- package/dist/consumer-client.js.map +1 -0
- package/dist/consumer.d.ts +156 -0
- package/dist/consumer.js +1228 -0
- package/dist/consumer.js.map +1 -0
- package/dist/contracts/index.d.ts +16 -0
- package/dist/contracts/index.js +42 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/schemas.d.ts +570 -0
- package/dist/contracts/schemas.js +902 -0
- package/dist/contracts/schemas.js.map +1 -0
- package/dist/contracts/usuarios.d.ts +163 -0
- package/dist/contracts/usuarios.js +5 -0
- package/dist/contracts/usuarios.js.map +1 -0
- package/dist/dedup.d.ts +29 -0
- package/dist/dedup.js +65 -0
- package/dist/dedup.js.map +1 -0
- package/dist/dlq.d.ts +161 -0
- package/dist/dlq.js +341 -0
- package/dist/dlq.js.map +1 -0
- package/dist/errors.d.ts +72 -0
- package/dist/errors.js +95 -0
- package/dist/errors.js.map +1 -0
- package/dist/headers.d.ts +92 -0
- package/dist/headers.js +144 -0
- package/dist/headers.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/redis.d.ts +118 -0
- package/dist/integrations/redis.js +272 -0
- package/dist/integrations/redis.js.map +1 -0
- package/dist/integrations/runtime.d.ts +232 -0
- package/dist/integrations/runtime.js +554 -0
- package/dist/integrations/runtime.js.map +1 -0
- package/dist/logger.d.ts +37 -0
- package/dist/logger.js +49 -0
- package/dist/logger.js.map +1 -0
- package/dist/messaging.d.ts +145 -0
- package/dist/messaging.js +164 -0
- package/dist/messaging.js.map +1 -0
- package/dist/naming.d.ts +64 -0
- package/dist/naming.js +167 -0
- package/dist/naming.js.map +1 -0
- package/dist/payload.d.ts +8 -0
- package/dist/payload.js +32 -0
- package/dist/payload.js.map +1 -0
- package/dist/publisher.d.ts +50 -0
- package/dist/publisher.js +199 -0
- package/dist/publisher.js.map +1 -0
- package/dist/retry.d.ts +24 -0
- package/dist/retry.js +42 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts +83 -0
- package/dist/schema.js +676 -0
- package/dist/schema.js.map +1 -0
- package/dist/telemetry.d.ts +52 -0
- package/dist/telemetry.js +221 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/transport.d.ts +130 -0
- package/dist/transport.js +283 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +38 -0
- package/dist/util.js +112 -0
- package/dist/util.js.map +1 -0
- package/dist/w3c.d.ts +16 -0
- package/dist/w3c.js +119 -0
- package/dist/w3c.js.map +1 -0
- package/package.json +74 -1
package/dist/headers.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message headers. In Kafka headers are bytes; in the SDK they travel as a `string → string`
|
|
3
|
+
* map, (de)coded as UTF-8 at the edge. Header names and value formats are protocol: identical
|
|
4
|
+
* to the Python SDK.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { errorMessage, errorTypeName, formatIsoUtc, truncateChars } from './util.js';
|
|
8
|
+
export const MESSAGE_ID = 'message_id';
|
|
9
|
+
export const EVENT_TYPE = 'event_type';
|
|
10
|
+
export const SCHEMA_VERSION = 'schema_version';
|
|
11
|
+
export const PUBLISHED_AT = 'published_at';
|
|
12
|
+
export const PRODUCER = 'producer';
|
|
13
|
+
export const CORRELATION_ID = 'correlation_id';
|
|
14
|
+
export const IDEMPOTENCY_KEY = 'idempotency_key';
|
|
15
|
+
export const TENANT_ID = 'x-tenant-id';
|
|
16
|
+
export const RETRY_COUNT = 'x-retry-count';
|
|
17
|
+
/** Epoch in milliseconds. */
|
|
18
|
+
export const RETRY_DUE_AT = 'x-retry-due-at';
|
|
19
|
+
export const FIRST_FAILED_AT = 'x-first-failed-at';
|
|
20
|
+
export const LAST_ERROR = 'x-last-error';
|
|
21
|
+
export const LAST_ERROR_TYPE = 'x-last-error-type';
|
|
22
|
+
export const ORIGINAL_TOPIC = 'x-original-topic';
|
|
23
|
+
export const ORIGINAL_PARTITION = 'x-original-partition';
|
|
24
|
+
export const ORIGINAL_OFFSET = 'x-original-offset';
|
|
25
|
+
export const DLQ_REASON = 'x-dlq-reason';
|
|
26
|
+
/** Error messages are truncated (in characters / code points, like Python) to keep headers small. */
|
|
27
|
+
export const MAX_ERROR_LENGTH = 1024;
|
|
28
|
+
const utf8 = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true });
|
|
29
|
+
/** Epoch milliseconds now. */
|
|
30
|
+
export function nowMs() {
|
|
31
|
+
return Date.now();
|
|
32
|
+
}
|
|
33
|
+
/** `MessageHeaders` → wire format (UTF-8), keeping insertion order. */
|
|
34
|
+
export function encode(headers) {
|
|
35
|
+
return Object.entries(headers).map(([name, value]) => [name, Buffer.from(value, 'utf8')]);
|
|
36
|
+
}
|
|
37
|
+
function decodeValue(value) {
|
|
38
|
+
if (value === null || value === undefined)
|
|
39
|
+
return '';
|
|
40
|
+
if (typeof value === 'string')
|
|
41
|
+
return value;
|
|
42
|
+
return utf8.decode(value); // invalid UTF-8 → U+FFFD, like errors="replace"
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Last occurrence wins; invalid UTF-8 is replaced (U+FFFD); `null` values become `''`.
|
|
46
|
+
* The result has no prototype, so a header named `__proto__` is kept as a plain entry.
|
|
47
|
+
*/
|
|
48
|
+
export function decode(raw) {
|
|
49
|
+
const decoded = Object.create(null);
|
|
50
|
+
if (raw === null || raw === undefined)
|
|
51
|
+
return decoded;
|
|
52
|
+
if (Array.isArray(raw)) {
|
|
53
|
+
for (const [name, value] of raw) {
|
|
54
|
+
decoded[name] = decodeValue(value);
|
|
55
|
+
}
|
|
56
|
+
return decoded;
|
|
57
|
+
}
|
|
58
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
if (value.length > 0)
|
|
61
|
+
decoded[name] = decodeValue(value[value.length - 1]);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
decoded[name] = decodeValue(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return decoded;
|
|
68
|
+
}
|
|
69
|
+
// Spec §4.1: `^\s*[+-]?[0-9]+\s*$` (ASCII); anything else → 0.
|
|
70
|
+
const HEADER_INT_RE = /^[ \t\n\r\f\v]*([+-]?[0-9]+)[ \t\n\r\f\v]*$/;
|
|
71
|
+
/** Integer header (`x-retry-count`, `x-retry-due-at`): absent, empty or invalid → 0. */
|
|
72
|
+
export function parseHeaderInt(value) {
|
|
73
|
+
const match = value === undefined ? null : HEADER_INT_RE.exec(value);
|
|
74
|
+
if (match === null)
|
|
75
|
+
return 0;
|
|
76
|
+
const parsed = Number(match[1]);
|
|
77
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
78
|
+
}
|
|
79
|
+
/** Reads `x-retry-count` tolerating absence/invalid values (→ 0); negative → 0. */
|
|
80
|
+
export function retryCount(headers) {
|
|
81
|
+
return Math.max(parseHeaderInt(headers[RETRY_COUNT]), 0);
|
|
82
|
+
}
|
|
83
|
+
/** Reads `x-retry-due-at` (epoch ms); absent or invalid → 0 (= already due). */
|
|
84
|
+
export function retryDueAtMs(headers) {
|
|
85
|
+
return parseHeaderInt(headers[RETRY_DUE_AT]);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Dedup key (spec §8.1): `idempotency_key` if not empty, else `message_id` if not empty, else
|
|
89
|
+
* `null` (no dedup). Used verbatim.
|
|
90
|
+
*/
|
|
91
|
+
export function dedupKey(headers) {
|
|
92
|
+
return headers[IDEMPOTENCY_KEY] || headers[MESSAGE_ID] || null;
|
|
93
|
+
}
|
|
94
|
+
/** Headers of a new publication: fresh UUID4 `message_id`, `correlation_id` given or new. */
|
|
95
|
+
export function buildPublishHeaders(init) {
|
|
96
|
+
const newId = init.newId ?? randomUUID;
|
|
97
|
+
const clock = init.clock ?? nowMs;
|
|
98
|
+
const headers = {
|
|
99
|
+
[MESSAGE_ID]: newId(),
|
|
100
|
+
[EVENT_TYPE]: init.eventType,
|
|
101
|
+
[SCHEMA_VERSION]: init.schemaVersion,
|
|
102
|
+
[PUBLISHED_AT]: formatIsoUtc(clock()),
|
|
103
|
+
[PRODUCER]: init.producer,
|
|
104
|
+
// Python: `correlation_id or uuid4()` (empty string also generates a new one).
|
|
105
|
+
[CORRELATION_ID]: init.correlationId || newId(),
|
|
106
|
+
};
|
|
107
|
+
if (init.idempotencyKey !== null && init.idempotencyKey !== undefined) {
|
|
108
|
+
headers[IDEMPOTENCY_KEY] = init.idempotencyKey;
|
|
109
|
+
}
|
|
110
|
+
// An empty tenant is the same as no tenant (spec vectors `publish-headers`).
|
|
111
|
+
if (init.tenantId)
|
|
112
|
+
headers[TENANT_ID] = init.tenantId;
|
|
113
|
+
return headers;
|
|
114
|
+
}
|
|
115
|
+
function withError(headers, error, position) {
|
|
116
|
+
const updated = { ...headers };
|
|
117
|
+
updated[LAST_ERROR] = truncateChars(errorMessage(error), MAX_ERROR_LENGTH);
|
|
118
|
+
updated[LAST_ERROR_TYPE] = errorTypeName(error);
|
|
119
|
+
// The origin is the first consumption (main topic), preserved across retries.
|
|
120
|
+
setDefault(updated, FIRST_FAILED_AT, () => formatIsoUtc((position.clock ?? nowMs)()));
|
|
121
|
+
setDefault(updated, ORIGINAL_TOPIC, () => position.topic);
|
|
122
|
+
setDefault(updated, ORIGINAL_PARTITION, () => String(position.partition));
|
|
123
|
+
setDefault(updated, ORIGINAL_OFFSET, () => String(position.offset));
|
|
124
|
+
return updated;
|
|
125
|
+
}
|
|
126
|
+
function setDefault(headers, name, value) {
|
|
127
|
+
if (!Object.hasOwn(headers, name))
|
|
128
|
+
headers[name] = value();
|
|
129
|
+
}
|
|
130
|
+
/** Headers for the next retry: increments `x-retry-count`, sets `x-retry-due-at` (epoch ms). */
|
|
131
|
+
export function buildRetryHeaders(headers, error, init) {
|
|
132
|
+
const updated = withError(headers, error, init);
|
|
133
|
+
updated[RETRY_COUNT] = String(retryCount(headers) + 1);
|
|
134
|
+
updated[RETRY_DUE_AT] = String(Math.trunc(init.dueAtMs));
|
|
135
|
+
return updated;
|
|
136
|
+
}
|
|
137
|
+
/** Headers for the DLQ: removes `x-retry-due-at`, sets `x-dlq-reason`, keeps `x-retry-count`. */
|
|
138
|
+
export function buildDlqHeaders(headers, error, init) {
|
|
139
|
+
const updated = withError(headers, error, init);
|
|
140
|
+
delete updated[RETRY_DUE_AT];
|
|
141
|
+
updated[DLQ_REASON] = init.reason;
|
|
142
|
+
return updated;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** youeduc-sdk-messaging — asynchronous messaging SDK over Apache Kafka (Node.js). */
|
|
2
|
+
export { type AdminFactory, type AdminTopicConfig, createConfluentAdmin, type EnsureTopicsOptions, ensureTopics, type KafkaAdminLike, } from './admin.js';
|
|
3
|
+
export { type ClientConfig, type Environment, KafkaConfig, type KafkaConfigInit, type KafkaSslOptions, SECURITY_PROTOCOLS, type SecurityProtocol, } from './config.js';
|
|
4
|
+
export { type AutoOffsetReset, Consumer, type ConsumerHandlers, type ConsumerOptions, isFatalPollError, } from './consumer.js';
|
|
5
|
+
export { ConfluentConsumerClient, type ConsumerClientFactory, type ConsumerClientHooks, type ConsumerRecord, createConfluentConsumer, type KafkaConsumerClient, type RdKafkaConsumerConstructor, type RdKafkaConsumerLike, type RdKafkaMessage, type RebalanceListener, type TopicPartition, type TopicPartitionOffset, } from './consumer-client.js';
|
|
6
|
+
/** Business contracts generated from `../contracts` (types + bundled schemas). */
|
|
7
|
+
export * as contracts from './contracts/index.js';
|
|
8
|
+
export { InMemoryDuplicateChecker, type InMemoryDuplicateCheckerOptions } from './dedup.js';
|
|
9
|
+
export { createConfluentReplayConsumer, createConfluentReplayProducer, type ReplayConsumerClient, type ReplayConsumerFactory, type ReplayDlqOptions, type ReplayProducerClient, type ReplayProducerFactory, type ReplayRecord, type ReplayReport, replayDlq, replayGroupId, replayHeaders, } from './dlq.js';
|
|
10
|
+
export { DedupUnavailableError, HandlerCancelledError, InvalidHandlerError, InvalidPayloadError, MessagingError, NotStartedError, PermanentProcessingError, PublishError, SchemaNotFoundError, SchemaValidationError, } from './errors.js';
|
|
11
|
+
export type { KafkaHeaders, MessageHeaders, RawHeaders } from './headers.js';
|
|
12
|
+
export * as headers from './headers.js';
|
|
13
|
+
/**
|
|
14
|
+
* Framework-agnostic runtime (canonical `SDK_MESSAGING` settings, consumer process). It has no
|
|
15
|
+
* static `redis` import: `RedisDedupStore` stays on the `./redis` subpath and `buildDedupStore`
|
|
16
|
+
* loads it on demand.
|
|
17
|
+
*/
|
|
18
|
+
export { buildDedupStore, buildMessaging, type ConsumerBuilder, type ConsumerFactory, type DedupStore, MessagingSettings, type MessagingSettingsInit, RedisSettings, type RunnableConsumer, type RunnableMessaging, runConsumers, type SdkMessagingMapping, type ServeConsumersOptions, type SignalSource, serveConsumers, } from './integrations/runtime.js';
|
|
19
|
+
export { defaultLogger, defaultPinoOptions, type LogFields, type Logger, noopLogger, type PinoLike, pinoLogger, setDefaultLogger, } from './logger.js';
|
|
20
|
+
export { type ManagedSender, Messaging, type MessagingConsumerOptions, type MessagingEnsureTopicsOptions, type MessagingOptions, type StoppableConsumer, } from './messaging.js';
|
|
21
|
+
export * as naming from './naming.js';
|
|
22
|
+
export { decodePayload } from './payload.js';
|
|
23
|
+
export { Publisher, type PublisherOptions, type PublishOptions, publishKey } from './publisher.js';
|
|
24
|
+
export { DEFAULT_RETRY_DELAYS_MS, RetryPolicy, type RetryPolicyOptions } from './retry.js';
|
|
25
|
+
export { ChainSchemaLoader, FileSystemSchemaLoader, InMemorySchemaLoader, JsonSchemaValidator, type SchemaFileSystem, SchemaRegistry, } from './schema.js';
|
|
26
|
+
export { DURATION_BUCKETS_S, NoopTelemetry, OtelTelemetry, type OtelTelemetryOptions, } from './telemetry.js';
|
|
27
|
+
export { type CompressionType, createConfluentProducer, isKafkaClientError, isRetriable, KafkaErrorCodes, type KafkaProducerLike, KafkaProducerSender, type KafkaProducerSenderOptions, type ProducerFactory, type ProducerHeaders, type ProducerRecordMetadata, } from './transport.js';
|
|
28
|
+
export { CONSUME_OUTCOMES, type ConsumedMessage, type ConsumeOutcome, type ConsumeRecord, type ConsumeSpan, createMessageInfo, DLQ_REASONS, type DlqReason, type DlqRecord, type DuplicateChecker, type JsonObject, type MessageHandler, type MessageInfo, type MessageInfoInit, type MessageSender, type PayloadValidationRequest, type PayloadValidator, type PublishRecord, type PublishResult, type PublishStatus, type RetryRecord, type SchemaLoader, type SchemaValidator, type SchemaValidatorFactory, type SendOptions, type SentRecord, type Telemetry, } from './types.js';
|
|
29
|
+
/** Package version (from `package.json`; the release tag `vX.Y.Z` → `X.Y.Z`). */
|
|
30
|
+
export declare const version: string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** youeduc-sdk-messaging — asynchronous messaging SDK over Apache Kafka (Node.js). */
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
export { createConfluentAdmin, ensureTopics, } from './admin.js';
|
|
4
|
+
export { KafkaConfig, SECURITY_PROTOCOLS, } from './config.js';
|
|
5
|
+
export { Consumer, isFatalPollError, } from './consumer.js';
|
|
6
|
+
export { ConfluentConsumerClient, createConfluentConsumer, } from './consumer-client.js';
|
|
7
|
+
/** Business contracts generated from `../contracts` (types + bundled schemas). */
|
|
8
|
+
export * as contracts from './contracts/index.js';
|
|
9
|
+
export { InMemoryDuplicateChecker } from './dedup.js';
|
|
10
|
+
export { createConfluentReplayConsumer, createConfluentReplayProducer, replayDlq, replayGroupId, replayHeaders, } from './dlq.js';
|
|
11
|
+
export { DedupUnavailableError, HandlerCancelledError, InvalidHandlerError, InvalidPayloadError, MessagingError, NotStartedError, PermanentProcessingError, PublishError, SchemaNotFoundError, SchemaValidationError, } from './errors.js';
|
|
12
|
+
export * as headers from './headers.js';
|
|
13
|
+
/**
|
|
14
|
+
* Framework-agnostic runtime (canonical `SDK_MESSAGING` settings, consumer process). It has no
|
|
15
|
+
* static `redis` import: `RedisDedupStore` stays on the `./redis` subpath and `buildDedupStore`
|
|
16
|
+
* loads it on demand.
|
|
17
|
+
*/
|
|
18
|
+
export { buildDedupStore, buildMessaging, MessagingSettings, RedisSettings, runConsumers, serveConsumers, } from './integrations/runtime.js';
|
|
19
|
+
export { defaultLogger, defaultPinoOptions, noopLogger, pinoLogger, setDefaultLogger, } from './logger.js';
|
|
20
|
+
export { Messaging, } from './messaging.js';
|
|
21
|
+
export * as naming from './naming.js';
|
|
22
|
+
export { decodePayload } from './payload.js';
|
|
23
|
+
export { Publisher, publishKey } from './publisher.js';
|
|
24
|
+
export { DEFAULT_RETRY_DELAYS_MS, RetryPolicy } from './retry.js';
|
|
25
|
+
export { ChainSchemaLoader, FileSystemSchemaLoader, InMemorySchemaLoader, JsonSchemaValidator, SchemaRegistry, } from './schema.js';
|
|
26
|
+
export { DURATION_BUCKETS_S, NoopTelemetry, OtelTelemetry, } from './telemetry.js';
|
|
27
|
+
export { createConfluentProducer, isKafkaClientError, isRetriable, KafkaErrorCodes, KafkaProducerSender, } from './transport.js';
|
|
28
|
+
export { CONSUME_OUTCOMES, createMessageInfo, DLQ_REASONS, } from './types.js';
|
|
29
|
+
/** Package version (from `package.json`; the release tag `vX.Y.Z` → `X.Y.Z`). */
|
|
30
|
+
export const version = createRequire(import.meta.url)('../package.json')
|
|
31
|
+
.version;
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAGL,oBAAoB,EAEpB,YAAY,GAEb,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,WAAW,EAGX,kBAAkB,GAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,QAAQ,EAGR,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,uBAAuB,EAIvB,uBAAuB,GAQxB,MAAM,sBAAsB,CAAC;AAC9B,kFAAkF;AAClF,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,wBAAwB,EAAwC,MAAM,YAAY,CAAC;AAC5F,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,EAQ7B,SAAS,EACT,aAAa,EACb,aAAa,GACd,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC;;;;GAIG;AACH,OAAO,EACL,eAAe,EACf,cAAc,EAId,iBAAiB,EAEjB,aAAa,EAGb,YAAY,EAIZ,cAAc,GACf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,aAAa,EACb,kBAAkB,EAGlB,UAAU,EAEV,UAAU,EACV,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,SAAS,GAKV,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAA8C,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnG,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAA2B,MAAM,YAAY,CAAC;AAC3F,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EAEnB,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,aAAa,GAEd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,EACX,eAAe,EAEf,mBAAmB,GAKpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,gBAAgB,EAKhB,iBAAiB,EACjB,WAAW,GAqBZ,MAAM,YAAY,CAAC;AAEpB,iFAAiF;AACjF,MAAM,CAAC,MAAM,OAAO,GAAY,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAyB;KACtG,OAAO,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared deduplication in Redis (spec §8.2), a {@link DuplicateChecker} per consumer group.
|
|
3
|
+
*
|
|
4
|
+
* Exposed as the subpath export `youeduc-sdk-messaging/redis` (never from the main entry point):
|
|
5
|
+
* `redis` (node-redis) is an optional peer dependency and only this module imports it, so
|
|
6
|
+
* applications without Redis never load it.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const store = RedisDedupStore.fromUrl('redis://redis:6379/0', { namespace: 'hmgcatalogo_api' });
|
|
10
|
+
* const consumer = messaging.consumer({ ..., group: 'learning-worker',
|
|
11
|
+
* duplicateChecker: store.checker('learning-worker') });
|
|
12
|
+
* ...
|
|
13
|
+
* await store.close();
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Isolation in a shared Redis: every key lives under the system/environment `namespace`
|
|
17
|
+
* (`catalogo_api`, `hmgcatalogo_api`...): `{namespace}:sdk-messaging:dedup:{group}:{dedupKey}`. The
|
|
18
|
+
* namespace has no `:` and the group is kebab-case, so the prefix is unambiguous. To clean an
|
|
19
|
+
* environment: `SCAN` + `UNLINK` on `{namespace}:sdk-messaging:*`.
|
|
20
|
+
*
|
|
21
|
+
* TTL (`ttlMs`, default 7 days): the mark must outlive every window in which the same message
|
|
22
|
+
* can still be redelivered (retry ladder, consumer lag, routing holds, manual replays). It is
|
|
23
|
+
* sent to Redis in whole seconds, rounded up, minimum 1 (`SET key "1" EX n`, no `NX`: marking
|
|
24
|
+
* again renews the TTL).
|
|
25
|
+
*
|
|
26
|
+
* Timeouts (`timeoutMs`, default 5000): every operation is bounded by it; `fromUrl` also uses it
|
|
27
|
+
* as the connection timeout (`socket.connectTimeout`, overridable through the client options).
|
|
28
|
+
* A timeout or a Redis failure becomes {@link DedupUnavailableError}: the consumer treats a
|
|
29
|
+
* failed query as "not processed" and only logs a failed mark, so with Redis down the
|
|
30
|
+
* consumption goes on **without** deduplication (handlers must be idempotent).
|
|
31
|
+
*
|
|
32
|
+
* The dedup key (idempotency key / message id) may be PII: it never appears in error messages
|
|
33
|
+
* of this module (only namespace, group and operation).
|
|
34
|
+
*/
|
|
35
|
+
import { type RedisClientOptions, type RedisFunctions, type RedisModules, type RedisScripts, type RespVersions } from 'redis';
|
|
36
|
+
import type { DuplicateChecker } from '../types.js';
|
|
37
|
+
export { DedupUnavailableError } from '../errors.js';
|
|
38
|
+
/** Default TTL of a mark: 7 days. */
|
|
39
|
+
export declare const DEFAULT_TTL_MS = 604800000;
|
|
40
|
+
/** Default timeout of each Redis operation (and of the connection created by `fromUrl`). */
|
|
41
|
+
export declare const DEFAULT_TIMEOUT_MS = 5000;
|
|
42
|
+
/**
|
|
43
|
+
* What the store uses from a Redis client. A node-redis client (`createClient()`) satisfies it;
|
|
44
|
+
* an injected client must already be connected (its lifecycle belongs to the caller).
|
|
45
|
+
*/
|
|
46
|
+
export interface RedisClientLike {
|
|
47
|
+
exists(key: string): Promise<unknown>;
|
|
48
|
+
set(key: string, value: string, options: {
|
|
49
|
+
expiration: {
|
|
50
|
+
type: 'EX';
|
|
51
|
+
value: number;
|
|
52
|
+
};
|
|
53
|
+
}): Promise<unknown>;
|
|
54
|
+
ping(): Promise<unknown>;
|
|
55
|
+
}
|
|
56
|
+
export interface RedisDedupStoreOptions {
|
|
57
|
+
/** System/environment prefix (spec §2.1): 1-100 chars of `[A-Za-z0-9_.-]`, no `:`. */
|
|
58
|
+
namespace: string;
|
|
59
|
+
/** Validity of a mark. Default 604 800 000 ms (7 days); sent as whole seconds, rounded up. */
|
|
60
|
+
ttlMs?: number;
|
|
61
|
+
/** Bound of every operation. Default 5000 ms. */
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
}
|
|
64
|
+
/** {@link RedisDedupStoreOptions} plus node-redis client options (they override the defaults). */
|
|
65
|
+
export type RedisDedupStoreFromUrlOptions = RedisDedupStoreOptions & Omit<RedisClientOptions<RedisModules, RedisFunctions, RedisScripts, RespVersions>, 'url'>;
|
|
66
|
+
/** Module-private members shared by the store and its checkers. */
|
|
67
|
+
declare const CALL: unique symbol;
|
|
68
|
+
declare const TTL_SECONDS: unique symbol;
|
|
69
|
+
/**
|
|
70
|
+
* Owner of the configuration (namespace, TTL, timeout) and of the Redis client. One store per
|
|
71
|
+
* process/system; one {@link RedisDuplicateChecker} per consumer group (`checker(group)`), all
|
|
72
|
+
* sharing the same client.
|
|
73
|
+
*/
|
|
74
|
+
export declare class RedisDedupStore {
|
|
75
|
+
#private;
|
|
76
|
+
/**
|
|
77
|
+
* Wraps a client owned by the caller: `close()` does not close it and the store never connects
|
|
78
|
+
* it (connect it before use).
|
|
79
|
+
*/
|
|
80
|
+
constructor(client: RedisClientLike, options: RedisDedupStoreOptions);
|
|
81
|
+
/**
|
|
82
|
+
* Creates a node-redis client for `url` (lazy: it connects on the first operation) and owns it:
|
|
83
|
+
* `close()` closes it. `socket.connectTimeout` defaults to `timeoutMs`; the remaining options
|
|
84
|
+
* are passed to `createClient` and override the defaults.
|
|
85
|
+
*/
|
|
86
|
+
static fromUrl(url: string, options: RedisDedupStoreFromUrlOptions): RedisDedupStore;
|
|
87
|
+
get namespace(): string;
|
|
88
|
+
get ttlMs(): number;
|
|
89
|
+
get timeoutMs(): number;
|
|
90
|
+
/** Checker of the consumer group `group` (validated by `naming.consumerGroup`). */
|
|
91
|
+
checker(group: string): RedisDuplicateChecker;
|
|
92
|
+
/** Health check bounded by `timeoutMs`. Never throws: failure → `false`. */
|
|
93
|
+
ping(): Promise<boolean>;
|
|
94
|
+
/** Closes the client only if it was created by {@link fromUrl}. Idempotent. */
|
|
95
|
+
close(): Promise<void>;
|
|
96
|
+
/** Runs a Redis command for a checker (timeout + error wrapping). */
|
|
97
|
+
[CALL]<T>(operation: string, group: string, command: (client: RedisClientLike) => Promise<T>): Promise<T>;
|
|
98
|
+
/** TTL sent to Redis (`EX`): whole seconds, rounded up, minimum 1. */
|
|
99
|
+
get [TTL_SECONDS](): number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* {@link DuplicateChecker} of one group, created by {@link RedisDedupStore.checker}.
|
|
103
|
+
*
|
|
104
|
+
* - `isProcessed`: `EXISTS` of the key.
|
|
105
|
+
* - `markProcessed`: `SET key "1" EX ttl`. Plain SET (no `NX`): a new mark overwrites and renews
|
|
106
|
+
* the TTL. Safe because the consumer only marks after the handler **succeeded**.
|
|
107
|
+
*/
|
|
108
|
+
export declare class RedisDuplicateChecker implements DuplicateChecker {
|
|
109
|
+
#private;
|
|
110
|
+
constructor(store: RedisDedupStore, group: string);
|
|
111
|
+
get group(): string;
|
|
112
|
+
get namespace(): string;
|
|
113
|
+
/** Redis key of `dedupKey`: `{namespace}:sdk-messaging:dedup:{group}:{dedupKey}`. */
|
|
114
|
+
keyFor(dedupKey: string): string;
|
|
115
|
+
isProcessed(key: string): Promise<boolean>;
|
|
116
|
+
markProcessed(key: string): Promise<void>;
|
|
117
|
+
toString(): string;
|
|
118
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared deduplication in Redis (spec §8.2), a {@link DuplicateChecker} per consumer group.
|
|
3
|
+
*
|
|
4
|
+
* Exposed as the subpath export `youeduc-sdk-messaging/redis` (never from the main entry point):
|
|
5
|
+
* `redis` (node-redis) is an optional peer dependency and only this module imports it, so
|
|
6
|
+
* applications without Redis never load it.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const store = RedisDedupStore.fromUrl('redis://redis:6379/0', { namespace: 'hmgcatalogo_api' });
|
|
10
|
+
* const consumer = messaging.consumer({ ..., group: 'learning-worker',
|
|
11
|
+
* duplicateChecker: store.checker('learning-worker') });
|
|
12
|
+
* ...
|
|
13
|
+
* await store.close();
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Isolation in a shared Redis: every key lives under the system/environment `namespace`
|
|
17
|
+
* (`catalogo_api`, `hmgcatalogo_api`...): `{namespace}:sdk-messaging:dedup:{group}:{dedupKey}`. The
|
|
18
|
+
* namespace has no `:` and the group is kebab-case, so the prefix is unambiguous. To clean an
|
|
19
|
+
* environment: `SCAN` + `UNLINK` on `{namespace}:sdk-messaging:*`.
|
|
20
|
+
*
|
|
21
|
+
* TTL (`ttlMs`, default 7 days): the mark must outlive every window in which the same message
|
|
22
|
+
* can still be redelivered (retry ladder, consumer lag, routing holds, manual replays). It is
|
|
23
|
+
* sent to Redis in whole seconds, rounded up, minimum 1 (`SET key "1" EX n`, no `NX`: marking
|
|
24
|
+
* again renews the TTL).
|
|
25
|
+
*
|
|
26
|
+
* Timeouts (`timeoutMs`, default 5000): every operation is bounded by it; `fromUrl` also uses it
|
|
27
|
+
* as the connection timeout (`socket.connectTimeout`, overridable through the client options).
|
|
28
|
+
* A timeout or a Redis failure becomes {@link DedupUnavailableError}: the consumer treats a
|
|
29
|
+
* failed query as "not processed" and only logs a failed mark, so with Redis down the
|
|
30
|
+
* consumption goes on **without** deduplication (handlers must be idempotent).
|
|
31
|
+
*
|
|
32
|
+
* The dedup key (idempotency key / message id) may be PII: it never appears in error messages
|
|
33
|
+
* of this module (only namespace, group and operation).
|
|
34
|
+
*/
|
|
35
|
+
import { createClient, } from 'redis';
|
|
36
|
+
import { DedupUnavailableError } from '../errors.js';
|
|
37
|
+
import * as naming from '../naming.js';
|
|
38
|
+
import { formatSeconds } from '../util.js';
|
|
39
|
+
export { DedupUnavailableError } from '../errors.js';
|
|
40
|
+
const KEY_SEGMENT = 'sdk-messaging:dedup';
|
|
41
|
+
/** Default TTL of a mark: 7 days. */
|
|
42
|
+
export const DEFAULT_TTL_MS = 604_800_000;
|
|
43
|
+
/** Default timeout of each Redis operation (and of the connection created by `fromUrl`). */
|
|
44
|
+
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
45
|
+
/** Fixed text (§8.2, same as Python): the URL never appears in the error, it may carry the password. */
|
|
46
|
+
const INVALID_URL_MESSAGE = 'invalid Redis URL: expected redis[s]://[[user]:password@]host[:port][/db] or unix://;' +
|
|
47
|
+
' percent-encode reserved characters (/ # ? @ :) in the password' +
|
|
48
|
+
' (the URL is not shown: it may contain a password)';
|
|
49
|
+
function requirePositiveFinite(value, field) {
|
|
50
|
+
if (typeof value !== 'number') {
|
|
51
|
+
throw new TypeError(`'${field}' must be a number; got ${String(value)}`);
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
54
|
+
throw new RangeError(`'${field}' must be finite and > 0; got ${String(value)}`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/** `Error` class name (node-redis errors do not all set `name`), never the message. */
|
|
59
|
+
function errorClassName(error) {
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
const ctor = error.constructor;
|
|
62
|
+
const name = typeof ctor?.name === 'string' && ctor.name !== '' ? ctor.name : error.name;
|
|
63
|
+
return name || 'Error';
|
|
64
|
+
}
|
|
65
|
+
return 'Error';
|
|
66
|
+
}
|
|
67
|
+
const TIMED_OUT = Symbol('timed out');
|
|
68
|
+
/** Module-private members shared by the store and its checkers. */
|
|
69
|
+
const CALL = Symbol('call');
|
|
70
|
+
const TTL_SECONDS = Symbol('ttlSeconds');
|
|
71
|
+
/**
|
|
72
|
+
* Owner of the configuration (namespace, TTL, timeout) and of the Redis client. One store per
|
|
73
|
+
* process/system; one {@link RedisDuplicateChecker} per consumer group (`checker(group)`), all
|
|
74
|
+
* sharing the same client.
|
|
75
|
+
*/
|
|
76
|
+
export class RedisDedupStore {
|
|
77
|
+
#client;
|
|
78
|
+
#owned = null;
|
|
79
|
+
#namespace;
|
|
80
|
+
#ttlMs;
|
|
81
|
+
#ttlSeconds;
|
|
82
|
+
#timeoutMs;
|
|
83
|
+
#connecting = null;
|
|
84
|
+
#closed = false;
|
|
85
|
+
/**
|
|
86
|
+
* Wraps a client owned by the caller: `close()` does not close it and the store never connects
|
|
87
|
+
* it (connect it before use).
|
|
88
|
+
*/
|
|
89
|
+
constructor(client, options) {
|
|
90
|
+
this.#namespace = naming.dedupNamespace(options.namespace);
|
|
91
|
+
this.#ttlMs = requirePositiveFinite(options.ttlMs ?? DEFAULT_TTL_MS, 'ttl_ms');
|
|
92
|
+
this.#timeoutMs = requirePositiveFinite(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, 'timeout_ms');
|
|
93
|
+
this.#ttlSeconds = Math.max(1, Math.ceil(this.#ttlMs / 1000));
|
|
94
|
+
this.#client = client;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates a node-redis client for `url` (lazy: it connects on the first operation) and owns it:
|
|
98
|
+
* `close()` closes it. `socket.connectTimeout` defaults to `timeoutMs`; the remaining options
|
|
99
|
+
* are passed to `createClient` and override the defaults.
|
|
100
|
+
*/
|
|
101
|
+
static fromUrl(url, options) {
|
|
102
|
+
// Never let node-redis' own error escape: WHATWG URL errors carry the whole URL (`input`).
|
|
103
|
+
const { namespace, ttlMs, timeoutMs, ...redisOptions } = options;
|
|
104
|
+
// Validate before creating the client (no leaked client on error).
|
|
105
|
+
naming.dedupNamespace(namespace);
|
|
106
|
+
requirePositiveFinite(ttlMs ?? DEFAULT_TTL_MS, 'ttl_ms');
|
|
107
|
+
const timeout = requirePositiveFinite(timeoutMs ?? DEFAULT_TIMEOUT_MS, 'timeout_ms');
|
|
108
|
+
const socket = { connectTimeout: timeout, ...(redisOptions.socket ?? {}) };
|
|
109
|
+
let client;
|
|
110
|
+
try {
|
|
111
|
+
client = createClient({ ...redisOptions, url, socket });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Rethrown below as a new error without `cause`: the original may quote the URL (password).
|
|
115
|
+
}
|
|
116
|
+
if (client === undefined)
|
|
117
|
+
throw new TypeError(INVALID_URL_MESSAGE);
|
|
118
|
+
// node-redis emits 'error' on every failed (re)connection attempt; without a listener the
|
|
119
|
+
// EventEmitter would crash the process. Failures surface as DedupUnavailableError instead.
|
|
120
|
+
client.on('error', () => { });
|
|
121
|
+
const store = new RedisDedupStore(client, {
|
|
122
|
+
namespace,
|
|
123
|
+
...(ttlMs === undefined ? {} : { ttlMs }),
|
|
124
|
+
timeoutMs: timeout,
|
|
125
|
+
});
|
|
126
|
+
store.#owned = client;
|
|
127
|
+
return store;
|
|
128
|
+
}
|
|
129
|
+
get namespace() {
|
|
130
|
+
return this.#namespace;
|
|
131
|
+
}
|
|
132
|
+
get ttlMs() {
|
|
133
|
+
return this.#ttlMs;
|
|
134
|
+
}
|
|
135
|
+
get timeoutMs() {
|
|
136
|
+
return this.#timeoutMs;
|
|
137
|
+
}
|
|
138
|
+
/** Checker of the consumer group `group` (validated by `naming.consumerGroup`). */
|
|
139
|
+
checker(group) {
|
|
140
|
+
return new RedisDuplicateChecker(this, naming.consumerGroup(group));
|
|
141
|
+
}
|
|
142
|
+
/** Health check bounded by `timeoutMs`. Never throws: failure → `false`. */
|
|
143
|
+
async ping() {
|
|
144
|
+
try {
|
|
145
|
+
await this.#bounded(() => this.#client.ping());
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Closes the client only if it was created by {@link fromUrl}. Idempotent. */
|
|
153
|
+
async close() {
|
|
154
|
+
if (this.#closed)
|
|
155
|
+
return;
|
|
156
|
+
this.#closed = true;
|
|
157
|
+
const owned = this.#owned;
|
|
158
|
+
if (owned === null || !owned.isOpen)
|
|
159
|
+
return;
|
|
160
|
+
if (!owned.isReady) {
|
|
161
|
+
// Still (re)connecting: nothing to flush; reject the offline queue right away.
|
|
162
|
+
owned.destroy();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let timer;
|
|
166
|
+
const timedOut = new Promise((resolve) => {
|
|
167
|
+
timer = setTimeout(() => resolve(TIMED_OUT), this.#timeoutMs);
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
// Graceful close waits for the pending replies; bounded, then forced.
|
|
171
|
+
if ((await Promise.race([owned.close(), timedOut])) === TIMED_OUT && owned.isOpen)
|
|
172
|
+
owned.destroy();
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Runs a Redis command for a checker (timeout + error wrapping). */
|
|
179
|
+
async [CALL](operation, group, command) {
|
|
180
|
+
try {
|
|
181
|
+
return await this.#bounded(() => command(this.#client));
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const reason = error === TIMED_OUT ? `timed out after ${formatSeconds(this.#timeoutMs)}s` : errorClassName(error);
|
|
185
|
+
throw new DedupUnavailableError(operation, this.#namespace, group, reason, {
|
|
186
|
+
...(error === TIMED_OUT ? {} : { cause: error }),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** TTL sent to Redis (`EX`): whole seconds, rounded up, minimum 1. */
|
|
191
|
+
get [TTL_SECONDS]() {
|
|
192
|
+
return this.#ttlSeconds;
|
|
193
|
+
}
|
|
194
|
+
/** Runs `fn` (after the lazy connection of an owned client) within `timeoutMs`. */
|
|
195
|
+
async #bounded(fn) {
|
|
196
|
+
let expired = false;
|
|
197
|
+
let timer;
|
|
198
|
+
const timeout = new Promise((_, reject) => {
|
|
199
|
+
timer = setTimeout(() => {
|
|
200
|
+
expired = true;
|
|
201
|
+
reject(TIMED_OUT);
|
|
202
|
+
}, this.#timeoutMs);
|
|
203
|
+
});
|
|
204
|
+
const run = (async () => {
|
|
205
|
+
await this.#ensureConnected();
|
|
206
|
+
// A command issued after the deadline would only pile up in the offline queue.
|
|
207
|
+
if (expired)
|
|
208
|
+
throw TIMED_OUT;
|
|
209
|
+
return fn();
|
|
210
|
+
})();
|
|
211
|
+
run.catch(() => { }); // a late rejection after the timeout won the race is not "unhandled"
|
|
212
|
+
try {
|
|
213
|
+
return await Promise.race([run, timeout]);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
#ensureConnected() {
|
|
220
|
+
const owned = this.#owned;
|
|
221
|
+
if (owned === null || this.#closed || owned.isOpen)
|
|
222
|
+
return this.#connecting ?? Promise.resolve();
|
|
223
|
+
// Not open: first use, or a previous attempt gave up (custom reconnectStrategy).
|
|
224
|
+
const connecting = owned.connect().then(() => undefined, (error) => {
|
|
225
|
+
if (this.#connecting === connecting)
|
|
226
|
+
this.#connecting = null;
|
|
227
|
+
throw error;
|
|
228
|
+
});
|
|
229
|
+
connecting.catch(() => { });
|
|
230
|
+
this.#connecting = connecting;
|
|
231
|
+
return connecting;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* {@link DuplicateChecker} of one group, created by {@link RedisDedupStore.checker}.
|
|
236
|
+
*
|
|
237
|
+
* - `isProcessed`: `EXISTS` of the key.
|
|
238
|
+
* - `markProcessed`: `SET key "1" EX ttl`. Plain SET (no `NX`): a new mark overwrites and renews
|
|
239
|
+
* the TTL. Safe because the consumer only marks after the handler **succeeded**.
|
|
240
|
+
*/
|
|
241
|
+
export class RedisDuplicateChecker {
|
|
242
|
+
#store;
|
|
243
|
+
#group;
|
|
244
|
+
#prefix;
|
|
245
|
+
constructor(store, group) {
|
|
246
|
+
this.#store = store;
|
|
247
|
+
this.#group = naming.consumerGroup(group);
|
|
248
|
+
this.#prefix = `${store.namespace}:${KEY_SEGMENT}:${this.#group}:`;
|
|
249
|
+
}
|
|
250
|
+
get group() {
|
|
251
|
+
return this.#group;
|
|
252
|
+
}
|
|
253
|
+
get namespace() {
|
|
254
|
+
return this.#store.namespace;
|
|
255
|
+
}
|
|
256
|
+
/** Redis key of `dedupKey`: `{namespace}:sdk-messaging:dedup:{group}:{dedupKey}`. */
|
|
257
|
+
keyFor(dedupKey) {
|
|
258
|
+
return this.#prefix + dedupKey;
|
|
259
|
+
}
|
|
260
|
+
async isProcessed(key) {
|
|
261
|
+
const count = await this.#store[CALL]('is_processed', this.#group, (client) => client.exists(this.keyFor(key)));
|
|
262
|
+
return Number(count) > 0;
|
|
263
|
+
}
|
|
264
|
+
async markProcessed(key) {
|
|
265
|
+
const ttl = this.#store[TTL_SECONDS];
|
|
266
|
+
await this.#store[CALL]('mark_processed', this.#group, (client) => client.set(this.keyFor(key), '1', { expiration: { type: 'EX', value: ttl } }));
|
|
267
|
+
}
|
|
268
|
+
toString() {
|
|
269
|
+
return `RedisDuplicateChecker(namespace=${this.namespace}, group=${this.#group})`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/integrations/redis.ts"],"names":[],"mappings":"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"}
|