@platformatic/kafka 2.5.0 → 2.6.1

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.
@@ -1,4 +1,4 @@
1
- import { type ValidateFunction } from 'ajv';
1
+ import { type Options, type ValidateFunction } from 'ajv';
2
2
  import { type Type } from 'avsc';
3
3
  import { type Root } from 'protobufjs';
4
4
  import { type Callback } from '../apis/definitions.ts';
@@ -20,6 +20,7 @@ export interface ConfluentSchemaRegistryOptions {
20
20
  };
21
21
  protobufTypeMapper?: ConfluentSchemaRegistryProtobufTypeMapper;
22
22
  jsonValidateSend?: boolean;
23
+ jsonAjvOptions?: Options;
23
24
  }
24
25
  export interface Schema {
25
26
  id: number;
@@ -1,7 +1,8 @@
1
+ import { Ajv2020 } from 'ajv/dist/2020.js';
1
2
  import avro from 'avsc';
2
3
  import { createRequire } from 'node:module';
3
4
  import { jsonDeserializer, jsonSerializer, stringDeserializer, stringSerializer } from "../clients/serde.js";
4
- import { ajv, EMPTY_BUFFER, UnsupportedFormatError, UserError } from "../index.js";
5
+ import { EMPTY_BUFFER, UnsupportedFormatError, UserError } from "../index.js";
5
6
  import { getCredential } from "../protocol/sasl/utils.js";
6
7
  import { AbstractSchemaRegistry } from "./abstract.js";
7
8
  const require = createRequire(import.meta.url);
@@ -10,13 +11,14 @@ export function defaultProtobufTypeMapper(_, type, context) {
10
11
  // Confluent Schema Registry convention
11
12
  return `${context.topic}-${type}`;
12
13
  }
13
- // TODO(ShogunPanda): Authentication support
14
14
  export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
15
15
  #url;
16
16
  #schemas;
17
17
  #protobufParse;
18
18
  #protobufTypeMapper;
19
19
  #jsonValidateSend;
20
+ #jsonAjv;
21
+ #pendingFetches;
20
22
  #auth;
21
23
  constructor(options) {
22
24
  super();
@@ -24,7 +26,9 @@ export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
24
26
  this.#schemas = new Map();
25
27
  this.#protobufTypeMapper = options.protobufTypeMapper ?? defaultProtobufTypeMapper;
26
28
  this.#jsonValidateSend = options.jsonValidateSend ?? false;
29
+ this.#jsonAjv = new Ajv2020({ allErrors: true, coerceTypes: false, strict: true, ...options.jsonAjvOptions });
27
30
  this.#auth = options.auth;
31
+ this.#pendingFetches = new Map();
28
32
  }
29
33
  getSchemaId(message, type) {
30
34
  if (message === null) {
@@ -42,42 +46,15 @@ export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
42
46
  return this.#schemas.get(id);
43
47
  }
44
48
  async fetchSchema(id, callback) {
49
+ let fetch = this.#pendingFetches.get(id);
50
+ if (!fetch) {
51
+ fetch = this.#fetchSchema(id).finally(() => {
52
+ this.#pendingFetches.delete(id);
53
+ });
54
+ this.#pendingFetches.set(id, fetch);
55
+ }
45
56
  try {
46
- const requestInit = {};
47
- if (this.#auth) {
48
- if (this.#auth.token) {
49
- const token = await getCredential('token', this.#auth.token);
50
- requestInit.headers = {
51
- Authorization: `Bearer ${token}`
52
- };
53
- }
54
- else {
55
- const username = await getCredential('username', this.#auth.username);
56
- const password = await getCredential('password', this.#auth.password);
57
- requestInit.headers = {
58
- Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`
59
- };
60
- }
61
- }
62
- const response = await fetch(`${this.#url}/schemas/ids/${id}`, requestInit);
63
- if (!response.ok) {
64
- throw new UserError(`Failed to fetch a schema: [HTTP ${response.status}]`, { response: await response.text() });
65
- }
66
- const responseBody = (await response.json());
67
- const { schema } = responseBody;
68
- const schemaType = responseBody.schemaType ?? 'AVRO';
69
- switch (schemaType) {
70
- case 'AVRO':
71
- this.#schemas.set(id, { id, type: 'avro', schema: avro.Type.forSchema(JSON.parse(schema)) });
72
- break;
73
- case 'PROTOBUF':
74
- this.#protobufParse ??= this.#loadProtobuf();
75
- this.#schemas.set(id, { id, type: 'protobuf', schema: this.#protobufParse(schema).root });
76
- break;
77
- case 'JSON':
78
- this.#schemas.set(id, { id, type: 'json', schema: ajv.compile(JSON.parse(schema)) });
79
- break;
80
- }
57
+ await fetch;
81
58
  process.nextTick(callback);
82
59
  }
83
60
  catch (err) {
@@ -136,6 +113,43 @@ export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
136
113
  registry.fetchSchema(schemaId, callback);
137
114
  };
138
115
  }
116
+ async #fetchSchema(id) {
117
+ const requestInit = {};
118
+ if (this.#auth) {
119
+ if (this.#auth.token) {
120
+ const token = await getCredential('token', this.#auth.token);
121
+ requestInit.headers = {
122
+ Authorization: `Bearer ${token}`
123
+ };
124
+ }
125
+ else {
126
+ const username = await getCredential('username', this.#auth.username);
127
+ const password = await getCredential('password', this.#auth.password);
128
+ requestInit.headers = {
129
+ Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`
130
+ };
131
+ }
132
+ }
133
+ const response = await fetch(`${this.#url}/schemas/ids/${id}`, requestInit);
134
+ if (!response.ok) {
135
+ throw new UserError(`Failed to fetch a schema: [HTTP ${response.status}]`, { response: await response.text() });
136
+ }
137
+ const responseBody = (await response.json());
138
+ const { schema } = responseBody;
139
+ const schemaType = responseBody.schemaType ?? 'AVRO';
140
+ switch (schemaType) {
141
+ case 'AVRO':
142
+ this.#schemas.set(id, { id, type: 'avro', schema: avro.Type.forSchema(JSON.parse(schema)) });
143
+ break;
144
+ case 'PROTOBUF':
145
+ this.#protobufParse ??= this.#loadProtobuf();
146
+ this.#schemas.set(id, { id, type: 'protobuf', schema: this.#protobufParse(schema).root });
147
+ break;
148
+ case 'JSON':
149
+ this.#schemas.set(id, { id, type: 'json', schema: this.#jsonAjv.compile(JSON.parse(schema)) });
150
+ break;
151
+ }
152
+ }
139
153
  #schemaSerializer(type, fallbackSerializer, data, headers, message) {
140
154
  /* c8 ignore next 3 - Hard to test */
141
155
  if (typeof data === 'undefined') {
@@ -169,7 +183,7 @@ export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
169
183
  const validate = schema.schema;
170
184
  const valid = validate(data);
171
185
  if (!valid) {
172
- throw new UserError(`JSON Schema validation failed before serialization: ${ajv.errorsText(validate.errors)}`, { type, data, headers, validationErrors: validate.errors });
186
+ throw new UserError(`JSON Schema validation failed before serialization: ${this.#jsonAjv.errorsText(validate.errors)}`, { type, data, headers, validationErrors: validate.errors });
173
187
  }
174
188
  }
175
189
  encodedMessage = Buffer.from(JSON.stringify(data));
@@ -208,7 +222,7 @@ export class ConfluentSchemaRegistry extends AbstractSchemaRegistry {
208
222
  const validate = schema.schema;
209
223
  const valid = validate(parsed);
210
224
  if (!valid) {
211
- throw new UserError(`JSON Schema validation failed before deserialization: ${ajv.errorsText(validate.errors)}`, { type, data: parsed, headers, validationErrors: validate.errors });
225
+ throw new UserError(`JSON Schema validation failed before deserialization: ${this.#jsonAjv.errorsText(validate.errors)}`, { type, data: parsed, headers, validationErrors: validate.errors });
212
226
  }
213
227
  return parsed;
214
228
  }
@@ -1,4 +1,4 @@
1
- import { type ValidateFunction } from 'ajv';
1
+ import { type Options, type ValidateFunction } from 'ajv';
2
2
  import { type Type } from 'avsc';
3
3
  import { type Root } from 'protobufjs';
4
4
  import { type Callback } from "../apis/definitions";
@@ -20,6 +20,7 @@ export interface ConfluentSchemaRegistryOptions {
20
20
  };
21
21
  protobufTypeMapper?: ConfluentSchemaRegistryProtobufTypeMapper;
22
22
  jsonValidateSend?: boolean;
23
+ jsonAjvOptions?: Options;
23
24
  }
24
25
  export interface Schema {
25
26
  id: number;
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = "@platformatic/kafka";
2
- export const version = "2.5.0";
2
+ export const version = "2.6.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/kafka",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "description": "Modern and performant client for Apache Kafka",
5
5
  "homepage": "https://github.com/platformatic/kafka",
6
6
  "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",