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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +218 -0
  3. package/dist/admin.d.ts +61 -0
  4. package/dist/admin.js +143 -0
  5. package/dist/admin.js.map +1 -0
  6. package/dist/config.d.ts +74 -0
  7. package/dist/config.js +128 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/consumer-client.d.ts +155 -0
  10. package/dist/consumer-client.js +299 -0
  11. package/dist/consumer-client.js.map +1 -0
  12. package/dist/consumer.d.ts +156 -0
  13. package/dist/consumer.js +1228 -0
  14. package/dist/consumer.js.map +1 -0
  15. package/dist/contracts/index.d.ts +16 -0
  16. package/dist/contracts/index.js +42 -0
  17. package/dist/contracts/index.js.map +1 -0
  18. package/dist/contracts/schemas.d.ts +570 -0
  19. package/dist/contracts/schemas.js +902 -0
  20. package/dist/contracts/schemas.js.map +1 -0
  21. package/dist/contracts/usuarios.d.ts +163 -0
  22. package/dist/contracts/usuarios.js +5 -0
  23. package/dist/contracts/usuarios.js.map +1 -0
  24. package/dist/dedup.d.ts +29 -0
  25. package/dist/dedup.js +65 -0
  26. package/dist/dedup.js.map +1 -0
  27. package/dist/dlq.d.ts +161 -0
  28. package/dist/dlq.js +341 -0
  29. package/dist/dlq.js.map +1 -0
  30. package/dist/errors.d.ts +72 -0
  31. package/dist/errors.js +95 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/headers.d.ts +92 -0
  34. package/dist/headers.js +144 -0
  35. package/dist/headers.js.map +1 -0
  36. package/dist/index.d.ts +30 -0
  37. package/dist/index.js +32 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/integrations/redis.d.ts +118 -0
  40. package/dist/integrations/redis.js +272 -0
  41. package/dist/integrations/redis.js.map +1 -0
  42. package/dist/integrations/runtime.d.ts +232 -0
  43. package/dist/integrations/runtime.js +554 -0
  44. package/dist/integrations/runtime.js.map +1 -0
  45. package/dist/logger.d.ts +37 -0
  46. package/dist/logger.js +49 -0
  47. package/dist/logger.js.map +1 -0
  48. package/dist/messaging.d.ts +145 -0
  49. package/dist/messaging.js +164 -0
  50. package/dist/messaging.js.map +1 -0
  51. package/dist/naming.d.ts +64 -0
  52. package/dist/naming.js +167 -0
  53. package/dist/naming.js.map +1 -0
  54. package/dist/payload.d.ts +8 -0
  55. package/dist/payload.js +32 -0
  56. package/dist/payload.js.map +1 -0
  57. package/dist/publisher.d.ts +50 -0
  58. package/dist/publisher.js +199 -0
  59. package/dist/publisher.js.map +1 -0
  60. package/dist/retry.d.ts +24 -0
  61. package/dist/retry.js +42 -0
  62. package/dist/retry.js.map +1 -0
  63. package/dist/schema.d.ts +83 -0
  64. package/dist/schema.js +676 -0
  65. package/dist/schema.js.map +1 -0
  66. package/dist/telemetry.d.ts +52 -0
  67. package/dist/telemetry.js +221 -0
  68. package/dist/telemetry.js.map +1 -0
  69. package/dist/transport.d.ts +130 -0
  70. package/dist/transport.js +283 -0
  71. package/dist/transport.js.map +1 -0
  72. package/dist/types.d.ts +148 -0
  73. package/dist/types.js +35 -0
  74. package/dist/types.js.map +1 -0
  75. package/dist/util.d.ts +38 -0
  76. package/dist/util.js +112 -0
  77. package/dist/util.js.map +1 -0
  78. package/dist/w3c.d.ts +16 -0
  79. package/dist/w3c.js +119 -0
  80. package/dist/w3c.js.map +1 -0
  81. package/package.json +74 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB,uCAAuC;AACvC,MAAM,CAAC,MAAM,UAAU,GAAW,MAAM,CAAC,MAAM,CAAC;IAC9C,KAAK,KAAI,CAAC;IACV,IAAI,KAAI,CAAC;IACT,IAAI,KAAI,CAAC;IACT,KAAK,KAAI,CAAC;CACX,CAAC,CAAC;AAUH,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,UAAU,EAAE,OAAO;QACnB,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;QACpD,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO;KACzC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,QAAmB;IAC5C,MAAM,MAAM,GAAa,QAAQ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAChE,OAAO;QACL,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC;QAC3D,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC;QACzD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC;QACzD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED,IAAI,MAA0B,CAAC;AAE/B,qEAAqE;AACrE,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,UAAU,EAAE,CAAC;IACxB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,GAAG,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,145 @@
