UNPKG

@decaf-ts/for-nano

Version:

decaf-ts persistence adapter for CouchDB via nano

621 lines 70.5 kB
import { ConflictError, InternalError, onCreate, } from "@decaf-ts/db-decorators"; import "reflect-metadata"; import { CouchDBAdapter, CouchDBKeys, generateIndexes, } from "@decaf-ts/for-couchdb"; import Nano from "nano"; import { Decoration, propMetadata, } from "@decaf-ts/decorator-validation"; import { PersistenceKeys, Repository, UnsupportedError, } from "@decaf-ts/core"; import { NanoFlavour } from "./constants.js"; import { NanoDispatch } from "./NanoDispatch.js"; /** * @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 */ export 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 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 */ export class NanoAdapter extends CouchDBAdapter { constructor(scope, alias) { super(scope, 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(); } /** * @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 = generateIndexes(models); for (const index of indexes) { const res = await this.native.createIndex(index); const { result, id, name } = res; if (result === "existing") throw new 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 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 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 InternalError(r.error); if (r.doc) { const res = Object.assign({}, r.doc); return this.assignMetadata(res, r.doc[CouchDBKeys.REV]); } throw new 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 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 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[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 InternalError(r.error); if (r.doc) { const res = Object.assign({}, r.doc); return this.assignMetadata(res, r.doc[CouchDBKeys.REV]); } throw new 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 Nano(`${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 CouchDBAdapter.parseError(e); } const { ok, error, reason } = result; if (!ok) throw 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 CouchDBAdapter.parseError(e); } const { ok } = result; if (!ok) throw new 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 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 InternalError(`Failed to authorize user ${user} to db ${dbName}`); } catch (e) { throw 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 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 = Repository.key(PersistenceKeys.CREATED_BY); const updatedByKey = Repository.key(PersistenceKeys.UPDATED_BY); Decoration.flavouredAs("nano") .for(createdByKey) .define(onCreate(createdByOnNanoCreateUpdate), propMetadata(createdByKey, {})) .apply(); Decoration.flavouredAs("nano") .for(updatedByKey) .define(onCreate(createdByOnNanoCreateUpdate), propMetadata(updatedByKey, {})) .apply(); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCxhQUFhLEVBRWIsYUFBYSxFQUNiLFFBQVEsR0FFVCxNQUFNLHlCQUF5QixDQUFDO0FBQ2pDLE9BQU8sa0JBQWtCLENBQUM7QUFDMUIsT0FBTyxFQUNMLGNBQWMsRUFDZCxXQUFXLEVBRVgsZUFBZSxHQUdoQixNQUFNLHVCQUF1QixDQUFDO0FBQy9CLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQVN4QixPQUFPLEVBRUwsVUFBVSxFQUVWLFlBQVksR0FDYixNQUFNLGdDQUFnQyxDQUFDO0FBRXhDLE9BQU8sRUFDTCxlQUFlLEVBRWYsVUFBVSxFQUNWLGdCQUFnQixHQUNqQixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sRUFBRSxXQUFXLEVBQUUsdUJBQW9CO0FBRTFDLE9BQU8sRUFBRSxZQUFZLEVBQUUsMEJBQXVCO0FBRTlDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBeUJHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSwyQkFBMkIsQ0FNL0MsT0FBMkIsRUFDM0IsSUFBTyxFQUNQLEdBQVksRUFDWixLQUFRO0lBRVIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQXFCLENBQUM7UUFDeEMsNkRBQTZEO0lBQy9ELENBQUM7SUFBQyxPQUFPLENBQVUsRUFBRSxDQUFDO1FBQ3BCLE1BQU0sSUFBSSxnQkFBZ0IsQ0FDeEIsZ0VBQWdFLENBQ2pFLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBdURHO0FBQ0gsTUFBTSxPQUFP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QUFJLENBQUMsQ0FBQztRQUNwQyxDQUFDO1FBQUMsT0FBTyxDQUFNLEVBQUUsQ0FBQztZQUNoQixNQUFNLGNBQWMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDckMsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQXNCRztJQUNILE1BQU0sQ0FBQyxVQUFVO1FBQ2YsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDaEUsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDaEUsVUFBVSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUM7YUFDM0IsR0FBRyxDQUFDLFlBQVksQ0FBQzthQUNqQixNQUFNLENBQ0wsUUFBUSxDQUFDLDJCQUEyQixDQUFDLEVBQ3JDLFlBQVksQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFDLENBQy9CO2FBQ0EsS0FBSyxFQUFFLENBQUM7UUFFWCxVQUFVLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQzthQUMzQixHQUFHLENBQUMsWUFBWSxDQUFDO2FBQ2pCLE1BQU0sQ0FDTCxRQUFRLENBQUMsMkJBQTJCLENBQUMsRUFDckMsWUFBWSxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsQ0FDL0I7YUFDQSxLQUFLLEVBQUUsQ0FBQztJQUNiLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbmZsaWN0RXJyb3IsXG4gIENvbnRleHQsXG4gIEludGVybmFsRXJyb3IsXG4gIG9uQ3JlYXRlLFxuICBPcGVyYXRpb25LZXlzLFxufSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcbmltcG9ydCBcInJlZmxlY3QtbWV0YWRhdGFcIjtcbmltcG9ydCB7XG4gIENvdWNoREJBZGFwdGVyLFxuICBDb3VjaERCS2V5cyxcbiAgQ3JlYXRlSW5kZXhSZXF1ZXN0LFxuICBnZW5lcmF0ZUluZGV4ZXMsXG4gIE1hbmdvUXVlcnksXG4gIE1hbmdvUmVzcG9uc2UsXG59IGZyb20gXCJAZGVjYWYtdHMvZm9yLWNvdWNoZGJcIjtcbmltcG9ydCBOYW5vIGZyb20gXCJuYW5vXCI7XG5pbXBvcnQge1xuICBEb2N1bWVudEJ1bGtSZXNwb25zZSxcbiAgRG9jdW1lbnRHZXRSZXNwb25zZSxcbiAgRG9jdW1lbnRJbnNlcnRSZXNwb25zZSxcbiAgRG9jdW1lbnRTY29wZSxcbiAgTWF5YmVEb2N1bWVudCxcbiAgU2VydmVyU2NvcGUsXG59IGZyb20gXCJuYW5vXCI7XG5pbXBvcnQge1xuICBDb25zdHJ1Y3RvcixcbiAgRGVjb3JhdGlvbixcbiAgTW9kZWwsXG4gIHByb3BNZXRhZGF0YSxcbn0gZnJvbSBcIkBkZWNhZi10cy9kZWNvcmF0b3ItdmFsaWRhdGlvblwiO1xuaW1wb3J0IHsgTmFub0ZsYWdzIH0gZnJvbSBcIi4vdHlwZXNcIjtcbmltcG9ydCB7XG4gIFBlcnNpc3RlbmNlS2V5cyxcbiAgUmVsYXRpb25zTWV0YWRhdGEsXG4gIFJlcG9zaXRvcnksXG4gIFVuc3VwcG9ydGVkRXJyb3IsXG59IGZyb20gXCJAZGVjYWYtdHMvY29yZVwiO1xuaW1wb3J0IHsgTmFub0ZsYXZvdXIgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcbmltcG9ydCB7IE5hbm9SZXBvc2l0b3J5IH0gZnJvbSBcIi4vTmFub1JlcG9zaXRvcnlcIjtcbmltcG9ydCB7IE5hbm9EaXNwYXRjaCB9IGZyb20gXCIuL05hbm9EaXNwYXRjaFwiO1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBTZXRzIHRoZSBjcmVhdG9yIG9yIHVwZGF0ZXIgZmllbGQgaW4gYSBtb2RlbCBiYXNlZCBvbiB0aGUgdXNlciBpbiB0aGUgY29udGV4dFxuICogQHN1bW1hcnkgQ2FsbGJhY2sgZnVuY3Rpb24gdXNlZCBpbiBkZWNvcmF0b3JzIHRvIGF1dG9tYXRpY2FsbHkgc2V0IHRoZSBjcmVhdGVkX2J5IG9yIHVwZGF0ZWRfYnkgZmllbGRzXG4gKiB3aXRoIHRoZSB1c2VybmFtZSBmcm9tIHRoZSBjb250ZXh0IHdoZW4gYSBkb2N1bWVudCBpcyBjcmVhdGVkIG9yIHVwZGF0ZWRcbiAqIEB0ZW1wbGF0ZSBNIC0gVHlwZSBleHRlbmRpbmcgTW9kZWxcbiAqIEB0ZW1wbGF0ZSBSIC0gVHlwZSBleHRlbmRpbmcgTmFub1JlcG9zaXRvcnk8TT5cbiAqIEB0ZW1wbGF0ZSBWIC0gVHlwZSBleHRlbmRpbmcgUmVsYXRpb25zTWV0YWRhdGFcbiAqIEBwYXJhbSB7Un0gdGhpcyAtIFRoZSByZXBvc2l0b3J5IGluc3RhbmNlXG4gKiBAcGFyYW0ge0NvbnRleHQ8TmFub0ZsYWdzPn0gY29udGV4dCAtIFRoZSBvcGVyYXRpb24gY29udGV4dCBjb250YWluaW5nIHVzZXIgaW5mb3JtYXRpb25cbiAqIEBwYXJhbSB7Vn0gZGF0YSAtIFRoZSByZWxhdGlvbiBtZXRhZGF0YVxuICogQHBhcmFtIGtleSAtIFRoZSBwcm9wZXJ0eSBrZXkgdG8gc2V0IHdpdGggdGhlIHVzZXJuYW1lXG4gKiBAcGFyYW0ge019IG1vZGVsIC0gVGhlIG1vZGVsIGluc3RhbmNlIGJlaW5nIGNyZWF0ZWQgb3IgdXBkYXRlZFxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn0gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgb3BlcmF0aW9uIGlzIGNvbXBsZXRlXG4gKiBAZnVuY3Rpb24gY3JlYXRlZEJ5T25OYW5vQ3JlYXRlVXBkYXRlXG4gKiBAbWVtYmVyT2YgbW9kdWxlOmZvci1uYW5vXG4gKiBAbWVybWFpZFxuICogc2VxdWVuY2VEaWFncmFtXG4gKiAgIHBhcnRpY2lwYW50IEYgYXMgY3JlYXRlZEJ5T25OYW5vQ3JlYXRlVXBkYXRlXG4gKiAgIHBhcnRpY2lwYW50IEMgYXMgQ29udGV4dFxuICogICBwYXJ0aWNpcGFudCBNIGFzIE1vZGVsXG4gKiAgIEYtPj5DOiBnZXQoXCJ1c2VyXCIpXG4gKiAgIEMtLT4+RjogdXNlciBvYmplY3RcbiAqICAgRi0+Pk06IHNldCBrZXkgdG8gdXNlci5uYW1lXG4gKiAgIE5vdGUgb3ZlciBGOiBJZiBubyB1c2VyIGluIGNvbnRleHRcbiAqICAgRi0tPj5GOiB0aHJvdyBVbnN1cHBvcnRlZEVycm9yXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVkQnlPbk5hbm9DcmVhdGVVcGRhdGU8XG4gIE0gZXh0ZW5kcyBNb2RlbCxcbiAgUiBleHRlbmRzIE5hbm9SZXBvc2l0b3J5PE0+LFxuICBWIGV4dGVuZHMgUmVsYXRpb25zTWV0YWRhdGEsXG4+KFxuICB0aGlzOiBSLFxuICBjb250ZXh0OiBDb250ZXh0PE5hbm9GbGFncz4sXG4gIGRhdGE6IFYsXG4gIGtleToga2V5b2YgTSxcbiAgbW9kZWw6IE1cbik6IFByb21pc2U8dm9pZD4ge1xuICB0cnkge1xuICAgIGNvbnN0IHVzZXIgPSBjb250ZXh0LmdldChcInVzZXJcIik7XG4gICAgbW9kZWxba2V5XSA9IHVzZXIubmFtZSBhcyBNW3R5cGVvZiBrZXldO1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW51c2VkLXZhcnNcbiAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgIHRocm93IG5ldyBVbnN1cHBvcnRlZEVycm9yKFxuICAgICAgXCJObyBVc2VyIGZvdW5kIGluIGNvbnRleHQuIFBsZWFzZSBwcm92aWRlIGEgdXNlciBpbiB0aGUgY29udGV4dFwiXG4gICAgKTtcbiAgfVxufVxuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBBZGFwdGVyIGZvciBpbnRlcmFjdGluZyB3aXRoIE5hbm8gZGF0YWJhc2VzXG4gKiBAc3VtbWFyeSBQcm92aWRlcyBhIHN0YW5kYXJkaXplZCBpbnRlcmZhY2UgZm9yIHBlcmZvcm1pbmcgQ1JVRCBvcGVyYXRpb25zIG9uIE5hbm8gZGF0YWJhc2VzLFxuICogZXh0ZW5kaW5nIHRoZSBDb3VjaERCIGFkYXB0ZXIgd2l0aCBOYW5vLXNwZWNpZmljIGZ1bmN0aW9uYWxpdHkuIFRoaXMgYWRhcHRlciBoYW5kbGVzIGRvY3VtZW50XG4gKiBjcmVhdGlvbiwgcmVhZGluZywgdXBkYXRpbmcsIGFuZCBkZWxldGlvbiwgYXMgd2VsbCBhcyBidWxrIG9wZXJhdGlvbnMgYW5kIGluZGV4IG1hbmFnZW1lbnQuXG4gKiBAdGVtcGxhdGUgRG9jdW1lbnRTY29wZSAtIFRoZSBOYW5vIGRvY3VtZW50IHNjb3BlIHR5cGVcbiAqIEB0ZW1wbGF0ZSBOYW5vRmxhZ3MgLSBDb25maWd1cmF0aW9uIGZsYWdzIGZvciBOYW5vIG9wZXJhdGlvbnNcbiAqIEB0ZW1wbGF0ZSBDb250ZXh0IC0gQ29udGV4dCB0eXBlIGZvciBvcGVyYXRpb25zXG4gKiBAcGFyYW0ge0RvY3VtZW50U2NvcGU8YW55Pn0gc2NvcGUgLSBUaGUgTmFubyBkb2N1bWVudCBzY29wZSB0byB1c2UgZm9yIGRhdGFiYXNlIG9wZXJhdGlvbnNcbiAqIEBwYXJhbSB7c3RyaW5nfSBbYWxpYXNdIC0gT3B0aW9uYWwgYWxpYXMgZm9yIHRoZSBhZGFwdGVyXG4gKiBAY2xhc3MgTmFub0FkYXB0ZXJcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDb25uZWN0IHRvIGEgTmFubyBkYXRhYmFzZVxuICogY29uc3Qgc2VydmVyID0gTmFub0FkYXB0ZXIuY29ubmVjdCgnYWRtaW4nLCAncGFzc3dvcmQnLCAnbG9jYWxob3N0OjU5ODQnKTtcbiAqIGNvbnN0IGRiID0gc2VydmVyLmRiLnVzZSgnbXlfZGF0YWJhc2UnKTtcbiAqXG4gKiAvLyBDcmVhdGUgYW4gYWRhcHRlciBpbnN0YW5jZVxuICogY29uc3QgYWRhcHRlciA9IG5ldyBOYW5vQWRhcHRlcihkYik7XG4gKlxuICogLy8gVXNlIHRoZSBhZGFwdGVyIGZvciBkYXRhYmFzZSBvcGVyYXRpb25zXG4gKiBjb25zdCBkb2N1bWVudCA9IGF3YWl0IGFkYXB0ZXIucmVhZCgndXNlcnMnLCAnMTIzJyk7XG4gKiBgYGBcbiAqIEBtZXJtYWlkXG4gKiBjbGFzc0RpYWdyYW1cbiAqICAgY2xhc3MgQ291Y2hEQkFkYXB0ZXIge1xuICogICAgICtmbGFncygpXG4gKiAgICAgK0Rpc3BhdGNoKClcbiAqICAgICAraW5kZXgoKVxuICogICAgICtjcmVhdGUoKVxuICogICAgICtyZWFkKClcbiAqICAgICArdXBkYXRlKClcbiAqICAgICArZGVsZXRlKClcbiAqICAgfVxuICogICBjbGFzcyBOYW5vQWRhcHRlciB7XG4gKiAgICAgK2ZsYWdzKClcbiAqICAgICArRGlzcGF0Y2goKVxuICogICAgICtpbmRleCgpXG4gKiAgICAgK2NyZWF0ZSgpXG4gKiAgICAgK2NyZWF0ZUFsbCgpXG4gKiAgICAgK3JlYWQoKVxuICogICAgICtyZWFkQWxsKClcbiAqICAgICArdXBkYXRlKClcbiAqICAgICArdXBkYXRlQWxsKClcbiAqICAgICArZGVsZXRlKClcbiAqICAgICArZGVsZXRlQWxsKClcbiAqICAgICArcmF3KClcbiAqICAgICArc3RhdGljIGNvbm5lY3QoKVxuICogICAgICtzdGF0aWMgY3JlYXRlRGF0YWJhc2UoKVxuICogICAgICtzdGF0aWMgZGVsZXRlRGF0YWJhc2UoKVxuICogICAgICtzdGF0aWMgY3JlYXRlVXNlcigpXG4gKiAgICAgK3N0YXRpYyBkZWxldGVVc2VyKClcbiAqICAgICArc3RhdGljIGRlY29yYXRpb24oKVxuICogICB9XG4gKiAgIENvdWNoREJBZGFwdGVyIDx8LS0gTmFub0FkYXB0ZXJcbiAqL1xuZXhwb3J0IGNsYXNzIE5hbm9BZGFwdGVyIGV4dGVuZHMgQ291Y2hEQkFkYXB0ZXI8XG4gIERvY3VtZW50U2NvcGU8YW55PixcbiAgTmFub0ZsYWdzLFxuICBDb250ZXh0PE5hbm9GbGFncz5cbj4ge1xuICBjb25zdHJ1Y3RvcihzY29wZTogRG9jdW1lbnRTY29wZTxhbnk+LCBhbGlhcz86IHN0cmluZykge1xuICAgIHN1cGVyKHNjb3BlLCBOYW5vRmxhdm91ciwgYWxpYXMpO1xuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBHZW5lcmF0ZXMgZmxhZ3MgZm9yIGRhdGFiYXNlIG9wZXJhdGlvbnNcbiAgICogQHN1bW1hcnkgQ3JlYXRlcyBhIHNldCBvZiBmbGFncyBmb3IgYSBzcGVjaWZpYyBvcGVyYXRpb24sIGluY2x1ZGluZyB1c2VyIGluZm9ybWF0aW9uXG4gICAqIEB0ZW1wbGF0ZSBNIC0gVHlwZSBleHRlbmRpbmcgTW9kZWxcbiAgICogQHBhcmFtIHtPcGVyYXRpb25LZXlzfSBvcGVyYXRpb24gLSBUaGUgb3BlcmF0aW9uIGJlaW5nIHBlcmZvcm1lZCAoY3JlYXRlLCByZWFkLCB1cGRhdGUsIGRlbGV0ZSlcbiAgICogQHBhcmFtIHtDb25zdHJ1Y3RvcjxNPn0gbW9kZWwgLSBUaGUgbW9kZWwgY29uc3RydWN0b3JcbiAgICogQHBhcmFtIHtQYXJ0aWFsPE5hbm9GbGFncz59IGZsYWdzIC0gUGFydGlhbCBmbGFncyB0byBiZSBtZXJnZWRcbiAgICogQHJldHVybiB7UHJvbWlzZTxOYW5vRmxhZ3M+fSBDb21wbGV0ZSBmbGFncyBmb3IgdGhlIG9wZXJhdGlvblxuICAgKi9cbiAgcHJvdGVjdGVkIG92ZXJyaWRlIGFzeW5jIGZsYWdzPE0gZXh0ZW5kcyBNb2RlbD4oXG4gICAgb3BlcmF0aW9uOiBPcGVyYXRpb25LZXlzLFxuICAgIG1vZGVsOiBDb25zdHJ1Y3RvcjxNPixcbiAgICBmbGFnczogUGFydGlhbDxOYW5vRmxhZ3M+XG4gICk6IFByb21pc2U8TmFub0ZsYWdzPiB7XG4gICAgcmV0dXJuIE9iamVjdC5hc3NpZ24oYXdhaXQgc3VwZXIuZmxhZ3Mob3BlcmF0aW9uLCBtb2RlbCwgZmxhZ3MpLCB7XG4gICAgICB1c2VyOiB7XG4gICAgICAgIG5hbWU6IHRoaXMubmF0aXZlLmNvbmZpZy51cmwuc3BsaXQoXCJAXCIpWzBdLnNwbGl0KFwiOlwiKVswXSxcbiAgICAgIH0sXG4gICAgfSkgYXMgTmFub0ZsYWdzO1xuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBDcmVhdGVzIGEgbmV3IE5hbm9EaXNwYXRjaCBpbnN0YW5jZVxuICAgKiBAc3VtbWFyeSBSZXR1cm5zIGEgZGlzcGF0Y2hlciBmb3IgaGFuZGxpbmcgTmFuby1zcGVjaWZpYyBvcGVyYXRpb25zXG4gICAqIEByZXR1cm4ge05hbm9EaXNwYXRjaH0gQSBuZXcgTmFub0Rpc3BhdGNoIGluc3RhbmNlXG4gICAqL1xuICBwcm90ZWN0ZWQgb3ZlcnJpZGUgRGlzcGF0Y2goKTogTmFub0Rpc3BhdGNoIHtcbiAgICByZXR1cm4gbmV3IE5hbm9EaXNwYXRjaCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBDcmVhdGVzIGRhdGFiYXNlIGluZGV4ZXMgZm9yIG1vZGVsc1xuICAgKiBAc3VtbWFyeSBHZW5lcmF0ZXMgYW5kIGNyZWF0ZXMgaW5kZXhlcyBpbiB0aGUgTmFubyBkYXRhYmFzZSBiYXNlZCBvbiB0aGUgcHJvdmlkZWQgbW9kZWxzXG4gICAqIEB0ZW1wbGF0ZSBNIC0gVHlwZSBleHRlbmRpbmcgTW9kZWxcbiAgICogQHBhcmFtIG1vZGVscyAtIE1vZGVsIGNvbnN0cnVjdG9ycyB0byBjcmVhdGUgaW5kZXhlcyBmb3JcbiAgICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn0gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiBhbGwgaW5kZXhlcyBhcmUgY3JlYXRlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBBIGFzIE5hbm9BZGFwdGVyXG4gICAqICAgcGFydGljaXBhbnQgRyBhcyBnZW5lcmF0ZUluZGV4ZXNcbiAgICogICBwYXJ0aWNpcGFudCBEQiBhcyBOYW5vIERhdGFiYXNlXG4gICAqICAgQS0+Pkc6IGdlbmVyYXRlSW5kZXhlcyhtb2RlbHMpXG4gICAqICAgRy0tPj5BOiBpbmRleGVzXG4gICAqICAgbG9vcCBGb3IgZWFjaCBpbmRleFxuICAgKiAgICAgQS0+PkRCOiBjcmVhdGVJbmRleChpbmRleClcbiAgICogICAgIERCLS0+PkE6IHJlc3BvbnNlXG4gICAqICAgICBOb3RlIG92ZXIgQTogQ2hlY2sgaWYgaW5kZXggYWxyZWFkeSBleGlzdHNcbiAgICogICAgIGFsdCBJbmRleCBleGlzdHNcbiAgICogICAgICAgQS0tPj5BOiB0aHJvdyBDb25mbGljdEVycm9yXG4gICAqICAgICBlbmRcbiAgICogICBlbmRcbiAgICovXG4gIHByb3RlY3RlZCBvdmVycmlkZSBhc3luYyBpbmRleDxNIGV4dGVuZHMgTW9kZWw+KFxuICAgIC4uLm1vZGVsczogQ29uc3RydWN0b3I8TT5bXVxuICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBpbmRleGVzOiBDcmVhdGVJbmRleFJlcXVlc3RbXSA9IGdlbmVyYXRlSW5kZXhlcyhtb2RlbHMpO1xuICAgIGZvciAoY29uc3QgaW5kZXggb2YgaW5kZXhlcykge1xuICAgICAgY29uc3QgcmVzID0gYXdhaXQgdGhpcy5uYXRpdmUuY3JlYXRlSW5kZXgoaW5kZXgpO1xuICAgICAgY29uc3QgeyByZXN1bHQsIGlkLCBuYW1lIH0gPSByZXM7XG4gICAgICBpZiAocmVzdWx0ID09PSBcImV4aXN0aW5nXCIpXG4gICAgICAgIHRocm93IG5ldyBDb25mbGljdEVycm9yKGBJbmRleCBmb3IgdGFibGUgJHtuYW1lfSB3aXRoIGlkICR7aWR9YCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBDcmVhdGVzIGEgbmV3IGRvY3VtZW50IGluIHRoZSBkYXRhYmFzZVxuICAgKiBAc3VtbWFyeSBJbnNlcnRzIGEgbmV3IGRvY3VtZW50IGludG8gdGhlIE5hbm8gZGF0YWJhc2Ugd2l0aCB0aGUgcHJvdmlkZWQgZGF0YVxuICAgKiBAcGFyYW0ge3N0cmluZ30gdGFibGVOYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHRhYmxlL2NvbGxlY3Rpb25cbiAgICogQHBhcmFtIHtzdHJpbmcgfCBudW1iZXJ9IGlkIC0gVGhlIGRvY3VtZW50IGlkZW50aWZpZXJcbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+fSBtb2RlbCAtIFRoZSBkb2N1bWVudCBkYXRhIHRvIGluc2VydFxuICAgKiBAcmV0dXJuIHtQcm9taXNlPFJlY29yZDxzdHJpbmcsIGFueT4+fSBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0aGUgY3JlYXRlZCBkb2N1bWVudCB3aXRoIG1ldGFkYXRhXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEEgYXMgTmFub0FkYXB0ZXJcbiAgICogICBwYXJ0aWNpcGFudCBEQiBhcyBOYW5vIERhdGFiYXNlXG4gICAqICAgQS0+PkRCOiBpbnNlcnQobW9kZWwpXG4gICAqICAgYWx0IFN1Y2Nlc3NcbiAgICogICAgIERCLS0+PkE6IHJlc3BvbnNlIHdpdGggb2s9dHJ1ZVxuICAgKiAgICAgQS0+PkE6IGFzc2lnbk1ldGFkYXRhKG1vZGVsLCByZXNwb25zZS5yZXYpXG4gICAqICAgICBBLS0+PkE6IHJldHVybiBkb2N1bWVudCB3aXRoIG1ldGFkYXRhXG4gICAqICAgZWxzZSBFcnJvclxuICAgKiAgICAgREItLT4+QTogZXJyb3JcbiAgICogICAgIEEtLT4+QTogdGhyb3cgcGFyc2VFcnJvcihlKVxuICAgKiAgIGVsc2UgTm90IE9LXG4gICAqICAgICBEQi0tPj5BOiByZXNwb25zZSB3aXRoIG9rPWZhbHNlXG4gICAqICAgICBBLS0+PkE6IHRocm93IEludGVybmFsRXJyb3JcbiAgICogICBlbmRcbiAgICovXG4gIG92ZXJyaWRlIGFzeW5jIGNyZWF0ZShcbiAgICB0YWJsZU5hbWU6IHN0cmluZyxcbiAgICBpZDogc3RyaW5nIHwgbnVtYmVyLFxuICAgIG1vZGVsOiBSZWNvcmQ8c3RyaW5nLCBhbnk+XG4gICk6IFByb21pc2U8UmVjb3JkPHN0cmluZywgYW55Pj4ge1xuICAgIGxldCByZXNwb25zZTogRG9jdW1lbnRJbnNlcnRSZXNwb25zZTtcbiAgICB0cnkge1xuICAgICAgcmVzcG9uc2UgPSBhd2FpdCB0aGlzLm5hdGl2ZS5pbnNlcnQobW9kZWwpO1xuICAgIH0gY2F0Y2ggKGU6IGFueSkge1xuICAgICAgdGhyb3cgdGhpcy5wYXJzZUVycm9yKGUpO1xuICAgIH1cblxuICAgIGlmICghcmVzcG9uc2Uub2spXG4gICAgICB0aHJvdyBuZXcgSW50ZXJuYWxFcnJvcihcbiAgICAgICAgYEZhaWxlZCB0byBpbnNlcnQgZG9jIGlkOiAke2lkfSBpbiB0YWJsZSAke3RhYmxlTmFtZX1gXG4gICAgICApO1xuICAgIHJldHVybiB0aGlzLmFzc2lnbk1ldGFkYXRhKG1vZGVsLCByZXNwb25zZS5yZXYpO1xuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBDcmVhdGVzIG11bHRpcGxlIGRvY3VtZW50cyBpbiB0aGUgZGF0YWJhc2VcbiAgICogQHN1bW1hcnkgSW5zZXJ0cyBtdWx0aXBsZSBkb2N1bWVudHMgaW50byB0aGUgTmFubyBkYXRhYmFzZSBpbiBhIHNpbmdsZSBidWxrIG9wZXJhdGlvblxuICAgKiBAcGFyYW0ge3N0cmluZ30gdGFibGVOYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHRhYmxlL2NvbGxlY3Rpb25cbiAgICogQHBhcmFtIHtzdHJpbmdbXSB8IG51bWJlcltdfSBpZHMgLSBBcnJheSBvZiBkb2N1bWVudCBpZGVudGlmaWVyc1xuICAgKiBAcGFyYW0gbW9kZWxzIC0gQXJyYXkgb2YgZG9jdW1lbnQgZGF0YSB0byBpbnNlcnRcbiAgICogQHJldHVybiBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byBhbiBhcnJheSBvZiBjcmVhdGVkIGRvY3VtZW50cyB3aXRoIG1ldGFkYXRhXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEEgYXMgTmFub0FkYXB0ZXJcbiAgICogICBwYXJ0aWNpcGFudCBEQiBhcyBOYW5vIERhdGFiYXNlXG4gICAqICAgQS0+PkRCOiBidWxrKHtkb2NzOiBtb2RlbHN9KVxuICAgKiAgIGFsdCBTdWNjZXNzXG4gICAqICAgICBEQi0tPj5BOiByZXNwb25zZSBhcnJheVxuICAgKiAgICAgQS0+PkE6IENoZWNrIGlmIGFsbCByZXNwb25zZXMgaGF2ZSBubyBlcnJvcnNcbiAgICogICAgIGFsdCBBbGwgT0tcbiAgICogICAgICAgQS0+PkE6IGFzc2lnbk11bHRpcGxlTWV0YWRhdGEobW9kZWxzLCByZXZzKVxuICAgKiAgICAgICBBLS0+PkE6IHJldHVybiBkb2N1bWVudHMgd2l0aCBtZXRhZGF0YVxuICAgKiAgICAgZWxzZSBTb21lIGVycm9yc1xuICAgKiAgICAgICBBLT4+QTogQ29sbGVjdCBlcnJvciBtZXNzYWdlc1xuICAgKiAgICAgICBBLS0+PkE6IHRocm93IEludGVybmFsRXJyb3Igd2l0aCBjb2xsZW