UNPKG

@confluentinc/schemaregistry

Version:
544 lines (543 loc) 24.7 kB
"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;