@drarzter/kafka-client 0.10.0 → 0.11.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 (57) hide show
  1. package/README.md +70 -2
  2. package/dist/{chunk-CMO7SMVK.mjs → chunk-OR7TPAAE.mjs} +110 -164
  3. package/dist/chunk-OR7TPAAE.mjs.map +1 -0
  4. package/dist/chunk-PQVBRDNV.mjs +149 -0
  5. package/dist/chunk-PQVBRDNV.mjs.map +1 -0
  6. package/dist/cli/index.js +115 -51
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/index.mjs +2 -1
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/client/kafka.client/consumer/features/delayed.d.ts.map +1 -1
  11. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts +1 -1
  12. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts.map +1 -1
  13. package/dist/client/kafka.client/consumer/features/snapshot.d.ts.map +1 -1
  14. package/dist/client/kafka.client/consumer/handler.d.ts +16 -2
  15. package/dist/client/kafka.client/consumer/handler.d.ts.map +1 -1
  16. package/dist/client/kafka.client/consumer/ops.d.ts +13 -0
  17. package/dist/client/kafka.client/consumer/ops.d.ts.map +1 -1
  18. package/dist/client/kafka.client/consumer/pipeline.d.ts +14 -13
  19. package/dist/client/kafka.client/consumer/pipeline.d.ts.map +1 -1
  20. package/dist/client/kafka.client/consumer/retry-topic.d.ts +4 -1
  21. package/dist/client/kafka.client/consumer/retry-topic.d.ts.map +1 -1
  22. package/dist/client/kafka.client/consumer/setup.d.ts +3 -0
  23. package/dist/client/kafka.client/consumer/setup.d.ts.map +1 -1
  24. package/dist/client/kafka.client/consumer/start.d.ts.map +1 -1
  25. package/dist/client/kafka.client/context.d.ts +3 -0
  26. package/dist/client/kafka.client/context.d.ts.map +1 -1
  27. package/dist/client/kafka.client/index.d.ts.map +1 -1
  28. package/dist/client/kafka.client/producer/ops.d.ts +12 -3
  29. package/dist/client/kafka.client/producer/ops.d.ts.map +1 -1
  30. package/dist/client/kafka.client/producer/send.d.ts +1 -1
  31. package/dist/client/message/schema-registry.d.ts +23 -4
  32. package/dist/client/message/schema-registry.d.ts.map +1 -1
  33. package/dist/client/message/serde.d.ts +68 -0
  34. package/dist/client/message/serde.d.ts.map +1 -0
  35. package/dist/client/message/topic.d.ts +25 -4
  36. package/dist/client/message/topic.d.ts.map +1 -1
  37. package/dist/client/transport/transport.interface.d.ts +6 -1
  38. package/dist/client/transport/transport.interface.d.ts.map +1 -1
  39. package/dist/client/types/config.types.d.ts +17 -0
  40. package/dist/client/types/config.types.d.ts.map +1 -1
  41. package/dist/core.d.ts +3 -0
  42. package/dist/core.d.ts.map +1 -1
  43. package/dist/core.js +146 -55
  44. package/dist/core.js.map +1 -1
  45. package/dist/core.mjs +9 -3
  46. package/dist/index.js +146 -55
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +9 -3
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/serde.d.ts +157 -0
  51. package/dist/serde.d.ts.map +1 -0
  52. package/dist/serde.js +308 -0
  53. package/dist/serde.js.map +1 -0
  54. package/dist/serde.mjs +158 -0
  55. package/dist/serde.mjs.map +1 -0
  56. package/package.json +20 -1
  57. package/dist/chunk-CMO7SMVK.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ ConfluentTransport,
2
3
  HEADER_CORRELATION_ID,
3
4
  HEADER_DELAYED_TARGET,
4
5
  HEADER_DELAYED_UNTIL,
