velocious 1.0.590 → 1.0.591

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.
Files changed (65) hide show
  1. package/README.md +4 -0
  2. package/build/background-jobs/pooled-runner-broker-identity.js +76 -0
  3. package/build/background-jobs/pooled-runner-child.js +15 -5
  4. package/build/background-jobs/store.js +11 -14
  5. package/build/background-jobs/worker.js +17 -2
  6. package/build/database/pool/base.js +18 -1
  7. package/build/environment-handlers/base.js +8 -0
  8. package/build/environment-handlers/node.js +12 -0
  9. package/build/src/background-jobs/pooled-runner-broker-identity.d.ts +44 -0
  10. package/build/src/background-jobs/pooled-runner-broker-identity.d.ts.map +1 -0
  11. package/build/src/background-jobs/pooled-runner-broker-identity.js +76 -0
  12. package/build/src/background-jobs/pooled-runner-child.js +16 -6
  13. package/build/src/background-jobs/store.d.ts.map +1 -1
  14. package/build/src/background-jobs/store.js +12 -14
  15. package/build/src/background-jobs/worker.d.ts +6 -0
  16. package/build/src/background-jobs/worker.d.ts.map +1 -1
  17. package/build/src/background-jobs/worker.js +17 -3
  18. package/build/src/database/pool/base.d.ts +6 -0
  19. package/build/src/database/pool/base.d.ts.map +1 -1
  20. package/build/src/database/pool/base.js +18 -2
  21. package/build/src/environment-handlers/base.d.ts +12 -0
  22. package/build/src/environment-handlers/base.d.ts.map +1 -1
  23. package/build/src/environment-handlers/base.js +8 -1
  24. package/build/src/environment-handlers/node.d.ts +11 -0
  25. package/build/src/environment-handlers/node.d.ts.map +1 -1
  26. package/build/src/environment-handlers/node.js +13 -1
  27. package/build/src/testing/shared-transaction-broker-client.d.ts +53 -0
  28. package/build/src/testing/shared-transaction-broker-client.d.ts.map +1 -0
  29. package/build/src/testing/shared-transaction-broker-client.js +105 -0
  30. package/build/src/testing/shared-transaction-broker.d.ts +153 -0
  31. package/build/src/testing/shared-transaction-broker.d.ts.map +1 -0
  32. package/build/src/testing/shared-transaction-broker.js +414 -0
  33. package/build/src/testing/shared-transaction-codec.d.ts +21 -0
  34. package/build/src/testing/shared-transaction-codec.d.ts.map +1 -0
  35. package/build/src/testing/shared-transaction-codec.js +114 -0
  36. package/build/src/testing/shared-transaction-connection-coordinator.d.ts +23 -0
  37. package/build/src/testing/shared-transaction-connection-coordinator.d.ts.map +1 -0
  38. package/build/src/testing/shared-transaction-connection-coordinator.js +36 -0
  39. package/build/src/testing/shared-transaction-proxy-driver.d.ts +42 -0
  40. package/build/src/testing/shared-transaction-proxy-driver.d.ts.map +1 -0
  41. package/build/src/testing/shared-transaction-proxy-driver.js +195 -0
  42. package/build/src/testing/test-runner.d.ts +20 -0
  43. package/build/src/testing/test-runner.d.ts.map +1 -1
  44. package/build/src/testing/test-runner.js +64 -3
  45. package/build/testing/shared-transaction-broker-client.js +107 -0
  46. package/build/testing/shared-transaction-broker.js +401 -0
  47. package/build/testing/shared-transaction-codec.js +110 -0
  48. package/build/testing/shared-transaction-connection-coordinator.js +38 -0
  49. package/build/testing/shared-transaction-proxy-driver.js +205 -0
  50. package/build/testing/test-runner.js +65 -2
  51. package/build/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +4 -2
  53. package/src/background-jobs/pooled-runner-broker-identity.js +76 -0
  54. package/src/background-jobs/pooled-runner-child.js +15 -5
  55. package/src/background-jobs/store.js +11 -14
  56. package/src/background-jobs/worker.js +17 -2
  57. package/src/database/pool/base.js +18 -1
  58. package/src/environment-handlers/base.js +8 -0
  59. package/src/environment-handlers/node.js +12 -0
  60. package/src/testing/shared-transaction-broker-client.js +107 -0
  61. package/src/testing/shared-transaction-broker.js +401 -0
  62. package/src/testing/shared-transaction-codec.js +110 -0
  63. package/src/testing/shared-transaction-connection-coordinator.js +38 -0
  64. package/src/testing/shared-transaction-proxy-driver.js +205 -0
  65. package/src/testing/test-runner.js +65 -2
