UNPKG

schematic-kafka

Version:

Encode and decode kafka messages with Confluent Schema Registry (pure typescript implementation)

335 lines 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaRegistryClient = exports.SchemaRegistryError = exports.CompatibilityMode = exports.SchemaType = void 0; const tslib_1 = require("tslib"); const httpsRequest = require("https"); const httpRequest = require("http"); const url_1 = require("url"); /** * Enum of supported schema types / protocols. * If this isn't provided the schema registry will assume AVRO. */ var SchemaType; (function (SchemaType) { SchemaType["AVRO"] = "AVRO"; SchemaType["PROTOBUF"] = "PROTOBUF"; SchemaType["JSON"] = "JSON"; })(SchemaType || (exports.SchemaType = SchemaType = {})); /** * Enum of supported schema compatibility modes. */ var CompatibilityMode; (function (CompatibilityMode) { CompatibilityMode["BACKWARD"] = "BACKWARD"; CompatibilityMode["BACKWARD_TRANSITIVE"] = "BACKWARD_TRANSITIVE"; CompatibilityMode["FORWARD"] = "FORWARD"; CompatibilityMode["FORWARD_TRANSITIVE"] = "FORWARD_TRANSITIVE"; CompatibilityMode["FULL"] = "FULL"; CompatibilityMode["FULL_TRANSITIVE"] = "FULL_TRANSITIVE"; CompatibilityMode["NONE"] = "NONE"; })(CompatibilityMode || (exports.CompatibilityMode = CompatibilityMode = {})); /** * Custom error to be used by the schema registry client */ class SchemaRegistryError extends Error { constructor(errorCode, message) { super(); this.message = `Schema registry error: code: ${errorCode} - ${message}`; this.errorCode = errorCode; } } exports.SchemaRegistryError = SchemaRegistryError; /** * schema registry client to interact with confluent schema registry * as specified here: https://docs.confluent.io/platform/current/schema-registry/develop/api.html * * Note: not all methods are implemented yet */ class SchemaRegistryClient { /** * create new SchemaRegistryClient instance */ constructor(options) { var _a, _b; const parsed = new url_1.URL(options.baseUrl); this.requester = parsed.protocol.startsWith("https") ? httpsRequest : httpRequest; this.basePath = parsed.pathname !== null ? parsed.pathname : "/"; const username = (_a = options.username) !== null && _a !== void 0 ? _a : parsed.username; const password = (_b = options.password) !== null && _b !== void 0 ? _b : parsed.password; this.baseRequestOptions = { host: parsed.hostname, port: parsed.port, headers: { Accept: "application/vnd.schemaregistry.v1+json", "Content-Type": "application/vnd.schemaregistry.v1+json", }, agent: options.agent, auth: username && password ? `${username}:${password}` : null, }; } // schemas section /** * Get a schema by its id * @param {number} schemaId id of the schema to fetch * @returns serialized schema information and schema type */ getSchemaById(schemaId) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}schemas/ids/${schemaId}`; const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path }); return JSON.parse(yield this.request(requestOptions)); }); } /** * Get types of possible schemas types * @returns array of possible schema types (typically: AVRO, PROTOBUF and JSON) */ getSchemaTypes() { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}schemas/types`; const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path }); return JSON.parse(yield this.request(requestOptions)); }); } /** * Get subject/version pairs for given id * @param schemaId version of schema registered * @returns subject and version number for the schema identified by the id */ listVersionsForId(schemaId) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}schemas/ids/${schemaId}/versions`; const versionListRequestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path }); return JSON.parse(yield this.request(versionListRequestOptions)); }); } // subject section /** * Get list of available subjects * @returns array of registered subjects */ listSubjects() { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}subjects`; const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path }); return JSON.parse(yield this.request(requestOptions)); }); } /** * Get list of versions registered under the specified subject * @param subject subject name * @returns array of schema versions */ listVersionsForSubject(subject) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const versionListRequestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path: `${this.basePath}subjects/${subject}/versions` }); return JSON.parse(yield this.request(versionListRequestOptions)); }); } /** * Deletes a schema. * Note: Should only be used in development mode. Don't delete schemas in production. * @param subject subject name * @returns list of deleted schema versions */ deleteSubject(subject_1) { return tslib_1.__awaiter(this, arguments, void 0, function* (subject, permanent = false) { const path = `${this.basePath}subjects/${subject}${permanent ? "?permanent=true" : ""}`; const versionListRequestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { method: "DELETE", path }); return JSON.parse(yield this.request(versionListRequestOptions)); }); } /** * Get schema for subject and version * @param subject subject name * @param version optional version to retrieve (if not provided the latest version will be fetched) * @returns schema and its metadata */ getSchemaForSubjectAndVersion(subject, version) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}subjects/${subject}/versions/${version !== null && version !== void 0 ? version : "latest"}`; const schemaInfoRequestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path }); return JSON.parse(yield this.request(schemaInfoRequestOptions)); }); } /** * Alias for getSchemaForSubjectAndVersion with version = latest * @param subject subject name * @returns schema and its metadata */ getLatestVersionForSubject(subject) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return yield this.getSchemaForSubjectAndVersion(subject); }); } /** * Get schema for subject and version * @param subject subject name * @param version schema version * @returns serialized schema */ getRawSchemaForSubjectAndVersion(subject, version) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}subjects/${subject}/versions/${version}/schema`; const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { path }); return yield this.request(requestOptions); }); } /** * Register schema in registry * * Note: The specification says it will return subject, id, version and the schema itself. * Factually it will only return the id of the newly created schema. * @param subject subject name * @param schema schema metadata and serialized schema representation * @returns schema metadata (or just the id of the newly created schema) */ registerSchema(subject, schema) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}subjects/${subject}/versions`; const body = JSON.stringify(schema); const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { method: "POST", headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions, body)); }); } /** * Check if a schema has already been registered under the specified subject. * If so, this returns the schema string along with its globally unique identifier, its version under this subject and the subject name. * @param subject subject name * @param schema serialized schema representation * @returns schema metadata and serialized schema */ checkSchema(subject, schema) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}subjects/${subject}`; const body = JSON.stringify(schema); const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { method: "POST", headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions, body)); }); } // TODO: DELETE /subjects/(string: subject)/versions/(versionId: version) // TODO: GET /subjects/(string: subject)/versions/{versionId: version}/referencedby // TODO: mode section // compatibility section /** * Check a schema for compatibility * @param subject * @param version * @param schema * @param verbose * @returns whether or not the schema is compatible with the provided schema, given the compatibility mode for the subject */ testCompatibility(subject, version, schema, verbose) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}compatibility/subjects/${subject}/versions/${version}${verbose ? "?verbose=true" : ""}`; const method = "POST"; const body = JSON.stringify(schema); const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { method, headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions, body)).is_compatible; }); } // config section /** * set schema registry default compatibility mode * @param compatibility new default compatibility mode * @returns new compatibility mode as echoed by the schema registry */ setConfig(compatibility) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}config`; const method = "PUT"; const body = JSON.stringify({ compatibility }); const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { method, headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions, body)).compatibility; }); } /** * get schema registry default compatibility mode */ getConfig() { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}config`; const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions)).compatibilityLevel; }); } /** * set compatibility mode for a subject * @param subject name of the subject * @param compatibility new compatibility mode * @returns new compatibility mode as echoed by the schema registry */ setSubjectConfig(subject, compatibility) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}config/${subject}`; const method = "PUT"; const body = JSON.stringify({ compatibility }); const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { method, headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions, body)).compatibility; }); } /** * get compatibility mode for a subject * @param subject name of the subject * @returns current compatibility mode for the subject */ getSubjectConfig(subject) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const path = `${this.basePath}config/${subject}`; const requestOptions = Object.assign(Object.assign({}, this.baseRequestOptions), { headers: Object.assign({}, this.baseRequestOptions.headers), path }); return JSON.parse(yield this.request(requestOptions)).compatibilityLevel; }); } /** * Internal http request helper */ request(requestOptions, requestBody) { if (requestBody && requestBody.length > 0) { requestOptions.headers = Object.assign(Object.assign({}, requestOptions.headers), { "Content-Length": Buffer.byteLength(requestBody) }); } return new Promise((resolve, reject) => { const req = this.requester .request(requestOptions, (res) => { let data = ""; res.on("data", (d) => { data += d; }); res.on("error", (e) => { // response error reject(e); }); res.on("end", () => { if (res.statusCode === 200) { return resolve(data); } if (data.length > 0) { try { let { error_code, message } = JSON.parse(data); // squash different 404 errors if ([404, 40401, 40403].includes(error_code)) { error_code = 404; } return reject(new SchemaRegistryError(error_code, message)); } catch (e) { return reject(e); } } else { return reject(new Error("Invalid schema registry response")); } }); }) .on("error", (e) => { // request error reject(e); }); if (requestBody) { req.write(requestBody); } req.end(); }); } } exports.SchemaRegistryClient = SchemaRegistryClient; //# sourceMappingURL=schema-registry-client.js.map