schematic-kafka
Version:
Encode and decode kafka messages with Confluent Schema Registry (pure typescript implementation)
335 lines • 15.1 kB
JavaScript
"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