@asymmetrik/janusgraph-manager
Version:
Janusgraph management tooling in NodeJS/Typescript
803 lines (759 loc) • 28.9 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else {
var a = factory();
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(this, function() {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/builders/EdgeBuilder.ts":
/*!*************************************!*\
!*** ./src/builders/EdgeBuilder.ts ***!
\*************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.EdgeBuilder = void 0;
class EdgeBuilder {
constructor(_label) {
this._label = _label;
this._multiplicity = 'MULTI';
this._properties = [];
}
multiplicity(multiplicity) {
this._multiplicity = multiplicity;
return this;
}
property(property) {
if (this._properties.some((p) => p.key === property.key))
return this;
this._properties.push(property);
return this;
}
build() {
let output = `if (!mgmt.containsEdgeLabel('${this._label}')) `;
output += `mgmt.makeEdgeLabel('${this._label}')`;
output +=
this._multiplicity != null
? `.multiplicity(${this._multiplicity})`
: '';
output += '.make();';
if (this._properties.length > 0) {
output += 'mgmt.addProperties(';
output += `mgmt.getEdgeLabel('${this._label}'), `;
output += [...this._properties]
.map((prop) => `mgmt.getPropertyKey('${prop.key}')`)
.join(', ');
output += ')';
}
return output;
}
}
exports.EdgeBuilder = EdgeBuilder;
/***/ }),
/***/ "./src/builders/EnableIndexBuilder.ts":
/*!********************************************!*\
!*** ./src/builders/EnableIndexBuilder.ts ***!
\********************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.EnableIndexBuilder = void 0;
/**
* Builds a management string that attempts to enable a specific index.
*/
class EnableIndexBuilder {
/**
* Default constructor.
* @param _name Index to attempt to enable.
* @param _graph Graph name that the index resides on. Default `graph`.
*/
constructor(_name, _graph = 'graph') {
this._name = _name;
this._graph = _graph;
this._action = 'ENABLE_INDEX';
}
type(type) {
this._type = type;
return this;
}
/**
* Sets the vertex label for VertexCentric indicies.
* @param label Label
* @returns The builder.
*/
label(label) {
if (this._type !== 'VertexCentric') {
console.warn(`Label ${label} set on EnableIndex builder. This only applies for VertexCentric indices.`);
}
this._label = label;
return this;
}
action(action) {
this._action = action;
return this;
}
/**
* Builds the output string.
* @returns String that calls ENABLE_INDEX in JG.
* @throws An Error if an attempt is made to enable a VertexCentric index without a label.
*/
build() {
let output = 'mgmt.updateIndex(';
if (this._type === 'VertexCentric') {
if (this._label == null || this._label === '')
throw Error(`Vertex Centric index '${this._name}' attempted to be enabled without a label definition.`);
output += `mgmt.getRelationIndex(mgmt.getEdgeLabel('${this._label}'), '${this._name}')`;
}
else {
output += `mgmt.getGraphIndex('${this._name}')`;
}
output += `, SchemaAction.${this._action}).get();`;
return output;
}
}
exports.EnableIndexBuilder = EnableIndexBuilder;
/***/ }),
/***/ "./src/builders/GraphIndexBuilder.ts":
/*!*******************************************!*\
!*** ./src/builders/GraphIndexBuilder.ts ***!
\*******************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.GraphIndexBuilder = void 0;
/**
* Index Builder for Composite or Mixed indices.
*
* For VertexCentric indicies, please use {@link VertexCentricIndexBuilder}
*/
class GraphIndexBuilder {
constructor(_name, _element) {
this._name = _name;
this._element = _element;
this._keys = [];
}
type(type) {
this._type = type;
return this;
}
key(key) {
if (this._keys.some((k) => k.field === key.field))
return this;
this._keys.push(key);
return this;
}
unique(unique = false) {
this._unique = unique;
return this;
}
label(label) {
this._label = label;
return this;
}
backend(backend) {
this._backend = backend;
return this;
}
build() {
if (this._keys.length === 0) {
throw Error(`Unable to generate index ${this._name} with no key definitions.`);
}
if (this._type !== 'Mixed' && this._backend != null) {
console.warn('Composite index type and non-null backend. Will ignore backend.');
}
let output = `if (!mgmt.containsGraphIndex('${this._name}')) `;
output += `mgmt.buildIndex('${this._name}', ${this._element}.class)`;
output += [...this._keys]
.map((key) => `.addKey(mgmt.getPropertyKey('${key.field}')${this._type === 'Mixed' && key.mapping != null
? `,Mapping.${key.mapping}.asParameter()`
: ''})`)
.join('');
output += this._unique ? `.unique()` : '';
output +=
this._label != null
? `.indexOnly(mgmt.get${this._element}Label('${this._label}'))`
: '';
return output.concat(this._type === 'Mixed'
? `.buildMixedIndex('${this._backend ?? 'search'}');`
: '.buildCompositeIndex();');
}
}
exports.GraphIndexBuilder = GraphIndexBuilder;
/***/ }),
/***/ "./src/builders/PropertyBuilder.ts":
/*!*****************************************!*\
!*** ./src/builders/PropertyBuilder.ts ***!
\*****************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PropertyBuilder = void 0;
class PropertyBuilder {
constructor(_key) {
this._key = _key;
this._cardinality = 'SINGLE';
}
cardinality(cardinality) {
this._cardinality = cardinality;
return this;
}
datatype(datatype) {
this._datatype = datatype;
return this;
}
build() {
let output = `if (!mgmt.containsPropertyKey('${this._key}')) `;
output += `mgmt.makePropertyKey('${this._key}')`;
output +=
this._datatype != null ? `.dataType(${this._datatype}.class)` : '';
output +=
this._cardinality != null
? `.cardinality(org.janusgraph.core.Cardinality.${this._cardinality})`
: '';
return output.concat('.make();');
}
}
exports.PropertyBuilder = PropertyBuilder;
/***/ }),
/***/ "./src/builders/VertexBuilder.ts":
/*!***************************************!*\
!*** ./src/builders/VertexBuilder.ts ***!
\***************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.VertexBuilder = void 0;
class VertexBuilder {
constructor(_label) {
this._label = _label;
this._properties = [];
}
property(property) {
if (this._properties.some((p) => p.key === property.key))
return this;
this._properties.push(property);
return this;
}
build() {
let output = `if (!mgmt.containsVertexLabel('${this._label}')) `;
output += `mgmt.makeVertexLabel('${this._label}')`;
output += '.make();';
if (this._properties.length > 0) {
output += 'mgmt.addProperties(';
output += `mgmt.getVertexLabel('${this._label}'), `;
output += [...this._properties]
.map((prop) => `mgmt.getPropertyKey('${prop.key}')`)
.join(', ');
output += ')';
}
return output;
}
}
exports.VertexBuilder = VertexBuilder;
/***/ }),
/***/ "./src/builders/VertexCentricIndexBuilder.ts":
/*!***************************************************!*\
!*** ./src/builders/VertexCentricIndexBuilder.ts ***!
\***************************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.VertexCentricIndexBuilder = void 0;
/**
* Index Builder for Vertex Centric indices.
*
* For Mixed/Composite, please use {@link IndexBuilder}
*/
class VertexCentricIndexBuilder {
constructor(_name) {
this._name = _name;
this._keys = new Set();
this._order = 'asc';
}
edgelabel(edgelabel) {
this._edgelabel = edgelabel;
return this;
}
direction(direction) {
this._direction = direction;
return this;
}
order(order) {
this._order = order;
return this;
}
key(key) {
this._keys.add(key);
return this;
}
build() {
if (this._keys.size === 0) {
throw Error(`Unable to generate vc index ${this._name} with no key definitions.`);
}
if (this._direction == null) {
throw Error(`Unable to generate vc index ${this._name} with no directionality.`);
}
if (this._edgelabel == null || this._edgelabel === '') {
throw Error(`Unable to generate vc index ${this._name} with no edge label.`);
}
let output = `if (!mgmt.containsRelationIndex(mgmt.getEdgeLabel('${this._edgelabel}'), '${this._name}')) `;
output += `mgmt.buildEdgeIndex(`;
output += `mgmt.getEdgeLabel('${this._edgelabel}'), `;
output += `'${this._name}', `;
output += `Direction.${this._direction}, `;
output += `Order.${this._order}, `;
output += [...this._keys]
.map((key) => `mgmt.getPropertyKey('${key}')`)
.join(', ');
return output.concat(');0;');
}
}
exports.VertexCentricIndexBuilder = VertexCentricIndexBuilder;
/***/ }),
/***/ "./src/builders/WaitForIndexBuilder.ts":
/*!*********************************************!*\
!*** ./src/builders/WaitForIndexBuilder.ts ***!
\*********************************************/
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.WaitForIndexBuilder = void 0;
/**
* Builds a string that instructs the management system to wait for an index to become available.
*/
class WaitForIndexBuilder {
/**
* Default constructor.
* @param name Name of the index to wait for.
* @param graph Name of the graph to use. Default is `graph`
*/
constructor(name, graph = 'graph') {
this.name = name;
this.graph = graph;
}
build() {
return `ManagementSystem.awaitGraphIndexStatus(${this.graph}, '${this.name}').status(SchemaStatus.ENABLED, SchemaStatus.REGISTERED).call().toString()`;
}
}
exports.WaitForIndexBuilder = WaitForIndexBuilder;
/***/ }),
/***/ "./src/builders/index.ts":
/*!*******************************!*\
!*** ./src/builders/index.ts ***!
\*******************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./EdgeBuilder */ "./src/builders/EdgeBuilder.ts"), exports);
__exportStar(__webpack_require__(/*! ./EnableIndexBuilder */ "./src/builders/EnableIndexBuilder.ts"), exports);
__exportStar(__webpack_require__(/*! ./GraphIndexBuilder */ "./src/builders/GraphIndexBuilder.ts"), exports);
__exportStar(__webpack_require__(/*! ./PropertyBuilder */ "./src/builders/PropertyBuilder.ts"), exports);
__exportStar(__webpack_require__(/*! ./WaitForIndexBuilder */ "./src/builders/WaitForIndexBuilder.ts"), exports);
__exportStar(__webpack_require__(/*! ./VertexBuilder */ "./src/builders/VertexBuilder.ts"), exports);
__exportStar(__webpack_require__(/*! ./VertexCentricIndexBuilder */ "./src/builders/VertexCentricIndexBuilder.ts"), exports);
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
var exports = __webpack_exports__;
/*!**********************************!*\
!*** ./src/JanusGraphManager.ts ***!
\**********************************/
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.JanusGraphManager = void 0;
const builders_1 = __webpack_require__(/*! ./builders */ "./src/builders/index.ts");
class JanusGraphManager {
/**
* Default constructor.
* @param client A preconfigured gremlin client for accessing gremlin-server.
* @param options JanusGraphOptions for accessing the graph:
* - graphName will have a default of `'graph'`.
* - useConfiguredGraphFactory will have a default of `false`
* - configPath has no default.
*/
constructor(client, options) {
this.client = client;
this.options = options;
this.state = 'NEW';
this.OPEN_MGMT = `mgmt = ${this.options.graphName}.openManagement();0;`;
if (options.graphName == null) {
this.options.graphName = 'graph';
}
if (options.useConfiguredGraphFactory == null) {
this.options.useConfiguredGraphFactory = false;
}
}
/**
* Opens the management system for the client session.
* Will be called internally to re-open the management system if a commit or error closes it.
* @returns A promise with the state of the manager.
*/
async init() {
try {
if (this.state !== 'READY') {
// Ensure that there are no open transactions while we are managing the graph.
await this.client.submit(`${this.options.graphName}.tx().rollback();`);
if (this.options.useConfiguredGraphFactory) {
// The ";0;" prevents the client from attempting to serialize the graph/management system.
await this.client.submit(`${this.options.graphName} = ConfiguredGraphFactory.open('${this.options.graphName}');0;`);
}
else if (this.options.configPath != null) {
await this.client.submit(`${this.options.graphName} = JanusGraphFactory.open('${this.options.configPath}');0;`);
}
await this.client.submit(this.OPEN_MGMT);
this.state = 'READY';
}
return Promise.resolve(this.state);
}
catch (err) {
this.state = 'ERROR';
return Promise.reject(err);
}
}
/**
* Builds and persists a single graph index.
* Currently, if an index with the same name is already created, this will *not* recreate the index.
* @param index GraphIndex to create.
* @param commit Whether or not to commit the changes.
* @returns
*/
async createGraphIndex(index, commit = false) {
await this.init();
const builder = new builders_1.GraphIndexBuilder(index.name, index.element ?? 'Vertex');
builder
.label(index.label)
.type(index.type)
.unique(index.unique)
.backend(index.backend);
index.keys.forEach((k) => builder.key(k));
try {
await this.client.submit(builder.build());
if (commit)
await this.commit();
return Promise.resolve(1);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Builds and persists a single VertexCentric index.
* Currently, if an index with the same name is already created, this will *not* recreate the index.
* @param index VertexCentricIndex to create.
* @param commit Whether or not to commit the changes.
* @returns
*/
async createVertexCentricIndex(index, commit = false) {
const builder = new builders_1.VertexCentricIndexBuilder(index.name);
builder
.direction(index.direction)
.edgelabel(index.edgelabel)
.order(index.order);
index.keys.forEach((k) => builder.key(k));
try {
await this.init();
await this.client.submit(builder.build());
if (commit)
await this.commit();
return Promise.resolve(1);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Will pause execution until the management system reports that all indices on a graph have reached the REGISTERED or ENABLED state.
* @param schema GraphSchema to process.
* @param graph Name of the traversal alias to analyze. Default: `graph`
* @returns A promise with the number of indices that reached REGISTERED or ENABLED state.
*/
async waitForIndices(schema, graph) {
try {
await this.init();
return (await Promise.all([...schema.graphIndices, ...schema.vcIndices].map((i) => this.waitForIndex(i, graph)))).length;
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Will pause execution until the management system reports that a single index on a graph has reached the REGISTERED or ENABLED state.
* @param index Index to process.
* @param graph Name of the traversal alias to analyze. Default: `graph`
* @returns A promise of 1, indicating that 1 index has reached REGISTERED or ENABLED state.
*/
async waitForIndex(index, graph) {
const builder = new builders_1.WaitForIndexBuilder(index.name, graph);
try {
await this.init();
await this.client.submit(builder.build());
return Promise.resolve(1);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Will build only the indices from a graph schema.
* @param schema - GraphSchema to get index definitions from.
* @param commit - Whether or not to commit and close the traversal. Default: `false`
* @returns A promise containing the number of successful traversals made.
*/
async createIndices(schema, commit = false) {
try {
await this.init();
let count = 0;
// Generate graph indices.
count += (await Promise.all(schema.graphIndices.map((i) => this.createGraphIndex(i)))).length;
// Generate vc indices.
count += (await Promise.all(schema.vcIndices.map((i) => this.createVertexCentricIndex(i)))).length;
if (commit) {
await this.commit();
}
return Promise.resolve(count);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Attempts to enable indices.
* @param schema - GraphSchema to enable indicies for.
* @param commit - Whether or not to commit and close the traversal. Default: `false`
* @returns A promise containing the number of successful traversals made.
*/
async enableIndices(schema, commit = false) {
try {
await this.init();
const gi = schema.graphIndices.map((i) => {
const builder = new builders_1.EnableIndexBuilder(i.name, schema.name);
return builder.action('ENABLE_INDEX').build();
});
const vci = schema.vcIndices.map((i) => {
const builder = new builders_1.EnableIndexBuilder(i.name, schema.name);
return builder
.type('VertexCentric')
.label(i.edgelabel)
.action('ENABLE_INDEX')
.build();
});
const count = (await Promise.all([...gi, ...vci].map(async (msg) => await this.client.submit(msg)))).length;
if (commit) {
await this.commit();
}
return Promise.resolve(count);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Attempts to reindex all indices.
* @param schema - GraphSchema to reindex indicies for.
* @param commit - Whether or not to commit and close the traversal. Default: `false`
* @returns A promise containing the number of successful traversals made.
*/
async reindexIndices(schema, commit = false) {
try {
await this.init();
const gi = schema.graphIndices.map((i) => {
const builder = new builders_1.EnableIndexBuilder(i.name, schema.name);
return builder.action('REINDEX').build();
});
const vci = schema.vcIndices.map((i) => {
const builder = new builders_1.EnableIndexBuilder(i.name, schema.name);
return builder
.type('VertexCentric')
.label(i.edgelabel)
.action('REINDEX')
.build();
});
const count = (await Promise.all([...gi, ...vci].map(async (msg) => await this.client.submit(msg)))).length;
if (commit) {
await this.commit();
}
return Promise.resolve(count);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Reindex an arbitrary index for an arbitrary graph.
* @param graphname The graph the index is located on.
* @param index The index to reindex.
* @param commit Whether or not to commit (default: `false`)
* @returns A promise containing the output of the reindex command.
*/
async reindex(graphname, index, commit = false) {
try {
await this.init();
const builder = new builders_1.EnableIndexBuilder(index.name, graphname);
if (index.edgelabel != null) {
builder
.type('VertexCentric')
.label(index.edgelabel);
}
const msg = builder.action('REINDEX').build();
const output = await this.client.submit(msg);
if (commit) {
await this.commit();
}
return Promise.resolve(output);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Creates all the schema definitions.
* @param schema - GraphSchema to get schema definitions from.
* @param indices - If set `true`, will build indices as well. Default `false`.
* @returns A promise containing the number of successful traversals made.
*/
async createSchema(schema, indices = false) {
try {
await this.init();
let count = 0;
// Extract/Build our properties definitions.
count += (await Promise.all([...schema.vertices, ...schema.edges]
.flatMap((v) => v.properties)
.map((p) => {
const builder = new builders_1.PropertyBuilder(p.key);
return builder
.datatype(p.datatype)
.cardinality(p.cardinality)
.build();
})
.map(async (msg) => await this.client.submit(msg)))).length;
// Create labels and associate properties for vertices.
count += (await Promise.all(schema.vertices
.map((v) => {
const builder = new builders_1.VertexBuilder(v.label);
v.properties.forEach((p) => builder.property(p));
return builder.build();
})
.map(async (msg) => await this.client.submit(msg)))).length;
// Create labels and associate properties for edges.
count += (await Promise.all(schema.edges
.map((e) => {
const builder = new builders_1.EdgeBuilder(e.label);
e.properties.forEach((p) => builder.property(p));
return builder.build();
})
.map(async (msg) => await this.client.submit(msg)))).length;
if (indices) {
await this.commit();
count += await this.createIndices(schema, true);
}
return Promise.resolve(count);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Retrieves all of the graph indices for the current graph.
* @returns A promise containing a list of all the indices.
*/
async getIndices() {
try {
await this.init();
const vindices = await this.client.submit('mgmt.getGraphIndexes(Vertex.class)');
const eindices = await this.client.submit('mgmt.getGraphIndexes(Edge.class)');
return Promise.resolve([vindices._items, eindices._items]);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Retrieves a console-friendly representation of the current graph schema
* @returns A promise containing a console-friendly string of the schema and state of indices
*/
async printSchema() {
try {
await this.init();
const data = await this.client.submit('mgmt.printSchema()');
return Promise.resolve(data._items);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Leverages the gremlin client to commit a management message.
* @param message Message to send prior to the commit. Not required.
* @returns A promise from client commit submission.
*/
async commit(message) {
try {
await this.init();
const commit = await this.client.submit(`${message ?? ''};mgmt.commit();`);
this.state = 'COMMIT';
return commit;
}
catch (err) {
this.state = 'ERROR';
return Promise.reject(err);
}
}
/**
* Attempts to close the gremlin client.
* @returns A promise with close status-- defers to {@see driver.Client.prototype.close()}
*/
async close() {
try {
if (['CLOSED', 'ERROR'].some((s) => s === this.state))
return;
const close = await this.client.close();
this.state = 'CLOSED';
return close;
}
catch (err) {
this.state = 'ERROR';
return Promise.reject(err);
}
}
}
exports.JanusGraphManager = JanusGraphManager;
})();
/******/ return __webpack_exports__;
/******/ })()
;
});