UNPKG

@decaf-ts/for-postgres

Version:
183 lines 20.7 kB
import { Dispatch } from "@decaf-ts/core"; import { InternalError, OperationKeys } from "@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 */ export class PostgresDispatch extends 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 = OperationKeys.CREATE; break; case "update": operationKey = OperationKeys.UPDATE; break; case "delete": operationKey = 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 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 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; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUG9zdGdyZXNEaXNwYXRjaC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9Qb3N0Z3Jlc0Rpc3BhdGNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUUxQyxPQUFPLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXZFOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Q0c7QUFDSCxNQUFNLE9BQU8sZ0JBQWlCLFNBQVEsUUFBYztJQUtsRCxZQUFvQixVQUFVLElBQUk7UUFDaEMsS0FBSyxFQUFFLENBQUM7UUFEVSxZQUFPLEdBQVAsT0FBTyxDQUFPO1FBSDFCLG1CQUFjLEdBQVcsQ0FBQyxDQUFDO0lBS25DLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FpQkc7SUFDTyxLQUFLLENBQUMsbUJBQW1CLENBQ2pDLFlBQTBCO1FBRTFCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBRW5ELElBQUksQ0FBQztZQUNILGdGQUFnRjtZQUNoRixNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUMsT0FBaUIsQ0FBQztZQUMvQyxNQUFNLENBQUMsS0FBSyxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3pELE1BQU0sR0FBRyxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7WUFFakMsSUFBSSxDQUFDLEtBQUssSUFBSSxDQUFDLFNBQVMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDeEMsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDLGdDQUFnQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQzlELENBQUM7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QUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDeEMsQ0FBQztRQUNILENBQUM7UUFFRCxtQkFBbUI7YUFDaEIsSUFBSSxDQUFDLElBQUksQ0FBQzthQUNWLElBQUksQ0FBQyxHQUFHLEVBQUU7WUFDVCxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFDO1FBQ3hELENBQUMsQ0FBQzthQUNELEtBQUssQ0FBQyxDQUFDLENBQVUsRUFBRSxFQUFFO1lBQ3BCLE1BQU0sSUFBSSxhQUFhLENBQ3JCLGtEQUFrRCxDQUFDLEVBQUUsQ0FDdEQsQ0FBQztRQUNKLENBQUMsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQUVEOztPQUVHO0lBQ0ksT0FBTztRQUNaLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUM7UUFDMUIsQ0FBQztJQUNILENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IERpc3BhdGNoIH0gZnJvbSBcIkBkZWNhZi10cy9jb3JlXCI7XG5pbXBvcnQgeyBQb29sLCBQb29sQ2xpZW50LCBOb3RpZmljYXRpb24gfSBmcm9tIFwicGdcIjtcbmltcG9ydCB7IEludGVybmFsRXJyb3IsIE9wZXJhdGlvbktleXMgfSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcblxuLyoqXG4gKiBAZGVzY3JpcHRpb24gRGlzcGF0Y2hlciBmb3IgUG9zdGdyZVNRTCBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzXG4gKiBAc3VtbWFyeSBIYW5kbGVzIHRoZSBzdWJzY3JpcHRpb24gdG8gYW5kIHByb2Nlc3Npbmcgb2YgZGF0YWJhc2UgY2hhbmdlIGV2ZW50cyBmcm9tIGEgUG9zdGdyZVNRTCBkYXRhYmFzZSxcbiAqIG5vdGlmeWluZyBvYnNlcnZlcnMgd2hlbiByZWNvcmRzIGFyZSBjcmVhdGVkLCB1cGRhdGVkLCBvciBkZWxldGVkXG4gKiBAdGVtcGxhdGUgUG9vbCAtIFRoZSBwZyBQb29sIHR5cGVcbiAqIEBwYXJhbSB7bnVtYmVyfSBbdGltZW91dD01MDAwXSAtIFRpbWVvdXQgaW4gbWlsbGlzZWNvbmRzIGZvciBub3RpZmljYXRpb24gcmVxdWVzdHNcbiAqIEBjbGFzcyBQb3N0Z3Jlc0Rpc3BhdGNoXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGEgZGlzcGF0Y2hlciBmb3IgYSBQb3N0Z3JlU1FMIGRhdGFiYXNlXG4gKiBjb25zdCBwb29sID0gbmV3IFBvb2woe1xuICogICB1c2VyOiAncG9zdGdyZXMnLFxuICogICBwYXNzd29yZDogJ3Bhc3N3b3JkJyxcbiAqICAgaG9zdDogJ2xvY2FsaG9zdCcsXG4gKiAgIHBvcnQ6IDU0MzIsXG4gKiAgIGRhdGFiYXNlOiAnbXlkYidcbiAqIH0pO1xuICogY29uc3QgYWRhcHRlciA9IG5ldyBQb3N0Z3JlU1FMQWRhcHRlckltcGwocG9vbCk7XG4gKiBjb25zdCBkaXNwYXRjaCA9IG5ldyBQb3N0Z3JlU1FMRGlzcGF0Y2goKTtcbiAqXG4gKiAvLyBUaGUgZGlzcGF0Y2hlciB3aWxsIGF1dG9tYXRpY2FsbHkgc3Vic2NyaWJlIHRvIG5vdGlmaWNhdGlvbnNcbiAqIC8vIGFuZCBub3RpZnkgb2JzZXJ2ZXJzIHdoZW4gcmVjb3JkcyBjaGFuZ2VcbiAqIGBgYFxuICogQG1lcm1haWRcbiAqIGNsYXNzRGlhZ3JhbVxuICogICBjbGFzcyBEaXNwYXRjaCB7XG4gKiAgICAgK2luaXRpYWxpemUoKVxuICogICAgICt1cGRhdGVPYnNlcnZlcnMoKVxuICogICB9XG4gKiAgIGNsYXNzIFBvc3RncmVTUUxEaXNwYXRjaCB7XG4gKiAgICAgLW9ic2VydmVyTGFzdFVwZGF0ZT86IHN0cmluZ1xuICogICAgIC1hdHRlbXB0Q291bnRlcjogbnVtYmVyXG4gKiAgICAgLXRpbWVvdXQ6IG51bWJlclxuICogICAgIC1jbGllbnQ/OiBQb29sQ2xpZW50XG4gKiAgICAgK2NvbnN0cnVjdG9yKHRpbWVvdXQpXG4gKiAgICAgI25vdGlmaWNhdGlvbkhhbmRsZXIoKVxuICogICAgICNpbml0aWFsaXplKClcbiAqICAgfVxuICogICBEaXNwYXRjaCA8fC0tIFBvc3RncmVTUUxEaXNwYXRjaFxuICovXG5leHBvcnQgY2xhc3MgUG9zdGdyZXNEaXNwYXRjaCBleHRlbmRzIERpc3BhdGNoPFBvb2w+IHtcbiAgcHJpdmF0ZSBvYnNlcnZlckxhc3RVcGRhdGU/OiBzdHJpbmc7XG4gIHByaXZhdGUgYXR0ZW1wdENvdW50ZXI6IG51bWJlciA9IDA7XG4gIHByaXZhdGUgY2xpZW50PzogUG9vbENsaWVudDtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHRpbWVvdXQgPSA1MDAwKSB7XG4gICAgc3VwZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gUHJvY2Vzc2VzIGRhdGFiYXNlIG5vdGlmaWNhdGlvbiBldmVudHNcbiAgICogQHN1bW1hcnkgSGFuZGxlcyB0aGUgbm90aWZpY2F0aW9ucyBmcm9tIFBvc3RncmVTUUwgTElTVEVOL05PVElGWSBtZWNoYW5pc20sXG4gICAqIGFuZCBub3RpZmllcyBvYnNlcnZlcnMgYWJvdXQgcmVjb3JkIGNoYW5nZXNcbiAgICogQHBhcmFtIHtOb3RpZmljYXRpb259IG5vdGlmaWNhdGlvbiAtIFRoZSBub3RpZmljYXRpb24gZnJvbSBQb3N0Z3JlU1FMXG4gICAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gYWxsIG5vdGlmaWNhdGlvbnMgaGF2ZSBiZWVuIHByb2Nlc3NlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBEIGFzIFBvc3RncmVTUUxEaXNwYXRjaFxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgcGFydGljaXBhbnQgTyBhcyBPYnNlcnZlcnNcbiAgICogICBOb3RlIG92ZXIgRDogUmVjZWl2ZSBub3RpZmljYXRpb24gZnJvbSBQb3N0Z3JlU1FMXG4gICAqICAgRC0+PkQ6IFBhcnNlIG5vdGlmaWNhdGlvbiBwYXlsb2FkXG4gICAqICAgRC0+PkQ6IEV4dHJhY3QgdGFibGUsIG9wZXJhdGlvbiwgYW5kIGlkc1xuICAgKiAgIEQtPj5POiB1cGRhdGVPYnNlcnZlcnModGFibGUsIG9wZXJhdGlvbiwgaWRzKVxuICAgKiAgIEQtPj5EOiBVcGRhdGUgb2JzZXJ2ZXJMYXN0VXBkYXRlXG4gICAqICAgRC0+Pkw6IExvZyBzdWNjZXNzZnVsIGRpc3BhdGNoXG4gICAqL1xuICBwcm90ZWN0ZWQgYXN5bmMgbm90aWZpY2F0aW9uSGFuZGxlcihcbiAgICBub3RpZmljYXRpb246IE5vdGlmaWNhdGlvblxuICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBsb2cgPSB0aGlzLmxvZy5mb3IodGhpcy5ub3RpZmljYXRpb25IYW5kbGVyKTtcblxuICAgIHRyeSB7XG4gICAgICAvLyBQYXJzZSB0aGUgbm90aWZpY2F0aW9uIHBheWxvYWQgKGV4cGVjdGVkIGZvcm1hdDogdGFibGU6b3BlcmF0aW9uOmlkMSxpZDIsLi4uKVxuICAgICAgY29uc3QgcGF5bG9hZCA9IG5vdGlmaWNhdGlvbi5wYXlsb2FkIGFzIHN0cmluZztcbiAgICAgIGNvbnN0IFt0YWJsZSwgb3BlcmF0aW9uLCBpZHNTdHJpbmddID0gcGF5bG9hZC5zcGxpdChcIjpcIik7XG4gICAgICBjb25zdCBpZHMgPSBpZHNTdHJpbmcuc3BsaXQoXCIsXCIpO1xuXG4gICAgICBpZiAoIXRhYmxlIHx8ICFvcGVyYXRpb24gfHwgIWlkcy5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIGxvZy5lcnJvcihgSW52YWxpZCBub3RpZmljYXRpb24gZm9ybWF0OiAke3BheWxvYWR9YCk7XG4gICAgICB9XG5cbiAgICAgIC8vIE1hcCBvcGVyYXRpb24gc3RyaW5nIHRvIE9wZXJhdGlvbktleXNcbiAgICAgIGxldCBvcGVyYXRpb25LZXk6IE9wZXJhdGlvbktleXM7XG4gICAgICBzd2l0Y2ggKG9wZXJhdGlvbi50b0xvd2VyQ2FzZSgpKSB7XG4gICAgICAgIGNhc2UgXCJpbnNlcnRcIjpcbiAgICAgICAgICBvcGVyYXRpb25LZXkgPSBPcGVyYXRpb25LZXlzLkNSRUFURTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSBcInVwZGF0ZVwiOlxuICAgICAgICAgIG9wZXJhdGlvbktleSA9IE9wZXJhdGlvbktleXMuVVBEQVRFO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIFwiZGVsZXRlXCI6XG4gICAgICAgICAgb3BlcmF0aW9uS2V5ID0gT3BlcmF0aW9uS2V5cy5ERUxFVEU7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgcmV0dXJuIGxvZy5lcnJvcihgVW5rbm93biBvcGVyYXRpb246ICR7b3BlcmF0aW9ufWApO1xuICAgICAgfVxuXG4gICAgICAvLyBOb3RpZnkgb2JzZXJ2ZXJzXG4gICAgICBhd2FpdCB0aGlzLnVwZGF0ZU9ic2VydmVycyh0YWJsZSwgb3BlcmF0aW9uS2V5LCBpZHMpO1xuICAgICAgdGhpcy5vYnNlcnZlckxhc3RVcGRhdGUgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG4gICAgICBsb2cudmVyYm9zZShgT2JzZXJ2ZXIgcmVmcmVzaCBkaXNwYXRjaGVkIGJ5ICR7b3BlcmF0aW9ufSBmb3IgJHt0YWJsZX1gKTtcbiAgICAgIGxvZy5kZWJ1ZyhgcGtzOiAke2lkc31gKTtcbiAgICB9IGNhdGNoIChlOiB1bmtub3duKSB7XG4gICAgICBsb2cuZXJyb3IoYEZhaWxlZCB0byBwcm9jZXNzIG5vdGlmaWNhdGlvbjogJHtlfWApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVzY3JpcHRpb24gSW5pdGlhbGl6ZXMgdGhlIGRpc3BhdGNoZXIgYW5kIHN1YnNjcmliZXMgdG8gZGF0YWJhc2Ugbm90aWZpY2F0aW9uc1xuICAgKiBAc3VtbWFyeSBTZXRzIHVwIHRoZSBMSVNURU4gbWVjaGFuaXNtIHRvIHN1YnNjcmliZSB0byBQb3N0Z3JlU1FMIG5vdGlmaWNhdGlvbnNcbiAgICogYW5kIGhhbmRsZXMgcmVjb25uZWN0aW9uIGF0dGVtcHRzIGlmIHRoZSBjb25uZWN0aW9uIGZhaWxzXG4gICAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59IEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gdGhlIHN1YnNjcmlwdGlvbiBpcyBlc3RhYmxpc2hlZFxuICAgKiBAbWVybWFpZFxuICAgKiBzZXF1ZW5jZURpYWdyYW1cbiAgICogICBwYXJ0aWNpcGFudCBEIGFzIFBvc3RncmVTUUxEaXNwYXRjaFxuICAgKiAgIHBhcnRpY2lwYW50IFMgYXMgc3Vic2NyaWJlVG9Qb3N0Z3JlU1FMXG4gICAqICAgcGFydGljaXBhbnQgREIgYXMgUG9zdGdyZVNRTCBEYXRhYmFzZVxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgRC0+PlM6IENhbGwgc3Vic2NyaWJlVG9Qb3N0Z3JlU1FMXG4gICAqICAgUy0+PlM6IENoZWNrIGFkYXB0ZXIgYW5kIG5hdGl2ZVxuICAgKiAgIGFsdCBObyBhZGFwdGVyIG9yIG5hdGl2ZVxuICAgKiAgICAgUy0tPj5TOiB0aHJvdyBJbnRlcm5hbEVycm9yXG4gICAqICAgZW5kXG4gICAqICAgUy0+PkRCOiBDb25uZWN0IGNsaWVudCBmcm9tIHBvb2xcbiAgICogICBTLT4+REI6IExJU1RFTiB0YWJsZV9jaGFuZ2VzXG4gICAqICAgYWx0IFN1Y2Nlc3NcbiAgICogICAgIERCLS0+PlM6IFN1YnNjcmlwdGlvbiBlc3RhYmxpc2hlZFxuICAgKiAgICAgUy0tPj5EOiBQcm9taXNlIHJlc29sdmVzXG4gICAqICAgICBELT4+TDogTG9nIHN1Y2Nlc3NmdWwgc3Vic2NyaXB0aW9uXG4gICAqICAgZWxzZSBFcnJvclxuICAgKiAgICAgREItLT4+UzogRXJyb3JcbiAgICogICAgIFMtPj5TOiBJbmNyZW1lbnQgYXR0ZW1wdENvdW50ZXJcbiAgICogICAgIGFsdCBhdHRlbXB0Q291bnRlciA+IDNcbiAgICogICAgICAgUy0+Pkw6IExvZyBlcnJvclxuICAgKiAgICAgICBTLS0+PkQ6IFByb21pc2UgcmVqZWN0c1xuICAgKiAgICAgZWxzZSBhdHRlbXB0Q291bnRlciA8PSAzXG4gICAqICAgICAgIFMtPj5MOiBMb2cgcmV0cnlcbiAgICogICAgICAgUy0+PlM6IFdhaXQgdGltZW91dFxuICAgKiAgICAgICBTLT4+UzogUmVjdXJzaXZlIGNhbGwgdG8gc3Vic2NyaWJlVG9Qb3N0Z3JlU1FMXG4gICAqICAgICBlbmRcbiAgICogICBlbmRcbiAgICovXG4gIHByb3RlY3RlZCBvdmVycmlkZSBhc3luYyBpbml0aWFsaXplKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGxvZyA9IHRoaXMubG9nLmZvcih0aGlzLmluaXRpYWxpemUpO1xuXG4gICAgYXN5bmMgZnVuY3Rpb24gc3Vic2NyaWJlVG9Qb3N0Z3Jlcyh0aGlzOiBQb3N0Z3Jlc0Rpc3BhdGNoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICBpZiAoIXRoaXMuYWRhcHRlciB8fCAhdGhpcy5uYXRpdmUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEludGVybmFsRXJyb3IoYE5vIGFkYXB0ZXIvbmF0aXZlIG9ic2VydmVkIGZvciBkaXNwYXRjaGApO1xuICAgICAgfVxuXG4gICAgICB0cnkge1xuICAgICAgICB0aGlzLmNsaWVudCA9IGF3YWl0IHRoaXMubmF0aXZlLmNvbm5lY3QoKTtcblxuICAgICAgICB0aGlzLmNsaWVudC5vbihcIm5vdGlmaWNhdGlvblwiLCB0aGlzLm5vdGlmaWNhdGlvbkhhbmRsZXIuYmluZCh0aGlzKSk7XG5cbiAgICAgICAgLy8gTGlzdGVuIGZvciB0YWJsZSBjaGFuZ2Ugbm90aWZpY2F0aW9uc1xuICAgICAgICAvLyBUaGlzIGFzc3VtZXMgeW91IGhhdmUgc2V0IHVwIHRyaWdnZXJzIGluIFBvc3RncmVTUUwgdG8gTk9USUZZIG9uIHRhYmxlIGNoYW5nZXNcbiAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgdGhpcy5jbGllbnQucXVlcnkoXCJMSVNURU4gdXNlcl90YWJsZV9jaGFuZ2VzXCIpO1xuXG4gICAgICAgIHRoaXMuYXR0ZW1wdENvdW50ZXIgPSAwO1xuICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICBpZiAodGhpcy5jbGllbnQpIHtcbiAgICAgICAgICB0aGlzLmNsaWVudC5yZWxlYXNlKCk7XG4gICAgICAgICAgdGhpcy5jbGllbnQgPSB1bmRlZmluZWQ7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoKyt0aGlzLmF0dGVtcHRDb3VudGVyID4gMykge1xuICAgICAgICAgIHJldHVybiBsb2cuZXJyb3IoXG4gICAgICAgICAgICBgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBQb3N0Z3JlcyBub3RpZmljYXRpb25zOiAke2V9YFxuICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICBsb2cuaW5mbyhcbiAgICAgICAgICBgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBQb3N0Z3JlcyBub3RpZmljYXRpb25zOiAke2V9LiBSZXRyeWluZyBpbiAke3RoaXMudGltZW91dH1tcy4uLmBcbiAgICAgICAgKTtcblxuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gc2V0VGltZW91dChyZXNvbHZlLCB0aGlzLnRpbWVvdXQpKTtcbiAgICAgICAgcmV0dXJuIHN1YnNjcmliZVRvUG9zdGdyZXMuY2FsbCh0aGlzKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBzdWJzY3JpYmVUb1Bvc3RncmVzXG4gICAgICAuY2FsbCh0aGlzKVxuICAgICAgLnRoZW4oKCkgPT4ge1xuICAgICAgICB0aGlzLmxvZy5pbmZvKGBTdWJzY3JpYmVkIHRvIFBvc3RncmVzIG5vdGlmaWNhdGlvbnNgKTtcbiAgICAgIH0pXG4gICAgICAuY2F0Y2goKGU6IHVua25vd24pID0+IHtcbiAgICAgICAgdGhyb3cgbmV3IEludGVybmFsRXJyb3IoXG4gICAgICAgICAgYEZhaWxlZCB0byBzdWJzY3JpYmUgdG8gUG9zdGdyZXMgbm90aWZpY2F0aW9uczogJHtlfWBcbiAgICAgICAgKTtcbiAgICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFudXAgbWV0aG9kIHRvIHJlbGVhc2UgcmVzb3VyY2VzIHdoZW4gdGhlIGRpc3BhdGNoZXIgaXMgbm8gbG9uZ2VyIG5lZWRlZFxuICAgKi9cbiAgcHVibGljIGNsZWFudXAoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuY2xpZW50KSB7XG4gICAgICB0aGlzLmNsaWVudC5yZWxlYXNlKCk7XG4gICAgICB0aGlzLmNsaWVudCA9IHVuZGVmaW5lZDtcbiAgICB9XG4gIH1cbn1cbiJdfQ==