@dbx-tools/postgres 0.6.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # `@dbx-tools/postgres`
2
+
3
+ Connection-correct PostgreSQL primitives for Node.js: advisory locks that hold the
4
+ connection they lock, and a structured topic bus over `LISTEN`/`NOTIFY`.
5
+
6
+ Both work against a plain `pg.Pool` or anything structurally compatible with one,
7
+ including the pool AppKit's Lakebase plugin exports — so a Databricks App gets
8
+ them without a second database client or connection pool.
9
+
10
+ ## Key Features
11
+
12
+ - Converts an arbitrary structured key (string, array, object, `Date`, or an
13
+ explicit `bigint`) into a stable signed 64-bit advisory-lock identifier.
14
+ - Derives a legal Postgres channel name from whatever identifies a channel, so no
15
+ call site has to sanitize an app name or a tenant id into an identifier.
16
+ - Holds a session lock on one dedicated pooled connection for the whole callback,
17
+ and hands that connection to the callback so protected work runs on it.
18
+ - Holds a transaction lock released atomically by `COMMIT` or `ROLLBACK`, which is
19
+ what one-time schema installation needs.
20
+ - Broadcasts a `type`/`metadata`/`body` envelope to every listening process over
21
+ `pg_notify`, filtered by topic in-process.
22
+ - Fills in project, machine, process, deployment, and optional AppKit sender
23
+ context automatically, with caller metadata always winning.
24
+ - Rejects a payload that would not survive a JSON round trip unchanged, at the
25
+ call site rather than on the wire.
26
+ - Reconnects a dropped listener with bounded backoff while subscribers remain.
27
+
28
+ ## Advisory Locks
29
+
30
+ ```ts
31
+ import { withAdvisoryLock } from "@dbx-tools/postgres";
32
+ import { Pool } from "pg";
33
+
34
+ const pool = new Pool();
35
+
36
+ await withAdvisoryLock(pool, ["invoice", invoiceId], async (client) => {
37
+ await client.query("UPDATE invoices SET status = 'sent' WHERE id = $1", [invoiceId]);
38
+ });
39
+ ```
40
+
41
+ Run the protected work on the `client` the callback receives. A `pool.query()`
42
+ inside the callback may land on a different connection, which is not the one
43
+ holding the lock.
44
+
45
+ Use the transaction-scoped helper for one-time database work:
46
+
47
+ ```ts
48
+ import { withAdvisoryTransactionLock } from "@dbx-tools/postgres";
49
+
50
+ await withAdvisoryTransactionLock(pool, { schema: "my_feature", version: 1 }, async (client) => {
51
+ await client.query("CREATE TABLE IF NOT EXISTS my_feature.events (id bigint primary key)");
52
+ });
53
+ ```
54
+
55
+ `advisoryLockId(key)` exposes the same reduction on its own. A `bigint` key is
56
+ used directly (narrowed to 64 bits) rather than hashed, so a lock can interoperate
57
+ with another implementation that publishes its numeric lock id — `pgmq`'s
58
+ installer lock, for instance. Anything else is canonicalized with
59
+ `object.toStableKey` from `@dbx-tools/shared-core` and hashed, so key order in an
60
+ object does not matter while a `1` and a `"1"` stay different locks. An array is
61
+ read as several parts, so `["invoice", 7]` and `"invoice_7"` are different locks. A
62
+ non-finite number or a cyclic key throws `TypeError` rather than yielding an
63
+ identity two callers could disagree about.
64
+
65
+ ## Topic Bus
66
+
67
+ ```ts
68
+ import { PostgresTopicBus } from "@dbx-tools/postgres";
69
+
70
+ const bus = new PostgresTopicBus(pool, {
71
+ metadata: async () => ({ publicIp: await resolvePublicIp() }),
72
+ onError: (cause) => logger.error("bus", { cause }),
73
+ });
74
+
75
+ const unsubscribe = await bus.listen("orders", ({ type, metadata, body }) => {
76
+ console.log(type, metadata.project, body);
77
+ });
78
+
79
+ await bus.broadcast("orders", {
80
+ type: "order.updated",
81
+ metadata: { traceId: "abc-123" },
82
+ body: { orderId: "123", status: "ready" },
83
+ });
84
+
85
+ await unsubscribe();
86
+ await bus.close();
87
+ ```
88
+
89
+ Every process listening on the channel receives every message; there are no
90
+ competing consumers. That makes the bus right for live fan-out — an SSE stream per
91
+ browser tab, a cache invalidation, a presence ping — and wrong for work
92
+ distribution. Use a table or a queue when a subscriber needs acks or replay.
93
+
94
+ ### The Envelope
95
+
96
+ | Field | Source | Notes |
97
+ | ------------- | ------ | ------------------------------------------------------------------ |
98
+ | `id` | bus | Unique per message across instances. Use it to dedupe or as `id:`. |
99
+ | `topic` | bus | The `broadcast` topic. Listeners on other topics never see it. |
100
+ | `type` | caller | Event name, e.g. `order.updated`. Required, non-blank. |
101
+ | `metadata` | merged | Automatic context under the caller's. See below. |
102
+ | `body` | caller | Passed through unchanged. |
103
+ | `publishedAt` | bus | ISO-8601, from the publisher's clock — not the database's. |
104
+
105
+ `publishedAt` and `id` are for display and dedupe. Neither orders messages from
106
+ different instances reliably.
107
+
108
+ ### Metadata Precedence
109
+
110
+ Weakest to strongest: automatic machine context, then AppKit sender identity, then
111
+ the bus's `metadata` option, then the per-message `metadata`. A key present at a
112
+ stronger layer is never overwritten, so passing `project` explicitly replaces the
113
+ inferred one.
114
+
115
+ Automatic keys, each omitted when it resolves to nothing: `project` (from
116
+ `DATABRICKS_APP_NAME`, `DATABRICKS_BUNDLE_NAME`, `PROJECT_NAME`, or
117
+ `npm_package_name`), `publicIp` (from `PUBLIC_IP`, `DATABRICKS_PUBLIC_IP`, or
118
+ `HOST_IP`), `hostname`, `cwd`, `platform`, `pid`, `environment`, `appName`,
119
+ `deploymentId`, and `databricksHost`. CPU architecture, runtime name, and runtime
120
+ version are left out on purpose: they are constant per deployment and every
121
+ message would pay for them against the payload limit.
122
+
123
+ `publicIp` is only read from the environment. The bus makes no network call to
124
+ discover one — pass a `metadata` function for that, which is also how to attach
125
+ context a process learns after construction. The function runs on every broadcast,
126
+ so memoize anything expensive.
127
+
128
+ With the optional `@databricks/appkit` peer installed and a user execution context
129
+ active, `senderId`, `senderName`, and `senderEmail` are added too. AppKit is
130
+ imported lazily and never required; outside a request, or without the package, the
131
+ bus just skips those keys.
132
+
133
+ ### What A Message May Contain
134
+
135
+ The body and metadata must survive `JSON.parse(JSON.stringify(x))` unchanged, which
136
+ is stricter than `JSON.stringify` not throwing. A `Date` becomes a string, `NaN`
137
+ and `Infinity` become `null`, a `Map` becomes `{}`, and `undefined` vanishes — each
138
+ reaches the listener as something other than what was sent, so all of them throw
139
+ `TypeError` at the call instead. Cycles, functions, symbols, bigints, and class
140
+ instances are rejected for the same reason.
141
+
142
+ The rule lives in `@dbx-tools/shared-core` as `object.isSerializableValue` and
143
+ `object.SerializableValue`, so a route can apply the same check to an untrusted
144
+ request body and answer 400 rather than 500, and a browser-side caller can share
145
+ the type.
146
+
147
+ The encoded envelope is capped at 7900 bytes, under PostgreSQL's 8000-byte
148
+ `NOTIFY` limit, and a larger one throws `RangeError`. Automatic metadata counts
149
+ against that, so broadcast a reference and let the receiver fetch anything big.
150
+
151
+ ### Connections, Lifecycle, And Failure
152
+
153
+ Publishing borrows a pooled connection per call. Listening cannot: `LISTEN` is
154
+ session state, so the bus checks out ONE dedicated client and keeps it for as long
155
+ as it has subscribers, however many topics and listeners are registered. Size the
156
+ pool with that long-lived checkout in mind.
157
+
158
+ Nothing connects until the first `listen`, or an explicit `start()` when you would
159
+ rather find out about a connection problem at boot. `close()` releases the client
160
+ and is required — register it with the host's shutdown hook. A closed bus stays
161
+ closed and throws instead of silently reconnecting.
162
+
163
+ When the notification connection drops, the bus reconnects on its own: immediately
164
+ first, then doubling from 250ms to a 5s ceiling, indefinitely, for as long as
165
+ subscribers remain. A Postgres restart or a rotated Lakebase credential recovers
166
+ without intervention. Messages published during the gap are lost. Every failed
167
+ attempt, every dropped connection, and every listener that throws is reported
168
+ through `onError`, which defaults to swallowing them — wire it to a logger in
169
+ anything long-running. A failing listener never affects the publisher or the other
170
+ listeners.
171
+
172
+ One channel carries many topics, so adding a topic costs nothing; give a genuinely
173
+ high-volume unrelated stream its own channel instead, since every listening
174
+ session decodes every message on the channel.
175
+
176
+ ### Naming A Channel
177
+
178
+ `channel` takes whatever identifies the channel, not a pre-sanitized identifier —
179
+ a name, a tenant id, a `[env, feature]` pair, a config object. One value or many:
180
+ an array is read as several parts, anything else as one.
181
+
182
+ ```ts
183
+ new PostgresTopicBus(pool, { channel: "billing-events" }).channelName;
184
+ // "billing_events_3x55ck"
185
+ new PostgresTopicBus(pool, { channel: ["billing", "production"] }).channelName;
186
+ // "billing_production_008jgf"
187
+ ```
188
+
189
+ The parts are tokenized into the readable half and a short hash of their canonical
190
+ form (`object.toStableKey`) is appended. The hash is what makes the mapping
191
+ trustworthy: tokenizing alone is lossy, so `my-app`, `my_app`, and `myApp` would
192
+ collapse onto one channel, and a name long enough to hit Postgres's 63-character
193
+ limit would collide with anything sharing its leading tokens. With the suffix the
194
+ readable part stays readable and distinct inputs stay distinct — including
195
+ `["billing", "prod"]` versus `"billing_prod"`, which differ in structure.
196
+
197
+ Derivation is deterministic across processes and runs, so every participant that
198
+ passes equivalent parts lands on the same channel without coordinating. Object key
199
+ order does not matter; a different spelling does. Read `bus.channelName` to see
200
+ what a set of parts resolved to, or to confirm two processes agree. Defaults to a
201
+ shared `dbx_tools_topic_bus` channel.
202
+
203
+ ## Why A Separate Package?
204
+
205
+ Advisory locks are connection-scoped, and that is easy to get wrong invisibly:
206
+ taking the lock with `pool.query()` and doing the protected work with another
207
+ `pool.query()` can use two different connections, so the lock protects nothing and
208
+ the code looks correct. The same applies to `LISTEN`, which is session state a
209
+ pooled query cannot hold. This package owns both lifecycles once, with no
210
+ dependency beyond `pg`, so a consumer that only needs a lock does not pull in a
211
+ message bus, an AppKit runtime, or a queue extension.
212
+
213
+ AppKit exposes Lakebase but has no lock helper and no message bus, so there is no
214
+ native surface to prefer here.
215
+
216
+ ## Module Map
217
+
218
+ | Module | Purpose |
219
+ | -------------- | ------------------------------------------------------------------ |
220
+ | `advisoryLock` | Stable lock IDs plus session- and transaction-scoped lock helpers. |
221
+ | `topicBus` | Structured topic broadcast/listen over PostgreSQL `NOTIFY`. |
222
+
223
+ Both modules are also flattened onto the package root, so
224
+ `import { withAdvisoryLock, PostgresTopicBus } from "@dbx-tools/postgres"` works
225
+ without the namespace.
package/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as advisoryLock from "./src/advisory-lock.ts";
6
+ export * as topicBus from "./src/topic-bus.ts";
7
+ export type { AdvisoryLockKey, PgPoolLike, PgQueryable } from "./src/advisory-lock.ts";
8
+ export { PostgresTopicBus } from "./src/topic-bus.ts";
9
+ export type { TopicMetadata, TopicMessage, TopicPublishInput, TopicListener, TopicMetadataProvider, PostgresTopicBusOptions } from "./src/topic-bus.ts";
10
+ export * from "./exports.ts";
@@ -0,0 +1 @@
1
+ export { advisoryLockId, withAdvisoryLock, withAdvisoryTransactionLock, } from "./src/advisory-lock.ts";
package/lib/exports.js ADDED
@@ -0,0 +1,6 @@
1
+ // Flat re-exports layered onto the generated `index.ts` barrel, which publishes
2
+ // each module as a namespace (`advisoryLock`, `topicBus`) and does not lift
3
+ // functions to the package root. These are the call-site names, so they are
4
+ // importable directly from `@dbx-tools/postgres`.
5
+ export { advisoryLockId, withAdvisoryLock, withAdvisoryTransactionLock, } from "./src/advisory-lock.js";
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL2V4cG9ydHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZ0ZBQWdGO0FBQ2hGLDRFQUE0RTtBQUM1RSw0RUFBNEU7QUFDNUUsa0RBQWtEO0FBQ2xELE9BQU8sRUFDTCxjQUFjLEVBQ2QsZ0JBQWdCLEVBQ2hCLDJCQUEyQixHQUM1QixNQUFNLHdCQUF3QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gRmxhdCByZS1leHBvcnRzIGxheWVyZWQgb250byB0aGUgZ2VuZXJhdGVkIGBpbmRleC50c2AgYmFycmVsLCB3aGljaCBwdWJsaXNoZXNcbi8vIGVhY2ggbW9kdWxlIGFzIGEgbmFtZXNwYWNlIChgYWR2aXNvcnlMb2NrYCwgYHRvcGljQnVzYCkgYW5kIGRvZXMgbm90IGxpZnRcbi8vIGZ1bmN0aW9ucyB0byB0aGUgcGFja2FnZSByb290LiBUaGVzZSBhcmUgdGhlIGNhbGwtc2l0ZSBuYW1lcywgc28gdGhleSBhcmVcbi8vIGltcG9ydGFibGUgZGlyZWN0bHkgZnJvbSBgQGRieC10b29scy9wb3N0Z3Jlc2AuXG5leHBvcnQge1xuICBhZHZpc29yeUxvY2tJZCxcbiAgd2l0aEFkdmlzb3J5TG9jayxcbiAgd2l0aEFkdmlzb3J5VHJhbnNhY3Rpb25Mb2NrLFxufSBmcm9tIFwiLi9zcmMvYWR2aXNvcnktbG9jay50c1wiO1xuIl19
package/lib/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * as advisoryLock from "./src/advisory-lock.ts";
2
+ export * as topicBus from "./src/topic-bus.ts";
3
+ export type { AdvisoryLockKey, PgPoolLike, PgQueryable } from "./src/advisory-lock.ts";
4
+ export { PostgresTopicBus } from "./src/topic-bus.ts";
5
+ export type { TopicMetadata, TopicMessage, TopicPublishInput, TopicListener, TopicMetadataProvider, PostgresTopicBusOptions } from "./src/topic-bus.ts";
6
+ export * from "./exports.ts";
package/lib/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export * as advisoryLock from "./src/advisory-lock.js";
5
+ export * as topicBus from "./src/topic-bus.js";
6
+ export { PostgresTopicBus } from "./src/topic-bus.js";
7
+ export * from "./exports.js";
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssWUFBWSxNQUFNLHdCQUF3QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxRQUFRLE1BQU0sb0JBQW9CLENBQUM7QUFFL0MsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFdEQsY0FBYyxjQUFjLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBhZHZpc29yeUxvY2sgZnJvbSBcIi4vc3JjL2Fkdmlzb3J5LWxvY2sudHNcIjtcbmV4cG9ydCAqIGFzIHRvcGljQnVzIGZyb20gXCIuL3NyYy90b3BpYy1idXMudHNcIjtcbmV4cG9ydCB0eXBlIHsgQWR2aXNvcnlMb2NrS2V5LCBQZ1Bvb2xMaWtlLCBQZ1F1ZXJ5YWJsZSB9IGZyb20gXCIuL3NyYy9hZHZpc29yeS1sb2NrLnRzXCI7XG5leHBvcnQgeyBQb3N0Z3Jlc1RvcGljQnVzIH0gZnJvbSBcIi4vc3JjL3RvcGljLWJ1cy50c1wiO1xuZXhwb3J0IHR5cGUgeyBUb3BpY01ldGFkYXRhLCBUb3BpY01lc3NhZ2UsIFRvcGljUHVibGlzaElucHV0LCBUb3BpY0xpc3RlbmVyLCBUb3BpY01ldGFkYXRhUHJvdmlkZXIsIFBvc3RncmVzVG9waWNCdXNPcHRpb25zIH0gZnJvbSBcIi4vc3JjL3RvcGljLWJ1cy50c1wiO1xuZXhwb3J0ICogZnJvbSBcIi4vZXhwb3J0cy50c1wiO1xuIl19
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Advisory-lock helpers for any `pg.Pool`-compatible pool.
3
+ *
4
+ * PostgreSQL advisory locks belong to a connection, not a pool. These helpers
5
+ * reserve one pooled client for the full callback, acquire the lock on that
6
+ * client, and release both in the correct order.
7
+ *
8
+ * @module
9
+ */
10
+ import type { Pool, PoolClient, QueryResult, QueryResultRow } from "pg";
11
+ /**
12
+ * What names a lock. Anything reducible to a stable identity: a string, an id, a
13
+ * `["invoice", id]` pair, a config object, or an explicit `bigint` to interoperate
14
+ * with another implementation's published lock id.
15
+ *
16
+ * One value or many: an array is read as multiple parts, anything else as a single
17
+ * part. So `["invoice", 7]` and `"invoice_7"` are different locks, since the
18
+ * canonical form sees different structure.
19
+ */
20
+ export type AdvisoryLockKey = unknown;
21
+ /** Structural pool shape accepted by the lock helpers. */
22
+ export type PgPoolLike = Pick<Pool, "connect">;
23
+ /** Structural query shape shared by `pg.PoolClient` and AppKit Lakebase. */
24
+ export interface PgQueryable {
25
+ query<T extends QueryResultRow = QueryResultRow>(text: string, values?: unknown[]): Promise<QueryResult<T>>;
26
+ }
27
+ /**
28
+ * Convert an arbitrary structured key into PostgreSQL's signed 64-bit advisory
29
+ * lock namespace. A bigint is preserved directly so callers can interoperate
30
+ * with another implementation that publishes its lock ID.
31
+ *
32
+ * Everything else is canonicalized with `object.toStableKey` and hashed, so key
33
+ * order in an object does not matter while a `1` and a `"1"` stay different locks.
34
+ * A cycle, a non-finite number, or a function/symbol key throws `TypeError`
35
+ * rather than yielding an identity two callers could disagree about.
36
+ */
37
+ export declare function advisoryLockId(key: AdvisoryLockKey): bigint;
38
+ /**
39
+ * Hold a session advisory lock for the duration of `fn`.
40
+ *
41
+ * The callback receives the dedicated `PoolClient` that owns the lock. Use it
42
+ * for any operation that must be protected by the lock.
43
+ */
44
+ export declare function withAdvisoryLock<T>(pool: PgPoolLike, key: AdvisoryLockKey, fn: (client: PoolClient) => Promise<T> | T): Promise<T>;
45
+ /**
46
+ * Run `fn` in a transaction while holding a transaction advisory lock.
47
+ *
48
+ * The lock is released atomically by `COMMIT` or `ROLLBACK`, making this the
49
+ * right primitive for one-time schema installation and migrations.
50
+ */
51
+ export declare function withAdvisoryTransactionLock<T>(pool: PgPoolLike, key: AdvisoryLockKey, fn: (client: PoolClient) => Promise<T> | T): Promise<T>;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Advisory-lock helpers for any `pg.Pool`-compatible pool.
3
+ *
4
+ * PostgreSQL advisory locks belong to a connection, not a pool. These helpers
5
+ * reserve one pooled client for the full callback, acquire the lock on that
6
+ * client, and release both in the correct order.
7
+ *
8
+ * @module
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { object } from "@dbx-tools/shared-core";
12
+ const SIGNED_BIGINT_BITS = 64;
13
+ /**
14
+ * Convert an arbitrary structured key into PostgreSQL's signed 64-bit advisory
15
+ * lock namespace. A bigint is preserved directly so callers can interoperate
16
+ * with another implementation that publishes its lock ID.
17
+ *
18
+ * Everything else is canonicalized with `object.toStableKey` and hashed, so key
19
+ * order in an object does not matter while a `1` and a `"1"` stay different locks.
20
+ * A cycle, a non-finite number, or a function/symbol key throws `TypeError`
21
+ * rather than yielding an identity two callers could disagree about.
22
+ */
23
+ export function advisoryLockId(key) {
24
+ if (typeof key === "bigint")
25
+ return BigInt.asIntN(SIGNED_BIGINT_BITS, key);
26
+ const parts = object.toOneOrMany(key);
27
+ const digest = createHash("sha256")
28
+ .update(parts.map((part) => object.toStableKey(part)).join("\u0000"))
29
+ .digest();
30
+ return digest.readBigInt64BE(0);
31
+ }
32
+ async function acquire(client, id, transaction) {
33
+ const fn = transaction ? "pg_advisory_xact_lock" : "pg_advisory_lock";
34
+ await client.query(`SELECT ${fn}($1::bigint)`, [id.toString()]);
35
+ }
36
+ async function unlock(client, id) {
37
+ const result = await client.query("SELECT pg_advisory_unlock($1::bigint) AS unlocked", [id.toString()]);
38
+ if (result.rows[0]?.unlocked !== true) {
39
+ throw new Error(`Postgres advisory lock ${id} was not held by this connection`);
40
+ }
41
+ }
42
+ /**
43
+ * Hold a session advisory lock for the duration of `fn`.
44
+ *
45
+ * The callback receives the dedicated `PoolClient` that owns the lock. Use it
46
+ * for any operation that must be protected by the lock.
47
+ */
48
+ export async function withAdvisoryLock(pool, key, fn) {
49
+ const id = advisoryLockId(key);
50
+ const client = await pool.connect();
51
+ let acquired = false;
52
+ let failed = false;
53
+ let failure;
54
+ let value;
55
+ try {
56
+ await acquire(client, id, false);
57
+ acquired = true;
58
+ value = await fn(client);
59
+ }
60
+ catch (error) {
61
+ failed = true;
62
+ failure = error;
63
+ }
64
+ let unlockFailure;
65
+ if (acquired) {
66
+ try {
67
+ await unlock(client, id);
68
+ }
69
+ catch (error) {
70
+ unlockFailure = error;
71
+ }
72
+ }
73
+ client.release(unlockFailure instanceof Error ? unlockFailure : undefined);
74
+ if (failed)
75
+ throw failure;
76
+ if (unlockFailure !== undefined)
77
+ throw unlockFailure;
78
+ return value;
79
+ }
80
+ /**
81
+ * Run `fn` in a transaction while holding a transaction advisory lock.
82
+ *
83
+ * The lock is released atomically by `COMMIT` or `ROLLBACK`, making this the
84
+ * right primitive for one-time schema installation and migrations.
85
+ */
86
+ export async function withAdvisoryTransactionLock(pool, key, fn) {
87
+ const id = advisoryLockId(key);
88
+ const client = await pool.connect();
89
+ let releaseError;
90
+ try {
91
+ await client.query("BEGIN");
92
+ await acquire(client, id, true);
93
+ const value = await fn(client);
94
+ await client.query("COMMIT");
95
+ return value;
96
+ }
97
+ catch (error) {
98
+ try {
99
+ await client.query("ROLLBACK");
100
+ }
101
+ catch (rollbackError) {
102
+ releaseError =
103
+ rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError));
104
+ }
105
+ throw error;
106
+ }
107
+ finally {
108
+ client.release(releaseError);
109
+ }
110
+ }
111
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWR2aXNvcnktbG9jay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZHZpc29yeS1sb2NrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN6QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFHaEQsTUFBTSxrQkFBa0IsR0FBRyxFQUFFLENBQUM7QUEwQjlCOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sVUFBVSxjQUFjLENBQUMsR0FBb0I7SUFDakQsSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRO1FBQUUsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLGtCQUFrQixFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQzNFLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdEMsTUFBTSxNQUFNLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQztTQUNoQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztTQUNwRSxNQUFNLEVBQUUsQ0FBQztJQUNaLE9BQU8sTUFBTSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNsQyxDQUFDO0FBRUQsS0FBSyxVQUFVLE9BQU8sQ0FBQyxNQUFtQixFQUFFLEVBQVUsRUFBRSxXQUFvQjtJQUMxRSxNQUFNLEVBQUUsR0FBRyxXQUFXLENBQUMsQ0FBQyxDQUFDLHVCQUF1QixDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQztJQUN0RSxNQUFNLE1BQU0sQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUFFLGNBQWMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDbEUsQ0FBQztBQUVELEtBQUssVUFBVSxNQUFNLENBQUMsTUFBbUIsRUFBRSxFQUFVO0lBQ25ELE1BQU0sTUFBTSxHQUFHLE1BQU0sTUFBTSxDQUFDLEtBQUssQ0FDL0IsbURBQW1ELEVBQ25ELENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQ2hCLENBQUM7SUFDRixJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsUUFBUSxLQUFLLElBQUksRUFBRSxDQUFDO1FBQ3RDLE1BQU0sSUFBSSxLQUFLLENBQUMsMEJBQTBCLEVBQUUsa0NBQWtDLENBQUMsQ0FBQztJQUNsRixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxnQkFBZ0IsQ0FDcEMsSUFBZ0IsRUFDaEIsR0FBb0IsRUFDcEIsRUFBMEM7SUFFMUMsTUFBTSxFQUFFLEdBQUcsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQy9CLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ3BDLElBQUksUUFBUSxHQUFHLEtBQUssQ0FBQztJQUNyQixJQUFJLE1BQU0sR0FBRyxLQUFLLENBQUM7SUFDbkIsSUFBSSxPQUFnQixDQUFDO0lBQ3JCLElBQUksS0FBb0IsQ0FBQztJQUV6QixJQUFJLENBQUM7UUFDSCxNQUFNLE9BQU8sQ0FBQyxNQUFNLEVBQUUsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ2pDLFFBQVEsR0FBRyxJQUFJLENBQUM7UUFDaEIsS0FBSyxHQUFHLE1BQU0sRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzNCLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxHQUFHLElBQUksQ0FBQztRQUNkLE9BQU8sR0FBRyxLQUFLLENBQUM7SUFDbEIsQ0FBQztJQUVELElBQUksYUFBc0IsQ0FBQztJQUMzQixJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQ2IsSUFBSSxDQUFDO1lBQ0gsTUFBTSxNQUFNLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzNCLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsYUFBYSxHQUFHLEtBQUssQ0FBQztRQUN4QixDQUFDO0lBQ0gsQ0FBQztJQUNELE1BQU0sQ0FBQyxPQUFPLENBQUMsYUFBYSxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUUzRSxJQUFJLE1BQU07UUFBRSxNQUFNLE9BQU8sQ0FBQztJQUMxQixJQUFJLGFBQWEsS0FBSyxTQUFTO1FBQUUsTUFBTSxhQUFhLENBQUM7SUFDckQsT0FBTyxLQUFVLENBQUM7QUFDcEIsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSwyQkFBMkIsQ0FDL0MsSUFBZ0IsRUFDaEIsR0FBb0IsRUFDcEIsRUFBMEM7SUFFMUMsTUFBTSxFQUFFLEdBQUcsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQy9CLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ3BDLElBQUksWUFBK0IsQ0FBQztJQUNwQyxJQUFJLENBQUM7UUFDSCxNQUFNLE1BQU0sQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDNUIsTUFBTSxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNoQyxNQUFNLEtBQUssR0FBRyxNQUFNLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMvQixNQUFNLE1BQU0sQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDN0IsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNqQyxDQUFDO1FBQUMsT0FBTyxhQUFhLEVBQUUsQ0FBQztZQUN2QixZQUFZO2dCQUNWLGFBQWEsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUM7UUFDdEYsQ0FBQztRQUNELE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztZQUFTLENBQUM7UUFDVCxNQUFNLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQy9CLENBQUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBBZHZpc29yeS1sb2NrIGhlbHBlcnMgZm9yIGFueSBgcGcuUG9vbGAtY29tcGF0aWJsZSBwb29sLlxuICpcbiAqIFBvc3RncmVTUUwgYWR2aXNvcnkgbG9ja3MgYmVsb25nIHRvIGEgY29ubmVjdGlvbiwgbm90IGEgcG9vbC4gVGhlc2UgaGVscGVyc1xuICogcmVzZXJ2ZSBvbmUgcG9vbGVkIGNsaWVudCBmb3IgdGhlIGZ1bGwgY2FsbGJhY2ssIGFjcXVpcmUgdGhlIGxvY2sgb24gdGhhdFxuICogY2xpZW50LCBhbmQgcmVsZWFzZSBib3RoIGluIHRoZSBjb3JyZWN0IG9yZGVyLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVIYXNoIH0gZnJvbSBcIm5vZGU6Y3J5cHRvXCI7XG5pbXBvcnQgeyBvYmplY3QgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHR5cGUgeyBQb29sLCBQb29sQ2xpZW50LCBRdWVyeVJlc3VsdCwgUXVlcnlSZXN1bHRSb3cgfSBmcm9tIFwicGdcIjtcblxuY29uc3QgU0lHTkVEX0JJR0lOVF9CSVRTID0gNjQ7XG5cbi8qKlxuICogV2hhdCBuYW1lcyBhIGxvY2suIEFueXRoaW5nIHJlZHVjaWJsZSB0byBhIHN0YWJsZSBpZGVudGl0eTogYSBzdHJpbmcsIGFuIGlkLCBhXG4gKiBgW1wiaW52b2ljZVwiLCBpZF1gIHBhaXIsIGEgY29uZmlnIG9iamVjdCwgb3IgYW4gZXhwbGljaXQgYGJpZ2ludGAgdG8gaW50ZXJvcGVyYXRlXG4gKiB3aXRoIGFub3RoZXIgaW1wbGVtZW50YXRpb24ncyBwdWJsaXNoZWQgbG9jayBpZC5cbiAqXG4gKiBPbmUgdmFsdWUgb3IgbWFueTogYW4gYXJyYXkgaXMgcmVhZCBhcyBtdWx0aXBsZSBwYXJ0cywgYW55dGhpbmcgZWxzZSBhcyBhIHNpbmdsZVxuICogcGFydC4gU28gYFtcImludm9pY2VcIiwgN11gIGFuZCBgXCJpbnZvaWNlXzdcImAgYXJlIGRpZmZlcmVudCBsb2Nrcywgc2luY2UgdGhlXG4gKiBjYW5vbmljYWwgZm9ybSBzZWVzIGRpZmZlcmVudCBzdHJ1Y3R1cmUuXG4gKi9cbmV4cG9ydCB0eXBlIEFkdmlzb3J5TG9ja0tleSA9IHVua25vd247XG5cbi8qKiBTdHJ1Y3R1cmFsIHBvb2wgc2hhcGUgYWNjZXB0ZWQgYnkgdGhlIGxvY2sgaGVscGVycy4gKi9cbmV4cG9ydCB0eXBlIFBnUG9vbExpa2UgPSBQaWNrPFBvb2wsIFwiY29ubmVjdFwiPjtcblxuLyoqIFN0cnVjdHVyYWwgcXVlcnkgc2hhcGUgc2hhcmVkIGJ5IGBwZy5Qb29sQ2xpZW50YCBhbmQgQXBwS2l0IExha2ViYXNlLiAqL1xuZXhwb3J0IGludGVyZmFjZSBQZ1F1ZXJ5YWJsZSB7XG4gIHF1ZXJ5PFQgZXh0ZW5kcyBRdWVyeVJlc3VsdFJvdyA9IFF1ZXJ5UmVzdWx0Um93PihcbiAgICB0ZXh0OiBzdHJpbmcsXG4gICAgdmFsdWVzPzogdW5rbm93bltdLFxuICApOiBQcm9taXNlPFF1ZXJ5UmVzdWx0PFQ+Pjtcbn1cblxudHlwZSBVbmxvY2tSb3cgPSBRdWVyeVJlc3VsdFJvdyAmIHsgdW5sb2NrZWQ6IGJvb2xlYW4gfTtcblxuLyoqXG4gKiBDb252ZXJ0IGFuIGFyYml0cmFyeSBzdHJ1Y3R1cmVkIGtleSBpbnRvIFBvc3RncmVTUUwncyBzaWduZWQgNjQtYml0IGFkdmlzb3J5XG4gKiBsb2NrIG5hbWVzcGFjZS4gQSBiaWdpbnQgaXMgcHJlc2VydmVkIGRpcmVjdGx5IHNvIGNhbGxlcnMgY2FuIGludGVyb3BlcmF0ZVxuICogd2l0aCBhbm90aGVyIGltcGxlbWVudGF0aW9uIHRoYXQgcHVibGlzaGVzIGl0cyBsb2NrIElELlxuICpcbiAqIEV2ZXJ5dGhpbmcgZWxzZSBpcyBjYW5vbmljYWxpemVkIHdpdGggYG9iamVjdC50b1N0YWJsZUtleWAgYW5kIGhhc2hlZCwgc28ga2V5XG4gKiBvcmRlciBpbiBhbiBvYmplY3QgZG9lcyBub3QgbWF0dGVyIHdoaWxlIGEgYDFgIGFuZCBhIGBcIjFcImAgc3RheSBkaWZmZXJlbnQgbG9ja3MuXG4gKiBBIGN5Y2xlLCBhIG5vbi1maW5pdGUgbnVtYmVyLCBvciBhIGZ1bmN0aW9uL3N5bWJvbCBrZXkgdGhyb3dzIGBUeXBlRXJyb3JgXG4gKiByYXRoZXIgdGhhbiB5aWVsZGluZyBhbiBpZGVudGl0eSB0d28gY2FsbGVycyBjb3VsZCBkaXNhZ3JlZSBhYm91dC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFkdmlzb3J5TG9ja0lkKGtleTogQWR2aXNvcnlMb2NrS2V5KTogYmlnaW50IHtcbiAgaWYgKHR5cGVvZiBrZXkgPT09IFwiYmlnaW50XCIpIHJldHVybiBCaWdJbnQuYXNJbnROKFNJR05FRF9CSUdJTlRfQklUUywga2V5KTtcbiAgY29uc3QgcGFydHMgPSBvYmplY3QudG9PbmVPck1hbnkoa2V5KTtcbiAgY29uc3QgZGlnZXN0ID0gY3JlYXRlSGFzaChcInNoYTI1NlwiKVxuICAgIC51cGRhdGUocGFydHMubWFwKChwYXJ0KSA9PiBvYmplY3QudG9TdGFibGVLZXkocGFydCkpLmpvaW4oXCJcXHUwMDAwXCIpKVxuICAgIC5kaWdlc3QoKTtcbiAgcmV0dXJuIGRpZ2VzdC5yZWFkQmlnSW50NjRCRSgwKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gYWNxdWlyZShjbGllbnQ6IFBnUXVlcnlhYmxlLCBpZDogYmlnaW50LCB0cmFuc2FjdGlvbjogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCBmbiA9IHRyYW5zYWN0aW9uID8gXCJwZ19hZHZpc29yeV94YWN0X2xvY2tcIiA6IFwicGdfYWR2aXNvcnlfbG9ja1wiO1xuICBhd2FpdCBjbGllbnQucXVlcnkoYFNFTEVDVCAke2ZufSgkMTo6YmlnaW50KWAsIFtpZC50b1N0cmluZygpXSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHVubG9jayhjbGllbnQ6IFBnUXVlcnlhYmxlLCBpZDogYmlnaW50KTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGNsaWVudC5xdWVyeTxVbmxvY2tSb3c+KFxuICAgIFwiU0VMRUNUIHBnX2Fkdmlzb3J5X3VubG9jaygkMTo6YmlnaW50KSBBUyB1bmxvY2tlZFwiLFxuICAgIFtpZC50b1N0cmluZygpXSxcbiAgKTtcbiAgaWYgKHJlc3VsdC5yb3dzWzBdPy51bmxvY2tlZCAhPT0gdHJ1ZSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgUG9zdGdyZXMgYWR2aXNvcnkgbG9jayAke2lkfSB3YXMgbm90IGhlbGQgYnkgdGhpcyBjb25uZWN0aW9uYCk7XG4gIH1cbn1cblxuLyoqXG4gKiBIb2xkIGEgc2Vzc2lvbiBhZHZpc29yeSBsb2NrIGZvciB0aGUgZHVyYXRpb24gb2YgYGZuYC5cbiAqXG4gKiBUaGUgY2FsbGJhY2sgcmVjZWl2ZXMgdGhlIGRlZGljYXRlZCBgUG9vbENsaWVudGAgdGhhdCBvd25zIHRoZSBsb2NrLiBVc2UgaXRcbiAqIGZvciBhbnkgb3BlcmF0aW9uIHRoYXQgbXVzdCBiZSBwcm90ZWN0ZWQgYnkgdGhlIGxvY2suXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3aXRoQWR2aXNvcnlMb2NrPFQ+KFxuICBwb29sOiBQZ1Bvb2xMaWtlLFxuICBrZXk6IEFkdmlzb3J5TG9ja0tleSxcbiAgZm46IChjbGllbnQ6IFBvb2xDbGllbnQpID0+IFByb21pc2U8VD4gfCBULFxuKTogUHJvbWlzZTxUPiB7XG4gIGNvbnN0IGlkID0gYWR2aXNvcnlMb2NrSWQoa2V5KTtcbiAgY29uc3QgY2xpZW50ID0gYXdhaXQgcG9vbC5jb25uZWN0KCk7XG4gIGxldCBhY3F1aXJlZCA9IGZhbHNlO1xuICBsZXQgZmFpbGVkID0gZmFsc2U7XG4gIGxldCBmYWlsdXJlOiB1bmtub3duO1xuICBsZXQgdmFsdWU6IFQgfCB1bmRlZmluZWQ7XG5cbiAgdHJ5IHtcbiAgICBhd2FpdCBhY3F1aXJlKGNsaWVudCwgaWQsIGZhbHNlKTtcbiAgICBhY3F1aXJlZCA9IHRydWU7XG4gICAgdmFsdWUgPSBhd2FpdCBmbihjbGllbnQpO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGZhaWxlZCA9IHRydWU7XG4gICAgZmFpbHVyZSA9IGVycm9yO1xuICB9XG5cbiAgbGV0IHVubG9ja0ZhaWx1cmU6IHVua25vd247XG4gIGlmIChhY3F1aXJlZCkge1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCB1bmxvY2soY2xpZW50LCBpZCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHVubG9ja0ZhaWx1cmUgPSBlcnJvcjtcbiAgICB9XG4gIH1cbiAgY2xpZW50LnJlbGVhc2UodW5sb2NrRmFpbHVyZSBpbnN0YW5jZW9mIEVycm9yID8gdW5sb2NrRmFpbHVyZSA6IHVuZGVmaW5lZCk7XG5cbiAgaWYgKGZhaWxlZCkgdGhyb3cgZmFpbHVyZTtcbiAgaWYgKHVubG9ja0ZhaWx1cmUgIT09IHVuZGVmaW5lZCkgdGhyb3cgdW5sb2NrRmFpbHVyZTtcbiAgcmV0dXJuIHZhbHVlIGFzIFQ7XG59XG5cbi8qKlxuICogUnVuIGBmbmAgaW4gYSB0cmFuc2FjdGlvbiB3aGlsZSBob2xkaW5nIGEgdHJhbnNhY3Rpb24gYWR2aXNvcnkgbG9jay5cbiAqXG4gKiBUaGUgbG9jayBpcyByZWxlYXNlZCBhdG9taWNhbGx5IGJ5IGBDT01NSVRgIG9yIGBST0xMQkFDS2AsIG1ha2luZyB0aGlzIHRoZVxuICogcmlnaHQgcHJpbWl0aXZlIGZvciBvbmUtdGltZSBzY2hlbWEgaW5zdGFsbGF0aW9uIGFuZCBtaWdyYXRpb25zLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gd2l0aEFkdmlzb3J5VHJhbnNhY3Rpb25Mb2NrPFQ+KFxuICBwb29sOiBQZ1Bvb2xMaWtlLFxuICBrZXk6IEFkdmlzb3J5TG9ja0tleSxcbiAgZm46IChjbGllbnQ6IFBvb2xDbGllbnQpID0+IFByb21pc2U8VD4gfCBULFxuKTogUHJvbWlzZTxUPiB7XG4gIGNvbnN0IGlkID0gYWR2aXNvcnlMb2NrSWQoa2V5KTtcbiAgY29uc3QgY2xpZW50ID0gYXdhaXQgcG9vbC5jb25uZWN0KCk7XG4gIGxldCByZWxlYXNlRXJyb3I6IEVycm9yIHwgdW5kZWZpbmVkO1xuICB0cnkge1xuICAgIGF3YWl0IGNsaWVudC5xdWVyeShcIkJFR0lOXCIpO1xuICAgIGF3YWl0IGFjcXVpcmUoY2xpZW50LCBpZCwgdHJ1ZSk7XG4gICAgY29uc3QgdmFsdWUgPSBhd2FpdCBmbihjbGllbnQpO1xuICAgIGF3YWl0IGNsaWVudC5xdWVyeShcIkNPTU1JVFwiKTtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGNsaWVudC5xdWVyeShcIlJPTExCQUNLXCIpO1xuICAgIH0gY2F0Y2ggKHJvbGxiYWNrRXJyb3IpIHtcbiAgICAgIHJlbGVhc2VFcnJvciA9XG4gICAgICAgIHJvbGxiYWNrRXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IHJvbGxiYWNrRXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKHJvbGxiYWNrRXJyb3IpKTtcbiAgICB9XG4gICAgdGhyb3cgZXJyb3I7XG4gIH0gZmluYWxseSB7XG4gICAgY2xpZW50LnJlbGVhc2UocmVsZWFzZUVycm9yKTtcbiAgfVxufVxuIl19