@decaf-ts/for-nano
Version:
decaf-ts persistence adapter for CouchDB via nano
856 lines (850 loc) • 102 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@decaf-ts/db-decorators'), require('reflect-metadata'), require('@decaf-ts/for-couchdb'), require('nano'), require('@decaf-ts/decorator-validation'), require('@decaf-ts/core')) :
typeof define === 'function' && define.amd ? define(['exports', '@decaf-ts/db-decorators', 'reflect-metadata', '@decaf-ts/for-couchdb', 'nano', '@decaf-ts/decorator-validation', '@decaf-ts/core'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["for-nano"] = {}, global.dbDecorators, null, global.forCouchdb, global.Nano, global.decoratorValidation, global.core));
})(this, (function (exports, dbDecorators, reflectMetadata, forCouchdb, Nano, decoratorValidation, core) { 'use strict';
/**
* @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 core.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(forCouchdb.CouchDBKeys.SEPARATOR);
return {
table: table,
id: id,
operation: r.deleted
? dbDecorators.OperationKeys.DELETE
: r.changes[r.changes.length - 1].rev.split("-")[0] === "1"
? dbDecorators.OperationKeys.CREATE
: dbDecorators.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 dbDecorators.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 dbDecorators.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 core.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 forCouchdb.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 = forCouchdb.generateIndexes(models);
for (const index of indexes) {
const res = await this.native.createIndex(index);
const { result, id, name } = res;
if (result === "existing")
throw new dbDecorators.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 dbDecorators.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 dbDecorators.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 dbDecorators.InternalError(r.error);
if (r.doc) {
const res = Object.assign({}, r.doc);
return this.assignMetadata(res, r.doc[forCouchdb.CouchDBKeys.REV]);
}
throw new dbDecorators.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 dbDecorators.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 dbDecorators.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[forCouchdb.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 dbDecorators.InternalError(r.error);
if (r.doc) {
const res = Object.assign({}, r.doc);
return this.assignMetadata(res, r.doc[forCouchdb.CouchDBKeys.REV]);
}
throw new dbDecorators.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 forCouchdb.CouchDBAdapter.parseError(e);
}
const { ok, error, reason } = result;
if (!ok)
throw forCouchdb.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 forCouchdb.CouchDBAdapter.parseError(e);
}
const { ok } = result;
if (!ok)
throw new dbDecorators.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 dbDecorators.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 dbDecorators.InternalError(`Failed to authorize user ${user} to db ${dbName}`);
}
catch (e) {
throw forCouchdb.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 forCouchdb.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.Repository.key(core.PersistenceKeys.CREATED_BY);
const updatedByKey = core.Repository.key(core.PersistenceKeys.UPDATED_BY);
decoratorValidation.Decoration.flavouredAs("nano")
.for(createdByKey)
.define(dbDecorators.onCreate(createdByOnNanoCreateUpdate), decoratorValidation.propMetadata(createdByKey, {}))
.apply();
decoratorValidation.Decoration.flavouredAs("nano")
.for(updatedByKey)
.define(dbDecorators.onCreate(createdByOnNanoCreateUpdate), decoratorValidation.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";
exports.NanoAdapter = NanoAdapter;
exports.NanoFlavour = NanoFlavour;
exports.VERSION = VERSION;
exports.createdByOnNanoCreateUpdate = createdByOnNanoCreateUpdate;
}));
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yLW5hbm8uY2pzIiwic291cmNlcyI6WyIuLi9zcmMvY29uc3RhbnRzLnRzIiwiLi4vc3JjL05hbm9EaXNwYXRjaC50cyIsIi4uL3NyYy9hZGFwdGVyLnRzIiwiLi4vc3JjL2luZGV4LnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGRlc2NyaXB0aW9uIElkZW50aWZpZXIgZm9yIHRoZSBOYW5vIGRhdGFiYXNlIGZsYXZvclxuICogQHN1bW1hcnkgQ29uc3RhbnQgc3RyaW5nIHRoYXQgaWRlbnRpZmllcyB0aGUgZGF0YWJhc2UgdHlwZSBhcyBcIm5hbm9cIiBmb3IgdXNlIGluIGFkYXB0ZXIgc2VsZWN0aW9uIGFuZCBjb25maWd1cmF0aW9uXG4gKiBAY29uc3QgTmFub0ZsYXZvdXJcbiAqIEBtZW1iZXJPZiBtb2R1bGU6Zm9yLW5hbm9cbiAqL1xuZXhwb3J0IGNvbnN0IE5hbm9GbGF2b3VyID0gXCJuYW5vXCI7XG4iLCJpbXBvcnQgeyBEaXNwYXRjaCB9IGZyb20gXCJAZGVjYWYtdHMvY29yZVwiO1xuaW1wb3J0IHtcbiAgRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2UsXG4gIERhdGFiYXNlQ2hhbmdlc1Jlc3VsdEl0ZW0sXG4gIERvY3VtZW50U2NvcGUsXG4gIFJlcXVlc3RFcnJvcixcbn0gZnJvbSBcIm5hbm9cIjtcbmltcG9ydCB7IEludGVybmFsRXJyb3IsIE9wZXJhdGlvbktleXMgfSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcbmltcG9ydCB7IENvdWNoREJLZXlzIH0gZnJvbSBcIkBkZWNhZi10cy9mb3ItY291Y2hkYlwiO1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBEaXNwYXRjaGVyIGZvciBOYW5vIGRhdGFiYXNlIGNoYW5nZSBldmVudHNcbiAqIEBzdW1tYXJ5IEhhbmRsZXMgdGhlIHN1YnNjcmlwdGlvbiB0byBhbmQgcHJvY2Vzc2luZyBvZiBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzIGZyb20gYSBOYW5vIGRhdGFiYXNlLFxuICogbm90aWZ5aW5nIG9ic2VydmVycyB3aGVuIGRvY3VtZW50cyBhcmUgY3JlYXRlZCwgdXBkYXRlZCwgb3IgZGVsZXRlZFxuICogQHRlbXBsYXRlIERvY3VtZW50U2NvcGUgLSBUaGUgTmFubyBkb2N1bWVudCBzY29wZSB0eXBlXG4gKiBAcGFyYW0ge251bWJlcn0gW3RpbWVvdXQ9NTAwMF0gLSBUaW1lb3V0IGluIG1pbGxpc2Vjb25kcyBmb3IgY2hhbmdlIGZlZWQgcmVxdWVzdHNcbiAqIEBjbGFzcyBOYW5vRGlzcGF0Y2hcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDcmVhdGUgYSBkaXNwYXRjaGVyIGZvciBhIE5hbm8gZGF0YWJhc2VcbiAqIGNvbnN0IGRiID0gc2VydmVyLmRiLnVzZSgnbXlfZGF0YWJhc2UnKTtcbiAqIGNvbnN0IGFkYXB0ZXIgPSBuZXcgTmFub0FkYXB0ZXIoZGIpO1xuICogY29uc3QgZGlzcGF0Y2ggPSBuZXcgTmFub0Rpc3BhdGNoKCk7XG4gKlxuICogLy8gVGhlIGRpc3BhdGNoZXIgd2lsbCBhdXRvbWF0aWNhbGx5IHN1YnNjcmliZSB0byBjaGFuZ2VzXG4gKiAvLyBhbmQgbm90aWZ5IG9ic2VydmVycyB3aGVuIGRvY3VtZW50cyBjaGFuZ2VcbiAqIGBgYFxuICogQG1lcm1haWRcbiAqIGNsYXNzRGlhZ3JhbVxuICogICBjbGFzcyBEaXNwYXRjaCB7XG4gKiAgICAgK2luaXRpYWxpemUoKVxuICogICAgICt1cGRhdGVPYnNlcnZlcnMoKVxuICogICB9XG4gKiAgIGNsYXNzIE5hbm9EaXNwYXRjaCB7XG4gKiAgICAgLW9ic2VydmVyTGFzdFVwZGF0ZT86IHN0cmluZ1xuICogICAgIC1hdHRlbXB0Q291bnRlcjogbnVtYmVyXG4gKiAgICAgLXRpbWVvdXQ6IG51bWJlclxuICogICAgICtjb25zdHJ1Y3Rvcih0aW1lb3V0KVxuICogICAgICNjaGFuZ2VIYW5kbGVyKClcbiAqICAgICAjaW5pdGlhbGl6ZSgpXG4gKiAgIH1cbiAqICAgRGlzcGF0Y2ggPHwtLSBOYW5vRGlzcGF0Y2hcbiAqL1xuZXhwb3J0IGNsYXNzIE5hbm9EaXNwYXRjaCBleHRlbmRzIERpc3BhdGNoPERvY3VtZW50U2NvcGU8YW55Pj4ge1xuICBwcml2YXRlIG9ic2VydmVyTGFzdFVwZGF0ZT86IHN0cmluZztcbiAgcHJpdmF0ZSBhdHRlbXB0Q291bnRlcjogbnVtYmVyID0gMDtcbiAgY29uc3RydWN0b3IocHJpdmF0ZSB0aW1lb3V0ID0gNTAwMCkge1xuICAgIHN1cGVyKCk7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIFByb2Nlc3NlcyBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzXG4gICAqIEBzdW1tYXJ5IEhhbmRsZXMgdGhlIHJlc3BvbnNlIGZyb20gdGhlIE5hbm8gY2hhbmdlcyBmZWVkLCBwcm9jZXNzZXMgdGhlIGNoYW5nZXMsXG4gICAqIGFuZCBub3RpZmllcyBvYnNlcnZlcnMgYWJvdXQgZG9jdW1lbnQgY2hhbmdlc1xuICAgKiBAcGFyYW0ge1JlcXVlc3RFcnJvciB8IG51bGx9IGVycm9yIC0gRXJyb3Igb2JqZWN0IGlmIHRoZSByZXF1ZXN0IGZhaWxlZFxuICAgKiBAcGFyYW0gcmVzcG9uc2UgLSBUaGUgY2hhbmdlcyByZXNwb25zZSBmcm9tIE5hbm9cbiAgICogQHBhcmFtIHthbnl9IFtoZWFkZXJzXSAtIFJlc3BvbnNlIGhlYWRlcnMgKHVudXNlZClcbiAgICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn0gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiBhbGwgY2hhbmdlcyBoYXZlIGJlZW4gcHJvY2Vzc2VkXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEQgYXMgTmFub0Rpc3BhdGNoXG4gICAqICAgcGFydGljaXBhbnQgTCBhcyBMb2dnZXJcbiAgICogICBwYXJ0aWNpcGFudCBPIGFzIE9ic2VydmVyc1xuICAgKiAgIE5vdGUgb3ZlciBEOiBSZWNlaXZlIGNoYW5nZXMgZnJvbSBOYW5vXG4gICAqICAgYWx0IEVycm9yIGluIHJlc3BvbnNlXG4gICAqICAgICBELT4+TDogTG9nIGVycm9yXG4gICAqICAgICBELS0+PkQ6IFJldHVybiBlYXJseVxuICAgKiAgIGVuZFxuICAgKiAgIGFsdCBSZXNwb25zZSBpcyBzdHJpbmdcbiAgICogICAgIEQtPj5EOiBQYXJzZSBKU09OIGZyb20gc3RyaW5nXG4gICAqICAgZW5kXG4gICAqICAgRC0+PkQ6IFByb2Nlc3MgY2hhbmdlc1xuICAgKiAgIEQtPj5EOiBHcm91cCBjaGFuZ2VzIGJ5IHRhYmxlIGFuZCBvcGVyYXRpb25cbiAgICogICBsb29wIEZvciBlYWNoIHRhYmxlXG4gICAqICAgICBsb29wIEZvciBlYWNoIG9wZXJhdGlvblxuICAgKiAgICAgICBELT4+TzogdXBkYXRlT2JzZXJ2ZXJzKHRhYmxlLCBvcGVyYXRpb24sIGlkcylcbiAgICogICAgICAgRC0+PkQ6IFVwZGF0ZSBvYnNlcnZlckxhc3RVcGRhdGVcbiAgICogICAgICAgRC0+Pkw6IExvZyBzdWNjZXNzZnVsIGRpc3BhdGNoXG4gICAqICAgICBlbmRcbiAgICogICBlbmRcbiAgICovXG4gIHByb3RlY3RlZCBhc3luYyBjaGFuZ2VIYW5kbGVyKFxuICAgIGVycm9yOiBSZXF1ZXN0RXJyb3IgfCBudWxsLFxuICAgIHJlc3BvbnNlOiAoRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2UgfCBEYXRhYmFzZUNoYW5nZXNSZXN1bHRJdGVtKVtdIHwgc3RyaW5nLFxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW51c2VkLXZhcnNcbiAgICBoZWFkZXJzPzogYW55XG4gICkge1xuICAgIGNvbnN0IGxvZyA9IHRoaXMubG9nLmZvcih0aGlzLmNoYW5nZUhhbmRsZXIpO1xuICAgIGlmIChlcnJvcikgcmV0dXJuIGxvZy5lcnJvcihgRXJyb3IgaW4gY2hhbmdlIHJlcXVlc3Q6ICR7ZXJyb3J9YCk7XG4gICAgdHJ5IHtcbiAgICAgIHJlc3BvbnNlID0gKFxuICAgICAgICB0eXBlb2YgcmVzcG9uc2UgPT09IFwic3RyaW5nXCJcbiAgICAgICAgICA/IHJlc3BvbnNlXG4gICAgICAgICAgICAgIC5zcGxpdChcIlxcblwiKVxuICAgICAgICAgICAgICAuZmlsdGVyKChyKSA9PiAhIXIpXG4gICAgICAgICAgICAgIC5tYXAoKHIpID0+IEpTT04ucGFyc2UocikpXG4gICAgICAgICAgOiByZXNwb25zZVxuICAgICAgKSBhcyBEYXRhYmFzZUNoYW5nZXNSZXNwb25zZVtdO1xuICAgIH0gY2F0Y2ggKGU6IHVua25vd24pIHtcbiAgICAgIHJldHVybiBsb2cuZXJyb3IoYEVycm9yIHBhcnNpbmcgY291Y2hkYiBjaGFuZ2UgZmVlZDogJHtlfWApO1xuICAgIH1cbiAgICBjb25zdCBjb3VudCA9IHJlc3BvbnNlLmxlbmd0aDtcbiAgICBpZiAoY291bnQgPiAwKSB7XG4gICAgICBsb2cuZGVidWcoYFJlY2VpdmVkICR7Y291bnR9IGNoYW5nZXMuIHByb2Nlc3NpbmcuLi5gKTtcbiAgICAgIGNvbnN0IGNoYW5nZXMgPSByZXNwb25zZVxuICAgICAgICAubWFwKChyZWMsIGkpID0+IHtcbiAgICAgICAgICBpZiAoaSA9PT0gY291bnQgLSAxKSB7XG4gICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgIHRoaXMub2JzZXJ2ZXJMYXN0VXBkYXRlID09PVxuICAgICAgICAgICAgICAocmVjIGFzIERhdGFiYXNlQ2hhbmdlc1Jlc3BvbnNlKS5sYXN0X3NlcVxuICAgICAgICAgICAgKVxuICAgICAgICAgICAgICBsb2cuZXJyb3IoXG4gICAgICAgICAgICAgICAgYEludmFsaWQgbGFzdCB1cGRhdGUgY2hlY2s6ICR7dGhpcy5vYnNlcnZlckxhc3RVcGRhdGV9ICE9PSAkeyhyZWMgYXMgRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2UpLmxhc3Rfc2VxfWBcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgICAgY29uc3QgciA9IHJlYyBhcyBEYXRhYmFzZUNoYW5nZXNSZXN1bHRJdGVtO1xuICAgICAgICAgIGNvbnN0IFt0YWJsZSwgaWRdID0gci5pZC5zcGxpdChDb3VjaERCS2V5cy5TRVBBUkFUT1IpO1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0YWJsZTogdGFibGUsXG4gICAgICAgICAgICBpZDogaWQsXG4gICAgICAgICAgICBvcGVyYXRpb246IHIuZGVsZXRlZFxuICAgICAgICAgICAgICA/IE9wZXJhdGlvbktleXMuREVMRVRFXG4gICAgICAgICAgICAgIDogci5jaGFuZ2VzW3IuY2hhbmdlcy5sZW5ndGggLSAxXS5yZXYuc3BsaXQoXCItXCIpWzBdID09PSBcIjFcIlxuICAgICAgICAgICAgICAgID8gT3BlcmF0aW9uS2V5cy5DUkVBVEVcbiAgICAgICAgICAgICAgICA6IE9wZXJhdGlvbktleXMuVVBEQVRFLFxuICAgICAgICAgICAgc3RlcDogci5jaGFuZ2VzW3IuY2hhbmdlcy5sZW5ndGggLSAxXS5yZXYsXG4gICAgICAgICAgfTtcbiAgICAgICAgfSlcbiAgICAgICAgLnJlZHVjZShcbiAgICAgICAgICAoXG4gICAgICAgICAgICBhY2N1bTogUmVjb3JkPFxuICAgICAgICAgICAgICBzdHJpbmcsXG4gICAgICAgICAgICAgIFJlY29yZDxcbiAgICAgICAgICAgICAgICBzdHJpbmcsXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgaWRzOiBTZXQ8YW55PjtcbiAgICAgICAgICAgICAgICAgIHN0ZXA6IHN0cmluZztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgID5cbiAgICAgICAgICAgID4sXG4gICAgICAgICAgICByXG4gICAgICAgICAgKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXIpIHJldHVybiBhY2N1bTtcbiAgICAgICAgICAgIGNvbnN0IHsgdGFibGUsIGlkLCBvcGVyYXRpb24sIHN0ZXAgfSA9IHIgYXMge1xuICAgICAgICAgICAgICB0YWJsZTogc3RyaW5nO1xuICAgICAgICAgICAgICBpZDogc3RyaW5nO1xuICAgICAgICAgICAgICBvcGVyYXRpb246IE9wZXJhdGlvbktleXM7XG4gICAgICAgICAgICAgIHN0ZXA6IHN0cmluZztcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBpZiAoIWFjY3VtW3RhYmxlXSkgYWNjdW1bdGFibGVdID0ge307XG4gICAgICAgICAgICBpZiAoIWFjY3VtW3RhYmxlXVtvcGVyYXRpb25dKVxuICAgICAgICAgICAgICBhY2N1bVt0YWJsZV1bb3BlcmF0aW9uXSA9IHsgaWRzOiBuZXcgU2V0KCksIHN0ZXA6IHN0ZXAgfTtcbiAgICAgICAgICAgIGFjY3VtW3RhYmxlXVtvcGVyYXRpb25dLmlkcy5hZGQoaWQpO1xuICAgICAgICAgICAgYWNjdW1bdGFibGVdW29wZXJhdGlvbl0uc3RlcCA9IHN0ZXA7XG4gICAgICAgICAgICByZXR1cm4gYWNjdW07XG4gICAgICAgICAgfSxcbiAgICAgICAgICB7fVxuICAgICAgICApO1xuXG4gICAgICBmb3IgKGNvbnN0IHRhYmxlIG9mIE9iamVjdC5rZXlzKGNoYW5nZXMpKSB7XG4gICAgICAgIGZvciAoY29uc3Qgb3Agb2YgT2JqZWN0LmtleXMoY2hhbmdlc1t0YWJsZV0pKSB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMudXBkYXRlT2JzZXJ2ZXJzKHRhYmxlLCBvcCwgW1xuICAgICAgICAgICAgICAuLi5jaGFuZ2VzW3RhYmxlXVtvcF0uaWRzLnZhbHVlcygpLFxuICAgICAgICAgICAgXSk7XG4gICAgICAgICAgICB0aGlzLm9ic2VydmVyTGFzdFVwZGF0ZSA9IGNoYW5nZXNbdGFibGVdW29wXS5zdGVwO1xuICAgICAgICAgICAgbG9nLnZlcmJvc2UoYE9ic2VydmVyIHJlZnJlc2ggZGlzcGF0Y2hlZCBieSAke29wfSBmb3IgJHt0YWJsZX1gKTtcbiAgICAgICAgICAgIGxvZy5kZWJ1ZyhgcGtzOiAke0FycmF5LmZyb20oY2hhbmdlc1t0YWJsZV1bb3BdLmlkcy52YWx1ZXMoKSl9YCk7XG4gICAgICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICAgICAgbG9nLmVycm9yKFxuICAgICAgICAgICAgICBgRmFpbGVkIHRvIGRpc3BhdGNoIG9ic2VydmVyIHJlZnJlc2ggZm9yICR7dGFibGV9LCBvcCAke29wfTogJHtlfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBJbml0aWFsaXplcyB0aGUgZGlzcGF0Y2hlciBhbmQgc3Vic2NyaWJlcyB0byBkYXRhYmFzZSBjaGFuZ2VzXG4gICAqIEBzdW1tYXJ5IFNldHMgdXAgdGhlIGNvbnRpbnVvdXMgY2hhbmdlcyBmZWVkIHN1YnNjcmlwdGlvbiB0byB0aGUgTmFubyBkYXRhYmFzZVxuICAgKiBhbmQgaGFuZGxlcyByZWNvbm5lY3Rpb24gYXR0ZW1wdHMgaWYgdGhlIGNvbm5lY3Rpb24gZmFpbHNcbiAgICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn0gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgc3Vic2NyaXB0aW9uIGlzIGVzdGFibGlzaGVkXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEQgYXMgTmFub0Rpc3BhdGNoXG4gICAqICAgcGFydGljaXBhbnQgUyBhcyBzdWJzY3JpYmVUb0NvdWNoXG4gICAqICAgcGFydGljaXBhbnQgREIgYXMgTmFubyBEYXRhYmFzZVxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgRC0+PlM6IENhbGwgc3Vic2NyaWJlVG9Db3VjaFxuICAgKiAgIFMtPj5TOiBDaGVjayBhZGFwdGVyIGFuZCBuYXRpdmVcbiAgICogICBhbHQgTm8gYWRhcHRlciBvciBuYXRpdmVcbiAgICogICAgIFMtLT4+UzogdGhyb3cgSW50ZXJuYWxFcnJvclxuICAgKiAgIGVuZFxuICAgKiAgIFMtPj5EQjogY2hhbmdlcyhvcHRpb25zLCBjaGFuZ2VIYW5kbGVyKVxuICAgKiAgIGFsdCBTdWNjZXNzXG4gICAqICAgICBEQi0tPj5TOiBTdWJzY3JpcHRpb24gZXN0YWJsaXNoZWRcbiAgICogICAgIFMtLT4+RDogUHJvbWlzZSByZXNvbHZlc1xuICAgKiAgICAgRC0+Pkw6IExvZyBzdWNjZXNzZnVsIHN1YnNjcmlwdGlvblxuICAgKiAgIGVsc2UgRXJyb3JcbiAgICogICAgIERCLS0+PlM6IEVycm9yXG4gICAqICAgICBTLT4+UzogSW5jcmVtZW50IGF0dGVtcHRDb3VudGVyXG4gICAqICAgICBhbHQgYXR0ZW1wdENvdW50ZXIgPiAzXG4gICAqICAgICAgIFMtPj5MOiBMb2cgZXJyb3JcbiAgICogICAgICAgUy0tPj5EOiBQcm9taXNlIHJlamVjdHNcbiAgICogICAgIGVsc2UgYXR0ZW1wdENvdW50ZXIgPD0gM1xuICAgKiAgICAgICBTLT4+TDogTG9nIHJldHJ5XG4gICAqICAgICAgIFMtPj5TOiBXYWl0IHRpbWVvdXRcbiAgICogICAgICAgUy0+PlM6IFJlY3Vyc2l2ZSBjYWxsIHRvIHN1YnNjcmliZVRvQ291Y2hcbiAgICogICAgIGVuZFxuICAgKiAgIGVuZFxuICAgKi9cbiAgcHJvdGVjdGVkIG92ZXJyaWRlIGFzeW5jIGluaXRpYWxpemUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgbG9nID0gdGhpcy5sb2cuZm9yKHRoaXMuaW5pdGlhbGl6ZSk7XG4gICAgY29uc3Qgc3ViTG9nID0gbG9nLmZvcihzdWJzY3JpYmVUb0NvdWNoKTtcbiAgICBhc3luYyBmdW5jdGlvbiBzdWJzY3JpYmVUb0NvdWNoKHRoaXM6IE5hbm9EaXNwYXRjaCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgaWYgKCF0aGlzLmFkYXB0ZXIgfHwgIXRoaXMubmF0aXZlKVxuICAgICAgICB0aHJvdyBuZXcgSW50ZXJuYWxFcnJvcihgTm8gYWRhcHRlci9uYXRpdmUgb2JzZXJ2ZWQgZm9yIGRpc3BhdGNoYCk7XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIHRoaXMubmF0aXZlLmNoYW5nZXMoXG4gICAgICAgICAge1xuICAgICAgICAgICAgZmVlZDogXCJjb250aW51b3VzXCIsXG4gICAgICAgICAgICBpbmNsdWRlX2RvY3M6IGZhbHNlLFxuICAgICAgICAgICAgc2luY2U6IHRoaXMub2JzZXJ2ZXJMYXN0VXBkYXRlIHx8IFwibm93XCIsXG4gICAgICAgICAgICB0aW1lb3V0OiB0aGlzLnRpbWVvdXQsXG4gICAgICAgICAgfSxcbiAgICAgICAgICB0aGlzLmNoYW5nZUhhbmRsZXIuYmluZCh0aGlzKSBhcyBhbnlcbiAgICAgICAgKTtcbiAgICAgIH0gY2F0Y2ggKGU6IHVua25vd24pIHtcbiAgICAgICAgaWYgKCsrdGhpcy5hdHRlbXB0Q291bnRlciA+IDMpXG4gICAgICAgICAgcmV0dXJuIHN1YkxvZy5lcnJvcihgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBjb3VjaGRiIGNoYW5nZXM6ICR7ZX1gKTtcbiAgICAgICAgc3ViTG9nLmluZm8oXG4gICAgICAgICAgYEZhaWxlZCB0byBzdWJzY3JpYmUgdG8gY291Y2hkYiBjaGFuZ2VzOiAke2V9LiBSZXRyeWluZyBpbiA1IHNlY29uZHMuLi5gXG4gICAgICAgICk7XG4gICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIHRoaXMudGltZW91dCkpO1xuICAgICAgICByZXR1cm4gc3Vic2NyaWJlVG9Db3VjaC5jYWxsKHRoaXMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHN1YnNjcmliZVRvQ291Y2hcbiAgICAgIC5jYWxsKHRoaXMpXG4gICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgIHRoaXMubG9nLmluZm8oYFN1YnNjcmliZWQgdG8gY291Y2hkYiBjaGFuZ2VzYCk7XG4gICAgICB9KVxuICAgICAgLmNhdGNoKChlOiB1bmtub3duKSA9PiB7XG4gICAgICAgIHRocm93IG5ldyBJbnRlcm5hbEVycm9yKGBGYWlsZWQgdG8gc3Vic2NyaWJlIHRvIGNvdWNoZGIgY2hhbmdlczogJHtlfWApO1xuICAgICAgfSk7XG4gIH1cbn1cbiIsImltcG9ydCB7XG4gIENvbmZsaWN0RXJyb3IsXG4gIENvbnRleHQsXG4gIEludGVybmFsRXJyb3IsXG4gIG9uQ3JlYXRlLFxuICBPcGVyYXRpb25LZXlzLFxufSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcbmltcG9ydCBcInJlZmxlY3QtbWV0YWRhdGFcIjtcbmltcG9ydCB7XG4gIENvdWNoREJBZGFwdGVyLFxuICBDb3VjaERCS2V5cyxcbiAgQ3JlYXRlSW5kZXhSZXF1ZXN0LFxuICBnZW5lcmF0ZUluZGV4ZXMsXG4gIE1hbmdvUXVlcnksXG4gIE1hbmdvUmVzcG9uc2UsXG59IGZyb20gXCJAZGVjYWYtdHMvZm9yLWNvdWNoZGJcIjtcbmltcG9ydCBOYW5vIGZyb20gXCJuYW5vXCI7XG5pbXBvcnQge1xuICBEb2N1bWVudEJ1bGtSZXNwb25zZSxcbiAgRG9jdW1lbnRHZXRSZXNwb25zZSxcbiAgRG9jdW1lbnRJbnNlcnRSZXNwb25zZSxcbiAgRG9jdW1lbnRTY29wZSxcbiAgTWF5YmVEb2N1bWVudCxcbiAgU2VydmVyU2NvcGUsXG59IGZyb20gXCJuYW5vXCI7XG5pbXBvcnQge1xuICBDb25zdHJ1Y3RvcixcbiAgRGVjb3JhdGlvbixcbiAgTW9kZWwsXG4gIHByb3BNZXRhZGF0YSxcbn0gZnJvbSBcIkBkZWNhZi10cy9kZWNvcmF0b3ItdmFsaWRhdGlvblwiO1xuaW1wb3J0IHsgTmFub0ZsYWdzIH0gZnJvbSBcIi4vdHlwZXNcIjtcbmltcG9ydCB7XG4gIFBlcnNpc3RlbmNlS2V5cyxcbiAgUmVsYXRpb25zTWV0YWRhdGEsXG4gIFJlcG9zaXRvcnksXG4gIFVuc3VwcG9ydGVkRXJyb3IsXG59IGZyb20gXCJAZGVjYWYtdHMvY29yZVwiO1xuaW1wb3J0IHsgTmFub0ZsYXZvdXIgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcbmltcG9ydCB7IE5hbm9SZXBvc2l0b3J5IH0gZnJvbSBcIi4vTmFub1JlcG9zaXRvcnlcIjtcbmltcG9ydCB7IE5hbm9EaXNwYXRjaCB9IGZyb20gXCIuL05hbm9EaXNwYXRjaFwiO1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBTZXRzIHRoZSBjcmVhdG9yIG9yIHVwZGF0ZXIgZmllbGQgaW4gYSBtb2RlbCBiYXNlZCBvbiB0aGUgdXNlciBpbiB0aGUgY29udGV4dFxuICogQHN1bW1hcnkgQ2FsbGJhY2sgZnVuY3Rpb24gdXNlZCBpbiBkZ