UNPKG

@decaf-ts/for-postgres

Version:
187 lines 20.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostgresDispatch = void 0; const core_1 = require("@decaf-ts/core"); const db_decorators_1 = require("@decaf-ts/db-decorators"); /** * @description Dispatcher for PostgreSQL database change events * @summary Handles the subscription to and processing of database change events from a PostgreSQL database, * notifying observers when records are created, updated, or deleted * @template Pool - The pg Pool type * @param {number} [timeout=5000] - Timeout in milliseconds for notification requests * @class PostgresDispatch * @example * ```typescript * // Create a dispatcher for a PostgreSQL database * const pool = new Pool({ * user: 'postgres', * password: 'password', * host: 'localhost', * port: 5432, * database: 'mydb' * }); * const adapter = new PostgreSQLAdapterImpl(pool); * const dispatch = new PostgreSQLDispatch(); * * // The dispatcher will automatically subscribe to notifications * // and notify observers when records change * ``` * @mermaid * classDiagram * class Dispatch { * +initialize() * +updateObservers() * } * class PostgreSQLDispatch { * -observerLastUpdate?: string * -attemptCounter: number * -timeout: number * -client?: PoolClient * +constructor(timeout) * #notificationHandler() * #initialize() * } * Dispatch <|-- PostgreSQLDispatch */ class PostgresDispatch extends core_1.Dispatch { constructor(timeout = 5000) { super(); this.timeout = timeout; this.attemptCounter = 0; } /** * @description Processes database notification events * @summary Handles the notifications from PostgreSQL LISTEN/NOTIFY mechanism, * and notifies observers about record changes * @param {Notification} notification - The notification from PostgreSQL * @return {Promise<void>} A promise that resolves when all notifications have been processed * @mermaid * sequenceDiagram * participant D as PostgreSQLDispatch * participant L as Logger * participant O as Observers * Note over D: Receive notification from PostgreSQL * D->>D: Parse notification payload * D->>D: Extract table, operation, and ids * D->>O: updateObservers(table, operation, ids) * D->>D: Update observerLastUpdate * D->>L: Log successful dispatch */ async notificationHandler(notification) { const log = this.log.for(this.notificationHandler); try { // Parse the notification payload (expected format: table:operation:id1,id2,...) const payload = notification.payload; const [table, operation, idsString] = payload.split(":"); const ids = idsString.split(","); if (!table || !operation || !ids.length) { return log.error(`Invalid notification format: ${payload}`); } // Map operation string to OperationKeys let operationKey; switch (operation.toLowerCase()) { case "insert": operationKey = db_decorators_1.OperationKeys.CREATE; break; case "update": operationKey = db_decorators_1.OperationKeys.UPDATE; break; case "delete": operationKey = db_decorators_1.OperationKeys.DELETE; break; default: return log.error(`Unknown operation: ${operation}`); } // Notify observers await this.updateObservers(table, operationKey, ids); this.observerLastUpdate = new Date().toISOString(); log.verbose(`Observer refresh dispatched by ${operation} for ${table}`); log.debug(`pks: ${ids}`); } catch (e) { log.error(`Failed to process notification: ${e}`); } } /** * @description Initializes the dispatcher and subscribes to database notifications * @summary Sets up the LISTEN mechanism to subscribe to PostgreSQL notifications * 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 PostgreSQLDispatch * participant S as subscribeToPostgreSQL * participant DB as PostgreSQL Database * participant L as Logger * D->>S: Call subscribeToPostgreSQL * S->>S: Check adapter and native * alt No adapter or native * S-->>S: throw InternalError * end * S->>DB: Connect client from pool * S->>DB: LISTEN table_changes * 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 subscribeToPostgreSQL * end * end */ async initialize() { const log = this.log.for(this.initialize); async function subscribeToPostgres() { if (!this.adapter || !this.native) { throw new db_decorators_1.InternalError(`No adapter/native observed for dispatch`); } try { this.client = await this.native.connect(); this.client.on("notification", this.notificationHandler.bind(this)); // Listen for table change notifications // This assumes you have set up triggers in PostgreSQL to NOTIFY on table changes const res = await this.client.query("LISTEN user_table_changes"); this.attemptCounter = 0; } catch (e) { if (this.client) { this.client.release(); this.client = undefined; } if (++this.attemptCounter > 3) { return log.error(`Failed to subscribe to Postgres notifications: ${e}`); } log.info(`Failed to subscribe to Postgres notifications: ${e}. Retrying in ${this.timeout}ms...`); await new Promise((resolve) => setTimeout(resolve, this.timeout)); return subscribeToPostgres.call(this); } } subscribeToPostgres .call(this) .then(() => { this.log.info(`Subscribed to Postgres notifications`); }) .catch((e) => { throw new db_decorators_1.InternalError(`Failed to subscribe to Postgres notifications: ${e}`); }); } /** * Cleanup method to release resources when the dispatcher is no longer needed */ cleanup() { if (this.client) { this.client.release(); this.client = undefined; } } } exports.PostgresDispatch = PostgresDispatch; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUG9zdGdyZXNEaXNwYXRjaC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9Qb3N0Z3Jlc0Rpc3BhdGNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlDQUEwQztBQUUxQywyREFBdUU7QUFFdkU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXVDRztBQUNILE1BQWEsZ0JBQWlCLFNBQVEsZUFBYztJQUtsRCxZQUFvQixVQUFVLElBQUk7UUFDaEMsS0FBSyxFQUFFLENBQUM7UUFEVSxZQUFPLEdBQVAsT0FBTyxDQUFPO1FBSDFCLG1CQUFjLEdBQVcsQ0FBQyxDQUFDO0lBS25DLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FpQkc7SUFDTyxLQUFLLENBQUMsbUJBQW1CLENBQ2pDLFlBQTBCO1FBRTFCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBRW5ELElBQUksQ0FBQztZQUNILGdGQUFnRjtZQUNoRixNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUMsT0FBaUIsQ0FBQztZQUMvQyxNQUFNLENBQUMsS0FBSyxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3pELE1BQU0sR0FBRyxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7WUFFakMsSUFBSSxDQUFDLEtBQUssSUFBSSxDQUFDLFNBQVMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDeEMsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDLGdDQUFnQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQzlELENBQUM7WUFFRCx3Q0FBd0M7WUFDeEMsSUFBSSxZQUEyQixDQUFDO1lBQ2hDLFFBQVEsU0FBUyxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7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QyxJQUFJLENBQUM7YUFDVixJQUFJLENBQUMsR0FBRyxFQUFFO1lBQ1QsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsc0NBQXNDLENBQUMsQ0FBQztRQUN4RCxDQUFDLENBQUM7YUFDRCxLQUFLLENBQUMsQ0FBQyxDQUFVLEVBQUUsRUFBRTtZQUNwQixNQUFNLElBQUksNkJBQWEsQ0FDckIsa0RBQWtELENBQUMsRUFBRSxDQUN0RCxDQUFDO1FBQ0osQ0FBQyxDQUFDLENBQUM7SUFDUCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPO1FBQ1osSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDaEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUN0QixJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQztRQUMxQixDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBbktELDRDQW1LQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IERpc3BhdGNoIH0gZnJvbSBcIkBkZWNhZi10cy9jb3JlXCI7XG5pbXBvcnQgeyBQb29sLCBQb29sQ2xpZW50LCBOb3RpZmljYXRpb24gfSBmcm9tIFwicGdcIjtcbmltcG9ydCB7IEludGVybmFsRXJyb3IsIE9wZXJhdGlvbktleXMgfSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcblxuLyoqXG4gKiBAZGVzY3JpcHRpb24gRGlzcGF0Y2hlciBmb3IgUG9zdGdyZVNRTCBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzXG4gKiBAc3VtbWFyeSBIYW5kbGVzIHRoZSBzdWJzY3JpcHRpb24gdG8gYW5kIHByb2Nlc3Npbmcgb2YgZGF0YWJhc2UgY2hhbmdlIGV2ZW50cyBmcm9tIGEgUG9zdGdyZVNRTCBkYXRhYmFzZSxcbiAqIG5vdGlmeWluZyBvYnNlcnZlcnMgd2hlbiByZWNvcmRzIGFyZSBjcmVhdGVkLCB1cGRhdGVkLCBvciBkZWxldGVkXG4gKiBAdGVtcGxhdGUgUG9vbCAtIFRoZSBwZyBQb29sIHR5cGVcbiAqIEBwYXJhbSB7bnVtYmVyfSBbdGltZW91dD01MDAwXSAtIFRpbWVvdXQgaW4gbWlsbGlzZWNvbmRzIGZvciBub3RpZmljYXRpb24gcmVxdWVzdHNcbiAqIEBjbGFzcyBQb3N0Z3Jlc0Rpc3BhdGNoXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGEgZGlzcGF0Y2hlciBmb3IgYSBQb3N0Z3JlU1FMIGRhdGFiYXNlXG4gKiBjb25zdCBwb29sID0gbmV3IFBvb2woe1xuICogICB1c2VyOiAncG9zdGdyZXMnLFxuICogICBwYXNzd29yZDogJ3Bhc3N3b3JkJyxcbiAqICAgaG9zdDogJ2xvY2FsaG9zdCcsXG4gKiAgIHBvcnQ6IDU0MzIsXG4gKiAgIGRhdGFiYXNlOiAnbXlkYidcbiAqIH0pO1xuICogY29uc3QgYWRhcHRlciA9IG5ldyBQb3N0Z3JlU1FMQWRhcHRlckltcGwocG9vbCk7XG4gKiBjb25zdCBkaXNwYXRjaCA9IG5ldyBQb3N0Z3JlU1FMRGlzcGF0Y2goKTtcbiAqXG4gKiAvLyBUaGUgZGlzcGF0Y2hlciB3aWxsIGF1dG9tYXRpY2FsbHkgc3Vic2NyaWJlIHRvIG5vdGlmaWNhdGlvbnNcbiAqIC8vIGFuZCBub3RpZnkgb2JzZXJ2ZXJzIHdoZW4gcmVjb3JkcyBjaGFuZ2VcbiAqIGBgYFxuICogQG1lcm1haWRcbiAqIGNsYXNzRGlhZ3JhbVxuICogICBjbGFzcyBEaXNwYXRjaCB7XG4gKiAgICAgK2luaXRpYWxpemUoKVxuICogICAgICt1cGRhdGVPYnNlcnZlcnMoKVxuICogICB9XG4gKiAgIGNsYXNzIFBvc3RncmVTUUxEaXNwYXRjaCB7XG4gKiAgICAgLW9ic2VydmVyTGFzdFVwZGF0ZT86IHN0cmluZ1xuICogICAgIC1hdHRlbXB0Q291bnRlcjogbnVtYmVyXG4gKiAgICAgLXRpbWVvdXQ6IG51bWJlclxuICogICAgIC1jbGllbnQ/OiBQb29sQ2xpZW50XG4gKiAgICAgK2NvbnN0cnVjdG9yKHRpbWVvdXQpXG4gKiAgICAgI25vdGlmaWNhdGlvbkhhbmRsZXIoKVxuICogICAgICNpbml0aWFsaXplKClcbiAqICAgfVxuICogICBEaXNwYXRjaCA8fC0tIFBvc3RncmVTUUxEaXNwYXRjaFxuICovXG5leHBvcnQgY2xhc3MgUG9zdGdyZXNEaXNwYXRjaCBleHRlbmRzIERpc3BhdGNoPFBvb2w+IHtcbiAgcHJpdmF0ZSBvYnNlcnZlckxhc3RVcGRhdGU/OiBzdHJpbmc7XG4gIHByaXZhdGUgYXR0ZW1wdENvdW50ZXI6IG51bWJlciA9IDA7XG4gIHByaXZhdGUgY2xpZW50PzogUG9vbENsaWVudDtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHRpbWVvdXQgPSA1MDAwKSB7XG4gICAgc3VwZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gUHJvY2Vzc2VzIGRhdGFiYXNlIG5vdGlmaWNhdGlvbiBldmVudHNcbiAgICogQHN1bW1hcnkgSGFuZGxlcyB0aGUgbm90aWZpY2F0aW9ucyBmcm9tIFBvc3RncmVTUUwgTElTVEVOL05PVElGWSBtZWNoYW5pc20sXG4gICAqIGFuZCBub3RpZmllcyBvYnNlcnZlcnMgYWJvdXQgcmVjb3JkIGNoYW5nZXNcbiAgICogQHBhcmFtIHtOb3RpZmljYXRpb259IG5vdGlmaWNhdGlvbiAtIFRoZSBub3RpZmljYXRpb24gZnJvbSBQb3N0Z3JlU1FMXG4gICAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gYWxsIG5vdGlmaWNhdGlvbnMgaGF2ZSBiZWVuIHByb2Nlc3NlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBEIGFzIFBvc3RncmVTUUxEaXNwYXRjaFxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgcGFydGljaXBhbnQgTyBhcyBPYnNlcnZlcnNcbiAgICogICBOb3RlIG92ZXIgRDogUmVjZWl2ZSBub3RpZmljYXRpb24gZnJvbSBQb3N0Z3JlU1FMXG4gICAqICAgRC0+PkQ6IFBhcnNlIG5vdGlmaWNhdGlvbiBwYXlsb2FkXG4gICAqICAgRC0+PkQ6IEV4dHJhY3QgdGFibGUsIG9wZXJhdGlvbiwgYW5kIGlkc1xuICAgKiAgIEQtPj5POiB1cGRhdGVPYnNlcnZlcnModGFibGUsIG9wZXJhdGlvbiwgaWRzKVxuICAgKiAgIEQtPj5EOiBVcGRhdGUgb2JzZXJ2ZXJMYXN0VXBkYXRlXG4gICAqICAgRC0+Pkw6IExvZyBzdWNjZXNzZnVsIGRpc3BhdGNoXG4gICAqL1xuICBwcm90ZWN0ZWQgYXN5bmMgbm90aWZpY2F0aW9uSGFuZGxlcihcbiAgICBub3RpZmljYXRpb246IE5vdGlmaWNhdGlvblxuICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBsb2cgPSB0aGlzLmxvZy5mb3IodGhpcy5ub3RpZmljYXRpb25IYW5kbGVyKTtcblxuICAgIHRyeSB7XG4gICAgICAvLyBQYXJzZSB0aGUgbm90aWZpY2F0aW9uIHBheWxvYWQgKGV4cGVjdGVkIGZvcm1hdDogdGFibGU6b3BlcmF0aW9uOmlkMSxpZDIsLi4uKVxuICAgICAgY29uc3QgcGF5bG9hZCA9IG5vdGlmaWNhdGlvbi5wYXlsb2FkIGFzIHN0cmluZztcbiAgICAgIGNvbnN0IFt0YWJsZSwgb3BlcmF0aW9uLCBpZHNTdHJpbmddID0gcGF5bG9hZC5zcGxpdChcIjpcIik7XG4gICAgICBjb25zdCBpZHMgPSBpZHNTdHJpbmcuc3BsaXQoXCIsXCIpO1xuXG4gICAgICBpZiAoIXRhYmxlIHx8ICFvcGVyYXRpb24gfHwgIWlkcy5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIGxvZy5lcnJvcihgSW52YWxpZCBub3RpZmljYXRpb24gZm9ybWF0OiAke3BheWxvYWR9YCk7XG4gICAgICB9XG5cbiAgICAgIC8vIE1hcCBvcGVyYXRpb24gc3RyaW5nIHRvIE9wZXJhdGlvbktleXNcbiAgICAgIGxldCBvcGVyYXRpb25LZXk6IE9wZXJhdGlvbktleXM7XG4gICAgICBzd2l0Y2ggKG9wZXJhdGlvbi50b0xvd2VyQ2FzZSgpKSB7XG4gICAgICAgIGNhc2UgXCJpbnNlcnRcIjpcbiAgICAgICAgICBvcGVyYXRpb25LZXkgPSBPcGVyYXRpb25LZXlzLkNSRUFURTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSBcInVwZGF0ZVwiOlxuICAgICAgICAgIG9wZXJhdGlvbktleSA9IE9wZXJhdGlvbktleXMuVVBEQVRFO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIFwiZGVsZXRlXCI6XG4gICAgICAgICAgb3BlcmF0aW9uS2V5ID0gT3BlcmF0aW9uS2V5cy5ERUxFVEU7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgcmV0dXJuIGxvZy5lcnJvcihgVW5rbm93biBvcGVyYXRpb246ICR7b3BlcmF0aW9ufWApO1xuICAgICAgfVxuXG4gICAgICAvLyBOb3RpZnkgb2JzZXJ2ZXJzXG4gICAgICBhd2FpdCB0aGlzLnVwZGF0ZU9ic2VydmVycyh0YWJsZSwgb3BlcmF0aW9uS2V5LCBpZHMpO1xuICAgICAgdGhpcy5vYnNlcnZlckxhc3RVcGRhdGUgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG4gICAgICBsb2cudmVyYm9zZShgT2JzZXJ2ZXIgcmVmcmVzaCBkaXNwYXRjaGVkIGJ5ICR7b3BlcmF0aW9ufSBmb3IgJHt0YWJsZX1gKTtcbiAgICAgIGxvZy5kZWJ1ZyhgcGtzOiAke2lkc31gKTtcbiAgICB9IGNhdGNoIChlOiB1bmtub3duKSB7XG4gICAgICBsb2cuZXJyb3IoYEZhaWxlZCB0byBwcm9jZXNzIG5vdGlmaWNhdGlvbjogJHtlfWApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gSW5pdGlhbGl6ZXMgdGhlIGRpc3BhdGNoZXIgYW5kIHN1YnNjcmliZXMgdG8gZGF0YWJhc2Ugbm90aWZpY2F0aW9uc1xuICAgKiBAc3VtbWFyeSBTZXRzIHVwIHRoZSBMSVNURU4gbWVjaGFuaXNtIHRvIHN1YnNjcmliZSB0byBQb3N0Z3JlU1FMIG5vdGlmaWNhdGlvbnNcbiAgICogYW5kIGhhbmRsZXMgcmVjb25uZWN0aW9uIGF0dGVtcHRzIGlmIHRoZSBjb25uZWN0aW9uIGZhaWxzXG4gICAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gdGhlIHN1YnNjcmlwdGlvbiBpcyBlc3RhYmxpc2hlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBEIGFzIFBvc3RncmVTUUxEaXNwYXRjaFxuICAgKiAgIHBhcnRpY2lwYW50IFMgYXMgc3Vic2NyaWJlVG9Qb3N0Z3JlU1FMXG4gICAqICAgcGFydGljaXBhbnQgREIgYXMgUG9zdGdyZVNRTCBEYXRhYmFzZVxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgRC0+PlM6IENhbGwgc3Vic2NyaWJlVG9Qb3N0Z3JlU1FMXG4gICAqICAgUy0+PlM6IENoZWNrIGFkYXB0ZXIgYW5kIG5hdGl2ZVxuICAgKiAgIGFsdCBObyBhZGFwdGVyIG9yIG5hdGl2ZVxuICAgKiAgICAgUy0tPj5TOiB0aHJvdyBJbnRlcm5hbEVycm9yXG4gICAqICAgZW5kXG4gICAqICAgUy0+PkRCOiBDb25uZWN0IGNsaWVudCBmcm9tIHBvb2xcbiAgICogICBTLT4+REI6IExJU1RFTiB0YWJsZV9jaGFuZ2VzXG4gICAqICAgYWx0IFN1Y2Nlc3NcbiAgICogICAgIERCLS0+PlM6IFN1YnNjcmlwdGlvbiBlc3RhYmxpc2hlZFxuICAgKiAgICAgUy0tPj5EOiBQcm9taXNlIHJlc29sdmVzXG4gICAqICAgICBELT4+TDogTG9nIHN1Y2Nlc3NmdWwgc3Vic2NyaXB0aW9uXG4gICAqICAgZWxzZSBFcnJvclxuICAgKiAgICAgREItLT4+UzogRXJyb3JcbiAgICogICAgIFMtPj5TOiBJbmNyZW1lbnQgYXR0ZW1wdENvdW50ZXJcbiAgICogICAgIGFsdCBhdHRlbXB0Q291bnRlciA+IDNcbiAgICogICAgICAgUy0+Pkw6IExvZyBlcnJvclxuICAgKiAgICAgICBTLS0+PkQ6IFByb21pc2UgcmVqZWN0c1xuICAgKiAgICAgZWxzZSBhdHRlbXB0Q291bnRlciA8PSAzXG4gICAqICAgICAgIFMtPj5MOiBMb2cgcmV0cnlcbiAgICogICAgICAgUy0+PlM6IFdhaXQgdGltZW91dFxuICAgKiAgICAgICBTLT4+UzogUmVjdXJzaXZlIGNhbGwgdG8gc3Vic2NyaWJlVG9Qb3N0Z3JlU1FMXG4gICAqICAgICBlbmRcbiAgICogICBlbmRcbiAgICovXG4gIHByb3RlY3RlZCBvdmVycmlkZSBhc3luYyBpbml0aWFsaXplKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGxvZyA9IHRoaXMubG9nLmZvcih0aGlzLmluaXRpYWxpemUpO1xuXG4gICAgYXN5bmMgZnVuY3Rpb24gc3Vic2NyaWJlVG9Qb3N0Z3Jlcyh0aGlzOiBQb3N0Z3Jlc0Rpc3BhdGNoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICBpZiAoIXRoaXMuYWRhcHRlciB8fCAhdGhpcy5uYXRpdmUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEludGVybmFsRXJyb3IoYE5vIGFkYXB0ZXIvbmF0aXZlIG9ic2VydmVkIGZvciBkaXNwYXRjaGApO1xuICAgICAgfVxuXG4gICAgICB0cnkge1xuICAgICAgICB0aGlzLmNsaWVudCA9IGF3YWl0IHRoaXMubmF0aXZlLmNvbm5lY3QoKTtcblxuICAgICAgICB0aGlzLmNsaWVudC5vbihcIm5vdGlmaWNhdGlvblwiLCB0aGlzLm5vdGlmaWNhdGlvbkhhbmRsZXIuYmluZCh0aGlzKSk7XG5cbiAgICAgICAgLy8gTGlzdGVuIGZvciB0YWJsZSBjaGFuZ2Ugbm90aWZpY2F0aW9uc1xuICAgICAgICAvLyBUaGlzIGFzc3VtZXMgeW91IGhhdmUgc2V0IHVwIHRyaWdnZXJzIGluIFBvc3RncmVTUUwgdG8gTk9USUZZIG9uIHRhYmxlIGNoYW5nZXNcbiAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgdGhpcy5jbGllbnQucXVlcnkoXCJMSVNURU4gdXNlcl90YWJsZV9jaGFuZ2VzXCIpO1xuXG4gICAgICAgIHRoaXMuYXR0ZW1wdENvdW50ZXIgPSAwO1xuICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICBpZiAodGhpcy5jbGllbnQpIHtcbiAgICAgICAgICB0aGlzLmNsaWVudC5yZWxlYXNlKCk7XG4gICAgICAgICAgdGhpcy5jbGllbnQgPSB1bmRlZmluZWQ7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoKyt0aGlzLmF0dGVtcHRDb3VudGVyID4gMykge1xuICAgICAgICAgIHJldHVybiBsb2cuZXJyb3IoXG4gICAgICAgICAgICBgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBQb3N0Z3JlcyBub3RpZmljYXRpb25zOiAke2V9YFxuICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICBsb2cuaW5mbyhcbiAgICAgICAgICBgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBQb3N0Z3JlcyBub3RpZmljYXRpb25zOiAke2V9LiBSZXRyeWluZyBpbiAke3RoaXMudGltZW91dH1tcy4uLmBcbiAgICAgICAgKTtcblxuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gc2V0VGltZW91dChyZXNvbHZlLCB0aGlzLnRpbWVvdXQpKTtcbiAgICAgICAgcmV0dXJuIHN1YnNjcmliZVRvUG9zdGdyZXMuY2FsbCh0aGlzKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBzdWJzY3JpYmVUb1Bvc3RncmVzXG4gICAgICAuY2FsbCh0aGlzKVxuICAgICAgLnRoZW4oKCkgPT4ge1xuICAgICAgICB0aGlzLmxvZy5pbmZvKGBTdWJzY3JpYmVkIHRvIFBvc3RncmVzIG5vdGlmaWNhdGlvbnNgKTtcbiAgICAgIH0pXG4gICAgICAuY2F0Y2goKGU6IHVua25vd24pID0+IHtcbiAgICAgICAgdGhyb3cgbmV3IEludGVybmFsRXJyb3IoXG4gICAgICAgICAgYEZhaWxlZCB0byBzdWJzY3JpYmUgdG8gUG9zdGdyZXMgbm90aWZpY2F0aW9uczogJHtlfWBcbiAgICAgICAgKTtcbiAgICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFudXAgbWV0aG9kIHRvIHJlbGVhc2UgcmVzb3VyY2VzIHdoZW4gdGhlIGRpc3BhdGNoZXIgaXMgbm8gbG9uZ2VyIG5lZWRlZFxuICAgKi9cbiAgcHVibGljIGNsZWFudXAoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuY2xpZW50KSB7XG4gICAgICB0aGlzLmNsaWVudC5yZWxlYXNlKCk7XG4gICAgICB0aGlzLmNsaWVudCA9IHVuZGVmaW5lZDtcbiAgICB9XG4gIH1cbn1cbiJdfQ==