@decaf-ts/for-nano
Version:
decaf-ts persistence adapter for CouchDB via nano
629 lines • 71.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NanoAdapter = void 0;
exports.createdByOnNanoCreateUpdate = createdByOnNanoCreateUpdate;
const db_decorators_1 = require("@decaf-ts/db-decorators");
require("reflect-metadata");
const for_couchdb_1 = require("@decaf-ts/for-couchdb");
const nano_1 = __importDefault(require("nano"));
const decorator_validation_1 = require("@decaf-ts/decorator-validation");
const core_1 = require("@decaf-ts/core");
const constants_1 = require("./constants.cjs");
const NanoDispatch_1 = require("./NanoDispatch.cjs");
/**
* @description Sets the creator or updater field in a model based on the user in the context
* @summary Callback function used in decorators to automatically set the created_by or updated_by fields
* with the username from the context when a document is created or updated
* @template M - Type extending Model
* @template R - Type extending NanoRepository<M>
* @template V - Type extending RelationsMetadata
* @param {R} this - The repository instance
* @param {Context<NanoFlags>} context - The operation context containing user information
* @param {V} data - The relation metadata
* @param key - The property key to set with the username
* @param {M} model - The model instance being created or updated
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function createdByOnNanoCreateUpdate
* @memberOf module:for-nano
* @mermaid
* sequenceDiagram
* participant F as createdByOnNanoCreateUpdate
* participant C as Context
* participant M as Model
* F->>C: get("user")
* C-->>F: user object
* F->>M: set key to user.name
* Note over F: If no user in context
* F-->>F: throw UnsupportedError
*/
async function createdByOnNanoCreateUpdate(context, data, key, model) {
try {
const user = context.get("user");
model[key] = user.name;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (e) {
throw new core_1.UnsupportedError("No User found in context. Please provide a user in the context");
}
}
/**
* @description Adapter for interacting with Nano databases
* @summary Provides a standardized interface for performing CRUD operations on Nano databases,
* extending the CouchDB adapter with Nano-specific functionality. This adapter handles document
* creation, reading, updating, and deletion, as well as bulk operations and index management.
* @template DocumentScope - The Nano document scope type
* @template NanoFlags - Configuration flags for Nano operations
* @template Context - Context type for operations
* @param {DocumentScope<any>} scope - The Nano document scope to use for database operations
* @param {string} [alias] - Optional alias for the adapter
* @class NanoAdapter
* @example
* ```typescript
* // Connect to a Nano database
* const server = NanoAdapter.connect('admin', 'password', 'localhost:5984');
* const db = server.db.use('my_database');
*
* // Create an adapter instance
* const adapter = new NanoAdapter(db);
*
* // Use the adapter for database operations
* const document = await adapter.read('users', '123');
* ```
* @mermaid
* classDiagram
* class CouchDBAdapter {
* +flags()
* +Dispatch()
* +index()
* +create()
* +read()
* +update()
* +delete()
* }
* class NanoAdapter {
* +flags()
* +Dispatch()
* +index()
* +create()
* +createAll()
* +read()
* +readAll()
* +update()
* +updateAll()
* +delete()
* +deleteAll()
* +raw()
* +static connect()
* +static createDatabase()
* +static deleteDatabase()
* +static createUser()
* +static deleteUser()
* +static decoration()
* }
* CouchDBAdapter <|-- NanoAdapter
*/
class NanoAdapter extends for_couchdb_1.CouchDBAdapter {
constructor(scope, alias) {
super(scope, constants_1.NanoFlavour, alias);
}
/**
* @description Generates flags for database operations
* @summary Creates a set of flags for a specific operation, including user information
* @template M - Type extending Model
* @param {OperationKeys} operation - The operation being performed (create, read, update, delete)
* @param {Constructor<M>} model - The model constructor
* @param {Partial<NanoFlags>} flags - Partial flags to be merged
* @return {Promise<NanoFlags>} Complete flags for the operation
*/
async flags(operation, model, flags) {
return Object.assign(await super.flags(operation, model, flags), {
user: {
name: this.native.config.url.split("@")[0].split(":")[0],
},
});
}
/**
* @description Creates a new NanoDispatch instance
* @summary Returns a dispatcher for handling Nano-specific operations
* @return {NanoDispatch} A new NanoDispatch instance
*/
Dispatch() {
return new NanoDispatch_1.NanoDispatch();
}
/**
* @description Creates database indexes for models
* @summary Generates and creates indexes in the Nano database based on the provided models
* @template M - Type extending Model
* @param models - Model constructors to create indexes for
* @return {Promise<void>} A promise that resolves when all indexes are created
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant G as generateIndexes
* participant DB as Nano Database
* A->>G: generateIndexes(models)
* G-->>A: indexes
* loop For each index
* A->>DB: createIndex(index)
* DB-->>A: response
* Note over A: Check if index already exists
* alt Index exists
* A-->>A: throw ConflictError
* end
* end
*/
async index(...models) {
const indexes = (0, for_couchdb_1.generateIndexes)(models);
for (const index of indexes) {
const res = await this.native.createIndex(index);
const { result, id, name } = res;
if (result === "existing")
throw new db_decorators_1.ConflictError(`Index for table ${name} with id ${id}`);
}
}
/**
* @description Creates a new document in the database
* @summary Inserts a new document into the Nano database with the provided data
* @param {string} tableName - The name of the table/collection
* @param {string | number} id - The document identifier
* @param {Record<string, any>} model - The document data to insert
* @return {Promise<Record<string, any>>} A promise that resolves to the created document with metadata
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Database
* A->>DB: insert(model)
* alt Success
* DB-->>A: response with ok=true
* A->>A: assignMetadata(model, response.rev)
* A-->>A: return document with metadata
* else Error
* DB-->>A: error
* A-->>A: throw parseError(e)
* else Not OK
* DB-->>A: response with ok=false
* A-->>A: throw InternalError
* end
*/
async create(tableName, id, model) {
let response;
try {
response = await this.native.insert(model);
}
catch (e) {
throw this.parseError(e);
}
if (!response.ok)
throw new db_decorators_1.InternalError(`Failed to insert doc id: ${id} in table ${tableName}`);
return this.assignMetadata(model, response.rev);
}
/**
* @description Creates multiple documents in the database
* @summary Inserts multiple documents into the Nano database in a single bulk operation
* @param {string} tableName - The name of the table/collection
* @param {string[] | number[]} ids - Array of document identifiers
* @param models - Array of document data to insert
* @return A promise that resolves to an array of created documents with metadata
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Database
* A->>DB: bulk({docs: models})
* alt Success
* DB-->>A: response array
* A->>A: Check if all responses have no errors
* alt All OK
* A->>A: assignMultipleMetadata(models, revs)
* A-->>A: return documents with metadata
* else Some errors
* A->>A: Collect error messages
* A-->>A: throw InternalError with collected messages
* end
* else Error
* DB-->>A: error
* A-->>A: throw parseError(e)
* end
*/
async createAll(tableName, ids, models) {
let response;
try {
response = await this.native.bulk({ docs: models });
}
catch (e) {
throw this.parseError(e);
}
if (!response.every((r) => !r.error)) {
const errors = response.reduce((accum, el, i) => {
if (el.error)
accum.push(`el ${i}: ${el.error}${el.reason ? ` - ${el.reason}` : ""}`);
return accum;
}, []);
throw new db_decorators_1.InternalError(errors.join("\n"));
}
return this.assignMultipleMetadata(models, response.map((r) => r.rev));
}
/**
* @description Retrieves a document from the database
* @summary Fetches a single document from the Nano database by its ID
* @param {string} tableName - The name of the table/collection
* @param {string | number} id - The document identifier
* @return {Promise<Record<string, any>>} A promise that resolves to the retrieved document with metadata
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Database
* A->>A: generateId(tableName, id)
* A->>DB: get(_id)
* alt Success
* DB-->>A: record
* A->>A: assignMetadata(record, record._rev)
* A-->>A: return document with metadata
* else Error
* DB-->>A: error
* A-->>A: throw parseError(e)
* end
*/
async read(tableName, id) {
const _id = this.generateId(tableName, id);
let record;
try {
record = await this.native.get(_id);
}
catch (e) {
throw this.parseError(e);
}
return this.assignMetadata(record, record._rev);
}
/**
* @description Retrieves multiple documents from the database
* @summary Fetches multiple documents from the Nano database by their IDs in a single operation
* @param {string} tableName - The name of the table/collection
* @param {Array<string | number | bigint>} ids - Array of document identifiers
* @return A promise that resolves to an array of retrieved documents with metadata
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Database
* A->>A: Map ids to generateId(tableName, id)
* A->>DB: fetch({keys: mappedIds}, {})
* DB-->>A: results
* A->>A: Process each result row
* loop For each row
* alt Row has error
* A-->>A: throw InternalError
* else Row has document
* A->>A: assignMetadata(doc, doc._rev)
* else No document
* A-->>A: throw InternalError
* end
* end
* A-->>A: return documents with metadata
*/
async readAll(tableName, ids) {
const results = await this.native.fetch({ keys: ids.map((id) => this.generateId(tableName, id)) }, {});
return results.rows.map((r) => {
if (r.error)
throw new db_decorators_1.InternalError(r.error);
if (r.doc) {
const res = Object.assign({}, r.doc);
return this.assignMetadata(res, r.doc[for_couchdb_1.CouchDBKeys.REV]);
}
throw new db_decorators_1.InternalError("Should be impossible");
});
}
/**
* @description Updates a document in the database
* @summary Updates an existing document in the Nano database with the provided data
* @param {string} tableName - The name of the table/collection
* @param {string | number} id - The document identifier
* @param {Record<string, any>} model - The updated document data
* @return {Promise<Record<string, any>>} A promise that resolves to the updated document with metadata
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Database
* A->>DB: insert(model)
* alt Success
* DB-->>A: response with ok=true
* A->>A: assignMetadata(model, response.rev)
* A-->>A: return document with metadata
* else Error
* DB-->>A: error
* A-->>A: throw parseError(e)
* else Not OK
* DB-->>A: response with ok=false
* A-->>A: throw InternalError
* end
*/
async update(tableName, id, model) {
let response;
try {
response = await this.native.insert(model);
}
catch (e) {
throw this.parseError(e);
}
if (!response.ok)
throw new db_decorators_1.InternalError(`Failed to update doc id: ${id} in table ${tableName}`);
return this.assignMetadata(model, response.rev);
}
async updateAll(tableName, ids, models) {
let response;
try {
response = await this.native.bulk({ docs: models });
}
catch (e) {
throw this.parseError(e);
}
if (!response.every((r) => !r.error)) {
const errors = response.reduce((accum, el, i) => {
if (el.error)
accum.push(`el ${i}: ${el.error}${el.reason ? ` - ${el.reason}` : ""}`);
return accum;
}, []);
throw new db_decorators_1.InternalError(errors.join("\n"));
}
return this.assignMultipleMetadata(models, response.map((r) => r.rev));
}
async delete(tableName, id) {
const _id = this.generateId(tableName, id);
let record;
try {
record = await this.native.get(_id);
await this.native.destroy(_id, record._rev);
}
catch (e) {
throw this.parseError(e);
}
return this.assignMetadata(record, record._rev);
}
async deleteAll(tableName, ids) {
const results = await this.native.fetch({ keys: ids.map((id) => this.generateId(tableName, id)) }, {});
const deletion = await this.native.bulk({
docs: results.rows.map((r) => {
r[for_couchdb_1.CouchDBKeys.DELETED] = true;
return r;
}),
});
deletion.forEach((d) => {
if (d.error)
console.error(d.error);
});
return results.rows.map((r) => {
if (r.error)
throw new db_decorators_1.InternalError(r.error);
if (r.doc) {
const res = Object.assign({}, r.doc);
return this.assignMetadata(res, r.doc[for_couchdb_1.CouchDBKeys.REV]);
}
throw new db_decorators_1.InternalError("Should be impossible");
});
}
async raw(rawInput, docsOnly = true) {
try {
const response = await this.native.find(rawInput);
if (response.warning)
console.warn(response.warning);
if (docsOnly)
return response.docs;
return response;
}
catch (e) {
throw this.parseError(e);
}
}
static connect(user, pass, host = "localhost:5984", protocol = "http") {
return (0, nano_1.default)(`${protocol}://${user}:${pass}@${host}`);
}
/**
* @description Creates a new database on the Nano server
* @summary Creates a new database with the specified name on the connected Nano server
* @param {ServerScope} con - The Nano server connection
* @param {string} name - The name of the database to create
* @return {Promise<void>} A promise that resolves when the database is created
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Server
* A->>DB: db.create(name)
* alt Success
* DB-->>A: result with ok=true
* else Error
* DB-->>A: error
* A-->>A: throw parseError(e)
* else Not OK
* DB-->>A: result with ok=false
* A-->>A: throw parseError(error, reason)
* end
*/
static async createDatabase(con, name) {
let result;
try {
result = await con.db.create(name);
}
catch (e) {
throw for_couchdb_1.CouchDBAdapter.parseError(e);
}
const { ok, error, reason } = result;
if (!ok)
throw for_couchdb_1.CouchDBAdapter.parseError(error, reason);
}
/**
* @description Deletes a database from the Nano server
* @summary Removes an existing database with the specified name from the connected Nano server
* @param {ServerScope} con - The Nano server connection
* @param {string} name - The name of the database to delete
* @return {Promise<void>} A promise that resolves when the database is deleted
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant DB as Nano Server
* A->>DB: db.destroy(name)
* alt Success
* DB-->>A: result with ok=true
* else Error
* DB-->>A: error
* A-->>A: throw parseError(e)
* else Not OK
* DB-->>A: result with ok=false
* A-->>A: throw InternalError
* end
*/
static async deleteDatabase(con, name) {
let result;
try {
result = await con.db.destroy(name);
}
catch (e) {
throw for_couchdb_1.CouchDBAdapter.parseError(e);
}
const { ok } = result;
if (!ok)
throw new db_decorators_1.InternalError(`Failed to delete database with name ${name}`);
}
/**
* @description Creates a new user and grants access to a database
* @summary Creates a new user in the Nano server and configures security to grant the user access to a specific database
* @param {ServerScope} con - The Nano server connection
* @param {string} dbName - The name of the database to grant access to
* @param {string} user - The username to create
* @param {string} pass - The password for the new user
* @param {string[]} [roles=["reader", "writer"]] - The roles to assign to the user
* @return {Promise<void>} A promise that resolves when the user is created and granted access
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant U as _users Database
* participant S as Security API
* A->>A: Create user object
* A->>U: insert(user)
* alt Success
* U-->>A: response with ok=true
* A->>S: PUT _security with user permissions
* alt Security Success
* S-->>A: security response with ok=true
* else Security Failure
* S-->>A: security response with ok=false
* A-->>A: throw InternalError
* end
* else Error
* U-->>A: error
* A-->>A: throw parseError(e)
* else Not OK
* U-->>A: response with ok=false
* A-->>A: throw InternalError
* end
*/
static async createUser(con, dbName, user, pass, roles = ["reader", "writer"]) {
const users = con.db.use("_users");
const usr = {
_id: "org.couchdb.user:" + user,
name: user,
password: pass,
roles: roles,
type: "user",
};
try {
const created = await users.insert(usr);
const { ok } = created;
if (!ok)
throw new db_decorators_1.InternalError(`Failed to create user ${user}`);
const security = await con.request({
db: dbName,
method: "put",
path: "_security",
// headers: {
//
// },
body: {
admins: {
names: [user],
roles: [],
},
members: {
names: [user],
roles: roles,
},
},
});
if (!security.ok)
throw new db_decorators_1.InternalError(`Failed to authorize user ${user} to db ${dbName}`);
}
catch (e) {
throw for_couchdb_1.CouchDBAdapter.parseError(e);
}
}
/**
* @description Deletes a user from the Nano server
* @summary Removes an existing user from the Nano server
* @param {ServerScope} con - The Nano server connection
* @param {string} dbName - The name of the database (used for logging purposes)
* @param {string} user - The username to delete
* @return {Promise<void>} A promise that resolves when the user is deleted
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant U as _users Database
* A->>A: Generate user ID
* A->>U: get(id)
* U-->>A: user document
* A->>U: destroy(id, user._rev)
* alt Success
* U-->>A: success response
* else Error
* U-->>A: error
* A-->>A: throw parseError(e)
* end
*/
static async deleteUser(con, dbName, user) {
const users = con.db.use("_users");
const id = "org.couchdb.user:" + user;
try {
const usr = await users.get(id);
await users.destroy(id, usr._rev);
}
catch (e) {
throw for_couchdb_1.CouchDBAdapter.parseError(e);
}
}
/**
* @description Sets up decorations for Nano-specific model properties
* @summary Configures decorators for created_by and updated_by fields in models to be automatically
* populated with the user from the context when documents are created or updated
* @return {void}
* @mermaid
* sequenceDiagram
* participant A as NanoAdapter
* participant D as Decoration
* participant R as Repository
* A->>R: key(PersistenceKeys.CREATED_BY)
* R-->>A: createdByKey
* A->>D: flavouredAs("nano")
* A->>D: for(createdByKey)
* A->>D: define(onCreate(createdByOnNanoCreateUpdate), propMetadata)
* A->>D: apply()
* A->>R: key(PersistenceKeys.UPDATED_BY)
* R-->>A: updatedByKey
* A->>D: flavouredAs("nano")
* A->>D: for(updatedByKey)
* A->>D: define(onCreate(createdByOnNanoCreateUpdate), propMetadata)
* A->>D: apply()
*/
static decoration() {
const createdByKey = core_1.Repository.key(core_1.PersistenceKeys.CREATED_BY);
const updatedByKey = core_1.Repository.key(core_1.PersistenceKeys.UPDATED_BY);
decorator_validation_1.Decoration.flavouredAs("nano")
.for(createdByKey)
.define((0, db_decorators_1.onCreate)(createdByOnNanoCreateUpdate), (0, decorator_validation_1.propMetadata)(createdByKey, {}))
.apply();
decorator_validation_1.Decoration.flavouredAs("nano")
.for(updatedByKey)
.define((0, db_decorators_1.onCreate)(createdByOnNanoCreateUpdate), (0, decorator_validation_1.propMetadata)(updatedByKey, {}))
.apply();
}
}
exports.NanoAdapter = NanoAdapter;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQW9FQSxrRUFvQkM7QUF4RkQsMkRBTWlDO0FBQ2pDLDRCQUEwQjtBQUMxQix1REFPK0I7QUFDL0IsZ0RBQXdCO0FBU3hCLHlFQUt3QztBQUV4Qyx5Q0FLd0I7QUFDeEIsK0NBQTBDO0FBRTFDLHFEQUE4QztBQUU5Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlCRztBQUNJLEtBQUssVUFBVSwyQkFBMkIsQ0FNL0MsT0FBMkIsRUFDM0IsSUFBTyxFQUNQLEdBQVksRUFDWixLQUFRO0lBRVIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQXFCLENBQUM7UUFDeEMsNkRBQTZEO0lBQy9ELENBQUM7SUFBQyxPQUFPLENBQVUsRUFBRSxDQUFDO1FBQ3BCLE1BQU0sSUFBSSx1QkFBZ0IsQ0FDeEIsZ0VBQWdFLENBQ2pFLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBdURHO0FBQ0gsTUFBYSxXQUFZLFNBQVEsNEJBSWhDO0lBQ0MsWUFBWSxLQUF5QixFQUFFLEtBQWM7UUFDbkQsS0FBSyxDQUFDLEtBQUssRUFBRSx1QkFBVyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFRDs7Ozs7Ozs7T0FRRztJQUNnQixLQUFLLENBQUMsS0FBSyxDQUM1QixTQUF3QixFQUN4QixLQUFxQixFQUNyQixLQUF5QjtRQUV6QixPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUUsS0FB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Q0FBQyxDQUFDO1FBQ2hFLE1BQU0sWUFBWSxHQUFHLGlCQUFVLENBQUMsR0FBRyxDQUFDLHNCQUFlLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDaEUsaUNBQVUsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDO2FBQzNCLEdBQUcsQ0FBQyxZQUFZLENBQUM7YUFDakIsTUFBTSxDQUNMLElBQUEsd0JBQVEsRUFBQywyQkFBMkIsQ0FBQyxFQUNyQyxJQUFBLG1DQUFZLEVBQUMsWUFBWSxFQUFFLEVBQUUsQ0FBQyxDQUMvQjthQUNBLEtBQUssRUFBRSxDQUFDO1FBRVgsaUNBQVUsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDO2FBQzNCLEdBQUcsQ0FBQyxZQUFZLENBQUM7YUFDakIsTUFBTSxDQUNMLElBQUEsd0JBQVEsRUFBQywyQkFBMkIsQ0FBQyxFQUNyQyxJQUFBLG1DQUFZLEVBQUMsWUFBWSxFQUFFLEVBQUUsQ0FBQyxDQUMvQjthQUNBLEtBQUssRUFBRSxDQUFDO0lBQ2IsQ0FBQztDQUNGO0FBM2xCRCxrQ0EybEJDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgQ29uZmxpY3RFcnJvcixcbiAgQ29udGV4dCxcbiAgSW50ZXJuYWxFcnJvcixcbiAgb25DcmVhdGUsXG4gIE9wZXJhdGlvbktleXMsXG59IGZyb20gXCJAZGVjYWYtdHMvZGItZGVjb3JhdG9yc1wiO1xuaW1wb3J0IFwicmVmbGVjdC1tZXRhZGF0YVwiO1xuaW1wb3J0IHtcbiAgQ291Y2hEQkFkYXB0ZXIsXG4gIENvdWNoREJLZXlzLFxuICBDcmVhdGVJbmRleFJlcXVlc3QsXG4gIGdlbmVyYXRlSW5kZXhlcyxcbiAgTWFuZ29RdWVyeSxcbiAgTWFuZ29SZXNwb25zZSxcbn0gZnJvbSBcIkBkZWNhZi10cy9mb3ItY291Y2hkYlwiO1xuaW1wb3J0IE5hbm8gZnJvbSBcIm5hbm9cIjtcbmltcG9ydCB7XG4gIERvY3VtZW50QnVsa1Jlc3BvbnNlLFxuICBEb2N1bWVudEdldFJlc3BvbnNlLFxuICBEb2N1bWVudEluc2VydFJlc3BvbnNlLFxuICBEb2N1bWVudFNjb3BlLFxuICBNYXliZURvY3VtZW50LFxuICBTZXJ2ZXJTY29wZSxcbn0gZnJvbSBcIm5hbm9cIjtcbmltcG9ydCB7XG4gIENvbnN0cnVjdG9yLFxuICBEZWNvcmF0aW9uLFxuICBNb2RlbCxcbiAgcHJvcE1ldGFkYXRhLFxufSBmcm9tIFwiQGRlY2FmLXRzL2RlY29yYXRvci12YWxpZGF0aW9uXCI7XG5pbXBvcnQgeyBOYW5vRmxhZ3MgfSBmcm9tIFwiLi90eXBlc1wiO1xuaW1wb3J0IHtcbiAgUGVyc2lzdGVuY2VLZXlzLFxuICBSZWxhdGlvbnNNZXRhZGF0YSxcbiAgUmVwb3NpdG9yeSxcbiAgVW5zdXBwb3J0ZWRFcnJvcixcbn0gZnJvbSBcIkBkZWNhZi10cy9jb3JlXCI7XG5pbXBvcnQgeyBOYW5vRmxhdm91ciB9IGZyb20gXCIuL2NvbnN0YW50c1wiO1xuaW1wb3J0IHsgTmFub1JlcG9zaXRvcnkgfSBmcm9tIFwiLi9OYW5vUmVwb3NpdG9yeVwiO1xuaW1wb3J0IHsgTmFub0Rpc3BhdGNoIH0gZnJvbSBcIi4vTmFub0Rpc3BhdGNoXCI7XG5cbi8qKlxuICogQGRlc2NyaXB0aW9uIFNldHMgdGhlIGNyZWF0b3Igb3IgdXBkYXRlciBmaWVsZCBpbiBhIG1vZGVsIGJhc2VkIG9uIHRoZSB1c2VyIGluIHRoZSBjb250ZXh0XG4gKiBAc3VtbWFyeSBDYWxsYmFjayBmdW5jdGlvbiB1c2VkIGluIGRlY29yYXRvcnMgdG8gYXV0b21hdGljYWxseSBzZXQgdGhlIGNyZWF0ZWRfYnkgb3IgdXBkYXRlZF9ieSBmaWVsZHNcbiAqIHdpdGggdGhlIHVzZXJuYW1lIGZyb20gdGhlIGNvbnRleHQgd2hlbiBhIGRvY3VtZW50IGlzIGNyZWF0ZWQgb3IgdXBkYXRlZFxuICogQHRlbXBsYXRlIE0gLSBUeXBlIGV4dGVuZGluZyBNb2RlbFxuICogQHRlbXBsYXRlIFIgLSBUeXBlIGV4dGVuZGluZyBOYW5vUmVwb3NpdG9yeTxNPlxuICogQHRlbXBsYXRlIFYgLSBUeXBlIGV4dGVuZGluZyBSZWxhdGlvbnNNZXRhZGF0YVxuICogQHBhcmFtIHtSfSB0aGlzIC0gVGhlIHJlcG9zaXRvcnkgaW5zdGFuY2VcbiAqIEBwYXJhbSB7Q29udGV4dDxOYW5vRmxhZ3M+fSBjb250ZXh0IC0gVGhlIG9wZXJhdGlvbiBjb250ZXh0IGNvbnRhaW5pbmcgdXNlciBpbmZvcm1hdGlvblxuICogQHBhcmFtIHtWfSBkYXRhIC0gVGhlIHJlbGF0aW9uIG1ldGFkYXRhXG4gKiBAcGFyYW0ga2V5IC0gVGhlIHByb3BlcnR5IGtleSB0byBzZXQgd2l0aCB0aGUgdXNlcm5hbWVcbiAqIEBwYXJhbSB7TX0gbW9kZWwgLSBUaGUgbW9kZWwgaW5zdGFuY2UgYmVpbmcgY3JlYXRlZCBvciB1cGRhdGVkXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fSBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBvcGVyYXRpb24gaXMgY29tcGxldGVcbiAqIEBmdW5jdGlvbiBjcmVhdGVkQnlPbk5hbm9DcmVhdGVVcGRhdGVcbiAqIEBtZW1iZXJPZiBtb2R1bGU6Zm9yLW5hbm9cbiAqIEBtZXJtYWlkXG4gKiBzZXF1ZW5jZURpYWdyYW1cbiAqICAgcGFydGljaXBhbnQgRiBhcyBjcmVhdGVkQnlPbk5hbm9DcmVhdGVVcGRhdGVcbiAqICAgcGFydGljaXBhbnQgQyBhcyBDb250ZXh0XG4gKiAgIHBhcnRpY2lwYW50IE0gYXMgTW9kZWxcbiAqICAgRi0+PkM6IGdldChcInVzZXJcIilcbiAqICAgQy0tPj5GOiB1c2VyIG9iamVjdFxuICogICBGLT4+TTogc2V0IGtleSB0byB1c2VyLm5hbWVcbiAqICAgTm90ZSBvdmVyIEY6IElmIG5vIHVzZXIgaW4gY29udGV4dFxuICogICBGLS0+PkY6IHRocm93IFVuc3VwcG9ydGVkRXJyb3JcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNyZWF0ZWRCeU9uTmFub0NyZWF0ZVVwZGF0ZTxcbiAgTSBleHRlbmRzIE1vZGVsLFxuICBSIGV4dGVuZHMgTmFub1JlcG9zaXRvcnk8TT4sXG4gIFYgZXh0ZW5kcyBSZWxhdGlvbnNNZXRhZGF0YSxcbj4oXG4gIHRoaXM6IFIsXG4gIGNvbnRleHQ6IENvbnRleHQ8TmFub0ZsYWdzPixcbiAgZGF0YTogVixcbiAga2V5OiBrZXlvZiBNLFxuICBtb2RlbDogTVxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIHRyeSB7XG4gICAgY29uc3QgdXNlciA9IGNvbnRleHQuZ2V0KFwidXNlclwiKTtcbiAgICBtb2RlbFtrZXldID0gdXNlci5uYW1lIGFzIE1bdHlwZW9mIGtleV07XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby11bnVzZWQtdmFyc1xuICB9IGNhdGNoIChlOiB1bmtub3duKSB7XG4gICAgdGhyb3cgbmV3IFVuc3VwcG9ydGVkRXJyb3IoXG4gICAgICBcIk5vIFVzZXIgZm91bmQgaW4gY29udGV4dC4gUGxlYXNlIHByb3ZpZGUgYSB1c2VyIGluIHRoZSBjb250ZXh0XCJcbiAgICApO1xuICB9XG59XG5cbi8qKlxuICogQGRlc2NyaXB0aW9uIEFkYXB0ZXIgZm9yIGludGVyYWN0aW5nIHdpdGggTmFubyBkYXRhYmFzZXNcbiAqIEBzdW1tYXJ5IFByb3ZpZGVzIGEgc3RhbmRhcmRpemVkIGludGVyZmFjZSBmb3IgcGVyZm9ybWluZyBDUlVEIG9wZXJhdGlvbnMgb24gTmFubyBkYXRhYmFzZXMsXG4gKiBleHRlbmRpbmcgdGhlIENvdWNoREIgYWRhcHRlciB3aXRoIE5hbm8tc3BlY2lmaWMgZnVuY3Rpb25hbGl0eS4gVGhpcyBhZGFwdGVyIGhhbmRsZXMgZG9jdW1lbnRcbiAqIGNyZWF0aW9uLCByZWFkaW5nLCB1cGRhdGluZywgYW5kIGRlbGV0aW9uLCBhcyB3ZWxsIGFzIGJ1bGsgb3BlcmF0aW9ucyBhbmQgaW5kZXggbWFuYWdlbWVudC5cbiAqIEB0ZW1wbGF0ZSBEb2N1bWVudFNjb3BlIC0gVGhlIE5hbm8gZG9jdW1lbnQgc2NvcGUgdHlwZVxuICogQHRlbXBsYXRlIE5hbm9GbGFncyAtIENvbmZpZ3VyYXRpb24gZmxhZ3MgZm9yIE5hbm8gb3BlcmF0aW9uc1xuICogQHRlbXBsYXRlIENvbnRleHQgLSBDb250ZXh0IHR5cGUgZm9yIG9wZXJhdGlvbnNcbiAqIEBwYXJhbSB7RG9jdW1lbnRTY29wZTxhbnk+fSBzY29wZSAtIFRoZSBOYW5vIGRvY3VtZW50IHNjb3BlIHRvIHVzZSBmb3IgZGF0YWJhc2Ugb3BlcmF0aW9uc1xuICogQHBhcmFtIHtzdHJpbmd9IFthbGlhc10gLSBPcHRpb25hbCBhbGlhcyBmb3IgdGhlIGFkYXB0ZXJcbiAqIEBjbGFzcyBOYW5vQWRhcHRlclxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIENvbm5lY3QgdG8gYSBOYW5vIGRhdGFiYXNlXG4gKiBjb25zdCBzZXJ2ZXIgPSBOYW5vQWRhcHRlci5jb25uZWN0KCdhZG1pbicsICdwYXNzd29yZCcsICdsb2NhbGhvc3Q6NTk4NCcpO1xuICogY29uc3QgZGIgPSBzZXJ2ZXIuZGIudXNlKCdteV9kYXRhYmFzZScpO1xuICpcbiAqIC8vIENyZWF0ZSBhbiBhZGFwdGVyIGluc3RhbmNlXG4gKiBjb25zdCBhZGFwdGVyID0gbmV3IE5hbm9BZGFwdGVyKGRiKTtcbiAqXG4gKiAvLyBVc2UgdGhlIGFkYXB0ZXIgZm9yIGRhdGFiYXNlIG9wZXJhdGlvbnNcbiAqIGNvbnN0IGRvY3VtZW50ID0gYXdhaXQgYWRhcHRlci5yZWFkKCd1c2VycycsICcxMjMnKTtcbiAqIGBgYFxuICogQG1lcm1haWRcbiAqIGNsYXNzRGlhZ3JhbVxuICogICBjbGFzcyBDb3VjaERCQWRhcHRlciB7XG4gKiAgICAgK2ZsYWdzKClcbiAqICAgICArRGlzcGF0Y2goKVxuICogICAgICtpbmRleCgpXG4gKiAgICAgK2NyZWF0ZSgpXG4gKiAgICAgK3JlYWQoKVxuICogICAgICt1cGRhdGUoKVxuICogICAgICtkZWxldGUoKVxuICogICB9XG4gKiAgIGNsYXNzIE5hbm9BZGFwdGVyIHtcbiAqICAgICArZmxhZ3MoKVxuICogICAgICtEaXNwYXRjaCgpXG4gKiAgICAgK2luZGV4KClcbiAqICAgICArY3JlYXRlKClcbiAqICAgICArY3JlYXRlQWxsKClcbiAqICAgICArcmVhZCgpXG4gKiAgICAgK3JlYWRBbGwoKVxuICogICAgICt1cGRhdGUoKVxuICogICAgICt1cGRhdGVBbGwoKVxuICogICAgICtkZWxldGUoKVxuICogICAgICtkZWxldGVBbGwoKVxuICogICAgICtyYXcoKVxuICogICAgICtzdGF0aWMgY29ubmVjdCgpXG4gKiAgICAgK3N0YXRpYyBjcmVhdGVEYXRhYmFzZSgpXG4gKiAgICAgK3N0YXRpYyBkZWxldGVEYXRhYmFzZSgpXG4gKiAgICAgK3N0YXRpYyBjcmVhdGVVc2VyKClcbiAqICAgICArc3RhdGljIGRlbGV0ZVVzZXIoKVxuICogICAgICtzdGF0aWMgZGVjb3JhdGlvbigpXG4gKiAgIH1cbiAqICAgQ291Y2hEQkFkYXB0ZXIgPHwtLSBOYW5vQWRhcHRlclxuICovXG5leHBvcnQgY2xhc3MgTmFub0FkYXB0ZXIgZXh0ZW5kcyBDb3VjaERCQWRhcHRlcjxcbiAgRG9jdW1lbnRTY29wZTxhbnk+LFxuICBOYW5vRmxhZ3MsXG4gIENvbnRleHQ8TmFub0ZsYWdzPlxuPiB7XG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBEb2N1bWVudFNjb3BlPGFueT4sIGFsaWFzPzogc3RyaW5nKSB7XG4gICAgc3VwZXIoc2NvcGUsIE5hbm9GbGF2b3VyLCBhbGlhcyk7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIEdlbmVyYXRlcyBmbGFncyBmb3IgZGF0YWJhc2Ugb3BlcmF0aW9uc1xuICAgKiBAc3VtbWFyeSBDcmVhdGVzIGEgc2V0IG9mIGZsYWdzIGZvciBhIHNwZWNpZmljIG9wZXJhdGlvbiwgaW5jbHVkaW5nIHVzZXIgaW5mb3JtYXRpb25cbiAgICogQHRlbXBsYXRlIE0gLSBUeXBlIGV4dGVuZGluZyBNb2RlbFxuICAgKiBAcGFyYW0ge09wZXJhdGlvbktleXN9IG9wZXJhdGlvbiAtIFRoZSBvcGVyYXRpb24gYmVpbmcgcGVyZm9ybWVkIChjcmVhdGUsIHJlYWQsIHVwZGF0ZSwgZGVsZXRlKVxuICAgKiBAcGFyYW0ge0NvbnN0cnVjdG9yPE0+fSBtb2RlbCAtIFRoZSBtb2RlbCBjb25zdHJ1Y3RvclxuICAgKiBAcGFyYW0ge1BhcnRpYWw8TmFub0ZsYWdzPn0gZmxhZ3MgLSBQYXJ0aWFsIGZsYWdzIHRvIGJlIG1lcmdlZFxuICAgKiBAcmV0dXJuIHtQcm9taXNlPE5hbm9GbGFncz59IENvbXBsZXRlIGZsYWdzIGZvciB0aGUgb3BlcmF0aW9uXG4gICAqL1xuICBwcm90ZWN0ZWQgb3ZlcnJpZGUgYXN5bmMgZmxhZ3M8TSBleHRlbmRzIE1vZGVsPihcbiAgICBvcGVyYXRpb246IE9wZXJhdGlvbktleXMsXG4gICAgbW9kZWw6IENvbnN0cnVjdG9yPE0+LFxuICAgIGZsYWdzOiBQYXJ0aWFsPE5hbm9GbGFncz5cbiAgKTogUHJvbWlzZTxOYW5vRmxhZ3M+IHtcbiAgICByZXR1cm4gT2JqZWN0LmFzc2lnbihhd2FpdCBzdXBlci5mbGFncyhvcGVyYXRpb24sIG1vZGVsLCBmbGFncyksIHtcbiAgICAgIHVzZXI6IHtcbiAgICAgICAgbmFtZTogdGhpcy5uYXRpdmUuY29uZmlnLnVybC5zcGxpdChcIkBcIilbMF0uc3BsaXQoXCI6XCIpWzBdLFxuICAgICAgfSxcbiAgICB9KSBhcyBOYW5vRmxhZ3M7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIENyZWF0ZXMgYSBuZXcgTmFub0Rpc3BhdGNoIGluc3RhbmNlXG4gICAqIEBzdW1tYXJ5IFJldHVybnMgYSBkaXNwYXRjaGVyIGZvciBoYW5kbGluZyBOYW5vLXNwZWNpZmljIG9wZXJhdGlvbnNcbiAgICogQHJldHVybiB7TmFub0Rpc3BhdGNofSBBIG5ldyBOYW5vRGlzcGF0Y2ggaW5zdGFuY2VcbiAgICovXG4gIHByb3RlY3RlZCBvdmVycmlkZSBEaXNwYXRjaCgpOiBOYW5vRGlzcGF0Y2gge1xuICAgIHJldHVybiBuZXcgTmFub0Rpc3BhdGNoKCk7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIENyZWF0ZXMgZGF0YWJhc2UgaW5kZXhlcyBmb3IgbW9kZWxzXG4gICAqIEBzdW1tYXJ5IEdlbmVyYXRlcyBhbmQgY3JlYXRlcyBpbmRleGVzIGluIHRoZSBOYW5vIGRhdGFiYXNlIGJhc2VkIG9uIHRoZSBwcm92aWRlZCBtb2RlbHNcbiAgICogQHRlbXBsYXRlIE0gLSBUeXBlIGV4dGVuZGluZyBNb2RlbFxuICAgKiBAcGFyYW0gbW9kZWxzIC0gTW9kZWwgY29uc3RydWN0b3JzIHRvIGNyZWF0ZSBpbmRleGVzIGZvclxuICAgKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fSBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIGFsbCBpbmRleGVzIGFyZSBjcmVhdGVkXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEEgYXMgTmFub0FkYXB0ZXJcbiAgICogICBwYXJ0aWNpcGFudCBHIGFzIGdlbmVyYXRlSW5kZXhlc1xuICAgKiAgIHBhcnRpY2lwYW50IERCIGFzIE5hbm8gRGF0YWJhc2VcbiAgICogICBBLT4+RzogZ2VuZXJhdGVJbmRleGVzKG1vZGVscylcbiAgICogICBHLS0+PkE6IGluZGV4ZXNcbiAgICogICBsb29wIEZvciBlYWNoIGluZGV4XG4gICAqICAgICBBLT4+REI6IGNyZWF0ZUluZGV4KGluZGV4KVxuICAgKiAgICAgREItLT4+QTogcmVzcG9uc2VcbiAgICogICAgIE5vdGUgb3ZlciBBOiBDaGVjayBpZiBpbmRleCBhbHJlYWR5IGV4aXN0c1xuICAgKiAgICAgYWx0IEluZGV4IGV4aXN0c1xuICAgKiAgICAgICBBLS0+PkE6IHRocm93IENvbmZsaWN0RXJyb3JcbiAgICogICAgIGVuZFxuICAgKiAgIGVuZFxuICAgKi9cbiAgcHJvdGVjdGVkIG92ZXJyaWRlIGFzeW5jIGluZGV4PE0gZXh0ZW5kcyBNb2RlbD4oXG4gICAgLi4ubW9kZWxzOiBDb25zdHJ1Y3RvcjxNPltdXG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGluZGV4ZXM6IENyZWF0ZUluZGV4UmVxdWVzdFtdID0gZ2VuZXJhdGVJbmRleGVzKG1vZGVscyk7XG4gICAgZm9yIChjb25zdCBpbmRleCBvZiBpbmRleGVzKSB7XG4gICAgICBjb25zdCByZXMgPSBhd2FpdCB0aGlzLm5hdGl2ZS5jcmVhdGVJbmRleChpbmRleCk7XG4gICAgICBjb25zdCB7IHJlc3VsdCwgaWQsIG5hbWUgfSA9IHJlcztcbiAgICAgIGlmIChyZXN1bHQgPT09IFwiZXhpc3RpbmdcIilcbiAgICAgICAgdGhyb3cgbmV3IENvbmZsaWN0RXJyb3IoYEluZGV4IGZvciB0YWJsZSAke25hbWV9IHdpdGggaWQgJHtpZH1gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIENyZWF0ZXMgYSBuZXcgZG9jdW1lbnQgaW4gdGhlIGRhdGFiYXNlXG4gICAqIEBzdW1tYXJ5IEluc2VydHMgYSBuZXcgZG9jdW1lbnQgaW50byB0aGUgTmFubyBkYXRhYmFzZSB3aXRoIHRoZSBwcm92aWRlZCBkYXRhXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB0YWJsZU5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgdGFibGUvY29sbGVjdGlvblxuICAgKiBAcGFyYW0ge3N0cmluZyB8IG51bWJlcn0gaWQgLSBUaGUgZG9jdW1lbnQgaWRlbnRpZmllclxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIGFueT59IG1vZGVsIC0gVGhlIGRvY3VtZW50IGRhdGEgdG8gaW5zZXJ0XG4gICAqIEByZXR1cm4ge1Byb21pc2U8UmVjb3JkPHN0cmluZywgYW55Pj59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRoZSBjcmVhdGVkIGRvY3VtZW50IHdpdGggbWV0YWRhdGFcbiAgICogQG1lcm1haWRcbiAgICogc2VxdWVuY2VEaWFncmFtXG4gICAqICAgcGFydGljaXBhbnQgQSBhcyBOYW5vQWRhcHRlclxuICAgKiAgIHBhcnRpY2lwYW50IERCIGFzIE5hbm8gRGF0YWJhc2VcbiAgICogICBBLT4+REI6IGluc2VydChtb2RlbClcbiAgICogICBhbHQgU3VjY2Vzc1xuICAgKiAgICAgREItLT4+QTogcmVzcG9uc2Ugd2l0aCBvaz10cnVlXG4gICAqICAgICBBLT4+QTogYXNzaWduTWV0YWRhdGEobW9kZWwsIHJlc3BvbnNlLnJldilcbiAgICogICAgIEEtLT4+QTogcmV0dXJuIGRvY3VtZW50IHdpdGggbWV0YWRhdGFcbiAgICogICBlbHNlIEVycm9yXG4gICAqICAgICBEQi0tPj5BOiBlcnJvclxuICAgKiAgICAgQS0tPj5BOiB0aHJvdyBwYXJzZUVycm9yKGUpXG4gICAqICAgZWxzZSBOb3QgT0tcbiAgICogICAgIERCLS0+PkE6IHJlc3BvbnNlIHdpdGggb2s9ZmFsc2VcbiAgICogICAgIEEtLT4+QTogdGhyb3cgSW50ZXJuYWxFcnJvclxuICAgKiAgIGVuZFxuICAgKi9cbiAgb3ZlcnJpZGUgYXN5bmMgY3JlYXRlKFxuICAgIHRhYmxlTmFtZTogc3RyaW5nLFxuICAgIGlkOiBzdHJpbmcgfCBudW1iZXIsXG4gICAgbW9kZWw6IFJlY29yZDxzdHJpbmcsIGFueT5cbiAgKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBhbnk+PiB7XG4gICAgbGV0IHJlc3BvbnNlOiBEb2N1bWVudEluc2VydFJlc3BvbnNlO1xuICAgIHRyeSB7XG4gICAgICByZXNwb25zZSA9IGF3YWl0IHRoaXMubmF0aXZlLmluc2VydChtb2RlbCk7XG4gICAgfSBjYXRjaCAoZTogYW55KSB7XG4gICAgICB0aHJvdyB0aGlzLnBhcnNlRXJyb3IoZSk7XG4gICAgfVxuXG4gICAgaWYgKCFyZXNwb25zZS5vaylcbiAgICAgIHRocm93IG5ldyBJbnRlcm5hbEVycm9yKFxuICAgICAgICBgRmFpbGVkIHRvIGluc2VydCBkb2MgaWQ6ICR7aWR9IGluIHRhYmxlICR7dGFibGVOYW1lfWBcbiAgICAgICk7XG4gICAgcmV0dXJuIHRoaXMuYXNzaWduTWV0YWRhdGEobW9kZWwsIHJlc3BvbnNlLnJldik7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIENyZWF0ZXMgbXVsdGlwbGUgZG9jdW1lbnRzIGluIHRoZSBkYXRhYmFzZVxuICAgKiBAc3VtbWFyeSBJbnNlcnRzIG11bHRpcGxlIGRvY3VtZW50cyBpbnRvIHRoZSBOYW5vIGRhdGFiYXNlIGluIGEgc2luZ2xlIGJ1bGsgb3BlcmF0aW9uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB0YWJsZU5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgdGFibGUvY29sbGVjdGlvblxuICAgKiBAcGFyYW0ge3N0cmluZ1tdIHwgbnVtYmVyW119IGlkcyAtIEFycmF5IG9mIGRvY3VtZW50IGlkZW50aWZpZXJzXG4gICAqIEBwYXJhbSBtb2RlbHMgLSBBcnJheSBvZiBkb2N1bWVudCBkYXRhIHRvIGluc2VydFxuICAgKiBAcmV0dXJuIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGFuIGFycmF5IG9mIGNyZWF0ZWQgZG9jdW1lbnRzIHdpdGggbWV0YWRhdGFcbiAgICogQG1lcm1haWRcbiAgICogc2VxdWVuY2VEaWFncmFtXG4gICAqICAgcGFydGljaXBhbnQgQSBhcyBOYW5vQ