@platformatic/kafka 1.26.0 → 1.27.0-alpha.2

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 (49) hide show
  1. package/README.md +58 -0
  2. package/dist/clients/consumer/consumer.js +24 -1
  3. package/dist/clients/consumer/messages-stream.js +66 -10
  4. package/dist/clients/consumer/options.d.ts +24 -0
  5. package/dist/clients/consumer/options.js +3 -1
  6. package/dist/clients/consumer/types.d.ts +4 -1
  7. package/dist/clients/producer/options.d.ts +2 -18
  8. package/dist/clients/producer/options.js +3 -1
  9. package/dist/clients/producer/producer.js +75 -15
  10. package/dist/clients/producer/types.d.ts +4 -1
  11. package/dist/clients/serde.d.ts +11 -6
  12. package/dist/errors.d.ts +5 -1
  13. package/dist/errors.js +8 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +2 -0
  16. package/dist/network/connection.d.ts +7 -6
  17. package/dist/protocol/definitions.js +1 -1
  18. package/dist/protocol/reader.js +1 -1
  19. package/dist/protocol/records.d.ts +7 -18
  20. package/dist/protocol/records.js +2 -6
  21. package/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
  22. package/dist/protocol/sasl/plain.d.ts +3 -3
  23. package/dist/protocol/sasl/scram-sha.d.ts +3 -3
  24. package/dist/protocol/sasl/utils.d.ts +3 -3
  25. package/dist/protocol/writer.js +1 -1
  26. package/dist/registries/abstract.d.ts +22 -0
  27. package/dist/registries/abstract.js +38 -0
  28. package/dist/registries/confluent-schema-registry.d.ts +41 -0
  29. package/dist/registries/confluent-schema-registry.js +222 -0
  30. package/dist/registries/index.d.ts +2 -0
  31. package/dist/registries/index.js +2 -0
  32. package/dist/typescript-4/dist/clients/consumer/options.d.ts +24 -0
  33. package/dist/typescript-4/dist/clients/consumer/types.d.ts +4 -1
  34. package/dist/typescript-4/dist/clients/producer/options.d.ts +2 -18
  35. package/dist/typescript-4/dist/clients/producer/types.d.ts +4 -1
  36. package/dist/typescript-4/dist/clients/serde.d.ts +11 -6
  37. package/dist/typescript-4/dist/errors.d.ts +5 -1
  38. package/dist/typescript-4/dist/index.d.ts +2 -1
  39. package/dist/typescript-4/dist/network/connection.d.ts +7 -6
  40. package/dist/typescript-4/dist/protocol/records.d.ts +7 -18
  41. package/dist/typescript-4/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
  42. package/dist/typescript-4/dist/protocol/sasl/plain.d.ts +3 -3
  43. package/dist/typescript-4/dist/protocol/sasl/scram-sha.d.ts +3 -3
  44. package/dist/typescript-4/dist/protocol/sasl/utils.d.ts +3 -3
  45. package/dist/typescript-4/dist/registries/abstract.d.ts +22 -0
  46. package/dist/typescript-4/dist/registries/confluent-schema-registry.d.ts +41 -0
  47. package/dist/typescript-4/dist/registries/index.d.ts +2 -0
  48. package/dist/version.js +1 -1
  49. package/package.json +4 -3
@@ -7,5 +7,5 @@ export const EMPTY_BUFFER = Buffer.alloc(0);
7
7
  export const EMPTY_UUID = Buffer.alloc(UUID_SIZE);
8
8
  // Since it is serialized at either 0 (for nullable) or 1 (since length is stored as length + 1), it always uses a single byte
9
9
  export const EMPTY_OR_SINGLE_COMPACT_LENGTH_SIZE = INT8_SIZE;
10
- // TODO(ShogunPanda): Tagged fields are not supported yet
10
+ // TODO: Tagged fields are not supported yet
11
11
  export const EMPTY_TAGGED_FIELDS_BUFFER = Buffer.from([0]);
@@ -300,7 +300,7 @@ export class Reader {
300
300
  }
301
301
  return reader();
302
302
  }
303
- // TODO(ShogunPanda): Tagged fields are not supported yet
303
+ // TODO: Tagged fields are not supported yet
304
304
  readTaggedFields() {
305
305
  const length = this.readVarInt();
306
306
  if (length > 0) {
@@ -17,6 +17,7 @@ export interface MessageBase<Key = Buffer, Value = Buffer> {
17
17
  }
18
18
  export interface MessageToProduce<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends MessageBase<Key, Value> {
19
19
  headers?: Map<HeaderKey, HeaderValue> | Record<string, HeaderValue>;
20
+ metadata?: unknown;
20
21
  }
21
22
  export interface MessageConsumerMetadata {
22
23
  coordinatorId: number;
@@ -67,6 +68,10 @@ export interface KafkaRecord {
67
68
  value: Buffer;
68
69
  headers: [Buffer, Buffer][];
69
70
  }
71
+ export interface MessageToConsume extends KafkaRecord {
72
+ topic: string;
73
+ partition: number;
74
+ }
70
75
  export interface RecordsBatch {
71
76
  firstOffset: bigint;
72
77
  length: number;
@@ -85,24 +90,8 @@ export interface RecordsBatch {
85
90
  export declare const messageSchema: {
86
91
  type: string;
87
92
  properties: {
88
- key: {
89
- oneOf: ({
90
- type: string;
91
- buffer?: undefined;
92
- } | {
93
- buffer: boolean;
94
- type?: undefined;
95
- })[];
96
- };
97
- value: {
98
- oneOf: ({
99
- type: string;
100
- buffer?: undefined;
101
- } | {
102
- buffer: boolean;
103
- type?: undefined;
104
- })[];
105
- };
93
+ key: boolean;
94
+ value: boolean;
106
95
  headers: {
107
96
  anyOf: ({
108
97
  map: boolean;
@@ -14,12 +14,8 @@ export const BATCH_HEAD = INT64_SIZE + INT32_SIZE; // FirstOffset + Length
14
14
  export const messageSchema = {
15
15
  type: 'object',
16
16
  properties: {
17
- key: {
18
- oneOf: [{ type: 'string' }, { buffer: true }]
19
- },
20
- value: {
21
- oneOf: [{ type: 'string' }, { buffer: true }]
22
- },
17
+ key: true,
18
+ value: true,
23
19
  headers: {
24
20
  // Note: we can't use oneOf here since a Map is also a 'object'. Thanks JS.
25
21
  anyOf: [
@@ -1,6 +1,6 @@
1
1
  import { type CallbackWithPromise } from '../../apis/callbacks.ts';
2
2
  import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
3
- import { type Connection, type SASLCredentialProvider } from '../../network/connection.ts';
3
+ import { type Connection, type CredentialProvider } from '../../network/connection.ts';
4
4
  export declare function jwtValidateAuthenticationBytes(authBytes: Buffer, callback: CallbackWithPromise<Buffer>): void;
5
- export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string | SASLCredentialProvider, extensions: Record<string, string> | SASLCredentialProvider<Record<string, string>>, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
6
- export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string | SASLCredentialProvider, extensions: Record<string, string> | SASLCredentialProvider<Record<string, string>>): Promise<SaslAuthenticateResponse>;
5
+ export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string | CredentialProvider, extensions: Record<string, string> | CredentialProvider<Record<string, string>>, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
6
+ export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string | CredentialProvider, extensions: Record<string, string> | CredentialProvider<Record<string, string>>): Promise<SaslAuthenticateResponse>;
@@ -1,5 +1,5 @@
1
1
  import { type CallbackWithPromise } from '../../apis/callbacks.ts';
2
2
  import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
3
- import { type Connection, type SASLCredentialProvider } from '../../network/connection.ts';
4
- export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
5
- export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider): Promise<SaslAuthenticateResponse>;
3
+ import { type Connection, type CredentialProvider } from '../../network/connection.ts';
4
+ export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string | CredentialProvider, passwordProvider: string | CredentialProvider, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
5
+ export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string | CredentialProvider, passwordProvider: string | CredentialProvider): Promise<SaslAuthenticateResponse>;
@@ -1,6 +1,6 @@
1
1
  import { type CallbackWithPromise } from '../../apis/callbacks.ts';
2
2
  import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
3
- import { type Connection, type SASLCredentialProvider } from '../../network/connection.ts';
3
+ import { type Connection, type CredentialProvider } from '../../network/connection.ts';
4
4
  export interface ScramAlgorithmDefinition {
5
5
  keyLength: number;
6
6
  algorithm: string;
@@ -35,5 +35,5 @@ export declare function hi(definition: ScramAlgorithmDefinition, password: strin
35
35
  export declare function hmac(definition: ScramAlgorithmDefinition, key: Buffer, data: string | Buffer): Buffer;
36
36
  export declare function xor(a: Buffer, b: Buffer): Buffer;
37
37
  export declare const defaultCrypto: ScramCryptoModule;
38
- export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider, crypto: ScramCryptoModule, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
39
- export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider, crypto?: ScramCryptoModule): Promise<SaslAuthenticateResponse>;
38
+ export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string | CredentialProvider, passwordProvider: string | CredentialProvider, crypto: ScramCryptoModule, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
39
+ export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string | CredentialProvider, passwordProvider: string | CredentialProvider, crypto?: ScramCryptoModule): Promise<SaslAuthenticateResponse>;
@@ -1,4 +1,4 @@
1
1
  import { type CallbackWithPromise } from '../../apis/index.ts';
2
- import { type SASLCredentialProvider } from '../../network/connection.ts';
3
- export declare function getCredential<T>(label: string, credentialOrProvider: T | SASLCredentialProvider<T>, callback: CallbackWithPromise<T>): void;
4
- export declare function getCredential<T>(label: string, credentialOrProvider: T | SASLCredentialProvider<T>): Promise<T>;
2
+ import { type CredentialProvider } from '../../network/connection.ts';
3
+ export declare function getCredential<T>(label: string, credentialOrProvider: T | CredentialProvider<T>, callback: CallbackWithPromise<T>): void;
4
+ export declare function getCredential<T>(label: string, credentialOrProvider: T | CredentialProvider<T>): Promise<T>;
@@ -210,7 +210,7 @@ export class Writer {
210
210
  }
211
211
  return this;
212
212
  }
213
- // TODO(ShogunPanda): Tagged fields are not supported yet
213
+ // TODO: Tagged fields are not supported yet
214
214
  appendTaggedFields(_ = []) {
215
215
  return this.append(EMPTY_TAGGED_FIELDS_BUFFER);
216
216
  }
@@ -0,0 +1,22 @@
1
+ import { type Callback } from '../apis/definitions.ts';
2
+ import { type BeforeDeserializationHook, type BeforeHookPayloadType, type BeforeSerializationHook, type Deserializers, type Serializers } from '../clients/serde.ts';
3
+ import { type MessageToProduce } from '../protocol/records.ts';
4
+ export interface SchemaRegistry<Id = unknown, Schema = unknown, Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> {
5
+ get(id: Id): Schema | undefined;
6
+ fetch(id: Id, callback?: (error?: Error) => void): void | Promise<void>;
7
+ getSchemaId(payload: Buffer | MessageToProduce<Key, Value, HeaderKey, HeaderValue>, type?: BeforeHookPayloadType): Id;
8
+ getSerializers(): Serializers<Key, Value, HeaderKey, HeaderValue>;
9
+ getDeserializers(): Deserializers<Key, Value, HeaderKey, HeaderValue>;
10
+ getBeforeSerializationHook(): BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue>;
11
+ getBeforeDeserializationHook(): BeforeDeserializationHook;
12
+ }
13
+ export declare function runAsyncSeries<V>(operation: (item: V, cb: Callback<void>) => void | Promise<void>, collection: V[], index: number, callback: Callback<void>): void;
14
+ export declare class AbstractSchemaRegistry<Id = unknown, Schema = unknown, Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> implements SchemaRegistry<Id, Schema, Key, Value, HeaderKey, HeaderValue> {
15
+ get(_: Id): Schema | undefined;
16
+ fetch(_i: unknown, _c?: (error?: Error) => void): void | Promise<void>;
17
+ getSchemaId(_p: Buffer | MessageToProduce<Key, Value, HeaderKey, HeaderValue>, _t?: BeforeHookPayloadType): Id;
18
+ getSerializers(): Serializers<Key, Value, HeaderKey, HeaderValue>;
19
+ getDeserializers(): Deserializers<Key, Value, HeaderKey, HeaderValue>;
20
+ getBeforeSerializationHook(): BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue>;
21
+ getBeforeDeserializationHook(): BeforeDeserializationHook;
22
+ }
@@ -0,0 +1,38 @@
1
+ export function runAsyncSeries(operation, collection, index, callback) {
2
+ operation(collection[index], error => {
3
+ if (error) {
4
+ callback(error);
5
+ return;
6
+ }
7
+ else if (index === collection.length - 1) {
8
+ callback(null);
9
+ return;
10
+ }
11
+ runAsyncSeries(operation, collection, index + 1, callback);
12
+ });
13
+ }
14
+ /* c8 ignore start */
15
+ export class AbstractSchemaRegistry {
16
+ get(_) {
17
+ throw new Error('AbstractSchemaRegistry.get() should be implemented in subclasses.');
18
+ }
19
+ fetch(_i, _c) {
20
+ throw new Error('AbstractSchemaRegistry.fetch() should be implemented in subclasses.');
21
+ }
22
+ getSchemaId(_p, _t) {
23
+ throw new Error('AbstractSchemaRegistry.getSchemaId() should be implemented in subclasses.');
24
+ }
25
+ getSerializers() {
26
+ throw new Error('AbstractSchemaRegistry.getSerializers() should be implemented in subclasses.');
27
+ }
28
+ getDeserializers() {
29
+ throw new Error('AbstractSchemaRegistry.getDeserializers() should be implemented in subclasses.');
30
+ }
31
+ getBeforeSerializationHook() {
32
+ throw new Error('AbstractSchemaRegistry.getBeforeSerializationHook() should be implemented in subclasses.');
33
+ }
34
+ getBeforeDeserializationHook() {
35
+ throw new Error('AbstractSchemaRegistry.getBeforeDeserializationHook() should be implemented in subclasses.');
36
+ }
37
+ }
38
+ /* c8 ignore end */
@@ -0,0 +1,41 @@
1
+ import { type ValidateFunction } from 'ajv';
2
+ import { type Type } from 'avsc';
3
+ import { type Root } from 'protobufjs';
4
+ import { type Callback } from '../apis/definitions.ts';
5
+ import { type BeforeDeserializationHook, type BeforeHookPayloadType, type BeforeSerializationHook, type Deserializers, type Serializers } from '../clients/serde.ts';
6
+ import { type CredentialProvider } from '../index.ts';
7
+ import { type MessageToConsume, type MessageToProduce } from '../protocol/records.ts';
8
+ import { AbstractSchemaRegistry } from './abstract.ts';
9
+ type ConfluentSchemaRegistryMessageToProduce = MessageToProduce<unknown, unknown, unknown, unknown>;
10
+ export interface ConfluentSchemaRegistryMetadata {
11
+ schemas?: Record<BeforeHookPayloadType, number>;
12
+ }
13
+ export type ConfluentSchemaRegistryProtobufTypeMapper = (id: number, type: BeforeHookPayloadType, context: ConfluentSchemaRegistryMessageToProduce | MessageToConsume) => string;
14
+ export interface ConfluentSchemaRegistryOptions {
15
+ url: string;
16
+ auth?: {
17
+ username?: string | CredentialProvider;
18
+ password?: string | CredentialProvider;
19
+ token?: string | CredentialProvider;
20
+ };
21
+ protobufTypeMapper?: ConfluentSchemaRegistryProtobufTypeMapper;
22
+ jsonValidateSend?: boolean;
23
+ }
24
+ export interface Schema {
25
+ id: number;
26
+ type: 'avro' | 'protobuf' | 'json';
27
+ schema: Type | Root | ValidateFunction;
28
+ }
29
+ export declare function defaultProtobufTypeMapper(_: number, type: BeforeHookPayloadType, context: ConfluentSchemaRegistryMessageToProduce | MessageToConsume): string;
30
+ export declare class ConfluentSchemaRegistry<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends AbstractSchemaRegistry<number | undefined, Schema, Key, Value, HeaderKey, HeaderValue> {
31
+ #private;
32
+ constructor(options: ConfluentSchemaRegistryOptions);
33
+ getSchemaId(message: Buffer | MessageToProduce<Key, Value, HeaderKey, HeaderValue>, type?: BeforeHookPayloadType): number | undefined;
34
+ get(id: number): Schema | undefined;
35
+ fetchSchema(id: number, callback: Callback<void>): Promise<void>;
36
+ getSerializers(): Serializers<Key, Value, HeaderKey, HeaderValue>;
37
+ getDeserializers(): Deserializers<Key, Value, HeaderKey, HeaderValue>;
38
+ getBeforeSerializationHook(): BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue>;
39
+ getBeforeDeserializationHook(): BeforeDeserializationHook;
40
+ }
41
+ export {};
@@ -0,0 +1,222 @@
1
+ import avro from 'avsc';
2
+ import { createRequire } from 'node:module';
3
+ import { jsonDeserializer, jsonSerializer, stringDeserializer, stringSerializer } from "../clients/serde.js";
4
+ import { ajv, EMPTY_BUFFER, UnsupportedFormatError, UserError } from "../index.js";
5
+ import { getCredential } from "../protocol/sasl/utils.js";
6
+ import { AbstractSchemaRegistry } from "./abstract.js";
7
+ const require = createRequire(import.meta.url);
8
+ /* c8 ignore next 8 */
9
+ export function defaultProtobufTypeMapper(_, type, context) {
10
+ // Confluent Schema Registry convention
11
+ return `${context.topic}-${type}`;
12
+ }
13
+ // TODO(ShogunPanda): Authentication support
14
+ export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
15
+ #url;
16
+ #schemas;
17
+ #protobufParse;
18
+ #protobufTypeMapper;
19
+ #jsonValidateSend;
20
+ #auth;
21
+ constructor(options) {
22
+ super();
23
+ this.#url = options.url;
24
+ this.#schemas = new Map();
25
+ this.#protobufTypeMapper = options.protobufTypeMapper ?? defaultProtobufTypeMapper;
26
+ this.#jsonValidateSend = options.jsonValidateSend ?? false;
27
+ this.#auth = options.auth;
28
+ }
29
+ getSchemaId(message, type) {
30
+ if (Buffer.isBuffer(message)) {
31
+ if (type !== 'value') {
32
+ return undefined;
33
+ }
34
+ return message.readInt32BE(1);
35
+ }
36
+ return message.metadata?.schemas?.[type];
37
+ }
38
+ get(id) {
39
+ return this.#schemas.get(id);
40
+ }
41
+ async fetchSchema(id, callback) {
42
+ try {
43
+ const requestInit = {};
44
+ if (this.#auth) {
45
+ if (this.#auth.token) {
46
+ const token = await getCredential('token', this.#auth.token);
47
+ requestInit.headers = {
48
+ Authorization: `Bearer ${token}`
49
+ };
50
+ }
51
+ else {
52
+ const username = await getCredential('username', this.#auth.username);
53
+ const password = await getCredential('password', this.#auth.password);
54
+ requestInit.headers = {
55
+ Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`
56
+ };
57
+ }
58
+ }
59
+ const response = await fetch(`${this.#url}/schemas/ids/${id}`, requestInit);
60
+ if (!response.ok) {
61
+ throw new UserError(`Failed to fetch a schema: [HTTP ${response.status}]`, { response: await response.text() });
62
+ }
63
+ const responseBody = await response.json();
64
+ const { schema, schemaType } = responseBody;
65
+ switch (schemaType) {
66
+ case 'AVRO':
67
+ this.#schemas.set(id, { id, type: 'avro', schema: avro.Type.forSchema(JSON.parse(schema)) });
68
+ break;
69
+ case 'PROTOBUF':
70
+ this.#protobufParse ??= this.#loadProtobuf();
71
+ this.#schemas.set(id, { id, type: 'protobuf', schema: this.#protobufParse(schema).root });
72
+ break;
73
+ case 'JSON':
74
+ this.#schemas.set(id, { id, type: 'json', schema: ajv.compile(JSON.parse(schema)) });
75
+ break;
76
+ }
77
+ process.nextTick(callback);
78
+ }
79
+ catch (err) {
80
+ process.nextTick(() => callback(err));
81
+ }
82
+ }
83
+ getSerializers() {
84
+ return {
85
+ key: this.#schemaSerializer.bind(this, 'key', stringSerializer),
86
+ value: this.#schemaSerializer.bind(this, 'value', jsonSerializer),
87
+ headerKey: this.#schemaSerializer.bind(this, 'headerKey', stringSerializer),
88
+ headerValue: this.#schemaSerializer.bind(this, 'headerValue', jsonSerializer)
89
+ };
90
+ }
91
+ getDeserializers() {
92
+ return {
93
+ key: this.#schemaDeserializer.bind(this, 'key', stringDeserializer),
94
+ value: this.#schemaDeserializer.bind(this, 'value', jsonDeserializer),
95
+ headerKey: this.#schemaDeserializer.bind(this, 'headerKey', stringDeserializer),
96
+ headerValue: this.#schemaDeserializer.bind(this, 'headerValue', jsonDeserializer)
97
+ };
98
+ }
99
+ getBeforeSerializationHook() {
100
+ const registry = this;
101
+ return function beforeSerialization(_, type, message, callback) {
102
+ // Extract the schema ID from the message metadata
103
+ const schemaId = registry.getSchemaId(message, type);
104
+ // When no schema ID is found, nothing to do
105
+ if (!schemaId) {
106
+ callback(null);
107
+ return;
108
+ }
109
+ // The schema is already fetch
110
+ if (registry.get(schemaId)) {
111
+ callback(null);
112
+ return;
113
+ }
114
+ registry.fetchSchema(schemaId, callback);
115
+ };
116
+ }
117
+ getBeforeDeserializationHook() {
118
+ const registry = this;
119
+ return function beforeDeserialization(payload, type, _message, callback) {
120
+ // Extract the schema ID from the message metadata
121
+ const schemaId = registry.getSchemaId(payload, type);
122
+ // When no schema ID is found, nothing to do
123
+ if (!schemaId) {
124
+ callback(null);
125
+ return;
126
+ }
127
+ // The schema is already fetch
128
+ if (registry.get(schemaId)) {
129
+ callback(null);
130
+ return;
131
+ }
132
+ registry.fetchSchema(schemaId, callback);
133
+ };
134
+ }
135
+ #schemaSerializer(type, fallbackSerializer, data, headers, message) {
136
+ /* c8 ignore next 3 - Hard to test */
137
+ if (typeof data === 'undefined') {
138
+ return EMPTY_BUFFER;
139
+ }
140
+ if (type === 'headerKey' || type === 'headerValue') {
141
+ message = headers;
142
+ }
143
+ const schemaId = message?.metadata?.schemas?.[type];
144
+ if (!schemaId) {
145
+ return fallbackSerializer(data);
146
+ }
147
+ const schema = this.#schemas.get(schemaId);
148
+ if (!schema) {
149
+ throw new UserError(`Schema with ID ${schemaId} not found.`, { missingSchema: schemaId });
150
+ }
151
+ let encodedMessage;
152
+ switch (schema.type) {
153
+ case 'avro':
154
+ encodedMessage = schema.schema.toBuffer(data);
155
+ break;
156
+ case 'protobuf':
157
+ {
158
+ const typeName = this.#protobufTypeMapper(schemaId, type, message);
159
+ const Type = schema.schema.lookupType(typeName);
160
+ encodedMessage = Buffer.from(Type.encode(Type.create(data)).finish());
161
+ }
162
+ break;
163
+ case 'json':
164
+ if (this.#jsonValidateSend) {
165
+ const validate = schema.schema;
166
+ const valid = validate(data);
167
+ if (!valid) {
168
+ throw new UserError(`JSON Schema validation failed before serialization: ${ajv.errorsText(validate.errors)}`, { type, data, headers, validationErrors: validate.errors });
169
+ }
170
+ }
171
+ encodedMessage = Buffer.from(JSON.stringify(data));
172
+ break;
173
+ }
174
+ const header = Buffer.alloc(5);
175
+ header.writeInt32BE(schemaId, 1);
176
+ return Buffer.concat([header, encodedMessage]);
177
+ }
178
+ #schemaDeserializer(type, fallbackDeserializer, data, headers, message) {
179
+ /* c8 ignore next 3 - Hard to test */
180
+ if (typeof data === 'undefined' || data.length === 0) {
181
+ return EMPTY_BUFFER;
182
+ }
183
+ if (type === 'headerKey' || type === 'headerValue') {
184
+ message = headers;
185
+ }
186
+ const schemaId = this.getSchemaId(data, type);
187
+ if (!schemaId) {
188
+ return fallbackDeserializer(data);
189
+ }
190
+ const schema = this.#schemas.get(schemaId);
191
+ if (!schema) {
192
+ throw new UserError(`Schema with ID ${schemaId} not found.`, { missingSchema: schemaId });
193
+ }
194
+ switch (schema.type) {
195
+ case 'avro':
196
+ return schema.schema.fromBuffer(data.subarray(5));
197
+ case 'protobuf': {
198
+ const typeName = this.#protobufTypeMapper(schemaId, type, message);
199
+ const Type = schema.schema.lookupType(typeName);
200
+ return Type.decode(data.subarray(5));
201
+ }
202
+ case 'json': {
203
+ const parsed = JSON.parse(data.subarray(5).toString('utf-8'));
204
+ const validate = schema.schema;
205
+ const valid = validate(parsed);
206
+ if (!valid) {
207
+ throw new UserError(`JSON Schema validation failed before deserialization: ${ajv.errorsText(validate.errors)}`, { type, data: parsed, headers, validationErrors: validate.errors });
208
+ }
209
+ return parsed;
210
+ }
211
+ }
212
+ }
213
+ #loadProtobuf() {
214
+ try {
215
+ return require('protobufjs').parse;
216
+ /* c8 ignore next 5 - In tests protobufjs is always available */
217
+ }
218
+ catch (e) {
219
+ throw new UnsupportedFormatError('Cannot load protobufjs module, which is an optionalDependency. Please check your local installation.');
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,2 @@
1
+ export * from './abstract.ts';
2
+ export * from './confluent-schema-registry.ts';
@@ -0,0 +1,2 @@
1
+ export * from "./abstract.js";
2
+ export * from "./confluent-schema-registry.js";
@@ -127,6 +127,12 @@ export declare const consumeOptionsProperties: {
127
127
  type: string;
128
128
  minimum: number;
129
129
  };
130
+ beforeDeserialization: {
131
+ function: boolean;
132
+ };
133
+ registry: {
134
+ type: string;
135
+ };
130
136
  };
131
137
  export declare const groupOptionsSchema: {
132
138
  type: string;
@@ -241,6 +247,12 @@ export declare const consumeOptionsSchema: {
241
247
  type: string;
242
248
  minimum: number;
243
249
  };
250
+ beforeDeserialization: {
251
+ function: boolean;
252
+ };
253
+ registry: {
254
+ type: string;
255
+ };
244
256
  groupInstanceId: {
245
257
  type: string;
246
258
  pattern: string;
@@ -396,6 +408,12 @@ export declare const consumerOptionsSchema: {
396
408
  type: string;
397
409
  minimum: number;
398
410
  };
411
+ beforeDeserialization: {
412
+ function: boolean;
413
+ };
414
+ registry: {
415
+ type: string;
416
+ };
399
417
  groupInstanceId: {
400
418
  type: string;
401
419
  pattern: string;
@@ -511,6 +529,12 @@ export declare const fetchOptionsSchema: {
511
529
  type: string;
512
530
  minimum: number;
513
531
  };
532
+ beforeDeserialization: {
533
+ function: boolean;
534
+ };
535
+ registry: {
536
+ type: string;
537
+ };
514
538
  groupInstanceId: {
515
539
  type: string;
516
540
  pattern: string;
@@ -2,8 +2,9 @@ import { type FetchRequestTopic } from "../../apis/consumer/fetch-v17";
2
2
  import { type GroupProtocols } from "../../apis/enumerations";
3
3
  import { type ConnectionPool } from "../../network/connection-pool";
4
4
  import { type KafkaRecord, type Message } from "../../protocol/records";
5
+ import { type SchemaRegistry } from "../../registries/abstract";
5
6
  import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from "../base/types";
6
- import { type Deserializers } from "../serde";
7
+ import { type BeforeDeserializationHook, type Deserializers } from "../serde";
7
8
  export interface GroupProtocolSubscription {
8
9
  name: string;
9
10
  version: number;
@@ -68,6 +69,8 @@ export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
68
69
  isolationLevel?: number;
69
70
  deserializers?: Partial<Deserializers<Key, Value, HeaderKey, HeaderValue>>;
70
71
  highWaterMark?: number;
72
+ beforeDeserialization?: BeforeDeserializationHook;
73
+ registry?: SchemaRegistry<unknown, unknown, Key, Value, HeaderKey, HeaderValue>;
71
74
  }
72
75
  export interface StreamOptions {
73
76
  topics: string[];
@@ -121,24 +121,8 @@ export declare const sendOptionsSchema: {
121
121
  items: {
122
122
  type: string;
123
123
  properties: {
124
- key: {
125
- oneOf: ({
126
- type: string;
127
- buffer?: undefined;
128
- } | {
129
- buffer: boolean;
130
- type?: undefined;
131
- })[];
132
- };
133
- value: {
134
- oneOf: ({
135
- type: string;
136
- buffer?: undefined;
137
- } | {
138
- buffer: boolean;
139
- type?: undefined;
140
- })[];
141
- };
124
+ key: boolean;
125
+ value: boolean;
142
126
  headers: {
143
127
  anyOf: ({
144
128
  map: boolean;
@@ -1,7 +1,8 @@
1
1
  import { type CompressionAlgorithmValue } from "../../protocol/compression";
2
2
  import { type MessageToProduce } from "../../protocol/records";
3
+ import { type SchemaRegistry } from "../../registries/abstract";
3
4
  import { type BaseOptions, type TopicWithPartitionAndOffset } from "../base/types";
4
- import { type Serializers } from "../serde";
5
+ import { type BeforeSerializationHook, type Serializers } from "../serde";
5
6
  export interface ProducerInfo {
6
7
  producerId: bigint;
7
8
  producerEpoch: number;
@@ -25,6 +26,8 @@ export interface ProduceOptions<Key, Value, HeaderKey, HeaderValue> {
25
26
  export type ProducerOptions<Key, Value, HeaderKey, HeaderValue> = BaseOptions & ProduceOptions<Key, Value, HeaderKey, HeaderValue> & {
26
27
  transactionalId?: string;
27
28
  serializers?: Partial<Serializers<Key, Value, HeaderKey, HeaderValue>>;
29
+ beforeSerialization?: BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue>;
30
+ registry?: SchemaRegistry<unknown, unknown, Key, Value, HeaderKey, HeaderValue>;
28
31
  };
29
32
  export type SendOptions<Key, Value, HeaderKey, HeaderValue> = {
30
33
  messages: MessageToProduce<Key, Value, HeaderKey, HeaderValue>[];
@@ -1,7 +1,9 @@
1
- export type Serializer<InputType = unknown> = (data?: InputType) => Buffer | undefined;
2
- export type Deserializer<OutputType = unknown> = (data?: Buffer) => OutputType | undefined;
3
- export type SerializerWithHeaders<InputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: InputType, headers?: Map<HeaderKey, HeaderValue>) => Buffer | undefined;
4
- export type DeserializerWithHeaders<OutputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: Buffer, headers?: Map<HeaderKey, HeaderValue>) => OutputType | undefined;
1
+ import { type Callback } from "../apis/definitions";
2
+ import { type MessageToConsume, type MessageToProduce } from "../protocol/records";
3
+ export type Serializer<InputType = unknown> = (data?: InputType, metadata?: unknown) => Buffer | undefined;
4
+ export type SerializerWithHeaders<InputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: InputType, headers?: Map<HeaderKey, HeaderValue>, message?: MessageToProduce<unknown, unknown, unknown, unknown>) => Buffer | undefined;
5
+ export type Deserializer<OutputType = unknown> = (data?: Buffer, message?: MessageToConsume) => OutputType | undefined;
6
+ export type DeserializerWithHeaders<OutputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: Buffer, headers?: Map<HeaderKey, HeaderValue>, message?: MessageToConsume) => OutputType | undefined;
5
7
  export interface Serializers<Key, Value, HeaderKey, HeaderValue> {
6
8
  key: SerializerWithHeaders<Key, HeaderKey, HeaderValue>;
7
9
  value: SerializerWithHeaders<Value, HeaderKey, HeaderValue>;
@@ -9,11 +11,14 @@ export interface Serializers<Key, Value, HeaderKey, HeaderValue> {
9
11
  headerValue: Serializer<HeaderValue>;
10
12
  }
11
13
  export interface Deserializers<Key, Value, HeaderKey, HeaderValue> {
12
- key: DeserializerWithHeaders<Key>;
13
- value: DeserializerWithHeaders<Value>;
14
+ key: DeserializerWithHeaders<Key, HeaderKey, HeaderValue>;
15
+ value: DeserializerWithHeaders<Value, HeaderKey, HeaderValue>;
14
16
  headerKey: Deserializer<HeaderKey>;
15
17
  headerValue: Deserializer<HeaderValue>;
16
18
  }
19
+ export type BeforeHookPayloadType = 'key' | 'value' | 'headerKey' | 'headerValue';
20
+ export type BeforeDeserializationHook = (payload: Buffer, type: BeforeHookPayloadType, message: MessageToConsume, callback: Callback<void>) => void | Promise<void>;
21
+ export type BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue> = (payload: unknown, type: BeforeHookPayloadType, message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, callback: Callback<void>) => void | Promise<void>;
17
22
  export declare function stringSerializer(data?: string): Buffer | undefined;
18
23
  export declare function stringDeserializer(data?: string | Buffer): string | undefined;
19
24
  export declare function jsonSerializer<T = Record<string, any>>(data?: T): Buffer | undefined;
@@ -2,7 +2,7 @@ import { type NullableString } from "./protocol/definitions";
2
2
  declare const kGenericError: unique symbol;
3
3
  declare const kMultipleErrors: unique symbol;
4
4
  export declare const ERROR_PREFIX = "PLT_KFK_";
5
- export declare const errorCodes: readonly ["PLT_KFK_AUTHENTICATION", "PLT_KFK_MULTIPLE", "PLT_KFK_NETWORK", "PLT_KFK_OUT_OF_BOUNDS", "PLT_KFK_PROTOCOL", "PLT_KFK_RESPONSE", "PLT_KFK_TIMEOUT", "PLT_KFK_UNEXPECTED_CORRELATION_ID", "PLT_KFK_UNFINISHED_WRITE_BUFFER", "PLT_KFK_UNSUPPORTED_API", "PLT_KFK_UNSUPPORTED_COMPRESSION", "PLT_KFK_UNSUPPORTED", "PLT_KFK_USER"];
5
+ export declare const errorCodes: readonly ["PLT_KFK_AUTHENTICATION", "PLT_KFK_MULTIPLE", "PLT_KFK_NETWORK", "PLT_KFK_OUT_OF_BOUNDS", "PLT_KFK_PROTOCOL", "PLT_KFK_RESPONSE", "PLT_KFK_TIMEOUT", "PLT_KFK_UNEXPECTED_CORRELATION_ID", "PLT_KFK_UNFINISHED_WRITE_BUFFER", "PLT_KFK_UNSUPPORTED_API", "PLT_KFK_UNSUPPORTED_COMPRESSION", "PLT_KFK_UNSUPPORTED_FORMAT", "PLT_KFK_UNSUPPORTED", "PLT_KFK_USER"];
6
6
  export type ErrorCode = (typeof errorCodes)[number];
7
7
  export type ErrorProperties = {
8
8
  cause?: Error;
@@ -73,6 +73,10 @@ export declare class UnsupportedCompressionError extends GenericError {
73
73
  static code: ErrorCode;
74
74
  constructor(message: string, properties?: ErrorProperties);
75
75
  }
76
+ export declare class UnsupportedFormatError extends GenericError {
77
+ static code: ErrorCode;
78
+ constructor(message: string, properties?: ErrorProperties);
79
+ }
76
80
  export declare class UnsupportedError extends GenericError {
77
81
  static code: ErrorCode;
78
82
  constructor(message: string, properties?: ErrorProperties);