@decaf-ts/for-nano
Version:
decaf-ts persistence adapter for CouchDB via nano
852 lines (847 loc) • 97 kB
JavaScript
import { OperationKeys, InternalError, ConflictError, onCreate } from '@decaf-ts/db-decorators';
import 'reflect-metadata';
import { CouchDBKeys, CouchDBAdapter, generateIndexes } from '@decaf-ts/for-couchdb';
import Nano from 'nano';
import { Decoration, propMetadata } from '@decaf-ts/decorator-validation';
import { Dispatch, Repository, PersistenceKeys, UnsupportedError } from '@decaf-ts/core';
/**
* @description Identifier for the Nano database flavor
* @summary Constant string that identifies the database type as "nano" for use in adapter selection and configuration
* @const NanoFlavour
* @memberOf module:for-nano
*/
const NanoFlavour = "nano";
/**
* @description Dispatcher for Nano database change events
* @summary Handles the subscription to and processing of database change events from a Nano database,
* notifying observers when documents are created, updated, or deleted
* @template DocumentScope - The Nano document scope type
* @param {number} [timeout=5000] - Timeout in milliseconds for change feed requests
* @class NanoDispatch
* @example
* ```typescript
* // Create a dispatcher for a Nano database
* const db = server.db.use('my_database');
* const adapter = new NanoAdapter(db);
* const dispatch = new NanoDispatch();
*
* // The dispatcher will automatically subscribe to changes
* // and notify observers when documents change
* ```
* @mermaid
* classDiagram
* class Dispatch {
* +initialize()
* +updateObservers()
* }
* class NanoDispatch {
* -observerLastUpdate?: string
* -attemptCounter: number
* -timeout: number
* +constructor(timeout)
* #changeHandler()
* #initialize()
* }
* Dispatch <|-- NanoDispatch
*/
class NanoDispatch extends Dispatch {
constructor(timeout = 5000) {
super();
this.timeout = timeout;
this.attemptCounter = 0;
}
/**
* @description Processes database change events
* @summary Handles the response from the Nano changes feed, processes the changes,
* and notifies observers about document changes
* @param {RequestError | null} error - Error object if the request failed
* @param response - The changes response from Nano
* @param {any} [headers] - Response headers (unused)
* @return {Promise<void>} A promise that resolves when all changes have been processed
* @mermaid
* sequenceDiagram
* participant D as NanoDispatch
* participant L as Logger
* participant O as Observers
* Note over D: Receive changes from Nano
* alt Error in response
* D->>L: Log error
* D-->>D: Return early
* end
* alt Response is string
* D->>D: Parse JSON from string
* end
* D->>D: Process changes
* D->>D: Group changes by table and operation
* loop For each table
* loop For each operation
* D->>O: updateObservers(table, operation, ids)
* D->>D: Update observerLastUpdate
* D->>L: Log successful dispatch
* end
* end
*/
async changeHandler(error, response,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
headers) {
const log = this.log.for(this.changeHandler);
if (error)
return log.error(`Error in change request: ${error}`);
try {
response = (typeof response === "string"
? response
.split("\n")
.filter((r) => !!r)
.map((r) => JSON.parse(r))
: response);
}
catch (e) {
return log.error(`Error parsing couchdb change feed: ${e}`);
}
const count = response.length;
if (count > 0) {
log.debug(`Received ${count} changes. processing...`);
const changes = response
.map((rec, i) => {
if (i === count - 1) {
if (this.observerLastUpdate ===
rec.last_seq)
log.error(`Invalid last update check: ${this.observerLastUpdate} !== ${rec.last_seq}`);
return;
}
const r = rec;
const [table, id] = r.id.split(CouchDBKeys.SEPARATOR);
return {
table: table,
id: id,
operation: r.deleted
? OperationKeys.DELETE
: r.changes[r.changes.length - 1].rev.split("-")[0] === "1"
? OperationKeys.CREATE
: OperationKeys.UPDATE,
step: r.changes[r.changes.length - 1].rev,
};
})
.reduce((accum, r) => {
if (!r)
return accum;
const { table, id, operation, step } = r;
if (!accum[table])
accum[table] = {};
if (!accum[table][operation])
accum[table][operation] = { ids: new Set(), step: step };
accum[table][operation].ids.add(id);
accum[table][operation].step = step;
return accum;
}, {});
for (const table of Object.keys(changes)) {
for (const op of Object.keys(changes[table])) {
try {
await this.updateObservers(table, op, [
...changes[table][op].ids.values(),
]);
this.observerLastUpdate = changes[table][op].step;
log.verbose(`Observer refresh dispatched by ${op} for ${table}`);
log.debug(`pks: ${Array.from(changes[table][op].ids.values())}`);
}
catch (e) {
log.error(`Failed to dispatch observer refresh for ${table}, op ${op}: ${e}`);
}
}
}
}
}
/**
* @description Initializes the dispatcher and subscribes to database changes
* @summary Sets up the continuous changes feed subscription to the Nano database
* and handles reconnection attempts if the connection fails
* @return {Promise<void>} A promise that resolves when the subscription is established
* @mermaid
* sequenceDiagram
* participant D as NanoDispatch
* participant S as subscribeToCouch
* participant DB as Nano Database
* participant L as Logger
* D->>S: Call subscribeToCouch
* S->>S: Check adapter and native
* alt No adapter or native
* S-->>S: throw InternalError
* end
* S->>DB: changes(options, changeHandler)
* alt Success
* DB-->>S: Subscription established
* S-->>D: Promise resolves
* D->>L: Log successful subscription
* else Error
* DB-->>S: Error
* S->>S: Increment attemptCounter
* alt attemptCounter > 3
* S->>L: Log error
* S-->>D: Promise rejects
* else attemptCounter <= 3
* S->>L: Log retry
* S->>S: Wait timeout
* S->>S: Recursive call to subscribeToCouch
* end
* end
*/
async initialize() {
const log = this.log.for(this.initialize);
const subLog = log.for(subscribeToCouch);
async function subscribeToCouch() {
if (!this.adapter || !this.native)
throw new InternalError(`No adapter/native observed for dispatch`);
try {
this.native.changes({
feed: "continuous",
include_docs: false,
since: this.observerLastUpdate || "now",
timeout: this.timeout,
}, this.changeHandler.bind(this));
}
catch (e) {
if (++this.attemptCounter > 3)
return subLog.error(`Failed to subscribe to couchdb changes: ${e}`);
subLog.info(`Failed to subscribe to couchdb changes: ${e}. Retrying in 5 seconds...`);
await new Promise((resolve) => setTimeout(resolve, this.timeout));
return subscribeToCouch.call(this);
}
}
subscribeToCouch
.call(this)
.then(() => {
this.log.info(`Subscribed to couchdb changes`);
})
.catch((e) => {
throw new InternalError(`Failed to subscribe to couchdb changes: ${e}`);
});
}
}
/**
* @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 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 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();
}
}
// Forces override for Nano Decoration
NanoAdapter.decoration();
/**
* @description A TypeScript module for interacting with Nano databases
* @summary This module provides a set of utilities, classes, and types for working with Nano databases. It includes repository patterns, adapters, and type definitions to simplify database operations.
* @module for-nano
*/
/**
* @description Package version identifier
* @summary Stores the current package version string for the for-nano module
* @const VERSION
* @memberOf module:for-nano
*/
const VERSION = "0.1.6";
export { NanoAdapter, NanoFlavour, VERSION, createdByOnNanoCreateUpdate };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yLW5hbm8uZXNtLmNqcyIsInNvdXJjZXMiOlsiLi4vc3JjL2NvbnN0YW50cy50cyIsIi4uL3NyYy9OYW5vRGlzcGF0Y2gudHMiLCIuLi9zcmMvYWRhcHRlci50cyIsIi4uL3NyYy9pbmRleC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBkZXNjcmlwdGlvbiBJZGVudGlmaWVyIGZvciB0aGUgTmFubyBkYXRhYmFzZSBmbGF2b3JcbiAqIEBzdW1tYXJ5IENvbnN0YW50IHN0cmluZyB0aGF0IGlkZW50aWZpZXMgdGhlIGRhdGFiYXNlIHR5cGUgYXMgXCJuYW5vXCIgZm9yIHVzZSBpbiBhZGFwdGVyIHNlbGVjdGlvbiBhbmQgY29uZmlndXJhdGlvblxuICogQGNvbnN0IE5hbm9GbGF2b3VyXG4gKiBAbWVtYmVyT2YgbW9kdWxlOmZvci1uYW5vXG4gKi9cbmV4cG9ydCBjb25zdCBOYW5vRmxhdm91ciA9IFwibmFub1wiO1xuIiwiaW1wb3J0IHsgRGlzcGF0Y2ggfSBmcm9tIFwiQGRlY2FmLXRzL2NvcmVcIjtcbmltcG9ydCB7XG4gIERhdGFiYXNlQ2hhbmdlc1Jlc3BvbnNlLFxuICBEYXRhYmFzZUNoYW5nZXNSZXN1bHRJdGVtLFxuICBEb2N1bWVudFNjb3BlLFxuICBSZXF1ZXN0RXJyb3IsXG59IGZyb20gXCJuYW5vXCI7XG5pbXBvcnQgeyBJbnRlcm5hbEVycm9yLCBPcGVyYXRpb25LZXlzIH0gZnJvbSBcIkBkZWNhZi10cy9kYi1kZWNvcmF0b3JzXCI7XG5pbXBvcnQgeyBDb3VjaERCS2V5cyB9IGZyb20gXCJAZGVjYWYtdHMvZm9yLWNvdWNoZGJcIjtcblxuLyoqXG4gKiBAZGVzY3JpcHRpb24gRGlzcGF0Y2hlciBmb3IgTmFubyBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzXG4gKiBAc3VtbWFyeSBIYW5kbGVzIHRoZSBzdWJzY3JpcHRpb24gdG8gYW5kIHByb2Nlc3Npbmcgb2YgZGF0YWJhc2UgY2hhbmdlIGV2ZW50cyBmcm9tIGEgTmFubyBkYXRhYmFzZSxcbiAqIG5vdGlmeWluZyBvYnNlcnZlcnMgd2hlbiBkb2N1bWVudHMgYXJlIGNyZWF0ZWQsIHVwZGF0ZWQsIG9yIGRlbGV0ZWRcbiAqIEB0ZW1wbGF0ZSBEb2N1bWVudFNjb3BlIC0gVGhlIE5hbm8gZG9jdW1lbnQgc2NvcGUgdHlwZVxuICogQHBhcmFtIHtudW1iZXJ9IFt0aW1lb3V0PTUwMDBdIC0gVGltZW91dCBpbiBtaWxsaXNlY29uZHMgZm9yIGNoYW5nZSBmZWVkIHJlcXVlc3RzXG4gKiBAY2xhc3MgTmFub0Rpc3BhdGNoXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGEgZGlzcGF0Y2hlciBmb3IgYSBOYW5vIGRhdGFiYXNlXG4gKiBjb25zdCBkYiA9IHNlcnZlci5kYi51c2UoJ215X2RhdGFiYXNlJyk7XG4gKiBjb25zdCBhZGFwdGVyID0gbmV3IE5hbm9BZGFwdGVyKGRiKTtcbiAqIGNvbnN0IGRpc3BhdGNoID0gbmV3IE5hbm9EaXNwYXRjaCgpO1xuICpcbiAqIC8vIFRoZSBkaXNwYXRjaGVyIHdpbGwgYXV0b21hdGljYWxseSBzdWJzY3JpYmUgdG8gY2hhbmdlc1xuICogLy8gYW5kIG5vdGlmeSBvYnNlcnZlcnMgd2hlbiBkb2N1bWVudHMgY2hhbmdlXG4gKiBgYGBcbiAqIEBtZXJtYWlkXG4gKiBjbGFzc0RpYWdyYW1cbiAqICAgY2xhc3MgRGlzcGF0Y2gge1xuICogICAgICtpbml0aWFsaXplKClcbiAqICAgICArdXBkYXRlT2JzZXJ2ZXJzKClcbiAqICAgfVxuICogICBjbGFzcyBOYW5vRGlzcGF0Y2gge1xuICogICAgIC1vYnNlcnZlckxhc3RVcGRhdGU/OiBzdHJpbmdcbiAqICAgICAtYXR0ZW1wdENvdW50ZXI6IG51bWJlclxuICogICAgIC10aW1lb3V0OiBudW1iZXJcbiAqICAgICArY29uc3RydWN0b3IodGltZW91dClcbiAqICAgICAjY2hhbmdlSGFuZGxlcigpXG4gKiAgICAgI2luaXRpYWxpemUoKVxuICogICB9XG4gKiAgIERpc3BhdGNoIDx8LS0gTmFub0Rpc3BhdGNoXG4gKi9cbmV4cG9ydCBjbGFzcyBOYW5vRGlzcGF0Y2ggZXh0ZW5kcyBEaXNwYXRjaDxEb2N1bWVudFNjb3BlPGFueT4+IHtcbiAgcHJpdmF0ZSBvYnNlcnZlckxhc3RVcGRhdGU/OiBzdHJpbmc7XG4gIHByaXZhdGUgYXR0ZW1wdENvdW50ZXI6IG51bWJlciA9IDA7XG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgdGltZW91dCA9IDUwMDApIHtcbiAgICBzdXBlcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBQcm9jZXNzZXMgZGF0YWJhc2UgY2hhbmdlIGV2ZW50c1xuICAgKiBAc3VtbWFyeSBIYW5kbGVzIHRoZSByZXNwb25zZSBmcm9tIHRoZSBOYW5vIGNoYW5nZXMgZmVlZCwgcHJvY2Vzc2VzIHRoZSBjaGFuZ2VzLFxuICAgKiBhbmQgbm90aWZpZXMgb2JzZXJ2ZXJzIGFib3V0IGRvY3VtZW50IGNoYW5nZXNcbiAgICogQHBhcmFtIHtSZXF1ZXN0RXJyb3IgfCBudWxsfSBlcnJvciAtIEVycm9yIG9iamVjdCBpZiB0aGUgcmVxdWVzdCBmYWlsZWRcbiAgICogQHBhcmFtIHJlc3BvbnNlIC0gVGhlIGNoYW5nZXMgcmVzcG9uc2UgZnJvbSBOYW5vXG4gICAqIEBwYXJhbSB7YW55fSBbaGVhZGVyc10gLSBSZXNwb25zZSBoZWFkZXJzICh1bnVzZWQpXG4gICAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gYWxsIGNoYW5nZXMgaGF2ZSBiZWVuIHByb2Nlc3NlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBEIGFzIE5hbm9EaXNwYXRjaFxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgcGFydGljaXBhbnQgTyBhcyBPYnNlcnZlcnNcbiAgICogICBOb3RlIG92ZXIgRDogUmVjZWl2ZSBjaGFuZ2VzIGZyb20gTmFub1xuICAgKiAgIGFsdCBFcnJvciBpbiByZXNwb25zZVxuICAgKiAgICAgRC0+Pkw6IExvZyBlcnJvclxuICAgKiAgICAgRC0tPj5EOiBSZXR1cm4gZWFybHlcbiAgICogICBlbmRcbiAgICogICBhbHQgUmVzcG9uc2UgaXMgc3RyaW5nXG4gICAqICAgICBELT4+RDogUGFyc2UgSlNPTiBmcm9tIHN0cmluZ1xuICAgKiAgIGVuZFxuICAgKiAgIEQtPj5EOiBQcm9jZXNzIGNoYW5nZXNcbiAgICogICBELT4+RDogR3JvdXAgY2hhbmdlcyBieSB0YWJsZSBhbmQgb3BlcmF0aW9uXG4gICAqICAgbG9vcCBGb3IgZWFjaCB0YWJsZVxuICAgKiAgICAgbG9vcCBGb3IgZWFjaCBvcGVyYXRpb25cbiAgICogICAgICAgRC0+Pk86IHVwZGF0ZU9ic2VydmVycyh0YWJsZSwgb3BlcmF0aW9uLCBpZHMpXG4gICAqICAgICAgIEQtPj5EOiBVcGRhdGUgb2JzZXJ2ZXJMYXN0VXBkYXRlXG4gICAqICAgICAgIEQtPj5MOiBMb2cgc3VjY2Vzc2Z1bCBkaXNwYXRjaFxuICAgKiAgICAgZW5kXG4gICAqICAgZW5kXG4gICAqL1xuICBwcm90ZWN0ZWQgYXN5bmMgY2hhbmdlSGFuZGxlcihcbiAgICBlcnJvcjogUmVxdWVzdEVycm9yIHwgbnVsbCxcbiAgICByZXNwb25zZTogKERhdGFiYXNlQ2hhbmdlc1Jlc3BvbnNlIHwgRGF0YWJhc2VDaGFuZ2VzUmVzdWx0SXRlbSlbXSB8IHN0cmluZyxcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gICAgaGVhZGVycz86IGFueVxuICApIHtcbiAgICBjb25zdCBsb2cgPSB0aGlzLmxvZy5mb3IodGhpcy5jaGFuZ2VIYW5kbGVyKTtcbiAgICBpZiAoZXJyb3IpIHJldHVybiBsb2cuZXJyb3IoYEVycm9yIGluIGNoYW5nZSByZXF1ZXN0OiAke2Vycm9yfWApO1xuICAgIHRyeSB7XG4gICAgICByZXNwb25zZSA9IChcbiAgICAgICAgdHlwZW9mIHJlc3BvbnNlID09PSBcInN0cmluZ1wiXG4gICAgICAgICAgPyByZXNwb25zZVxuICAgICAgICAgICAgICAuc3BsaXQoXCJcXG5cIilcbiAgICAgICAgICAgICAgLmZpbHRlcigocikgPT4gISFyKVxuICAgICAgICAgICAgICAubWFwKChyKSA9PiBKU09OLnBhcnNlKHIpKVxuICAgICAgICAgIDogcmVzcG9uc2VcbiAgICAgICkgYXMgRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2VbXTtcbiAgICB9IGNhdGNoIChlOiB1bmtub3duKSB7XG4gICAgICByZXR1cm4gbG9nLmVycm9yKGBFcnJvciBwYXJzaW5nIGNvdWNoZGIgY2hhbmdlIGZlZWQ6ICR7ZX1gKTtcbiAgICB9XG4gICAgY29uc3QgY291bnQgPSByZXNwb25zZS5sZW5ndGg7XG4gICAgaWYgKGNvdW50ID4gMCkge1xuICAgICAgbG9nLmRlYnVnKGBSZWNlaXZlZCAke2NvdW50fSBjaGFuZ2VzLiBwcm9jZXNzaW5nLi4uYCk7XG4gICAgICBjb25zdCBjaGFuZ2VzID0gcmVzcG9uc2VcbiAgICAgICAgLm1hcCgocmVjLCBpKSA9PiB7XG4gICAgICAgICAgaWYgKGkgPT09IGNvdW50IC0gMSkge1xuICAgICAgICAgICAgaWYgKFxuICAgICAgICAgICAgICB0aGlzLm9ic2VydmVyTGFzdFVwZGF0ZSA9PT1cbiAgICAgICAgICAgICAgKHJlYyBhcyBEYXRhYmFzZUNoYW5nZXNSZXNwb25zZSkubGFzdF9zZXFcbiAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgbG9nLmVycm9yKFxuICAgICAgICAgICAgICAgIGBJbnZhbGlkIGxhc3QgdXBkYXRlIGNoZWNrOiAke3RoaXMub2JzZXJ2ZXJMYXN0VXBkYXRlfSAhPT0gJHsocmVjIGFzIERhdGFiYXNlQ2hhbmdlc1Jlc3BvbnNlKS5sYXN0X3NlcX1gXG4gICAgICAgICAgICAgICk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IHIgPSByZWMgYXMgRGF0YWJhc2VDaGFuZ2VzUmVzdWx0SXRlbTtcbiAgICAgICAgICBjb25zdCBbdGFibGUsIGlkXSA9IHIuaWQuc3BsaXQoQ291Y2hEQktleXMuU0VQQVJBVE9SKTtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgdGFibGU6IHRhYmxlLFxuICAgICAgICAgICAgaWQ6IGlkLFxuICAgICAgICAgICAgb3BlcmF0aW9uOiByLmRlbGV0ZWRcbiAgICAgICAgICAgICAgPyBPcGVyYXRpb25LZXlzLkRFTEVURVxuICAgICAgICAgICAgICA6IHIuY2hhbmdlc1tyLmNoYW5nZXMubGVuZ3RoIC0gMV0ucmV2LnNwbGl0KFwiLVwiKVswXSA9PT0gXCIxXCJcbiAgICAgICAgICAgICAgICA/IE9wZXJhdGlvbktleXMuQ1JFQVRFXG4gICAgICAgICAgICAgICAgOiBPcGVyYXRpb25LZXlzLlVQREFURSxcbiAgICAgICAgICAgIHN0ZXA6IHIuY2hhbmdlc1tyLmNoYW5nZXMubGVuZ3RoIC0gMV0ucmV2LFxuICAgICAgICAgIH07XG4gICAgICAgIH0pXG4gICAgICAgIC5yZWR1Y2UoXG4gICAgICAgICAgKFxuICAgICAgICAgICAgYWNjdW06IFJlY29yZDxcbiAgICAgICAgICAgICAgc3RyaW5nLFxuICAgICAgICAgICAgICBSZWNvcmQ8XG4gICAgICAgICAgICAgICAgc3RyaW5nLFxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgIGlkczogU2V0PGFueT47XG4gICAgICAgICAgICAgICAgICBzdGVwOiBzdHJpbmc7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICA+XG4gICAgICAgICAgICA+LFxuICAgICAgICAgICAgclxuICAgICAgICAgICkgPT4ge1xuICAgICAgICAgICAgaWYgKCFyKSByZXR1cm4gYWNjdW07XG4gICAgICAgICAgICBjb25zdCB7IHRhYmxlLCBpZCwgb3BlcmF0aW9uLCBzdGVwIH0gPSByIGFzIHtcbiAgICAgICAgICAgICAgdGFibGU6IHN0cmluZztcbiAgICAgICAgICAgICAgaWQ6IHN0cmluZztcbiAgICAgICAgICAgICAgb3BlcmF0aW9uOiBPcGVyYXRpb25LZXlzO1xuICAgICAgICAgICAgICBzdGVwOiBzdHJpbmc7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgaWYgKCFhY2N1bVt0YWJsZV0pIGFjY3VtW3RhYmxlXSA9IHt9O1xuICAgICAgICAgICAgaWYgKCFhY2N1bVt0YWJsZV1bb3BlcmF0aW9uXSlcbiAgICAgICAgICAgICAgYWNjdW1bdGFibGVdW29wZXJhdGlvbl0gPSB7IGlkczogbmV3IFNldCgpLCBzdGVwOiBzdGVwIH07XG4gICAgICAgICAgICBhY2N1bVt0YWJsZV1bb3BlcmF0aW9uXS5pZHMuYWRkKGlkKTtcbiAgICAgICAgICAgIGFjY3VtW3RhYmxlXVtvcGVyYXRpb25dLnN0ZXAgPSBzdGVwO1xuICAgICAgICAgICAgcmV0dXJuIGFjY3VtO1xuICAgICAgICAgIH0sXG4gICAgICAgICAge31cbiAgICAgICAgKTtcblxuICAgICAgZm9yIChjb25zdCB0YWJsZSBvZiBPYmplY3Qua2V5cyhjaGFuZ2VzKSkge1xuICAgICAgICBmb3IgKGNvbnN0IG9wIG9mIE9iamVjdC5rZXlzKGNoYW5nZXNbdGFibGVdKSkge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnVwZGF0ZU9ic2VydmVycyh0YWJsZSwgb3AsIFtcbiAgICAgICAgICAgICAgLi4uY2hhbmdlc1t0YWJsZV1bb3BdLmlkcy52YWx1ZXMoKSxcbiAgICAgICAgICAgIF0pO1xuICAgICAgICAgICAgdGhpcy5vYnNlcnZlckxhc3RVcGRhdGUgPSBjaGFuZ2VzW3RhYmxlXVtvcF0uc3RlcDtcbiAgICAgICAgICAgIGxvZy52ZXJib3NlKGBPYnNlcnZlciByZWZyZXNoIGRpc3BhdGNoZWQgYnkgJHtvcH0gZm9yICR7dGFibGV9YCk7XG4gICAgICAgICAgICBsb2cuZGVidWcoYHBrczogJHtBcnJheS5mcm9tKGNoYW5nZXNbdGFibGVdW29wXS5pZHMudmFsdWVzKCkpfWApO1xuICAgICAgICAgIH0gY2F0Y2ggKGU6IHVua25vd24pIHtcbiAgICAgICAgICAgIGxvZy5lcnJvcihcbiAgICAgICAgICAgICAgYEZhaWxlZCB0byBkaXNwYXRjaCBvYnNlcnZlciByZWZyZXNoIGZvciAke3RhYmxlfSwgb3AgJHtvcH06ICR7ZX1gXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gSW5pdGlhbGl6ZXMgdGhlIGRpc3BhdGNoZXIgYW5kIHN1YnNjcmliZXMgdG8gZGF0YWJhc2UgY2hhbmdlc1xuICAgKiBAc3VtbWFyeSBTZXRzIHVwIHRoZSBjb250aW51b3VzIGNoYW5nZXMgZmVlZCBzdWJzY3JpcHRpb24gdG8gdGhlIE5hbm8gZGF0YWJhc2VcbiAgICogYW5kIGhhbmRsZXMgcmVjb25uZWN0aW9uIGF0dGVtcHRzIGlmIHRoZSBjb25uZWN0aW9uIGZhaWxzXG4gICAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gdGhlIHN1YnNjcmlwdGlvbiBpcyBlc3RhYmxpc2hlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBEIGFzIE5hbm9EaXNwYXRjaFxuICAgKiAgIHBhcnRpY2lwYW50IFMgYXMgc3Vic2NyaWJlVG9Db3VjaFxuICAgKiAgIHBhcnRpY2lwYW50IERCIGFzIE5hbm8gRGF0YWJhc2VcbiAgICogICBwYXJ0aWNpcGFudCBMIGFzIExvZ2dlclxuICAgKiAgIEQtPj5TOiBDYWxsIHN1YnNjcmliZVRvQ291Y2hcbiAgICogICBTLT4+UzogQ2hlY2sgYWRhcHRlciBhbmQgbmF0aXZlXG4gICAqICAgYWx0IE5vIGFkYXB0ZXIgb3IgbmF0aXZlXG4gICAqICAgICBTLS0+PlM6IHRocm93IEludGVybmFsRXJyb3JcbiAgICogICBlbmRcbiAgICogICBTLT4+REI6IGNoYW5nZXMob3B0aW9ucywgY2hhbmdlSGFuZGxlcilcbiAgICogICBhbHQgU3VjY2Vzc1xuICAgKiAgICAgREItLT4+UzogU3Vic2NyaXB0aW9uIGVzdGFibGlzaGVkXG4gICAqICAgICBTLS0+PkQ6IFByb21pc2UgcmVzb2x2ZXNcbiAgICogICAgIEQtPj5MOiBMb2cgc3VjY2Vzc2Z1bCBzdWJzY3JpcHRpb25cbiAgICogICBlbHNlIEVycm9yXG4gICAqICAgICBEQi0tPj5TOiBFcnJvclxuICAgKiAgICAgUy0+PlM6IEluY3JlbWVudCBhdHRlbXB0Q291bnRlclxuICAgKiAgICAgYWx0IGF0dGVtcHRDb3VudGVyID4gM1xuICAgKiAgICAgICBTLT4+TDogTG9nIGVycm9yXG4gICAqICAgICAgIFMtLT4+RDogUHJvbWlzZSByZWplY3RzXG4gICAqICAgICBlbHNlIGF0dGVtcHRDb3VudGVyIDw9IDNcbiAgICogICAgICAgUy0+Pkw6IExvZyByZXRyeVxuICAgKiAgICAgICBTLT4+UzogV2FpdCB0aW1lb3V0XG4gICAqICAgICAgIFMtPj5TOiBSZWN1cnNpdmUgY2FsbCB0byBzdWJzY3JpYmVUb0NvdWNoXG4gICAqICAgICBlbmRcbiAgICogICBlbmRcbiAgICovXG4gIHByb3RlY3RlZCBvdmVycmlkZSBhc3luYyBpbml0aWFsaXplKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGxvZyA9IHRoaXMubG9nLmZvcih0aGlzLmluaXRpYWxpemUpO1xuICAgIGNvbnN0IHN1YkxvZyA9IGxvZy5mb3Ioc3Vic2NyaWJlVG9Db3VjaCk7XG4gICAgYXN5bmMgZnVuY3Rpb24gc3Vic2NyaWJlVG9Db3VjaCh0aGlzOiBOYW5vRGlzcGF0Y2gpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgIGlmICghdGhpcy5hZGFwdGVyIHx8ICF0aGlzLm5hdGl2ZSlcbiAgICAgICAgdGhyb3cgbmV3IEludGVybmFsRXJyb3IoYE5vIGFkYXB0ZXIvbmF0aXZlIG9ic2VydmVkIGZvciBkaXNwYXRjaGApO1xuXG4gICAgICB0cnkge1xuICAgICAgICB0aGlzLm5hdGl2ZS5jaGFuZ2VzKFxuICAgICAgICAgIHtcbiAgICAgICAgICAgIGZlZWQ6IFwiY29udGludW91c1wiLFxuICAgICAgICAgICAgaW5jbHVkZV9kb2NzOiBmYWxzZSxcbiAgICAgICAgICAgIHNpbmNlOiB0aGlzLm9ic2VydmVyTGFzdFVwZGF0ZSB8fCBcIm5vd1wiLFxuICAgICAgICAgICAgdGltZW91dDogdGhpcy50aW1lb3V0LFxuICAgICAgICAgIH0sXG4gICAgICAgICAgdGhpcy5jaGFuZ2VIYW5kbGVyLmJpbmQodGhpcykgYXMgYW55XG4gICAgICAgICk7XG4gICAgICB9IGNhdGNoIChlOiB1bmtub3duKSB7XG4gICAgICAgIGlmICgrK3RoaXMuYXR0ZW1wdENvdW50ZXIgPiAzKVxuICAgICAgICAgIHJldHVybiBzdWJMb2cuZXJyb3IoYEZhaWxlZCB0byBzdWJzY3JpYmUgdG8gY291Y2hkYiBjaGFuZ2VzOiAke2V9YCk7XG4gICAgICAgIHN1YkxvZy5pbmZvKFxuICAgICAgICAgIGBGYWlsZWQgdG8gc3Vic2NyaWJlIHRvIGNvdWNoZGIgY2hhbmdlczogJHtlfS4gUmV0cnlpbmcgaW4gNSBzZWNvbmRzLi4uYFxuICAgICAgICApO1xuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gc2V0VGltZW91dChyZXNvbHZlLCB0aGlzLnRpbWVvdXQpKTtcbiAgICAgICAgcmV0dXJuIHN1YnNjcmliZVRvQ291Y2guY2FsbCh0aGlzKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBzdWJzY3JpYmVUb0NvdWNoXG4gICAgICAuY2FsbCh0aGlzKVxuICAgICAgLnRoZW4oKCkgPT4ge1xuICAgICAgICB0aGlzLmxvZy5pbmZvKGBTdWJzY3JpYmVkIHRvIGNvdWNoZGIgY2hhbmdlc2ApO1xuICAgICAgfSlcbiAgICAgIC5jYXRjaCgoZTogdW5rbm93bikgPT4ge1xuICAgICAgICB0aHJvdyBuZXcgSW50ZXJuYWxFcnJvcihgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBjb3VjaGRiIGNoYW5nZXM6ICR7ZX1gKTtcbiAgICAgIH0pO1xuICB9XG59XG4iLCJpbXBvcnQge1xuICBDb25mbGljdEVycm9yLFxuICBDb250ZXh0LFxuICBJbnRlcm5hbEVycm9yLFxuICBvbkNyZWF0ZSxcbiAgT3BlcmF0aW9uS2V5cyxcbn0gZnJvbSBcIkBkZWNhZi10cy9kYi1kZWNvcmF0b3JzXCI7XG5pbXBvcnQgXCJyZWZsZWN0LW1ldGFkYXRhXCI7XG5pbXBvcnQge1xuICBDb3VjaERCQWRhcHRlcixcbiAgQ291Y2hEQktleXMsXG4gIENyZWF0ZUluZGV4UmVxdWVzdCxcbiAgZ2VuZXJhdGVJbmRleGVzLFxuICBNYW5nb1F1ZXJ5LFxuICBNYW5nb1Jlc3BvbnNlLFxufSBmcm9tIFwiQGRlY2FmLXRzL2Zvci1jb3VjaGRiXCI7XG5pbXBvcnQgTmFubyBmcm9tIFwibmFub1wiO1xuaW1wb3J0IHtcbiAgRG9jdW1lbnRCdWxrUmVzcG9uc2UsXG4gIERvY3VtZW50R2V0UmVzcG9uc2UsXG4gIERvY3VtZW50SW5zZXJ0UmVzcG9uc2UsXG4gIERvY3VtZW50U2NvcGUsXG4gIE1heWJlRG9jdW1lbnQsXG4gIFNlcnZlclNjb3BlLFxufSBmcm9tIFwibmFub1wiO1xuaW1wb3J0IHtcbiAgQ29uc3RydWN0b3IsXG4gIERlY29yYXRpb24sXG4gIE1vZGVsLFxuICBwcm9wTWV0YWRhdGEsXG59IGZyb20gXCJAZGVjYWYtdHMvZGVjb3JhdG9yLXZhbGlkYXRpb25cIjtcbmltcG9ydCB7IE5hbm9GbGFncyB9IGZyb20gXCIuL3R5cGVzXCI7XG5pbXBvcnQge1xuICBQZXJzaXN0ZW5jZUtleXMsXG4gIFJlbGF0aW9uc01ldGFkYXRhLFxuICBSZXBvc2l0b3J5LFxuICBVbnN1cHBvcnRlZEVycm9yLFxufSBmcm9tIFwiQGRlY2FmLXRzL2NvcmVcIjtcbmltcG9ydCB7IE5hbm9GbGF2b3VyIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQgeyBOYW5vUmVwb3NpdG9yeSB9IGZyb20gXCIuL05hbm9SZXBvc2l0b3J5XCI7XG5pbXBvcnQgeyBOYW5vRGlzcGF0Y2ggfSBmcm9tIFwiLi9OYW5vRGlzcGF0Y2hcIjtcblxuLyoqXG4gKiBAZGVzY3JpcHRpb24gU2V0cyB0aGUgY3JlYXRvciBvciB1cGRhdGVyIGZpZWxkIGluIGEgbW9kZWwgYmFzZWQgb24gdGhlIHVzZXIgaW4gdGhlIGNvbnRleHRcbiAqIEBzdW1tYXJ5IENhbGxiYWNrIGZ1bmN0aW9uIHVzZWQgaW4gZGVjb3JhdG9ycyB0byBhdXRvbWF0aWNhbGx5IHNldCB0aGUgY3JlYXRlZF9ieSBvciB1cGRhdGVkX2J5IGZpZWxkc1xuICogd2l0aCB0aGUgdXNlcm5hbWUgZnJvbSB0aGUgY29udGV4dCB3aGVuIGEgZG9jdW1lbnQgaXMgY3JlYXRlZCBvciB1cGRhdGVkXG4gKiBAdGVtcGxhdGUgTSAtIFR5cGUgZXh0ZW5kaW5nIE1vZGVsXG4gKiBAdGVtcGxhdGUgUiAtIFR5cGUgZXh0ZW5kaW5nIE5hbm9SZXBvc2l0b3J5PE0+XG4gKiBAdGVtcGxhdGUgViAtIFR5cGUgZXh0ZW5kaW5nIFJlbGF0aW9uc01ldGFkYXRhXG4gKiBAcGFyYW0ge1J9IHRoaXMgLSBUaGUgcmVwb3NpdG9yeSBpbnN0YW5jZVxuICogQHBhcmFtIHtDb250ZXh0PE5hbm9GbGFncz59IGNvbnRleHQgLSBUaGUgb3BlcmF0aW9uIGNvbnRleHQgY29udGFpbmluZyB1c2VyIGluZm9ybWF0aW9uXG4gKiBAcGFyYW0ge1Z9IGRhdGEgLSBUaGUgcmVsYXRpb24gbWV0YWRhdGFcbiAqIEBwYXJhbSBrZXkgLSBUaGUgcHJvcGVydHkga2V5IHRvIHNldCB3aXRoIHRoZSB1c2VybmFtZVxuICogQHBhcmFtIHtNfSBtb2RlbCAtIFRoZSBtb2RlbCBpbnN0YW5jZSBiZWluZyBjcmVhdGVkIG9yIHVwZGF0ZWRcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gdGhlIG9wZXJhdGlvbiBpcyBjb21wbGV0ZVxuICogQGZ1bmN0aW9uIGNyZWF0ZWRCeU9uTmFub0NyZWF0ZVVwZGF0ZVxuICogQG1lbWJlck9mIG1vZHVsZTpmb3ItbmFub1xuICogQG1lcm1haWRcbiAqIHNlcXVlbmNlRGlhZ3JhbVxuICogICBwYXJ0aWNpcGFudCBGIGFzIGNyZWF0ZWRCeU9uTmFub0NyZWF0ZVVwZGF0ZVxuICogICBwYXJ0aWNpcGFudCBDIGFzIENvbnRleHRcbiAqICAgcGFydGljaXBhbnQgTSBhcyBNb2RlbFxuICogICBGLT4+QzogZ2V0KFwidXNlclwiKVxuICogICBDLS0+PkY6IHVzZXIgb2JqZWN0XG4gKiAgIEYtPj5NOiBzZXQga2V5IHRvIHVzZXIubmFtZVxuICogICBOb3RlIG92ZXIgRjogSWYgbm8gdXNlciBpbiBjb250ZXh0XG4gKiAgIEYtLT4+RjogdGhyb3cgVW5zdXBwb3J0ZWRFcnJvclxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3JlYXRlZEJ5T25OYW5vQ3JlYXRlVXBkYXRlPFxuICBNIGV4dGVuZHMgTW9kZWwsXG4gIFIgZXh0ZW5kcyBOYW5vUmVwb3NpdG9yeTxNPixcbiAgViBleHRlbmRzIFJlbGF0aW9uc01ldGFkYXRhLFxuPihcbiAgdGhpczogUixcbiAgY29udGV4dDogQ29udGV4dDxOYW5vRmxhZ3M+LFxuICBkYXRhOiBWLFxuICBrZXk6IGtleW9mIE0sXG4gIG1vZGVsOiBNXG4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCB1c2VyID0gY29udGV4dC5nZXQoXCJ1c2VyXCIpO1xuICAgIG1vZGVsW2tleV0gPSB1c2VyLm5hbWUgYXMgTVt0eXBlb2Yga2V5XTtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gIH0gY2F0Y2ggKGU6IHVua25vd24pIHtcbiAgICB0aHJvdyBuZXcgVW5zdXBwb3J0ZWRFcnJvcihcbiAgICAgIFwiTm8gVXNlciBmb3VuZCBpbiBjb250ZXh0LiBQbGVhc2UgcHJvdmlkZSBhIHVzZXIgaW4gdGhlIGNvbnRleHRcIlxuICAgICk7XG4gIH1cbn1cblxuLyoqXG4gKiBAZGVzY3JpcHRpb24gQWRhcHRlciBmb3IgaW50ZXJhY3Rpbmcgd2l0aCBOYW5vIGRhdGFiYXNlc1xuICogQHN1bW1hcnkgUHJvdmlkZXMgYSBzdGFuZGFyZGl6ZWQgaW50ZXJmYWNlIGZvciBwZXJmb3JtaW5nIENSVUQgb3BlcmF0aW9ucyBvbiBOYW5vIGRhdGFiYXNlcyxcbiAqIGV4dGVuZGluZyB0aGUgQ291Y2hEQiBhZGFwdGVyIHdpdGggTmFuby1zcGVjaWZpYyBmdW5jdGlvbmFsaXR5LiBUaGlzIGFkYXB0ZXIgaGFuZGxlcyBkb2N1bWVudFxuICogY3JlYXRpb24sIHJlYWRpbmcsIHVwZGF0aW5nLCBhbmQgZGVsZXRpb24sIGFzIHdlbGwgYXMgYnVsayBvcGVyYXRpb25zIGFuZCBpbmRleCBtYW5hZ2VtZW50LlxuICogQHRlbXBsYXRlIERvY3VtZW50U2NvcGUgLSBUaGUgTmFubyBkb2N1bWVudCBzY29wZSB0eXBlXG4gKiBAdGVtcGxhdGUgTmFub0ZsYWdzIC0gQ29uZmlndXJhdGlvbiBmbGFncyBmb3IgTmFubyBvcGVyYXRpb25zXG4gKiBAdGVtcGxhdGUgQ29udGV4dCAtIENvbnRleHQgdHlwZSBmb3Igb3BlcmF0aW9uc1xuICogQHBhcmFtIHtEb2N1bWVudFNjb3BlPGFueT59IHNjb3BlIC0gVGhlIE5hbm8gZG9jdW1lbnQgc2NvcGUgdG8gdXNlIGZvciBkYXRhYmFzZSBvcGVyYXRpb25zXG4gKiBAcGFyYW0ge3N0cmluZ30gW2FsaWFzXSAtIE9wdGlvbmFsIGFsaWFzIGZvciB0aGUgYWRhcHRlclxuICogQGNsYXNzIE5hbm9BZGFwdGVyXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ29ubmVjdCB0byBhIE5hbm8gZGF0YWJhc2VcbiAqIGNvbnN0IHNlcnZlciA9IE5hbm9BZGFwdGVyLmNvbm5lY3QoJ2FkbWluJywgJ3Bhc3N3b3JkJywgJ2xvY2FsaG9zdDo1OTg0Jyk7XG4gKiBjb25zdCBkYiA9IHNlcnZlci5kYi51c2UoJ215X2RhdGFiYXNlJyk7XG4gKlxuICogLy8gQ3JlYXRlIGFuIGFkYXB0ZXIgaW5zdGFuY2VcbiAqIGNvbnN0IGFkYXB0ZXIgPSBuZXcgTmFub0FkYXB0ZXIoZGIpO1xuICpcbiAqIC8vIFVzZSB0aGUgYWRhcHRlciBmb3IgZGF0YWJhc2Ugb3BlcmF0aW9uc1xuICogY29uc3QgZG9jdW1lbnQgPSBhd2FpdCBhZGFwdGVyLnJlYWQoJ3VzZXJzJywgJzEyMycpO1xuICogYGBgXG4gKiBAbWVybWFpZFxuICogY2xhc3NEaWFncmFtXG4gKiAgIGNsYXNzIENvdWNoREJBZGFwdGVyIHtcbiAqICAgICArZmxhZ3MoKVxuICogICAgICtEaXNwYXRjaCgpXG4gKiAgICAgK2luZGV4KClcbiAqICAgICArY3JlYXRlKClcbiAqICAgICArcmVhZCgpXG4gKiAgICAgK3VwZGF0ZSgpXG4gKiAgICAgK2RlbGV0ZSgpXG4gKiAgIH1cbiAqICAgY2xhc3MgTmFub0FkYXB0ZXIge1xuICogICAgICtmbGFncygpXG4gKiAgICAgK0Rpc3BhdGNoKClcbiAqICAgICAraW5kZXgoKVxuICogICAgICtjcmVhdGUoKVxuICogICAgICtjcmVhdGVBbGwoKVxuICogICAgICtyZWFkKClcbiAqICAgICArcmVhZEFsbCgpXG4gKiAgICAgK3VwZGF0ZSgpXG4gKiAgICAgK3VwZGF0ZUFsbCgpXG4gKiAgICAgK2RlbGV0ZSgpXG4gKiAgICAgK2RlbGV0ZUFsbCgpXG4gKiAgICAgK3JhdygpXG4gKiAgICAgK3N0YXRpYyBjb25uZWN0KClcbiAqICAgICArc3RhdGljIGNyZWF0ZURhdGFiYXNlKClcbiAqICAgICArc3RhdGljIGRlbGV0ZURhdGFiYXNlKClcbiAqICAgICArc3RhdGljIGNyZWF0ZVVzZXIoKVxuICogICAgICtzdGF0aWM