UNPKG

@asymmetrik/janusgraph-manager

Version:

Janusgraph management tooling in NodeJS/Typescript

803 lines (759 loc) 28.9 kB
(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__; /******/ })() ; });