@eventferry/schema-registry 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Samet GOKTEPE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @eventferry/schema-registry
2
+
3
+ Confluent Schema Registry [`Serializer`](../core) for [eventferry](../../README.md):
4
+ encode outbox payloads as **Avro / Protobuf / JSON Schema** in the Confluent wire
5
+ format, instead of plain JSON.
6
+
7
+ ```bash
8
+ npm i @eventferry/schema-registry @kafkajs/confluent-schema-registry
9
+ ```
10
+
11
+ ```ts
12
+ import { SchemaRegistrySerializer } from "@eventferry/schema-registry";
13
+ import { Relay } from "@eventferry/core";
14
+
15
+ const serializer = new SchemaRegistrySerializer({
16
+ host: "http://localhost:8081",
17
+ schemas: { "orders.created": { type: "AVRO", schema: orderCreatedAvsc } },
18
+ });
19
+
20
+ new Relay({ store, publisher, serializer }); // also works with PostgresStreamingRelay
21
+ ```
22
+
23
+ Topics without a configured schema use the subject's latest registered schema
24
+ (default subject: `${topic}-value`). On the consumer, decode with the same client:
25
+ `await registry.decode(message.value)`.
26
+
27
+ See the [root README](../../README.md) for the full picture.
package/dist/index.cjs ADDED
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ SchemaRegistrySerializer: () => SchemaRegistrySerializer
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/serializer.ts
38
+ var DEFAULT_CONTENT_TYPE = "application/vnd.confluent.avro";
39
+ var SchemaRegistrySerializer = class {
40
+ contentType;
41
+ schemas;
42
+ subject;
43
+ host;
44
+ idCache = /* @__PURE__ */ new Map();
45
+ registry;
46
+ constructor(opts) {
47
+ if (!opts.registry && !opts.host) {
48
+ throw new Error(
49
+ "SchemaRegistrySerializer requires either a `registry` client or a `host`."
50
+ );
51
+ }
52
+ this.registry = opts.registry ?? null;
53
+ this.host = opts.host ?? null;
54
+ this.schemas = opts.schemas ?? {};
55
+ this.subject = opts.subject ?? ((topic) => `${topic}-value`);
56
+ this.contentType = opts.contentType ?? DEFAULT_CONTENT_TYPE;
57
+ }
58
+ async serialize(record) {
59
+ const registry = await this.getRegistry();
60
+ const id = await this.schemaId(registry, record.topic);
61
+ return registry.encode(id, record.payload);
62
+ }
63
+ schemaId(registry, topic) {
64
+ const cached = this.idCache.get(topic);
65
+ if (cached) return cached;
66
+ const subject = this.subject(topic);
67
+ const spec = this.schemas[topic];
68
+ const lookup = spec ? registry.register({ type: spec.type, schema: spec.schema }, { subject }).then((r) => r.id) : registry.getLatestSchemaId(subject);
69
+ const guarded = lookup.catch((err) => {
70
+ this.idCache.delete(topic);
71
+ throw err;
72
+ });
73
+ this.idCache.set(topic, guarded);
74
+ return guarded;
75
+ }
76
+ async getRegistry() {
77
+ if (this.registry) return this.registry;
78
+ const mod = await importSchemaRegistry();
79
+ this.registry = new mod.SchemaRegistry({ host: this.host });
80
+ return this.registry;
81
+ }
82
+ };
83
+ async function importSchemaRegistry() {
84
+ try {
85
+ return await import("@kafkajs/confluent-schema-registry");
86
+ } catch {
87
+ throw new Error(
88
+ 'SchemaRegistrySerializer with `host` needs the "@kafkajs/confluent-schema-registry" package. Run: npm i @kafkajs/confluent-schema-registry'
89
+ );
90
+ }
91
+ }
92
+ // Annotate the CommonJS export names for ESM import in node:
93
+ 0 && (module.exports = {
94
+ SchemaRegistrySerializer
95
+ });
96
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/serializer.ts"],"sourcesContent":["export * from \"./serializer.js\";\n","import type { OutboxRecord, Serializer } from \"@eventferry/core\";\n\nexport type SchemaType = \"AVRO\" | \"PROTOBUF\" | \"JSON\";\n\nexport interface SchemaSpec {\n type: SchemaType;\n /** Schema definition string (avsc JSON / .proto / JSON Schema). */\n schema: string;\n}\n\n/**\n * The subset of a Confluent Schema Registry client this serializer uses. The\n * `@kafkajs/confluent-schema-registry` `SchemaRegistry` satisfies it structurally.\n */\nexport interface SchemaRegistryClient {\n register(\n schema: { type: string; schema: string },\n opts?: { subject: string },\n ): Promise<{ id: number }>;\n getLatestSchemaId(subject: string): Promise<number>;\n encode(registryId: number, payload: unknown): Promise<Buffer>;\n}\n\nexport interface SchemaRegistrySerializerOptions {\n /** Inject a ready client (tests, custom config). */\n registry?: SchemaRegistryClient;\n /** Or construct one from a host (requires @kafkajs/confluent-schema-registry). */\n host?: string;\n /** Per-topic schema to register. Topics omitted here use the subject's latest. */\n schemas?: Record<string, SchemaSpec>;\n /** Subject naming. Default TopicNameStrategy: `${topic}-value`. */\n subject?: (topic: string) => string;\n /** content-type header value. Default \"application/vnd.confluent.avro\". */\n contentType?: string;\n}\n\nconst DEFAULT_CONTENT_TYPE = \"application/vnd.confluent.avro\";\n\n/**\n * A core {@link Serializer} that encodes payloads with a Confluent Schema Registry\n * (Avro / Protobuf / JSON Schema). Drop it into `Relay`/`PostgresStreamingRelay`'s\n * `serializer` option. The schema id per topic is resolved once and cached.\n */\nexport class SchemaRegistrySerializer implements Serializer {\n readonly contentType: string;\n private readonly schemas: Record<string, SchemaSpec>;\n private readonly subject: (topic: string) => string;\n private readonly host: string | null;\n private readonly idCache = new Map<string, Promise<number>>();\n private registry: SchemaRegistryClient | null;\n\n constructor(opts: SchemaRegistrySerializerOptions) {\n if (!opts.registry && !opts.host) {\n throw new Error(\n \"SchemaRegistrySerializer requires either a `registry` client or a `host`.\",\n );\n }\n this.registry = opts.registry ?? null;\n this.host = opts.host ?? null;\n this.schemas = opts.schemas ?? {};\n this.subject = opts.subject ?? ((topic) => `${topic}-value`);\n this.contentType = opts.contentType ?? DEFAULT_CONTENT_TYPE;\n }\n\n async serialize(record: OutboxRecord): Promise<Buffer> {\n const registry = await this.getRegistry();\n const id = await this.schemaId(registry, record.topic);\n return registry.encode(id, record.payload);\n }\n\n private schemaId(\n registry: SchemaRegistryClient,\n topic: string,\n ): Promise<number> {\n const cached = this.idCache.get(topic);\n if (cached) return cached;\n\n const subject = this.subject(topic);\n const spec = this.schemas[topic];\n const lookup = spec\n ? registry\n .register({ type: spec.type, schema: spec.schema }, { subject })\n .then((r) => r.id)\n : registry.getLatestSchemaId(subject);\n\n // Cache the in-flight promise so concurrent first calls don't double-register;\n // drop it on failure so a transient error can be retried.\n const guarded = lookup.catch((err) => {\n this.idCache.delete(topic);\n throw err;\n });\n this.idCache.set(topic, guarded);\n return guarded;\n }\n\n private async getRegistry(): Promise<SchemaRegistryClient> {\n if (this.registry) return this.registry;\n const mod = await importSchemaRegistry();\n this.registry = new mod.SchemaRegistry({ host: this.host as string });\n return this.registry;\n }\n}\n\nasync function importSchemaRegistry(): Promise<{\n SchemaRegistry: new (cfg: { host: string }) => SchemaRegistryClient;\n}> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (await import(\"@kafkajs/confluent-schema-registry\")) as any;\n } catch {\n throw new Error(\n 'SchemaRegistrySerializer with `host` needs the \"@kafkajs/confluent-schema-registry\" package. Run: npm i @kafkajs/confluent-schema-registry',\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACoCA,IAAM,uBAAuB;AAOtB,IAAM,2BAAN,MAAqD;AAAA,EACjD;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU,oBAAI,IAA6B;AAAA,EACpD;AAAA,EAER,YAAY,MAAuC;AACjD,QAAI,CAAC,KAAK,YAAY,CAAC,KAAK,MAAM;AAChC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,WAAW,KAAK,YAAY;AACjC,SAAK,OAAO,KAAK,QAAQ;AACzB,SAAK,UAAU,KAAK,WAAW,CAAC;AAChC,SAAK,UAAU,KAAK,YAAY,CAAC,UAAU,GAAG,KAAK;AACnD,SAAK,cAAc,KAAK,eAAe;AAAA,EACzC;AAAA,EAEA,MAAM,UAAU,QAAuC;AACrD,UAAM,WAAW,MAAM,KAAK,YAAY;AACxC,UAAM,KAAK,MAAM,KAAK,SAAS,UAAU,OAAO,KAAK;AACrD,WAAO,SAAS,OAAO,IAAI,OAAO,OAAO;AAAA,EAC3C;AAAA,EAEQ,SACN,UACA,OACiB;AACjB,UAAM,SAAS,KAAK,QAAQ,IAAI,KAAK;AACrC,QAAI,OAAQ,QAAO;AAEnB,UAAM,UAAU,KAAK,QAAQ,KAAK;AAClC,UAAM,OAAO,KAAK,QAAQ,KAAK;AAC/B,UAAM,SAAS,OACX,SACG,SAAS,EAAE,MAAM,KAAK,MAAM,QAAQ,KAAK,OAAO,GAAG,EAAE,QAAQ,CAAC,EAC9D,KAAK,CAAC,MAAM,EAAE,EAAE,IACnB,SAAS,kBAAkB,OAAO;AAItC,UAAM,UAAU,OAAO,MAAM,CAAC,QAAQ;AACpC,WAAK,QAAQ,OAAO,KAAK;AACzB,YAAM;AAAA,IACR,CAAC;AACD,SAAK,QAAQ,IAAI,OAAO,OAAO;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,cAA6C;AACzD,QAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,UAAM,MAAM,MAAM,qBAAqB;AACvC,SAAK,WAAW,IAAI,IAAI,eAAe,EAAE,MAAM,KAAK,KAAe,CAAC;AACpE,WAAO,KAAK;AAAA,EACd;AACF;AAEA,eAAe,uBAEZ;AACD,MAAI;AAEF,WAAQ,MAAM,OAAO,oCAAoC;AAAA,EAC3D,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,55 @@
1
+ import { Serializer, OutboxRecord } from '@eventferry/core';
2
+
3
+ type SchemaType = "AVRO" | "PROTOBUF" | "JSON";
4
+ interface SchemaSpec {
5
+ type: SchemaType;
6
+ /** Schema definition string (avsc JSON / .proto / JSON Schema). */
7
+ schema: string;
8
+ }
9
+ /**
10
+ * The subset of a Confluent Schema Registry client this serializer uses. The
11
+ * `@kafkajs/confluent-schema-registry` `SchemaRegistry` satisfies it structurally.
12
+ */
13
+ interface SchemaRegistryClient {
14
+ register(schema: {
15
+ type: string;
16
+ schema: string;
17
+ }, opts?: {
18
+ subject: string;
19
+ }): Promise<{
20
+ id: number;
21
+ }>;
22
+ getLatestSchemaId(subject: string): Promise<number>;
23
+ encode(registryId: number, payload: unknown): Promise<Buffer>;
24
+ }
25
+ interface SchemaRegistrySerializerOptions {
26
+ /** Inject a ready client (tests, custom config). */
27
+ registry?: SchemaRegistryClient;
28
+ /** Or construct one from a host (requires @kafkajs/confluent-schema-registry). */
29
+ host?: string;
30
+ /** Per-topic schema to register. Topics omitted here use the subject's latest. */
31
+ schemas?: Record<string, SchemaSpec>;
32
+ /** Subject naming. Default TopicNameStrategy: `${topic}-value`. */
33
+ subject?: (topic: string) => string;
34
+ /** content-type header value. Default "application/vnd.confluent.avro". */
35
+ contentType?: string;
36
+ }
37
+ /**
38
+ * A core {@link Serializer} that encodes payloads with a Confluent Schema Registry
39
+ * (Avro / Protobuf / JSON Schema). Drop it into `Relay`/`PostgresStreamingRelay`'s
40
+ * `serializer` option. The schema id per topic is resolved once and cached.
41
+ */
42
+ declare class SchemaRegistrySerializer implements Serializer {
43
+ readonly contentType: string;
44
+ private readonly schemas;
45
+ private readonly subject;
46
+ private readonly host;
47
+ private readonly idCache;
48
+ private registry;
49
+ constructor(opts: SchemaRegistrySerializerOptions);
50
+ serialize(record: OutboxRecord): Promise<Buffer>;
51
+ private schemaId;
52
+ private getRegistry;
53
+ }
54
+
55
+ export { type SchemaRegistryClient, SchemaRegistrySerializer, type SchemaRegistrySerializerOptions, type SchemaSpec, type SchemaType };
@@ -0,0 +1,55 @@
1
+ import { Serializer, OutboxRecord } from '@eventferry/core';
2
+
3
+ type SchemaType = "AVRO" | "PROTOBUF" | "JSON";
4
+ interface SchemaSpec {
5
+ type: SchemaType;
6
+ /** Schema definition string (avsc JSON / .proto / JSON Schema). */
7
+ schema: string;
8
+ }
9
+ /**
10
+ * The subset of a Confluent Schema Registry client this serializer uses. The
11
+ * `@kafkajs/confluent-schema-registry` `SchemaRegistry` satisfies it structurally.
12
+ */
13
+ interface SchemaRegistryClient {
14
+ register(schema: {
15
+ type: string;
16
+ schema: string;
17
+ }, opts?: {
18
+ subject: string;
19
+ }): Promise<{
20
+ id: number;
21
+ }>;
22
+ getLatestSchemaId(subject: string): Promise<number>;
23
+ encode(registryId: number, payload: unknown): Promise<Buffer>;
24
+ }
25
+ interface SchemaRegistrySerializerOptions {
26
+ /** Inject a ready client (tests, custom config). */
27
+ registry?: SchemaRegistryClient;
28
+ /** Or construct one from a host (requires @kafkajs/confluent-schema-registry). */
29
+ host?: string;
30
+ /** Per-topic schema to register. Topics omitted here use the subject's latest. */
31
+ schemas?: Record<string, SchemaSpec>;
32
+ /** Subject naming. Default TopicNameStrategy: `${topic}-value`. */
33
+ subject?: (topic: string) => string;
34
+ /** content-type header value. Default "application/vnd.confluent.avro". */
35
+ contentType?: string;
36
+ }
37
+ /**
38
+ * A core {@link Serializer} that encodes payloads with a Confluent Schema Registry
39
+ * (Avro / Protobuf / JSON Schema). Drop it into `Relay`/`PostgresStreamingRelay`'s
40
+ * `serializer` option. The schema id per topic is resolved once and cached.
41
+ */
42
+ declare class SchemaRegistrySerializer implements Serializer {
43
+ readonly contentType: string;
44
+ private readonly schemas;
45
+ private readonly subject;
46
+ private readonly host;
47
+ private readonly idCache;
48
+ private registry;
49
+ constructor(opts: SchemaRegistrySerializerOptions);
50
+ serialize(record: OutboxRecord): Promise<Buffer>;
51
+ private schemaId;
52
+ private getRegistry;
53
+ }
54
+
55
+ export { type SchemaRegistryClient, SchemaRegistrySerializer, type SchemaRegistrySerializerOptions, type SchemaSpec, type SchemaType };
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ // src/serializer.ts
2
+ var DEFAULT_CONTENT_TYPE = "application/vnd.confluent.avro";
3
+ var SchemaRegistrySerializer = class {
4
+ contentType;
5
+ schemas;
6
+ subject;
7
+ host;
8
+ idCache = /* @__PURE__ */ new Map();
9
+ registry;
10
+ constructor(opts) {
11
+ if (!opts.registry && !opts.host) {
12
+ throw new Error(
13
+ "SchemaRegistrySerializer requires either a `registry` client or a `host`."
14
+ );
15
+ }
16
+ this.registry = opts.registry ?? null;
17
+ this.host = opts.host ?? null;
18
+ this.schemas = opts.schemas ?? {};
19
+ this.subject = opts.subject ?? ((topic) => `${topic}-value`);
20
+ this.contentType = opts.contentType ?? DEFAULT_CONTENT_TYPE;
21
+ }
22
+ async serialize(record) {
23
+ const registry = await this.getRegistry();
24
+ const id = await this.schemaId(registry, record.topic);
25
+ return registry.encode(id, record.payload);
26
+ }
27
+ schemaId(registry, topic) {
28
+ const cached = this.idCache.get(topic);
29
+ if (cached) return cached;
30
+ const subject = this.subject(topic);
31
+ const spec = this.schemas[topic];
32
+ const lookup = spec ? registry.register({ type: spec.type, schema: spec.schema }, { subject }).then((r) => r.id) : registry.getLatestSchemaId(subject);
33
+ const guarded = lookup.catch((err) => {
34
+ this.idCache.delete(topic);
35
+ throw err;
36
+ });
37
+ this.idCache.set(topic, guarded);
38
+ return guarded;
39
+ }
40
+ async getRegistry() {
41
+ if (this.registry) return this.registry;
42
+ const mod = await importSchemaRegistry();
43
+ this.registry = new mod.SchemaRegistry({ host: this.host });
44
+ return this.registry;
45
+ }
46
+ };
47
+ async function importSchemaRegistry() {
48
+ try {
49
+ return await import("@kafkajs/confluent-schema-registry");
50
+ } catch {
51
+ throw new Error(
52
+ 'SchemaRegistrySerializer with `host` needs the "@kafkajs/confluent-schema-registry" package. Run: npm i @kafkajs/confluent-schema-registry'
53
+ );
54
+ }
55
+ }
56
+ export {
57
+ SchemaRegistrySerializer
58
+ };
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serializer.ts"],"sourcesContent":["import type { OutboxRecord, Serializer } from \"@eventferry/core\";\n\nexport type SchemaType = \"AVRO\" | \"PROTOBUF\" | \"JSON\";\n\nexport interface SchemaSpec {\n type: SchemaType;\n /** Schema definition string (avsc JSON / .proto / JSON Schema). */\n schema: string;\n}\n\n/**\n * The subset of a Confluent Schema Registry client this serializer uses. The\n * `@kafkajs/confluent-schema-registry` `SchemaRegistry` satisfies it structurally.\n */\nexport interface SchemaRegistryClient {\n register(\n schema: { type: string; schema: string },\n opts?: { subject: string },\n ): Promise<{ id: number }>;\n getLatestSchemaId(subject: string): Promise<number>;\n encode(registryId: number, payload: unknown): Promise<Buffer>;\n}\n\nexport interface SchemaRegistrySerializerOptions {\n /** Inject a ready client (tests, custom config). */\n registry?: SchemaRegistryClient;\n /** Or construct one from a host (requires @kafkajs/confluent-schema-registry). */\n host?: string;\n /** Per-topic schema to register. Topics omitted here use the subject's latest. */\n schemas?: Record<string, SchemaSpec>;\n /** Subject naming. Default TopicNameStrategy: `${topic}-value`. */\n subject?: (topic: string) => string;\n /** content-type header value. Default \"application/vnd.confluent.avro\". */\n contentType?: string;\n}\n\nconst DEFAULT_CONTENT_TYPE = \"application/vnd.confluent.avro\";\n\n/**\n * A core {@link Serializer} that encodes payloads with a Confluent Schema Registry\n * (Avro / Protobuf / JSON Schema). Drop it into `Relay`/`PostgresStreamingRelay`'s\n * `serializer` option. The schema id per topic is resolved once and cached.\n */\nexport class SchemaRegistrySerializer implements Serializer {\n readonly contentType: string;\n private readonly schemas: Record<string, SchemaSpec>;\n private readonly subject: (topic: string) => string;\n private readonly host: string | null;\n private readonly idCache = new Map<string, Promise<number>>();\n private registry: SchemaRegistryClient | null;\n\n constructor(opts: SchemaRegistrySerializerOptions) {\n if (!opts.registry && !opts.host) {\n throw new Error(\n \"SchemaRegistrySerializer requires either a `registry` client or a `host`.\",\n );\n }\n this.registry = opts.registry ?? null;\n this.host = opts.host ?? null;\n this.schemas = opts.schemas ?? {};\n this.subject = opts.subject ?? ((topic) => `${topic}-value`);\n this.contentType = opts.contentType ?? DEFAULT_CONTENT_TYPE;\n }\n\n async serialize(record: OutboxRecord): Promise<Buffer> {\n const registry = await this.getRegistry();\n const id = await this.schemaId(registry, record.topic);\n return registry.encode(id, record.payload);\n }\n\n private schemaId(\n registry: SchemaRegistryClient,\n topic: string,\n ): Promise<number> {\n const cached = this.idCache.get(topic);\n if (cached) return cached;\n\n const subject = this.subject(topic);\n const spec = this.schemas[topic];\n const lookup = spec\n ? registry\n .register({ type: spec.type, schema: spec.schema }, { subject })\n .then((r) => r.id)\n : registry.getLatestSchemaId(subject);\n\n // Cache the in-flight promise so concurrent first calls don't double-register;\n // drop it on failure so a transient error can be retried.\n const guarded = lookup.catch((err) => {\n this.idCache.delete(topic);\n throw err;\n });\n this.idCache.set(topic, guarded);\n return guarded;\n }\n\n private async getRegistry(): Promise<SchemaRegistryClient> {\n if (this.registry) return this.registry;\n const mod = await importSchemaRegistry();\n this.registry = new mod.SchemaRegistry({ host: this.host as string });\n return this.registry;\n }\n}\n\nasync function importSchemaRegistry(): Promise<{\n SchemaRegistry: new (cfg: { host: string }) => SchemaRegistryClient;\n}> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (await import(\"@kafkajs/confluent-schema-registry\")) as any;\n } catch {\n throw new Error(\n 'SchemaRegistrySerializer with `host` needs the \"@kafkajs/confluent-schema-registry\" package. Run: npm i @kafkajs/confluent-schema-registry',\n );\n }\n}\n"],"mappings":";AAoCA,IAAM,uBAAuB;AAOtB,IAAM,2BAAN,MAAqD;AAAA,EACjD;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU,oBAAI,IAA6B;AAAA,EACpD;AAAA,EAER,YAAY,MAAuC;AACjD,QAAI,CAAC,KAAK,YAAY,CAAC,KAAK,MAAM;AAChC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,WAAW,KAAK,YAAY;AACjC,SAAK,OAAO,KAAK,QAAQ;AACzB,SAAK,UAAU,KAAK,WAAW,CAAC;AAChC,SAAK,UAAU,KAAK,YAAY,CAAC,UAAU,GAAG,KAAK;AACnD,SAAK,cAAc,KAAK,eAAe;AAAA,EACzC;AAAA,EAEA,MAAM,UAAU,QAAuC;AACrD,UAAM,WAAW,MAAM,KAAK,YAAY;AACxC,UAAM,KAAK,MAAM,KAAK,SAAS,UAAU,OAAO,KAAK;AACrD,WAAO,SAAS,OAAO,IAAI,OAAO,OAAO;AAAA,EAC3C;AAAA,EAEQ,SACN,UACA,OACiB;AACjB,UAAM,SAAS,KAAK,QAAQ,IAAI,KAAK;AACrC,QAAI,OAAQ,QAAO;AAEnB,UAAM,UAAU,KAAK,QAAQ,KAAK;AAClC,UAAM,OAAO,KAAK,QAAQ,KAAK;AAC/B,UAAM,SAAS,OACX,SACG,SAAS,EAAE,MAAM,KAAK,MAAM,QAAQ,KAAK,OAAO,GAAG,EAAE,QAAQ,CAAC,EAC9D,KAAK,CAAC,MAAM,EAAE,EAAE,IACnB,SAAS,kBAAkB,OAAO;AAItC,UAAM,UAAU,OAAO,MAAM,CAAC,QAAQ;AACpC,WAAK,QAAQ,OAAO,KAAK;AACzB,YAAM;AAAA,IACR,CAAC;AACD,SAAK,QAAQ,IAAI,OAAO,OAAO;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,cAA6C;AACzD,QAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,UAAM,MAAM,MAAM,qBAAqB;AACvC,SAAK,WAAW,IAAI,IAAI,eAAe,EAAE,MAAM,KAAK,KAAe,CAAC;AACpE,WAAO,KAAK;AAAA,EACd;AACF;AAEA,eAAe,uBAEZ;AACD,MAAI;AAEF,WAAQ,MAAM,OAAO,oCAAoC;AAAA,EAC3D,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@eventferry/schema-registry",
3
+ "version": "1.0.0",
4
+ "description": "Confluent Schema Registry serializer for @eventferry (Avro/Protobuf/JSON Schema)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "outbox",
21
+ "schema-registry",
22
+ "avro",
23
+ "protobuf",
24
+ "json-schema",
25
+ "confluent"
26
+ ],
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/SametGoktepe/eventferry.git",
31
+ "directory": "packages/schema-registry"
32
+ },
33
+ "homepage": "https://github.com/SametGoktepe/eventferry/tree/main/packages/schema-registry#readme",
34
+ "bugs": "https://github.com/SametGoktepe/eventferry/issues",
35
+ "peerDependencies": {
36
+ "@kafkajs/confluent-schema-registry": "^3.0.0",
37
+ "@eventferry/core": "1.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@kafkajs/confluent-schema-registry": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@kafkajs/confluent-schema-registry": "^3.0.0",
46
+ "tsup": "^8.3.5",
47
+ "typescript": "^5.7.2",
48
+ "vitest": "^2.1.8",
49
+ "@eventferry/core": "1.0.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "test": "vitest",
54
+ "test:run": "vitest run",
55
+ "typecheck": "tsc --noEmit"
56
+ }
57
+ }