@compforge/harness-toolbox 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -250,3 +250,13 @@ Disposal closes forwards and the exec stream; EOF closes remote listeners/connec
|
|
|
250
250
|
connection is lost without EOF, a 30-second heartbeat lease bounds remote process lifetime.
|
|
251
251
|
A dead relay invalidates its routes and fails callers; no SQL or other protocol operation is replayed.
|
|
252
252
|
The root should finish or cancel protocol work before disposing the transport.
|
|
253
|
+
|
|
254
|
+
### OpenSearch connection preparation
|
|
255
|
+
|
|
256
|
+
`openOpenSearch` probes the resolved endpoint before returning. It tries the supplied TCP transports
|
|
257
|
+
in order only for connection failures; HTTP authorization/server errors stop preparation, and failed
|
|
258
|
+
reads are never replayed on another route. `onRoute` reports the selected transport and safe fallback
|
|
259
|
+
reason. The caller owns shared transports such as `PodRelayTransport` and their cleanup.
|
|
260
|
+
|
|
261
|
+
Routed HTTPS preserves the original TLS server name. Bun disables HTTP keep-alive because its reused
|
|
262
|
+
socket recovery can replay a failed request below the driver; Node retains connection pooling.
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolboxError
|
|
3
|
+
} from "./chunk-zhkrydeg.js";
|
|
4
|
+
import {
|
|
5
|
+
isConnectionNetworkError
|
|
6
|
+
} from "./chunk-h8fwx0k7.js";
|
|
7
|
+
|
|
1
8
|
// src/opensearch/client.ts
|
|
2
|
-
import { Client } from "@opensearch-project/opensearch";
|
|
9
|
+
import { Client, Connection, errors } from "@opensearch-project/opensearch";
|
|
3
10
|
function isOpenSearchReadApi(search) {
|
|
4
11
|
return typeof search.request === "function";
|
|
5
12
|
}
|
|
13
|
+
|
|
14
|
+
class OpenSearchConnection extends Connection {
|
|
15
|
+
buildRequestObject(params) {
|
|
16
|
+
return { ...super.buildRequestObject(params), servername: this.ssl?.servername };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
6
19
|
function createClient(options) {
|
|
7
20
|
return new Client({
|
|
8
21
|
node: options.node,
|
|
22
|
+
Connection: OpenSearchConnection,
|
|
9
23
|
auth: options.auth.username && options.auth.password ? { username: options.auth.username, password: options.auth.password } : undefined,
|
|
10
|
-
ssl: { rejectUnauthorized: false },
|
|
24
|
+
ssl: { rejectUnauthorized: false, servername: options.servername },
|
|
11
25
|
requestTimeout: options.requestTimeoutMs ?? 60000,
|
|
12
|
-
maxRetries: 0
|
|
26
|
+
maxRetries: 0,
|
|
27
|
+
agent: { keepAlive: !process.versions.bun }
|
|
13
28
|
});
|
|
14
29
|
}
|
|
15
30
|
|
|
@@ -36,8 +51,18 @@ class OpenSearchEngine {
|
|
|
36
51
|
});
|
|
37
52
|
return response.body;
|
|
38
53
|
}
|
|
39
|
-
ping() {
|
|
40
|
-
|
|
54
|
+
async ping(signal, timeoutMs) {
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
const request = this.client.ping({}, timeoutMs === undefined ? undefined : { requestTimeout: timeoutMs });
|
|
57
|
+
const abort = () => {
|
|
58
|
+
request.abort?.();
|
|
59
|
+
};
|
|
60
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
61
|
+
try {
|
|
62
|
+
return await request;
|
|
63
|
+
} finally {
|
|
64
|
+
signal?.removeEventListener("abort", abort);
|
|
65
|
+
}
|
|
41
66
|
}
|
|
42
67
|
close() {
|
|
43
68
|
return this.client.close();
|
|
@@ -46,6 +71,7 @@ class OpenSearchEngine {
|
|
|
46
71
|
|
|
47
72
|
class OpenSearchClient {
|
|
48
73
|
source;
|
|
74
|
+
lifecycle;
|
|
49
75
|
#controller = new AbortController;
|
|
50
76
|
signal;
|
|
51
77
|
#initialization;
|
|
@@ -54,6 +80,7 @@ class OpenSearchClient {
|
|
|
54
80
|
#operations = new Set;
|
|
55
81
|
constructor(source, lifecycle = {}) {
|
|
56
82
|
this.source = source;
|
|
83
|
+
this.lifecycle = lifecycle;
|
|
57
84
|
this.signal = lifecycle.signal ? AbortSignal.any([lifecycle.signal, this.#controller.signal]) : this.#controller.signal;
|
|
58
85
|
}
|
|
59
86
|
initialize() {
|
|
@@ -61,15 +88,42 @@ class OpenSearchClient {
|
|
|
61
88
|
return this.#initialization ??= (async () => {
|
|
62
89
|
const target = await this.source.resolve();
|
|
63
90
|
this.signal.throwIfAborted();
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
const original = new URL(target.node);
|
|
92
|
+
let reason;
|
|
93
|
+
for (const [index, transport] of this.source.transports.entries()) {
|
|
94
|
+
this.signal.throwIfAborted();
|
|
95
|
+
if (transport.kind !== "tcp")
|
|
96
|
+
throw new Error("OpenSearch requires a TCP transport");
|
|
97
|
+
let engine;
|
|
98
|
+
try {
|
|
99
|
+
const endpoint = await transport.connect({
|
|
100
|
+
host: original.hostname,
|
|
101
|
+
port: Number(original.port || (original.protocol === "https:" ? 443 : 80)),
|
|
102
|
+
servername: target.servername ?? original.hostname
|
|
103
|
+
});
|
|
104
|
+
this.signal.throwIfAborted();
|
|
105
|
+
const url = new URL(original);
|
|
106
|
+
url.hostname = endpoint.host;
|
|
107
|
+
url.port = String(endpoint.port);
|
|
108
|
+
engine = new OpenSearchEngine({ ...target, node: url.toString(), servername: endpoint.servername ?? target.servername ?? original.hostname });
|
|
109
|
+
await engine.ping(this.signal, target.connectTimeoutMs ?? target.requestTimeoutMs ?? 1e4);
|
|
110
|
+
this.signal.throwIfAborted();
|
|
111
|
+
this.lifecycle.onRoute?.({ transport: transport.name, reason });
|
|
112
|
+
this.#engine = engine;
|
|
113
|
+
return;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
await engine?.close();
|
|
116
|
+
this.signal.throwIfAborted();
|
|
117
|
+
const network = isConnectionNetworkError(error);
|
|
118
|
+
if (!network && !(error instanceof errors.ConnectionError) && !(error instanceof errors.TimeoutError))
|
|
119
|
+
throw error;
|
|
120
|
+
reason = network ? String(error.code) : error.name;
|
|
121
|
+
if (index === this.source.transports.length - 1) {
|
|
122
|
+
throw new ToolboxError(`OpenSearch connect failed (${reason})`, { kind: "connection_failed", code: reason, cause: error });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
throw new Error("OpenSearch DataSource has no transport");
|
|
73
127
|
})();
|
|
74
128
|
}
|
|
75
129
|
#ready() {
|
|
@@ -9,6 +9,10 @@ export interface OpenSearchOptions {
|
|
|
9
9
|
node: string;
|
|
10
10
|
auth: OpenSearchAuth;
|
|
11
11
|
requestTimeoutMs?: number;
|
|
12
|
+
/** Deadline for the initialization probe; query deadlines remain independent. */
|
|
13
|
+
connectTimeoutMs?: number;
|
|
14
|
+
/** Original TLS identity when a transport maps the socket to localhost. */
|
|
15
|
+
servername?: string;
|
|
12
16
|
}
|
|
13
17
|
export interface OpenSearchClientApi {
|
|
14
18
|
count(params: Record<string, unknown>): Promise<{
|
|
@@ -19,7 +23,11 @@ export interface OpenSearchClientApi {
|
|
|
19
23
|
search(params: Record<string, unknown>): Promise<{
|
|
20
24
|
body: SearchResult;
|
|
21
25
|
}>;
|
|
22
|
-
ping(params?: Record<string, unknown
|
|
26
|
+
ping(params?: Record<string, unknown>, options?: {
|
|
27
|
+
requestTimeout?: number;
|
|
28
|
+
}): Promise<unknown> & {
|
|
29
|
+
abort?(): void;
|
|
30
|
+
};
|
|
23
31
|
transport?: {
|
|
24
32
|
request(params: {
|
|
25
33
|
method: string;
|
|
@@ -43,12 +51,18 @@ export declare class OpenSearchEngine implements OpenSearchReadApi {
|
|
|
43
51
|
count(index: string, query: SearchQuery): Promise<number>;
|
|
44
52
|
search(index: string, body: SearchQuery): Promise<SearchResult>;
|
|
45
53
|
request(path: string, query?: SearchQuery): Promise<unknown>;
|
|
46
|
-
ping(): Promise<unknown>;
|
|
54
|
+
ping(signal?: AbortSignal, timeoutMs?: number): Promise<unknown>;
|
|
47
55
|
close(): Promise<void>;
|
|
48
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* @spec Only connection preparation may advance to another Transport; reads are never replayed.
|
|
59
|
+
* @why The official client connects lazily and wraps socket errors, so creating it does not prove reachability.
|
|
60
|
+
* @case id=opensearch_connect_fallback,desc=`Host cannot reach target`,expect=`Probe next TCP route`,forbid=`Replay a failed search`
|
|
61
|
+
*/
|
|
49
62
|
export declare class OpenSearchClient implements ManagedClient, OpenSearchReadApi {
|
|
50
63
|
#private;
|
|
51
64
|
private readonly source;
|
|
65
|
+
private readonly lifecycle;
|
|
52
66
|
readonly signal: AbortSignal;
|
|
53
67
|
constructor(source: ConnectionSource<OpenSearchOptions>, lifecycle?: ClientLifecycle);
|
|
54
68
|
initialize(): Promise<void>;
|
|
@@ -3,7 +3,14 @@ import {
|
|
|
3
3
|
OpenSearchEngine,
|
|
4
4
|
isOpenSearchReadApi,
|
|
5
5
|
openOpenSearch
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-b7f39m4v.js";
|
|
7
|
+
import"../chunk-zhkrydeg.js";
|
|
8
|
+
import"../chunk-h8fwx0k7.js";
|
|
9
|
+
import"../chunk-wv4jwhvj.js";
|
|
10
|
+
import"../chunk-631vce5z.js";
|
|
11
|
+
import"../chunk-hmah9t8e.js";
|
|
12
|
+
import"../chunk-mn6dwsvh.js";
|
|
13
|
+
import"../chunk-ssj93brx.js";
|
|
7
14
|
export {
|
|
8
15
|
openOpenSearch,
|
|
9
16
|
isOpenSearchReadApi,
|
package/dist/opensearch/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
OpenSearchEngine,
|
|
4
4
|
isOpenSearchReadApi,
|
|
5
5
|
openOpenSearch
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-b7f39m4v.js";
|
|
7
7
|
import {
|
|
8
8
|
searchAfterPages
|
|
9
9
|
} from "../chunk-99p0zt4f.js";
|
|
@@ -11,6 +11,13 @@ import {
|
|
|
11
11
|
parseOpenSearchEndpoint
|
|
12
12
|
} from "../chunk-s1sjyem7.js";
|
|
13
13
|
import"../chunk-djk4kmxb.js";
|
|
14
|
+
import"../chunk-zhkrydeg.js";
|
|
15
|
+
import"../chunk-h8fwx0k7.js";
|
|
16
|
+
import"../chunk-wv4jwhvj.js";
|
|
17
|
+
import"../chunk-631vce5z.js";
|
|
18
|
+
import"../chunk-hmah9t8e.js";
|
|
19
|
+
import"../chunk-mn6dwsvh.js";
|
|
20
|
+
import"../chunk-ssj93brx.js";
|
|
14
21
|
export {
|
|
15
22
|
searchAfterPages,
|
|
16
23
|
parseOpenSearchEndpoint,
|