@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.
- package/README.md +58 -0
- package/dist/clients/consumer/consumer.js +24 -1
- package/dist/clients/consumer/messages-stream.js +66 -10
- package/dist/clients/consumer/options.d.ts +24 -0
- package/dist/clients/consumer/options.js +3 -1
- package/dist/clients/consumer/types.d.ts +4 -1
- package/dist/clients/producer/options.d.ts +2 -18
- package/dist/clients/producer/options.js +3 -1
- package/dist/clients/producer/producer.js +75 -15
- package/dist/clients/producer/types.d.ts +4 -1
- package/dist/clients/serde.d.ts +11 -6
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/network/connection.d.ts +7 -6
- package/dist/protocol/definitions.js +1 -1
- package/dist/protocol/reader.js +1 -1
- package/dist/protocol/records.d.ts +7 -18
- package/dist/protocol/records.js +2 -6
- package/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
- package/dist/protocol/sasl/plain.d.ts +3 -3
- package/dist/protocol/sasl/scram-sha.d.ts +3 -3
- package/dist/protocol/sasl/utils.d.ts +3 -3
- package/dist/protocol/writer.js +1 -1
- package/dist/registries/abstract.d.ts +22 -0
- package/dist/registries/abstract.js +38 -0
- package/dist/registries/confluent-schema-registry.d.ts +41 -0
- package/dist/registries/confluent-schema-registry.js +222 -0
- package/dist/registries/index.d.ts +2 -0
- package/dist/registries/index.js +2 -0
- package/dist/typescript-4/dist/clients/consumer/options.d.ts +24 -0
- package/dist/typescript-4/dist/clients/consumer/types.d.ts +4 -1
- package/dist/typescript-4/dist/clients/producer/options.d.ts +2 -18
- package/dist/typescript-4/dist/clients/producer/types.d.ts +4 -1
- package/dist/typescript-4/dist/clients/serde.d.ts +11 -6
- package/dist/typescript-4/dist/errors.d.ts +5 -1
- package/dist/typescript-4/dist/index.d.ts +2 -1
- package/dist/typescript-4/dist/network/connection.d.ts +7 -6
- package/dist/typescript-4/dist/protocol/records.d.ts +7 -18
- package/dist/typescript-4/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
- package/dist/typescript-4/dist/protocol/sasl/plain.d.ts +3 -3
- package/dist/typescript-4/dist/protocol/sasl/scram-sha.d.ts +3 -3
- package/dist/typescript-4/dist/protocol/sasl/utils.d.ts +3 -3
- package/dist/typescript-4/dist/registries/abstract.d.ts +22 -0
- package/dist/typescript-4/dist/registries/confluent-schema-registry.d.ts +41 -0
- package/dist/typescript-4/dist/registries/index.d.ts +2 -0
- package/dist/version.js +1 -1
- 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
|
|
10
|
+
// TODO: Tagged fields are not supported yet
|
|
11
11
|
export const EMPTY_TAGGED_FIELDS_BUFFER = Buffer.from([0]);
|
package/dist/protocol/reader.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/protocol/records.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 |
|
|
6
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string |
|
|
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
|
|
4
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string |
|
|
5
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string |
|
|
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
|
|
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 |
|
|
39
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string |
|
|
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
|
|
3
|
-
export declare function getCredential<T>(label: string, credentialOrProvider: T |
|
|
4
|
-
export declare function getCredential<T>(label: string, credentialOrProvider: 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>;
|
package/dist/protocol/writer.js
CHANGED
|
@@ -210,7 +210,7 @@ export class Writer {
|
|
|
210
210
|
}
|
|
211
211
|
return this;
|
|
212
212
|
}
|
|
213
|
-
// TODO
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
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);
|