velocious 1.0.551 → 1.0.552
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/build/background-jobs/json-socket.js +25 -0
- package/build/background-jobs/socket-request.js +48 -6
- package/build/background-jobs/status-reporter.js +14 -2
- package/build/configuration-types.js +1 -0
- package/build/database/drivers/base.js +12 -0
- package/build/database/drivers/mssql/index.js +52 -3
- package/build/database/drivers/mysql/index.js +34 -0
- package/build/database/structure-sql-loader.js +12 -6
- package/build/src/background-jobs/json-socket.d.ts +20 -0
- package/build/src/background-jobs/json-socket.d.ts.map +1 -1
- package/build/src/background-jobs/json-socket.js +25 -1
- package/build/src/background-jobs/socket-request.d.ts +13 -1
- package/build/src/background-jobs/socket-request.d.ts.map +1 -1
- package/build/src/background-jobs/socket-request.js +44 -7
- package/build/src/background-jobs/status-reporter.d.ts +12 -1
- package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
- package/build/src/background-jobs/status-reporter.js +14 -3
- package/build/src/configuration-types.d.ts +5 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -1
- package/build/src/database/drivers/base.d.ts +9 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +12 -1
- package/build/src/database/drivers/mssql/index.d.ts +9 -0
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +45 -2
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +35 -1
- package/build/src/database/structure-sql-loader.d.ts.map +1 -1
- package/build/src/database/structure-sql-loader.js +14 -8
- package/package.json +1 -1
- package/src/background-jobs/json-socket.js +25 -0
- package/src/background-jobs/socket-request.js +48 -6
- package/src/background-jobs/status-reporter.js +14 -2
- package/src/configuration-types.js +1 -0
- package/src/database/drivers/base.js +12 -0
- package/src/database/drivers/mssql/index.js +52 -3
- package/src/database/drivers/mysql/index.js +34 -0
- package/src/database/structure-sql-loader.js +12 -6
|
@@ -44,6 +44,19 @@ export default class JsonSocket extends EventEmitter {
|
|
|
44
44
|
* the main's liveness sweep to drop a wedged/silent worker.
|
|
45
45
|
* @type {number | undefined} */
|
|
46
46
|
this.lastSeenAt = undefined
|
|
47
|
+
/**
|
|
48
|
+
* Internal test-only observability counter — NOT public API. Number of times
|
|
49
|
+
* `destroy()` has run, incremented immediately before the raw socket
|
|
50
|
+
* `destroy()` call so specs can assert the actual teardown method that ran
|
|
51
|
+
* rather than a self-reported flag. Do not read or depend on this outside tests.
|
|
52
|
+
* @type {number} */
|
|
53
|
+
this._destroyCallCount = 0
|
|
54
|
+
/**
|
|
55
|
+
* Internal test-only observability counter — NOT public API. Number of times
|
|
56
|
+
* `close()` has run, incremented immediately before the raw socket `end()`
|
|
57
|
+
* call. Do not read or depend on this outside tests.
|
|
58
|
+
* @type {number} */
|
|
59
|
+
this._closeCallCount = 0
|
|
47
60
|
this.buffer = ""
|
|
48
61
|
this.socket.setEncoding("utf8")
|
|
49
62
|
this.socket.on("data", (chunk) => this._onData(String(chunk)))
|
|
@@ -91,6 +104,18 @@ export default class JsonSocket extends EventEmitter {
|
|
|
91
104
|
* @returns {void}
|
|
92
105
|
*/
|
|
93
106
|
close() {
|
|
107
|
+
this._closeCallCount++
|
|
94
108
|
this.socket.end()
|
|
95
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Forcibly destroys the underlying socket. Unlike {@link close}, which
|
|
113
|
+
* half-closes gracefully via `end()`, this tears the connection down
|
|
114
|
+
* immediately so a stalled/aborted request does not leave the socket alive.
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
destroy() {
|
|
118
|
+
this._destroyCallCount++
|
|
119
|
+
this.socket.destroy()
|
|
120
|
+
}
|
|
96
121
|
}
|
|
@@ -15,6 +15,16 @@ export default class BackgroundJobsSocketRequest {
|
|
|
15
15
|
this.host = host
|
|
16
16
|
this.port = port
|
|
17
17
|
this.role = role
|
|
18
|
+
/**
|
|
19
|
+
* Internal test-only observability reference — NOT public API. Holds the
|
|
20
|
+
* JsonSocket wrapper this request created so the timeout spec can inspect the
|
|
21
|
+
* wrapper's own `destroy()`/`close()` call counters — direct evidence of which
|
|
22
|
+
* teardown method actually ran, not a self-reported flag. Retains the single
|
|
23
|
+
* (already torn-down) wrapper for the request's lifetime. Do not expose or
|
|
24
|
+
* depend on this outside tests.
|
|
25
|
+
* @type {JsonSocket | undefined}
|
|
26
|
+
*/
|
|
27
|
+
this._jsonSocket = undefined
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/**
|
|
@@ -23,28 +33,60 @@ export default class BackgroundJobsSocketRequest {
|
|
|
23
33
|
* @param {object} args - Options.
|
|
24
34
|
* @param {(jsonSocket: JsonSocket) => void} args.onConnect - Called after the socket connects.
|
|
25
35
|
* @param {(args: {message: import("./types.js").BackgroundJobSocketMessage, resolve: (value: T) => void, reject: (error: Error) => void}) => void} args.onMessage - Message handler.
|
|
36
|
+
* @param {AbortSignal} [args.signal] - Aborts the request; on abort the pending socket is destroyed and the promise rejects with the signal reason when it is an Error, otherwise with a generic abort Error.
|
|
26
37
|
* @returns {Promise<T>} - Resolved request value.
|
|
27
38
|
*/
|
|
28
|
-
async run({onConnect, onMessage}) {
|
|
39
|
+
async run({onConnect, onMessage, signal}) {
|
|
29
40
|
const socket = net.createConnection({host: this.host, port: this.port})
|
|
30
41
|
const jsonSocket = new JsonSocket(socket)
|
|
31
42
|
|
|
43
|
+
this._jsonSocket = jsonSocket
|
|
44
|
+
|
|
32
45
|
return await new Promise((resolve, reject) => {
|
|
33
46
|
let finished = false
|
|
34
47
|
/**
|
|
35
48
|
* Finish.
|
|
49
|
+
* @param {object} options - Options.
|
|
50
|
+
* @param {boolean} [options.destroy] - Destroy the socket instead of gracefully closing it.
|
|
36
51
|
* @param {() => void} callback - Finish callback.
|
|
37
52
|
*/
|
|
38
|
-
const finish = (callback) => {
|
|
53
|
+
const finish = ({destroy = false} = {}, callback) => {
|
|
39
54
|
if (finished) return
|
|
40
55
|
finished = true
|
|
56
|
+
if (signal) signal.removeEventListener("abort", onAbort)
|
|
41
57
|
jsonSocket.removeAllListeners()
|
|
42
|
-
|
|
58
|
+
|
|
59
|
+
if (destroy) {
|
|
60
|
+
jsonSocket.destroy()
|
|
61
|
+
} else {
|
|
62
|
+
jsonSocket.close()
|
|
63
|
+
}
|
|
64
|
+
|
|
43
65
|
callback()
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Handles a cooperative abort: tears down the pending socket and rejects
|
|
70
|
+
* with the signal reason when it is an Error.
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
const onAbort = () => {
|
|
74
|
+
const reason = signal?.reason
|
|
75
|
+
|
|
76
|
+
finish({destroy: true}, () => reject(reason instanceof Error ? reason : new Error("Background job socket request aborted")))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (signal) {
|
|
80
|
+
if (signal.aborted) {
|
|
81
|
+
onAbort()
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
signal.addEventListener("abort", onAbort)
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
jsonSocket.on("error", (error) => {
|
|
47
|
-
finish(() => reject(error))
|
|
89
|
+
finish({}, () => reject(error))
|
|
48
90
|
})
|
|
49
91
|
|
|
50
92
|
/**
|
|
@@ -54,8 +96,8 @@ export default class BackgroundJobsSocketRequest {
|
|
|
54
96
|
jsonSocket.on("message", (message) => {
|
|
55
97
|
onMessage({
|
|
56
98
|
message,
|
|
57
|
-
resolve: (value) => finish(() => resolve(value)),
|
|
58
|
-
reject: (error) => finish(() => reject(error))
|
|
99
|
+
resolve: (value) => finish({}, () => resolve(value)),
|
|
100
|
+
reject: (error) => finish({}, () => reject(error))
|
|
59
101
|
})
|
|
60
102
|
})
|
|
61
103
|
|
|
@@ -15,11 +15,20 @@ export default class BackgroundJobsStatusReporter {
|
|
|
15
15
|
* @param {import("../configuration.js").default} args.configuration - Configuration.
|
|
16
16
|
* @param {string} [args.host] - Host.
|
|
17
17
|
* @param {number} [args.port] - Port.
|
|
18
|
+
* @param {number} [args.attemptTimeoutMs] - Per-attempt socket-request timeout in milliseconds (default: 5000).
|
|
18
19
|
*/
|
|
19
|
-
constructor({configuration, host, port}) {
|
|
20
|
+
constructor({configuration, host, port, attemptTimeoutMs = 5000}) {
|
|
20
21
|
this.configuration = configuration
|
|
21
22
|
this.host = host
|
|
22
23
|
this.port = port
|
|
24
|
+
this.attemptTimeoutMs = attemptTimeoutMs
|
|
25
|
+
/**
|
|
26
|
+
* Internal test-only observability state — NOT public API. References the most
|
|
27
|
+
* recent socket request so the timeout spec can inspect how its socket was torn
|
|
28
|
+
* down. Do not expose or depend on this outside tests.
|
|
29
|
+
* @type {BackgroundJobsSocketRequest | undefined}
|
|
30
|
+
*/
|
|
31
|
+
this._lastRequest = undefined
|
|
23
32
|
this.logger = new Logger(this)
|
|
24
33
|
}
|
|
25
34
|
|
|
@@ -39,10 +48,13 @@ export default class BackgroundJobsStatusReporter {
|
|
|
39
48
|
const host = this.host || config.host
|
|
40
49
|
const port = typeof this.port === "number" ? this.port : config.port
|
|
41
50
|
|
|
42
|
-
await timeout({timeout:
|
|
51
|
+
await timeout({timeout: this.attemptTimeoutMs}, async ({control}) => {
|
|
43
52
|
const request = new BackgroundJobsSocketRequest({host, port, role: "reporter"})
|
|
44
53
|
|
|
54
|
+
this._lastRequest = request
|
|
55
|
+
|
|
45
56
|
await request.run({
|
|
57
|
+
signal: control.signal,
|
|
46
58
|
onConnect: (jsonSocket) => {
|
|
47
59
|
jsonSocket.send({
|
|
48
60
|
type: status === "completed" ? "job-complete" : "job-failed",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
* @property {function() : ?} [getConnection] - Custom connection factory override.
|
|
73
73
|
* @property {string} [host] - Database host.
|
|
74
74
|
* @property {boolean} [migrations] - Whether migrations are enabled for this database.
|
|
75
|
+
* @property {boolean} [multipleStatements] - (MySQL) Opt in to multi-statement queries so a whole structure SQL dump loads in one round-trip via `StructureSqlLoader`. Off by default; ordinary queries otherwise reject stacked statements.
|
|
75
76
|
* @property {string} [password] - Password for the database user.
|
|
76
77
|
* @property {number} [port] - Database port.
|
|
77
78
|
* @property {string} [primaryKeyType] - Default type for implicit migration primary keys and references. Defaults to `uuid`.
|
|
@@ -583,6 +583,18 @@ export default class VelociousDatabaseDriversBase {
|
|
|
583
583
|
return null
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Executes a whole multi-statement structure SQL script in a single round-trip when
|
|
588
|
+
* the driver supports it, running on this connection (so the caller's foreign-key
|
|
589
|
+
* handling applies). Returns true if it ran the whole script; false when the caller
|
|
590
|
+
* should run the statements individually. The base driver has no batch path.
|
|
591
|
+
* @param {string} _structureSql - Full multi-statement structure SQL.
|
|
592
|
+
* @returns {Promise<boolean>} - Whether the script was executed as one batch.
|
|
593
|
+
*/
|
|
594
|
+
async execStructureScript(_structureSql) {
|
|
595
|
+
return false
|
|
596
|
+
}
|
|
597
|
+
|
|
586
598
|
/**
|
|
587
599
|
* Runs get table by name.
|
|
588
600
|
* @param {string} name - Name.
|
|
@@ -23,6 +23,17 @@ import Upsert from "./sql/upsert.js"
|
|
|
23
23
|
import Update from "./sql/update.js"
|
|
24
24
|
import UUID from "pure-uuid"
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* SQL Server error number raised by `sp_releaseapplock` when the current
|
|
28
|
+
* session does not hold the requested application lock. Releasing a lock the
|
|
29
|
+
* session no longer holds is a normal race (a shared connection's final
|
|
30
|
+
* check-in may already have auto-released it), which the cross-driver
|
|
31
|
+
* `releaseAdvisoryLock` contract models by resolving to `false`. We translate
|
|
32
|
+
* this specific error into that result rather than letting it escape.
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
const APPLOCK_NOT_HELD_ERROR_NUMBER = 1223
|
|
36
|
+
|
|
26
37
|
export default class VelociousDatabaseDriversMssql extends Base{
|
|
27
38
|
async connect() {
|
|
28
39
|
const args = this.getArgs()
|
|
@@ -640,18 +651,56 @@ export default class VelociousDatabaseDriversMssql extends Base{
|
|
|
640
651
|
|
|
641
652
|
/**
|
|
642
653
|
* Runs release advisory lock.
|
|
654
|
+
*
|
|
655
|
+
* `sp_releaseapplock` returns 0 when the lock was released, but SQL Server
|
|
656
|
+
* raises error {@link APPLOCK_NOT_HELD_ERROR_NUMBER} instead of returning a
|
|
657
|
+
* failure code when the session does not currently hold the lock. That
|
|
658
|
+
* error aborts the batch before the trailing `SELECT` can run, so we catch
|
|
659
|
+
* it and resolve to `false` to honor the cross-driver contract for an
|
|
660
|
+
* already-unheld lock.
|
|
643
661
|
* @param {string} name - Lock name.
|
|
644
662
|
* @returns {Promise<boolean>} - True if the lock was held by this session and has now been released.
|
|
645
663
|
*/
|
|
646
664
|
async _releaseAdvisoryLock(name) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
665
|
+
let rows
|
|
666
|
+
|
|
667
|
+
try {
|
|
668
|
+
rows = await this.query(
|
|
669
|
+
`DECLARE @velocious_advisory_lock_result INT; EXEC @velocious_advisory_lock_result = sp_releaseapplock @Resource = ${this.quote(name)}, @LockOwner = 'Session'; SELECT @velocious_advisory_lock_result AS velocious_advisory_lock_result`
|
|
670
|
+
)
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (this._isApplockNotHeldError(error)) return false
|
|
673
|
+
|
|
674
|
+
throw error
|
|
675
|
+
}
|
|
676
|
+
|
|
650
677
|
const result = Number(rows?.[0]?.velocious_advisory_lock_result)
|
|
651
678
|
|
|
652
679
|
return result === 0
|
|
653
680
|
}
|
|
654
681
|
|
|
682
|
+
/**
|
|
683
|
+
* Detects the SQL Server "application lock is not currently held" error
|
|
684
|
+
* raised by `sp_releaseapplock`. It walks the wrapped-error cause chain
|
|
685
|
+
* because `query` re-wraps the driver's `RequestError` in a plain `Error`,
|
|
686
|
+
* and matches on the stable numeric error number rather than the message.
|
|
687
|
+
* @param {unknown} error - Error thrown while releasing the lock.
|
|
688
|
+
* @returns {boolean} - True if the error means the lock was not held by this session.
|
|
689
|
+
*/
|
|
690
|
+
_isApplockNotHeldError(error) {
|
|
691
|
+
let current = error
|
|
692
|
+
|
|
693
|
+
while (current instanceof Error) {
|
|
694
|
+
const errorNumber = /** @type {{number?: unknown}} */ (current).number
|
|
695
|
+
|
|
696
|
+
if (typeof errorNumber === "number" && errorNumber === APPLOCK_NOT_HELD_ERROR_NUMBER) return true
|
|
697
|
+
|
|
698
|
+
current = current.cause
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return false
|
|
702
|
+
}
|
|
703
|
+
|
|
655
704
|
/**
|
|
656
705
|
* Returns true if any session currently holds the application lock.
|
|
657
706
|
*
|
|
@@ -190,6 +190,10 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
190
190
|
|
|
191
191
|
if ("username" in args) connectArgs["user"] = args["username"]
|
|
192
192
|
if ("charset" in args) connectArgs["charset"] = args["charset"]
|
|
193
|
+
// Opt-in only. Lets a whole structure SQL dump run in one round-trip via
|
|
194
|
+
// {@link execStructureScript}; off by default so ordinary queries keep rejecting
|
|
195
|
+
// stacked statements.
|
|
196
|
+
if ("multipleStatements" in args) connectArgs["multipleStatements"] = Boolean(digg(args, "multipleStatements"))
|
|
193
197
|
|
|
194
198
|
return connectArgs
|
|
195
199
|
}
|
|
@@ -417,6 +421,36 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
417
421
|
})
|
|
418
422
|
}
|
|
419
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Executes a full multi-statement structure SQL script in one round-trip when the
|
|
426
|
+
* connection was configured with `multipleStatements: true`. Runs on the pooled
|
|
427
|
+
* connection so the caller's `SET FOREIGN_KEY_CHECKS = 0` applies. Returns false so
|
|
428
|
+
* the caller runs statements individually when multi-statement queries are off.
|
|
429
|
+
* @param {string} structureSql - Full multi-statement structure SQL.
|
|
430
|
+
* @returns {Promise<boolean>} - Whether the script was executed as one batch.
|
|
431
|
+
*/
|
|
432
|
+
async execStructureScript(structureSql) {
|
|
433
|
+
if (!this.getArgs().multipleStatements) return false
|
|
434
|
+
|
|
435
|
+
// The batched pool call below bypasses Base#query, so re-run the same read-only
|
|
436
|
+
// write guard the per-statement path applies before executing the dump.
|
|
437
|
+
this._assertWritableQuery(structureSql)
|
|
438
|
+
|
|
439
|
+
if (!this.pool) await this.connect()
|
|
440
|
+
if (!this.pool) throw new Error("MySQL pool failed to initialize")
|
|
441
|
+
|
|
442
|
+
const pool = this.pool
|
|
443
|
+
|
|
444
|
+
await new Promise((resolve, reject) => {
|
|
445
|
+
pool.query(structureSql, (error) => {
|
|
446
|
+
if (error) reject(error)
|
|
447
|
+
else resolve(undefined)
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
420
454
|
/**
|
|
421
455
|
* Runs query to sql.
|
|
422
456
|
* @param {import("../../query/index.js").default} query - Query instance.
|
|
@@ -31,17 +31,23 @@ export default class StructureSqlLoader {
|
|
|
31
31
|
await db.disableForeignKeys()
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
// Prefer a single round-trip for the whole dump: a driver-native multi-statement
|
|
35
|
+
// batch (e.g. MySQL with `multipleStatements`) first, then a native `exec`
|
|
36
|
+
// connection (e.g. SQLite), and finally per-statement execution. Running the
|
|
37
|
+
// whole dump at once is far faster than issuing every CREATE separately.
|
|
38
|
+
if (!await db.execStructureScript(structureSql)) {
|
|
39
|
+
if (executableConnection) {
|
|
40
|
+
await executableConnection.exec(structureSql)
|
|
41
|
+
} else {
|
|
42
|
+
for (const statement of statements) {
|
|
43
|
+
await db.query(statement)
|
|
44
|
+
}
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
} finally {
|
|
42
48
|
await db.enableForeignKeys()
|
|
43
49
|
|
|
44
|
-
// The native `exec`
|
|
50
|
+
// The batch / native `exec` paths mutate the schema outside `Base#query`, so the
|
|
45
51
|
// usual post-DDL schema-cache invalidation never runs. Clear it here so a
|
|
46
52
|
// caller that read schema metadata before provisioning (e.g. an empty table
|
|
47
53
|
// list) does not keep seeing the pre-load schema afterwards. Harmless for the
|
|
@@ -40,6 +40,19 @@ export default class JsonSocket extends JsonSocket_base {
|
|
|
40
40
|
* the main's liveness sweep to drop a wedged/silent worker.
|
|
41
41
|
* @type {number | undefined} */
|
|
42
42
|
lastSeenAt: number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Internal test-only observability counter — NOT public API. Number of times
|
|
45
|
+
* `destroy()` has run, incremented immediately before the raw socket
|
|
46
|
+
* `destroy()` call so specs can assert the actual teardown method that ran
|
|
47
|
+
* rather than a self-reported flag. Do not read or depend on this outside tests.
|
|
48
|
+
* @type {number} */
|
|
49
|
+
_destroyCallCount: number;
|
|
50
|
+
/**
|
|
51
|
+
* Internal test-only observability counter — NOT public API. Number of times
|
|
52
|
+
* `close()` has run, incremented immediately before the raw socket `end()`
|
|
53
|
+
* call. Do not read or depend on this outside tests.
|
|
54
|
+
* @type {number} */
|
|
55
|
+
_closeCallCount: number;
|
|
43
56
|
buffer: string;
|
|
44
57
|
/**
|
|
45
58
|
* Runs on data.
|
|
@@ -58,6 +71,13 @@ export default class JsonSocket extends JsonSocket_base {
|
|
|
58
71
|
* @returns {void}
|
|
59
72
|
*/
|
|
60
73
|
close(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Forcibly destroys the underlying socket. Unlike {@link close}, which
|
|
76
|
+
* half-closes gracefully via `end()`, this tears the connection down
|
|
77
|
+
* immediately so a stalled/aborted request does not leave the socket alive.
|
|
78
|
+
* @returns {void}
|
|
79
|
+
*/
|
|
80
|
+
destroy(): void;
|
|
61
81
|
}
|
|
62
82
|
export {};
|
|
63
83
|
//# sourceMappingURL=json-socket.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-socket.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/json-socket.js"],"names":[],"mappings":";AAIA;IACE;;;OAGG;IACH,oBAFW,OAAO,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"json-socket.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/json-socket.js"],"names":[],"mappings":";AAIA;IACE;;;OAGG;IACH,oBAFW,OAAO,KAAK,EAAE,MAAM,EAyD9B;IArDC,kCAAoB;IACpB;;oCAEgC;IAChC,UADU,MAAM,GAAG,SAAS,CACH;IACzB;;yBAEqB;IACrB,4BADU,OAAO,CACsB;IACvC;;yBAEqB;IACrB,oBADU,OAAO,CACa;IAC9B;;yBAEqB;IACrB,mBADU,OAAO,CACY;IAC7B,sBAAsB;IACtB,mBADW,OAAO,CACY;IAC9B;;yBAEqB;IACrB,mBADU,OAAO,CACY;IAC7B;;;;;yBAKqB;IACrB,mBADU,OAAO,CACa;IAC9B;;;oCAGgC;IAChC,YADU,MAAM,GAAG,SAAS,CACD;IAC3B;;;;;wBAKoB;IACpB,mBADU,MAAM,CACU;IAC1B;;;;wBAIoB;IACpB,iBADU,MAAM,CACQ;IACxB,eAAgB;IAOlB;;;;OAIG;IACH,eAHW,MAAM,GACJ,IAAI,CAqBhB;IAED;;;;OAIG;IACH,cAHW,OAAC,GACC,IAAI,CAIhB;IAED;;;OAGG;IACH,SAFa,IAAI,CAKhB;IAED;;;;;OAKG;IACH,WAFa,IAAI,CAKhB;CACF"}
|
|
@@ -42,6 +42,19 @@ export default class JsonSocket extends EventEmitter {
|
|
|
42
42
|
* the main's liveness sweep to drop a wedged/silent worker.
|
|
43
43
|
* @type {number | undefined} */
|
|
44
44
|
this.lastSeenAt = undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Internal test-only observability counter — NOT public API. Number of times
|
|
47
|
+
* `destroy()` has run, incremented immediately before the raw socket
|
|
48
|
+
* `destroy()` call so specs can assert the actual teardown method that ran
|
|
49
|
+
* rather than a self-reported flag. Do not read or depend on this outside tests.
|
|
50
|
+
* @type {number} */
|
|
51
|
+
this._destroyCallCount = 0;
|
|
52
|
+
/**
|
|
53
|
+
* Internal test-only observability counter — NOT public API. Number of times
|
|
54
|
+
* `close()` has run, incremented immediately before the raw socket `end()`
|
|
55
|
+
* call. Do not read or depend on this outside tests.
|
|
56
|
+
* @type {number} */
|
|
57
|
+
this._closeCallCount = 0;
|
|
45
58
|
this.buffer = "";
|
|
46
59
|
this.socket.setEncoding("utf8");
|
|
47
60
|
this.socket.on("data", (chunk) => this._onData(String(chunk)));
|
|
@@ -85,7 +98,18 @@ export default class JsonSocket extends EventEmitter {
|
|
|
85
98
|
* @returns {void}
|
|
86
99
|
*/
|
|
87
100
|
close() {
|
|
101
|
+
this._closeCallCount++;
|
|
88
102
|
this.socket.end();
|
|
89
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Forcibly destroys the underlying socket. Unlike {@link close}, which
|
|
106
|
+
* half-closes gracefully via `end()`, this tears the connection down
|
|
107
|
+
* immediately so a stalled/aborted request does not leave the socket alive.
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
destroy() {
|
|
111
|
+
this._destroyCallCount++;
|
|
112
|
+
this.socket.destroy();
|
|
113
|
+
}
|
|
90
114
|
}
|
|
91
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
115
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoianNvbi1zb2NrZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL2pzb24tc29ja2V0LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLFlBQVksTUFBTSwyQkFBMkIsQ0FBQTtBQUVwRCxNQUFNLENBQUMsT0FBTyxPQUFPLFVBQVcsU0FBUSxZQUFZO0lBQ2xEOzs7T0FHRztJQUNILFlBQVksTUFBTTtRQUNoQixLQUFLLEVBQUUsQ0FBQTtRQUNQLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFBO1FBQ3BCOzt3Q0FFZ0M7UUFDaEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxTQUFTLENBQUE7UUFDekI7OzZCQUVxQjtRQUNyQixJQUFJLENBQUMsMEJBQTBCLEdBQUcsS0FBSyxDQUFBO1FBQ3ZDOzs2QkFFcUI7UUFDckIsSUFBSSxDQUFDLGtCQUFrQixHQUFHLElBQUksQ0FBQTtRQUM5Qjs7NkJBRXFCO1FBQ3JCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUE7UUFDN0Isc0JBQXNCO1FBQ3RCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxLQUFLLENBQUE7UUFDOUI7OzZCQUVxQjtRQUNyQixJQUFJLENBQUMsaUJBQWlCLEdBQUcsSUFBSSxDQUFBO1FBQzdCOzs7Ozs2QkFLcUI7UUFDckIsSUFBSSxDQUFDLGlCQUFpQixHQUFHLEtBQUssQ0FBQTtRQUM5Qjs7O3dDQUdnQztRQUNoQyxJQUFJLENBQUMsVUFBVSxHQUFHLFNBQVMsQ0FBQTtRQUMzQjs7Ozs7NEJBS29CO1FBQ3BCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxDQUFDLENBQUE7UUFDMUI7Ozs7NEJBSW9CO1FBQ3BCLElBQUksQ0FBQyxlQUFlLEdBQUcsQ0FBQyxDQUFBO1FBQ3hCLElBQUksQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFBO1FBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1FBQy9CLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQzlELElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUE7UUFDakQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFBO0lBQy9ELENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsT0FBTyxDQUFDLEtBQUs7UUFDWCxJQUFJLENBQUMsTUFBTSxJQUFJLEtBQUssQ0FBQTtRQUVwQixPQUFPLElBQUksRUFBRSxDQUFDO1lBQ1osTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7WUFDOUMsSUFBSSxZQUFZLEtBQUssQ0FBQyxDQUFDO2dCQUFFLE1BQUs7WUFFOUIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFlBQVksQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFBO1lBQ3RELElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsWUFBWSxHQUFHLENBQUMsQ0FBQyxDQUFBO1lBRWpELElBQUksQ0FBQyxJQUFJO2dCQUFFLFNBQVE7WUFFbkIsSUFBSSxDQUFDO2dCQUNILE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUE7Z0JBQ2hDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFBO1lBQy9CLENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNmLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFBO1lBQzNCLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxJQUFJLENBQUMsT0FBTztRQUNWLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDbkQsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUs7UUFDSCxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUE7UUFDdEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQTtJQUNuQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxPQUFPO1FBQ0wsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUE7UUFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQTtJQUN2QixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IEV2ZW50RW1pdHRlciBmcm9tIFwiLi4vdXRpbHMvZXZlbnQtZW1pdHRlci5qc1wiXG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEpzb25Tb2NrZXQgZXh0ZW5kcyBFdmVudEVtaXR0ZXIge1xuICAvKipcbiAgICogUnVucyBjb25zdHJ1Y3Rvci5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCJuZXRcIikuU29ja2V0fSBzb2NrZXQgLSBTb2NrZXQgaW5zdGFuY2UuXG4gICAqL1xuICBjb25zdHJ1Y3Rvcihzb2NrZXQpIHtcbiAgICBzdXBlcigpXG4gICAgdGhpcy5zb2NrZXQgPSBzb2NrZXRcbiAgICAvKipcbiAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICogQHR5cGUge3N0cmluZyB8IHVuZGVmaW5lZH0gKi9cbiAgICB0aGlzLndvcmtlcklkID0gdW5kZWZpbmVkXG4gICAgLyoqXG4gICAgICogTmFycm93cyB0aGUgcnVudGltZSB2YWx1ZSB0byB0aGUgZG9jdW1lbnRlZCB0eXBlLlxuICAgICAqIEB0eXBlIHtib29sZWFufSAqL1xuICAgIHRoaXMuc3VwcG9ydHNIYW5kb2ZmSWRSZXBvcnRpbmcgPSBmYWxzZVxuICAgIC8qKlxuICAgICAqIE5hcnJvd3MgdGhlIHJ1bnRpbWUgdmFsdWUgdG8gdGhlIGRvY3VtZW50ZWQgdHlwZS5cbiAgICAgKiBAdHlwZSB7Ym9vbGVhbn0gKi9cbiAgICB0aGlzLmFjY2VwdHNTcGF3bmVkSm9icyA9IHRydWVcbiAgICAvKipcbiAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICogQHR5cGUge2Jvb2xlYW59ICovXG4gICAgdGhpcy5hY2NlcHRzRm9ya2VkSm9icyA9IHRydWVcbiAgICAvKiogQHR5cGUge2Jvb2xlYW59ICovXG4gICAgdGhpcy5hY2NlcHRzUG9vbGVkSm9icyA9IGZhbHNlXG4gICAgLyoqXG4gICAgICogTmFycm93cyB0aGUgcnVudGltZSB2YWx1ZSB0byB0aGUgZG9jdW1lbnRlZCB0eXBlLlxuICAgICAqIEB0eXBlIHtib29sZWFufSAqL1xuICAgIHRoaXMuYWNjZXB0c0lubGluZUpvYnMgPSB0cnVlXG4gICAgLyoqXG4gICAgICogV2hldGhlciB0aGlzIHdvcmtlciBhZHZlcnRpc2VkIGhlYXJ0YmVhdCBzdXBwb3J0IGluIGl0cyBoZWxsby4gT25seVxuICAgICAqIGhlYXJ0YmVhdC1jYXBhYmxlIHdvcmtlcnMgYXJlIHN1YmplY3QgdG8gdGhlIG1haW4ncyBzdGFsZS1saXZlbmVzc1xuICAgICAqIGV2aWN0aW9uOyBhIGxlZ2FjeSB3b3JrZXIgKGUuZy4gbWlkIHJvbGxpbmcgZGVwbG95KSBpcyBleGVtcHQgc28gaXRzXG4gICAgICogYWN0aXZlIGxlYXNlcyBhcmUgbm90IHJlbGVhc2VkIHdoaWxlIGl0IGlzIHN0aWxsIHJ1bm5pbmcgdGhlbS5cbiAgICAgKiBAdHlwZSB7Ym9vbGVhbn0gKi9cbiAgICB0aGlzLnN1cHBvcnRzSGVhcnRiZWF0ID0gZmFsc2VcbiAgICAvKipcbiAgICAgKiBMYXN0IHRpbWUgKG1zKSB0aGUgbWFpbiBzYXcgYW55IG1lc3NhZ2UgZnJvbSB0aGlzIHdvcmtlciBzb2NrZXQ7IHVzZWQgYnlcbiAgICAgKiB0aGUgbWFpbidzIGxpdmVuZXNzIHN3ZWVwIHRvIGRyb3AgYSB3ZWRnZWQvc2lsZW50IHdvcmtlci5cbiAgICAgKiBAdHlwZSB7bnVtYmVyIHwgdW5kZWZpbmVkfSAqL1xuICAgIHRoaXMubGFzdFNlZW5BdCA9IHVuZGVmaW5lZFxuICAgIC8qKlxuICAgICAqIEludGVybmFsIHRlc3Qtb25seSBvYnNlcnZhYmlsaXR5IGNvdW50ZXIg4oCUIE5PVCBwdWJsaWMgQVBJLiBOdW1iZXIgb2YgdGltZXNcbiAgICAgKiBgZGVzdHJveSgpYCBoYXMgcnVuLCBpbmNyZW1lbnRlZCBpbW1lZGlhdGVseSBiZWZvcmUgdGhlIHJhdyBzb2NrZXRcbiAgICAgKiBgZGVzdHJveSgpYCBjYWxsIHNvIHNwZWNzIGNhbiBhc3NlcnQgdGhlIGFjdHVhbCB0ZWFyZG93biBtZXRob2QgdGhhdCByYW5cbiAgICAgKiByYXRoZXIgdGhhbiBhIHNlbGYtcmVwb3J0ZWQgZmxhZy4gRG8gbm90IHJlYWQgb3IgZGVwZW5kIG9uIHRoaXMgb3V0c2lkZSB0ZXN0cy5cbiAgICAgKiBAdHlwZSB7bnVtYmVyfSAqL1xuICAgIHRoaXMuX2Rlc3Ryb3lDYWxsQ291bnQgPSAwXG4gICAgLyoqXG4gICAgICogSW50ZXJuYWwgdGVzdC1vbmx5IG9ic2VydmFiaWxpdHkgY291bnRlciDigJQgTk9UIHB1YmxpYyBBUEkuIE51bWJlciBvZiB0aW1lc1xuICAgICAqIGBjbG9zZSgpYCBoYXMgcnVuLCBpbmNyZW1lbnRlZCBpbW1lZGlhdGVseSBiZWZvcmUgdGhlIHJhdyBzb2NrZXQgYGVuZCgpYFxuICAgICAqIGNhbGwuIERvIG5vdCByZWFkIG9yIGRlcGVuZCBvbiB0aGlzIG91dHNpZGUgdGVzdHMuXG4gICAgICogQHR5cGUge251bWJlcn0gKi9cbiAgICB0aGlzLl9jbG9zZUNhbGxDb3VudCA9IDBcbiAgICB0aGlzLmJ1ZmZlciA9IFwiXCJcbiAgICB0aGlzLnNvY2tldC5zZXRFbmNvZGluZyhcInV0ZjhcIilcbiAgICB0aGlzLnNvY2tldC5vbihcImRhdGFcIiwgKGNodW5rKSA9PiB0aGlzLl9vbkRhdGEoU3RyaW5nKGNodW5rKSkpXG4gICAgdGhpcy5zb2NrZXQub24oXCJjbG9zZVwiLCAoKSA9PiB0aGlzLmVtaXQoXCJjbG9zZVwiKSlcbiAgICB0aGlzLnNvY2tldC5vbihcImVycm9yXCIsIChlcnJvcikgPT4gdGhpcy5lbWl0KFwiZXJyb3JcIiwgZXJyb3IpKVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgb24gZGF0YS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGNodW5rIC0gRGF0YSBjaHVuay5cbiAgICogQHJldHVybnMge3ZvaWR9XG4gICAqL1xuICBfb25EYXRhKGNodW5rKSB7XG4gICAgdGhpcy5idWZmZXIgKz0gY2h1bmtcblxuICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICBjb25zdCBuZXdsaW5lSW5kZXggPSB0aGlzLmJ1ZmZlci5pbmRleE9mKFwiXFxuXCIpXG4gICAgICBpZiAobmV3bGluZUluZGV4ID09PSAtMSkgYnJlYWtcblxuICAgICAgY29uc3QgbGluZSA9IHRoaXMuYnVmZmVyLnNsaWNlKDAsIG5ld2xpbmVJbmRleCkudHJpbSgpXG4gICAgICB0aGlzLmJ1ZmZlciA9IHRoaXMuYnVmZmVyLnNsaWNlKG5ld2xpbmVJbmRleCArIDEpXG5cbiAgICAgIGlmICghbGluZSkgY29udGludWVcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9IEpTT04ucGFyc2UobGluZSlcbiAgICAgICAgdGhpcy5lbWl0KFwibWVzc2FnZVwiLCBtZXNzYWdlKVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgdGhpcy5lbWl0KFwiZXJyb3JcIiwgZXJyb3IpXG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgc2VuZC5cbiAgICogQHBhcmFtIHs/fSBtZXNzYWdlIC0gTWVzc2FnZSB0byBzZW5kLlxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIHNlbmQobWVzc2FnZSkge1xuICAgIHRoaXMuc29ja2V0LndyaXRlKGAke0pTT04uc3RyaW5naWZ5KG1lc3NhZ2UpfVxcbmApXG4gIH1cblxuICAvKipcbiAgICogUnVucyBjbG9zZS5cbiAgICogQHJldHVybnMge3ZvaWR9XG4gICAqL1xuICBjbG9zZSgpIHtcbiAgICB0aGlzLl9jbG9zZUNhbGxDb3VudCsrXG4gICAgdGhpcy5zb2NrZXQuZW5kKClcbiAgfVxuXG4gIC8qKlxuICAgKiBGb3JjaWJseSBkZXN0cm95cyB0aGUgdW5kZXJseWluZyBzb2NrZXQuIFVubGlrZSB7QGxpbmsgY2xvc2V9LCB3aGljaFxuICAgKiBoYWxmLWNsb3NlcyBncmFjZWZ1bGx5IHZpYSBgZW5kKClgLCB0aGlzIHRlYXJzIHRoZSBjb25uZWN0aW9uIGRvd25cbiAgICogaW1tZWRpYXRlbHkgc28gYSBzdGFsbGVkL2Fib3J0ZWQgcmVxdWVzdCBkb2VzIG5vdCBsZWF2ZSB0aGUgc29ja2V0IGFsaXZlLlxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIGRlc3Ryb3koKSB7XG4gICAgdGhpcy5fZGVzdHJveUNhbGxDb3VudCsrXG4gICAgdGhpcy5zb2NrZXQuZGVzdHJveSgpXG4gIH1cbn1cbiJdfQ==
|
|
@@ -14,21 +14,33 @@ export default class BackgroundJobsSocketRequest {
|
|
|
14
14
|
host: string;
|
|
15
15
|
port: number;
|
|
16
16
|
role: "client" | "reporter";
|
|
17
|
+
/**
|
|
18
|
+
* Internal test-only observability reference — NOT public API. Holds the
|
|
19
|
+
* JsonSocket wrapper this request created so the timeout spec can inspect the
|
|
20
|
+
* wrapper's own `destroy()`/`close()` call counters — direct evidence of which
|
|
21
|
+
* teardown method actually ran, not a self-reported flag. Retains the single
|
|
22
|
+
* (already torn-down) wrapper for the request's lifetime. Do not expose or
|
|
23
|
+
* depend on this outside tests.
|
|
24
|
+
* @type {JsonSocket | undefined}
|
|
25
|
+
*/
|
|
26
|
+
_jsonSocket: JsonSocket | undefined;
|
|
17
27
|
/**
|
|
18
28
|
* Runs run.
|
|
19
29
|
* @template T
|
|
20
30
|
* @param {object} args - Options.
|
|
21
31
|
* @param {(jsonSocket: JsonSocket) => void} args.onConnect - Called after the socket connects.
|
|
22
32
|
* @param {(args: {message: import("./types.js").BackgroundJobSocketMessage, resolve: (value: T) => void, reject: (error: Error) => void}) => void} args.onMessage - Message handler.
|
|
33
|
+
* @param {AbortSignal} [args.signal] - Aborts the request; on abort the pending socket is destroyed and the promise rejects with the signal reason when it is an Error, otherwise with a generic abort Error.
|
|
23
34
|
* @returns {Promise<T>} - Resolved request value.
|
|
24
35
|
*/
|
|
25
|
-
run<T>({ onConnect, onMessage }: {
|
|
36
|
+
run<T>({ onConnect, onMessage, signal }: {
|
|
26
37
|
onConnect: (jsonSocket: JsonSocket) => void;
|
|
27
38
|
onMessage: (args: {
|
|
28
39
|
message: import("./types.js").BackgroundJobSocketMessage;
|
|
29
40
|
resolve: (value: T) => void;
|
|
30
41
|
reject: (error: Error) => void;
|
|
31
42
|
}) => void;
|
|
43
|
+
signal?: AbortSignal | undefined;
|
|
32
44
|
}): Promise<T>;
|
|
33
45
|
}
|
|
34
46
|
import JsonSocket from "./json-socket.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-request.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/socket-request.js"],"names":[],"mappings":"AAKA;IACE;;;;;;OAMG;IACH,kCAJG;QAAqB,IAAI,EAAjB,MAAM;QACO,IAAI,EAAjB,MAAM;QACsB,IAAI,EAAhC,QAAQ,GAAG,UAAU;KAC/B,
|
|
1
|
+
{"version":3,"file":"socket-request.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/socket-request.js"],"names":[],"mappings":"AAKA;IACE;;;;;;OAMG;IACH,kCAJG;QAAqB,IAAI,EAAjB,MAAM;QACO,IAAI,EAAjB,MAAM;QACsB,IAAI,EAAhC,QAAQ,GAAG,UAAU;KAC/B,EAeA;IAbC,aAAgB;IAChB,aAAgB;IAChB,4BAAgB;IAChB;;;;;;;;OAQG;IACH,aAFU,UAAU,GAAG,SAAS,CAEJ;IAG9B;;;;;;;;OAQG;IACH,IAPa,CAAC,oCAEX;QAA+C,SAAS,EAAhD,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI;QAC8G,SAAS,EAAvJ,CAAC,IAAI,EAAE;YAAC,OAAO,EAAE,OAAO,YAAY,EAAE,0BAA0B,CAAC;YAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;YAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;SAAC,KAAK,IAAI;QACpH,MAAM;KACjC,GAAU,OAAO,CAAC,CAAC,CAAC,CAwEtB;CACF;uBA1GsB,kBAAkB"}
|
|
@@ -13,6 +13,16 @@ export default class BackgroundJobsSocketRequest {
|
|
|
13
13
|
this.host = host;
|
|
14
14
|
this.port = port;
|
|
15
15
|
this.role = role;
|
|
16
|
+
/**
|
|
17
|
+
* Internal test-only observability reference — NOT public API. Holds the
|
|
18
|
+
* JsonSocket wrapper this request created so the timeout spec can inspect the
|
|
19
|
+
* wrapper's own `destroy()`/`close()` call counters — direct evidence of which
|
|
20
|
+
* teardown method actually ran, not a self-reported flag. Retains the single
|
|
21
|
+
* (already torn-down) wrapper for the request's lifetime. Do not expose or
|
|
22
|
+
* depend on this outside tests.
|
|
23
|
+
* @type {JsonSocket | undefined}
|
|
24
|
+
*/
|
|
25
|
+
this._jsonSocket = undefined;
|
|
16
26
|
}
|
|
17
27
|
/**
|
|
18
28
|
* Runs run.
|
|
@@ -20,27 +30,54 @@ export default class BackgroundJobsSocketRequest {
|
|
|
20
30
|
* @param {object} args - Options.
|
|
21
31
|
* @param {(jsonSocket: JsonSocket) => void} args.onConnect - Called after the socket connects.
|
|
22
32
|
* @param {(args: {message: import("./types.js").BackgroundJobSocketMessage, resolve: (value: T) => void, reject: (error: Error) => void}) => void} args.onMessage - Message handler.
|
|
33
|
+
* @param {AbortSignal} [args.signal] - Aborts the request; on abort the pending socket is destroyed and the promise rejects with the signal reason when it is an Error, otherwise with a generic abort Error.
|
|
23
34
|
* @returns {Promise<T>} - Resolved request value.
|
|
24
35
|
*/
|
|
25
|
-
async run({ onConnect, onMessage }) {
|
|
36
|
+
async run({ onConnect, onMessage, signal }) {
|
|
26
37
|
const socket = net.createConnection({ host: this.host, port: this.port });
|
|
27
38
|
const jsonSocket = new JsonSocket(socket);
|
|
39
|
+
this._jsonSocket = jsonSocket;
|
|
28
40
|
return await new Promise((resolve, reject) => {
|
|
29
41
|
let finished = false;
|
|
30
42
|
/**
|
|
31
43
|
* Finish.
|
|
44
|
+
* @param {object} options - Options.
|
|
45
|
+
* @param {boolean} [options.destroy] - Destroy the socket instead of gracefully closing it.
|
|
32
46
|
* @param {() => void} callback - Finish callback.
|
|
33
47
|
*/
|
|
34
|
-
const finish = (callback) => {
|
|
48
|
+
const finish = ({ destroy = false } = {}, callback) => {
|
|
35
49
|
if (finished)
|
|
36
50
|
return;
|
|
37
51
|
finished = true;
|
|
52
|
+
if (signal)
|
|
53
|
+
signal.removeEventListener("abort", onAbort);
|
|
38
54
|
jsonSocket.removeAllListeners();
|
|
39
|
-
|
|
55
|
+
if (destroy) {
|
|
56
|
+
jsonSocket.destroy();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
jsonSocket.close();
|
|
60
|
+
}
|
|
40
61
|
callback();
|
|
41
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Handles a cooperative abort: tears down the pending socket and rejects
|
|
65
|
+
* with the signal reason when it is an Error.
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
const onAbort = () => {
|
|
69
|
+
const reason = signal?.reason;
|
|
70
|
+
finish({ destroy: true }, () => reject(reason instanceof Error ? reason : new Error("Background job socket request aborted")));
|
|
71
|
+
};
|
|
72
|
+
if (signal) {
|
|
73
|
+
if (signal.aborted) {
|
|
74
|
+
onAbort();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
signal.addEventListener("abort", onAbort);
|
|
78
|
+
}
|
|
42
79
|
jsonSocket.on("error", (error) => {
|
|
43
|
-
finish(() => reject(error));
|
|
80
|
+
finish({}, () => reject(error));
|
|
44
81
|
});
|
|
45
82
|
/**
|
|
46
83
|
* Handles the socket response message.
|
|
@@ -49,8 +86,8 @@ export default class BackgroundJobsSocketRequest {
|
|
|
49
86
|
jsonSocket.on("message", (message) => {
|
|
50
87
|
onMessage({
|
|
51
88
|
message,
|
|
52
|
-
resolve: (value) => finish(() => resolve(value)),
|
|
53
|
-
reject: (error) => finish(() => reject(error))
|
|
89
|
+
resolve: (value) => finish({}, () => resolve(value)),
|
|
90
|
+
reject: (error) => finish({}, () => reject(error))
|
|
54
91
|
});
|
|
55
92
|
});
|
|
56
93
|
socket.on("connect", () => {
|
|
@@ -60,4 +97,4 @@ export default class BackgroundJobsSocketRequest {
|
|
|
60
97
|
});
|
|
61
98
|
}
|
|
62
99
|
}
|
|
63
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29ja2V0LXJlcXVlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL3NvY2tldC1yZXF1ZXN0LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLEdBQUcsTUFBTSxLQUFLLENBQUE7QUFDckIsT0FBTyxVQUFVLE1BQU0sa0JBQWtCLENBQUE7QUFFekMsTUFBTSxDQUFDLE9BQU8sT0FBTywyQkFBMkI7SUFDOUM7Ozs7OztPQU1HO0lBQ0gsWUFBWSxFQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFDO1FBQzVCLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFBO1FBQ2hCLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFBO1FBQ2hCLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFBO1FBQ2hCOzs7Ozs7OztXQVFHO1FBQ0gsSUFBSSxDQUFDLFdBQVcsR0FBRyxTQUFTLENBQUE7SUFDOUIsQ0FBQztJQUVEOzs7Ozs7OztPQVFHO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFDLFNBQVMsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFDO1FBQ3RDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFDLElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxFQUFDLENBQUMsQ0FBQTtRQUN2RSxNQUFNLFVBQVUsR0FBRyxJQUFJLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUV6QyxJQUFJLENBQUMsV0FBVyxHQUFHLFVBQVUsQ0FBQTtRQUU3QixPQUFPLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDM0MsSUFBSSxRQUFRLEdBQUcsS0FBSyxDQUFBO1lBQ3BCOzs7OztlQUtHO1lBQ0gsTUFBTSxNQUFNLEdBQUcsQ0FBQyxFQUFDLE9BQU8sR0FBRyxLQUFLLEVBQUMsR0FBRyxFQUFFLEVBQUUsUUFBUSxFQUFFLEVBQUU7Z0JBQ2xELElBQUksUUFBUTtvQkFBRSxPQUFNO2dCQUNwQixRQUFRLEdBQUcsSUFBSSxDQUFBO2dCQUNmLElBQUksTUFBTTtvQkFBRSxNQUFNLENBQUMsbUJBQW1CLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFBO2dCQUN4RCxVQUFVLENBQUMsa0JBQWtCLEVBQUUsQ0FBQTtnQkFFL0IsSUFBSSxPQUFPLEVBQUUsQ0FBQztvQkFDWixVQUFVLENBQUMsT0FBTyxFQUFFLENBQUE7Z0JBQ3RCLENBQUM7cUJBQU0sQ0FBQztvQkFDTixVQUFVLENBQUMsS0FBSyxFQUFFLENBQUE7Z0JBQ3BCLENBQUM7Z0JBRUQsUUFBUSxFQUFFLENBQUE7WUFDWixDQUFDLENBQUE7WUFFRDs7OztlQUlHO1lBQ0gsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFO2dCQUNuQixNQUFNLE1BQU0sR0FBRyxNQUFNLEVBQUUsTUFBTSxDQUFBO2dCQUU3QixNQUFNLENBQUMsRUFBQyxPQUFPLEVBQUUsSUFBSSxFQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLE1BQU0sWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQyxDQUFDLENBQUE7WUFDOUgsQ0FBQyxDQUFBO1lBRUQsSUFBSSxNQUFNLEVBQUUsQ0FBQztnQkFDWCxJQUFJLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztvQkFDbkIsT0FBTyxFQUFFLENBQUE7b0JBQ1QsT0FBTTtnQkFDUixDQUFDO2dCQUVELE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUE7WUFDM0MsQ0FBQztZQUVELFVBQVUsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7Z0JBQy9CLE1BQU0sQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUE7WUFDakMsQ0FBQyxDQUFDLENBQUE7WUFFRjs7O2VBR0c7WUFDSCxVQUFVLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFO2dCQUNuQyxTQUFTLENBQUM7b0JBQ1IsT0FBTztvQkFDUCxPQUFPLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUNwRCxNQUFNLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO2lCQUNuRCxDQUFDLENBQUE7WUFDSixDQUFDLENBQUMsQ0FBQTtZQUVGLE1BQU0sQ0FBQyxFQUFFLENBQUMsU0FBUyxFQUFFLEdBQUcsRUFBRTtnQkFDeEIsVUFBVSxDQUFDLElBQUksQ0FBQyxFQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUMsQ0FBQyxDQUFBO2dCQUNqRCxTQUFTLENBQUMsVUFBVSxDQUFDLENBQUE7WUFDdkIsQ0FBQyxDQUFDLENBQUE7UUFDSixDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgbmV0IGZyb20gXCJuZXRcIlxuaW1wb3J0IEpzb25Tb2NrZXQgZnJvbSBcIi4vanNvbi1zb2NrZXQuanNcIlxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBCYWNrZ3JvdW5kSm9ic1NvY2tldFJlcXVlc3Qge1xuICAvKipcbiAgICogUnVucyBjb25zdHJ1Y3Rvci5cbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5ob3N0IC0gSG9zdC5cbiAgICogQHBhcmFtIHtudW1iZXJ9IGFyZ3MucG9ydCAtIFBvcnQuXG4gICAqIEBwYXJhbSB7XCJjbGllbnRcIiB8IFwicmVwb3J0ZXJcIn0gYXJncy5yb2xlIC0gU29ja2V0IHJvbGUuXG4gICAqL1xuICBjb25zdHJ1Y3Rvcih7aG9zdCwgcG9ydCwgcm9sZX0pIHtcbiAgICB0aGlzLmhvc3QgPSBob3N0XG4gICAgdGhpcy5wb3J0ID0gcG9ydFxuICAgIHRoaXMucm9sZSA9IHJvbGVcbiAgICAvKipcbiAgICAgKiBJbnRlcm5hbCB0ZXN0LW9ubHkgb2JzZXJ2YWJpbGl0eSByZWZlcmVuY2Ug4oCUIE5PVCBwdWJsaWMgQVBJLiBIb2xkcyB0aGVcbiAgICAgKiBKc29uU29ja2V0IHdyYXBwZXIgdGhpcyByZXF1ZXN0IGNyZWF0ZWQgc28gdGhlIHRpbWVvdXQgc3BlYyBjYW4gaW5zcGVjdCB0aGVcbiAgICAgKiB3cmFwcGVyJ3Mgb3duIGBkZXN0cm95KClgL2BjbG9zZSgpYCBjYWxsIGNvdW50ZXJzIOKAlCBkaXJlY3QgZXZpZGVuY2Ugb2Ygd2hpY2hcbiAgICAgKiB0ZWFyZG93biBtZXRob2QgYWN0dWFsbHkgcmFuLCBub3QgYSBzZWxmLXJlcG9ydGVkIGZsYWcuIFJldGFpbnMgdGhlIHNpbmdsZVxuICAgICAqIChhbHJlYWR5IHRvcm4tZG93bikgd3JhcHBlciBmb3IgdGhlIHJlcXVlc3QncyBsaWZldGltZS4gRG8gbm90IGV4cG9zZSBvclxuICAgICAqIGRlcGVuZCBvbiB0aGlzIG91dHNpZGUgdGVzdHMuXG4gICAgICogQHR5cGUge0pzb25Tb2NrZXQgfCB1bmRlZmluZWR9XG4gICAgICovXG4gICAgdGhpcy5fanNvblNvY2tldCA9IHVuZGVmaW5lZFxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgcnVuLlxuICAgKiBAdGVtcGxhdGUgVFxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIE9wdGlvbnMuXG4gICAqIEBwYXJhbSB7KGpzb25Tb2NrZXQ6IEpzb25Tb2NrZXQpID0+IHZvaWR9IGFyZ3Mub25Db25uZWN0IC0gQ2FsbGVkIGFmdGVyIHRoZSBzb2NrZXQgY29ubmVjdHMuXG4gICAqIEBwYXJhbSB7KGFyZ3M6IHttZXNzYWdlOiBpbXBvcnQoXCIuL3R5cGVzLmpzXCIpLkJhY2tncm91bmRKb2JTb2NrZXRNZXNzYWdlLCByZXNvbHZlOiAodmFsdWU6IFQpID0+IHZvaWQsIHJlamVjdDogKGVycm9yOiBFcnJvcikgPT4gdm9pZH0pID0+IHZvaWR9IGFyZ3Mub25NZXNzYWdlIC0gTWVzc2FnZSBoYW5kbGVyLlxuICAgKiBAcGFyYW0ge0Fib3J0U2lnbmFsfSBbYXJncy5zaWduYWxdIC0gQWJvcnRzIHRoZSByZXF1ZXN0OyBvbiBhYm9ydCB0aGUgcGVuZGluZyBzb2NrZXQgaXMgZGVzdHJveWVkIGFuZCB0aGUgcHJvbWlzZSByZWplY3RzIHdpdGggdGhlIHNpZ25hbCByZWFzb24gd2hlbiBpdCBpcyBhbiBFcnJvciwgb3RoZXJ3aXNlIHdpdGggYSBnZW5lcmljIGFib3J0IEVycm9yLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxUPn0gLSBSZXNvbHZlZCByZXF1ZXN0IHZhbHVlLlxuICAgKi9cbiAgYXN5bmMgcnVuKHtvbkNvbm5lY3QsIG9uTWVzc2FnZSwgc2lnbmFsfSkge1xuICAgIGNvbnN0IHNvY2tldCA9IG5ldC5jcmVhdGVDb25uZWN0aW9uKHtob3N0OiB0aGlzLmhvc3QsIHBvcnQ6IHRoaXMucG9ydH0pXG4gICAgY29uc3QganNvblNvY2tldCA9IG5ldyBKc29uU29ja2V0KHNvY2tldClcblxuICAgIHRoaXMuX2pzb25Tb2NrZXQgPSBqc29uU29ja2V0XG5cbiAgICByZXR1cm4gYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgbGV0IGZpbmlzaGVkID0gZmFsc2VcbiAgICAgIC8qKlxuICAgICAgICogRmluaXNoLlxuICAgICAgICogQHBhcmFtIHtvYmplY3R9IG9wdGlvbnMgLSBPcHRpb25zLlxuICAgICAgICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy5kZXN0cm95XSAtIERlc3Ryb3kgdGhlIHNvY2tldCBpbnN0ZWFkIG9mIGdyYWNlZnVsbHkgY2xvc2luZyBpdC5cbiAgICAgICAqIEBwYXJhbSB7KCkgPT4gdm9pZH0gY2FsbGJhY2sgLSBGaW5pc2ggY2FsbGJhY2suXG4gICAgICAgKi9cbiAgICAgIGNvbnN0IGZpbmlzaCA9ICh7ZGVzdHJveSA9IGZhbHNlfSA9IHt9LCBjYWxsYmFjaykgPT4ge1xuICAgICAgICBpZiAoZmluaXNoZWQpIHJldHVyblxuICAgICAgICBmaW5pc2hlZCA9IHRydWVcbiAgICAgICAgaWYgKHNpZ25hbCkgc2lnbmFsLnJlbW92ZUV2ZW50TGlzdGVuZXIoXCJhYm9ydFwiLCBvbkFib3J0KVxuICAgICAgICBqc29uU29ja2V0LnJlbW92ZUFsbExpc3RlbmVycygpXG5cbiAgICAgICAgaWYgKGRlc3Ryb3kpIHtcbiAgICAgICAgICBqc29uU29ja2V0LmRlc3Ryb3koKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGpzb25Tb2NrZXQuY2xvc2UoKVxuICAgICAgICB9XG5cbiAgICAgICAgY2FsbGJhY2soKVxuICAgICAgfVxuXG4gICAgICAvKipcbiAgICAgICAqIEhhbmRsZXMgYSBjb29wZXJhdGl2ZSBhYm9ydDogdGVhcnMgZG93biB0aGUgcGVuZGluZyBzb2NrZXQgYW5kIHJlamVjdHNcbiAgICAgICAqIHdpdGggdGhlIHNpZ25hbCByZWFzb24gd2hlbiBpdCBpcyBhbiBFcnJvci5cbiAgICAgICAqIEByZXR1cm5zIHt2b2lkfVxuICAgICAgICovXG4gICAgICBjb25zdCBvbkFib3J0ID0gKCkgPT4ge1xuICAgICAgICBjb25zdCByZWFzb24gPSBzaWduYWw/LnJlYXNvblxuXG4gICAgICAgIGZpbmlzaCh7ZGVzdHJveTogdHJ1ZX0sICgpID0+IHJlamVjdChyZWFzb24gaW5zdGFuY2VvZiBFcnJvciA/IHJlYXNvbiA6IG5ldyBFcnJvcihcIkJhY2tncm91bmQgam9iIHNvY2tldCByZXF1ZXN0IGFib3J0ZWRcIikpKVxuICAgICAgfVxuXG4gICAgICBpZiAoc2lnbmFsKSB7XG4gICAgICAgIGlmIChzaWduYWwuYWJvcnRlZCkge1xuICAgICAgICAgIG9uQWJvcnQoKVxuICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG5cbiAgICAgICAgc2lnbmFsLmFkZEV2ZW50TGlzdGVuZXIoXCJhYm9ydFwiLCBvbkFib3J0KVxuICAgICAgfVxuXG4gICAgICBqc29uU29ja2V0Lm9uKFwiZXJyb3JcIiwgKGVycm9yKSA9PiB7XG4gICAgICAgIGZpbmlzaCh7fSwgKCkgPT4gcmVqZWN0KGVycm9yKSlcbiAgICAgIH0pXG5cbiAgICAgIC8qKlxuICAgICAgICogSGFuZGxlcyB0aGUgc29ja2V0IHJlc3BvbnNlIG1lc3NhZ2UuXG4gICAgICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlNvY2tldE1lc3NhZ2V9IG1lc3NhZ2UgLSBTb2NrZXQgbWVzc2FnZS5cbiAgICAgICAqL1xuICAgICAganNvblNvY2tldC5vbihcIm1lc3NhZ2VcIiwgKG1lc3NhZ2UpID0+IHtcbiAgICAgICAgb25NZXNzYWdlKHtcbiAgICAgICAgICBtZXNzYWdlLFxuICAgICAgICAgIHJlc29sdmU6ICh2YWx1ZSkgPT4gZmluaXNoKHt9LCAoKSA9PiByZXNvbHZlKHZhbHVlKSksXG4gICAgICAgICAgcmVqZWN0OiAoZXJyb3IpID0+IGZpbmlzaCh7fSwgKCkgPT4gcmVqZWN0KGVycm9yKSlcbiAgICAgICAgfSlcbiAgICAgIH0pXG5cbiAgICAgIHNvY2tldC5vbihcImNvbm5lY3RcIiwgKCkgPT4ge1xuICAgICAgICBqc29uU29ja2V0LnNlbmQoe3R5cGU6IFwiaGVsbG9cIiwgcm9sZTogdGhpcy5yb2xlfSlcbiAgICAgICAgb25Db25uZWN0KGpzb25Tb2NrZXQpXG4gICAgICB9KVxuICAgIH0pXG4gIH1cbn1cbiJdfQ==
|