1
+ /** Facade: one shared producer, publisher and consumers already wired. */
2
+ import { type AdminFactory } from './admin.js';
3
+ import type { KafkaConfig } from './config.js';
4
+ import { type AutoOffsetReset, Consumer, type ConsumerHandlers } from './consumer.js';
5
+ import type { ConsumerClientFactory } from './consumer-client.js';
6
+ import { type Logger } from './logger.js';
7
+ import { Publisher, type PublishOptions } from './publisher.js';
8
+ import { RetryPolicy } from './retry.js';
9
+ import type { DuplicateChecker, MessageSender, PayloadValidator, PublishResult, SchemaLoader, Telemetry } from './types.js';
10
+ /** A sender with a lifecycle (`KafkaProducerSender` or a test fake). */
11
+ export interface ManagedSender extends MessageSender {
12
+ start(): Promise<void>;
13
+ stop(): Promise<void>;
14
+ }
15
+ /**
16
+ * Anything `Messaging.stop()` must stop before the producer (consumers created through the
17
+ * facade). They use the shared producer to route retries/DLQ while draining.
18
+ */
19
+ export interface StoppableConsumer {
20
+ stop(options?: {
21
+ drainTimeoutMs?: number;
22
+ }): Promise<void>;
23
+ }
24
+ export interface MessagingOptions {
25
+ /** Kebab-case name: `producer` header, default consumer group, `service` metric label. */
26
+ service: string;
27
+ /** `environment` label / `deployment.environment.name`. */
28
+ environment: string;
29
+ /** `FileSystemSchemaLoader`, `InMemorySchemaLoader` or a custom `load(domain, event, version)`. */
30
+ schemaLoader: SchemaLoader;
31
+ /** Default `new OtelTelemetry(service, environment)` (global OTel providers; no-op if unset). */
32
+ telemetry?: Telemetry;
33
+ /** Maximum wait for the broker ack. Default 30 000 ms (ignored when `sender` is given). */
34
+ sendTimeoutMs?: number;
35
+ /** Default `new KafkaProducerSender(kafka, {sendTimeoutMs})`. */
36
+ sender?: ManagedSender;
37
+ /** Default: the SDK default logger. */
38
+ logger?: Logger;
39
+ /** Admin factory used by `ensureTopics` (tests inject fakes). */
40
+ adminFactory?: AdminFactory;
41
+ /**
42
+ * Resource prefix (spec §2.7), e.g. `hmg`/`prd`: first segment of every topic and group id of
43
+ * the facade (publisher, consumers, `ensureTopics`). Use it when environments share a cluster.
44
+ * Default: none.
45
+ */
46
+ resourcePrefix?: string | null;
47
+ }
48
+ export interface MessagingEnsureTopicsOptions {
49
+ /** Consumer group whose retry/DLQ topics are created. Default: the service name. */
50
+ group?: string;
51
+ /** Use the **same** policy as the consumer. Default `new RetryPolicy()`. */
52
+ retryPolicy?: RetryPolicy;
53
+ /** Partitions of the main topic. Default 3. */
54
+ numPartitions?: number;
55
+ /** Partitions of the retry topics. Default: `numPartitions`. */
56
+ retryNumPartitions?: number;
57
+ /** Partitions of the DLQ. Default: `numPartitions`. */
58
+ dlqNumPartitions?: number;
59
+ /** Default 1. */
60
+ replicationFactor?: number;
61
+ /** Configs of every created topic (e.g. `{"max.message.bytes": "1048576"}`). */
62
+ topicConfigs?: Readonly<Record<string, string>>;
63
+ /** Main topic configs, merged over `topicConfigs` (these keys win). */
64
+ mainTopicConfigs?: Readonly<Record<string, string>>;
65
+ /** Retry topic configs, merged over `topicConfigs` (these keys win). */
66
+ retryTopicConfigs?: Readonly<Record<string, string>>;
67
+ /** DLQ configs, merged over `topicConfigs` (these keys win). */
68
+ dlqTopicConfigs?: Readonly<Record<string, string>>;
69
+ }
70
+ /** Options of {@link Messaging.consumer} (spec §17); see `ConsumerOptions` for each one. */
71
+ export interface MessagingConsumerOptions {
72
+ domain: string;
73
+ version: string;
74
+ handlers: ConsumerHandlers;
75
+ /** Default: the service name. */
76
+ group?: string | undefined;
77
+ retryPolicy?: RetryPolicy | undefined;
78
+ duplicateChecker?: DuplicateChecker | null | undefined;
79
+ processingTimeoutMs?: number | null | undefined;
80
+ maxPollRecords?: number | undefined;
81
+ maxPollIntervalMs?: number | undefined;
82
+ autoOffsetReset?: AutoOffsetReset | undefined;
83
+ pollTimeoutMs?: number | undefined;
84
+ routingFailureBackoffMs?: number | undefined;
85
+ consumerFactory?: ConsumerClientFactory | undefined;
86
+ /** Clock in epoch milliseconds. Default `Date.now`. */
87
+ clock?: (() => number) | undefined;
88
+ }
89
+ /**
90
+ * SDK entry point.
91
+ *
92
+ * A single idempotent producer is used to publish and to route retries/DLQ of every consumer
93
+ * created by the facade. `stop()` stops the consumers (draining the in-flight work) and then
94
+ * the producer.
95
+ *
96
+ * Without an explicit `telemetry` it uses {@link OtelTelemetry} with the global OpenTelemetry
97
+ * providers: spans/metrics are emitted if the application configured OpenTelemetry and it is a
98
+ * no-op otherwise.
99
+ */
100
+ export declare class Messaging {
101
+ #private;
102
+ readonly kafka: KafkaConfig;
103
+ readonly service: string;
104
+ readonly environment: string;
105
+ readonly telemetry: Telemetry;
106
+ /** Payload validator shared by publisher and consumers (schema cache per process). */
107
+ readonly registry: PayloadValidator;
108
+ /** The shared producer (publish + retry/DLQ routing). */
109
+ readonly sender: ManagedSender;
110
+ readonly publisher: Publisher;
111
+ /** Resource prefix applied to topics and group ids (`null` = none). */
112
+ readonly resourcePrefix: string | null;
113
+ /** Consumers created by the facade; `stop()` stops them before the producer. */
114
+ protected readonly consumers: StoppableConsumer[];
115
+ constructor(kafka: KafkaConfig, options: MessagingOptions);
116
+ /** The logger used by the facade and the components it creates. */
117
+ get logger(): Logger;
118
+ start(): Promise<void>;
119
+ /**
120
+ * Stops the consumers (in parallel, draining up to `drainTimeoutMs`) and then the producer,
121
+ * which they use to send retries/DLQ while draining. The first consumer failure is rethrown
122
+ * after the producer is stopped.
123
+ */
124
+ stop(options?: {
125
+ drainTimeoutMs?: number;
126
+ }): Promise<void>;
127
+ /** Shortcut for {@link Publisher.publish}. */
128
+ publish(domain: string, aggregate: string, event: string, version: string, payload: Readonly<Record<string, unknown>>, options?: PublishOptions): Promise<PublishResult>;
129
+ /**
130
+ * Creates (without starting) a {@link Consumer} bound to this facade: shared producer for
131
+ * retry/DLQ, the schema registry, the telemetry and the logger. `group` defaults to the service
132
+ * name. `stop()` stops it before the producer.
133
+ */
134
+ consumer(options: MessagingConsumerOptions): Consumer;
135
+ /**
136
+ * Creates (if missing) the domain topic and the retry and DLQ topics of the group (default
137
+ * = service name, like the consumer), with the facade's resource prefix. For environments where
138
+ * the topology does not come from IaC. Returns the topics created (spec §2.6).
139
+ *
140
+ * `numPartitions` applies to the main topic; `retryNumPartitions` / `dlqNumPartitions` (default
141
+ * `numPartitions`) to retry / DLQ. `topicConfigs` applies to all; `mainTopicConfigs` /
142
+ * `retryTopicConfigs` / `dlqTopicConfigs` are merged over it. Existing topics are not changed.
143
+ */
144
+ ensureTopics(domain: string, version: string, options?: MessagingEnsureTopicsOptions): Promise<string[]>;
145
+ }
@@ -0,0 +1,164 @@
1
+ /** Facade: one shared producer, publisher and consumers already wired. */
2
+ import { createTopics } from './admin.js';
3
+ import { Consumer } from './consumer.js';
4
+ import { defaultLogger } from './logger.js';
5
+ import * as naming from './naming.js';
6
+ import { Publisher } from './publisher.js';
7
+ import { RetryPolicy } from './retry.js';
8
+ import { SchemaRegistry } from './schema.js';
9
+ import { OtelTelemetry } from './telemetry.js';
10
+ import { KafkaProducerSender } from './transport.js';
11
+ const DEFAULT_RETRY_POLICY = new RetryPolicy();
12
+ /**
13
+ * SDK entry point.
14
+ *
15
+ * A single idempotent producer is used to publish and to route retries/DLQ of every consumer
16
+ * created by the facade. `stop()` stops the consumers (draining the in-flight work) and then
17
+ * the producer.
18
+ *
19
+ * Without an explicit `telemetry` it uses {@link OtelTelemetry} with the global OpenTelemetry
20
+ * providers: spans/metrics are emitted if the application configured OpenTelemetry and it is a
21
+ * no-op otherwise.
22
+ */
23
+ export class Messaging {
24
+ kafka;
25
+ service;
26
+ environment;
27
+ telemetry;
28
+ /** Payload validator shared by publisher and consumers (schema cache per process). */
29
+ registry;
30
+ /** The shared producer (publish + retry/DLQ routing). */
31
+ sender;
32
+ publisher;
33
+ /** Resource prefix applied to topics and group ids (`null` = none). */
34
+ resourcePrefix;
35
+ #logger;
36
+ #adminFactory;
37
+ /** Consumers created by the facade; `stop()` stops them before the producer. */
38
+ consumers = [];
39
+ constructor(kafka, options) {
40
+ this.kafka = kafka;
41
+ this.service = naming.serviceName(options.service);
42
+ this.resourcePrefix = naming.resourcePrefix(options.resourcePrefix);
43
+ this.environment = options.environment;
44
+ this.telemetry = options.telemetry ?? new OtelTelemetry(options.service, options.environment);
45
+ this.#logger = options.logger;
46
+ this.#adminFactory = options.adminFactory;
47
+ this.registry = new SchemaRegistry(options.schemaLoader);
48
+ this.sender =
49
+ options.sender ??
50
+ new KafkaProducerSender(kafka, {
51
+ ...(options.sendTimeoutMs === undefined ? {} : { sendTimeoutMs: options.sendTimeoutMs }),
52
+ ...(options.logger === undefined ? {} : { logger: options.logger }),
53
+ });
54
+ this.publisher = new Publisher(this.sender, this.registry, {
55
+ producer: this.service,
56
+ telemetry: this.telemetry,
57
+ logger: options.logger,
58
+ resourcePrefix: this.resourcePrefix,
59
+ });
60
+ }
61
+ /** The logger used by the facade and the components it creates. */
62
+ get logger() {
63
+ return this.#logger ?? defaultLogger();
64
+ }
65
+ async start() {
66
+ await this.sender.start();
67
+ }
68
+ /**
69
+ * Stops the consumers (in parallel, draining up to `drainTimeoutMs`) and then the producer,
70
+ * which they use to send retries/DLQ while draining. The first consumer failure is rethrown
71
+ * after the producer is stopped.
72
+ */
73
+ async stop(options = {}) {
74
+ const drainTimeoutMs = options.drainTimeoutMs ?? 30_000;
75
+ try {
76
+ const results = await Promise.allSettled(this.consumers.map((consumer) => consumer.stop({ drainTimeoutMs })));
77
+ const failure = results.find((result) => result.status === 'rejected');
78
+ if (failure !== undefined)
79
+ throw failure.reason;
80
+ }
81
+ finally {
82
+ await this.sender.stop();
83
+ }
84
+ }
85
+ /** Shortcut for {@link Publisher.publish}. */
86
+ publish(domain, aggregate, event, version, payload, options = {}) {
87
+ return this.publisher.publish(domain, aggregate, event, version, payload, options);
88
+ }
89
+ /**
90
+ * Creates (without starting) a {@link Consumer} bound to this facade: shared producer for
91
+ * retry/DLQ, the schema registry, the telemetry and the logger. `group` defaults to the service
92
+ * name. `stop()` stops it before the producer.
93
+ */
94
+ consumer(options) {
95
+ const consumer = new Consumer(this.kafka, {
96
+ group: options.group || this.service,
97
+ domain: options.domain,
98
+ version: options.version,
99
+ handlers: options.handlers,
100
+ validator: this.registry,
101
+ sender: this.sender,
102
+ retryPolicy: options.retryPolicy,
103
+ duplicateChecker: options.duplicateChecker,
104
+ telemetry: this.telemetry,
105
+ processingTimeoutMs: options.processingTimeoutMs,
106
+ maxPollRecords: options.maxPollRecords,
107
+ maxPollIntervalMs: options.maxPollIntervalMs,
108
+ autoOffsetReset: options.autoOffsetReset,
109
+ pollTimeoutMs: options.pollTimeoutMs,
110
+ routingFailureBackoffMs: options.routingFailureBackoffMs,
111
+ consumerFactory: options.consumerFactory,
112
+ clock: options.clock,
113
+ logger: this.#logger,
114
+ resourcePrefix: this.resourcePrefix,
115
+ });
116
+ this.consumers.push(consumer);
117
+ return consumer;
118
+ }
119
+ /**
120
+ * Creates (if missing) the domain topic and the retry and DLQ topics of the group (default
121
+ * = service name, like the consumer), with the facade's resource prefix. For environments where
122
+ * the topology does not come from IaC. Returns the topics created (spec §2.6).
123
+ *
124
+ * `numPartitions` applies to the main topic; `retryNumPartitions` / `dlqNumPartitions` (default
125
+ * `numPartitions`) to retry / DLQ. `topicConfigs` applies to all; `mainTopicConfigs` /
126
+ * `retryTopicConfigs` / `dlqTopicConfigs` are merged over it. Existing topics are not changed.
127
+ */
128
+ async ensureTopics(domain, version, options = {}) {
129
+ const numPartitions = options.numPartitions ?? 3;
130
+ for (const [name, value] of [
131
+ ['num_partitions', numPartitions],
132
+ ['retry_num_partitions', options.retryNumPartitions],
133
+ ['dlq_num_partitions', options.dlqNumPartitions],
134
+ ]) {
135
+ if (value !== undefined && (typeof value !== 'number' || !Number.isInteger(value) || value < 1)) {
136
+ throw new RangeError(`'${name}' must be >= 1; got ${String(value)}`);
137
+ }
138
+ }
139
+ const main = naming.topic(domain, version, { resourcePrefix: this.resourcePrefix });
140
+ // Empty or absent → service (like consumer() and Python's `group or service`).
141
+ const group = options.group || this.service;
142
+ const retryPolicy = options.retryPolicy ?? DEFAULT_RETRY_POLICY;
143
+ const base = options.topicConfigs ?? {};
144
+ const specs = [
145
+ { topic: main, numPartitions, topicConfigs: { ...base, ...options.mainTopicConfigs } },
146
+ ...retryPolicy.retryTopics(main, group).map((topic) => ({
147
+ topic,
148
+ numPartitions: options.retryNumPartitions ?? numPartitions,
149
+ topicConfigs: { ...base, ...options.retryTopicConfigs },
150
+ })),
151
+ {
152
+ topic: naming.dlqTopic(main, group),
153
+ numPartitions: options.dlqNumPartitions ?? numPartitions,
154
+ topicConfigs: { ...base, ...options.dlqTopicConfigs },
155
+ },
156
+ ];
157
+ return createTopics(this.kafka, specs, {
158
+ ...(options.replicationFactor === undefined ? {} : { replicationFactor: options.replicationFactor }),
159
+ ...(this.#adminFactory === undefined ? {} : { adminFactory: this.#adminFactory }),
160
+ ...(this.#logger === undefined ? {} : { logger: this.#logger }),
161
+ });
162
+ }
163
+ }
164
+ //# sourceMappingURL=messaging.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messaging.js","sourceRoot":"","sources":["../src/messaging.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Naming convention (single source). No other module builds names by hand.
3
+ *
4
+ * Main topic: {domain}.events.{version} enrollment.events.v1
5
+ * event_type: {aggregate}.{event} student.enrolled
6
+ * schema slug: {aggregate}-{event} student-enrolled
7
+ * Retry (per group): {topic}.{group}.retry.{label} enrollment.events.v1.learning-worker.retry.30s
8
+ * DLQ (per group): {topic}.{group}.dlq enrollment.events.v1.learning-worker.dlq
9
+ *
10
+ * Resource prefix (optional, environment; spec §2.7): first segment of the main topic and of the
11
+ * group id (`hmg.enrollment.events.v1`, `hmg.learning-worker`). Retry and DLQ derive from the
12
+ * already prefixed main topic and the bare group, so the prefix appears only once.
13
+ *
14
+ * Invalid input throws `TypeError` (Python: `ValueError`) with the same message as Python.
15
+ * Delays are in milliseconds (`Ms`) and must be whole seconds: the label of a retry topic is
16
+ * built from seconds, exactly like Python (`30000` → `30s`, `120000` → `2m`, `3600000` → `1h`).
17
+ */
18
+ /** Kafka limit for topic names. */
19
+ export declare const MAX_TOPIC_LENGTH = 249;
20
+ /** Options of the naming functions that accept the resource prefix (spec §2.7). */
21
+ export interface ResourcePrefixOptions {
22
+ /** Environment prefix (`hmg`, `prd`); `null`/absent = no prefix. */
23
+ resourcePrefix?: string | null | undefined;
24
+ }
25
+ /**
26
+ * Validates the resource prefix (kebab-case); `null`/`undefined` → `null` (no prefix).
27
+ * Invalid → `TypeError` (Python: `ValueError`).
28
+ */
29
+ export declare function resourcePrefix(value: string | null | undefined): string | null;
30
+ /** `{domain}.events.{version}`; with a prefix, `{prefix}.{domain}.events.{version}`. */
31
+ export declare function topic(domain: string, version: string, options?: ResourcePrefixOptions): string;
32
+ /** `{aggregate}.{event}`; `event` is a fact in the past (`student.enrolled`). */
33
+ export declare function eventType(aggregate: string, event: string): string;
34
+ /** Inverse of {@link eventType}; validates the format. */
35
+ export declare function splitEventType(value: string): [aggregate: string, event: string];
36
+ /** `student.enrolled` → `student-enrolled` (directory of the schema). */
37
+ export declare function schemaSlug(eventTypeValue: string): string;
38
+ /** 30000 → '30s', 120000 → '2m', 3600000 → '1h'. Uses the largest exact unit. */
39
+ export declare function delayLabel(delayMs: number): string;
40
+ /** Inverse of {@link delayLabel}: '2m' → 120000 (milliseconds). */
41
+ export declare function parseDelayLabel(label: string): number;
42
+ /**
43
+ * Regex source that matches the main topic and **all** retry topics of the group, including
44
+ * delays that left the `RetryPolicy` (no orphan retries).
45
+ *
46
+ * Same alternatives and anchors as Python's `retry_topic_pattern`; the escaping follows JS
47
+ * (`-` is not escaped), so the string is not byte-identical to Python's `re.escape` output.
48
+ */
49
+ export declare function retryTopicPattern(mainTopic: string, group: string): string;
50
+ /** Delay (ms) of a retry topic of the group, or `null` if it is not one. */
51
+ export declare function retryDelayOf(topicName: string, mainTopic: string, group: string): number | null;
52
+ /** `{main}.{group}.retry.{label}`. */
53
+ export declare function retryTopic(mainTopic: string, group: string, delayMs: number): string;
54
+ /** `{main}.{group}.dlq`. */
55
+ export declare function dlqTopic(mainTopic: string, group: string): string;
56
+ /** Name of the producing service (header `producer`), kebab-case. */
57
+ export declare function serviceName(service: string): string;
58
+ /**
59
+ * Prefix of the system/environment in a shared store (`hmgcatalogo_api`): 1-100 characters among
60
+ * letters, digits, `_`, `.`, `-`, starting with a letter or digit; no `:`.
61
+ */
62
+ export declare function dedupNamespace(namespace: string): string;
63
+ /** Kafka `group.id`: the validated logical name; with a prefix, `{prefix}.{group}`. */
64
+ export declare function consumerGroup(group: string, options?: ResourcePrefixOptions): string;
package/dist/naming.js ADDED
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Naming convention (single source). No other module builds names by hand.
3
+ *
4
+ * Main topic: {domain}.events.{version} enrollment.events.v1
5
+ * event_type: {aggregate}.{event} student.enrolled
6
+ * schema slug: {aggregate}-{event} student-enrolled
7
+ * Retry (per group): {topic}.{group}.retry.{label} enrollment.events.v1.learning-worker.retry.30s
8
+ * DLQ (per group): {topic}.{group}.dlq enrollment.events.v1.learning-worker.dlq
9
+ *
10
+ * Resource prefix (optional, environment; spec §2.7): first segment of the main topic and of the
11
+ * group id (`hmg.enrollment.events.v1`, `hmg.learning-worker`). Retry and DLQ derive from the
12
+ * already prefixed main topic and the bare group, so the prefix appears only once.
13
+ *
14
+ * Invalid input throws `TypeError` (Python: `ValueError`) with the same message as Python.
15
+ * Delays are in milliseconds (`Ms`) and must be whole seconds: the label of a retry topic is
16
+ * built from seconds, exactly like Python (`30000` → `30s`, `120000` → `2m`, `3600000` → `1h`).
17
+ */
18
+ import { pyRepr } from './util.js';
19
+ const KEBAB_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
20
+ const VERSION_RE = /^v[1-9][0-9]*$/;
21
+ const DELAY_LABEL_RE = /^([1-9][0-9]*)([smh])$/;
22
+ const NAMESPACE_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/;
23
+ const UNIT_SECONDS = { s: 1, m: 60, h: 3600 };
24
+ /** Kafka limit for topic names. */
25
+ export const MAX_TOPIC_LENGTH = 249;
26
+ function requireKebab(value, field) {
27
+ if (typeof value !== 'string' || !KEBAB_RE.test(value)) {
28
+ throw new TypeError(`${pyRepr(field)} must be kebab-case (lowercase letters, digits, hyphens); got ${describe(value)}`);
29
+ }
30
+ }
31
+ function requireVersion(value) {
32
+ if (typeof value !== 'string' || !VERSION_RE.test(value)) {
33
+ throw new TypeError(`'version' must look like 'v1', 'v2', ...; got ${describe(value)}`);
34
+ }
35
+ }
36
+ function describe(value) {
37
+ return typeof value === 'string' ? pyRepr(value) : String(value);
38
+ }
39
+ function checkedTopic(name) {
40
+ if (name.length > MAX_TOPIC_LENGTH) {
41
+ throw new TypeError(`topic name exceeds ${MAX_TOPIC_LENGTH} characters: ${pyRepr(name)}`);
42
+ }
43
+ return name;
44
+ }
45
+ /**
46
+ * Validates the resource prefix (kebab-case); `null`/`undefined` → `null` (no prefix).
47
+ * Invalid → `TypeError` (Python: `ValueError`).
48
+ */
49
+ export function resourcePrefix(value) {
50
+ if (value === null || value === undefined)
51
+ return null;
52
+ requireKebab(value, 'resourcePrefix');
53
+ return value;
54
+ }
55
+ function prefixed(name, options) {
56
+ const prefix = resourcePrefix(options?.resourcePrefix);
57
+ return prefix === null ? name : `${prefix}.${name}`;
58
+ }
59
+ /** `{domain}.events.{version}`; with a prefix, `{prefix}.{domain}.events.{version}`. */
60
+ export function topic(domain, version, options) {
61
+ requireKebab(domain, 'domain');
62
+ requireVersion(version);
63
+ return checkedTopic(prefixed(`${domain}.events.${version}`, options));
64
+ }
65
+ /** `{aggregate}.{event}`; `event` is a fact in the past (`student.enrolled`). */
66
+ export function eventType(aggregate, event) {
67
+ requireKebab(aggregate, 'aggregate');
68
+ requireKebab(event, 'event');
69
+ return `${aggregate}.${event}`;
70
+ }
71
+ /** Inverse of {@link eventType}; validates the format. */
72
+ export function splitEventType(value) {
73
+ const index = typeof value === 'string' ? value.indexOf('.') : -1;
74
+ if (index < 0) {
75
+ throw new TypeError(`event_type must be '<aggregate>.<event>'; got ${describe(value)}`);
76
+ }
77
+ const aggregate = value.slice(0, index);
78
+ const event = value.slice(index + 1);
79
+ eventType(aggregate, event);
80
+ return [aggregate, event];
81
+ }
82
+ /** `student.enrolled` → `student-enrolled` (directory of the schema). */
83
+ export function schemaSlug(eventTypeValue) {
84
+ const [aggregate, event] = splitEventType(eventTypeValue);
85
+ return `${aggregate}-${event}`;
86
+ }
87
+ /** 30000 → '30s', 120000 → '2m', 3600000 → '1h'. Uses the largest exact unit. */
88
+ export function delayLabel(delayMs) {
89
+ if (typeof delayMs !== 'number' ||
90
+ !Number.isFinite(delayMs) ||
91
+ delayMs <= 0 ||
92
+ !Number.isInteger(delayMs / 1000)) {
93
+ throw new TypeError(`delay must be a positive whole number of seconds; got ${String(delayMs)}ms`);
94
+ }
95
+ const seconds = delayMs / 1000;
96
+ if (seconds % 3600 === 0)
97
+ return `${seconds / 3600}h`;
98
+ if (seconds % 60 === 0)
99
+ return `${seconds / 60}m`;
100
+ return `${seconds}s`;
101
+ }
102
+ /** Inverse of {@link delayLabel}: '2m' → 120000 (milliseconds). */
103
+ export function parseDelayLabel(label) {
104
+ const match = typeof label === 'string' ? DELAY_LABEL_RE.exec(label) : null;
105
+ if (match === null)
106
+ throw new TypeError(`invalid delay label ${describe(label)}`);
107
+ return Number(match[1]) * (UNIT_SECONDS[match[2] ?? 's'] ?? 1) * 1000;
108
+ }
109
+ function escapeRegExp(value) {
110
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
111
+ }
112
+ /**
113
+ * Regex source that matches the main topic and **all** retry topics of the group, including
114
+ * delays that left the `RetryPolicy` (no orphan retries).
115
+ *
116
+ * Same alternatives and anchors as Python's `retry_topic_pattern`; the escaping follows JS
117
+ * (`-` is not escaped), so the string is not byte-identical to Python's `re.escape` output.
118
+ */
119
+ export function retryTopicPattern(mainTopic, group) {
120
+ requireKebab(group, 'group');
121
+ const main = escapeRegExp(mainTopic);
122
+ return `^${main}$|^${main}\\.${escapeRegExp(group)}\\.retry\\.[1-9][0-9]*[smh]$`;
123
+ }
124
+ /** Delay (ms) of a retry topic of the group, or `null` if it is not one. */
125
+ export function retryDelayOf(topicName, mainTopic, group) {
126
+ const prefix = `${mainTopic}.${group}.retry.`;
127
+ if (!topicName.startsWith(prefix))
128
+ return null;
129
+ try {
130
+ return parseDelayLabel(topicName.slice(prefix.length));
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ /** `{main}.{group}.retry.{label}`. */
137
+ export function retryTopic(mainTopic, group, delayMs) {
138
+ requireKebab(group, 'group');
139
+ return checkedTopic(`${mainTopic}.${group}.retry.${delayLabel(delayMs)}`);
140
+ }
141
+ /** `{main}.{group}.dlq`. */
142
+ export function dlqTopic(mainTopic, group) {
143
+ requireKebab(group, 'group');
144
+ return checkedTopic(`${mainTopic}.${group}.dlq`);
145
+ }
146
+ /** Name of the producing service (header `producer`), kebab-case. */
147
+ export function serviceName(service) {
148
+ requireKebab(service, 'service');
149
+ return service;
150
+ }
151
+ /**
152
+ * Prefix of the system/environment in a shared store (`hmgcatalogo_api`): 1-100 characters among
153
+ * letters, digits, `_`, `.`, `-`, starting with a letter or digit; no `:`.
154
+ */
155
+ export function dedupNamespace(namespace) {
156
+ if (typeof namespace !== 'string' || !NAMESPACE_RE.test(namespace)) {
157
+ throw new TypeError("'namespace' must be 1-100 chars of letters, digits, '_', '.', '-'" +
158
+ ` starting with a letter or digit (no ':'); got ${describe(namespace)}`);
159
+ }
160
+ return namespace;
161
+ }
162
+ /** Kafka `group.id`: the validated logical name; with a prefix, `{prefix}.{group}`. */
163
+ export function consumerGroup(group, options) {
164
+ requireKebab(group, 'group');
165
+ return prefixed(group, options);
166
+ }
167
+ //# sourceMappingURL=naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.js","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /** Decoding of a consumed record value (spec §3). For the consumer and the DLQ replay. */
2
+ import type { JsonObject } from './types.js';
3
+ /**
4
+ * Value bytes → JSON object. Strict UTF-8 (a single leading BOM is ignored), strict JSON
5
+ * (no NaN/Infinity, no trailing garbage); duplicate keys: the last wins. Failures throw
6
+ * `InvalidPayloadError` with the exact spec messages, never quoting payload bytes.
7
+ */
8
+ export declare function decodePayload(value: Uint8Array | null | undefined): JsonObject;
@@ -0,0 +1,32 @@
1
+ /** Decoding of a consumed record value (spec §3). For the consumer and the DLQ replay. */
2
+ import { InvalidPayloadError } from './errors.js';
3
+ const strictUtf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: false });
4
+ function jsonTypeName(value) {
5
+ if (value === null)
6
+ return 'null';
7
+ if (Array.isArray(value))
8
+ return 'array';
9
+ return typeof value; // string | number | boolean
10
+ }
11
+ /**
12
+ * Value bytes → JSON object. Strict UTF-8 (a single leading BOM is ignored), strict JSON
13
+ * (no NaN/Infinity, no trailing garbage); duplicate keys: the last wins. Failures throw
14
+ * `InvalidPayloadError` with the exact spec messages, never quoting payload bytes.
15
+ */
16
+ export function decodePayload(value) {
17
+ if (value === null || value === undefined) {
18
+ throw new InvalidPayloadError('payload is empty (null record value)');
19
+ }
20
+ let payload;
21
+ try {
22
+ payload = JSON.parse(strictUtf8.decode(value));
23
+ }
24
+ catch {
25
+ throw new InvalidPayloadError('payload is not valid UTF-8 JSON');
26
+ }
27
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
28
+ throw new InvalidPayloadError(`payload must be a JSON object; got ${jsonTypeName(payload)}`);
29
+ }
30
+ return payload;
31
+ }
32
+ //# sourceMappingURL=payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../src/payload.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,UAAU,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AAE/E,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACzC,OAAO,OAAO,KAAK,CAAC,CAAC,4BAA4B;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAoC;IAChE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,MAAM,IAAI,mBAAmB,CAAC,sCAAsC,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAAC,iCAAiC,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,mBAAmB,CAAC,sCAAsC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,OAAqB,CAAC;AAC/B,CAAC"}