@@ -13,7 +14,6 @@ import {
13
14
  KafkaProcessingError,
14
15
  KafkaRetryExhaustedError,
15
16
  KafkaValidationError,
16
- SchemaRegistryClient,
17
17
  awsMskIamProvider,
18
18
  buildEnvelopeHeaders,
19
19
  consumerOptionsFromEnv,
@@ -24,7 +24,6 @@ import {
24
24
  getEnvelopeContext,
25
25
  kafkaClientConfigFromEnv,
26
26
  mergeConsumerOptions,
27
- registrySchema,
28
27
  resolveSecurityOptions,
29
28
  runWithEnvelopeContext,
30
29
  startOutboxRelay,
@@ -33,7 +32,12 @@ import {
33
32
  toMskIamPolicy,
34
33
  topic,
35
34
  versionedSchema
36
- } from "./chunk-CMO7SMVK.mjs";
35
+ } from "./chunk-OR7TPAAE.mjs";
36
+ import {
37
+ JsonSerde,
38
+ SchemaRegistryClient,
39
+ registrySchema
40
+ } from "./chunk-PQVBRDNV.mjs";
37
41
  import {
38
42
  __decorateClass,
39
43
  __decorateParam
@@ -238,6 +242,7 @@ KafkaHealthIndicator = __decorateClass([
238
242
  Injectable2()
239
243
  ], KafkaHealthIndicator);
240
244
  export {
245
+ ConfluentTransport,
241
246
  HEADER_CORRELATION_ID,
242
247
  HEADER_DELAYED_TARGET,
243
248
  HEADER_DELAYED_UNTIL,
@@ -249,6 +254,7 @@ export {
249
254
  InMemoryDedupStore,
250
255
  InMemoryOutboxStore,
251
256
  InjectKafkaClient,
257
+ JsonSerde,
252
258
  KAFKA_CLIENT,
253
259
  KAFKA_SUBSCRIBER_METADATA,
254
260
  KafkaClient,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/nest/kafka.module.ts","../src/nest/kafka.constants.ts","../src/nest/kafka.explorer.ts","../src/nest/kafka.decorator.ts","../src/nest/kafka.health.ts"],"sourcesContent":["import { Module, DynamicModule, Provider, Logger } from \"@nestjs/common\";\nimport { DiscoveryModule } from \"@nestjs/core\";\nimport {\n KafkaClient,\n ClientId,\n GroupId,\n TopicMapConstraint,\n KafkaInstrumentation,\n KafkaClientOptions,\n} from \"../client/kafka.client\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { KafkaExplorer } from \"./kafka.explorer\";\n\n/** Shared configuration fields for both `register()` and `registerAsync()`. */\ninterface KafkaModuleBaseOptions {\n /** Optional name for multi-client setups. Must match `@InjectKafkaClient(name)`. */\n name?: string;\n /** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */\n isGlobal?: boolean;\n}\n\n/** Synchronous configuration for `KafkaModule.register()`. */\nexport interface KafkaModuleOptions extends KafkaModuleBaseOptions {\n /** Unique Kafka client identifier. */\n clientId: ClientId;\n /** Consumer group identifier. */\n groupId: GroupId;\n /** List of Kafka broker addresses. */\n brokers: string[];\n /** Auto-create topics via admin on first use (send/consume). Useful for development. */\n autoCreateTopics?: boolean;\n /** When `true`, string topic keys are validated against any schema previously registered via a TopicDescriptor. Default: `true`. */\n strictSchemas?: boolean;\n /** Number of partitions for auto-created topics. Default: `1`. */\n numPartitions?: number;\n /** Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths. */\n instrumentation?: KafkaInstrumentation[];\n /** Called when a message is dropped without being sent to a DLQ. @see `KafkaClientOptions.onMessageLost` */\n onMessageLost?: KafkaClientOptions[\"onMessageLost\"];\n /** Called whenever a consumer group rebalance occurs. @see `KafkaClientOptions.onRebalance` */\n onRebalance?: KafkaClientOptions[\"onRebalance\"];\n /** Transactional producer ID — must be unique per process/replica. @see `KafkaClientOptions.transactionalId` */\n transactionalId?: KafkaClientOptions[\"transactionalId\"];\n /** Recover the Lamport clock from these topics on startup. @see `KafkaClientOptions.clockRecovery` */\n clockRecovery?: KafkaClientOptions[\"clockRecovery\"];\n /** Delay producer sends when consumer lag exceeds a threshold. @see `KafkaClientOptions.lagThrottle` */\n lagThrottle?: KafkaClientOptions[\"lagThrottle\"];\n /** Client-wide TTL expiry callback. @see `KafkaClientOptions.onTtlExpired` */\n onTtlExpired?: KafkaClientOptions[\"onTtlExpired\"];\n /** Custom transport implementation (e.g. `FakeTransport` in tests). @see `KafkaClientOptions.transport` */\n transport?: KafkaClientOptions[\"transport\"];\n /** Transport security (TLS + SASL, incl. MSK IAM / GCP OAUTHBEARER). @see `KafkaClientOptions.security` */\n security?: KafkaClientOptions[\"security\"];\n}\n\n/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */\nexport interface KafkaModuleAsyncOptions extends KafkaModuleBaseOptions {\n imports?: any[];\n useFactory: (\n ...args: any[]\n ) => KafkaModuleOptions | Promise<KafkaModuleOptions>;\n inject?: any[];\n}\n\n/**\n * NestJS dynamic module for registering type-safe Kafka clients.\n * Use `register()` for static config or `registerAsync()` for DI-based config.\n */\n@Module({})\nexport class KafkaModule {\n /** Register a Kafka client with static options. */\n static register<T extends TopicMapConstraint<T>>(\n options: KafkaModuleOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(options.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: () => KafkaModule.buildClient<T>(options),\n };\n\n return {\n global: options.isGlobal ?? false,\n module: KafkaModule,\n imports: [DiscoveryModule],\n providers: [kafkaClientProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n\n /** Register a Kafka client with async/factory-based options. */\n static registerAsync<T extends TopicMapConstraint<T>>(\n asyncOptions: KafkaModuleAsyncOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(asyncOptions.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: async (...args: any[]): Promise<KafkaClient<T>> =>\n KafkaModule.buildClient<T>(await asyncOptions.useFactory(...args)),\n inject: asyncOptions.inject || [],\n };\n\n return {\n global: asyncOptions.isGlobal ?? false,\n module: KafkaModule,\n imports: [...(asyncOptions.imports || []), DiscoveryModule],\n providers: [kafkaClientProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n\n private static async buildClient<T extends TopicMapConstraint<T>>(\n options: KafkaModuleOptions,\n ): Promise<KafkaClient<T>> {\n const client = new KafkaClient<T>(\n options.clientId,\n options.groupId,\n options.brokers,\n {\n autoCreateTopics: options.autoCreateTopics,\n strictSchemas: options.strictSchemas,\n numPartitions: options.numPartitions,\n instrumentation: options.instrumentation,\n onMessageLost: options.onMessageLost,\n onRebalance: options.onRebalance,\n transactionalId: options.transactionalId,\n clockRecovery: options.clockRecovery,\n lagThrottle: options.lagThrottle,\n onTtlExpired: options.onTtlExpired,\n transport: options.transport,\n security: options.security,\n logger: new Logger(`KafkaClient:${options.clientId}`),\n },\n );\n await client.connectProducer();\n return client;\n }\n}\n","/** Default DI token for the Kafka client. */\nexport const KAFKA_CLIENT = \"KAFKA_CLIENT\";\n\n/** Returns the DI token for a named (or default) Kafka client instance. */\nexport const getKafkaClientToken = (name?: string): string =>\n name ? `KAFKA_CLIENT_${name}` : KAFKA_CLIENT;\n","import { Inject, Injectable, OnModuleInit, Logger } from \"@nestjs/common\";\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { KafkaClient } from \"../client/kafka.client\";\nimport {\n KAFKA_SUBSCRIBER_METADATA,\n KafkaSubscriberMetadata,\n} from \"./kafka.decorator\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\n\ninterface SubscriberEntry extends KafkaSubscriberMetadata {\n methodName: string | symbol;\n}\n\n/**\n * Process-level registry of already-wired subscriptions, keyed by provider\n * instance. Every `KafkaModule.register()` call contributes its own\n * `KafkaExplorer`, and each explorer scans ALL providers — without this guard\n * a multi-client app would wire every `@SubscribeTo` handler once per\n * registered module (duplicate consumers / \"called twice\" startup errors).\n * Keyed by instance (not constructor) so separate Nest apps in one process\n * (e.g. tests) still wire their own instances independently.\n */\nconst wiredSubscriptions = new WeakMap<object, Set<string>>();\n\n/** Discovers `@SubscribeTo()` decorators and wires them to their Kafka clients on startup. */\n@Injectable()\nexport class KafkaExplorer implements OnModuleInit {\n private readonly logger = new Logger(KafkaExplorer.name);\n\n constructor(\n @Inject(DiscoveryService)\n private readonly discoveryService: DiscoveryService,\n @Inject(ModuleRef)\n private readonly moduleRef: ModuleRef,\n ) {}\n\n /**\n * Scan all NestJS providers for `@SubscribeTo()` metadata and wire each decorated\n * method to its Kafka client via `startConsumer` or `startBatchConsumer`.\n *\n * Called automatically by the NestJS lifecycle — do not invoke manually.\n */\n async onModuleInit() {\n const providers = this.discoveryService.getProviders();\n\n for (const wrapper of providers) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== \"object\") continue;\n\n const metadata: SubscriberEntry[] | undefined = Reflect.getMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n instance.constructor,\n );\n\n if (!metadata || metadata.length === 0) continue;\n\n for (const entry of metadata) {\n const token = getKafkaClientToken(entry.clientName);\n\n const entryKey = `${token}:${String(entry.methodName)}`;\n let wired = wiredSubscriptions.get(instance);\n if (!wired) {\n wired = new Set();\n wiredSubscriptions.set(instance, wired);\n }\n if (wired.has(entryKey)) continue; // already wired by another KafkaExplorer instance\n wired.add(entryKey);\n\n let client: KafkaClient<any>;\n\n try {\n client = this.moduleRef.get(token, { strict: false });\n } catch {\n this.logger.error(\n `KafkaClient \"${entry.clientName || \"default\"}\" not found for @SubscribeTo on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n continue;\n }\n\n const handler = (instance as any)[entry.methodName].bind(instance);\n\n const consumerOptions = { ...entry.options };\n if (entry.schemas) {\n consumerOptions.schemas = entry.schemas;\n }\n\n if (entry.batch) {\n await client.startBatchConsumer(\n entry.topics as any,\n async (envelopes: any[], meta: any) => {\n await handler(envelopes, meta);\n },\n consumerOptions,\n );\n } else {\n await client.startConsumer(\n entry.topics as any,\n async (envelope: any) => {\n await handler(envelope);\n },\n consumerOptions,\n );\n }\n\n this.logger.log(\n `Registered @SubscribeTo(${entry.topics.join(\", \")})${entry.batch ? \" [batch]\" : \"\"} on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n }\n }\n }\n}\n","import { Inject } from \"@nestjs/common\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { ConsumerOptions } from \"../client/kafka.client\";\nimport { TopicDescriptor, SchemaLike } from \"../client/message/topic\";\n\n/** Reflect metadata key used to store `@SubscribeTo` entries on a class constructor. */\nexport const KAFKA_SUBSCRIBER_METADATA = \"KAFKA_SUBSCRIBER_METADATA\";\n\n/** Internal shape stored per `@SubscribeTo()` decoration on a class. */\nexport interface KafkaSubscriberMetadata {\n /** Resolved topic name strings (descriptors are unwrapped to their `__topic` string). */\n topics: string[];\n /** Per-topic schema validators extracted from `TopicDescriptor` objects (if any). */\n schemas?: Map<string, SchemaLike>;\n /** Additional consumer options forwarded to `startConsumer` / `startBatchConsumer`. */\n options?: ConsumerOptions;\n /** Named client identifier — resolves to `KAFKA_CLIENT_<clientName>` in the DI container. */\n clientName?: string;\n /** When `true`, routes to `startBatchConsumer` instead of `startConsumer`. */\n batch?: boolean;\n /** Name of the decorated method on the provider class. */\n methodName?: string | symbol;\n}\n\n/** Inject a `KafkaClient` instance. Pass a name to target a specific named client. */\nexport const InjectKafkaClient = (name?: string): ParameterDecorator =>\n Inject(getKafkaClientToken(name));\n\n/**\n * Method decorator that auto-subscribes the decorated method to one or more Kafka topics\n * when the NestJS module initialises.\n *\n * The decorated method receives a fully-decoded `EventEnvelope` for each message\n * (or an array of envelopes + `BatchMeta` when `batch: true`).\n *\n * @param topics One or more topic names or `TopicDescriptor` objects. Schemas embedded in\n * descriptors are automatically extracted and forwarded to the consumer.\n * @param options Consumer and routing options:\n * - All `ConsumerOptions` fields (`groupId`, `retry`, `dlq`, `fromBeginning`, …)\n * - `clientName` — target a named `KafkaClient` (resolves `KAFKA_CLIENT_<name>` from the DI container)\n * - `batch` — use `startBatchConsumer` instead of `startConsumer`\n *\n * @example\n * ```ts\n * @SubscribeTo('orders.created', { groupId: 'orders-svc', retry: { maxRetries: 3 } })\n * async handleOrder(envelope: EventEnvelope<Order>) { ... }\n *\n * @SubscribeTo(OrdersTopic, { batch: true })\n * async handleBatch(envelopes: EventEnvelope<Order>[], meta: BatchMeta) { ... }\n * ```\n */\nexport const SubscribeTo = (\n topics:\n | string\n | string[]\n | TopicDescriptor\n | TopicDescriptor[]\n | (string | TopicDescriptor)[],\n options?: ConsumerOptions & { clientName?: string; batch?: boolean },\n): MethodDecorator => {\n const arr = Array.isArray(topics) ? topics : [topics];\n const topicsArray = arr.map((t) => (typeof t === \"string\" ? t : t.__topic));\n\n // Extract schemas from descriptors that have them\n const schemas = new Map<string, SchemaLike>();\n for (const t of arr) {\n if (typeof t !== \"string\" && t.__schema) {\n schemas.set(t.__topic, t.__schema);\n }\n }\n\n const { clientName, batch, ...consumerOptions } = options || {};\n\n return (target, propertyKey, _descriptor) => {\n const existing: KafkaSubscriberMetadata[] =\n Reflect.getMetadata(KAFKA_SUBSCRIBER_METADATA, target.constructor) || [];\n\n Reflect.defineMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n [\n ...existing,\n {\n topics: topicsArray,\n schemas: schemas.size > 0 ? schemas : undefined,\n options: Object.keys(consumerOptions).length\n ? consumerOptions\n : undefined,\n clientName,\n batch,\n methodName: propertyKey,\n },\n ],\n target.constructor,\n );\n };\n};\n","import { Injectable } from \"@nestjs/common\";\nimport type {\n IKafkaClient,\n KafkaHealthResult,\n TopicMapConstraint,\n} from \"../client/types\";\nexport type { KafkaHealthResult } from \"../client/types\";\n\n/** Health check service. Call `check(client)` to verify broker connectivity. */\n@Injectable()\nexport class KafkaHealthIndicator {\n async check<T extends TopicMapConstraint<T>>(\n client: IKafkaClient<T>,\n ): Promise<KafkaHealthResult> {\n return client.checkStatus();\n }\n}\n"],"mappings":"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","names":["Logger","Inject","Inject","Logger","Injectable","Injectable"]}
1
+ {"version":3,"sources":["../src/nest/kafka.module.ts","../src/nest/kafka.constants.ts","../src/nest/kafka.explorer.ts","../src/nest/kafka.decorator.ts","../src/nest/kafka.health.ts"],"sourcesContent":["import { Module, DynamicModule, Provider, Logger } from \"@nestjs/common\";\nimport { DiscoveryModule } from \"@nestjs/core\";\nimport {\n KafkaClient,\n ClientId,\n GroupId,\n TopicMapConstraint,\n KafkaInstrumentation,\n KafkaClientOptions,\n} from \"../client/kafka.client\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { KafkaExplorer } from \"./kafka.explorer\";\n\n/** Shared configuration fields for both `register()` and `registerAsync()`. */\ninterface KafkaModuleBaseOptions {\n /** Optional name for multi-client setups. Must match `@InjectKafkaClient(name)`. */\n name?: string;\n /** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */\n isGlobal?: boolean;\n}\n\n/** Synchronous configuration for `KafkaModule.register()`. */\nexport interface KafkaModuleOptions extends KafkaModuleBaseOptions {\n /** Unique Kafka client identifier. */\n clientId: ClientId;\n /** Consumer group identifier. */\n groupId: GroupId;\n /** List of Kafka broker addresses. */\n brokers: string[];\n /** Auto-create topics via admin on first use (send/consume). Useful for development. */\n autoCreateTopics?: boolean;\n /** When `true`, string topic keys are validated against any schema previously registered via a TopicDescriptor. Default: `true`. */\n strictSchemas?: boolean;\n /** Number of partitions for auto-created topics. Default: `1`. */\n numPartitions?: number;\n /** Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths. */\n instrumentation?: KafkaInstrumentation[];\n /** Called when a message is dropped without being sent to a DLQ. @see `KafkaClientOptions.onMessageLost` */\n onMessageLost?: KafkaClientOptions[\"onMessageLost\"];\n /** Called whenever a consumer group rebalance occurs. @see `KafkaClientOptions.onRebalance` */\n onRebalance?: KafkaClientOptions[\"onRebalance\"];\n /** Transactional producer ID — must be unique per process/replica. @see `KafkaClientOptions.transactionalId` */\n transactionalId?: KafkaClientOptions[\"transactionalId\"];\n /** Recover the Lamport clock from these topics on startup. @see `KafkaClientOptions.clockRecovery` */\n clockRecovery?: KafkaClientOptions[\"clockRecovery\"];\n /** Delay producer sends when consumer lag exceeds a threshold. @see `KafkaClientOptions.lagThrottle` */\n lagThrottle?: KafkaClientOptions[\"lagThrottle\"];\n /** Client-wide TTL expiry callback. @see `KafkaClientOptions.onTtlExpired` */\n onTtlExpired?: KafkaClientOptions[\"onTtlExpired\"];\n /** Custom transport implementation (e.g. `FakeTransport` in tests). @see `KafkaClientOptions.transport` */\n transport?: KafkaClientOptions[\"transport\"];\n /** Transport security (TLS + SASL, incl. MSK IAM / GCP OAUTHBEARER). @see `KafkaClientOptions.security` */\n security?: KafkaClientOptions[\"security\"];\n}\n\n/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */\nexport interface KafkaModuleAsyncOptions extends KafkaModuleBaseOptions {\n imports?: any[];\n useFactory: (\n ...args: any[]\n ) => KafkaModuleOptions | Promise<KafkaModuleOptions>;\n inject?: any[];\n}\n\n/**\n * NestJS dynamic module for registering type-safe Kafka clients.\n * Use `register()` for static config or `registerAsync()` for DI-based config.\n */\n@Module({})\nexport class KafkaModule {\n /** Register a Kafka client with static options. */\n static register<T extends TopicMapConstraint<T>>(\n options: KafkaModuleOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(options.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: () => KafkaModule.buildClient<T>(options),\n };\n\n return {\n global: options.isGlobal ?? false,\n module: KafkaModule,\n imports: [DiscoveryModule],\n providers: [kafkaClientProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n\n /** Register a Kafka client with async/factory-based options. */\n static registerAsync<T extends TopicMapConstraint<T>>(\n asyncOptions: KafkaModuleAsyncOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(asyncOptions.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: async (...args: any[]): Promise<KafkaClient<T>> =>\n KafkaModule.buildClient<T>(await asyncOptions.useFactory(...args)),\n inject: asyncOptions.inject || [],\n };\n\n return {\n global: asyncOptions.isGlobal ?? false,\n module: KafkaModule,\n imports: [...(asyncOptions.imports || []), DiscoveryModule],\n providers: [kafkaClientProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n\n private static async buildClient<T extends TopicMapConstraint<T>>(\n options: KafkaModuleOptions,\n ): Promise<KafkaClient<T>> {\n const client = new KafkaClient<T>(\n options.clientId,\n options.groupId,\n options.brokers,\n {\n autoCreateTopics: options.autoCreateTopics,\n strictSchemas: options.strictSchemas,\n numPartitions: options.numPartitions,\n instrumentation: options.instrumentation,\n onMessageLost: options.onMessageLost,\n onRebalance: options.onRebalance,\n transactionalId: options.transactionalId,\n clockRecovery: options.clockRecovery,\n lagThrottle: options.lagThrottle,\n onTtlExpired: options.onTtlExpired,\n transport: options.transport,\n security: options.security,\n logger: new Logger(`KafkaClient:${options.clientId}`),\n },\n );\n await client.connectProducer();\n return client;\n }\n}\n","/** Default DI token for the Kafka client. */\nexport const KAFKA_CLIENT = \"KAFKA_CLIENT\";\n\n/** Returns the DI token for a named (or default) Kafka client instance. */\nexport const getKafkaClientToken = (name?: string): string =>\n name ? `KAFKA_CLIENT_${name}` : KAFKA_CLIENT;\n","import { Inject, Injectable, OnModuleInit, Logger } from \"@nestjs/common\";\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { KafkaClient } from \"../client/kafka.client\";\nimport {\n KAFKA_SUBSCRIBER_METADATA,\n KafkaSubscriberMetadata,\n} from \"./kafka.decorator\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\n\ninterface SubscriberEntry extends KafkaSubscriberMetadata {\n methodName: string | symbol;\n}\n\n/**\n * Process-level registry of already-wired subscriptions, keyed by provider\n * instance. Every `KafkaModule.register()` call contributes its own\n * `KafkaExplorer`, and each explorer scans ALL providers — without this guard\n * a multi-client app would wire every `@SubscribeTo` handler once per\n * registered module (duplicate consumers / \"called twice\" startup errors).\n * Keyed by instance (not constructor) so separate Nest apps in one process\n * (e.g. tests) still wire their own instances independently.\n */\nconst wiredSubscriptions = new WeakMap<object, Set<string>>();\n\n/** Discovers `@SubscribeTo()` decorators and wires them to their Kafka clients on startup. */\n@Injectable()\nexport class KafkaExplorer implements OnModuleInit {\n private readonly logger = new Logger(KafkaExplorer.name);\n\n constructor(\n @Inject(DiscoveryService)\n private readonly discoveryService: DiscoveryService,\n @Inject(ModuleRef)\n private readonly moduleRef: ModuleRef,\n ) {}\n\n /**\n * Scan all NestJS providers for `@SubscribeTo()` metadata and wire each decorated\n * method to its Kafka client via `startConsumer` or `startBatchConsumer`.\n *\n * Called automatically by the NestJS lifecycle — do not invoke manually.\n */\n async onModuleInit() {\n const providers = this.discoveryService.getProviders();\n\n for (const wrapper of providers) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== \"object\") continue;\n\n const metadata: SubscriberEntry[] | undefined = Reflect.getMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n instance.constructor,\n );\n\n if (!metadata || metadata.length === 0) continue;\n\n for (const entry of metadata) {\n const token = getKafkaClientToken(entry.clientName);\n\n const entryKey = `${token}:${String(entry.methodName)}`;\n let wired = wiredSubscriptions.get(instance);\n if (!wired) {\n wired = new Set();\n wiredSubscriptions.set(instance, wired);\n }\n if (wired.has(entryKey)) continue; // already wired by another KafkaExplorer instance\n wired.add(entryKey);\n\n let client: KafkaClient<any>;\n\n try {\n client = this.moduleRef.get(token, { strict: false });\n } catch {\n this.logger.error(\n `KafkaClient \"${entry.clientName || \"default\"}\" not found for @SubscribeTo on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n continue;\n }\n\n const handler = (instance as any)[entry.methodName].bind(instance);\n\n const consumerOptions = { ...entry.options };\n if (entry.schemas) {\n consumerOptions.schemas = entry.schemas;\n }\n\n if (entry.batch) {\n await client.startBatchConsumer(\n entry.topics as any,\n async (envelopes: any[], meta: any) => {\n await handler(envelopes, meta);\n },\n consumerOptions,\n );\n } else {\n await client.startConsumer(\n entry.topics as any,\n async (envelope: any) => {\n await handler(envelope);\n },\n consumerOptions,\n );\n }\n\n this.logger.log(\n `Registered @SubscribeTo(${entry.topics.join(\", \")})${entry.batch ? \" [batch]\" : \"\"} on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n }\n }\n }\n}\n","import { Inject } from \"@nestjs/common\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { ConsumerOptions } from \"../client/kafka.client\";\nimport { TopicDescriptor, SchemaLike } from \"../client/message/topic\";\n\n/** Reflect metadata key used to store `@SubscribeTo` entries on a class constructor. */\nexport const KAFKA_SUBSCRIBER_METADATA = \"KAFKA_SUBSCRIBER_METADATA\";\n\n/** Internal shape stored per `@SubscribeTo()` decoration on a class. */\nexport interface KafkaSubscriberMetadata {\n /** Resolved topic name strings (descriptors are unwrapped to their `__topic` string). */\n topics: string[];\n /** Per-topic schema validators extracted from `TopicDescriptor` objects (if any). */\n schemas?: Map<string, SchemaLike>;\n /** Additional consumer options forwarded to `startConsumer` / `startBatchConsumer`. */\n options?: ConsumerOptions;\n /** Named client identifier — resolves to `KAFKA_CLIENT_<clientName>` in the DI container. */\n clientName?: string;\n /** When `true`, routes to `startBatchConsumer` instead of `startConsumer`. */\n batch?: boolean;\n /** Name of the decorated method on the provider class. */\n methodName?: string | symbol;\n}\n\n/** Inject a `KafkaClient` instance. Pass a name to target a specific named client. */\nexport const InjectKafkaClient = (name?: string): ParameterDecorator =>\n Inject(getKafkaClientToken(name));\n\n/**\n * Method decorator that auto-subscribes the decorated method to one or more Kafka topics\n * when the NestJS module initialises.\n *\n * The decorated method receives a fully-decoded `EventEnvelope` for each message\n * (or an array of envelopes + `BatchMeta` when `batch: true`).\n *\n * @param topics One or more topic names or `TopicDescriptor` objects. Schemas embedded in\n * descriptors are automatically extracted and forwarded to the consumer.\n * @param options Consumer and routing options:\n * - All `ConsumerOptions` fields (`groupId`, `retry`, `dlq`, `fromBeginning`, …)\n * - `clientName` — target a named `KafkaClient` (resolves `KAFKA_CLIENT_<name>` from the DI container)\n * - `batch` — use `startBatchConsumer` instead of `startConsumer`\n *\n * @example\n * ```ts\n * @SubscribeTo('orders.created', { groupId: 'orders-svc', retry: { maxRetries: 3 } })\n * async handleOrder(envelope: EventEnvelope<Order>) { ... }\n *\n * @SubscribeTo(OrdersTopic, { batch: true })\n * async handleBatch(envelopes: EventEnvelope<Order>[], meta: BatchMeta) { ... }\n * ```\n */\nexport const SubscribeTo = (\n topics:\n | string\n | string[]\n | TopicDescriptor\n | TopicDescriptor[]\n | (string | TopicDescriptor)[],\n options?: ConsumerOptions & { clientName?: string; batch?: boolean },\n): MethodDecorator => {\n const arr = Array.isArray(topics) ? topics : [topics];\n const topicsArray = arr.map((t) => (typeof t === \"string\" ? t : t.__topic));\n\n // Extract schemas from descriptors that have them\n const schemas = new Map<string, SchemaLike>();\n for (const t of arr) {\n if (typeof t !== \"string\" && t.__schema) {\n schemas.set(t.__topic, t.__schema);\n }\n }\n\n const { clientName, batch, ...consumerOptions } = options || {};\n\n return (target, propertyKey, _descriptor) => {\n const existing: KafkaSubscriberMetadata[] =\n Reflect.getMetadata(KAFKA_SUBSCRIBER_METADATA, target.constructor) || [];\n\n Reflect.defineMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n [\n ...existing,\n {\n topics: topicsArray,\n schemas: schemas.size > 0 ? schemas : undefined,\n options: Object.keys(consumerOptions).length\n ? consumerOptions\n : undefined,\n clientName,\n batch,\n methodName: propertyKey,\n },\n ],\n target.constructor,\n );\n };\n};\n","import { Injectable } from \"@nestjs/common\";\nimport type {\n IKafkaClient,\n KafkaHealthResult,\n TopicMapConstraint,\n} from \"../client/types\";\nexport type { KafkaHealthResult } from \"../client/types\";\n\n/** Health check service. Call `check(client)` to verify broker connectivity. */\n@Injectable()\nexport class KafkaHealthIndicator {\n async check<T extends TopicMapConstraint<T>>(\n client: IKafkaClient<T>,\n ): Promise<KafkaHealthResult> {\n return client.checkStatus();\n }\n}\n"],"mappings":"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","names":["Logger","Inject","Inject","Logger","Injectable","Injectable"]}
@@ -0,0 +1,157 @@
1
+ import type { MessageSerde, SerdeContext } from "./client/message/serde";
2
+ import type { SchemaRegistryClient } from "./client/message/schema-registry";
3
+ export { JsonSerde } from "./client/message/serde";
4
+ export type { MessageSerde, SerdeContext } from "./client/message/serde";
5
+ export { SchemaRegistryClient } from "./client/message/schema-registry";
6
+ /** Injectable dynamic-import function — overridable in tests. */
7
+ type ImportFn = (specifier: string) => Promise<any>;
8
+ /** Options common to the registry-backed serdes. */
9
+ interface RegistrySerdeCommonOptions {
10
+ /** Schema Registry client used to resolve/register schema ids. */
11
+ registry: SchemaRegistryClient;
12
+ /**
13
+ * Subject name. Defaults to Confluent `TopicNameStrategy`
14
+ * (`<topic>-value` / `<topic>-key`). Provide a literal string or a
15
+ * function of the {@link SerdeContext} to override.
16
+ */
17
+ subject?: string | ((ctx: SerdeContext) => string);
18
+ /**
19
+ * Register `schema` on first serialize to obtain its id (dev-friendly).
20
+ * Default `false` → the id is resolved via `getLatestSchema(subject)`.
21
+ */
22
+ autoRegister?: boolean;
23
+ /** @internal Injectable dynamic import for tests. */
24
+ importFn?: ImportFn;
25
+ }
26
+ /** Options for {@link avroSerde}. */
27
+ export interface AvroSerdeOptions extends RegistrySerdeCommonOptions {
28
+ /**
29
+ * Avro schema (JSON string or object) used to serialize, and as the
30
+ * write-schema fallback. Required to serialize; deserialize resolves the
31
+ * writer schema from the registry via the wire-format id.
32
+ */
33
+ schema?: string | object;
34
+ }
35
+ /**
36
+ * Confluent-wire-format **Avro** serde backed by a Schema Registry.
37
+ *
38
+ * Produces/consumes the exact byte layout Java/Go clients use, so this library
39
+ * interoperates with them through a shared registry:
40
+ *
41
+ * ```
42
+ * [magic 0x00][schema id: 4-byte big-endian][avro binary]
43
+ * ```
44
+ *
45
+ * Uses the optional peer dependency [`avsc`](https://www.npmjs.com/package/avsc)
46
+ * via dynamic import — install it to enable Avro:
47
+ *
48
+ * ```bash
49
+ * npm install avsc
50
+ * ```
51
+ *
52
+ * - **serialize**: resolves the subject from the context, obtains the schema id
53
+ * (`registerSchema` when `autoRegister`, else `getLatestSchema`), Avro-encodes
54
+ * `value` against `schema`, and frames the bytes.
55
+ * - **deserialize**: reads the magic byte + big-endian id, resolves the writer
56
+ * schema via `registry.getSchemaById(id)` (cached forever), and Avro-decodes
57
+ * the remainder. The reader schema equals the writer schema in v1 — full
58
+ * reader-schema resolution / schema evolution is a future enhancement.
59
+ *
60
+ * The parsed `avsc` type is cached per schema string so repeated messages don't
61
+ * re-parse the schema.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { avroSerde } from '@drarzter/kafka-client/serde';
66
+ * import { SchemaRegistryClient } from '@drarzter/kafka-client';
67
+ *
68
+ * const registry = new SchemaRegistryClient({ baseUrl: 'http://localhost:8081' });
69
+ * const orderSchema = {
70
+ * type: 'record',
71
+ * name: 'Order',
72
+ * fields: [{ name: 'orderId', type: 'string' }, { name: 'amount', type: 'double' }],
73
+ * };
74
+ *
75
+ * // Per-topic:
76
+ * const Orders = topic('orders')
77
+ * .serde(avroSerde({ registry, schema: orderSchema }))
78
+ * .type<Order>();
79
+ *
80
+ * // Client-wide:
81
+ * const kafka = new KafkaClient(id, group, brokers, {
82
+ * serde: avroSerde({ registry, schema: orderSchema }),
83
+ * });
84
+ * ```
85
+ */
86
+ export declare function avroSerde(options: AvroSerdeOptions): MessageSerde;
87
+ /** Options for {@link protobufSerde}. */
88
+ export interface ProtobufSerdeOptions extends RegistrySerdeCommonOptions {
89
+ /**
90
+ * Fully-qualified Protobuf message name to encode/decode,
91
+ * e.g. `"com.acme.orders.Order"`.
92
+ */
93
+ messageType: string;
94
+ /**
95
+ * `.proto` source string defining {@link ProtobufSerdeOptions.messageType}.
96
+ * Required to serialize; deserialize resolves the writer `.proto` from the
97
+ * registry via the wire-format id.
98
+ */
99
+ schema?: string;
100
+ }
101
+ /**
102
+ * Confluent-wire-format **Protobuf** serde backed by a Schema Registry.
103
+ *
104
+ * Produces/consumes the exact byte layout Java/Go clients use:
105
+ *
106
+ * ```
107
+ * [magic 0x00][schema id: 4-byte big-endian][message-index][protobuf binary]
108
+ * ```
109
+ *
110
+ * The **message-index** identifies which message type within the `.proto` file
111
+ * was used. For the first/top-level message type (index `[0]`) Confluent writes
112
+ * the single byte `0x00`. This serde implements that top-level case only —
113
+ * multiple/nested message types are a documented v1 limitation and cause a
114
+ * clear error on deserialize.
115
+ *
116
+ * Uses the optional peer dependency
117
+ * [`protobufjs`](https://www.npmjs.com/package/protobufjs) via dynamic import —
118
+ * install it to enable Protobuf:
119
+ *
120
+ * ```bash
121
+ * npm install protobufjs
122
+ * ```
123
+ *
124
+ * - **serialize**: obtains the schema id (`registerSchema` when `autoRegister`,
125
+ * else `getLatestSchema`), encodes `value` with the `protobufjs` `Type`, and
126
+ * frames it with the `0x00` message-index byte.
127
+ * - **deserialize**: reads the magic byte + big-endian id + message-index (which
128
+ * must be the single `0x00` byte), resolves the writer `.proto` via
129
+ * `registry.getSchemaById(id)` (cached forever), and decodes the remainder.
130
+ *
131
+ * The parsed `protobufjs` `Type` is cached per schema string so repeated
132
+ * messages don't re-parse the `.proto`.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * import { protobufSerde } from '@drarzter/kafka-client/serde';
137
+ * import { SchemaRegistryClient } from '@drarzter/kafka-client';
138
+ *
139
+ * const registry = new SchemaRegistryClient({ baseUrl: 'http://localhost:8081' });
140
+ * const proto = `
141
+ * syntax = "proto3";
142
+ * message Order { string orderId = 1; double amount = 2; }
143
+ * `;
144
+ *
145
+ * // Per-topic:
146
+ * const Orders = topic('orders')
147
+ * .serde(protobufSerde({ registry, schema: proto, messageType: 'Order' }))
148
+ * .type<Order>();
149
+ *
150
+ * // Client-wide:
151
+ * const kafka = new KafkaClient(id, group, brokers, {
152
+ * serde: protobufSerde({ registry, schema: proto, messageType: 'Order' }),
153
+ * });
154
+ * ```
155
+ */
156
+ export declare function protobufSerde(options: ProtobufSerdeOptions): MessageSerde;
157
+ //# sourceMappingURL=serde.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serde.d.ts","sourceRoot":"","sources":["../src/serde.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAI7E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAKxE,iEAAiE;AACjE,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAqEpD,oDAAoD;AACpD,UAAU,0BAA0B;IAClC,kEAAkE;IAClE,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,YAAY,KAAK,MAAM,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAiB,SAAQ,0BAA0B;IAClE;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CA4DjE;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAqB,SAAQ,0BAA0B;IACtE;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,YAAY,CAiFzE"}
package/dist/serde.js ADDED
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/serde.ts
21
+ var serde_exports = {};
22
+ __export(serde_exports, {
23
+ JsonSerde: () => JsonSerde,
24
+ SchemaRegistryClient: () => SchemaRegistryClient,
25
+ avroSerde: () => avroSerde,
26
+ protobufSerde: () => protobufSerde
27
+ });
28
+ module.exports = __toCommonJS(serde_exports);
29
+ var import_node_module = require("module");
30
+
31
+ // src/client/message/serde.ts
32
+ var JsonSerde = class {
33
+ /** JSON-stringify the validated payload. Returns a UTF-8 string. */
34
+ serialize(value) {
35
+ return JSON.stringify(value);
36
+ }
37
+ /** JSON-parse UTF-8 wire bytes into an object. */
38
+ deserialize(data) {
39
+ return JSON.parse(data.toString("utf8"));
40
+ }
41
+ };
42
+
43
+ // src/client/message/schema-registry.ts
44
+ var SchemaRegistryClient = class {
45
+ constructor(options) {
46
+ this.options = options;
47
+ if (!options.baseUrl) {
48
+ throw new Error("SchemaRegistryClient: baseUrl is required");
49
+ }
50
+ this.fetchFn = options.fetchFn ?? fetch;
51
+ this.cacheTtlMs = options.cacheTtlMs ?? 3e5;
52
+ }
53
+ options;
54
+ fetchFn;
55
+ cacheTtlMs;
56
+ latestCache = /* @__PURE__ */ new Map();
57
+ /**
58
+ * `id → schema` cache. Schema ids are immutable in a Confluent-compatible
59
+ * registry (a given id always maps to the same schema string), so entries
60
+ * are cached for the lifetime of the client with no TTL.
61
+ */
62
+ byIdCache = /* @__PURE__ */ new Map();
63
+ headers() {
64
+ const h = {
65
+ "Content-Type": "application/vnd.schemaregistry.v1+json"
66
+ };
67
+ if (this.options.auth) {
68
+ const { username, password } = this.options.auth;
69
+ h["Authorization"] = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
70
+ }
71
+ return h;
72
+ }
73
+ async request(method, path, body) {
74
+ const url = `${this.options.baseUrl.replace(/\/$/, "")}${path}`;
75
+ const res = await this.fetchFn(url, {
76
+ method,
77
+ headers: this.headers(),
78
+ ...body !== void 0 && { body: JSON.stringify(body) }
79
+ });
80
+ if (!res.ok) {
81
+ const text = await res.text().catch(() => "");
82
+ throw new Error(
83
+ `SchemaRegistry ${method} ${path} failed: ${res.status} ${res.statusText}${text ? ` \u2014 ${text}` : ""}`
84
+ );
85
+ }
86
+ return await res.json();
87
+ }
88
+ /** Fetch the latest schema registered under `subject`. Cached for `cacheTtlMs`. */
89
+ async getLatestSchema(subject) {
90
+ const cached = this.latestCache.get(subject);
91
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
92
+ const raw = await this.request("GET", `/subjects/${encodeURIComponent(subject)}/versions/latest`);
93
+ const value = {
94
+ id: raw.id,
95
+ version: raw.version,
96
+ schema: raw.schema
97
+ };
98
+ this.latestCache.set(subject, {
99
+ value,
100
+ expiresAt: Date.now() + this.cacheTtlMs
101
+ });
102
+ return value;
103
+ }
104
+ /**
105
+ * Fetch a schema by its globally unique registry id (`GET /schemas/ids/{id}`).
106
+ *
107
+ * Used by the Avro/Protobuf serdes on the deserialize path: the writer schema
108
+ * id is read from the Confluent wire-format prefix, then resolved here. Results
109
+ * are cached forever (schema ids are immutable), so a given id triggers exactly
110
+ * one registry round-trip regardless of how many messages reference it.
111
+ */
112
+ async getSchemaById(id) {
113
+ const cached = this.byIdCache.get(id);
114
+ if (cached) return cached;
115
+ const raw = await this.request(
116
+ "GET",
117
+ `/schemas/ids/${id}`
118
+ );
119
+ const value = { id, schema: raw.schema, schemaType: raw.schemaType };
120
+ this.byIdCache.set(id, value);
121
+ return value;
122
+ }
123
+ /** Fetch a specific schema version of a subject. */
124
+ async getSchemaVersion(subject, version) {
125
+ const raw = await this.request(
126
+ "GET",
127
+ `/subjects/${encodeURIComponent(subject)}/versions/${version}`
128
+ );
129
+ return { id: raw.id, version: raw.version, schema: raw.schema };
130
+ }
131
+ /**
132
+ * Register a schema under `subject` (idempotent — re-registering the same
133
+ * schema returns the existing id). Returns the registry-assigned schema id.
134
+ */
135
+ async registerSchema(subject, schema, schemaType = "JSON") {
136
+ this.latestCache.delete(subject);
137
+ return this.request(
138
+ "POST",
139
+ `/subjects/${encodeURIComponent(subject)}/versions`,
140
+ { schema, schemaType }
141
+ );
142
+ }
143
+ /**
144
+ * Test `schema` against the subject's compatibility policy without registering.
145
+ * Returns `true` when the registry reports the schema as compatible.
146
+ */
147
+ async checkCompatibility(subject, schema, schemaType = "JSON") {
148
+ const res = await this.request(
149
+ "POST",
150
+ `/compatibility/subjects/${encodeURIComponent(subject)}/versions/latest`,
151
+ { schema, schemaType }
152
+ );
153
+ return res.is_compatible;
154
+ }
155
+ };
156
+
157
+ // src/serde.ts
158
+ var MAGIC_BYTE = 0;
159
+ var defaultImport = async (specifier) => {
160
+ const base = typeof __filename !== "undefined" ? __filename : `${process.cwd()}/index.js`;
161
+ const req = (0, import_node_module.createRequire)(base);
162
+ const mod = req(specifier);
163
+ return mod?.default ?? mod;
164
+ };
165
+ function resolveSubject(ctx, subject) {
166
+ if (typeof subject === "function") return subject(ctx);
167
+ if (typeof subject === "string") return subject;
168
+ return `${ctx.topic}-${ctx.isKey ? "key" : "value"}`;
169
+ }
170
+ function readWireHeader(data, serdeName) {
171
+ if (data.length < 5) {
172
+ throw new Error(
173
+ `${serdeName}: message too short to be Confluent-framed (need >= 5 bytes, got ${data.length}).`
174
+ );
175
+ }
176
+ const magic = data[0];
177
+ if (magic !== MAGIC_BYTE) {
178
+ throw new Error(
179
+ `${serdeName}: unexpected magic byte 0x${(magic ?? 0).toString(16).padStart(2, "0")} (expected 0x00). The message is not Confluent wire-format \u2014 check the producer's serializer.`
180
+ );
181
+ }
182
+ const id = data.readInt32BE(1);
183
+ return { id, offset: 5 };
184
+ }
185
+ function frame(id, ...payloadParts) {
186
+ const header = Buffer.alloc(5);
187
+ header.writeUInt8(MAGIC_BYTE, 0);
188
+ header.writeInt32BE(id, 1);
189
+ return Buffer.concat([header, ...payloadParts]);
190
+ }
191
+ function avroSerde(options) {
192
+ const importFn = options.importFn ?? defaultImport;
193
+ const schemaString = options.schema === void 0 ? void 0 : typeof options.schema === "string" ? options.schema : JSON.stringify(options.schema);
194
+ const typeCache = /* @__PURE__ */ new Map();
195
+ let avscMod;
196
+ async function loadAvsc() {
197
+ if (avscMod) return avscMod;
198
+ try {
199
+ avscMod = await importFn("avsc");
200
+ } catch {
201
+ throw new Error(
202
+ "avroSerde: package 'avsc' is not installed. Run `npm install avsc` to enable Avro serialization."
203
+ );
204
+ }
205
+ return avscMod;
206
+ }
207
+ async function typeFor(schemaStr) {
208
+ const cached = typeCache.get(schemaStr);
209
+ if (cached) return cached;
210
+ const avsc = await loadAvsc();
211
+ const type = avsc.Type.forSchema(JSON.parse(schemaStr));
212
+ typeCache.set(schemaStr, type);
213
+ return type;
214
+ }
215
+ return {
216
+ async serialize(value, ctx) {
217
+ if (schemaString === void 0) {
218
+ throw new Error(
219
+ "avroSerde: `schema` is required to serialize \u2014 pass the Avro schema (JSON string or object) in the serde options."
220
+ );
221
+ }
222
+ const subject = resolveSubject(ctx, options.subject);
223
+ const id = options.autoRegister ? (await options.registry.registerSchema(subject, schemaString, "AVRO")).id : (await options.registry.getLatestSchema(subject)).id;
224
+ const type = await typeFor(schemaString);
225
+ const payload = type.toBuffer(value);
226
+ return frame(id, payload);
227
+ },
228
+ async deserialize(data, _ctx) {
229
+ const { id, offset } = readWireHeader(data, "avroSerde");
230
+ const registered = await options.registry.getSchemaById(id);
231
+ const type = await typeFor(registered.schema);
232
+ return type.fromBuffer(data.subarray(offset));
233
+ }
234
+ };
235
+ }
236
+ function protobufSerde(options) {
237
+ const importFn = options.importFn ?? defaultImport;
238
+ const typeCache = /* @__PURE__ */ new Map();
239
+ let protobufMod;
240
+ async function loadProtobuf() {
241
+ if (protobufMod) return protobufMod;
242
+ try {
243
+ protobufMod = await importFn("protobufjs");
244
+ } catch {
245
+ throw new Error(
246
+ "protobufSerde: package 'protobufjs' is not installed. Run `npm install protobufjs` to enable Protobuf serialization."
247
+ );
248
+ }
249
+ return protobufMod;
250
+ }
251
+ async function typeFor(protoSource) {
252
+ const cacheKey = `${protoSource}::${options.messageType}`;
253
+ const cached = typeCache.get(cacheKey);
254
+ if (cached) return cached;
255
+ const protobuf = await loadProtobuf();
256
+ const parsed = protobuf.parse(protoSource);
257
+ const type = parsed.root.lookupType(options.messageType);
258
+ typeCache.set(cacheKey, type);
259
+ return type;
260
+ }
261
+ return {
262
+ async serialize(value, ctx) {
263
+ if (options.schema === void 0) {
264
+ throw new Error(
265
+ "protobufSerde: `schema` is required to serialize \u2014 pass the .proto source string in the serde options."
266
+ );
267
+ }
268
+ const subject = resolveSubject(ctx, options.subject);
269
+ const id = options.autoRegister ? (await options.registry.registerSchema(
270
+ subject,
271
+ options.schema,
272
+ "PROTOBUF"
273
+ )).id : (await options.registry.getLatestSchema(subject)).id;
274
+ const type = await typeFor(options.schema);
275
+ const payload = Buffer.from(
276
+ type.encode(type.create(value)).finish()
277
+ );
278
+ const messageIndex = Buffer.from([0]);
279
+ return frame(id, messageIndex, payload);
280
+ },
281
+ async deserialize(data, _ctx) {
282
+ const { id, offset } = readWireHeader(data, "protobufSerde");
283
+ const indexByte = data[offset];
284
+ if (indexByte !== 0) {
285
+ throw new Error(
286
+ `protobufSerde: nested/multiple message types are not supported in v1 (message-index byte was 0x${(indexByte ?? 0).toString(16).padStart(2, "0")}, expected 0x00 for the top-level message type).`
287
+ );
288
+ }
289
+ const registered = await options.registry.getSchemaById(id);
290
+ const type = await typeFor(registered.schema);
291
+ const decoded = type.decode(data.subarray(offset + 1));
292
+ return type.toObject(decoded, {
293
+ longs: String,
294
+ enums: String,
295
+ bytes: Buffer,
296
+ defaults: true
297
+ });
298
+ }
299
+ };
300
+ }
301
+ // Annotate the CommonJS export names for ESM import in node:
302
+ 0 && (module.exports = {
303
+ JsonSerde,
304
+ SchemaRegistryClient,
305
+ avroSerde,
306
+ protobufSerde
307
+ });
308
+ //# sourceMappingURL=serde.js.map