@confluentinc/schemaregistry
Version:
Node.js client for Confluent Schema Registry
544 lines (543 loc) • 24.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaRegistryClient = exports.RuleMode = exports.RulePhase = exports.Compatibility = void 0;
exports.minimize = minimize;
const rest_service_1 = require("./rest-service");
const json_stringify_deterministic_1 = __importDefault(require("json-stringify-deterministic"));
const lru_cache_1 = require("lru-cache");
const async_mutex_1 = require("async-mutex");
const mock_schemaregistry_client_1 = require("./mock-schemaregistry-client");
/*
* Confluent-Schema-Registry-TypeScript - Node.js wrapper for Confluent Schema Registry
*
* Copyright (c) 2024 Confluent, Inc.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE.txt file for details.
*/
var Compatibility;
(function (Compatibility) {
Compatibility["NONE"] = "NONE";
Compatibility["BACKWARD"] = "BACKWARD";
Compatibility["FORWARD"] = "FORWARD";
Compatibility["FULL"] = "FULL";
Compatibility["BACKWARD_TRANSITIVE"] = "BACKWARD_TRANSITIVE";
Compatibility["FORWARD_TRANSITIVE"] = "FORWARD_TRANSITIVE";
Compatibility["FULL_TRANSITIVE"] = "FULL_TRANSITIVE";
})(Compatibility || (exports.Compatibility = Compatibility = {}));
var RulePhase;
(function (RulePhase) {
RulePhase["MIGRATION"] = "MIGRATION";
RulePhase["DOMAIN"] = "DOMAIN";
RulePhase["ENCODING"] = "ENCODING";
})(RulePhase || (exports.RulePhase = RulePhase = {}));
var RuleMode;
(function (RuleMode) {
RuleMode["UPGRADE"] = "UPGRADE";
RuleMode["DOWNGRADE"] = "DOWNGRADE";
RuleMode["UPDOWN"] = "UPDOWN";
RuleMode["WRITE"] = "WRITE";
RuleMode["READ"] = "READ";
RuleMode["WRITEREAD"] = "WRITEREAD";
})(RuleMode || (exports.RuleMode = RuleMode = {}));
// Ensure that SchemaMetadata fields are removed from the SchemaInfo
function minimize(info) {
return {
schemaType: info.schemaType,
schema: info.schema,
references: info.references,
metadata: info.metadata,
ruleSet: info.ruleSet
};
}
/**
* SchemaRegistryClient is a client for interacting with the Confluent Schema Registry.
* This client will cache responses from Schema Registry to reduce network requests.
*/
class SchemaRegistryClient {
/**
* Create a new Schema Registry client.
* @param config - The client configuration.
*/
constructor(config) {
this.clientConfig = config;
const cacheOptions = {
max: config.cacheCapacity !== undefined ? config.cacheCapacity : 1000,
...(config.cacheLatestTtlSecs !== undefined && { ttl: config.cacheLatestTtlSecs * 1000 })
};
this.restService = new rest_service_1.RestService(config.baseURLs, config.isForward, config.createAxiosDefaults, config.basicAuthCredentials, config.bearerAuthCredentials, config.maxRetries, config.retriesWaitMs, config.retriesMaxWaitMs);
this.idToSchemaInfoCache = new lru_cache_1.LRUCache(cacheOptions);
this.guidToSchemaInfoCache = new lru_cache_1.LRUCache(cacheOptions);
this.infoToSchemaCache = new lru_cache_1.LRUCache(cacheOptions);
this.latestToSchemaCache = new lru_cache_1.LRUCache(cacheOptions);
this.schemaToVersionCache = new lru_cache_1.LRUCache(cacheOptions);
this.versionToSchemaCache = new lru_cache_1.LRUCache(cacheOptions);
this.metadataToSchemaCache = new lru_cache_1.LRUCache(cacheOptions);
this.schemaToIdMutex = new async_mutex_1.Mutex();
this.idToSchemaInfoMutex = new async_mutex_1.Mutex();
this.guidToSchemaInfoMutex = new async_mutex_1.Mutex();
this.infoToSchemaMutex = new async_mutex_1.Mutex();
this.latestToSchemaMutex = new async_mutex_1.Mutex();
this.schemaToVersionMutex = new async_mutex_1.Mutex();
this.versionToSchemaMutex = new async_mutex_1.Mutex();
this.metadataToSchemaMutex = new async_mutex_1.Mutex();
}
static newClient(config) {
let url = config.baseURLs[0];
if (url.startsWith("mock://")) {
return new mock_schemaregistry_client_1.MockClient(config);
}
return new SchemaRegistryClient(config);
}
config() {
return this.clientConfig;
}
/**
* Register a schema with the Schema Registry and return the schema ID.
* @param subject - The subject under which to register the schema.
* @param schema - The schema to register.
* @param normalize - Whether to normalize the schema before registering.
*/
async register(subject, schema, normalize = false) {
const metadataResult = await this.registerFullResponse(subject, schema, normalize);
return metadataResult.id;
}
/**
* Register a schema with the Schema Registry and return the full response.
* @param subject - The subject under which to register the schema.
* @param schema - The schema to register.
* @param normalize - Whether to normalize the schema before registering.
*/
async registerFullResponse(subject, schema, normalize = false) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: minimize(schema) });
return await this.infoToSchemaMutex.runExclusive(async () => {
const cachedSchemaMetadata = this.infoToSchemaCache.get(cacheKey);
if (cachedSchemaMetadata) {
return cachedSchemaMetadata;
}
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/subjects/${subject}/versions?normalize=${normalize}`, 'POST', schema);
this.infoToSchemaCache.set(cacheKey, response.data);
return response.data;
});
}
/**
* Get a schema by subject and ID.
* @param subject - The subject under which the schema is registered.
* @param id - The schema ID.
* @param format - The format of the schema.
*/
async getBySubjectAndId(subject, id, format) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, id });
return await this.idToSchemaInfoMutex.runExclusive(async () => {
const cachedSchema = this.idToSchemaInfoCache.get(cacheKey);
if (cachedSchema) {
return cachedSchema;
}
subject = encodeURIComponent(subject);
let formatStr = format != null ? `&format=${format}` : '';
const response = await this.restService.handleRequest(`/schemas/ids/${id}?subject=${subject}${formatStr}`, 'GET');
this.idToSchemaInfoCache.set(cacheKey, response.data);
return response.data;
});
}
/**
* Get a schema by GUID.
* @param guid - The schema GUID.
* @param format - The format of the schema.
*/
async getByGuid(guid, format) {
return await this.guidToSchemaInfoMutex.runExclusive(async () => {
const cachedSchema = this.guidToSchemaInfoCache.get(guid);
if (cachedSchema) {
return cachedSchema;
}
let formatStr = format != null ? `?format=${format}` : '';
const response = await this.restService.handleRequest(`/schemas/guids/${guid}${formatStr}`, 'GET');
this.guidToSchemaInfoCache.set(guid, response.data);
return response.data;
});
}
/**
* Get the ID for a schema.
* @param subject - The subject under which the schema is registered.
* @param schema - The schema whose ID to get.
* @param normalize - Whether to normalize the schema before getting the ID.
*/
async getId(subject, schema, normalize = false) {
const metadataResult = await this.getIdFullResponse(subject, schema, normalize);
return metadataResult.id;
}
/**
* Get the ID for a schema.
* @param subject - The subject under which the schema is registered.
* @param schema - The schema whose ID to get.
* @param normalize - Whether to normalize the schema before getting the ID.
*/
async getIdFullResponse(subject, schema, normalize = false) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: minimize(schema) });
return await this.schemaToIdMutex.runExclusive(async () => {
const cachedSchemaMetadata = this.infoToSchemaCache.get(cacheKey);
if (cachedSchemaMetadata) {
// Allow the schema to be looked up again if version is not valid
// This is for backward compatibility with versions before CP 8.0
if (cachedSchemaMetadata.version != null && cachedSchemaMetadata.version > 0) {
return cachedSchemaMetadata;
}
}
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/subjects/${subject}?normalize=${normalize}`, 'POST', schema);
this.infoToSchemaCache.set(cacheKey, response.data);
return response.data;
});
}
/**
* Get the latest schema metadata for a subject.
* @param subject - The subject for which to get the latest schema metadata.
* @param format - The format of the schema.
*/
async getLatestSchemaMetadata(subject, format) {
return await this.latestToSchemaMutex.runExclusive(async () => {
const cachedSchema = this.latestToSchemaCache.get(subject);
if (cachedSchema) {
return cachedSchema;
}
subject = encodeURIComponent(subject);
let formatStr = format != null ? `?format=${format}` : '';
const response = await this.restService.handleRequest(`/subjects/${subject}/versions/latest${formatStr}`, 'GET');
this.latestToSchemaCache.set(subject, response.data);
return response.data;
});
}
/**
* Get the schema metadata for a subject and version.
* @param subject - The subject for which to get the schema metadata.
* @param version - The version of the schema.
* @param deleted - Whether to include deleted schemas.
* @param format - The format of the schema.
*/
async getSchemaMetadata(subject, version, deleted = false, format) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, version, deleted });
return await this.versionToSchemaMutex.runExclusive(async () => {
const cachedSchemaMetadata = this.versionToSchemaCache.get(cacheKey);
if (cachedSchemaMetadata) {
return cachedSchemaMetadata;
}
subject = encodeURIComponent(subject);
let formatStr = format != null ? `&format=${format}` : '';
const response = await this.restService.handleRequest(`/subjects/${subject}/versions/${version}?deleted=${deleted}${formatStr}`, 'GET');
this.versionToSchemaCache.set(cacheKey, response.data);
return response.data;
});
}
/**
* Get the latest schema metadata for a subject with the given metadata.
* @param subject - The subject for which to get the latest schema metadata.
* @param metadata - The metadata to match.
* @param deleted - Whether to include deleted schemas.
* @param format - The format of the schema.
*/
async getLatestWithMetadata(subject, metadata, deleted = false, format) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, metadata, deleted });
return await this.metadataToSchemaMutex.runExclusive(async () => {
const cachedSchemaMetadata = this.metadataToSchemaCache.get(cacheKey);
if (cachedSchemaMetadata) {
return cachedSchemaMetadata;
}
subject = encodeURIComponent(subject);
let metadataStr = '';
for (const key in metadata) {
const encodedKey = encodeURIComponent(key);
const encodedValue = encodeURIComponent(metadata[key]);
metadataStr += `&key=${encodedKey}&value=${encodedValue}`;
}
let formatStr = format != null ? `&format=${format}` : '';
const response = await this.restService.handleRequest(`/subjects/${subject}/metadata?deleted=${deleted}&${metadataStr}${formatStr}`, 'GET');
this.metadataToSchemaCache.set(cacheKey, response.data);
return response.data;
});
}
/**
* Get all versions of a schema for a subject.
* @param subject - The subject for which to get all versions.
*/
async getAllVersions(subject) {
const response = await this.restService.handleRequest(`/subjects/${subject}/versions`, 'GET');
return response.data;
}
/**
* Get the version of a schema for a subject.
* @param subject - The subject for which to get the version.
* @param schema - The schema for which to get the version.
* @param normalize - Whether to normalize the schema before getting the version.
*/
async getVersion(subject, schema, normalize = false, deleted = false) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: minimize(schema), deleted });
return await this.schemaToVersionMutex.runExclusive(async () => {
const cachedVersion = this.schemaToVersionCache.get(cacheKey);
if (cachedVersion) {
return cachedVersion;
}
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/subjects/${subject}?normalize=${normalize}&deleted=${deleted}`, 'POST', schema);
this.schemaToVersionCache.set(cacheKey, response.data.version);
return response.data.version;
});
}
/**
* Get all subjects in the Schema Registry.
*/
async getAllSubjects() {
const response = await this.restService.handleRequest(`/subjects`, 'GET');
return response.data;
}
/**
* Delete a subject from the Schema Registry.
* @param subject - The subject to delete.
* @param permanent - Whether to permanently delete the subject.
*/
async deleteSubject(subject, permanent = false) {
await this.infoToSchemaMutex.runExclusive(async () => {
this.infoToSchemaCache.forEach((_, key) => {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject) {
this.infoToSchemaCache.delete(key);
}
});
});
await this.schemaToVersionMutex.runExclusive(async () => {
this.schemaToVersionCache.forEach((_, key) => {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject) {
this.schemaToVersionCache.delete(key);
}
});
});
await this.versionToSchemaMutex.runExclusive(async () => {
this.versionToSchemaCache.forEach((_, key) => {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject) {
this.versionToSchemaCache.delete(key);
}
});
});
await this.idToSchemaInfoMutex.runExclusive(async () => {
this.idToSchemaInfoCache.forEach((_, key) => {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject) {
this.idToSchemaInfoCache.delete(key);
}
});
});
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/subjects/${subject}?permanent=${permanent}`, 'DELETE');
return response.data;
}
/**
* Delete a version of a subject from the Schema Registry.
* @param subject - The subject to delete.
* @param version - The version to delete.
* @param permanent - Whether to permanently delete the version.
*/
async deleteSubjectVersion(subject, version, permanent = false) {
return await this.schemaToVersionMutex.runExclusive(async () => {
let metadataValue;
this.schemaToVersionCache.forEach((value, key) => {
const parsedKey = JSON.parse(key);
if (parsedKey.subject === subject && value === version) {
this.schemaToVersionCache.delete(key);
const infoToSchemaCacheKey = (0, json_stringify_deterministic_1.default)({ subject: subject, schema: minimize(parsedKey.schema) });
this.infoToSchemaMutex.runExclusive(async () => {
metadataValue = this.infoToSchemaCache.get(infoToSchemaCacheKey);
if (metadataValue) {
this.infoToSchemaCache.delete(infoToSchemaCacheKey);
const cacheKeyID = (0, json_stringify_deterministic_1.default)({ subject: subject, id: metadataValue.id });
this.idToSchemaInfoMutex.runExclusive(async () => {
this.idToSchemaInfoCache.delete(cacheKeyID);
});
}
});
}
});
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject: subject, version: version });
this.versionToSchemaMutex.runExclusive(async () => {
this.versionToSchemaCache.delete(cacheKey);
});
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/subjects/${subject}/versions/${version}?permanent=${permanent}`, 'DELETE');
return response.data;
});
}
/**
* Test the compatibility of a schema with the latest schema for a subject.
* @param subject - The subject for which to test compatibility.
* @param schema - The schema to test compatibility.
*/
async testSubjectCompatibility(subject, schema) {
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/compatibility/subjects/${subject}/versions/latest`, 'POST', schema);
return response.data.is_compatible;
}
/**
* Test the compatibility of a schema with a specific version of a subject.
* @param subject - The subject for which to test compatibility.
* @param version - The version of the schema for which to test compatibility.
* @param schema - The schema to test compatibility.
*/
async testCompatibility(subject, version, schema) {
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/compatibility/subjects/${subject}/versions/${version}`, 'POST', schema);
return response.data.is_compatible;
}
/**
* Get the compatibility level for a subject.
* @param subject - The subject for which to get the compatibility level.
*/
async getCompatibility(subject) {
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/config/${subject}`, 'GET');
return response.data.compatibilityLevel;
}
/**
* Update the compatibility level for a subject.
* @param subject - The subject for which to update the compatibility level.
* @param update - The compatibility level to update to.
*/
async updateCompatibility(subject, update) {
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/config/${subject}`, 'PUT', { compatibility: update });
return response.data.compatibility;
}
/**
* Get the default/global compatibility level.
*/
async getDefaultCompatibility() {
const response = await this.restService.handleRequest(`/config`, 'GET');
return response.data.compatibilityLevel;
}
/**
* Update the default/global compatibility level.
* @param update - The compatibility level to update to.
*/
async updateDefaultCompatibility(update) {
const response = await this.restService.handleRequest(`/config`, 'PUT', { compatibility: update });
return response.data.compatibility;
}
/**
* Get the config for a subject.
* @param subject - The subject for which to get the config.
*/
async getConfig(subject) {
subject = encodeURIComponent(subject);
const response = await this.restService.handleRequest(`/config/${subject}`, 'GET');
return response.data;
}
/**
* Update the config for a subject.
* @param subject - The subject for which to update the config.
* @param update - The config to update to.
*/
async updateConfig(subject, update) {
const response = await this.restService.handleRequest(`/config/${subject}`, 'PUT', update);
return response.data;
}
/**
* Get the default/global config.
*/
async getDefaultConfig() {
const response = await this.restService.handleRequest(`/config`, 'GET');
return response.data;
}
/**
* Update the default/global config.
* @param update - The config to update to.
*/
async updateDefaultConfig(update) {
const response = await this.restService.handleRequest(`/config`, 'PUT', update);
return response.data;
}
/**
* Clear the latest caches.
*/
clearLatestCaches() {
this.latestToSchemaCache.clear();
this.metadataToSchemaCache.clear();
}
/**
* Clear all caches.
*/
clearCaches() {
this.idToSchemaInfoCache.clear();
this.guidToSchemaInfoCache.clear();
this.infoToSchemaCache.clear();
this.latestToSchemaCache.clear();
this.schemaToVersionCache.clear();
this.versionToSchemaCache.clear();
this.metadataToSchemaCache.clear();
}
/**
* Close the client.
*/
async close() {
this.clearCaches();
}
// Cache methods for testing
async addToInfoToSchemaCache(subject, schema, metadata) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: minimize(schema) });
await this.infoToSchemaMutex.runExclusive(async () => {
this.infoToSchemaCache.set(cacheKey, metadata);
});
}
async addToSchemaToVersionCache(subject, schema, version) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, schema: minimize(schema) });
await this.schemaToVersionMutex.runExclusive(async () => {
this.schemaToVersionCache.set(cacheKey, version);
});
}
async addToVersionToSchemaCache(subject, version, metadata) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, version });
await this.versionToSchemaMutex.runExclusive(async () => {
this.versionToSchemaCache.set(cacheKey, metadata);
});
}
async addToIdToSchemaInfoCache(subject, id, schema) {
const cacheKey = (0, json_stringify_deterministic_1.default)({ subject, id });
await this.idToSchemaInfoMutex.runExclusive(async () => {
this.idToSchemaInfoCache.set(cacheKey, schema);
});
}
async addToGuidToSchemaInfoCache(guid, schema) {
await this.guidToSchemaInfoMutex.runExclusive(async () => {
this.guidToSchemaInfoCache.set(guid, schema);
});
}
async getInfoToSchemaCacheSize() {
return await this.infoToSchemaMutex.runExclusive(async () => {
return this.infoToSchemaCache.size;
});
}
async getSchemaToVersionCacheSize() {
return await this.schemaToVersionMutex.runExclusive(async () => {
return this.schemaToVersionCache.size;
});
}
async getVersionToSchemaCacheSize() {
return await this.versionToSchemaMutex.runExclusive(async () => {
return this.versionToSchemaCache.size;
});
}
async getIdToSchemaInfoCacheSize() {
return await this.idToSchemaInfoMutex.runExclusive(async () => {
return this.idToSchemaInfoCache.size;
});
}
async getGuidToSchemaInfoCacheSize() {
return await this.guidToSchemaInfoMutex.runExclusive(async () => {
return this.guidToSchemaInfoCache.size;
});
}
}
exports.SchemaRegistryClient = SchemaRegistryClient;