@compforge/harness-toolbox 0.4.8 → 0.5.1
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 +41 -9
- package/dist/{chunk-1ts7pswj.js → chunk-0d8vs5q8.js} +33 -2
- package/dist/{chunk-h8fwx0k7.js → chunk-18gcp0ck.js} +32 -7
- package/dist/{chunk-35cjec9z.js → chunk-3b877a0y.js} +6 -6
- package/dist/{chunk-gm4swndx.js → chunk-43mcyrh4.js} +14 -24
- package/dist/chunk-56fev9np.js +142 -0
- package/dist/{chunk-zp1s833x.js → chunk-7m95c7pn.js} +26 -13
- package/dist/{chunk-f3vq900j.js → chunk-92aacbwh.js} +1 -1
- package/dist/{chunk-x4ntb898.js → chunk-c2b2jwm4.js} +18 -18
- package/dist/chunk-ehmx0yhp.js +52 -0
- package/dist/{chunk-t4hrnaj0.js → chunk-ex429qjt.js} +22 -20
- package/dist/{chunk-m391jmpw.js → chunk-hbhbragw.js} +9 -9
- package/dist/{chunk-fqrknvr8.js → chunk-kap6ka3n.js} +27 -12
- package/dist/{chunk-vymm4g9h.js → chunk-kxpecgy7.js} +106 -37
- package/dist/{chunk-nftzckdy.js → chunk-mvkxs8d0.js} +17 -2
- package/dist/{chunk-f98qrpx1.js → chunk-njs48b51.js} +28 -27
- package/dist/chunk-sb5qzv37.js +62 -0
- package/dist/{chunk-dx6yvj81.js → chunk-xhfbark7.js} +1 -1
- package/dist/chunk-z8e1ckkq.js +16 -0
- package/dist/connection/acquire.d.ts +3 -4
- package/dist/connection/acquire.js +2 -4
- package/dist/datasource.d.ts +0 -6
- package/dist/file/client.js +2 -2
- package/dist/file/index.js +4 -4
- package/dist/file/local.js +3 -3
- package/dist/file/pod.js +3 -3
- package/dist/http/client.d.ts +15 -0
- package/dist/http/client.js +21 -0
- package/dist/http/handler.d.ts +10 -0
- package/dist/http/handler.js +6 -0
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +21 -0
- package/dist/http/types.d.ts +34 -0
- package/dist/http/types.js +0 -0
- package/dist/kubernetes/client.js +9 -9
- package/dist/kubernetes/environment.js +9 -9
- package/dist/kubernetes/exec-transport.js +2 -2
- package/dist/kubernetes/pod-log-datasource.js +1 -1
- package/dist/kubernetes/port-forward.d.ts +4 -0
- package/dist/kubernetes/port-forward.js +1 -1
- package/dist/kubernetes/resource-transport.js +2 -2
- package/dist/kubernetes/service-port-forward.d.ts +3 -2
- package/dist/kubernetes/service-port-forward.js +2 -2
- package/dist/mysql/client.d.ts +7 -7
- package/dist/mysql/client.js +12 -7
- package/dist/mysql/index.js +16 -11
- package/dist/opensearch/client.d.ts +14 -4
- package/dist/opensearch/client.js +15 -8
- package/dist/opensearch/index.js +15 -8
- package/dist/redis/client.d.ts +7 -6
- package/dist/redis/client.js +12 -7
- package/dist/redis/index.js +16 -11
- package/dist/s3/client.d.ts +5 -3
- package/dist/s3/client.js +16 -2
- package/dist/s3/datasource.js +15 -8
- package/dist/s3/handler.d.ts +2 -3
- package/dist/s3/handler.js +2 -1
- package/dist/s3/index.js +15 -8
- package/dist/transport/index.d.ts +18 -8
- package/dist/transport/index.js +17 -4
- package/dist/transport/lifecycle.d.ts +22 -0
- package/dist/transport/lifecycle.js +8 -0
- package/dist/transport/pod-relay.d.ts +7 -4
- package/dist/transport/pod-relay.js +6 -3
- package/dist/transport/service-port-forward.d.ts +30 -0
- package/dist/transport/service-port-forward.js +15 -0
- package/package.json +9 -3
- package/dist/{chunk-x0yhfg29.js → chunk-5w85c7cs.js} +3 -3
- package/dist/{chunk-ww11q58v.js → chunk-rncagjbc.js} +3 -3
- package/dist/{chunk-r3rnsvwv.js → chunk-xeft7af0.js} +3 -3
|
@@ -11,20 +11,20 @@ import {
|
|
|
11
11
|
destroyMysqlConnection
|
|
12
12
|
} from "./chunk-835fssgz.js";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "./chunk-gm4swndx.js";
|
|
17
|
-
import {
|
|
18
|
-
ToolboxError
|
|
19
|
-
} from "./chunk-rkt41za2.js";
|
|
14
|
+
isConnectionNetworkError
|
|
15
|
+
} from "./chunk-18gcp0ck.js";
|
|
20
16
|
import {
|
|
21
17
|
ConcurrencyPool
|
|
22
18
|
} from "./chunk-v3hxvnrc.js";
|
|
23
19
|
import {
|
|
24
|
-
|
|
25
|
-
} from "./chunk-
|
|
20
|
+
Transport
|
|
21
|
+
} from "./chunk-sb5qzv37.js";
|
|
22
|
+
import {
|
|
23
|
+
ToolboxError
|
|
24
|
+
} from "./chunk-rkt41za2.js";
|
|
26
25
|
|
|
27
26
|
// src/mysql/client.ts
|
|
27
|
+
import { raceSignal } from "race-signal";
|
|
28
28
|
function mysqlOperationError(error, phase) {
|
|
29
29
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "PROTOCOL_CONNECTION_LOST")
|
|
30
30
|
return error;
|
|
@@ -36,15 +36,15 @@ function mysqlOperationError(error, phase) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
class MysqlDatabase {
|
|
39
|
-
transports;
|
|
40
39
|
options;
|
|
41
40
|
connect;
|
|
42
41
|
#queries = new ConcurrencyPool(1);
|
|
43
42
|
#connections = new Map;
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
#transport;
|
|
44
|
+
constructor(transport, options, connect = connectMysql) {
|
|
46
45
|
this.options = options;
|
|
47
46
|
this.connect = connect;
|
|
47
|
+
this.#transport = transport instanceof Transport ? transport : new Transport(transport);
|
|
48
48
|
}
|
|
49
49
|
query(target, sql, values) {
|
|
50
50
|
return this.#queries.run(() => this.#query(target, sql, values), this.options.signal);
|
|
@@ -125,14 +125,14 @@ class MysqlDatabase {
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
#open(target) {
|
|
128
|
-
return
|
|
128
|
+
return this.#transport.acquire({
|
|
129
129
|
name: "MySQL connection",
|
|
130
130
|
...this.options,
|
|
131
131
|
open: async (transport, attempt) => {
|
|
132
132
|
if (transport.kind === "python")
|
|
133
133
|
return { connection: { kind: "python", transport }, transport: transport.name };
|
|
134
134
|
try {
|
|
135
|
-
const endpoint = await transport.connect(target);
|
|
135
|
+
const endpoint = await transport.connect(target, attempt);
|
|
136
136
|
const connection = await this.connect({
|
|
137
137
|
host: endpoint.host,
|
|
138
138
|
port: endpoint.port,
|
|
@@ -175,7 +175,8 @@ class MysqlDatabase {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
class MysqlClient {
|
|
178
|
-
|
|
178
|
+
configuration;
|
|
179
|
+
transport;
|
|
179
180
|
options;
|
|
180
181
|
#controller = new AbortController;
|
|
181
182
|
signal;
|
|
@@ -183,17 +184,18 @@ class MysqlClient {
|
|
|
183
184
|
#disposal;
|
|
184
185
|
#database;
|
|
185
186
|
#target;
|
|
186
|
-
constructor(
|
|
187
|
-
this.
|
|
187
|
+
constructor(configuration, transport, options) {
|
|
188
|
+
this.configuration = configuration;
|
|
189
|
+
this.transport = transport;
|
|
188
190
|
this.options = options;
|
|
189
191
|
this.signal = options.signal ? AbortSignal.any([options.signal, this.#controller.signal]) : this.#controller.signal;
|
|
190
192
|
}
|
|
191
193
|
initialize() {
|
|
192
194
|
this.signal.throwIfAborted();
|
|
193
195
|
return this.#initialization ??= (async () => {
|
|
194
|
-
this.#target = await
|
|
196
|
+
this.#target = await raceSignal(typeof this.configuration === "function" ? this.configuration() : Promise.resolve(this.configuration), this.signal);
|
|
195
197
|
this.signal.throwIfAborted();
|
|
196
|
-
this.#database = new MysqlDatabase(this.
|
|
198
|
+
this.#database = new MysqlDatabase(this.transport, { ...this.options, signal: this.signal });
|
|
197
199
|
await this.#database.initialize(this.#target);
|
|
198
200
|
})();
|
|
199
201
|
}
|
|
@@ -215,8 +217,8 @@ class MysqlClient {
|
|
|
215
217
|
})();
|
|
216
218
|
}
|
|
217
219
|
}
|
|
218
|
-
async function openMysql(
|
|
219
|
-
const client = new MysqlClient(
|
|
220
|
+
async function openMysql(configuration, transport, options) {
|
|
221
|
+
const client = new MysqlClient(configuration, transport, options);
|
|
220
222
|
try {
|
|
221
223
|
await client.initialize();
|
|
222
224
|
} catch (error) {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KubernetesResourceTransport
|
|
3
|
+
} from "./chunk-5w85c7cs.js";
|
|
4
|
+
import {
|
|
5
|
+
KubernetesExecTransport
|
|
6
|
+
} from "./chunk-xeft7af0.js";
|
|
1
7
|
import {
|
|
2
8
|
resolveWorkload
|
|
3
9
|
} from "./chunk-ae84rzq5.js";
|
|
4
10
|
import {
|
|
5
11
|
ServicePortForwarder
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import {
|
|
8
|
-
KubernetesResourceTransport
|
|
9
|
-
} from "./chunk-x0yhfg29.js";
|
|
12
|
+
} from "./chunk-0d8vs5q8.js";
|
|
10
13
|
import {
|
|
11
|
-
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
KubectlExecutor
|
|
15
|
+
} from "./chunk-rq5fc007.js";
|
|
13
16
|
import {
|
|
14
17
|
KubernetesError
|
|
15
18
|
} from "./chunk-rkt41za2.js";
|
|
16
|
-
import {
|
|
17
|
-
KubectlExecutor
|
|
18
|
-
} from "./chunk-rq5fc007.js";
|
|
19
19
|
|
|
20
20
|
// src/kubernetes/client.ts
|
|
21
21
|
class KubernetesClient {
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
POD_RELAY_SCRIPT
|
|
3
|
+
} from "./chunk-ssj93brx.js";
|
|
1
4
|
import {
|
|
2
5
|
startPortForward
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-mvkxs8d0.js";
|
|
4
7
|
import {
|
|
5
8
|
KubectlExecutor
|
|
6
9
|
} from "./chunk-rq5fc007.js";
|
|
7
10
|
import {
|
|
8
|
-
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
Transport
|
|
12
|
+
} from "./chunk-sb5qzv37.js";
|
|
10
13
|
|
|
11
14
|
// src/transport/pod-relay.ts
|
|
12
|
-
|
|
15
|
+
import { raceSignal } from "race-signal";
|
|
16
|
+
class PodRelayTransport extends Transport {
|
|
13
17
|
options;
|
|
14
18
|
kind = "tcp";
|
|
15
19
|
name = "pod-relay";
|
|
16
|
-
#controller = new AbortController;
|
|
17
20
|
#signal;
|
|
18
21
|
#executor;
|
|
19
22
|
#startForward;
|
|
20
23
|
#targets = new Map;
|
|
21
24
|
#pending = new Map;
|
|
22
25
|
#forwards = [];
|
|
26
|
+
#exits = [];
|
|
23
27
|
#startup;
|
|
24
28
|
#execution;
|
|
25
29
|
#input;
|
|
@@ -28,6 +32,7 @@ class PodRelayTransport {
|
|
|
28
32
|
#disposal;
|
|
29
33
|
#sequence = 0;
|
|
30
34
|
constructor(options, dependencies) {
|
|
35
|
+
super([], options.signal);
|
|
31
36
|
this.options = options;
|
|
32
37
|
for (const key of ["startupTimeoutMs", "connectTimeoutMs", "maxConnections", "maxTargets", "maxCandidatePods"]) {
|
|
33
38
|
if (!Number.isSafeInteger(options[key]) || options[key] <= 0)
|
|
@@ -35,10 +40,14 @@ class PodRelayTransport {
|
|
|
35
40
|
}
|
|
36
41
|
this.#executor = dependencies?.executor ?? new KubectlExecutor(options);
|
|
37
42
|
this.#startForward = dependencies?.startForward ?? startPortForward;
|
|
38
|
-
this.#signal =
|
|
43
|
+
this.#signal = this.resourceSignal;
|
|
39
44
|
this.#signal.addEventListener("abort", this.#onAbort, { once: true });
|
|
40
45
|
}
|
|
46
|
+
paths() {
|
|
47
|
+
return [this];
|
|
48
|
+
}
|
|
41
49
|
async initialize() {
|
|
50
|
+
this.signal.throwIfAborted();
|
|
42
51
|
this.#check();
|
|
43
52
|
}
|
|
44
53
|
#check() {
|
|
@@ -175,7 +184,8 @@ class PodRelayTransport {
|
|
|
175
184
|
this.#input.enqueue(new TextEncoder().encode(`${JSON.stringify(value)}
|
|
176
185
|
`));
|
|
177
186
|
}
|
|
178
|
-
connect(endpoint) {
|
|
187
|
+
connect(endpoint, attempt) {
|
|
188
|
+
this.signal.throwIfAborted();
|
|
179
189
|
this.#check();
|
|
180
190
|
if (!endpoint.host || !Number.isInteger(endpoint.port) || endpoint.port < 1 || endpoint.port > 65535) {
|
|
181
191
|
return Promise.reject(new Error("Invalid relay endpoint"));
|
|
@@ -192,7 +202,7 @@ class PodRelayTransport {
|
|
|
192
202
|
pending = this.#connect(endpoint, this.#startup);
|
|
193
203
|
this.#targets.set(key, pending);
|
|
194
204
|
}
|
|
195
|
-
return pending
|
|
205
|
+
return raceSignal(pending, attempt ? AbortSignal.any([this.signal, attempt.signal]) : this.signal);
|
|
196
206
|
}
|
|
197
207
|
async#connect(endpoint, startup) {
|
|
198
208
|
const pod = await startup;
|
|
@@ -225,10 +235,13 @@ class PodRelayTransport {
|
|
|
225
235
|
...this.options,
|
|
226
236
|
target: { kind: "pod", name: pod.target.pod },
|
|
227
237
|
remotePort,
|
|
228
|
-
timeoutMs: this.options.startupTimeoutMs
|
|
238
|
+
timeoutMs: this.options.startupTimeoutMs,
|
|
239
|
+
signal: this.#signal
|
|
229
240
|
});
|
|
230
241
|
if (!forwarded.ok || !forwarded.value)
|
|
231
242
|
throw new Error("Pod relay port-forward unavailable");
|
|
243
|
+
if (forwarded.value.exited)
|
|
244
|
+
this.#exits.push(forwarded.value.exited);
|
|
232
245
|
try {
|
|
233
246
|
this.#check();
|
|
234
247
|
} catch (error) {
|
|
@@ -238,14 +251,16 @@ class PodRelayTransport {
|
|
|
238
251
|
this.#forwards.push(forwarded.value);
|
|
239
252
|
return { host: "127.0.0.1", port: forwarded.value.localPort };
|
|
240
253
|
}
|
|
241
|
-
|
|
254
|
+
close() {
|
|
242
255
|
return this.#disposal ??= (async () => {
|
|
243
|
-
this.#controller.abort(new Error("Pod relay disposed"));
|
|
244
256
|
this.#fail(new Error("Pod relay disposed"));
|
|
245
257
|
await Promise.allSettled(this.#targets.values());
|
|
246
|
-
await this.#execution;
|
|
258
|
+
const completed = await Promise.allSettled([this.#execution, ...this.#exits]);
|
|
247
259
|
this.#signal.removeEventListener("abort", this.#onAbort);
|
|
248
260
|
this.#targets.clear();
|
|
261
|
+
const errors = completed.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
262
|
+
if (errors.length)
|
|
263
|
+
throw new AggregateError(errors, "Pod relay cleanup failed");
|
|
249
264
|
})();
|
|
250
265
|
}
|
|
251
266
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-gm4swndx.js";
|
|
2
|
+
isConnectionNetworkError
|
|
3
|
+
} from "./chunk-18gcp0ck.js";
|
|
5
4
|
import {
|
|
6
5
|
TimeoutError,
|
|
7
6
|
ToolboxError
|
|
8
7
|
} from "./chunk-rkt41za2.js";
|
|
9
|
-
import {
|
|
10
|
-
isConnectionNetworkError
|
|
11
|
-
} from "./chunk-h8fwx0k7.js";
|
|
12
8
|
|
|
13
9
|
// src/opensearch/client.ts
|
|
10
|
+
import { raceSignal } from "race-signal";
|
|
14
11
|
import { Client, Connection, errors } from "@opensearch-project/opensearch";
|
|
12
|
+
import { isIP } from "node:net";
|
|
13
|
+
function openSearchServername(host) {
|
|
14
|
+
if (!host)
|
|
15
|
+
return;
|
|
16
|
+
const unwrapped = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
17
|
+
return isIP(unwrapped) ? undefined : host;
|
|
18
|
+
}
|
|
15
19
|
function describeOpenSearchError(error) {
|
|
16
20
|
const parts = [];
|
|
17
21
|
const seen = new Set;
|
|
@@ -36,16 +40,30 @@ function isOpenSearchReadApi(search) {
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
class OpenSearchConnection extends Connection {
|
|
43
|
+
request(params, callback) {
|
|
44
|
+
let completed = false;
|
|
45
|
+
try {
|
|
46
|
+
return super.request(params, (error, response) => {
|
|
47
|
+
completed = true;
|
|
48
|
+
callback(error, response);
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (!completed)
|
|
52
|
+
this._openRequests--;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
39
56
|
buildRequestObject(params) {
|
|
40
|
-
return { ...super.buildRequestObject(params), servername: this.ssl?.servername };
|
|
57
|
+
return { ...super.buildRequestObject(params), servername: openSearchServername(this.ssl?.servername) };
|
|
41
58
|
}
|
|
42
59
|
}
|
|
43
60
|
function createClient(options) {
|
|
44
61
|
return new Client({
|
|
45
62
|
node: options.node,
|
|
46
63
|
Connection: OpenSearchConnection,
|
|
64
|
+
headers: options.authority ? { host: options.authority } : undefined,
|
|
47
65
|
auth: options.auth.username && options.auth.password ? { username: options.auth.username, password: options.auth.password } : undefined,
|
|
48
|
-
ssl: { rejectUnauthorized: false, servername: options.servername },
|
|
66
|
+
ssl: { rejectUnauthorized: false, servername: openSearchServername(options.servername) },
|
|
49
67
|
requestTimeout: options.requestTimeoutMs ?? 60000,
|
|
50
68
|
maxRetries: 0,
|
|
51
69
|
agent: { keepAlive: !process.versions.bun }
|
|
@@ -55,26 +73,36 @@ function createClient(options) {
|
|
|
55
73
|
class OpenSearchEngine {
|
|
56
74
|
options;
|
|
57
75
|
client;
|
|
76
|
+
#requests = new Set;
|
|
77
|
+
#closed = false;
|
|
58
78
|
constructor(options, client) {
|
|
59
79
|
this.options = options;
|
|
60
80
|
this.client = client ?? createClient(options);
|
|
61
81
|
}
|
|
82
|
+
#run(start) {
|
|
83
|
+
if (this.#closed)
|
|
84
|
+
throw new Error("OpenSearch engine is closed");
|
|
85
|
+
const request = start();
|
|
86
|
+
this.#requests.add(request);
|
|
87
|
+
request.then(() => this.#requests.delete(request), () => this.#requests.delete(request));
|
|
88
|
+
return request;
|
|
89
|
+
}
|
|
62
90
|
async count(index, query) {
|
|
63
|
-
const response = await this.client.count({ index, body: { query } });
|
|
91
|
+
const response = await this.#run(() => this.client.count({ index, body: { query } }));
|
|
64
92
|
return Number(response.body.count ?? 0);
|
|
65
93
|
}
|
|
66
94
|
async search(index, body) {
|
|
67
|
-
const response = await this.client.search({ index, body });
|
|
95
|
+
const response = await this.#run(() => this.client.search({ index, body }));
|
|
68
96
|
return response.body;
|
|
69
97
|
}
|
|
70
98
|
async request(path, query) {
|
|
71
99
|
if (!this.client.transport)
|
|
72
100
|
throw new Error("OpenSearch client 不支持通用只读请求");
|
|
73
|
-
const response = await this.client.transport.request({
|
|
101
|
+
const response = await this.#run(() => this.client.transport.request({
|
|
74
102
|
method: "GET",
|
|
75
103
|
path,
|
|
76
104
|
querystring: query
|
|
77
|
-
});
|
|
105
|
+
}));
|
|
78
106
|
return response.body;
|
|
79
107
|
}
|
|
80
108
|
async ping(signal, timeoutMs = this.options.requestTimeoutMs ?? 60000) {
|
|
@@ -87,9 +115,10 @@ class OpenSearchEngine {
|
|
|
87
115
|
const controller = new AbortController;
|
|
88
116
|
const timer = setTimeout(() => controller.abort(new TimeoutError(`OpenSearch probe timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
89
117
|
const active = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
118
|
+
let request;
|
|
90
119
|
try {
|
|
91
|
-
|
|
92
|
-
const result = await
|
|
120
|
+
request = this.#run(() => this.client.ping({}, { requestTimeout: timeoutMs }));
|
|
121
|
+
const result = await raceSignal(request, active);
|
|
93
122
|
log(`OpenSearch probe ok target=${target} elapsed_ms=${Date.now() - started}`);
|
|
94
123
|
return result;
|
|
95
124
|
} catch (error) {
|
|
@@ -97,13 +126,18 @@ class OpenSearchEngine {
|
|
|
97
126
|
throw error;
|
|
98
127
|
} finally {
|
|
99
128
|
clearTimeout(timer);
|
|
129
|
+
if (active.aborted)
|
|
130
|
+
request?.abort?.();
|
|
100
131
|
}
|
|
101
132
|
}
|
|
102
133
|
async close() {
|
|
134
|
+
this.#closed = true;
|
|
135
|
+
for (const request of this.#requests)
|
|
136
|
+
request.abort?.();
|
|
103
137
|
const controller = new AbortController;
|
|
104
138
|
const timer = setTimeout(() => controller.abort(new TimeoutError("OpenSearch close timed out after 2000ms")), 2000);
|
|
105
139
|
try {
|
|
106
|
-
await
|
|
140
|
+
await raceSignal(this.client.close(), controller.signal);
|
|
107
141
|
} catch (error) {
|
|
108
142
|
this.options.onDiagnostic?.(`OpenSearch close failed ${describeOpenSearchError(error)}`);
|
|
109
143
|
throw error;
|
|
@@ -123,27 +157,39 @@ function openSearchRetryReason(error) {
|
|
|
123
157
|
}
|
|
124
158
|
|
|
125
159
|
class OpenSearchClient {
|
|
126
|
-
|
|
160
|
+
configuration;
|
|
161
|
+
transport;
|
|
127
162
|
lifecycle;
|
|
128
163
|
#controller = new AbortController;
|
|
129
164
|
signal;
|
|
130
165
|
#initialization;
|
|
131
166
|
#disposal;
|
|
132
167
|
#engine;
|
|
168
|
+
#node;
|
|
169
|
+
get node() {
|
|
170
|
+
return this.#node;
|
|
171
|
+
}
|
|
172
|
+
#cleanupErrors = [];
|
|
133
173
|
#operations = new Set;
|
|
134
|
-
constructor(
|
|
135
|
-
this.
|
|
174
|
+
constructor(configuration, transport, lifecycle = {}) {
|
|
175
|
+
this.configuration = configuration;
|
|
176
|
+
this.transport = transport;
|
|
136
177
|
this.lifecycle = lifecycle;
|
|
137
178
|
this.signal = lifecycle.signal ? AbortSignal.any([lifecycle.signal, this.#controller.signal]) : this.#controller.signal;
|
|
138
179
|
}
|
|
139
180
|
initialize() {
|
|
140
181
|
this.signal.throwIfAborted();
|
|
141
182
|
return this.#initialization ??= (async () => {
|
|
142
|
-
const target = await
|
|
183
|
+
const target = await raceSignal(typeof this.configuration === "function" ? this.configuration() : Promise.resolve(this.configuration), this.signal);
|
|
143
184
|
this.signal.throwIfAborted();
|
|
144
|
-
const
|
|
185
|
+
const explicit = target.node.includes("://");
|
|
186
|
+
const preferred = target.preferredScheme ?? "http";
|
|
187
|
+
const original = new URL(explicit ? target.node : `${preferred}://${target.node}`);
|
|
188
|
+
if (!explicit && !original.port)
|
|
189
|
+
original.port = "9200";
|
|
190
|
+
const schemes = explicit ? [original.protocol] : preferred === "https" ? ["https:", "http:"] : ["http:", "https:"];
|
|
145
191
|
try {
|
|
146
|
-
this.#engine = await
|
|
192
|
+
this.#engine = await this.transport.acquire({
|
|
147
193
|
name: "OpenSearch connection",
|
|
148
194
|
signal: this.signal,
|
|
149
195
|
connectTimeoutMs: target.connectTimeoutMs ?? target.requestTimeoutMs ?? 1e4,
|
|
@@ -155,23 +201,40 @@ class OpenSearchClient {
|
|
|
155
201
|
this.lifecycle.onDiagnostic?.(`OpenSearch route start transport=${transport.name} target=${original.protocol}//${original.host} timeout_ms=${attempt.remainingMs()}`);
|
|
156
202
|
const endpoint = await transport.connect({
|
|
157
203
|
host: original.hostname,
|
|
158
|
-
port: Number(original.port || (original.protocol === "https:" ? 443 : 80))
|
|
159
|
-
|
|
160
|
-
});
|
|
204
|
+
port: Number(original.port || (original.protocol === "https:" ? 443 : 80))
|
|
205
|
+
}, attempt);
|
|
161
206
|
attempt.signal.throwIfAborted();
|
|
162
207
|
const url = new URL(original);
|
|
163
208
|
url.hostname = endpoint.host;
|
|
164
209
|
url.port = String(endpoint.port);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
210
|
+
for (const [index, scheme] of schemes.entries()) {
|
|
211
|
+
url.protocol = scheme;
|
|
212
|
+
const engine = new OpenSearchEngine({
|
|
213
|
+
...target,
|
|
214
|
+
onDiagnostic: this.lifecycle.onDiagnostic,
|
|
215
|
+
node: url.toString(),
|
|
216
|
+
authority: original.host,
|
|
217
|
+
servername: target.servername ?? original.hostname
|
|
218
|
+
});
|
|
170
219
|
try {
|
|
171
|
-
await engine.
|
|
172
|
-
|
|
173
|
-
|
|
220
|
+
await engine.ping(attempt.signal, attempt.remainingMs());
|
|
221
|
+
const selected = new URL(original);
|
|
222
|
+
selected.protocol = scheme;
|
|
223
|
+
this.#node = selected.toString().replace(/\/$/, "");
|
|
224
|
+
return { connection: engine, transport: transport.name };
|
|
225
|
+
} catch (error) {
|
|
226
|
+
try {
|
|
227
|
+
await engine.close();
|
|
228
|
+
} catch (cleanup) {
|
|
229
|
+
this.#cleanupErrors.push(cleanup);
|
|
230
|
+
throw new AggregateError([error, cleanup], "OpenSearch initialization and cleanup failed", { cause: error });
|
|
231
|
+
}
|
|
232
|
+
attempt.signal.throwIfAborted();
|
|
233
|
+
if (index === schemes.length - 1 || openSearchRetryReason(error) === undefined)
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
174
236
|
}
|
|
237
|
+
throw new Error("OpenSearch has no protocol candidate");
|
|
175
238
|
},
|
|
176
239
|
close: (engine) => engine.close(),
|
|
177
240
|
retryReason: openSearchRetryReason
|
|
@@ -220,21 +283,27 @@ class OpenSearchClient {
|
|
|
220
283
|
return this.#disposal ??= (async () => {
|
|
221
284
|
this.#controller.abort(new Error("OpenSearch client disposed"));
|
|
222
285
|
await this.#initialization?.catch(() => {});
|
|
223
|
-
await Promise.allSettled(this.#operations);
|
|
224
286
|
await this.#engine?.close();
|
|
287
|
+
await Promise.allSettled(this.#operations);
|
|
288
|
+
if (this.#cleanupErrors.length)
|
|
289
|
+
throw new AggregateError(this.#cleanupErrors, "OpenSearch cleanup failed");
|
|
225
290
|
})();
|
|
226
291
|
}
|
|
227
292
|
}
|
|
228
|
-
async function openOpenSearch(
|
|
229
|
-
const client = new OpenSearchClient(
|
|
293
|
+
async function openOpenSearch(configuration, transport, lifecycle = {}) {
|
|
294
|
+
const client = new OpenSearchClient(configuration, transport, lifecycle);
|
|
230
295
|
try {
|
|
231
296
|
await client.initialize();
|
|
232
297
|
} catch (error) {
|
|
233
|
-
|
|
298
|
+
try {
|
|
299
|
+
await client.dispose();
|
|
300
|
+
} catch (cleanup) {
|
|
301
|
+
throw new AggregateError([error, cleanup], "OpenSearch initialization and cleanup failed", { cause: error });
|
|
302
|
+
}
|
|
234
303
|
throw error;
|
|
235
304
|
}
|
|
236
305
|
lifecycle.onDispose?.(() => client.dispose());
|
|
237
306
|
return client;
|
|
238
307
|
}
|
|
239
308
|
|
|
240
|
-
export { describeOpenSearchError, isOpenSearchReadApi, OpenSearchEngine, OpenSearchClient, openOpenSearch };
|
|
309
|
+
export { openSearchServername, describeOpenSearchError, isOpenSearchReadApi, OpenSearchEngine, OpenSearchClient, openOpenSearch };
|
|
@@ -69,7 +69,14 @@ async function startPortForward(opts) {
|
|
|
69
69
|
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
70
70
|
if (opts.signal?.aborted)
|
|
71
71
|
abort();
|
|
72
|
-
|
|
72
|
+
let alive = true;
|
|
73
|
+
proc.exited.then(() => {
|
|
74
|
+
alive = false;
|
|
75
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
76
|
+
}, () => {
|
|
77
|
+
alive = false;
|
|
78
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
79
|
+
});
|
|
73
80
|
const decoder = new TextDecoder;
|
|
74
81
|
const deadline = Date.now() + timeoutMs;
|
|
75
82
|
let out = "";
|
|
@@ -105,7 +112,7 @@ async function startPortForward(opts) {
|
|
|
105
112
|
log(`port-forward listening local=127.0.0.1:${m[1]} elapsed_ms=${Date.now() - started}`);
|
|
106
113
|
return {
|
|
107
114
|
ok: true,
|
|
108
|
-
value: { target, localPort: Number(m[1]), command: argv, stop }
|
|
115
|
+
value: { target, localPort: Number(m[1]), command: argv, stop, exited: proc.exited, isAlive: () => alive && !stopped }
|
|
109
116
|
};
|
|
110
117
|
}
|
|
111
118
|
}
|
|
@@ -141,6 +148,14 @@ class PortForwardScope {
|
|
|
141
148
|
for (const forward of this.#forwards.splice(0).reverse())
|
|
142
149
|
forward.stop();
|
|
143
150
|
}
|
|
151
|
+
async dispose() {
|
|
152
|
+
const forwards = [...this.#forwards];
|
|
153
|
+
this.stop();
|
|
154
|
+
const settled = await Promise.allSettled(forwards.map((forward) => forward.exited));
|
|
155
|
+
const errors = settled.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
156
|
+
if (errors.length)
|
|
157
|
+
throw new AggregateError(errors, "Port-forward cleanup failed");
|
|
158
|
+
}
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
export { startPortForward, PortForwardScope };
|