@compforge/harness-toolbox 0.4.7 → 0.5.0
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 +15 -8
- package/dist/{chunk-1ts7pswj.js → chunk-0d8vs5q8.js} +33 -2
- package/dist/{chunk-h8fwx0k7.js → chunk-18gcp0ck.js} +32 -7
- package/dist/{chunk-gm4swndx.js → chunk-43mcyrh4.js} +14 -24
- package/dist/{chunk-zp1s833x.js → chunk-7hbvjzb9.js} +25 -12
- package/dist/{chunk-f3vq900j.js → chunk-92aacbwh.js} +1 -1
- package/dist/{chunk-35cjec9z.js → chunk-ccre3nh1.js} +6 -6
- package/dist/chunk-ehmx0yhp.js +52 -0
- package/dist/{chunk-t4hrnaj0.js → chunk-ex429qjt.js} +22 -20
- package/dist/{chunk-fqrknvr8.js → chunk-kap6ka3n.js} +27 -12
- package/dist/{chunk-m4d29vfd.js → chunk-kxpecgy7.js} +109 -36
- 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-m391jmpw.js → chunk-ze1hng62.js} +9 -9
- 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/kubernetes/client.js +8 -8
- package/dist/kubernetes/environment.js +8 -8
- package/dist/kubernetes/exec-transport.js +2 -2
- package/dist/kubernetes/pod-log-client.js +2 -2
- package/dist/kubernetes/pod-log-datasource.js +3 -3
- 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 +18 -13
- 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 +14 -1
- package/dist/s3/datasource.js +13 -7
- package/dist/s3/index.js +13 -7
- 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 +3 -2
- package/dist/{chunk-mnad4zkp.js → chunk-010339ga.js} +3 -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
|
@@ -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;
|
|
@@ -19,8 +23,10 @@ function describeOpenSearchError(error) {
|
|
|
19
23
|
seen.add(error);
|
|
20
24
|
const value = error;
|
|
21
25
|
const safe = (value2) => typeof value2 === "number" ? String(value2) : typeof value2 === "string" && /^[a-zA-Z0-9_.-]{1,80}$/.test(value2) ? value2 : undefined;
|
|
26
|
+
const argument = (value.code === "ERR_INVALID_ARG_VALUE" || value.code === "ERR_INVALID_ARG_TYPE") && typeof value.message === "string" ? value.message.match(/^The ["']([a-zA-Z][a-zA-Z0-9_.]{0,79})["'] (?:argument|option|property)/)?.[1] ?? value.message.match(/^The (?:argument|option|property) ["']([a-zA-Z][a-zA-Z0-9_.]{0,79})["']/)?.[1] : undefined;
|
|
22
27
|
parts.push([
|
|
23
28
|
`error=${safe(value.name) ?? "Error"}`,
|
|
29
|
+
argument && `argument=${argument}`,
|
|
24
30
|
safe(value.code) && `code=${safe(value.code)}`,
|
|
25
31
|
safe(value.kind) && `kind=${safe(value.kind)}`,
|
|
26
32
|
safe(value.statusCode ?? value.meta?.statusCode) && `status=${safe(value.statusCode ?? value.meta?.statusCode)}`
|
|
@@ -34,16 +40,30 @@ function isOpenSearchReadApi(search) {
|
|
|
34
40
|
}
|
|
35
41
|
|
|
36
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
|
+
}
|
|
37
56
|
buildRequestObject(params) {
|
|
38
|
-
return { ...super.buildRequestObject(params), servername: this.ssl?.servername };
|
|
57
|
+
return { ...super.buildRequestObject(params), servername: openSearchServername(this.ssl?.servername) };
|
|
39
58
|
}
|
|
40
59
|
}
|
|
41
60
|
function createClient(options) {
|
|
42
61
|
return new Client({
|
|
43
62
|
node: options.node,
|
|
44
63
|
Connection: OpenSearchConnection,
|
|
64
|
+
headers: options.authority ? { host: options.authority } : undefined,
|
|
45
65
|
auth: options.auth.username && options.auth.password ? { username: options.auth.username, password: options.auth.password } : undefined,
|
|
46
|
-
ssl: { rejectUnauthorized: false, servername: options.servername },
|
|
66
|
+
ssl: { rejectUnauthorized: false, servername: openSearchServername(options.servername) },
|
|
47
67
|
requestTimeout: options.requestTimeoutMs ?? 60000,
|
|
48
68
|
maxRetries: 0,
|
|
49
69
|
agent: { keepAlive: !process.versions.bun }
|
|
@@ -53,26 +73,36 @@ function createClient(options) {
|
|
|
53
73
|
class OpenSearchEngine {
|
|
54
74
|
options;
|
|
55
75
|
client;
|
|
76
|
+
#requests = new Set;
|
|
77
|
+
#closed = false;
|
|
56
78
|
constructor(options, client) {
|
|
57
79
|
this.options = options;
|
|
58
80
|
this.client = client ?? createClient(options);
|
|
59
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
|
+
}
|
|
60
90
|
async count(index, query) {
|
|
61
|
-
const response = await this.client.count({ index, body: { query } });
|
|
91
|
+
const response = await this.#run(() => this.client.count({ index, body: { query } }));
|
|
62
92
|
return Number(response.body.count ?? 0);
|
|
63
93
|
}
|
|
64
94
|
async search(index, body) {
|
|
65
|
-
const response = await this.client.search({ index, body });
|
|
95
|
+
const response = await this.#run(() => this.client.search({ index, body }));
|
|
66
96
|
return response.body;
|
|
67
97
|
}
|
|
68
98
|
async request(path, query) {
|
|
69
99
|
if (!this.client.transport)
|
|
70
100
|
throw new Error("OpenSearch client 不支持通用只读请求");
|
|
71
|
-
const response = await this.client.transport.request({
|
|
101
|
+
const response = await this.#run(() => this.client.transport.request({
|
|
72
102
|
method: "GET",
|
|
73
103
|
path,
|
|
74
104
|
querystring: query
|
|
75
|
-
});
|
|
105
|
+
}));
|
|
76
106
|
return response.body;
|
|
77
107
|
}
|
|
78
108
|
async ping(signal, timeoutMs = this.options.requestTimeoutMs ?? 60000) {
|
|
@@ -85,9 +115,10 @@ class OpenSearchEngine {
|
|
|
85
115
|
const controller = new AbortController;
|
|
86
116
|
const timer = setTimeout(() => controller.abort(new TimeoutError(`OpenSearch probe timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
87
117
|
const active = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
118
|
+
let request;
|
|
88
119
|
try {
|
|
89
|
-
|
|
90
|
-
const result = await
|
|
120
|
+
request = this.#run(() => this.client.ping({}, { requestTimeout: timeoutMs }));
|
|
121
|
+
const result = await raceSignal(request, active);
|
|
91
122
|
log(`OpenSearch probe ok target=${target} elapsed_ms=${Date.now() - started}`);
|
|
92
123
|
return result;
|
|
93
124
|
} catch (error) {
|
|
@@ -95,13 +126,18 @@ class OpenSearchEngine {
|
|
|
95
126
|
throw error;
|
|
96
127
|
} finally {
|
|
97
128
|
clearTimeout(timer);
|
|
129
|
+
if (active.aborted)
|
|
130
|
+
request?.abort?.();
|
|
98
131
|
}
|
|
99
132
|
}
|
|
100
133
|
async close() {
|
|
134
|
+
this.#closed = true;
|
|
135
|
+
for (const request of this.#requests)
|
|
136
|
+
request.abort?.();
|
|
101
137
|
const controller = new AbortController;
|
|
102
138
|
const timer = setTimeout(() => controller.abort(new TimeoutError("OpenSearch close timed out after 2000ms")), 2000);
|
|
103
139
|
try {
|
|
104
|
-
await
|
|
140
|
+
await raceSignal(this.client.close(), controller.signal);
|
|
105
141
|
} catch (error) {
|
|
106
142
|
this.options.onDiagnostic?.(`OpenSearch close failed ${describeOpenSearchError(error)}`);
|
|
107
143
|
throw error;
|
|
@@ -121,27 +157,39 @@ function openSearchRetryReason(error) {
|
|
|
121
157
|
}
|
|
122
158
|
|
|
123
159
|
class OpenSearchClient {
|
|
124
|
-
|
|
160
|
+
configuration;
|
|
161
|
+
transport;
|
|
125
162
|
lifecycle;
|
|
126
163
|
#controller = new AbortController;
|
|
127
164
|
signal;
|
|
128
165
|
#initialization;
|
|
129
166
|
#disposal;
|
|
130
167
|
#engine;
|
|
168
|
+
#node;
|
|
169
|
+
get node() {
|
|
170
|
+
return this.#node;
|
|
171
|
+
}
|
|
172
|
+
#cleanupErrors = [];
|
|
131
173
|
#operations = new Set;
|
|
132
|
-
constructor(
|
|
133
|
-
this.
|
|
174
|
+
constructor(configuration, transport, lifecycle = {}) {
|
|
175
|
+
this.configuration = configuration;
|
|
176
|
+
this.transport = transport;
|
|
134
177
|
this.lifecycle = lifecycle;
|
|
135
178
|
this.signal = lifecycle.signal ? AbortSignal.any([lifecycle.signal, this.#controller.signal]) : this.#controller.signal;
|
|
136
179
|
}
|
|
137
180
|
initialize() {
|
|
138
181
|
this.signal.throwIfAborted();
|
|
139
182
|
return this.#initialization ??= (async () => {
|
|
140
|
-
const target = await
|
|
183
|
+
const target = await raceSignal(typeof this.configuration === "function" ? this.configuration() : Promise.resolve(this.configuration), this.signal);
|
|
141
184
|
this.signal.throwIfAborted();
|
|
142
|
-
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:"];
|
|
143
191
|
try {
|
|
144
|
-
this.#engine = await
|
|
192
|
+
this.#engine = await this.transport.acquire({
|
|
145
193
|
name: "OpenSearch connection",
|
|
146
194
|
signal: this.signal,
|
|
147
195
|
connectTimeoutMs: target.connectTimeoutMs ?? target.requestTimeoutMs ?? 1e4,
|
|
@@ -153,21 +201,40 @@ class OpenSearchClient {
|
|
|
153
201
|
this.lifecycle.onDiagnostic?.(`OpenSearch route start transport=${transport.name} target=${original.protocol}//${original.host} timeout_ms=${attempt.remainingMs()}`);
|
|
154
202
|
const endpoint = await transport.connect({
|
|
155
203
|
host: original.hostname,
|
|
156
|
-
port: Number(original.port || (original.protocol === "https:" ? 443 : 80))
|
|
157
|
-
|
|
158
|
-
});
|
|
204
|
+
port: Number(original.port || (original.protocol === "https:" ? 443 : 80))
|
|
205
|
+
}, attempt);
|
|
159
206
|
attempt.signal.throwIfAborted();
|
|
160
207
|
const url = new URL(original);
|
|
161
208
|
url.hostname = endpoint.host;
|
|
162
209
|
url.port = String(endpoint.port);
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
});
|
|
219
|
+
try {
|
|
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
|
+
}
|
|
170
236
|
}
|
|
237
|
+
throw new Error("OpenSearch has no protocol candidate");
|
|
171
238
|
},
|
|
172
239
|
close: (engine) => engine.close(),
|
|
173
240
|
retryReason: openSearchRetryReason
|
|
@@ -216,21 +283,27 @@ class OpenSearchClient {
|
|
|
216
283
|
return this.#disposal ??= (async () => {
|
|
217
284
|
this.#controller.abort(new Error("OpenSearch client disposed"));
|
|
218
285
|
await this.#initialization?.catch(() => {});
|
|
219
|
-
await Promise.allSettled(this.#operations);
|
|
220
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");
|
|
221
290
|
})();
|
|
222
291
|
}
|
|
223
292
|
}
|
|
224
|
-
async function openOpenSearch(
|
|
225
|
-
const client = new OpenSearchClient(
|
|
293
|
+
async function openOpenSearch(configuration, transport, lifecycle = {}) {
|
|
294
|
+
const client = new OpenSearchClient(configuration, transport, lifecycle);
|
|
226
295
|
try {
|
|
227
296
|
await client.initialize();
|
|
228
297
|
} catch (error) {
|
|
229
|
-
|
|
298
|
+
try {
|
|
299
|
+
await client.dispose();
|
|
300
|
+
} catch (cleanup) {
|
|
301
|
+
throw new AggregateError([error, cleanup], "OpenSearch initialization and cleanup failed", { cause: error });
|
|
302
|
+
}
|
|
230
303
|
throw error;
|
|
231
304
|
}
|
|
232
305
|
lifecycle.onDispose?.(() => client.dispose());
|
|
233
306
|
return client;
|
|
234
307
|
}
|
|
235
308
|
|
|
236
|
-
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 };
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
acquireConnection,
|
|
3
|
-
awaitWithSignal
|
|
4
|
-
} from "./chunk-gm4swndx.js";
|
|
5
1
|
import {
|
|
6
2
|
discoverRedisTopology
|
|
7
3
|
} from "./chunk-5tbzf9wd.js";
|
|
4
|
+
import {
|
|
5
|
+
isConnectionNetworkError
|
|
6
|
+
} from "./chunk-18gcp0ck.js";
|
|
7
|
+
import {
|
|
8
|
+
Transport
|
|
9
|
+
} from "./chunk-sb5qzv37.js";
|
|
8
10
|
import {
|
|
9
11
|
TimeoutError
|
|
10
12
|
} from "./chunk-rkt41za2.js";
|
|
11
|
-
import {
|
|
12
|
-
isConnectionNetworkError
|
|
13
|
-
} from "./chunk-h8fwx0k7.js";
|
|
14
13
|
|
|
15
14
|
// src/redis/client.ts
|
|
15
|
+
import { raceSignal } from "race-signal";
|
|
16
16
|
import { createClient, SocketClosedUnexpectedlyError, ConnectionTimeoutError } from "@redis/client";
|
|
17
17
|
function stringValue(value) {
|
|
18
18
|
if (Buffer.isBuffer(value))
|
|
@@ -96,7 +96,7 @@ class RedisConnection {
|
|
|
96
96
|
client.destroy();
|
|
97
97
|
};
|
|
98
98
|
try {
|
|
99
|
-
await
|
|
99
|
+
await raceSignal(client.connect(), signal);
|
|
100
100
|
signal.throwIfAborted();
|
|
101
101
|
return new RedisConnection(endpoint, client, config.timeoutMs, () => lastError, config.signal);
|
|
102
102
|
} catch (error) {
|
|
@@ -119,12 +119,14 @@ class RedisConnection {
|
|
|
119
119
|
const timer = setTimeout(() => timeout.abort(new TimeoutError(`Redis ${operationName} timed out after ${this.timeoutMs}ms (${this.endpoint.host}:${this.endpoint.port})`)), this.timeoutMs);
|
|
120
120
|
const signal = this.signal ? AbortSignal.any([this.signal, timeout.signal]) : timeout.signal;
|
|
121
121
|
try {
|
|
122
|
-
return await
|
|
122
|
+
return await raceSignal(Promise.resolve().then(() => {
|
|
123
123
|
signal.throwIfAborted();
|
|
124
124
|
return operation();
|
|
125
|
-
}), signal
|
|
125
|
+
}), signal);
|
|
126
126
|
} finally {
|
|
127
127
|
clearTimeout(timer);
|
|
128
|
+
if (signal.aborted)
|
|
129
|
+
this.close();
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
ping() {
|
|
@@ -171,19 +173,17 @@ class RedisAccess {
|
|
|
171
173
|
#connections = new Map;
|
|
172
174
|
#controller = new AbortController;
|
|
173
175
|
#signal;
|
|
174
|
-
#
|
|
176
|
+
#transport;
|
|
175
177
|
#closing;
|
|
176
178
|
constructor(transports, baseConfig, connectionFactory = RedisConnection.connect, lifecycle = {}) {
|
|
177
179
|
this.baseConfig = baseConfig;
|
|
178
180
|
this.connectionFactory = connectionFactory;
|
|
179
181
|
this.lifecycle = lifecycle;
|
|
180
|
-
this.#
|
|
181
|
-
if (!this.#transports.length)
|
|
182
|
-
throw new Error("Redis requires a TCP transport");
|
|
182
|
+
this.#transport = transports instanceof Transport ? transports : new Transport(Array.isArray(transports) ? transports : [transports]);
|
|
183
183
|
for (const value of [
|
|
184
184
|
baseConfig.timeoutMs,
|
|
185
185
|
baseConfig.connectTimeoutMs ?? baseConfig.timeoutMs,
|
|
186
|
-
baseConfig.acquireTimeoutMs ?? (baseConfig.connectTimeoutMs ?? baseConfig.timeoutMs)
|
|
186
|
+
baseConfig.acquireTimeoutMs ?? (baseConfig.connectTimeoutMs ?? baseConfig.timeoutMs)
|
|
187
187
|
]) {
|
|
188
188
|
if (!Number.isFinite(value) || value <= 0)
|
|
189
189
|
throw new Error("Redis timeouts must be positive milliseconds");
|
|
@@ -214,14 +214,16 @@ class RedisAccess {
|
|
|
214
214
|
return pending;
|
|
215
215
|
}
|
|
216
216
|
#open(endpoint, database, credentials) {
|
|
217
|
-
return
|
|
217
|
+
return this.#transport.acquire({
|
|
218
218
|
name: "Redis connection",
|
|
219
219
|
signal: this.#signal,
|
|
220
220
|
connectTimeoutMs: this.baseConfig.connectTimeoutMs ?? this.baseConfig.timeoutMs,
|
|
221
221
|
acquireTimeoutMs: this.baseConfig.acquireTimeoutMs,
|
|
222
222
|
onRoute: this.lifecycle.onRoute,
|
|
223
223
|
open: async (transport, attempt) => {
|
|
224
|
-
|
|
224
|
+
if (transport.kind !== "tcp")
|
|
225
|
+
throw new Error("Redis requires a TCP transport");
|
|
226
|
+
const mapped = await transport.connect(endpoint, attempt);
|
|
225
227
|
const connection = await this.connectionFactory(endpoint, mapped, {
|
|
226
228
|
...this.baseConfig,
|
|
227
229
|
...credentials,
|
|
@@ -263,7 +265,8 @@ class RedisAccess {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
class RedisClient {
|
|
266
|
-
|
|
268
|
+
configuration;
|
|
269
|
+
transport;
|
|
267
270
|
lifecycle;
|
|
268
271
|
#controller = new AbortController;
|
|
269
272
|
signal;
|
|
@@ -271,22 +274,20 @@ class RedisClient {
|
|
|
271
274
|
#disposal;
|
|
272
275
|
#access;
|
|
273
276
|
#target;
|
|
274
|
-
constructor(
|
|
275
|
-
this.
|
|
277
|
+
constructor(configuration, transport, lifecycle = {}) {
|
|
278
|
+
this.configuration = configuration;
|
|
279
|
+
this.transport = transport;
|
|
276
280
|
this.lifecycle = lifecycle;
|
|
277
281
|
this.signal = lifecycle.signal ? AbortSignal.any([lifecycle.signal, this.#controller.signal]) : this.#controller.signal;
|
|
278
282
|
}
|
|
279
283
|
initialize() {
|
|
280
284
|
this.signal.throwIfAborted();
|
|
281
285
|
return this.#initialization ??= (async () => {
|
|
282
|
-
this.#target = await
|
|
286
|
+
this.#target = await raceSignal(typeof this.configuration === "function" ? this.configuration() : Promise.resolve(this.configuration), this.signal);
|
|
283
287
|
this.signal.throwIfAborted();
|
|
284
|
-
if (!this.source.transports.length || this.source.transports.some((transport) => transport.kind !== "tcp")) {
|
|
285
|
-
throw new Error("Redis requires TCP transports");
|
|
286
|
-
}
|
|
287
288
|
if (this.#target.clusterType === "cluster" && this.#target.database !== 0)
|
|
288
289
|
throw new Error("Redis Cluster requires database 0");
|
|
289
|
-
this.#access = new RedisAccess(this.
|
|
290
|
+
this.#access = new RedisAccess(this.transport, this.#target, undefined, { ...this.lifecycle, signal: this.signal });
|
|
290
291
|
})();
|
|
291
292
|
}
|
|
292
293
|
get access() {
|
|
@@ -311,8 +312,8 @@ class RedisClient {
|
|
|
311
312
|
})();
|
|
312
313
|
}
|
|
313
314
|
}
|
|
314
|
-
async function openRedis(
|
|
315
|
-
const client = new RedisClient(
|
|
315
|
+
async function openRedis(configuration, transport, lifecycle = {}) {
|
|
316
|
+
const client = new RedisClient(configuration, transport, lifecycle);
|
|
316
317
|
try {
|
|
317
318
|
await client.initialize();
|
|
318
319
|
} catch (error) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquireConnection
|
|
3
|
+
} from "./chunk-43mcyrh4.js";
|
|
4
|
+
|
|
5
|
+
// src/transport/lifecycle.ts
|
|
6
|
+
class Transport {
|
|
7
|
+
candidates;
|
|
8
|
+
#controller = new AbortController;
|
|
9
|
+
resourceSignal = this.#controller.signal;
|
|
10
|
+
signal;
|
|
11
|
+
#pending = new Set;
|
|
12
|
+
#cleanupErrors = [];
|
|
13
|
+
#disposal;
|
|
14
|
+
constructor(candidates = [], signal) {
|
|
15
|
+
this.candidates = candidates;
|
|
16
|
+
this.signal = signal ? AbortSignal.any([signal, this.#controller.signal]) : this.#controller.signal;
|
|
17
|
+
}
|
|
18
|
+
paths() {
|
|
19
|
+
return this.candidates;
|
|
20
|
+
}
|
|
21
|
+
async initialize() {
|
|
22
|
+
this.signal.throwIfAborted();
|
|
23
|
+
}
|
|
24
|
+
acquire(options) {
|
|
25
|
+
this.signal.throwIfAborted();
|
|
26
|
+
const signal = options.signal ? AbortSignal.any([options.signal, this.signal]) : this.signal;
|
|
27
|
+
return acquireConnection(this.paths(), {
|
|
28
|
+
...options,
|
|
29
|
+
signal,
|
|
30
|
+
onPending: (pending) => {
|
|
31
|
+
this.#pending.add(pending);
|
|
32
|
+
pending.then(() => this.#pending.delete(pending), () => this.#pending.delete(pending));
|
|
33
|
+
},
|
|
34
|
+
close: async (connection) => {
|
|
35
|
+
try {
|
|
36
|
+
await options.close(connection);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
this.#cleanupErrors.push(error);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async close() {}
|
|
45
|
+
dispose() {
|
|
46
|
+
return this.#disposal ??= (async () => {
|
|
47
|
+
this.#controller.abort(new Error("Transport disposed"));
|
|
48
|
+
const errors = [];
|
|
49
|
+
try {
|
|
50
|
+
await this.close();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
errors.push(error);
|
|
53
|
+
}
|
|
54
|
+
await Promise.allSettled([...this.#pending]);
|
|
55
|
+
errors.push(...this.#cleanupErrors);
|
|
56
|
+
if (errors.length)
|
|
57
|
+
throw new AggregateError(errors, "Transport cleanup failed");
|
|
58
|
+
})();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { Transport };
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KubernetesResourceTransport
|
|
3
|
+
} from "./chunk-5w85c7cs.js";
|
|
1
4
|
import {
|
|
2
5
|
resolveWorkload
|
|
3
6
|
} from "./chunk-ae84rzq5.js";
|
|
4
|
-
import {
|
|
5
|
-
ServicePortForwarder
|
|
6
|
-
} from "./chunk-1ts7pswj.js";
|
|
7
|
-
import {
|
|
8
|
-
KubernetesResourceTransport
|
|
9
|
-
} from "./chunk-x0yhfg29.js";
|
|
10
7
|
import {
|
|
11
8
|
KubernetesExecTransport
|
|
12
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-xeft7af0.js";
|
|
13
10
|
import {
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
11
|
+
ServicePortForwarder
|
|
12
|
+
} from "./chunk-0d8vs5q8.js";
|
|
16
13
|
import {
|
|
17
14
|
KubectlExecutor
|
|
18
15
|
} from "./chunk-rq5fc007.js";
|
|
16
|
+
import {
|
|
17
|
+
KubernetesError
|
|
18
|
+
} from "./chunk-rkt41za2.js";
|
|
19
19
|
|
|
20
20
|
// src/kubernetes/client.ts
|
|
21
21
|
class KubernetesClient {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { ClientLifecycle } from "../datasource";
|
|
2
|
-
/** Stop waiting without leaving rejected preparation promises unobserved. */
|
|
3
|
-
export declare function awaitWithSignal<T>(pending: Promise<T>, signal: AbortSignal, cancel?: () => void): Promise<T>;
|
|
4
2
|
export interface ConnectionAttempt {
|
|
5
3
|
readonly signal: AbortSignal;
|
|
6
4
|
/** Remaining mapping + handshake budget, not a fresh timeout for each phase. */
|
|
7
5
|
remainingMs(): number;
|
|
8
6
|
}
|
|
9
|
-
interface Acquisition<R, C> {
|
|
7
|
+
export interface Acquisition<R, C> {
|
|
10
8
|
name: string;
|
|
11
9
|
connectTimeoutMs: number;
|
|
12
10
|
acquireTimeoutMs?: number;
|
|
@@ -19,6 +17,8 @@ interface Acquisition<R, C> {
|
|
|
19
17
|
/** Undefined means terminal; authentication and protocol errors must not change route. */
|
|
20
18
|
retryReason(error: unknown): string | undefined;
|
|
21
19
|
onRoute?: ClientLifecycle["onRoute"];
|
|
20
|
+
/** Owner tracks late completion through its rollback, not only the initial open promise. */
|
|
21
|
+
onPending?: (pending: Promise<unknown>) => void;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* @spec One acquisition owns ordered attempts and a deadline covering mapping and handshake.
|
|
@@ -31,4 +31,3 @@ interface Acquisition<R, C> {
|
|
|
31
31
|
export declare function acquireConnection<R extends {
|
|
32
32
|
readonly name: string;
|
|
33
33
|
}, C>(routes: readonly R[], options: Acquisition<R, C>): Promise<C>;
|
|
34
|
-
export {};
|
package/dist/datasource.d.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
export { clientKey, ClientManager, EnvironmentContext, type Client, type ClientProvider, type DataSource, type ServiceDataSource } from "@compforge/harness-common";
|
|
2
|
-
import type { Transport } from "./transport";
|
|
3
2
|
export { ToolboxError, KubernetesError, type ErrorKind } from "./errors";
|
|
4
|
-
/** Resolution owns configuration semantics; transports preserve the resolved target and identity. */
|
|
5
|
-
export interface ConnectionSource<Target> {
|
|
6
|
-
resolve(): Promise<Target>;
|
|
7
|
-
readonly transports: readonly Transport[];
|
|
8
|
-
}
|
|
9
3
|
export interface ClientLifecycle {
|
|
10
4
|
signal?: AbortSignal;
|
|
11
5
|
onDispose?: (dispose: () => Promise<void>) => void;
|
package/dist/file/client.js
CHANGED
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
BoundedFileClient,
|
|
3
3
|
fileClientOptions,
|
|
4
4
|
filePaths
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import"../chunk-rkt41za2.js";
|
|
5
|
+
} from "../chunk-rncagjbc.js";
|
|
7
6
|
import"../chunk-v3hxvnrc.js";
|
|
7
|
+
import"../chunk-rkt41za2.js";
|
|
8
8
|
export {
|
|
9
9
|
filePaths,
|
|
10
10
|
fileClientOptions,
|
package/dist/file/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PodFileClient,
|
|
3
3
|
PodFileDataSource
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-xhfbark7.js";
|
|
5
5
|
import {
|
|
6
6
|
LocalFileClient,
|
|
7
7
|
LocalFileDataSource
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import"../chunk-
|
|
8
|
+
} from "../chunk-92aacbwh.js";
|
|
9
|
+
import"../chunk-rncagjbc.js";
|
|
10
|
+
import"../chunk-v3hxvnrc.js";
|
|
10
11
|
import {
|
|
11
12
|
FileError
|
|
12
13
|
} from "../chunk-rkt41za2.js";
|
|
13
|
-
import"../chunk-v3hxvnrc.js";
|
|
14
14
|
|
|
15
15
|
// src/file/index.ts
|
|
16
16
|
import { FileDataSource } from "@compforge/harness-common";
|
package/dist/file/local.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LocalFileClient,
|
|
3
3
|
LocalFileDataSource
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import"../chunk-
|
|
6
|
-
import"../chunk-rkt41za2.js";
|
|
4
|
+
} from "../chunk-92aacbwh.js";
|
|
5
|
+
import"../chunk-rncagjbc.js";
|
|
7
6
|
import"../chunk-v3hxvnrc.js";
|
|
7
|
+
import"../chunk-rkt41za2.js";
|
|
8
8
|
export {
|
|
9
9
|
LocalFileDataSource,
|
|
10
10
|
LocalFileClient
|
package/dist/file/pod.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PodFileClient,
|
|
3
3
|
PodFileDataSource
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import"../chunk-
|
|
6
|
-
import"../chunk-rkt41za2.js";
|
|
4
|
+
} from "../chunk-xhfbark7.js";
|
|
5
|
+
import"../chunk-rncagjbc.js";
|
|
7
6
|
import"../chunk-v3hxvnrc.js";
|
|
7
|
+
import"../chunk-rkt41za2.js";
|
|
8
8
|
export {
|
|
9
9
|
PodFileDataSource,
|
|
10
10
|
PodFileClient
|