package/README.md CHANGED
@@ -2072,6 +2072,10 @@ Tests default to a 60-second timeout. Override per test with `{timeoutSeconds: 5
2072
2072
 
2073
2073
  Request tests share transaction-active, non-tenant database connections with their in-process HTTP handlers. Eligibility is evaluated when each request is dispatched, so a hook can start a transaction and issue a request in the same callback. This makes uncommitted setup visible to handlers while preserving rollback isolation. Without an active transaction, handlers use independent pooled connections, so concurrency and locking tests can opt out of transaction cleanup and exercise production-style connections. Shared connection state is scoped to the test lifecycle and cleared around each test. See [docs/testing-guidelines.md](docs/testing-guidelines.md#request-test-database-connections).
2074
2074
 
2075
+ Transactional tests also share active non-tenant connections with real forked, reusable pooled, and spawned background-job child runners through a per-attempt test-only loopback broker. Parent setup and child writes therefore occupy the same physical transaction and roll back together, including background-job persistence. Multiple configured databases route by identifier; tenant-only databases remain excluded. Tests using `{transaction: false, truncate: true}` retain ordinary independent physical connections for concurrency and locking coverage. See [docs/testing-guidelines.md](docs/testing-guidelines.md#request-test-database-connections).
2076
+
2077
+ Warm pooled children receive the active broker capability per job, discard retained proxy state when the capability changes, and fail closed if a transactional dispatch lacks coordinates. Child transaction/savepoint work holds a FIFO lease on the parent physical connection until the matching root release or rollback.
2078
+
2075
2079
  # Writing a request test
2076
2080
 
2077
2081
  First create a test file under something like the following path 'src/routes/accounts/create-test.js' with something like the following content:
@@ -0,0 +1,76 @@
1
+ // @ts-check
2
+
3
+ export default class PooledRunnerBrokerIdentity {
4
+ /**
5
+ * Creates a pooled runner identity coordinator.
6
+ * @param {{closeConnections: () => Promise<void>}} args - Connection cleanup hook.
7
+ */
8
+ constructor({closeConnections}) {
9
+ this.closeConnections = closeConnections
10
+ /** @type {string | undefined} */
11
+ this.activeIdentity = undefined
12
+ /** @type {{identity: string, promise: Promise<void>} | undefined} */
13
+ this.pending = undefined
14
+ this.activeUsers = 0
15
+ }
16
+
17
+ /**
18
+ * Gets the current prepared identity.
19
+ * @returns {string | undefined} - Current prepared identity.
20
+ */
21
+ current() { return this.activeIdentity }
22
+
23
+ /**
24
+ * Prepares one identity, sharing an in-flight same-identity rotation.
25
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
26
+ * @returns {Promise<void>} - Resolves after stale connections close.
27
+ */
28
+ async prepare(config) {
29
+ const identity = JSON.stringify(config)
30
+ if (this.pending) {
31
+ if (this.pending.identity !== identity) throw new Error("Pooled runner cannot mix shared transaction broker capabilities concurrently")
32
+ return await this.pending.promise
33
+ }
34
+ if (this.activeIdentity === identity) return
35
+ if (this.activeUsers > 0) throw new Error("Pooled runner cannot mix shared transaction broker capabilities concurrently")
36
+ if (this.activeIdentity === undefined) {
37
+ this.activeIdentity = identity
38
+ return
39
+ }
40
+
41
+ const promise = this.rotate(identity)
42
+ this.pending = {identity, promise}
43
+ try {
44
+ await promise
45
+ } finally {
46
+ this.pending = undefined
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Runs work while preventing a different identity from replacing its connections.
52
+ * @template T
53
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
54
+ * @param {() => Promise<T>} callback - Job callback.
55
+ * @returns {Promise<T>} - Job result.
56
+ */
57
+ async run(config, callback) {
58
+ await this.prepare(config)
59
+ this.activeUsers++
60
+ try {
61
+ return await callback()
62
+ } finally {
63
+ this.activeUsers--
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Rotates retained connection state to an identity.
69
+ * @param {string} identity - Target identity.
70
+ * @returns {Promise<void>} - Resolves after rotation.
71
+ */
72
+ async rotate(identity) {
73
+ await this.closeConnections()
74
+ this.activeIdentity = identity
75
+ }
76
+ }
@@ -3,6 +3,8 @@
3
3
  import runJobPayload, { BackgroundJobPerformedFailure } from "./job-runner.js"
4
4
  import { closeRunnerConnections, currentConfigurationOrNull } from "./runner-graceful-shutdown.js"
5
5
  import setRunnerProcessTitle from "./runner-process-title.js"
6
+ import PooledRunnerBrokerIdentity from "./pooled-runner-broker-identity.js"
7
+ import { runWithSharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js"
6
8
 
7
9
  const BASE_PROCESS_TITLE = "velocious background-jobs-runner"
8
10
 
@@ -33,6 +35,9 @@ async function shutdownRunner(exitCode) {
33
35
  * @type {Set<string>}
34
36
  */
35
37
  const runningJobIds = new Set()
38
+ const brokerIdentity = new PooledRunnerBrokerIdentity({
39
+ closeConnections: async () => await closeRunnerConnections(currentConfigurationOrNull())
40
+ })
36
41
 
37
42
  /**
38
43
  * Sets an aggregate process title from the current in-flight count. A child runs
@@ -51,11 +56,11 @@ function updateProcessTitle() {
51
56
  /**
52
57
  * Checks whether an IPC value is a runnable pooled job message.
53
58
  * @param {ReturnType<typeof JSON.parse>} message - IPC message.
54
- * @returns {message is {type: "job", payload: import("./types.js").BackgroundJobPayload & {id: string}}} - Whether this is a valid job message.
59
+ * @returns {message is {type: "job", payload: import("./types.js").BackgroundJobPayload & {id: string}, sharedTransactionBroker?: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig}} - Whether this is a valid job message.
55
60
  */
56
61
  function isJobMessage(message) {
57
62
  if (!message || typeof message !== "object") return false
58
- const record = /** @type {{type?: ReturnType<typeof JSON.parse>, payload?: ReturnType<typeof JSON.parse>}} */ (message)
63
+ const record = /** @type {{type?: ReturnType<typeof JSON.parse>, payload?: ReturnType<typeof JSON.parse>, sharedTransactionBroker?: ReturnType<typeof JSON.parse>}} */ (message)
59
64
 
60
65
  return record.type === "job" && !!record.payload && typeof record.payload === "object" && typeof record.payload.id === "string"
61
66
  }
@@ -95,11 +100,16 @@ function sendOutcome({jobId, acknowledged, status, error}) {
95
100
  * per-job try/catch) ends the child, which the worker sees as an exit and
96
101
  * reclaims for the whole in-flight set.
97
102
  * @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
103
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} sharedTransactionBroker - Per-job broker configuration.
98
104
  * @returns {Promise<void>} - Resolves after reporting.
99
105
  */
100
- async function runJob(payload) {
106
+ async function runJob(payload, sharedTransactionBroker) {
101
107
  try {
102
- const status = await runJobPayload(payload, {closeConnections: false, manageProcessTitle: false})
108
+ const status = await runWithSharedTransactionBrokerConfig(sharedTransactionBroker, async () => {
109
+ return await brokerIdentity.run(sharedTransactionBroker, async () => {
110
+ return await runJobPayload(payload, {closeConnections: false, manageProcessTitle: false})
111
+ })
112
+ })
103
113
  await sendOutcome({jobId: payload.id, acknowledged: true, status})
104
114
  } catch (error) {
105
115
  if (error instanceof BackgroundJobPerformedFailure) {
@@ -125,7 +135,7 @@ function handleMessage(message) {
125
135
 
126
136
  runningJobIds.add(message.payload.id)
127
137
  updateProcessTitle()
128
- void runJob(message.payload)
138
+ void runJob(message.payload, message.sharedTransactionBroker || {expected: false})
129
139
  }
130
140
 
131
141
  process.on("message", (message) => handleMessage(message))
@@ -6,6 +6,7 @@ import TableData from "../database/table-data/index.js"
6
6
  import VelociousError from "../velocious-error.js"
7
7
  import BackgroundJobRecord from "./job-record.js"
8
8
  import normalizeBackgroundJobError from "./normalize-error.js"
9
+ import {coordinateSharedTransactionConnection} from "../testing/shared-transaction-connection-coordinator.js"
9
10
 
10
11
  /**
11
12
  * PreparedBackgroundJob type.
@@ -2376,23 +2377,19 @@ export default class BackgroundJobsStore {
2376
2377
  * @returns {Promise<T>} - Callback result.
2377
2378
  */
2378
2379
  async _withDb(callback) {
2379
- const pool = this.configuration.getDatabasePool(this.getDatabaseIdentifier())
2380
- let callbackCalled = false
2381
- /**
2382
- * Defines result.
2383
- * @type {T | undefined} */
2384
- let result
2385
-
2386
- await pool.withConnection({name: "Background jobs store"}, async (db) => {
2387
- callbackCalled = true
2388
- result = await callback(db)
2389
- })
2380
+ const databaseIdentifier = this.getDatabaseIdentifier()
2381
+ const pool = this.configuration.getDatabasePool(databaseIdentifier)
2390
2382
 
2391
- if (!callbackCalled) {
2392
- throw new Error("Background jobs store callback was not invoked")
2383
+ if (!pool.testSharedConnection()) {
2384
+ return await pool.withConnection({name: "Background jobs store"}, callback)
2393
2385
  }
2394
2386
 
2395
- return /** @type {T} */ (result)
2387
+ return await this.configuration.runWithTestSharedConnectionContexts(async () => {
2388
+ return await this.configuration.ensureConnections({databaseIdentifiers: [databaseIdentifier], name: "Background jobs store"}, async (dbs) => {
2389
+ const connection = dbs[databaseIdentifier]
2390
+ return await coordinateSharedTransactionConnection(connection, async () => await callback(connection))
2391
+ })
2392
+ })
2396
2393
  }
2397
2394
 
2398
2395
  /**
@@ -693,13 +693,26 @@ export default class BackgroundJobsWorker {
693
693
 
694
694
  state.inflight.set(payload.id, {payload, resolve, timeoutTimer})
695
695
  try {
696
- child.send({type: "job", payload})
696
+ child.send({type: "job", payload, sharedTransactionBroker: this._pooledJobSharedTransactionBrokerConfig()})
697
697
  } catch (error) {
698
698
  void this._handlePooledChildFailure({child, error})
699
699
  }
700
700
  })
701
701
  }
702
702
 
703
+ /**
704
+ * Captures the current test attempt's broker mode at dispatch time. A warm
705
+ * pooled child must never rely on its immutable fork-time environment.
706
+ * @returns {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} - Per-job broker configuration.
707
+ */
708
+ _pooledJobSharedTransactionBrokerConfig() {
709
+ const serialized = process.env.VELOCIOUS_TEST_SHARED_TRANSACTION_BROKER
710
+ if (!serialized) return {expected: false}
711
+
712
+ const config = JSON.parse(Buffer.from(serialized, "base64url").toString("utf8"))
713
+ return {...config, expected: true}
714
+ }
715
+
703
716
  /**
704
717
  * Selects a pooled child to run the next job, or undefined when every non-retiring
705
718
  * child is already full (the caller then lazily spawns one). Among children with a
@@ -792,7 +805,7 @@ export default class BackgroundJobsWorker {
792
805
  const config = configuration.getBackgroundJobsConfig()
793
806
  const child = fork(POOLED_RUNNER_ENTRY_PATH, [], {
794
807
  cwd: configuration.getDirectory(), execArgv: [], stdio: ["ignore", "ignore", "ignore", "ipc"],
795
- env: Object.assign({}, process.env, {VELOCIOUS_ENV: configuration.getEnvironment(), VELOCIOUS_BACKGROUND_JOBS_HOST: config.host, VELOCIOUS_BACKGROUND_JOBS_PORT: `${config.port}`})
808
+ env: Object.assign({}, process.env, {VELOCIOUS_BACKGROUND_JOB_CHILD: "1", VELOCIOUS_ENV: configuration.getEnvironment(), VELOCIOUS_BACKGROUND_JOBS_HOST: config.host, VELOCIOUS_BACKGROUND_JOBS_PORT: `${config.port}`})
796
809
  })
797
810
  this.pooledChildren.add(child)
798
811
  this.inflightProcessChildren.add(child)
@@ -989,6 +1002,7 @@ export default class BackgroundJobsWorker {
989
1002
  execArgv: [],
990
1003
  stdio: ["ignore", "ignore", "ignore", "ipc"],
991
1004
  env: Object.assign({}, process.env, {
1005
+ VELOCIOUS_BACKGROUND_JOB_CHILD: "1",
992
1006
  VELOCIOUS_ENV: configuration.getEnvironment(),
993
1007
  VELOCIOUS_BACKGROUND_JOBS_HOST: backgroundJobsConfig.host,
994
1008
  VELOCIOUS_BACKGROUND_JOBS_PORT: `${backgroundJobsConfig.port}`
@@ -1216,6 +1230,7 @@ export default class BackgroundJobsWorker {
1216
1230
  detached: true,
1217
1231
  stdio: "ignore",
1218
1232
  env: Object.assign({}, process.env, {
1233
+ VELOCIOUS_BACKGROUND_JOB_CHILD: "1",
1219
1234
  VELOCIOUS_ENV: configuration.getEnvironment(),
1220
1235
  VELOCIOUS_BACKGROUND_JOBS_HOST: backgroundJobsConfig.host,
1221
1236
  VELOCIOUS_BACKGROUND_JOBS_PORT: `${backgroundJobsConfig.port}`,
@@ -193,6 +193,15 @@ class VelociousDatabasePoolBase {
193
193
  return callback()
194
194
  }
195
195
 
196
+ /**
197
+ * Returns the connection registered for test-only in-process sharing.
198
+ * Base pools do not need a separate shared connection.
199
+ * @returns {import("../drivers/base.js").default | undefined} - Shared connection.
200
+ */
201
+ testSharedConnection() {
202
+ return undefined
203
+ }
204
+
196
205
  /**
197
206
  * Returns whether the current connection is pinned to an execution context.
198
207
  * @returns {boolean} - Whether the current connection can be reused by nested code.
@@ -349,7 +358,15 @@ class VelociousDatabasePoolBase {
349
358
 
350
359
  if (!DriverClass) throw new Error("No driver class set in database pool or in given config")
351
360
 
352
- const connection = new DriverClass(config, this.configuration)
361
+ const sharedConnection = config.tenantOnly
362
+ ? undefined
363
+ : await this.configuration.getEnvironmentHandler().createTestSharedTransactionConnection({
364
+ DriverClass,
365
+ config,
366
+ configuration: this.configuration,
367
+ databaseIdentifier: this.identifier
368
+ })
369
+ const connection = sharedConnection || new DriverClass(config, this.configuration)
353
370
 
354
371
  try {
355
372
  await connection.connect()
@@ -19,6 +19,14 @@ import {validateTimeZone} from "../time-zone.js"
19
19
  */
20
20
 
21
21
  export default class VelociousEnvironmentHandlerBase {
22
+ /**
23
+ * Node test runtimes may replace a physical child connection with a broker
24
+ * proxy. Other environments never participate in this test-only protocol.
25
+ * @param {{DriverClass: typeof import("../database/drivers/base.js").default, config: import("../configuration-types.js").DatabaseConfigurationType, configuration: import("../configuration.js").default, databaseIdentifier: string}} _args - Connection details.
26
+ * @returns {Promise<import("../database/drivers/base.js").default | undefined>} - Optional proxy.
27
+ */
28
+ async createTestSharedTransactionConnection(_args) { return undefined }
29
+
22
30
  /**
23
31
  * Mutable ambient tenant used by runtimes without async-context storage.
24
32
  * @type {ReturnType<typeof JSON.parse> | undefined}
@@ -36,6 +36,7 @@ import InitializerFromRequireContext from "../database/initializer-from-require-
36
36
  import toImportSpecifier from "../utils/to-import-specifier.js"
37
37
  import {validateTimeZone} from "../time-zone.js"
38
38
  import AttachmentPathSource from "./node/attachment-path-source.js"
39
+ import { createSharedTransactionProxyDriver, sharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js"
39
40
 
40
41
  /**
41
42
  * Defines this typedef.
@@ -63,6 +64,17 @@ function pathWithinAllowedPrefixes(filePath, allowedPathPrefixes) {
63
64
  }
64
65
 
65
66
  export default class VelociousEnvironmentHandlerNode extends Base{
67
+ /**
68
+ * Creates a test-only child proxy when TestRunner supplied an active broker.
69
+ * @param {{DriverClass: typeof import("../database/drivers/base.js").default, config: import("../configuration-types.js").DatabaseConfigurationType, configuration: import("../configuration.js").default, databaseIdentifier: string}} args - Connection details.
70
+ * @returns {Promise<import("../database/drivers/base.js").default | undefined>} - Optional proxy.
71
+ */
72
+ async createTestSharedTransactionConnection({DriverClass, config, configuration, databaseIdentifier}) {
73
+ const brokerConfig = sharedTransactionBrokerConfig(databaseIdentifier)
74
+ if (!brokerConfig) return undefined
75
+ return createSharedTransactionProxyDriver(DriverClass, config, configuration, databaseIdentifier, brokerConfig)
76
+ }
77
+
66
78
  /**
67
79
  * Timezone async local storage.
68
80
  * @type {import("node:async_hooks").AsyncLocalStorage<TimezoneStore> | undefined} */
@@ -0,0 +1,44 @@
1
+ export default class PooledRunnerBrokerIdentity {
2
+ closeConnections: () => Promise<void>;
3
+ /** @type {string | undefined} */
4
+ activeIdentity: string | undefined;
5
+ /** @type {{identity: string, promise: Promise<void>} | undefined} */
6
+ pending: {
7
+ identity: string;
8
+ promise: Promise<void>;
9
+ } | undefined;
10
+ activeUsers: number;
11
+ /**
12
+ * Creates a pooled runner identity coordinator.
13
+ * @param {{closeConnections: () => Promise<void>}} args - Connection cleanup hook.
14
+ */
15
+ constructor({ closeConnections }: {
16
+ closeConnections: () => Promise<void>;
17
+ });
18
+ /**
19
+ * Gets the current prepared identity.
20
+ * @returns {string | undefined} - Current prepared identity.
21
+ */
22
+ current(): string | undefined;
23
+ /**
24
+ * Prepares one identity, sharing an in-flight same-identity rotation.
25
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
26
+ * @returns {Promise<void>} - Resolves after stale connections close.
27
+ */
28
+ prepare(config: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig): Promise<void>;
29
+ /**
30
+ * Runs work while preventing a different identity from replacing its connections.
31
+ * @template T
32
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
33
+ * @param {() => Promise<T>} callback - Job callback.
34
+ * @returns {Promise<T>} - Job result.
35
+ */
36
+ run<T>(config: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig, callback: () => Promise<T>): Promise<T>;
37
+ /**
38
+ * Rotates retained connection state to an identity.
39
+ * @param {string} identity - Target identity.
40
+ * @returns {Promise<void>} - Resolves after rotation.
41
+ */
42
+ rotate(identity: string): Promise<void>;
43
+ }
44
+ //# sourceMappingURL=pooled-runner-broker-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pooled-runner-broker-identity.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/pooled-runner-broker-identity.js"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,OAAO,0BAA0B;IAMtC,gBAAgB,QAHa,OAAO,CAAC,IAAI,CAAC;IAI/C,iCAAiC;IAC5B,cAAc,EADR,MAAM,GAAG,SAAS;IAE7B,qEAAqE;IAChE,OAAO,EADD;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;KAAC,GAAG,SAAS;IAE5D,WAAW;IAVlB;;;OAGG;IACH,YAAY,EAAC,gBAAgB,EAAC,EAFnB;QAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAEnB,EAO7B;IAED;;;OAGG;IACH,OAAO,IAFM,MAAM,GAAG,SAAS,CAES;IAExC;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAHT,OAAO,+CAA+C,EAAE,gCAG/C,GAFP,OAAO,CAAC,IAAI,CAAC,CAsBzB;IAED;;;;;;OAMG;IACG,GAAG,CALI,CAAC,EAKJ,MAAM,EAJL,OAAO,+CAA+C,EAAE,gCAInD,EAAE,QAAQ,EAHf,MAAM,OAAO,CAAC,CAAC,CAGA,GAFb,OAAO,CAAC,CAAC,CAAC,CAUtB;IAED;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAHV,MAGU,GAFR,OAAO,CAAC,IAAI,CAAC,CAKzB;CACF"}
@@ -0,0 +1,76 @@
1
+ // @ts-check
2
+ export default class PooledRunnerBrokerIdentity {
3
+ /**
4
+ * Creates a pooled runner identity coordinator.
5
+ * @param {{closeConnections: () => Promise<void>}} args - Connection cleanup hook.
6
+ */
7
+ constructor({ closeConnections }) {
8
+ this.closeConnections = closeConnections;
9
+ /** @type {string | undefined} */
10
+ this.activeIdentity = undefined;
11
+ /** @type {{identity: string, promise: Promise<void>} | undefined} */
12
+ this.pending = undefined;
13
+ this.activeUsers = 0;
14
+ }
15
+ /**
16
+ * Gets the current prepared identity.
17
+ * @returns {string | undefined} - Current prepared identity.
18
+ */
19
+ current() { return this.activeIdentity; }
20
+ /**
21
+ * Prepares one identity, sharing an in-flight same-identity rotation.
22
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
23
+ * @returns {Promise<void>} - Resolves after stale connections close.
24
+ */
25
+ async prepare(config) {
26
+ const identity = JSON.stringify(config);
27
+ if (this.pending) {
28
+ if (this.pending.identity !== identity)
29
+ throw new Error("Pooled runner cannot mix shared transaction broker capabilities concurrently");
30
+ return await this.pending.promise;
31
+ }
32
+ if (this.activeIdentity === identity)
33
+ return;
34
+ if (this.activeUsers > 0)
35
+ throw new Error("Pooled runner cannot mix shared transaction broker capabilities concurrently");
36
+ if (this.activeIdentity === undefined) {
37
+ this.activeIdentity = identity;
38
+ return;
39
+ }
40
+ const promise = this.rotate(identity);
41
+ this.pending = { identity, promise };
42
+ try {
43
+ await promise;
44
+ }
45
+ finally {
46
+ this.pending = undefined;
47
+ }
48
+ }
49
+ /**
50
+ * Runs work while preventing a different identity from replacing its connections.
51
+ * @template T
52
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
53
+ * @param {() => Promise<T>} callback - Job callback.
54
+ * @returns {Promise<T>} - Job result.
55
+ */
56
+ async run(config, callback) {
57
+ await this.prepare(config);
58
+ this.activeUsers++;
59
+ try {
60
+ return await callback();
61
+ }
62
+ finally {
63
+ this.activeUsers--;
64
+ }
65
+ }
66
+ /**
67
+ * Rotates retained connection state to an identity.
68
+ * @param {string} identity - Target identity.
69
+ * @returns {Promise<void>} - Resolves after rotation.
70
+ */
71
+ async rotate(identity) {
72
+ await this.closeConnections();
73
+ this.activeIdentity = identity;
74
+ }
75
+ }
76
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbGVkLXJ1bm5lci1icm9rZXItaWRlbnRpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL3Bvb2xlZC1ydW5uZXItYnJva2VyLWlkZW50aXR5LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixNQUFNLENBQUMsT0FBTyxPQUFPLDBCQUEwQjtJQUM3Qzs7O09BR0c7SUFDSCxZQUFZLEVBQUMsZ0JBQWdCLEVBQUM7UUFDNUIsSUFBSSxDQUFDLGdCQUFnQixHQUFHLGdCQUFnQixDQUFBO1FBQ3hDLGlDQUFpQztRQUNqQyxJQUFJLENBQUMsY0FBYyxHQUFHLFNBQVMsQ0FBQTtRQUMvQixxRUFBcUU7UUFDckUsSUFBSSxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUE7UUFDeEIsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUE7SUFDdEIsQ0FBQztJQUVEOzs7T0FHRztJQUNILE9BQU8sS0FBSyxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUEsQ0FBQyxDQUFDO0lBRXhDOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU07UUFDbEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUN2QyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNqQixJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxLQUFLLFFBQVE7Z0JBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4RUFBOEUsQ0FBQyxDQUFBO1lBQ3ZJLE9BQU8sTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQTtRQUNuQyxDQUFDO1FBQ0QsSUFBSSxJQUFJLENBQUMsY0FBYyxLQUFLLFFBQVE7WUFBRSxPQUFNO1FBQzVDLElBQUksSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4RUFBOEUsQ0FBQyxDQUFBO1FBQ3pILElBQUksSUFBSSxDQUFDLGNBQWMsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUN0QyxJQUFJLENBQUMsY0FBYyxHQUFHLFFBQVEsQ0FBQTtZQUM5QixPQUFNO1FBQ1IsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUE7UUFDckMsSUFBSSxDQUFDLE9BQU8sR0FBRyxFQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUMsQ0FBQTtRQUNsQyxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sQ0FBQTtRQUNmLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxPQUFPLEdBQUcsU0FBUyxDQUFBO1FBQzFCLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsUUFBUTtRQUN4QixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDMUIsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFBO1FBQ2xCLElBQUksQ0FBQztZQUNILE9BQU8sTUFBTSxRQUFRLEVBQUUsQ0FBQTtRQUN6QixDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUE7UUFDcEIsQ0FBQztJQUNILENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLE1BQU0sQ0FBQyxRQUFRO1FBQ25CLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUE7UUFDN0IsSUFBSSxDQUFDLGNBQWMsR0FBRyxRQUFRLENBQUE7SUFDaEMsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFBvb2xlZFJ1bm5lckJyb2tlcklkZW50aXR5IHtcbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBwb29sZWQgcnVubmVyIGlkZW50aXR5IGNvb3JkaW5hdG9yLlxuICAgKiBAcGFyYW0ge3tjbG9zZUNvbm5lY3Rpb25zOiAoKSA9PiBQcm9taXNlPHZvaWQ+fX0gYXJncyAtIENvbm5lY3Rpb24gY2xlYW51cCBob29rLlxuICAgKi9cbiAgY29uc3RydWN0b3Ioe2Nsb3NlQ29ubmVjdGlvbnN9KSB7XG4gICAgdGhpcy5jbG9zZUNvbm5lY3Rpb25zID0gY2xvc2VDb25uZWN0aW9uc1xuICAgIC8qKiBAdHlwZSB7c3RyaW5nIHwgdW5kZWZpbmVkfSAqL1xuICAgIHRoaXMuYWN0aXZlSWRlbnRpdHkgPSB1bmRlZmluZWRcbiAgICAvKiogQHR5cGUge3tpZGVudGl0eTogc3RyaW5nLCBwcm9taXNlOiBQcm9taXNlPHZvaWQ+fSB8IHVuZGVmaW5lZH0gKi9cbiAgICB0aGlzLnBlbmRpbmcgPSB1bmRlZmluZWRcbiAgICB0aGlzLmFjdGl2ZVVzZXJzID0gMFxuICB9XG5cbiAgLyoqXG4gICAqIEdldHMgdGhlIGN1cnJlbnQgcHJlcGFyZWQgaWRlbnRpdHkuXG4gICAqIEByZXR1cm5zIHtzdHJpbmcgfCB1bmRlZmluZWR9IC0gQ3VycmVudCBwcmVwYXJlZCBpZGVudGl0eS5cbiAgICovXG4gIGN1cnJlbnQoKSB7IHJldHVybiB0aGlzLmFjdGl2ZUlkZW50aXR5IH1cblxuICAvKipcbiAgICogUHJlcGFyZXMgb25lIGlkZW50aXR5LCBzaGFyaW5nIGFuIGluLWZsaWdodCBzYW1lLWlkZW50aXR5IHJvdGF0aW9uLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL3Rlc3Rpbmcvc2hhcmVkLXRyYW5zYWN0aW9uLXByb3h5LWRyaXZlci5qc1wiKS5TaGFyZWRUcmFuc2FjdGlvbkJyb2tlckpvYkNvbmZpZ30gY29uZmlnIC0gRGlzcGF0Y2ggY29uZmlndXJhdGlvbi5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgc3RhbGUgY29ubmVjdGlvbnMgY2xvc2UuXG4gICAqL1xuICBhc3luYyBwcmVwYXJlKGNvbmZpZykge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gSlNPTi5zdHJpbmdpZnkoY29uZmlnKVxuICAgIGlmICh0aGlzLnBlbmRpbmcpIHtcbiAgICAgIGlmICh0aGlzLnBlbmRpbmcuaWRlbnRpdHkgIT09IGlkZW50aXR5KSB0aHJvdyBuZXcgRXJyb3IoXCJQb29sZWQgcnVubmVyIGNhbm5vdCBtaXggc2hhcmVkIHRyYW5zYWN0aW9uIGJyb2tlciBjYXBhYmlsaXRpZXMgY29uY3VycmVudGx5XCIpXG4gICAgICByZXR1cm4gYXdhaXQgdGhpcy5wZW5kaW5nLnByb21pc2VcbiAgICB9XG4gICAgaWYgKHRoaXMuYWN0aXZlSWRlbnRpdHkgPT09IGlkZW50aXR5KSByZXR1cm5cbiAgICBpZiAodGhpcy5hY3RpdmVVc2VycyA+IDApIHRocm93IG5ldyBFcnJvcihcIlBvb2xlZCBydW5uZXIgY2Fubm90IG1peCBzaGFyZWQgdHJhbnNhY3Rpb24gYnJva2VyIGNhcGFiaWxpdGllcyBjb25jdXJyZW50bHlcIilcbiAgICBpZiAodGhpcy5hY3RpdmVJZGVudGl0eSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aGlzLmFjdGl2ZUlkZW50aXR5ID0gaWRlbnRpdHlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IHByb21pc2UgPSB0aGlzLnJvdGF0ZShpZGVudGl0eSlcbiAgICB0aGlzLnBlbmRpbmcgPSB7aWRlbnRpdHksIHByb21pc2V9XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHByb21pc2VcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5wZW5kaW5nID0gdW5kZWZpbmVkXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgd29yayB3aGlsZSBwcmV2ZW50aW5nIGEgZGlmZmVyZW50IGlkZW50aXR5IGZyb20gcmVwbGFjaW5nIGl0cyBjb25uZWN0aW9ucy5cbiAgICogQHRlbXBsYXRlIFRcbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi90ZXN0aW5nL3NoYXJlZC10cmFuc2FjdGlvbi1wcm94eS1kcml2ZXIuanNcIikuU2hhcmVkVHJhbnNhY3Rpb25Ccm9rZXJKb2JDb25maWd9IGNvbmZpZyAtIERpc3BhdGNoIGNvbmZpZ3VyYXRpb24uXG4gICAqIEBwYXJhbSB7KCkgPT4gUHJvbWlzZTxUPn0gY2FsbGJhY2sgLSBKb2IgY2FsbGJhY2suXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFQ+fSAtIEpvYiByZXN1bHQuXG4gICAqL1xuICBhc3luYyBydW4oY29uZmlnLCBjYWxsYmFjaykge1xuICAgIGF3YWl0IHRoaXMucHJlcGFyZShjb25maWcpXG4gICAgdGhpcy5hY3RpdmVVc2VycysrXG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCBjYWxsYmFjaygpXG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHRoaXMuYWN0aXZlVXNlcnMtLVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSb3RhdGVzIHJldGFpbmVkIGNvbm5lY3Rpb24gc3RhdGUgdG8gYW4gaWRlbnRpdHkuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBpZGVudGl0eSAtIFRhcmdldCBpZGVudGl0eS5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgcm90YXRpb24uXG4gICAqL1xuICBhc3luYyByb3RhdGUoaWRlbnRpdHkpIHtcbiAgICBhd2FpdCB0aGlzLmNsb3NlQ29ubmVjdGlvbnMoKVxuICAgIHRoaXMuYWN0aXZlSWRlbnRpdHkgPSBpZGVudGl0eVxuICB9XG59XG4iXX0=
@@ -2,6 +2,8 @@
2
2
  import runJobPayload, { BackgroundJobPerformedFailure } from "./job-runner.js";
3
3
  import { closeRunnerConnections, currentConfigurationOrNull } from "./runner-graceful-shutdown.js";
4
4
  import setRunnerProcessTitle from "./runner-process-title.js";
5
+ import PooledRunnerBrokerIdentity from "./pooled-runner-broker-identity.js";
6
+ import { runWithSharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js";
5
7
  const BASE_PROCESS_TITLE = "velocious background-jobs-runner";
6
8
  setRunnerProcessTitle();
7
9
  let shuttingDown = false;
@@ -27,6 +29,9 @@ async function shutdownRunner(exitCode) {
27
29
  * @type {Set<string>}
28
30
  */
29
31
  const runningJobIds = new Set();
32
+ const brokerIdentity = new PooledRunnerBrokerIdentity({
33
+ closeConnections: async () => await closeRunnerConnections(currentConfigurationOrNull())
34
+ });
30
35
  /**
31
36
  * Sets an aggregate process title from the current in-flight count. A child runs
32
37
  * jobs concurrently, so a per-job title (which `runJobPayload` would snapshot and
@@ -42,12 +47,12 @@ function updateProcessTitle() {
42
47
  /**
43
48
  * Checks whether an IPC value is a runnable pooled job message.
44
49
  * @param {ReturnType<typeof JSON.parse>} message - IPC message.
45
- * @returns {message is {type: "job", payload: import("./types.js").BackgroundJobPayload & {id: string}}} - Whether this is a valid job message.
50
+ * @returns {message is {type: "job", payload: import("./types.js").BackgroundJobPayload & {id: string}, sharedTransactionBroker?: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig}} - Whether this is a valid job message.
46
51
  */
47
52
  function isJobMessage(message) {
48
53
  if (!message || typeof message !== "object")
49
54
  return false;
50
- const record = /** @type {{type?: ReturnType<typeof JSON.parse>, payload?: ReturnType<typeof JSON.parse>}} */ (message);
55
+ const record = /** @type {{type?: ReturnType<typeof JSON.parse>, payload?: ReturnType<typeof JSON.parse>, sharedTransactionBroker?: ReturnType<typeof JSON.parse>}} */ (message);
51
56
  return record.type === "job" && !!record.payload && typeof record.payload === "object" && typeof record.payload.id === "string";
52
57
  }
53
58
  /**
@@ -83,11 +88,16 @@ function sendOutcome({ jobId, acknowledged, status, error }) {
83
88
  * per-job try/catch) ends the child, which the worker sees as an exit and
84
89
  * reclaims for the whole in-flight set.
85
90
  * @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
91
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} sharedTransactionBroker - Per-job broker configuration.
86
92
  * @returns {Promise<void>} - Resolves after reporting.
87
93
  */
88
- async function runJob(payload) {
94
+ async function runJob(payload, sharedTransactionBroker) {
89
95
  try {
90
- const status = await runJobPayload(payload, { closeConnections: false, manageProcessTitle: false });
96
+ const status = await runWithSharedTransactionBrokerConfig(sharedTransactionBroker, async () => {
97
+ return await brokerIdentity.run(sharedTransactionBroker, async () => {
98
+ return await runJobPayload(payload, { closeConnections: false, manageProcessTitle: false });
99
+ });
100
+ });
91
101
  await sendOutcome({ jobId: payload.id, acknowledged: true, status });
92
102
  }
93
103
  catch (error) {
@@ -115,10 +125,10 @@ function handleMessage(message) {
115
125
  return;
116
126
  runningJobIds.add(message.payload.id);
117
127
  updateProcessTitle();
118
- void runJob(message.payload);
128
+ void runJob(message.payload, message.sharedTransactionBroker || { expected: false });
119
129
  }
120
130
  process.on("message", (message) => handleMessage(message));
121
131
  process.once("disconnect", () => void shutdownRunner(0));
122
132
  for (const signal of ["SIGTERM", "SIGINT"])
123
133
  process.once(signal, () => void shutdownRunner(1));
124
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbGVkLXJ1bm5lci1jaGlsZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvcG9vbGVkLXJ1bm5lci1jaGlsZC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxhQUFhLEVBQUUsRUFBRSw2QkFBNkIsRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzlFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLCtCQUErQixDQUFBO0FBQ2xHLE9BQU8scUJBQXFCLE1BQU0sMkJBQTJCLENBQUE7QUFFN0QsTUFBTSxrQkFBa0IsR0FBRyxrQ0FBa0MsQ0FBQTtBQUU3RCxxQkFBcUIsRUFBRSxDQUFBO0FBRXZCLElBQUksWUFBWSxHQUFHLEtBQUssQ0FBQTtBQUV4Qjs7Ozs7O0dBTUc7QUFDSCxLQUFLLFVBQVUsY0FBYyxDQUFDLFFBQVE7SUFDcEMsSUFBSSxZQUFZO1FBQUUsT0FBTTtJQUN4QixZQUFZLEdBQUcsSUFBSSxDQUFBO0lBRW5CLE1BQU0sc0JBQXNCLENBQUMsMEJBQTBCLEVBQUUsQ0FBQyxDQUFBO0lBQzFELE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUE7QUFDeEIsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sYUFBYSxHQUFHLElBQUksR0FBRyxFQUFFLENBQUE7QUFFL0I7Ozs7Ozs7R0FPRztBQUNILFNBQVMsa0JBQWtCO0lBQ3pCLE1BQU0sS0FBSyxHQUFHLGFBQWEsQ0FBQyxJQUFJLENBQUE7SUFFaEMsT0FBTyxDQUFDLEtBQUssR0FBRyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLGtCQUFrQixLQUFLLEtBQUssSUFBSSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQTtBQUNwSCxDQUFD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QUcsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQ3JDLGtCQUFrQixFQUFFLENBQUE7SUFDcEIsS0FBSyxNQUFNLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0FBQzlCLENBQUM7QUFFRCxPQUFPLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUE7QUFDMUQsT0FBTyxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsR0FBRyxFQUFFLENBQUMsS0FBSyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUN4RCxLQUFLLE1BQU0sTUFBTSxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQztJQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IHJ1bkpvYlBheWxvYWQsIHsgQmFja2dyb3VuZEpvYlBlcmZvcm1lZEZhaWx1cmUgfSBmcm9tIFwiLi9qb2ItcnVubmVyLmpzXCJcbmltcG9ydCB7IGNsb3NlUnVubmVyQ29ubmVjdGlvbnMsIGN1cnJlbnRDb25maWd1cmF0aW9uT3JOdWxsIH0gZnJvbSBcIi4vcnVubmVyLWdyYWNlZnVsLXNodXRkb3duLmpzXCJcbmltcG9ydCBzZXRSdW5uZXJQcm9jZXNzVGl0bGUgZnJvbSBcIi4vcnVubmVyLXByb2Nlc3MtdGl0bGUuanNcIlxuXG5jb25zdCBCQVNFX1BST0NFU1NfVElUTEUgPSBcInZlbG9jaW91cyBiYWNrZ3JvdW5kLWpvYnMtcnVubmVyXCJcblxuc2V0UnVubmVyUHJvY2Vzc1RpdGxlKClcblxubGV0IHNodXR0aW5nRG93biA9IGZhbHNlXG5cbi8qKlxuICogQ2xvc2VzIHRoZSBydW5uZXIncyBjb25uZWN0aW9ucyDigJQgcmVsZWFzaW5nIGFueSBhZHZpc29yeSBsb2NrIGEga2lsbGVkLW1pZC1wYXNzXG4gKiBqb2Igc3RpbGwgaG9sZHMg4oCUIGJlZm9yZSBleGl0aW5nLCBpbnN0ZWFkIG9mIGxlYXZpbmcgYSBoYWxmLW9wZW4gc2Vzc2lvbiB0aGF0XG4gKiBrZWVwcyB0aGUgbG9jayB1bnRpbCB0aGUgREIgc2VydmVyJ3MgYHdhaXRfdGltZW91dGAuXG4gKiBAcGFyYW0ge251bWJlcn0gZXhpdENvZGUgLSBQcm9jZXNzIGV4aXQgY29kZS5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICovXG5hc3luYyBmdW5jdGlvbiBzaHV0ZG93blJ1bm5lcihleGl0Q29kZSkge1xuICBpZiAoc2h1dHRpbmdEb3duKSByZXR1cm5cbiAgc2h1dHRpbmdEb3duID0gdHJ1ZVxuXG4gIGF3YWl0IGNsb3NlUnVubmVyQ29ubmVjdGlvbnMoY3VycmVudENvbmZpZ3VyYXRpb25Pck51bGwoKSlcbiAgcHJvY2Vzcy5leGl0KGV4aXRDb2RlKVxufVxuXG4vKipcbiAqIElkcyBvZiBqb2JzIGN1cnJlbnRseSBydW5uaW5nIGluIHRoaXMgY2hpbGQuIEEgcG9vbGVkIGNoaWxkIHJ1bnMgdXAgdG9cbiAqIGBwb29sZWRSdW5uZXJDb25jdXJyZW5jeWAgam9icyBhdCBvbmNlICh0aGUgd29ya2VyIG9ubHkgZGlzcGF0Y2hlcyB3aXRoaW4gdGhhdFxuICogYm91bmQpOyB0aGUgc2V0IGRlZHVwZXMgYSByZWRlbGl2ZXJlZCBqb2IgaWQgYW5kIGxldHMgZWFjaCBqb2Igc2V0dGxlXG4gKiBpbmRlcGVuZGVudGx5LlxuICogQHR5cGUge1NldDxzdHJpbmc+fVxuICovXG5jb25zdCBydW5uaW5nSm9iSWRzID0gbmV3IFNldCgpXG5cbi8qKlxuICogU2V0cyBhbiBhZ2dyZWdhdGUgcHJvY2VzcyB0aXRsZSBmcm9tIHRoZSBjdXJyZW50IGluLWZsaWdodCBjb3VudC4gQSBjaGlsZCBydW5zXG4gKiBqb2JzIGNvbmN1cnJlbnRseSwgc28gYSBwZXItam9iIHRpdGxlICh3aGljaCBgcnVuSm9iUGF5bG9hZGAgd291bGQgc25hcHNob3QgYW5kXG4gKiByZXN0b3JlIGFyb3VuZCBhIHNpbmdsZSBqb2IpIGNhbm5vdCByZXByZXNlbnQgdGhlIHByb2Nlc3Mg4oCUIGludGVybGVhdmVkXG4gKiBjb21wbGV0aW9ucyB3b3VsZCBsZWF2ZSBhIHN0YWxlIGxhYmVsLiBSZWNvbXB1dGluZyBmcm9tIGBydW5uaW5nSm9iSWRzLnNpemVgIGlzXG4gKiBjb25jdXJyZW5jeS1zYWZlIGFuZCBob25lc3Q6IGBwc2AvYHRvcGAgc2hvdyBob3cgbWFueSBqb2JzIHRoZSBjaGlsZCBpcyBydW5uaW5nLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIHVwZGF0ZVByb2Nlc3NUaXRsZSgpIHtcbiAgY29uc3QgY291bnQgPSBydW5uaW5nSm9iSWRzLnNpemVcblxuICBwcm9jZXNzLnRpdGxlID0gY291bnQgPiAwID8gYCR7QkFTRV9QUk9DRVNTX1RJVExFfTogJHtjb3VudH0gJHtjb3VudCA9PT0gMSA/IFwiam9iXCIgOiBcImpvYnNcIn1gIDogQkFTRV9QUk9DRVNTX1RJVExFXG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgYW4gSVBDIHZhbHVlIGlzIGEgcnVubmFibGUgcG9vbGVkIGpvYiBtZXNzYWdlLlxuICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gbWVzc2FnZSAtIElQQyBtZXNzYWdlLlxuICogQHJldHVybnMge21lc3NhZ2UgaXMge3R5cGU6IFwiam9iXCIsIHBheWxvYWQ6IGltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlBheWxvYWQgJiB7aWQ6IHN0cmluZ319fSAtIFdoZXRoZXIgdGhpcyBpcyBhIHZhbGlkIGpvYiBtZXNzYWdlLlxuICovXG5mdW5jdGlvbiBpc0pvYk1lc3NhZ2UobWVzc2FnZSkge1xuICBpZiAoIW1lc3NhZ2UgfHwgdHlwZW9mIG1lc3NhZ2UgIT09IFwib2JqZWN0XCIpIHJldHVybiBmYWxzZVxuICBjb25zdCByZWNvcmQgPSAvKiogQHR5cGUge3t0eXBlPzogUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4sIHBheWxvYWQ/OiBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn19ICovIChtZXNzYWdlKVxuXG4gIHJldHVybiByZWNvcmQudHlwZSA9PT0gXCJqb2JcIiAmJiAhIXJlY29yZC5wYXlsb2FkICYmIHR5cGVvZiByZWNvcmQucGF5bG9hZCA9PT0gXCJvYmplY3RcIiAmJiB0eXBlb2YgcmVjb3JkLnBheWxvYWQuaWQgPT09IFwic3RyaW5nXCJcbn1cblxuLyoqXG4gKiBTZW5kcyB0aGUgdGVybWluYWwgb3V0Y29tZSBhZnRlciB0aGUgbWFpbi9EQiByZXBvcnQgaGFzIGJlZW4gYWNrbm93bGVkZ2VkIG9yIHJlamVjdGVkLlxuICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPdXRjb21lLlxuICogQHBhcmFtIHtzdHJpbmd9IGFyZ3Muam9iSWQgLSBKb2IgaWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IGFyZ3MuYWNrbm93bGVkZ2VkIC0gV2hldGhlciB0aGUgdGVybWluYWwgcmVwb3J0IHdhcyBhY2tub3dsZWRnZWQuXG4gKiBAcGFyYW0ge1wiY29tcGxldGVkXCIgfCBcImZhaWxlZFwiIHwgXCJyZXNjaGVkdWxlZFwifSBbYXJncy5zdGF0dXNdIC0gQWNrbm93bGVkZ2VkIG91dGNvbWUuXG4gKiBAcGFyYW0ge0Vycm9yfSBbYXJncy5lcnJvcl0gLSBSZXBvcnRpbmcgZXJyb3Igd2hlbiBhY2tub3dsZWRnZW1lbnQgd2FzIG5vdCBvYnRhaW5lZC5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIElQQyBhY2NlcHRzIHRoZSBtZXNzYWdlLlxuICovXG5mdW5jdGlvbiBzZW5kT3V0Y29tZSh7am9iSWQsIGFja25vd2xlZGdlZCwgc3RhdHVzLCBlcnJvcn0pIHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7XG4gICAgaWYgKCFwcm9jZXNzLnNlbmQpIHtcbiAgICAgIHJlc29sdmUodW5kZWZpbmVkKVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgcHJvY2Vzcy5zZW5kKHtcbiAgICAgIHR5cGU6IFwiam9iLW91dGNvbWVcIixcbiAgICAgIGpvYklkLFxuICAgICAgYWNrbm93bGVkZ2VkLFxuICAgICAgc3RhdHVzLFxuICAgICAgcnNzQnl0ZXM6IHByb2Nlc3MubWVtb3J5VXNhZ2UoKS5yc3MsXG4gICAgICBlcnJvcjogZXJyb3I/Lm1lc3NhZ2VcbiAgICB9LCAoKSA9PiByZXNvbHZlKHVuZGVmaW5lZCkpXG4gIH0pXG59XG5cbi8qKlxuICogUnVucyBvbmUgam9iIGNvbmN1cnJlbnRseSB3aXRoIGFueSBzaWJsaW5ncyBhbmQgcmVwb3J0cyBpdHMgb3duIHRlcm1pbmFsXG4gKiBvdXRjb21lLiBBIHNpbmdsZSBqb2IncyB1bmV4cGVjdGVkIGZhaWx1cmUgcmVwb3J0cyB0aGF0IGpvYiBmb3IgcmVjbGFtYXRpb25cbiAqIChgYWNrbm93bGVkZ2VkOiBmYWxzZWApIGJ1dCBkb2VzIE5PVCB0YWtlIGRvd24gdGhlIGNoaWxkIOKAlCBpdHMgY29uY3VycmVudFxuICogc2libGluZ3Mga2VlcCBydW5uaW5nLiBPbmx5IGEgcHJvY2Vzcy1sZXZlbCBmYXVsdCAod2hpY2ggZXNjYXBlcyBldmVyeVxuICogcGVyLWpvYiB0cnkvY2F0Y2gpIGVuZHMgdGhlIGNoaWxkLCB3aGljaCB0aGUgd29ya2VyIHNlZXMgYXMgYW4gZXhpdCBhbmRcbiAqIHJlY2xhaW1zIGZvciB0aGUgd2hvbGUgaW4tZmxpZ2h0IHNldC5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUGF5bG9hZCAmIHtpZDogc3RyaW5nfX0gcGF5bG9hZCAtIEpvYiBwYXlsb2FkLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgcmVwb3J0aW5nLlxuICovXG5hc3luYyBmdW5jdGlvbiBydW5Kb2IocGF5bG9hZCkge1xuICB0cnkge1xuICAgIGNvbnN0IHN0YXR1cyA9IGF3YWl0IHJ1bkpvYlBheWxvYWQocGF5bG9hZCwge2Nsb3NlQ29ubmVjdGlvbnM6IGZhbHNlLCBtYW5hZ2VQcm9jZXNzVGl0bGU6IGZhbHNlfSlcbiAgICBhd2FpdCBzZW5kT3V0Y29tZSh7am9iSWQ6IHBheWxvYWQuaWQsIGFja25vd2xlZGdlZDogdHJ1ZSwgc3RhdHVzfSlcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBCYWNrZ3JvdW5kSm9iUGVyZm9ybWVkRmFpbHVyZSkge1xuICAgICAgYXdhaXQgc2VuZE91dGNvbWUoe2pvYklkOiBwYXlsb2FkLmlkLCBhY2tub3dsZWRnZWQ6IHRydWUsIHN0YXR1czogXCJmYWlsZWRcIn0pXG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbnN0IHJlcG9ydEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gICAgICBjb25zb2xlLmVycm9yKFwiUG9vbGVkIGJhY2tncm91bmQgam9iIHJ1bm5lciBmYWlsZWQgYmVmb3JlIHRlcm1pbmFsIGFja25vd2xlZGdlbWVudDpcIiwgcmVwb3J0RXJyb3IpXG4gICAgICBhd2FpdCBzZW5kT3V0Y29tZSh7am9iSWQ6IHBheWxvYWQuaWQsIGFja25vd2xlZGdlZDogZmFsc2UsIGVycm9yOiByZXBvcnRFcnJvcn0pXG4gICAgfVxuICB9IGZpbmFsbHkge1xuICAgIHJ1bm5pbmdKb2JJZHMuZGVsZXRlKHBheWxvYWQuaWQpXG4gICAgdXBkYXRlUHJvY2Vzc1RpdGxlKClcbiAgfVxufVxuXG4vKipcbiAqIEhhbmRsZXMgYSBqb2IgbWVzc2FnZSwgc3RhcnRpbmcgaXQgYWxvbmdzaWRlIGFueSBjb25jdXJyZW50IHNpYmxpbmdzLlxuICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gbWVzc2FnZSAtIElQQyBtZXNzYWdlLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIGhhbmRsZU1lc3NhZ2UobWVzc2FnZSkge1xuICBpZiAoIWlzSm9iTWVzc2FnZShtZXNzYWdlKSB8fCBydW5uaW5nSm9iSWRzLmhhcyhtZXNzYWdlLnBheWxvYWQuaWQpKSByZXR1cm5cblxuICBydW5uaW5nSm9iSWRzLmFkZChtZXNzYWdlLnBheWxvYWQuaWQpXG4gIHVwZGF0ZVByb2Nlc3NUaXRsZSgpXG4gIHZvaWQgcnVuSm9iKG1lc3NhZ2UucGF5bG9hZClcbn1cblxucHJvY2Vzcy5vbihcIm1lc3NhZ2VcIiwgKG1lc3NhZ2UpID0+IGhhbmRsZU1lc3NhZ2UobWVzc2FnZSkpXG5wcm9jZXNzLm9uY2UoXCJkaXNjb25uZWN0XCIsICgpID0+IHZvaWQgc2h1dGRvd25SdW5uZXIoMCkpXG5mb3IgKGNvbnN0IHNpZ25hbCBvZiBbXCJTSUdURVJNXCIsIFwiU0lHSU5UXCJdKSBwcm9jZXNzLm9uY2Uoc2lnbmFsLCAoKSA9PiB2b2lkIHNodXRkb3duUnVubmVyKDEpKVxuIl19
134
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbGVkLXJ1bm5lci1jaGlsZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvcG9vbGVkLXJ1bm5lci1jaGlsZC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxhQUFhLEVBQUUsRUFBRSw2QkFBNkIsRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzlFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLCtCQUErQixDQUFBO0FBQ2xHLE9BQU8scUJBQXFCLE1BQU0sMkJBQTJCLENBQUE7QUFDN0QsT0FBTywwQkFBMEIsTUFBTSxvQ0FBb0MsQ0FBQTtBQUMzRSxPQUFPLEVBQUUsb0NBQW9DLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQTtBQUVwRyxNQUFNLGtCQUFrQixHQUFHLGtDQUFrQyxDQUFBO0FBRTdELHFCQUFxQixFQUFFLENBQUE7QUFFdkIsSUFBSSxZQUFZLEdBQUcsS0FBSyxDQUFBO0FBRXhCOzs7Ozs7R0FNRztBQUNILEtBQUssVUFBVSxjQUFjLENBQUMsUUFBUTtJQUNwQyxJQUFJLFlBQVk7UUFBRSxPQUFNO0lBQ3hCLFlBQVksR0FBRyxJQUFJLENBQUE7SUFFbkIsTUFBTSxzQkFBc0IsQ0FBQywwQkFBMEIsRUFBRSxDQUFDLENBQUE7SUFDMUQsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQTtBQUN4QixDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxhQUFhLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQTtBQUMvQixNQUFNLGNBQWMsR0FBRyxJQUFJLDBCQUEwQixDQUFDO0lBQ3BELGdCQUFnQixFQUFFLEtBQUssSUFBSSxFQUFFLENBQUMsTUFBTSxzQkFBc0IsQ0FBQywwQkFBMEIsRUFBRSxDQUFDO0NBQ3pGLENBQUMsQ0FBQTtBQUVGOzs7Ozs7O0dBT0c7QUFDSCxTQUFTLGtCQUFrQjtJQUN6QixNQUFNLEtBQUssR0FBRyxhQUFhLENBQUMsSUFBSSxDQUFBO0lBRWhDLE9BQU8sQ0FBQyxLQUFLLEdBQUcsS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxrQkFBa0IsS0FBSyxLQUFLLElBQUksS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsa0JBQWtCLENBQUE7QUFDcEgsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUFTLFlBQVksQ0FBQyxPQUFPO0lBQzNCLElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUTtRQUFFLE9BQU8sS0FBSyxDQUFBO0lBQ3pELE1BQU0sTUFBTSxHQUFHLHVKQUF1SixDQUFDLENBQUMsT0FBTyxDQUFDLENBQUE7SUFFaEwsT0FBTyxNQUFNLENBQUMsSUFBSSxLQUFLLEtBQUssSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sSUFBSSxPQUFPLE1BQU0sQ0FBQyxPQUFPLEtBQUssUUFBUSxJQUFJLE9BQU8sTUFBTSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEtBQUssUUFBUSxDQUFBO0FBQ2pJLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILFNBQVMsV0FBVyxDQUFDLEVBQUMsS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFDO0lBQ3ZELE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRTtRQUM3QixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ2xCLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQTtZQUNsQixPQUFNO1FBQ1IsQ0FBQztRQUVELE9BQU8sQ0FBQyxJQUFJLENBQUM7WUFDWCxJQUFJLEVBQUUsYUFBYTtZQUNuQixLQUFLO1lBQ0wsWUFBWTtZQUNaLE1BQU07WUFDTixRQUFRLEVBQUUsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDLEdBQUc7WUFDbkMsS0FBSyxFQUFFLEtBQUssRUFBRSxPQUFPO1NBQ3RCLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUE7SUFDOUIsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQ7Ozs7Ozs7Ozs7R0FVRztBQUNILEtBQUssVUFBVSxNQUFNLENBQUMsT0FBTyxFQUFFLHVCQUF1QjtJQUNwRCxJQUFJLENBQUM7UUFDSCxNQUFNLE1BQU0sR0FBRyxNQUFNLG9DQUFvQyxDQUFDLHVCQUF1QixFQUFFLEtBQUssSUFBSSxFQUFFO1lBQzVGLE9BQU8sTUFBTSxjQUFjLENBQUMsR0FBRyxDQUFDLHVCQUF1QixFQUFFLEtBQUssSUFBSSxFQUFFO2dCQUNsRSxPQUFPLE1BQU0sYUFBYSxDQUFDLE9BQU8sRUFBRSxFQUFDLGdCQUFnQixFQUFFLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxLQUFLLEVBQUMsQ0FBQyxDQUFBO1lBQzNGLENBQUMsQ0FBQyxDQUFBO1FBQ0osQ0FBQyxDQUFDLENBQUE7UUFDRixNQUFNLFdBQVcsQ0FBQyxFQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsRUFBRSxFQUFFLFlBQVksRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFDLENBQUMsQ0FBQTtJQUNwRSxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLElBQUksS0FBSyxZQUFZLDZCQUE2QixFQUFFLENBQUM7WUFDbkQsTUFBTSxXQUFXLENBQUMsRUFBQyxLQUFLLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUMsQ0FBQyxDQUFBO1FBQzlFLENBQUM7YUFBTSxDQUFDO1lBQ04sTUFBTSxXQUFXLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQTtZQUM3RSxPQUFPLENBQUMsS0FBSyxDQUFDLHNFQUFzRSxFQUFFLFdBQVcsQ0FBQyxDQUFBO1lBQ2xHLE1BQU0sV0FBVyxDQUFDLEVBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxFQUFFLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFDLENBQUMsQ0FBQTtRQUNqRixDQUFDO0lBQ0gsQ0FBQztZQUFTLENBQUM7UUFDVCxhQUFhLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQTtRQUNoQyxrQkFBa0IsRUFBRSxDQUFBO0lBQ3RCLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQVMsYUFBYSxDQUFDLE9BQU87SUFDNUIsSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsSUFBSSxhQUFhLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQUUsT0FBTTtJQUUzRSxhQUFhLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLENBQUE7SUFDckMsa0JBQWtCLEVBQUUsQ0FBQTtJQUNwQixLQUFLLE1BQU0sQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyx1QkFBdUIsSUFBSSxFQUFDLFFBQVEsRUFBRSxLQUFLLEVBQUMsQ0FBQyxDQUFBO0FBQ3BGLENBQUM7QUFFRCxPQUFPLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUE7QUFDMUQsT0FBTyxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsR0FBRyxFQUFFLENBQUMsS0FBSyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUN4RCxLQUFLLE1BQU0sTUFBTSxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQztJQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IHJ1bkpvYlBheWxvYWQsIHsgQmFja2dyb3VuZEpvYlBlcmZvcm1lZEZhaWx1cmUgfSBmcm9tIFwiLi9qb2ItcnVubmVyLmpzXCJcbmltcG9ydCB7IGNsb3NlUnVubmVyQ29ubmVjdGlvbnMsIGN1cnJlbnRDb25maWd1cmF0aW9uT3JOdWxsIH0gZnJvbSBcIi4vcnVubmVyLWdyYWNlZnVsLXNodXRkb3duLmpzXCJcbmltcG9ydCBzZXRSdW5uZXJQcm9jZXNzVGl0bGUgZnJvbSBcIi4vcnVubmVyLXByb2Nlc3MtdGl0bGUuanNcIlxuaW1wb3J0IFBvb2xlZFJ1bm5lckJyb2tlcklkZW50aXR5IGZyb20gXCIuL3Bvb2xlZC1ydW5uZXItYnJva2VyLWlkZW50aXR5LmpzXCJcbmltcG9ydCB7IHJ1bldpdGhTaGFyZWRUcmFuc2FjdGlvbkJyb2tlckNvbmZpZyB9IGZyb20gXCIuLi90ZXN0aW5nL3NoYXJlZC10cmFuc2FjdGlvbi1wcm94eS1kcml2ZXIuanNcIlxuXG5jb25zdCBCQVNFX1BST0NFU1NfVElUTEUgPSBcInZlbG9jaW91cyBiYWNrZ3JvdW5kLWpvYnMtcnVubmVyXCJcblxuc2V0UnVubmVyUHJvY2Vzc1RpdGxlKClcblxubGV0IHNodXR0aW5nRG93biA9IGZhbHNlXG5cbi8qKlxuICogQ2xvc2VzIHRoZSBydW5uZXIncyBjb25uZWN0aW9ucyDigJQgcmVsZWFzaW5nIGFueSBhZHZpc29yeSBsb2NrIGEga2lsbGVkLW1pZC1wYXNzXG4gKiBqb2Igc3RpbGwgaG9sZHMg4oCUIGJlZm9yZSBleGl0aW5nLCBpbnN0ZWFkIG9mIGxlYXZpbmcgYSBoYWxmLW9wZW4gc2Vzc2lvbiB0aGF0XG4gKiBrZWVwcyB0aGUgbG9jayB1bnRpbCB0aGUgREIgc2VydmVyJ3MgYHdhaXRfdGltZW91dGAuXG4gKiBAcGFyYW0ge251bWJlcn0gZXhpdENvZGUgLSBQcm9jZXNzIGV4aXQgY29kZS5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICovXG5hc3luYyBmdW5jdGlvbiBzaHV0ZG93blJ1bm5lcihleGl0Q29kZSkge1xuICBpZiAoc2h1dHRpbmdEb3duKSByZXR1cm5cbiAgc2h1dHRpbmdEb3duID0gdHJ1ZVxuXG4gIGF3YWl0IGNsb3NlUnVubmVyQ29ubmVjdGlvbnMoY3VycmVudENvbmZpZ3VyYXRpb25Pck51bGwoKSlcbiAgcHJvY2Vzcy5leGl0KGV4aXRDb2RlKVxufVxuXG4vKipcbiAqIElkcyBvZiBqb2JzIGN1cnJlbnRseSBydW5uaW5nIGluIHRoaXMgY2hpbGQuIEEgcG9vbGVkIGNoaWxkIHJ1bnMgdXAgdG9cbiAqIGBwb29sZWRSdW5uZXJDb25jdXJyZW5jeWAgam9icyBhdCBvbmNlICh0aGUgd29ya2VyIG9ubHkgZGlzcGF0Y2hlcyB3aXRoaW4gdGhhdFxuICogYm91bmQpOyB0aGUgc2V0IGRlZHVwZXMgYSByZWRlbGl2ZXJlZCBqb2IgaWQgYW5kIGxldHMgZWFjaCBqb2Igc2V0dGxlXG4gKiBpbmRlcGVuZGVudGx5LlxuICogQHR5cGUge1NldDxzdHJpbmc+fVxuICovXG5jb25zdCBydW5uaW5nSm9iSWRzID0gbmV3IFNldCgpXG5jb25zdCBicm9rZXJJZGVudGl0eSA9IG5ldyBQb29sZWRSdW5uZXJCcm9rZXJJZGVudGl0eSh7XG4gIGNsb3NlQ29ubmVjdGlvbnM6IGFzeW5jICgpID0+IGF3YWl0IGNsb3NlUnVubmVyQ29ubmVjdGlvbnMoY3VycmVudENvbmZpZ3VyYXRpb25Pck51bGwoKSlcbn0pXG5cbi8qKlxuICogU2V0cyBhbiBhZ2dyZWdhdGUgcHJvY2VzcyB0aXRsZSBmcm9tIHRoZSBjdXJyZW50IGluLWZsaWdodCBjb3VudC4gQSBjaGlsZCBydW5zXG4gKiBqb2JzIGNvbmN1cnJlbnRseSwgc28gYSBwZXItam9iIHRpdGxlICh3aGljaCBgcnVuSm9iUGF5bG9hZGAgd291bGQgc25hcHNob3QgYW5kXG4gKiByZXN0b3JlIGFyb3VuZCBhIHNpbmdsZSBqb2IpIGNhbm5vdCByZXByZXNlbnQgdGhlIHByb2Nlc3Mg4oCUIGludGVybGVhdmVkXG4gKiBjb21wbGV0aW9ucyB3b3VsZCBsZWF2ZSBhIHN0YWxlIGxhYmVsLiBSZWNvbXB1dGluZyBmcm9tIGBydW5uaW5nSm9iSWRzLnNpemVgIGlzXG4gKiBjb25jdXJyZW5jeS1zYWZlIGFuZCBob25lc3Q6IGBwc2AvYHRvcGAgc2hvdyBob3cgbWFueSBqb2JzIHRoZSBjaGlsZCBpcyBydW5uaW5nLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIHVwZGF0ZVByb2Nlc3NUaXRsZSgpIHtcbiAgY29uc3QgY291bnQgPSBydW5uaW5nSm9iSWRzLnNpemVcblxuICBwcm9jZXNzLnRpdGxlID0gY291bnQgPiAwID8gYCR7QkFTRV9QUk9DRVNTX1RJVExFfTogJHtjb3VudH0gJHtjb3VudCA9PT0gMSA/IFwiam9iXCIgOiBcImpvYnNcIn1gIDogQkFTRV9QUk9DRVNTX1RJVExFXG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgYW4gSVBDIHZhbHVlIGlzIGEgcnVubmFibGUgcG9vbGVkIGpvYiBtZXNzYWdlLlxuICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gbWVzc2FnZSAtIElQQyBtZXNzYWdlLlxuICogQHJldHVybnMge21lc3NhZ2UgaXMge3R5cGU6IFwiam9iXCIsIHBheWxvYWQ6IGltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlBheWxvYWQgJiB7aWQ6IHN0cmluZ30sIHNoYXJlZFRyYW5zYWN0aW9uQnJva2VyPzogaW1wb3J0KFwiLi4vdGVzdGluZy9zaGFyZWQtdHJhbnNhY3Rpb24tcHJveHktZHJpdmVyLmpzXCIpLlNoYXJlZFRyYW5zYWN0aW9uQnJva2VySm9iQ29uZmlnfX0gLSBXaGV0aGVyIHRoaXMgaXMgYSB2YWxpZCBqb2IgbWVzc2FnZS5cbiAqL1xuZnVuY3Rpb24gaXNKb2JNZXNzYWdlKG1lc3NhZ2UpIHtcbiAgaWYgKCFtZXNzYWdlIHx8IHR5cGVvZiBtZXNzYWdlICE9PSBcIm9iamVjdFwiKSByZXR1cm4gZmFsc2VcbiAgY29uc3QgcmVjb3JkID0gLyoqIEB0eXBlIHt7dHlwZT86IFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+LCBwYXlsb2FkPzogUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4sIHNoYXJlZFRyYW5zYWN0aW9uQnJva2VyPzogUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59fSAqLyAobWVzc2FnZSlcblxuICByZXR1cm4gcmVjb3JkLnR5cGUgPT09IFwiam9iXCIgJiYgISFyZWNvcmQucGF5bG9hZCAmJiB0eXBlb2YgcmVjb3JkLnBheWxvYWQgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIHJlY29yZC5wYXlsb2FkLmlkID09PSBcInN0cmluZ1wiXG59XG5cbi8qKlxuICogU2VuZHMgdGhlIHRlcm1pbmFsIG91dGNvbWUgYWZ0ZXIgdGhlIG1haW4vREIgcmVwb3J0IGhhcyBiZWVuIGFja25vd2xlZGdlZCBvciByZWplY3RlZC5cbiAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3V0Y29tZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmpvYklkIC0gSm9iIGlkLlxuICogQHBhcmFtIHtib29sZWFufSBhcmdzLmFja25vd2xlZGdlZCAtIFdoZXRoZXIgdGhlIHRlcm1pbmFsIHJlcG9ydCB3YXMgYWNrbm93bGVkZ2VkLlxuICogQHBhcmFtIHtcImNvbXBsZXRlZFwiIHwgXCJmYWlsZWRcIiB8IFwicmVzY2hlZHVsZWRcIn0gW2FyZ3Muc3RhdHVzXSAtIEFja25vd2xlZGdlZCBvdXRjb21lLlxuICogQHBhcmFtIHtFcnJvcn0gW2FyZ3MuZXJyb3JdIC0gUmVwb3J0aW5nIGVycm9yIHdoZW4gYWNrbm93bGVkZ2VtZW50IHdhcyBub3Qgb2J0YWluZWQuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciBJUEMgYWNjZXB0cyB0aGUgbWVzc2FnZS5cbiAqL1xuZnVuY3Rpb24gc2VuZE91dGNvbWUoe2pvYklkLCBhY2tub3dsZWRnZWQsIHN0YXR1cywgZXJyb3J9KSB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgIGlmICghcHJvY2Vzcy5zZW5kKSB7XG4gICAgICByZXNvbHZlKHVuZGVmaW5lZClcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHByb2Nlc3Muc2VuZCh7XG4gICAgICB0eXBlOiBcImpvYi1vdXRjb21lXCIsXG4gICAgICBqb2JJZCxcbiAgICAgIGFja25vd2xlZGdlZCxcbiAgICAgIHN0YXR1cyxcbiAgICAgIHJzc0J5dGVzOiBwcm9jZXNzLm1lbW9yeVVzYWdlKCkucnNzLFxuICAgICAgZXJyb3I6IGVycm9yPy5tZXNzYWdlXG4gICAgfSwgKCkgPT4gcmVzb2x2ZSh1bmRlZmluZWQpKVxuICB9KVxufVxuXG4vKipcbiAqIFJ1bnMgb25lIGpvYiBjb25jdXJyZW50bHkgd2l0aCBhbnkgc2libGluZ3MgYW5kIHJlcG9ydHMgaXRzIG93biB0ZXJtaW5hbFxuICogb3V0Y29tZS4gQSBzaW5nbGUgam9iJ3MgdW5leHBlY3RlZCBmYWlsdXJlIHJlcG9ydHMgdGhhdCBqb2IgZm9yIHJlY2xhbWF0aW9uXG4gKiAoYGFja25vd2xlZGdlZDogZmFsc2VgKSBidXQgZG9lcyBOT1QgdGFrZSBkb3duIHRoZSBjaGlsZCDigJQgaXRzIGNvbmN1cnJlbnRcbiAqIHNpYmxpbmdzIGtlZXAgcnVubmluZy4gT25seSBhIHByb2Nlc3MtbGV2ZWwgZmF1bHQgKHdoaWNoIGVzY2FwZXMgZXZlcnlcbiAqIHBlci1qb2IgdHJ5L2NhdGNoKSBlbmRzIHRoZSBjaGlsZCwgd2hpY2ggdGhlIHdvcmtlciBzZWVzIGFzIGFuIGV4aXQgYW5kXG4gKiByZWNsYWltcyBmb3IgdGhlIHdob2xlIGluLWZsaWdodCBzZXQuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlBheWxvYWQgJiB7aWQ6IHN0cmluZ319IHBheWxvYWQgLSBKb2IgcGF5bG9hZC5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vdGVzdGluZy9zaGFyZWQtdHJhbnNhY3Rpb24tcHJveHktZHJpdmVyLmpzXCIpLlNoYXJlZFRyYW5zYWN0aW9uQnJva2VySm9iQ29uZmlnfSBzaGFyZWRUcmFuc2FjdGlvbkJyb2tlciAtIFBlci1qb2IgYnJva2VyIGNvbmZpZ3VyYXRpb24uXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciByZXBvcnRpbmcuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJ1bkpvYihwYXlsb2FkLCBzaGFyZWRUcmFuc2FjdGlvbkJyb2tlcikge1xuICB0cnkge1xuICAgIGNvbnN0IHN0YXR1cyA9IGF3YWl0IHJ1bldpdGhTaGFyZWRUcmFuc2FjdGlvbkJyb2tlckNvbmZpZyhzaGFyZWRUcmFuc2FjdGlvbkJyb2tlciwgYXN5bmMgKCkgPT4ge1xuICAgICAgcmV0dXJuIGF3YWl0IGJyb2tlcklkZW50aXR5LnJ1bihzaGFyZWRUcmFuc2FjdGlvbkJyb2tlciwgYXN5bmMgKCkgPT4ge1xuICAgICAgICByZXR1cm4gYXdhaXQgcnVuSm9iUGF5bG9hZChwYXlsb2FkLCB7Y2xvc2VDb25uZWN0aW9uczogZmFsc2UsIG1hbmFnZVByb2Nlc3NUaXRsZTogZmFsc2V9KVxuICAgICAgfSlcbiAgICB9KVxuICAgIGF3YWl0IHNlbmRPdXRjb21lKHtqb2JJZDogcGF5bG9hZC5pZCwgYWNrbm93bGVkZ2VkOiB0cnVlLCBzdGF0dXN9KVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlKSB7XG4gICAgICBhd2FpdCBzZW5kT3V0Y29tZSh7am9iSWQ6IHBheWxvYWQuaWQsIGFja25vd2xlZGdlZDogdHJ1ZSwgc3RhdHVzOiBcImZhaWxlZFwifSlcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgcmVwb3J0RXJyb3IgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKGVycm9yKSlcbiAgICAgIGNvbnNvbGUuZXJyb3IoXCJQb29sZWQgYmFja2dyb3VuZCBqb2IgcnVubmVyIGZhaWxlZCBiZWZvcmUgdGVybWluYWwgYWNrbm93bGVkZ2VtZW50OlwiLCByZXBvcnRFcnJvcilcbiAgICAgIGF3YWl0IHNlbmRPdXRjb21lKHtqb2JJZDogcGF5bG9hZC5pZCwgYWNrbm93bGVkZ2VkOiBmYWxzZSwgZXJyb3I6IHJlcG9ydEVycm9yfSlcbiAgICB9XG4gIH0gZmluYWxseSB7XG4gICAgcnVubmluZ0pvYklkcy5kZWxldGUocGF5bG9hZC5pZClcbiAgICB1cGRhdGVQcm9jZXNzVGl0bGUoKVxuICB9XG59XG5cbi8qKlxuICogSGFuZGxlcyBhIGpvYiBtZXNzYWdlLCBzdGFydGluZyBpdCBhbG9uZ3NpZGUgYW55IGNvbmN1cnJlbnQgc2libGluZ3MuXG4gKiBAcGFyYW0ge1JldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+fSBtZXNzYWdlIC0gSVBDIG1lc3NhZ2UuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqL1xuZnVuY3Rpb24gaGFuZGxlTWVzc2FnZShtZXNzYWdlKSB7XG4gIGlmICghaXNKb2JNZXNzYWdlKG1lc3NhZ2UpIHx8IHJ1bm5pbmdKb2JJZHMuaGFzKG1lc3NhZ2UucGF5bG9hZC5pZCkpIHJldHVyblxuXG4gIHJ1bm5pbmdKb2JJZHMuYWRkKG1lc3NhZ2UucGF5bG9hZC5pZClcbiAgdXBkYXRlUHJvY2Vzc1RpdGxlKClcbiAgdm9pZCBydW5Kb2IobWVzc2FnZS5wYXlsb2FkLCBtZXNzYWdlLnNoYXJlZFRyYW5zYWN0aW9uQnJva2VyIHx8IHtleHBlY3RlZDogZmFsc2V9KVxufVxuXG5wcm9jZXNzLm9uKFwibWVzc2FnZVwiLCAobWVzc2FnZSkgPT4gaGFuZGxlTWVzc2FnZShtZXNzYWdlKSlcbnByb2Nlc3Mub25jZShcImRpc2Nvbm5lY3RcIiwgKCkgPT4gdm9pZCBzaHV0ZG93blJ1bm5lcigwKSlcbmZvciAoY29uc3Qgc2lnbmFsIG9mIFtcIlNJR1RFUk1cIiwgXCJTSUdJTlRcIl0pIHByb2Nlc3Mub25jZShzaWduYWwsICgpID0+IHZvaWQgc2h1dGRvd25SdW5uZXIoMSkpXG4iXX0=