@compforge/harness-toolbox 0.3.8 → 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 +10 -0
- package/dist/{chunk-qqntng20.js → chunk-ahc00qs5.js} +21 -4
- package/dist/{chunk-w3a4p6jm.js → chunk-b7f39m4v.js} +68 -14
- package/dist/mysql/client.d.ts +5 -1
- package/dist/mysql/client.js +2 -1
- package/dist/mysql/index.js +2 -1
- package/dist/opensearch/client.d.ts +16 -2
- package/dist/opensearch/client.js +8 -1
- package/dist/opensearch/index.js +8 -1
- package/package.json +1 -1
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.
|
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
queryMysqlViaPod,
|
|
10
10
|
queryMysqlViaPodBatch
|
|
11
11
|
} from "./chunk-m91t44jh.js";
|
|
12
|
+
import {
|
|
13
|
+
ToolboxError
|
|
14
|
+
} from "./chunk-zhkrydeg.js";
|
|
12
15
|
import {
|
|
13
16
|
ConcurrencyPool
|
|
14
17
|
} from "./chunk-v3hxvnrc.js";
|
|
@@ -18,6 +21,16 @@ import {
|
|
|
18
21
|
|
|
19
22
|
// src/mysql/client.ts
|
|
20
23
|
import { createConnection } from "mysql2/promise";
|
|
24
|
+
function mysqlOperationError(error, phase) {
|
|
25
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "PROTOCOL_CONNECTION_LOST")
|
|
26
|
+
return error;
|
|
27
|
+
return new ToolboxError(`MySQL ${phase} failed: connection lost (PROTOCOL_CONNECTION_LOST)`, {
|
|
28
|
+
kind: "connection_lost",
|
|
29
|
+
code: error.code,
|
|
30
|
+
cause: error
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
class MysqlDatabase {
|
|
22
35
|
transports;
|
|
23
36
|
options;
|
|
@@ -56,6 +69,8 @@ class MysqlDatabase {
|
|
|
56
69
|
throw new Error("Bounded readonly MySQL queries require a native TCP transport");
|
|
57
70
|
try {
|
|
58
71
|
return await queryReadonlySession(session.connection, sql, values, limits, this.options.signal);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw mysqlOperationError(error, "query");
|
|
59
74
|
} finally {
|
|
60
75
|
destroyMysqlConnection(session.connection);
|
|
61
76
|
}
|
|
@@ -94,7 +109,7 @@ class MysqlDatabase {
|
|
|
94
109
|
this.#connections.delete(key);
|
|
95
110
|
if (session?.kind === "tcp")
|
|
96
111
|
destroyMysqlConnection(session.connection);
|
|
97
|
-
throw error;
|
|
112
|
+
throw session ? mysqlOperationError(error, "query") : error;
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
async#query(target, sql, values) {
|
|
@@ -130,9 +145,11 @@ class MysqlDatabase {
|
|
|
130
145
|
this.options.onRoute?.({ transport: endpoint.host === target.host && endpoint.port === target.port ? "direct" : transport.name, reason });
|
|
131
146
|
return { kind: "tcp", connection };
|
|
132
147
|
} catch (error) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
const failure = mysqlOperationError(error, "connect");
|
|
149
|
+
const connectionLost = failure instanceof ToolboxError && failure.kind === "connection_lost";
|
|
150
|
+
if (!isConnectionNetworkError(failure) && !connectionLost || i === this.transports.length - 1)
|
|
151
|
+
throw failure;
|
|
152
|
+
reason = String(failure.code);
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
155
|
throw new Error("MySQL DataSource has no transport");
|
|
@@ -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() {
|
package/dist/mysql/client.d.ts
CHANGED
|
@@ -8,7 +8,11 @@ export interface MysqlDatabaseOptions extends ClientLifecycle {
|
|
|
8
8
|
queryTimeoutMs: number;
|
|
9
9
|
}
|
|
10
10
|
type ConnectionFactory = (options: ConnectionOptions) => Promise<Connection>;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* @spec SQL is executed once; only native connection establishment may advance to another transport.
|
|
13
|
+
* @why mysql2 reports a socket closed during handshake as PROTOCOL_CONNECTION_LOST, before any SQL is sent.
|
|
14
|
+
* @case id=mysql_handshake_closed,desc=`Peer closes during handshake`,expect=`Try the next transport`,forbid=`Replay an interrupted query`
|
|
15
|
+
*/
|
|
12
16
|
export declare class MysqlDatabase implements Database {
|
|
13
17
|
#private;
|
|
14
18
|
private readonly transports;
|
package/dist/mysql/client.js
CHANGED
|
@@ -2,10 +2,11 @@ import {
|
|
|
2
2
|
MysqlClient,
|
|
3
3
|
MysqlDatabase,
|
|
4
4
|
openMysql
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-ahc00qs5.js";
|
|
6
6
|
import"../chunk-age65q4n.js";
|
|
7
7
|
import"../chunk-136egyff.js";
|
|
8
8
|
import"../chunk-m91t44jh.js";
|
|
9
|
+
import"../chunk-zhkrydeg.js";
|
|
9
10
|
import"../chunk-v3hxvnrc.js";
|
|
10
11
|
import"../chunk-h8fwx0k7.js";
|
|
11
12
|
import"../chunk-wv4jwhvj.js";
|
package/dist/mysql/index.js
CHANGED
|
@@ -14,10 +14,11 @@ import {
|
|
|
14
14
|
MysqlClient,
|
|
15
15
|
MysqlDatabase,
|
|
16
16
|
openMysql
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-ahc00qs5.js";
|
|
18
18
|
import"../chunk-age65q4n.js";
|
|
19
19
|
import"../chunk-136egyff.js";
|
|
20
20
|
import"../chunk-m91t44jh.js";
|
|
21
|
+
import"../chunk-zhkrydeg.js";
|
|
21
22
|
import"../chunk-v3hxvnrc.js";
|
|
22
23
|
import"../chunk-h8fwx0k7.js";
|
|
23
24
|
import"../chunk-wv4jwhvj.js";
|
|
@@ -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,
|