velocious 1.0.529 → 1.0.530

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 CHANGED
@@ -1977,6 +1977,7 @@ export default new Configuration({
1977
1977
  maxConcurrentForkedJobs: 4,
1978
1978
  maxConcurrentInlineJobs: 4,
1979
1979
  pooledRunnerCount: 4,
1980
+ pooledRunnerConcurrency: 1,
1980
1981
  pooledRunnerMaxJobs: 100,
1981
1982
  pooledRunnerMaxRssBytes: 536870912,
1982
1983
  pooledRunnerMaxLifetimeMs: 3600000,
@@ -2008,7 +2009,7 @@ VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS=5400000
2008
2009
 
2009
2010
  `maxConcurrentInlineJobs` (default: `4`) caps how many `executionMode: "inline"` jobs a single `background-jobs-worker` process runs in parallel. Concurrency is at the JS event-loop level: every job in flight shares the worker's process and DB connection pool, so the cap should fit the pool, not the CPU count. Forking remains the right tool when you need memory isolation across long-running jobs or want to use more cores. The older `forked: false` option still maps to inline mode.
2010
2011
 
2011
- New jobs default to `executionMode: "pooled"`: a worker runs them serially in warm, reusable Node child runners. `pooledRunnerCount` (default: `4`) bounds this independent per-worker capacity. It and `pooledRunnerMaxJobs` must be finite positive integers; the RSS and lifetime limits must be finite positive numbers. A child is recycled after an acknowledged job when it reaches `pooledRunnerMaxJobs` (default: `100`), `pooledRunnerMaxRssBytes` (default: `536870912`, or 512 MiB), or `pooledRunnerMaxLifetimeMs` (default: `3600000`, or one hour). Pooled rows remain readable by older mains during rolling upgrades and run there with legacy one-shot fork behavior. `forked: true` remains an explicit alias for `"forked"`; `forked: false` remains inline. See [execution modes and pooled runners](docs/background-jobs.md#execution-modes-and-pooled-runners).
2012
+ New jobs default to `executionMode: "pooled"`: a worker runs them in warm, reusable Node child runners. `pooledRunnerCount` (default: `4`) bounds this independent per-worker pool, and `pooledRunnerConcurrency` (default: `1`) sets how many jobs each child runs at once on its own event loop, so total pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency` — raise concurrency for I/O-bound jobs to get high throughput from a bounded, isolated set of processes. `pooledRunnerCount`, `pooledRunnerConcurrency`, and `pooledRunnerMaxJobs` must be finite positive integers; the RSS and lifetime limits must be finite positive numbers. A child is recycled after an acknowledged job when it reaches `pooledRunnerMaxJobs` (default: `100`), `pooledRunnerMaxRssBytes` (default: `536870912`, or 512 MiB), or `pooledRunnerMaxLifetimeMs` (default: `3600000`, or one hour). Pooled rows remain readable by older mains during rolling upgrades and run there with legacy one-shot fork behavior. `forked: true` remains an explicit alias for `"forked"`; `forked: false` remains inline. See [execution modes and pooled runners](docs/background-jobs.md#execution-modes-and-pooled-runners).
2012
2013
 
2013
2014
  `maxConcurrentForkedJobs` (default: `4`) caps how many out-of-process `executionMode: "forked"` or `executionMode: "spawned"` jobs one worker may keep in flight. Forked jobs use `child_process.fork()` with an attached IPC channel. After the main process acknowledges their durable status report, forked and spawned one-shot runners exit without waiting for graceful Beacon/database teardown; the OS closes their process-owned resources. A missing or rejected status acknowledgement makes the runner exit as failed instead of reporting clean success. Spawned jobs use the legacy `background-jobs-runner` CLI process via `child_process.spawn()` and are only for callers that intentionally want that spawned behavior.
2014
2015
 
@@ -78,9 +78,10 @@ function runnerProcessTitle(JobClass, payload) {
78
78
  * @param {import("./types.js").BackgroundJobPayload} payload - Payload.
79
79
  * @param {object} [options] - Runner options.
80
80
  * @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
81
+ * @param {boolean} [options.manageProcessTitle] - Whether to set the per-job process title and restore it afterwards. Off for concurrent pooled runners, where interleaved snapshot/restore of the single process-wide `process.title` would corrupt it; the pooled child owns an aggregate title instead.
81
82
  * @returns {Promise<void>} - Resolves when complete.
82
83
  */
83
- export default async function runJobPayload(payload, {closeConnections = true} = {}) {
84
+ export default async function runJobPayload(payload, {closeConnections = true, manageProcessTitle = true} = {}) {
84
85
  const configuration = await configurationResolver()
85
86
  configuration.setCurrent()
86
87
  await configuration.initialize({type: "background-jobs-runner"})
@@ -98,8 +99,9 @@ export default async function runJobPayload(payload, {closeConnections = true} =
98
99
 
99
100
  // Name the process after the job it is running so `ps`/`top` show what each
100
101
  // runner is doing; restored in the `finally` below when the job finishes.
102
+ // Skipped for concurrent pooled runners, whose child owns an aggregate title.
101
103
  const previousTitle = process.title
102
- process.title = runnerProcessTitle(JobClass, payload)
104
+ if (manageProcessTitle) process.title = runnerProcessTitle(JobClass, payload)
103
105
 
104
106
  try {
105
107
  try {
@@ -136,7 +138,7 @@ export default async function runJobPayload(payload, {closeConnections = true} =
136
138
  } finally {
137
139
  // Restore the runner's base title so a lingering/idle runner (or a reused
138
140
  // one) doesn't misreport a finished job as still running.
139
- process.title = previousTitle
141
+ if (manageProcessTitle) process.title = previousTitle
140
142
  if (closeConnections) {
141
143
  try {
142
144
  await configuration.disconnectBeacon()
@@ -3,8 +3,32 @@
3
3
  import runJobPayload, { BackgroundJobPerformedFailure } from "./job-runner.js"
4
4
  import setRunnerProcessTitle from "./runner-process-title.js"
5
5
 
6
+ const BASE_PROCESS_TITLE = "velocious background-jobs-runner"
7
+
6
8
  setRunnerProcessTitle()
7
- let running = false
9
+
10
+ /**
11
+ * Ids of jobs currently running in this child. A pooled child runs up to
12
+ * `pooledRunnerConcurrency` jobs at once (the worker only dispatches within that
13
+ * bound); the set dedupes a redelivered job id and lets each job settle
14
+ * independently.
15
+ * @type {Set<string>}
16
+ */
17
+ const runningJobIds = new Set()
18
+
19
+ /**
20
+ * Sets an aggregate process title from the current in-flight count. A child runs
21
+ * jobs concurrently, so a per-job title (which `runJobPayload` would snapshot and
22
+ * restore around a single job) cannot represent the process — interleaved
23
+ * completions would leave a stale label. Recomputing from `runningJobIds.size` is
24
+ * concurrency-safe and honest: `ps`/`top` show how many jobs the child is running.
25
+ * @returns {void}
26
+ */
27
+ function updateProcessTitle() {
28
+ const count = runningJobIds.size
29
+
30
+ process.title = count > 0 ? `${BASE_PROCESS_TITLE}: ${count} ${count === 1 ? "job" : "jobs"}` : BASE_PROCESS_TITLE
31
+ }
8
32
 
9
33
  /**
10
34
  * Checks whether an IPC value is a runnable pooled job message.
@@ -46,33 +70,46 @@ function sendOutcome({jobId, acknowledged, status, error}) {
46
70
  }
47
71
 
48
72
  /**
49
- * Handles one serial job message.
50
- * @param {?} message - IPC message.
73
+ * Runs one job concurrently with any siblings and reports its own terminal
74
+ * outcome. A single job's unexpected failure reports that job for reclamation
75
+ * (`acknowledged: false`) but does NOT take down the child — its concurrent
76
+ * siblings keep running. Only a process-level fault (which escapes every
77
+ * per-job try/catch) ends the child, which the worker sees as an exit and
78
+ * reclaims for the whole in-flight set.
79
+ * @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
51
80
  * @returns {Promise<void>} - Resolves after reporting.
52
81
  */
53
- async function handleMessage(message) {
54
- if (running || !isJobMessage(message)) return
55
-
56
- running = true
82
+ async function runJob(payload) {
57
83
  try {
58
- await runJobPayload(message.payload, {closeConnections: false})
59
- await sendOutcome({jobId: message.payload.id, acknowledged: true, status: "completed"})
84
+ await runJobPayload(payload, {closeConnections: false, manageProcessTitle: false})
85
+ await sendOutcome({jobId: payload.id, acknowledged: true, status: "completed"})
60
86
  } catch (error) {
61
87
  if (error instanceof BackgroundJobPerformedFailure) {
62
- await sendOutcome({jobId: message.payload.id, acknowledged: true, status: "failed"})
88
+ await sendOutcome({jobId: payload.id, acknowledged: true, status: "failed"})
63
89
  } else {
64
90
  const reportError = error instanceof Error ? error : new Error(String(error))
65
91
  console.error("Pooled background job runner failed before terminal acknowledgement:", reportError)
66
- await sendOutcome({jobId: message.payload.id, acknowledged: false, error: reportError})
67
- process.exitCode = 1
68
- if (process.disconnect) process.disconnect()
69
- setImmediate(() => process.exit(1))
92
+ await sendOutcome({jobId: payload.id, acknowledged: false, error: reportError})
70
93
  }
71
94
  } finally {
72
- running = false
95
+ runningJobIds.delete(payload.id)
96
+ updateProcessTitle()
73
97
  }
74
98
  }
75
99
 
76
- process.on("message", (message) => { void handleMessage(message) })
100
+ /**
101
+ * Handles a job message, starting it alongside any concurrent siblings.
102
+ * @param {?} message - IPC message.
103
+ * @returns {void}
104
+ */
105
+ function handleMessage(message) {
106
+ if (!isJobMessage(message) || runningJobIds.has(message.payload.id)) return
107
+
108
+ runningJobIds.add(message.payload.id)
109
+ updateProcessTitle()
110
+ void runJob(message.payload)
111
+ }
112
+
113
+ process.on("message", (message) => handleMessage(message))
77
114
  process.once("disconnect", () => process.exit(0))
78
115
  for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => process.exit(1))
@@ -66,6 +66,7 @@ export default class BackgroundJobsWorker {
66
66
  * @param {number} [args.maxConcurrentForkedJobs] - Override the process runner concurrency cap from `configuration.getBackgroundJobsConfig()`.
67
67
  * @param {number} [args.maxConcurrentInlineJobs] - Override the inline-job concurrency cap from `configuration.getBackgroundJobsConfig()`.
68
68
  * @param {number} [args.pooledRunnerCount] - Override the pooled runner count.
69
+ * @param {number} [args.pooledRunnerConcurrency] - Override the per-runner concurrency.
69
70
  * @param {number} [args.pooledRunnerMaxJobs] - Override the per-runner recycle job count.
70
71
  * @param {number} [args.pooledRunnerMaxRssBytes] - Override the per-runner recycle RSS limit.
71
72
  * @param {number} [args.pooledRunnerMaxLifetimeMs] - Override the per-runner recycle lifetime.
@@ -73,7 +74,7 @@ export default class BackgroundJobsWorker {
73
74
  * @param {number} [args.heartbeatIntervalMs] - Override the liveness heartbeat interval (default 15000ms).
74
75
  * @param {number} [args.jobTimeoutMs] - Override the forked-job wall-clock timeout from `configuration.getBackgroundJobsConfig()`. `0` disables it.
75
76
  */
76
- constructor({configuration, host, port, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, jobTimeoutMs} = {}) {
77
+ constructor({configuration, host, port, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, jobTimeoutMs} = {}) {
77
78
  /**
78
79
  * Narrows the runtime value to the documented type.
79
80
  * @type {Promise<import("../configuration.js").default>} */
@@ -110,10 +111,12 @@ export default class BackgroundJobsWorker {
110
111
  * @type {number} */
111
112
  this.maxConcurrentForkedJobs = this.maxConcurrentForkedJobsOverride || 4
112
113
  this.pooledRunnerCountOverride = positiveInteger(pooledRunnerCount)
114
+ this.pooledRunnerConcurrencyOverride = positiveInteger(pooledRunnerConcurrency)
113
115
  this.pooledRunnerMaxJobsOverride = positiveInteger(pooledRunnerMaxJobs)
114
116
  this.pooledRunnerMaxRssBytesOverride = positiveNumber(pooledRunnerMaxRssBytes)
115
117
  this.pooledRunnerMaxLifetimeMsOverride = positiveNumber(pooledRunnerMaxLifetimeMs)
116
118
  this.pooledRunnerCount = this.pooledRunnerCountOverride || 4
119
+ this.pooledRunnerConcurrency = this.pooledRunnerConcurrencyOverride || 1
117
120
  this.pooledRunnerMaxJobs = this.pooledRunnerMaxJobsOverride || 100
118
121
  this.pooledRunnerMaxRssBytes = this.pooledRunnerMaxRssBytesOverride || 512 * 1024 * 1024
119
122
  this.pooledRunnerMaxLifetimeMs = this.pooledRunnerMaxLifetimeMsOverride || 60 * 60 * 1000
@@ -183,7 +186,7 @@ export default class BackgroundJobsWorker {
183
186
  this.inflightPooledJobs = new Set()
184
187
  /** @type {Set<import("node:child_process").ChildProcess>} */
185
188
  this.pooledChildren = new Set()
186
- /** @type {Map<import("node:child_process").ChildProcess, {createdAtMs: number, jobsRun: number, payload?: import("./types.js").BackgroundJobPayload & {id: string}, resolve?: (value: void) => void, settling?: boolean}>} */
189
+ /** @type {Map<import("node:child_process").ChildProcess, {createdAtMs: number, jobsRun: number, inflight: Map<string, {payload: import("./types.js").BackgroundJobPayload & {id: string}, resolve?: (value: void) => void}>, retiring: boolean, settling?: boolean}>} */
187
190
  this.pooledChildStates = new Map()
188
191
  }
189
192
 
@@ -210,6 +213,7 @@ export default class BackgroundJobsWorker {
210
213
  }
211
214
  const poolConfig = this.configuration.getBackgroundJobsConfig()
212
215
  if (typeof this.pooledRunnerCountOverride !== "number") this.pooledRunnerCount = poolConfig.pooledRunnerCount
216
+ if (typeof this.pooledRunnerConcurrencyOverride !== "number") this.pooledRunnerConcurrency = poolConfig.pooledRunnerConcurrency
213
217
  if (typeof this.pooledRunnerMaxJobsOverride !== "number") this.pooledRunnerMaxJobs = poolConfig.pooledRunnerMaxJobs
214
218
  if (typeof this.pooledRunnerMaxRssBytesOverride !== "number") this.pooledRunnerMaxRssBytes = poolConfig.pooledRunnerMaxRssBytes
215
219
  if (typeof this.pooledRunnerMaxLifetimeMsOverride !== "number") this.pooledRunnerMaxLifetimeMs = poolConfig.pooledRunnerMaxLifetimeMs
@@ -582,7 +586,7 @@ export default class BackgroundJobsWorker {
582
586
  _readyMessage() {
583
587
  const acceptsProcessJob = this.inflightProcessJobs.size < this.maxConcurrentForkedJobs
584
588
  const acceptsInline = this.inflightInlineJobs.size < this.maxConcurrentInlineJobs
585
- const acceptsPooled = this.inflightPooledJobs.size < this.pooledRunnerCount
589
+ const acceptsPooled = this._availablePooledSlots() > 0
586
590
 
587
591
  if (!acceptsProcessJob && !acceptsInline && !acceptsPooled) return null
588
592
 
@@ -612,23 +616,54 @@ export default class BackgroundJobsWorker {
612
616
  }
613
617
 
614
618
  /**
615
- * Runs a payload in an idle pooled child.
619
+ * Free pooled slots across the pool: open slots in non-retiring children plus
620
+ * the slots we could add by spawning more children up to `pooledRunnerCount`.
621
+ * Retiring children (draining before replacement) never contribute capacity.
622
+ * @returns {number} - Number of pooled jobs the worker can accept right now.
623
+ */
624
+ _availablePooledSlots() {
625
+ let openInExisting = 0
626
+ let nonRetiringChildren = 0
627
+
628
+ for (const child of this.pooledChildren) {
629
+ const state = this.pooledChildStates.get(child)
630
+ if (!state || state.retiring) continue
631
+ nonRetiringChildren += 1
632
+ openInExisting += this.pooledRunnerConcurrency - state.inflight.size
633
+ }
634
+
635
+ const spawnableChildren = Math.max(0, this.pooledRunnerCount - nonRetiringChildren)
636
+
637
+ return openInExisting + spawnableChildren * this.pooledRunnerConcurrency
638
+ }
639
+
640
+ /**
641
+ * Runs a payload on a pooled child with a free concurrency slot, spawning a
642
+ * new child when every non-retiring child is full and the pool is below
643
+ * `pooledRunnerCount`. Each child runs up to `pooledRunnerConcurrency` jobs at
644
+ * once on its own event loop.
616
645
  * @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
617
646
  * @returns {Promise<void>} - Resolves after the durable report.
618
647
  */
619
648
  _runPooledJob(payload) {
620
- let child = [...this.pooledChildren].find((candidate) => !this.pooledChildStates.get(candidate)?.payload)
649
+ let child
650
+ for (const candidate of this.pooledChildren) {
651
+ const state = this.pooledChildStates.get(candidate)
652
+ if (state && !state.retiring && state.inflight.size < this.pooledRunnerConcurrency) {
653
+ child = candidate
654
+ break
655
+ }
656
+ }
621
657
  if (!child) child = this._createPooledChild()
622
658
  const state = this.pooledChildStates.get(child)
623
659
  if (!state) throw new Error("Pooled runner state missing")
624
660
 
625
661
  return new Promise((resolve) => {
626
- state.payload = payload
627
- state.resolve = resolve
662
+ state.inflight.set(payload.id, {payload, resolve})
628
663
  try {
629
664
  child.send({type: "job", payload})
630
665
  } catch (error) {
631
- this._handlePooledChildFailure({child, error})
666
+ void this._handlePooledChildFailure({child, error})
632
667
  }
633
668
  })
634
669
  }
@@ -647,7 +682,7 @@ export default class BackgroundJobsWorker {
647
682
  })
648
683
  this.pooledChildren.add(child)
649
684
  this.inflightProcessChildren.add(child)
650
- this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0})
685
+ this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0, inflight: new Map(), retiring: false})
651
686
  child.on("message", (message) => this._handlePooledChildMessage({child, message}))
652
687
  child.once("exit", (code, signal) => this._handlePooledChildFailure({child, error: new Error(`Pooled background job runner exited: code=${code} signal=${signal || "none"}`)}))
653
688
  child.once("error", (error) => this._handlePooledChildFailure({child, error}))
@@ -655,7 +690,8 @@ export default class BackgroundJobsWorker {
655
690
  }
656
691
 
657
692
  /**
658
- * Handles a pooled child's durable-report acknowledgement.
693
+ * Handles a pooled child's per-job durable-report acknowledgement. A child
694
+ * runs jobs concurrently and reports one `job-outcome` per job id.
659
695
  * @param {object} args - Message details.
660
696
  * @param {import("node:child_process").ChildProcess} args.child - Pooled child.
661
697
  * @param {?} args.message - IPC message.
@@ -665,23 +701,70 @@ export default class BackgroundJobsWorker {
665
701
  if (!message || typeof message !== "object") return
666
702
  const record = /** @type {{type?: ?, jobId?: ?, acknowledged?: ?, rssBytes?: ?, error?: ?}} */ (message)
667
703
  const state = this.pooledChildStates.get(child)
668
- if (record.type !== "job-outcome" || !state?.payload || record.jobId !== state.payload.id || state.settling) return
669
- if (record.acknowledged !== true) {
670
- void this._handlePooledChildFailure({child, error: new Error(typeof record.error === "string" ? record.error : "Pooled runner terminal report was not acknowledged")})
671
- return
672
- }
673
- const resolve = state.resolve
674
- state.payload = undefined
675
- state.resolve = undefined
704
+ if (record.type !== "job-outcome" || !state || state.settling || typeof record.jobId !== "string") return
705
+ const entry = state.inflight.get(record.jobId)
706
+ if (!entry) return
707
+
708
+ state.inflight.delete(record.jobId)
676
709
  state.jobsRun += 1
677
- if (resolve) resolve(undefined)
710
+ const resolve = entry.resolve
711
+
712
+ if (record.acknowledged === true) {
713
+ if (resolve) resolve(undefined)
714
+ } else {
715
+ // The child stayed alive but could not confirm this one job's terminal
716
+ // report; reclaim just this job — its concurrent siblings are unaffected.
717
+ void this._reportJobResult({
718
+ jobId: entry.payload.id,
719
+ status: "failed",
720
+ error: new Error(typeof record.error === "string" ? record.error : "Pooled runner terminal report was not acknowledged"),
721
+ handoffId: entry.payload.handoffId,
722
+ handedOffAtMs: entry.payload.handedOffAtMs,
723
+ workerId: entry.payload.workerId || this.workerId
724
+ }).finally(() => { if (resolve) resolve(undefined) })
725
+ }
726
+
678
727
  const rssBytes = typeof record.rssBytes === "number" ? record.rssBytes : Number.POSITIVE_INFINITY
679
728
  const runnerAgeMs = Date.now() - state.createdAtMs
680
- if (state.jobsRun >= this.pooledRunnerMaxJobs || rssBytes >= this.pooledRunnerMaxRssBytes || runnerAgeMs >= this.pooledRunnerMaxLifetimeMs || this.shouldStop) this._retirePooledChild(child)
729
+ if (!state.retiring && (state.jobsRun >= this.pooledRunnerMaxJobs || rssBytes >= this.pooledRunnerMaxRssBytes || runnerAgeMs >= this.pooledRunnerMaxLifetimeMs || this.shouldStop)) {
730
+ this._beginRetirePooledChild(child)
731
+ }
732
+ this._terminateIfDrained(child)
733
+ }
734
+
735
+ /**
736
+ * Marks a pooled child for retirement and eagerly spawns a single replacement
737
+ * (1-for-1) so its capacity is restored immediately without waiting for it to
738
+ * finish draining. The retiring child stops receiving new jobs and is
739
+ * terminated only once its in-flight set drains, so a long-running job (e.g. a
740
+ * build) is never cut off.
741
+ * @param {import("node:child_process").ChildProcess} child - Child to retire.
742
+ * @returns {void}
743
+ */
744
+ _beginRetirePooledChild(child) {
745
+ const state = this.pooledChildStates.get(child)
746
+ if (!state || state.retiring) return
747
+
748
+ state.retiring = true
749
+ // Best-effort pre-warm: skip when stopping (no new work) or before the
750
+ // worker is initialized (no configuration to fork a child from).
751
+ if (!this.shouldStop && this.configuration) this._createPooledChild()
752
+ }
753
+
754
+ /**
755
+ * Terminates a retiring pooled child once it has no in-flight jobs left.
756
+ * @param {import("node:child_process").ChildProcess} child - Child to check.
757
+ * @returns {void}
758
+ */
759
+ _terminateIfDrained(child) {
760
+ const state = this.pooledChildStates.get(child)
761
+ if (!state || !state.retiring || state.inflight.size > 0) return
762
+
763
+ this._retirePooledChild(child)
681
764
  }
682
765
 
683
766
  /**
684
- * Retires an idle pooled child.
767
+ * Retires a drained pooled child (removes it from tracking, then SIGTERMs it).
685
768
  * @param {import("node:child_process").ChildProcess} child - Child process to retire.
686
769
  * @returns {void}
687
770
  */
@@ -693,7 +776,11 @@ export default class BackgroundJobsWorker {
693
776
  }
694
777
 
695
778
  /**
696
- * Removes and reports an unhealthy pooled child.
779
+ * Removes an exited/unhealthy pooled child and reports every job that was
780
+ * in-flight on it as failed — a process-level crash's blast radius is the
781
+ * child's whole in-flight set. Capacity is refilled lazily on the next
782
+ * dispatch (a spawnable slot is still advertised), avoiding a tight respawn
783
+ * loop when a child crashes on startup.
697
784
  * @param {object} args - Failure details.
698
785
  * @param {import("node:child_process").ChildProcess} args.child - Pooled child.
699
786
  * @param {?} args.error - Failure.
@@ -705,21 +792,22 @@ export default class BackgroundJobsWorker {
705
792
  if (state) state.settling = true
706
793
  this.pooledChildren.delete(child)
707
794
  this.inflightProcessChildren.delete(child)
708
- if (!state?.payload) {
709
- this.pooledChildStates.delete(child)
710
- return
711
- }
712
- const payload = state.payload
713
- await this._reportJobResult({
714
- jobId: payload.id,
715
- status: "failed",
716
- error,
717
- handoffId: payload.handoffId,
718
- handedOffAtMs: payload.handedOffAtMs,
719
- workerId: payload.workerId || this.workerId
720
- })
795
+
796
+ const entries = state ? [...state.inflight.values()] : []
797
+ if (state) state.inflight.clear()
721
798
  this.pooledChildStates.delete(child)
722
- if (state.resolve) state.resolve(undefined)
799
+
800
+ await Promise.allSettled(entries.map(async (entry) => {
801
+ await this._reportJobResult({
802
+ jobId: entry.payload.id,
803
+ status: "failed",
804
+ error,
805
+ handoffId: entry.payload.handoffId,
806
+ handedOffAtMs: entry.payload.handedOffAtMs,
807
+ workerId: entry.payload.workerId || this.workerId
808
+ })
809
+ if (entry.resolve) entry.resolve(undefined)
810
+ }))
723
811
  }
724
812
 
725
813
  /**
@@ -165,6 +165,7 @@
165
165
  * for workload-shaped limits use per-queue caps (`queues`) instead, which are
166
166
  * enforced cluster-wide and are immune to duplicate worker processes.
167
167
  * @property {number} [pooledRunnerCount] - Number of warm, reusable child runners owned by each worker. Pooled capacity is separate from inline and forked/spawned capacity. Default: `4`.
168
+ * @property {number} [pooledRunnerConcurrency] - Number of jobs each pooled child runs concurrently on its own event loop. Total per-worker pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency`. `1` (default) keeps each child serial; raise it for I/O-bound jobs so a bounded set of isolated processes handles high concurrency (like the inline lane) without one process per concurrent job. Default: `1`.
168
169
  * @property {number} [pooledRunnerMaxJobs] - Number of sequential jobs a pooled child runs before it is replaced, bounding process-level resource accumulation. Default: `100`.
169
170
  * @property {number} [pooledRunnerMaxRssBytes] - RSS bytes after an acknowledged job at which a pooled child is replaced. Default: `536870912` (512 MiB).
170
171
  * @property {number} [pooledRunnerMaxLifetimeMs] - Age after an acknowledged job at which a pooled child is replaced. Default: `3600000` (one hour).
@@ -192,6 +192,12 @@ export default class VelociousConfiguration {
192
192
  }
193
193
 
194
194
  this._isInitialized = false
195
+ /**
196
+ * In-progress `initialize()` promise, memoized so concurrent callers await
197
+ * the same bootstrap. Reset to undefined if initialization fails.
198
+ * @type {Promise<void> | undefined}
199
+ */
200
+ this._initializePromise = undefined
195
201
  this.httpServer = httpServer || {}
196
202
  /**
197
203
  * Stores the http server instance value.
@@ -1269,6 +1275,7 @@ export default class VelociousConfiguration {
1269
1275
  const envMaxConcurrentForkedRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_FORKED_JOBS
1270
1276
  const envMaxConcurrentRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_INLINE_JOBS
1271
1277
  const envPooledRunnerCountRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_COUNT
1278
+ const envPooledRunnerConcurrencyRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_CONCURRENCY
1272
1279
  const envPooledRunnerMaxJobsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_JOBS
1273
1280
  const envPooledRunnerMaxRssBytesRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_RSS_BYTES
1274
1281
  const envPooledRunnerMaxLifetimeMsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_LIFETIME_MS
@@ -1279,6 +1286,7 @@ export default class VelociousConfiguration {
1279
1286
  const envMaxConcurrentForked = envMaxConcurrentForkedRaw ? Number(envMaxConcurrentForkedRaw) : undefined
1280
1287
  const envMaxConcurrent = envMaxConcurrentRaw ? Number(envMaxConcurrentRaw) : undefined
1281
1288
  const envPooledRunnerCount = envPooledRunnerCountRaw ? Number(envPooledRunnerCountRaw) : undefined
1289
+ const envPooledRunnerConcurrency = envPooledRunnerConcurrencyRaw ? Number(envPooledRunnerConcurrencyRaw) : undefined
1282
1290
  const envPooledRunnerMaxJobs = envPooledRunnerMaxJobsRaw ? Number(envPooledRunnerMaxJobsRaw) : undefined
1283
1291
  const envPooledRunnerMaxRssBytes = envPooledRunnerMaxRssBytesRaw ? Number(envPooledRunnerMaxRssBytesRaw) : undefined
1284
1292
  const envPooledRunnerMaxLifetimeMs = envPooledRunnerMaxLifetimeMsRaw ? Number(envPooledRunnerMaxLifetimeMsRaw) : undefined
@@ -1299,6 +1307,9 @@ export default class VelociousConfiguration {
1299
1307
  const pooledRunnerCount = typeof configured.pooledRunnerCount === "number" && Number.isFinite(configured.pooledRunnerCount) && Number.isInteger(configured.pooledRunnerCount) && configured.pooledRunnerCount >= 1
1300
1308
  ? configured.pooledRunnerCount
1301
1309
  : (!("pooledRunnerCount" in configured) && typeof envPooledRunnerCount === "number" && Number.isFinite(envPooledRunnerCount) && Number.isInteger(envPooledRunnerCount) && envPooledRunnerCount >= 1 ? envPooledRunnerCount : 4)
1310
+ const pooledRunnerConcurrency = typeof configured.pooledRunnerConcurrency === "number" && Number.isFinite(configured.pooledRunnerConcurrency) && Number.isInteger(configured.pooledRunnerConcurrency) && configured.pooledRunnerConcurrency >= 1
1311
+ ? configured.pooledRunnerConcurrency
1312
+ : (!("pooledRunnerConcurrency" in configured) && typeof envPooledRunnerConcurrency === "number" && Number.isFinite(envPooledRunnerConcurrency) && Number.isInteger(envPooledRunnerConcurrency) && envPooledRunnerConcurrency >= 1 ? envPooledRunnerConcurrency : 1)
1302
1313
  const pooledRunnerMaxJobs = typeof configured.pooledRunnerMaxJobs === "number" && Number.isFinite(configured.pooledRunnerMaxJobs) && Number.isInteger(configured.pooledRunnerMaxJobs) && configured.pooledRunnerMaxJobs >= 1
1303
1314
  ? configured.pooledRunnerMaxJobs
1304
1315
  : (!("pooledRunnerMaxJobs" in configured) && typeof envPooledRunnerMaxJobs === "number" && Number.isFinite(envPooledRunnerMaxJobs) && Number.isInteger(envPooledRunnerMaxJobs) && envPooledRunnerMaxJobs >= 1 ? envPooledRunnerMaxJobs : 100)
@@ -1336,7 +1347,7 @@ export default class VelociousConfiguration {
1336
1347
  : 60 * 60 * 1000
1337
1348
  }
1338
1349
 
1339
- return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
1350
+ return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
1340
1351
  }
1341
1352
 
1342
1353
  /**
@@ -1934,9 +1945,16 @@ export default class VelociousConfiguration {
1934
1945
  * @returns {Promise<void>} - Resolves when complete.
1935
1946
  */
1936
1947
  async initialize({type} = {type: "undefined"}) {
1937
- if (!this.isInitialized()) {
1938
- this._isInitialized = true
1939
-
1948
+ if (this._isInitialized) return
1949
+ // Memoize the in-progress initialization so concurrent callers await the same
1950
+ // bootstrap instead of racing. `_isInitialized` was previously set to `true`
1951
+ // up front, so a second caller (e.g. a pooled runner with
1952
+ // `pooledRunnerConcurrency > 1` starting several jobs on a cold child) could
1953
+ // skip initialization and load models / perform a job while the first call
1954
+ // was still awaiting model discovery and initializers. Mirrors connectBeacon.
1955
+ if (this._initializePromise) return await this._initializePromise
1956
+
1957
+ this._initializePromise = (async () => {
1940
1958
  await this.initializeModels({type})
1941
1959
  await this.getEnvironmentHandler().autoDiscoverResources(this)
1942
1960
  this._mergeDiscoveredAbilityResources()
@@ -1957,6 +1975,17 @@ export default class VelociousConfiguration {
1957
1975
  }
1958
1976
  }
1959
1977
  }
1978
+
1979
+ this._isInitialized = true
1980
+ })()
1981
+
1982
+ try {
1983
+ await this._initializePromise
1984
+ } catch (error) {
1985
+ // Let a later call retry a failed initialization instead of every future
1986
+ // caller awaiting the same cached rejection.
1987
+ this._initializePromise = undefined
1988
+ throw error
1960
1989
  }
1961
1990
  }
1962
1991
 
@@ -3,10 +3,12 @@
3
3
  * @param {import("./types.js").BackgroundJobPayload} payload - Payload.
4
4
  * @param {object} [options] - Runner options.
5
5
  * @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
6
+ * @param {boolean} [options.manageProcessTitle] - Whether to set the per-job process title and restore it afterwards. Off for concurrent pooled runners, where interleaved snapshot/restore of the single process-wide `process.title` would corrupt it; the pooled child owns an aggregate title instead.
6
7
  * @returns {Promise<void>} - Resolves when complete.
7
8
  */
8
- export default function runJobPayload(payload: import("./types.js").BackgroundJobPayload, { closeConnections }?: {
9
+ export default function runJobPayload(payload: import("./types.js").BackgroundJobPayload, { closeConnections, manageProcessTitle }?: {
9
10
  closeConnections?: boolean | undefined;
11
+ manageProcessTitle?: boolean | undefined;
10
12
  }): Promise<void>;
11
13
  export class BackgroundJobPerformedFailure extends Error {
12
14
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"job-runner.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job-runner.js"],"names":[],"mappings":"AA2EA;;;;;;GAMG;AACH,+CALW,OAAO,YAAY,EAAE,oBAAoB,yBAEjD;IAA0B,gBAAgB;CAC1C,GAAU,OAAO,CAAC,IAAI,CAAC,CAmEzB;AA3ID;IACE;;;OAGG;IACH,mBAFW,KAAK,EAKf;CACF"}
1
+ {"version":3,"file":"job-runner.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job-runner.js"],"names":[],"mappings":"AA2EA;;;;;;;GAOG;AACH,+CANW,OAAO,YAAY,EAAE,oBAAoB,6CAEjD;IAA0B,gBAAgB;IAChB,kBAAkB;CAC5C,GAAU,OAAO,CAAC,IAAI,CAAC,CAoEzB;AA7ID;IACE;;;OAGG;IACH,mBAFW,KAAK,EAKf;CACF"}
@@ -68,9 +68,10 @@ function runnerProcessTitle(JobClass, payload) {
68
68
  * @param {import("./types.js").BackgroundJobPayload} payload - Payload.
69
69
  * @param {object} [options] - Runner options.
70
70
  * @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
71
+ * @param {boolean} [options.manageProcessTitle] - Whether to set the per-job process title and restore it afterwards. Off for concurrent pooled runners, where interleaved snapshot/restore of the single process-wide `process.title` would corrupt it; the pooled child owns an aggregate title instead.
71
72
  * @returns {Promise<void>} - Resolves when complete.
72
73
  */
73
- export default async function runJobPayload(payload, { closeConnections = true } = {}) {
74
+ export default async function runJobPayload(payload, { closeConnections = true, manageProcessTitle = true } = {}) {
74
75
  const configuration = await configurationResolver();
75
76
  configuration.setCurrent();
76
77
  await configuration.initialize({ type: "background-jobs-runner" });
@@ -86,8 +87,10 @@ export default async function runJobPayload(payload, { closeConnections = true }
86
87
  const perform = jobInstance.perform;
87
88
  // Name the process after the job it is running so `ps`/`top` show what each
88
89
  // runner is doing; restored in the `finally` below when the job finishes.
90
+ // Skipped for concurrent pooled runners, whose child owns an aggregate title.
89
91
  const previousTitle = process.title;
90
- process.title = runnerProcessTitle(JobClass, payload);
92
+ if (manageProcessTitle)
93
+ process.title = runnerProcessTitle(JobClass, payload);
91
94
  try {
92
95
  try {
93
96
  await configuration.withConnections({ name: `Background job runner: ${payload.jobName}` }, async () => {
@@ -123,7 +126,8 @@ export default async function runJobPayload(payload, { closeConnections = true }
123
126
  finally {
124
127
  // Restore the runner's base title so a lingering/idle runner (or a reused
125
128
  // one) doesn't misreport a finished job as still running.
126
- process.title = previousTitle;
129
+ if (manageProcessTitle)
130
+ process.title = previousTitle;
127
131
  if (closeConnections) {
128
132
  try {
129
133
  await configuration.disconnectBeacon();
@@ -134,4 +138,4 @@ export default async function runJobPayload(payload, { closeConnections = true }
134
138
  }
135
139
  }
136
140
  }
137
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiam9iLXJ1bm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvam9iLXJ1bm5lci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxxQkFBcUIsTUFBTSw4QkFBOEIsQ0FBQTtBQUNoRSxPQUFPLHFCQUFxQixNQUFNLG1CQUFtQixDQUFBO0FBQ3JELE9BQU8sNEJBQTRCLE1BQU0sc0JBQXNCLENBQUE7QUFFL0QsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLENBQUE7QUFFcEMsTUFBTSxPQUFPLDZCQUE4QixTQUFRLEtBQUs7SUFDdEQ7OztPQUdHO0lBQ0gsWUFBWSxLQUFLO1FBQ2YsS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBQyxLQUFLLEVBQUMsQ0FBQyxDQUFBO1FBQzdCLElBQUksQ0FBQyxJQUFJLEdBQUcsK0JBQStCLENBQUE7SUFDN0MsQ0FBQztDQUNGO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLGFBQWEsRUFBRSxLQUFLO0lBQ2xELE1BQU0sV0FBVyxHQUFHLGFBQWEsQ0FBQyxjQUFjLEVBQUUsQ0FBQTtJQUNsRCxNQUFNLGVBQWUsR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO0lBQ2pGLE1BQU0sT0FBTyxHQUFHO1FBQ2QsT0FBTyxFQUFFLEVBQUMsUUFBUSxFQUFFLHdCQUF3QixFQUFFLEtBQUssRUFBRSxjQUFjLEVBQUM7UUFDcEUsS0FBSyxFQUFFLGVBQWU7S0FDdkIsQ0FBQTtJQUNELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxhQUFhLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDO1dBQy9ELFdBQVcsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRS9D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WUFDeEMsQ0FBQztvQkFBUyxDQUFDO2dCQUNULE1BQU0sYUFBYSxDQUFDLHdCQUF3QixFQUFFLENBQUE7WUFDaEQsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgY29uZmlndXJhdGlvblJlc29sdmVyIGZyb20gXCIuLi9jb25maWd1cmF0aW9uLXJlc29sdmVyLmpzXCJcbmltcG9ydCBCYWNrZ3JvdW5kSm9iUmVnaXN0cnkgZnJvbSBcIi4vam9iLXJlZ2lzdHJ5LmpzXCJcbmltcG9ydCBCYWNrZ3JvdW5kSm9ic1N0YXR1c1JlcG9ydGVyIGZyb20gXCIuL3N0YXR1cy1yZXBvcnRlci5qc1wiXG5cbmNvbnN0IEJFQUNPTl9SRUFEWV9USU1FT1VUX01TID0gNTAwMFxuXG5leHBvcnQgY2xhc3MgQmFja2dyb3VuZEpvYlBlcmZvcm1lZEZhaWx1cmUgZXh0ZW5kcyBFcnJvciB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgcGVyZm9ybWVkLWpvYiBmYWlsdXJlIGFmdGVyIGl0cyB0ZXJtaW5hbCByZXBvcnQgaXMgYWNrbm93bGVkZ2VkLlxuICAgKiBAcGFyYW0ge0Vycm9yfSBjYXVzZSAtIEEgam9iIHBlcmZvcm0gZXJyb3Igd2hvc2UgZmFpbGVkIHRlcm1pbmFsIHJlcG9ydCB3YXMgYWNrbm93bGVkZ2VkLlxuICAgKi9cbiAgY29uc3RydWN0b3IoY2F1c2UpIHtcbiAgICBzdXBlcihjYXVzZS5tZXNzYWdlLCB7Y2F1c2V9KVxuICAgIHRoaXMubmFtZSA9IFwiQmFja2dyb3VuZEpvYlBlcmZvcm1lZEZhaWx1cmVcIlxuICB9XG59XG5cbi8qKlxuICogUnVucyByZXBvcnQgYmVhY29uIHJlYWR5IGVycm9yLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uLlxuICogQHBhcmFtIHs/fSBlcnJvciAtIEJlYWNvbiByZWFkaW5lc3MgZXJyb3IuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqL1xuZnVuY3Rpb24gcmVwb3J0QmVhY29uUmVhZHlFcnJvcihjb25maWd1cmF0aW9uLCBlcnJvcikge1xuICBjb25zdCBlcnJvckV2ZW50cyA9IGNvbmZpZ3VyYXRpb24uZ2V0RXJyb3JFdmVudHMoKVxuICBjb25zdCBub3JtYWxpemVkRXJyb3IgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKGVycm9yKSlcbiAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICBjb250ZXh0OiB7cGVlclR5cGU6IFwiYmFja2dyb3VuZC1qb2JzLXJ1bm5lclwiLCBzdGFnZTogXCJiZWFjb24tcmVhZHlcIn0sXG4gICAgZXJyb3I6IG5vcm1hbGl6ZWRFcnJvclxuICB9XG4gIGNvbnN0IGhhc0xpc3RlbmVyID0gZXJyb3JFdmVudHMubGlzdGVuZXJDb3VudChcImZyYW1ld29yay1lcnJvclwiKSA+IDBcbiAgICB8fCBlcnJvckV2ZW50cy5saXN0ZW5lckNvdW50KFwiYWxsLWVycm9yXCIpID4gMFxuXG4gIGVycm9yRXZlbnRzLmVtaXQoXCJmcmFtZXdvcmstZXJyb3JcIiwgcGF5bG9hZClcbiAgZXJyb3JFdmVudHMuZW1pdChcImFsbC1lcnJvclwiLCB7Li4ucGF5bG9hZCwgZXJyb3JUeXBlOiBcImZyYW1ld29yay1lcnJvclwifSlcblxuICBpZiAoIWhhc0xpc3RlbmVyKSB7XG4gICAgY29uc29sZS5lcnJvcihgW3ZlbG9jaW91cyBmcmFtZXdvcmstZXJyb3Igc3RhZ2U9YmVhY29uLXJlYWR5XSAke25vcm1hbGl6ZWRFcnJvci5tZXNzYWdlfWApXG4gIH1cbn1cblxuLyoqXG4gKiBSdW5zIGNvbm5lY3QgYmVhY29uLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGNvbm5lY3RCZWFjb24oY29uZmlndXJhdGlvbikge1xuICBjb25zdCBiZWFjb25DbGllbnQgPSBhd2FpdCBjb25maWd1cmF0aW9uLmNvbm5lY3RCZWFjb24oe3BlZXJUeXBlOiBcImJhY2tncm91bmQtam9icy1ydW5uZXJcIn0pXG5cbiAgaWYgKCFiZWFjb25DbGllbnQpIHJldHVyblxuXG4gIHRyeSB7XG4gICAgYXdhaXQgYmVhY29uQ2xpZW50LndhaXRGb3JSZWFkeSh7dGltZW91dE1zOiBCRUFDT05fUkVBRFlfVElNRU9VVF9NU30pXG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgcmVwb3J0QmVhY29uUmVhZHlFcnJvcihjb25maWd1cmF0aW9uLCBlcnJvcilcbiAgfVxufVxuXG4vKipcbiAqIFJlc29sdmVzIHRoZSBwcm9jZXNzIHRpdGxlIHRvIHNob3cgd2hpbGUgYSBqb2IgcnVuczogdGhlIGpvYiBjbGFzcydzIGRlY2xhcmVkXG4gKiBgc3RhdGljIHByb2Nlc3NUaXRsZWAsIGVsc2UgYSBgdmVsb2Npb3VzIGpvYi1ydW5uZXI6IDxKb2JOYW1lPmAgZmFsbGJhY2suXG4gKiBAcGFyYW0ge3R5cGVvZiBpbXBvcnQoXCIuL2pvYi5qc1wiKS5kZWZhdWx0fSBKb2JDbGFzcyAtIFJlc29sdmVkIGpvYiBjbGFzcy5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUGF5bG9hZH0gcGF5bG9hZCAtIFBheWxvYWQuXG4gKiBAcmV0dXJucyB7c3RyaW5nfSAtIFByb2Nlc3MgdGl0bGUuXG4gKi9cbmZ1bmN0aW9uIHJ1bm5lclByb2Nlc3NUaXRsZShKb2JDbGFzcywgcGF5bG9hZCkge1xuICBjb25zdCBkZWNsYXJlZCA9IEpvYkNsYXNzLnByb2Nlc3NUaXRsZVxuXG4gIGlmICh0eXBlb2YgZGVjbGFyZWQgPT09IFwic3RyaW5nXCIgJiYgZGVjbGFyZWQubGVuZ3RoID4gMCkgcmV0dXJuIGRlY2xhcmVkXG5cbiAgcmV0dXJuIGB2ZWxvY2lvdXMgam9iLXJ1bm5lcjogJHtwYXlsb2FkLmpvYk5hbWV9YFxufVxuXG4vKipcbiAqIFJ1bnMgcnVuIGpvYiBwYXlsb2FkLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL3R5cGVzLmpzXCIpLkJhY2tncm91bmRKb2JQYXlsb2FkfSBwYXlsb2FkIC0gUGF5bG9hZC5cbiAqIEBwYXJhbSB7b2JqZWN0fSBbb3B0aW9uc10gLSBSdW5uZXIgb3B0aW9ucy5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMuY2xvc2VDb25uZWN0aW9uc10gLSBXaGV0aGVyIHRvIGdyYWNlZnVsbHkgY2xvc2UgZnJhbWV3b3JrIGNvbm5lY3Rpb25zIGFmdGVyIHRoZSBqb2IuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLlxuICovXG5leHBvcnQgZGVmYXVsdCBhc3luYyBmdW5jdGlvbiBydW5Kb2JQYXlsb2FkKHBheWxvYWQsIHtjbG9zZUNvbm5lY3Rpb25zID0gdHJ1ZX0gPSB7fSkge1xuICBjb25zdCBjb25maWd1cmF0aW9uID0gYXdhaXQgY29uZmlndXJhdGlvblJlc29sdmVyKClcbiAgY29uZmlndXJhdGlvbi5zZXRDdXJyZW50KClcbiAgYXdhaXQgY29uZmlndXJhdGlvbi5pbml0aWFsaXplKHt0eXBlOiBcImJhY2tncm91bmQtam9icy1ydW5uZXJcIn0pXG4gIGF3YWl0IGNvbm5lY3RCZWFjb24oY29uZmlndXJhdGlvbilcbiAgY29uc3QgcmVwb3J0ZXIgPSBuZXcgQmFja2dyb3VuZEpvYnNTdGF0dXNSZXBvcnRlcih7Y29uZmlndXJhdGlvbn0pXG5cbiAgY29uc3QgcmVnaXN0cnkgPSBuZXcgQmFja2dyb3VuZEpvYlJlZ2lzdHJ5KHtjb25maWd1cmF0aW9ufSlcbiAgYXdhaXQgcmVnaXN0cnkubG9hZCgpXG4gIGNvbnN0IEpvYkNsYXNzID0gcmVnaXN0cnkuZ2V0Sm9iQnlOYW1lKHBheWxvYWQuam9iTmFtZSlcbiAgY29uc3Qgam9iSW5zdGFuY2UgPSBuZXcgSm9iQ2xhc3MoKVxuICAvKipcbiAgICogUGVyZm9ybS5cbiAgICogQHR5cGUgeyguLi5hcmdzOiBBcnJheTw/PikgPT4gUHJvbWlzZTx2b2lkPn0gKi9cbiAgY29uc3QgcGVyZm9ybSA9IGpvYkluc3RhbmNlLnBlcmZvcm1cblxuICAvLyBOYW1lIHRoZSBwcm9jZXNzIGFmdGVyIHRoZSBqb2IgaXQgaXMgcnVubmluZyBzbyBgcHNgL2B0b3BgIHNob3cgd2hhdCBlYWNoXG4gIC8vIHJ1bm5lciBpcyBkb2luZzsgcmVzdG9yZWQgaW4gdGhlIGBmaW5hbGx5YCBiZWxvdyB3aGVuIHRoZSBqb2IgZmluaXNoZXMuXG4gIGNvbnN0IHByZXZpb3VzVGl0bGUgPSBwcm9jZXNzLnRpdGxlXG4gIHByb2Nlc3MudGl0bGUgPSBydW5uZXJQcm9jZXNzVGl0bGUoSm9iQ2xhc3MsIHBheWxvYWQpXG5cbiAgdHJ5IHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi53aXRoQ29ubmVjdGlvbnMoe25hbWU6IGBCYWNrZ3JvdW5kIGpvYiBydW5uZXI6ICR7cGF5bG9hZC5qb2JOYW1lfWB9LCBhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHBlcmZvcm0uYXBwbHkoam9iSW5zdGFuY2UsIHBheWxvYWQuYXJncyB8fCBbXSlcbiAgICAgIH0pXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IHBlcmZvcm1lZEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gICAgICBpZiAocGF5bG9hZC5pZCkge1xuICAgICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICAgIGpvYklkOiBwYXlsb2FkLmlkLFxuICAgICAgICAgIHN0YXR1czogXCJmYWlsZWRcIixcbiAgICAgICAgICBlcnJvcjogcGVyZm9ybWVkRXJyb3IsXG4gICAgICAgICAgaGFuZG9mZklkOiBwYXlsb2FkLmhhbmRvZmZJZCxcbiAgICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgICBoYW5kZWRPZmZBdE1zOiBwYXlsb2FkLmhhbmRlZE9mZkF0TXMsXG4gICAgICAgICAgbWF4RHVyYXRpb25NczogMzAwMDBcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgdGhyb3cgbmV3IEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlKHBlcmZvcm1lZEVycm9yKVxuICAgIH1cblxuICAgIGlmIChwYXlsb2FkLmlkKSB7XG4gICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICBqb2JJZDogcGF5bG9hZC5pZCxcbiAgICAgICAgc3RhdHVzOiBcImNvbXBsZXRlZFwiLFxuICAgICAgICBoYW5kb2ZmSWQ6IHBheWxvYWQuaGFuZG9mZklkLFxuICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgaGFuZGVkT2ZmQXRNczogcGF5bG9hZC5oYW5kZWRPZmZBdE1zLFxuICAgICAgICBtYXhEdXJhdGlvbk1zOiAzMDAwMFxuICAgICAgfSlcbiAgICB9XG4gIH0gZmluYWxseSB7XG4gICAgLy8gUmVzdG9yZSB0aGUgcnVubmVyJ3MgYmFzZSB0aXRsZSBzbyBhIGxpbmdlcmluZy9pZGxlIHJ1bm5lciAob3IgYSByZXVzZWRcbiAgICAvLyBvbmUpIGRvZXNuJ3QgbWlzcmVwb3J0IGEgZmluaXNoZWQgam9iIGFzIHN0aWxsIHJ1bm5pbmcuXG4gICAgcHJvY2Vzcy50aXRsZSA9IHByZXZpb3VzVGl0bGVcbiAgICBpZiAoY2xvc2VDb25uZWN0aW9ucykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi5kaXNjb25uZWN0QmVhY29uKClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIGF3YWl0IGNvbmZpZ3VyYXRpb24uY2xvc2VEYXRhYmFzZUNvbm5lY3Rpb25zKClcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiJdfQ==
141
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiam9iLXJ1bm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvam9iLXJ1bm5lci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxxQkFBcUIsTUFBTSw4QkFBOEIsQ0FBQTtBQUNoRSxPQUFPLHFCQUFxQixNQUFNLG1CQUFtQixDQUFBO0FBQ3JELE9BQU8sNEJBQTRCLE1BQU0sc0JBQXNCLENBQUE7QUFFL0QsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLENBQUE7QUFFcEMsTUFBTSxPQUFPLDZCQUE4QixTQUFRLEtBQUs7SUFDdEQ7OztPQUdHO0lBQ0gsWUFBWSxLQUFLO1FBQ2YsS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBQyxLQUFLLEVBQUMsQ0FBQyxDQUFBO1FBQzdCLElBQUksQ0FBQyxJQUFJLEdBQUcsK0JBQStCLENBQUE7SUFDN0MsQ0FBQztDQUNGO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLGFBQWEsRUFBRSxLQUFLO0lBQ2xELE1BQU0sV0FBVyxHQUFHLGFBQWEsQ0FBQyxjQUFjLEVBQUUsQ0FBQTtJQUNsRCxNQUFNLGVBQWUsR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO0lBQ2pGLE1BQU0sT0FBTyxHQUFHO1FBQ2QsT0FBTyxFQUFFLEVBQUMsUUFBUSxFQUFFLHdCQUF3QixFQUFFLEtBQUssRUFBRSxjQUFjLEVBQUM7UUFDcEUsS0FBSyxFQUFFLGVBQWU7S0FDdkIsQ0FBQTtJQUNELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxhQUFhLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDO1dBQy9ELFdBQVcsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRS9D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QkFBZ0IsRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQztnQkFDSCxNQUFNLGFBQWEsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFBO1lBQ3hDLENBQUM7b0JBQVMsQ0FBQztnQkFDVCxNQUFNLGFBQWEsQ0FBQyx3QkFBd0IsRUFBRSxDQUFBO1lBQ2hELENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IGNvbmZpZ3VyYXRpb25SZXNvbHZlciBmcm9tIFwiLi4vY29uZmlndXJhdGlvbi1yZXNvbHZlci5qc1wiXG5pbXBvcnQgQmFja2dyb3VuZEpvYlJlZ2lzdHJ5IGZyb20gXCIuL2pvYi1yZWdpc3RyeS5qc1wiXG5pbXBvcnQgQmFja2dyb3VuZEpvYnNTdGF0dXNSZXBvcnRlciBmcm9tIFwiLi9zdGF0dXMtcmVwb3J0ZXIuanNcIlxuXG5jb25zdCBCRUFDT05fUkVBRFlfVElNRU9VVF9NUyA9IDUwMDBcblxuZXhwb3J0IGNsYXNzIEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlIGV4dGVuZHMgRXJyb3Ige1xuICAvKipcbiAgICogQ3JlYXRlcyBhIHBlcmZvcm1lZC1qb2IgZmFpbHVyZSBhZnRlciBpdHMgdGVybWluYWwgcmVwb3J0IGlzIGFja25vd2xlZGdlZC5cbiAgICogQHBhcmFtIHtFcnJvcn0gY2F1c2UgLSBBIGpvYiBwZXJmb3JtIGVycm9yIHdob3NlIGZhaWxlZCB0ZXJtaW5hbCByZXBvcnQgd2FzIGFja25vd2xlZGdlZC5cbiAgICovXG4gIGNvbnN0cnVjdG9yKGNhdXNlKSB7XG4gICAgc3VwZXIoY2F1c2UubWVzc2FnZSwge2NhdXNlfSlcbiAgICB0aGlzLm5hbWUgPSBcIkJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlXCJcbiAgfVxufVxuXG4vKipcbiAqIFJ1bnMgcmVwb3J0IGJlYWNvbiByZWFkeSBlcnJvci5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBjb25maWd1cmF0aW9uIC0gQ29uZmlndXJhdGlvbi5cbiAqIEBwYXJhbSB7P30gZXJyb3IgLSBCZWFjb24gcmVhZGluZXNzIGVycm9yLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIHJlcG9ydEJlYWNvblJlYWR5RXJyb3IoY29uZmlndXJhdGlvbiwgZXJyb3IpIHtcbiAgY29uc3QgZXJyb3JFdmVudHMgPSBjb25maWd1cmF0aW9uLmdldEVycm9yRXZlbnRzKClcbiAgY29uc3Qgbm9ybWFsaXplZEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgY29udGV4dDoge3BlZXJUeXBlOiBcImJhY2tncm91bmQtam9icy1ydW5uZXJcIiwgc3RhZ2U6IFwiYmVhY29uLXJlYWR5XCJ9LFxuICAgIGVycm9yOiBub3JtYWxpemVkRXJyb3JcbiAgfVxuICBjb25zdCBoYXNMaXN0ZW5lciA9IGVycm9yRXZlbnRzLmxpc3RlbmVyQ291bnQoXCJmcmFtZXdvcmstZXJyb3JcIikgPiAwXG4gICAgfHwgZXJyb3JFdmVudHMubGlzdGVuZXJDb3VudChcImFsbC1lcnJvclwiKSA+IDBcblxuICBlcnJvckV2ZW50cy5lbWl0KFwiZnJhbWV3b3JrLWVycm9yXCIsIHBheWxvYWQpXG4gIGVycm9yRXZlbnRzLmVtaXQoXCJhbGwtZXJyb3JcIiwgey4uLnBheWxvYWQsIGVycm9yVHlwZTogXCJmcmFtZXdvcmstZXJyb3JcIn0pXG5cbiAgaWYgKCFoYXNMaXN0ZW5lcikge1xuICAgIGNvbnNvbGUuZXJyb3IoYFt2ZWxvY2lvdXMgZnJhbWV3b3JrLWVycm9yIHN0YWdlPWJlYWNvbi1yZWFkeV0gJHtub3JtYWxpemVkRXJyb3IubWVzc2FnZX1gKVxuICB9XG59XG5cbi8qKlxuICogUnVucyBjb25uZWN0IGJlYWNvbi5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBjb25maWd1cmF0aW9uIC0gQ29uZmlndXJhdGlvbi5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICovXG5hc3luYyBmdW5jdGlvbiBjb25uZWN0QmVhY29uKGNvbmZpZ3VyYXRpb24pIHtcbiAgY29uc3QgYmVhY29uQ2xpZW50ID0gYXdhaXQgY29uZmlndXJhdGlvbi5jb25uZWN0QmVhY29uKHtwZWVyVHlwZTogXCJiYWNrZ3JvdW5kLWpvYnMtcnVubmVyXCJ9KVxuXG4gIGlmICghYmVhY29uQ2xpZW50KSByZXR1cm5cblxuICB0cnkge1xuICAgIGF3YWl0IGJlYWNvbkNsaWVudC53YWl0Rm9yUmVhZHkoe3RpbWVvdXRNczogQkVBQ09OX1JFQURZX1RJTUVPVVRfTVN9KVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHJlcG9ydEJlYWNvblJlYWR5RXJyb3IoY29uZmlndXJhdGlvbiwgZXJyb3IpXG4gIH1cbn1cblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgcHJvY2VzcyB0aXRsZSB0byBzaG93IHdoaWxlIGEgam9iIHJ1bnM6IHRoZSBqb2IgY2xhc3MncyBkZWNsYXJlZFxuICogYHN0YXRpYyBwcm9jZXNzVGl0bGVgLCBlbHNlIGEgYHZlbG9jaW91cyBqb2ItcnVubmVyOiA8Sm9iTmFtZT5gIGZhbGxiYWNrLlxuICogQHBhcmFtIHt0eXBlb2YgaW1wb3J0KFwiLi9qb2IuanNcIikuZGVmYXVsdH0gSm9iQ2xhc3MgLSBSZXNvbHZlZCBqb2IgY2xhc3MuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlBheWxvYWR9IHBheWxvYWQgLSBQYXlsb2FkLlxuICogQHJldHVybnMge3N0cmluZ30gLSBQcm9jZXNzIHRpdGxlLlxuICovXG5mdW5jdGlvbiBydW5uZXJQcm9jZXNzVGl0bGUoSm9iQ2xhc3MsIHBheWxvYWQpIHtcbiAgY29uc3QgZGVjbGFyZWQgPSBKb2JDbGFzcy5wcm9jZXNzVGl0bGVcblxuICBpZiAodHlwZW9mIGRlY2xhcmVkID09PSBcInN0cmluZ1wiICYmIGRlY2xhcmVkLmxlbmd0aCA+IDApIHJldHVybiBkZWNsYXJlZFxuXG4gIHJldHVybiBgdmVsb2Npb3VzIGpvYi1ydW5uZXI6ICR7cGF5bG9hZC5qb2JOYW1lfWBcbn1cblxuLyoqXG4gKiBSdW5zIHJ1biBqb2IgcGF5bG9hZC5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUGF5bG9hZH0gcGF5bG9hZCAtIFBheWxvYWQuXG4gKiBAcGFyYW0ge29iamVjdH0gW29wdGlvbnNdIC0gUnVubmVyIG9wdGlvbnMuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmNsb3NlQ29ubmVjdGlvbnNdIC0gV2hldGhlciB0byBncmFjZWZ1bGx5IGNsb3NlIGZyYW1ld29yayBjb25uZWN0aW9ucyBhZnRlciB0aGUgam9iLlxuICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy5tYW5hZ2VQcm9jZXNzVGl0bGVdIC0gV2hldGhlciB0byBzZXQgdGhlIHBlci1qb2IgcHJvY2VzcyB0aXRsZSBhbmQgcmVzdG9yZSBpdCBhZnRlcndhcmRzLiBPZmYgZm9yIGNvbmN1cnJlbnQgcG9vbGVkIHJ1bm5lcnMsIHdoZXJlIGludGVybGVhdmVkIHNuYXBzaG90L3Jlc3RvcmUgb2YgdGhlIHNpbmdsZSBwcm9jZXNzLXdpZGUgYHByb2Nlc3MudGl0bGVgIHdvdWxkIGNvcnJ1cHQgaXQ7IHRoZSBwb29sZWQgY2hpbGQgb3ducyBhbiBhZ2dyZWdhdGUgdGl0bGUgaW5zdGVhZC5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGFzeW5jIGZ1bmN0aW9uIHJ1bkpvYlBheWxvYWQocGF5bG9hZCwge2Nsb3NlQ29ubmVjdGlvbnMgPSB0cnVlLCBtYW5hZ2VQcm9jZXNzVGl0bGUgPSB0cnVlfSA9IHt9KSB7XG4gIGNvbnN0IGNvbmZpZ3VyYXRpb24gPSBhd2FpdCBjb25maWd1cmF0aW9uUmVzb2x2ZXIoKVxuICBjb25maWd1cmF0aW9uLnNldEN1cnJlbnQoKVxuICBhd2FpdCBjb25maWd1cmF0aW9uLmluaXRpYWxpemUoe3R5cGU6IFwiYmFja2dyb3VuZC1qb2JzLXJ1bm5lclwifSlcbiAgYXdhaXQgY29ubmVjdEJlYWNvbihjb25maWd1cmF0aW9uKVxuICBjb25zdCByZXBvcnRlciA9IG5ldyBCYWNrZ3JvdW5kSm9ic1N0YXR1c1JlcG9ydGVyKHtjb25maWd1cmF0aW9ufSlcblxuICBjb25zdCByZWdpc3RyeSA9IG5ldyBCYWNrZ3JvdW5kSm9iUmVnaXN0cnkoe2NvbmZpZ3VyYXRpb259KVxuICBhd2FpdCByZWdpc3RyeS5sb2FkKClcbiAgY29uc3QgSm9iQ2xhc3MgPSByZWdpc3RyeS5nZXRKb2JCeU5hbWUocGF5bG9hZC5qb2JOYW1lKVxuICBjb25zdCBqb2JJbnN0YW5jZSA9IG5ldyBKb2JDbGFzcygpXG4gIC8qKlxuICAgKiBQZXJmb3JtLlxuICAgKiBAdHlwZSB7KC4uLmFyZ3M6IEFycmF5PD8+KSA9PiBQcm9taXNlPHZvaWQ+fSAqL1xuICBjb25zdCBwZXJmb3JtID0gam9iSW5zdGFuY2UucGVyZm9ybVxuXG4gIC8vIE5hbWUgdGhlIHByb2Nlc3MgYWZ0ZXIgdGhlIGpvYiBpdCBpcyBydW5uaW5nIHNvIGBwc2AvYHRvcGAgc2hvdyB3aGF0IGVhY2hcbiAgLy8gcnVubmVyIGlzIGRvaW5nOyByZXN0b3JlZCBpbiB0aGUgYGZpbmFsbHlgIGJlbG93IHdoZW4gdGhlIGpvYiBmaW5pc2hlcy5cbiAgLy8gU2tpcHBlZCBmb3IgY29uY3VycmVudCBwb29sZWQgcnVubmVycywgd2hvc2UgY2hpbGQgb3ducyBhbiBhZ2dyZWdhdGUgdGl0bGUuXG4gIGNvbnN0IHByZXZpb3VzVGl0bGUgPSBwcm9jZXNzLnRpdGxlXG4gIGlmIChtYW5hZ2VQcm9jZXNzVGl0bGUpIHByb2Nlc3MudGl0bGUgPSBydW5uZXJQcm9jZXNzVGl0bGUoSm9iQ2xhc3MsIHBheWxvYWQpXG5cbiAgdHJ5IHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi53aXRoQ29ubmVjdGlvbnMoe25hbWU6IGBCYWNrZ3JvdW5kIGpvYiBydW5uZXI6ICR7cGF5bG9hZC5qb2JOYW1lfWB9LCBhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHBlcmZvcm0uYXBwbHkoam9iSW5zdGFuY2UsIHBheWxvYWQuYXJncyB8fCBbXSlcbiAgICAgIH0pXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IHBlcmZvcm1lZEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gICAgICBpZiAocGF5bG9hZC5pZCkge1xuICAgICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICAgIGpvYklkOiBwYXlsb2FkLmlkLFxuICAgICAgICAgIHN0YXR1czogXCJmYWlsZWRcIixcbiAgICAgICAgICBlcnJvcjogcGVyZm9ybWVkRXJyb3IsXG4gICAgICAgICAgaGFuZG9mZklkOiBwYXlsb2FkLmhhbmRvZmZJZCxcbiAgICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgICBoYW5kZWRPZmZBdE1zOiBwYXlsb2FkLmhhbmRlZE9mZkF0TXMsXG4gICAgICAgICAgbWF4RHVyYXRpb25NczogMzAwMDBcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgdGhyb3cgbmV3IEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlKHBlcmZvcm1lZEVycm9yKVxuICAgIH1cblxuICAgIGlmIChwYXlsb2FkLmlkKSB7XG4gICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICBqb2JJZDogcGF5bG9hZC5pZCxcbiAgICAgICAgc3RhdHVzOiBcImNvbXBsZXRlZFwiLFxuICAgICAgICBoYW5kb2ZmSWQ6IHBheWxvYWQuaGFuZG9mZklkLFxuICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgaGFuZGVkT2ZmQXRNczogcGF5bG9hZC5oYW5kZWRPZmZBdE1zLFxuICAgICAgICBtYXhEdXJhdGlvbk1zOiAzMDAwMFxuICAgICAgfSlcbiAgICB9XG4gIH0gZmluYWxseSB7XG4gICAgLy8gUmVzdG9yZSB0aGUgcnVubmVyJ3MgYmFzZSB0aXRsZSBzbyBhIGxpbmdlcmluZy9pZGxlIHJ1bm5lciAob3IgYSByZXVzZWRcbiAgICAvLyBvbmUpIGRvZXNuJ3QgbWlzcmVwb3J0IGEgZmluaXNoZWQgam9iIGFzIHN0aWxsIHJ1bm5pbmcuXG4gICAgaWYgKG1hbmFnZVByb2Nlc3NUaXRsZSkgcHJvY2Vzcy50aXRsZSA9IHByZXZpb3VzVGl0bGVcbiAgICBpZiAoY2xvc2VDb25uZWN0aW9ucykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi5kaXNjb25uZWN0QmVhY29uKClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIGF3YWl0IGNvbmZpZ3VyYXRpb24uY2xvc2VEYXRhYmFzZUNvbm5lY3Rpb25zKClcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiJdfQ==