@compforge/harness-toolbox 0.1.0 → 0.1.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 +37 -1
- package/dist/chunk-9j36zvn4.js +35 -0
- package/dist/chunk-x4ntb898.js +46 -0
- package/dist/chunk-zp1s833x.js +200 -0
- package/dist/kubernetes/client.js +2 -2
- package/dist/kubernetes/service-port-forward.js +2 -2
- package/dist/mysql/client.js +2 -2
- package/dist/mysql/index.js +6 -6
- package/dist/redis/index.js +7 -7
- package/dist/s3/client.d.ts +40 -0
- package/dist/s3/client.js +8 -0
- package/dist/s3/datasource.d.ts +14 -0
- package/dist/s3/datasource.js +12 -0
- package/dist/s3/handler.d.ts +13 -0
- package/dist/s3/handler.js +6 -0
- package/dist/s3/index.d.ts +3 -0
- package/dist/s3/index.js +15 -0
- package/dist/s3/types.d.ts +51 -0
- package/dist/s3/types.js +0 -0
- package/package.json +14 -4
- package/dist/{chunk-205a4vma.js → chunk-p097seg7.js} +3 -3
- package/dist/{chunk-mkhyty75.js → chunk-sry9e02n.js} +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Harness Toolbox
|
|
2
2
|
|
|
3
3
|
Shared infrastructure for diagnostic tools, test runners and business adapters. Use the same
|
|
4
|
-
clients to access Kubernetes, MySQL, Redis and
|
|
4
|
+
clients to access Kubernetes, MySQL, Redis, OpenSearch and S3-compatible object storage without depending on a particular
|
|
5
5
|
command, plugin protocol, test model or verdict.
|
|
6
6
|
|
|
7
7
|
The package supports Node.js 22+ and Bun. Install with `npm install @compforge/harness-toolbox`.
|
|
@@ -45,3 +45,39 @@ capture policy, shared concurrency pool and byte budget; the package does not ch
|
|
|
45
45
|
limits. Temporary capture files live until client disposal.
|
|
46
46
|
|
|
47
47
|
See [toolbox contracts](../../../docs/toolbox.md) for ownership and cross-language semantics.
|
|
48
|
+
|
|
49
|
+
## Read S3 objects
|
|
50
|
+
|
|
51
|
+
S3 uses the official AWS SDK behind the same managed lifecycle. Supply resolved connection
|
|
52
|
+
settings and explicit limits; the package does not discover business configuration or credentials.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { ClientManager } from "@compforge/harness-toolbox";
|
|
56
|
+
import { S3DataSource } from "@compforge/harness-toolbox/s3";
|
|
57
|
+
|
|
58
|
+
const clients = new ClientManager();
|
|
59
|
+
try {
|
|
60
|
+
const s3 = await clients.get(new S3DataSource({
|
|
61
|
+
endpoint: "https://s3.example.com", region: "us-east-1",
|
|
62
|
+
credentials: { accessKeyId: "...", secretAccessKey: "..." },
|
|
63
|
+
}, { concurrency: 4, connectTimeoutMs: 5_000, requestTimeoutMs: 15_000 }));
|
|
64
|
+
const object = await s3.headObject("artifacts", "runs/example.json");
|
|
65
|
+
const prefix = await s3.readObject("artifacts", "runs/example.json", { maxBytes: 65_536 });
|
|
66
|
+
// prefix.truncated distinguishes a bounded prefix from the complete object.
|
|
67
|
+
} finally {
|
|
68
|
+
await clients.dispose();
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`listObjects` and `listBuckets` return one explicitly bounded page with continuation tokens;
|
|
73
|
+
callers own traversal and total scan budgets. Bucket HEAD and versioning are also available.
|
|
74
|
+
Errors preserve SDK status/code: a failed HEAD is not converted to `exists=false`. Reads stop at
|
|
75
|
+
the byte cap and close the body; request deadlines include queueing and body consumption.
|
|
76
|
+
No write operations, automatic retries or implicit region redirects are exposed.
|
|
77
|
+
|
|
78
|
+
For a mapped TCP transport, pass `{ key: "cluster/context identity", transport }` as the third
|
|
79
|
+
DataSource argument. The caller owns that transport's lifetime. Path-style addressing is required
|
|
80
|
+
for mapped routes; direct endpoints also support virtual-hosted buckets. Signed Host and TLS
|
|
81
|
+
identity remain the logical endpoint, not the forwarded address. Private CAs may be supplied
|
|
82
|
+
explicitly with `ca`; certificate verification stays enabled. Initialization prepares the client
|
|
83
|
+
and route without listing buckets or claiming that credentials have been validated.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dataSourceKey
|
|
3
|
+
} from "./chunk-hqx7j9xe.js";
|
|
4
|
+
import {
|
|
5
|
+
S3Client
|
|
6
|
+
} from "./chunk-zp1s833x.js";
|
|
7
|
+
import {
|
|
8
|
+
DirectTransport
|
|
9
|
+
} from "./chunk-xy4g56g4.js";
|
|
10
|
+
|
|
11
|
+
// src/s3/datasource.ts
|
|
12
|
+
class S3DataSource {
|
|
13
|
+
key;
|
|
14
|
+
#target;
|
|
15
|
+
#limits;
|
|
16
|
+
#transport;
|
|
17
|
+
constructor(target, limits, route) {
|
|
18
|
+
this.#target = structuredClone(target);
|
|
19
|
+
this.#limits = { ...limits };
|
|
20
|
+
this.#transport = route?.transport ?? new DirectTransport;
|
|
21
|
+
this.key = dataSourceKey("s3", {
|
|
22
|
+
target: this.#target,
|
|
23
|
+
limits: this.#limits,
|
|
24
|
+
route: route ? { key: route.key, transport: route.transport.name } : "direct"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
createClient(signal) {
|
|
28
|
+
return new S3Client({
|
|
29
|
+
resolve: async () => this.#target,
|
|
30
|
+
transports: [this.#transport]
|
|
31
|
+
}, this.#limits, { signal });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { S3DataSource };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/s3/handler.ts
|
|
2
|
+
import { Agent as HttpAgent } from "node:http";
|
|
3
|
+
import { Agent as HttpsAgent } from "node:https";
|
|
4
|
+
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
|
5
|
+
function s3Handler(target, route, limits) {
|
|
6
|
+
const endpoint = new URL(target.endpoint);
|
|
7
|
+
const mapped = route.host !== endpoint.hostname || route.port !== Number(endpoint.port || (endpoint.protocol === "https:" ? 443 : 80));
|
|
8
|
+
if (mapped && target.forcePathStyle === false) {
|
|
9
|
+
throw new Error("S3 mapped transports require path-style addressing to preserve bucket and TLS identity");
|
|
10
|
+
}
|
|
11
|
+
const pool = { keepAlive: true, maxSockets: limits.concurrency, maxTotalSockets: limits.concurrency };
|
|
12
|
+
const httpAgent = new HttpAgent(pool);
|
|
13
|
+
const httpsAgent = new HttpsAgent({
|
|
14
|
+
...pool,
|
|
15
|
+
ca: target.ca,
|
|
16
|
+
rejectUnauthorized: true,
|
|
17
|
+
...mapped ? { servername: endpoint.hostname } : {}
|
|
18
|
+
});
|
|
19
|
+
const transport = new NodeHttpHandler({
|
|
20
|
+
httpAgent,
|
|
21
|
+
httpsAgent,
|
|
22
|
+
connectionTimeout: limits.connectTimeoutMs,
|
|
23
|
+
requestTimeout: limits.requestTimeoutMs,
|
|
24
|
+
throwOnRequestTimeout: true
|
|
25
|
+
});
|
|
26
|
+
const handler = {
|
|
27
|
+
handle(request, options) {
|
|
28
|
+
if (!mapped)
|
|
29
|
+
return transport.handle(request, options);
|
|
30
|
+
const routed = request.clone();
|
|
31
|
+
routed.hostname = route.host;
|
|
32
|
+
routed.port = route.port;
|
|
33
|
+
return transport.handle(routed, options);
|
|
34
|
+
},
|
|
35
|
+
destroy() {
|
|
36
|
+
transport.destroy();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return { handler, destroy: () => {
|
|
40
|
+
handler.destroy();
|
|
41
|
+
httpAgent.destroy();
|
|
42
|
+
httpsAgent.destroy();
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { s3Handler };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConcurrencyPool
|
|
3
|
+
} from "./chunk-v3hxvnrc.js";
|
|
4
|
+
import {
|
|
5
|
+
s3Handler
|
|
6
|
+
} from "./chunk-x4ntb898.js";
|
|
7
|
+
|
|
8
|
+
// src/s3/client.ts
|
|
9
|
+
import {
|
|
10
|
+
S3Client as AwsS3Client,
|
|
11
|
+
HeadBucketCommand,
|
|
12
|
+
HeadObjectCommand,
|
|
13
|
+
ListBucketsCommand,
|
|
14
|
+
ListObjectsV2Command,
|
|
15
|
+
GetBucketVersioningCommand,
|
|
16
|
+
GetObjectCommand
|
|
17
|
+
} from "@aws-sdk/client-s3";
|
|
18
|
+
import { Readable } from "node:stream";
|
|
19
|
+
function positive(value, name, maximum = Number.MAX_SAFE_INTEGER) {
|
|
20
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
21
|
+
throw new Error(`${name} must be an integer between 1 and ${maximum}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function metadata(value) {
|
|
25
|
+
return {
|
|
26
|
+
size: value.ContentLength,
|
|
27
|
+
etag: value.ETag,
|
|
28
|
+
lastModified: value.LastModified,
|
|
29
|
+
contentType: value.ContentType,
|
|
30
|
+
versionId: value.VersionId,
|
|
31
|
+
metadata: value.Metadata ?? {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class S3Client {
|
|
36
|
+
source;
|
|
37
|
+
limits;
|
|
38
|
+
lifecycle;
|
|
39
|
+
#controller = new AbortController;
|
|
40
|
+
signal;
|
|
41
|
+
#pool;
|
|
42
|
+
#operations = new Set;
|
|
43
|
+
#initialization;
|
|
44
|
+
#disposal;
|
|
45
|
+
#sdk;
|
|
46
|
+
#http;
|
|
47
|
+
constructor(source, limits, lifecycle = {}) {
|
|
48
|
+
this.source = source;
|
|
49
|
+
this.limits = limits;
|
|
50
|
+
this.lifecycle = lifecycle;
|
|
51
|
+
positive(limits.concurrency, "concurrency");
|
|
52
|
+
positive(limits.connectTimeoutMs, "connectTimeoutMs", 2147483647);
|
|
53
|
+
positive(limits.requestTimeoutMs, "requestTimeoutMs", 2147483647);
|
|
54
|
+
this.#pool = new ConcurrencyPool(limits.concurrency);
|
|
55
|
+
this.signal = lifecycle.signal ? AbortSignal.any([lifecycle.signal, this.#controller.signal]) : this.#controller.signal;
|
|
56
|
+
}
|
|
57
|
+
initialize() {
|
|
58
|
+
this.signal.throwIfAborted();
|
|
59
|
+
return this.#initialization ??= this.#initialize();
|
|
60
|
+
}
|
|
61
|
+
async#initialize() {
|
|
62
|
+
const target = await this.source.resolve();
|
|
63
|
+
this.signal.throwIfAborted();
|
|
64
|
+
const url = new URL(target.endpoint);
|
|
65
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
66
|
+
throw new Error("S3 endpoint must be an HTTP(S) URL without embedded credentials, query or fragment");
|
|
67
|
+
}
|
|
68
|
+
const transport = this.source.transports[0];
|
|
69
|
+
if (!transport || transport.kind !== "tcp")
|
|
70
|
+
throw new Error("S3 requires a TCP transport");
|
|
71
|
+
const route = await transport.connect({
|
|
72
|
+
host: url.hostname,
|
|
73
|
+
port: Number(url.port || (url.protocol === "https:" ? 443 : 80)),
|
|
74
|
+
servername: url.hostname
|
|
75
|
+
});
|
|
76
|
+
this.signal.throwIfAborted();
|
|
77
|
+
this.#http = s3Handler(target, route, this.limits);
|
|
78
|
+
this.#sdk = new AwsS3Client({
|
|
79
|
+
endpoint: target.endpoint,
|
|
80
|
+
region: target.region,
|
|
81
|
+
credentials: target.credentials,
|
|
82
|
+
forcePathStyle: target.forcePathStyle ?? true,
|
|
83
|
+
requestHandler: this.#http.handler,
|
|
84
|
+
maxAttempts: 1,
|
|
85
|
+
followRegionRedirects: false,
|
|
86
|
+
responseChecksumValidation: "WHEN_REQUIRED"
|
|
87
|
+
});
|
|
88
|
+
this.lifecycle.onRoute?.({ transport: transport.name });
|
|
89
|
+
}
|
|
90
|
+
#run(options, operation) {
|
|
91
|
+
this.signal.throwIfAborted();
|
|
92
|
+
const sdk = this.#sdk;
|
|
93
|
+
if (!sdk)
|
|
94
|
+
throw new Error("S3 client is not initialized");
|
|
95
|
+
const timeout = new AbortController;
|
|
96
|
+
const timer = setTimeout(() => timeout.abort(new Error("S3 request deadline exceeded")), this.limits.requestTimeoutMs);
|
|
97
|
+
timer.unref();
|
|
98
|
+
const signal = AbortSignal.any([this.signal, timeout.signal, ...options.signal ? [options.signal] : []]);
|
|
99
|
+
const pending = this.#pool.run(() => operation(sdk, signal), signal).finally(() => clearTimeout(timer));
|
|
100
|
+
this.#operations.add(pending);
|
|
101
|
+
pending.then(() => this.#operations.delete(pending), () => this.#operations.delete(pending));
|
|
102
|
+
return pending;
|
|
103
|
+
}
|
|
104
|
+
headBucket(bucket, options = {}) {
|
|
105
|
+
return this.#run(options, async (sdk, signal) => {
|
|
106
|
+
await sdk.send(new HeadBucketCommand({ Bucket: bucket }), { abortSignal: signal });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
headObject(bucket, key, options = {}) {
|
|
110
|
+
return this.#run(options, async (sdk, signal) => metadata(await sdk.send(new HeadObjectCommand({ Bucket: bucket, Key: key, VersionId: options.versionId }), { abortSignal: signal })));
|
|
111
|
+
}
|
|
112
|
+
listObjects(bucket, options) {
|
|
113
|
+
positive(options.maxKeys, "maxKeys", 1000);
|
|
114
|
+
return this.#run(options, async (sdk, signal) => {
|
|
115
|
+
const page = await sdk.send(new ListObjectsV2Command({
|
|
116
|
+
Bucket: bucket,
|
|
117
|
+
MaxKeys: options.maxKeys,
|
|
118
|
+
Prefix: options.prefix,
|
|
119
|
+
Delimiter: options.delimiter,
|
|
120
|
+
ContinuationToken: options.continuationToken
|
|
121
|
+
}), { abortSignal: signal });
|
|
122
|
+
return {
|
|
123
|
+
objects: (page.Contents ?? []).filter((item) => item.Key !== undefined).map((item) => ({
|
|
124
|
+
key: item.Key,
|
|
125
|
+
size: item.Size,
|
|
126
|
+
etag: item.ETag,
|
|
127
|
+
lastModified: item.LastModified
|
|
128
|
+
})),
|
|
129
|
+
prefixes: (page.CommonPrefixes ?? []).flatMap((item) => item.Prefix === undefined ? [] : [item.Prefix]),
|
|
130
|
+
truncated: page.IsTruncated ?? false,
|
|
131
|
+
continuationToken: page.NextContinuationToken
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
listBuckets(options) {
|
|
136
|
+
positive(options.maxBuckets, "maxBuckets", 1e4);
|
|
137
|
+
return this.#run(options, async (sdk, signal) => {
|
|
138
|
+
const page = await sdk.send(new ListBucketsCommand({
|
|
139
|
+
MaxBuckets: options.maxBuckets,
|
|
140
|
+
ContinuationToken: options.continuationToken
|
|
141
|
+
}), { abortSignal: signal });
|
|
142
|
+
return { buckets: (page.Buckets ?? []).filter((item) => item.Name !== undefined).map((item) => ({ name: item.Name, creationDate: item.CreationDate })), continuationToken: page.ContinuationToken };
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
getBucketVersioning(bucket, options = {}) {
|
|
146
|
+
return this.#run(options, async (sdk, signal) => {
|
|
147
|
+
const result = await sdk.send(new GetBucketVersioningCommand({ Bucket: bucket }), { abortSignal: signal });
|
|
148
|
+
if (result.Status === "Enabled")
|
|
149
|
+
return "enabled";
|
|
150
|
+
return result.Status === "Suspended" ? "suspended" : "disabled";
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
readObject(bucket, key, options) {
|
|
154
|
+
positive(options.maxBytes, "maxBytes");
|
|
155
|
+
return this.#run(options, async (sdk, signal) => {
|
|
156
|
+
const result = await sdk.send(new GetObjectCommand({
|
|
157
|
+
Bucket: bucket,
|
|
158
|
+
Key: key,
|
|
159
|
+
VersionId: options.versionId
|
|
160
|
+
}), { abortSignal: signal });
|
|
161
|
+
const body = result.Body;
|
|
162
|
+
if (!(body instanceof Readable))
|
|
163
|
+
throw new Error("S3 response is missing a Node readable body");
|
|
164
|
+
const abort = () => body.destroy(new Error("S3 object read aborted", { cause: signal.reason }));
|
|
165
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
166
|
+
const chunks = [];
|
|
167
|
+
let size = 0;
|
|
168
|
+
let truncated = false;
|
|
169
|
+
try {
|
|
170
|
+
signal.throwIfAborted();
|
|
171
|
+
for await (const chunk of body) {
|
|
172
|
+
signal.throwIfAborted();
|
|
173
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
174
|
+
const remaining = options.maxBytes - size;
|
|
175
|
+
chunks.push(Buffer.from(bytes.subarray(0, remaining)));
|
|
176
|
+
size += Math.min(remaining, bytes.length);
|
|
177
|
+
if (bytes.length > remaining || size === options.maxBytes && result.ContentLength !== undefined && result.ContentLength > size) {
|
|
178
|
+
truncated = true;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { ...metadata(result), bytes: Buffer.concat(chunks, size), truncated };
|
|
183
|
+
} finally {
|
|
184
|
+
signal.removeEventListener("abort", abort);
|
|
185
|
+
body.destroy();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
dispose() {
|
|
190
|
+
return this.#disposal ??= (async () => {
|
|
191
|
+
this.#controller.abort(new Error("S3 client disposed"));
|
|
192
|
+
await this.#initialization?.catch(() => {});
|
|
193
|
+
await Promise.allSettled(this.#operations);
|
|
194
|
+
this.#sdk?.destroy();
|
|
195
|
+
this.#http?.destroy();
|
|
196
|
+
})();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { S3Client };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ServicePortForwarder
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import"../chunk-hv2kx47y.js";
|
|
3
|
+
} from "../chunk-p097seg7.js";
|
|
5
4
|
import"../chunk-j46x7j7c.js";
|
|
6
5
|
import"../chunk-6fnhzhdb.js";
|
|
6
|
+
import"../chunk-hv2kx47y.js";
|
|
7
7
|
import {
|
|
8
8
|
KubectlExecutor
|
|
9
9
|
} from "../chunk-0rs1aacd.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ServicePortForwarder
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import"../chunk-hv2kx47y.js";
|
|
3
|
+
} from "../chunk-p097seg7.js";
|
|
5
4
|
import"../chunk-j46x7j7c.js";
|
|
6
5
|
import"../chunk-6fnhzhdb.js";
|
|
6
|
+
import"../chunk-hv2kx47y.js";
|
|
7
7
|
import"../chunk-0rs1aacd.js";
|
|
8
8
|
import"../chunk-ztenxgjv.js";
|
|
9
9
|
export {
|
package/dist/mysql/client.js
CHANGED
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
MysqlClient,
|
|
3
3
|
MysqlDatabase,
|
|
4
4
|
openMysql
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import"../chunk-v3hxvnrc.js";
|
|
5
|
+
} from "../chunk-sry9e02n.js";
|
|
7
6
|
import"../chunk-s06hs7a8.js";
|
|
7
|
+
import"../chunk-v3hxvnrc.js";
|
|
8
8
|
import"../chunk-xy4g56g4.js";
|
|
9
9
|
export {
|
|
10
10
|
openMysql,
|
package/dist/mysql/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MysqlClient,
|
|
3
|
-
MysqlDatabase,
|
|
4
|
-
openMysql
|
|
5
|
-
} from "../chunk-mkhyty75.js";
|
|
6
|
-
import"../chunk-v3hxvnrc.js";
|
|
7
1
|
import {
|
|
8
2
|
parseMysqlEnvTarget
|
|
9
3
|
} from "../chunk-qq77x9ek.js";
|
|
10
4
|
import"../chunk-pfar9cm3.js";
|
|
5
|
+
import {
|
|
6
|
+
MysqlClient,
|
|
7
|
+
MysqlDatabase,
|
|
8
|
+
openMysql
|
|
9
|
+
} from "../chunk-sry9e02n.js";
|
|
11
10
|
import"../chunk-s06hs7a8.js";
|
|
11
|
+
import"../chunk-v3hxvnrc.js";
|
|
12
12
|
import"../chunk-xy4g56g4.js";
|
|
13
13
|
export {
|
|
14
14
|
parseMysqlEnvTarget,
|
package/dist/redis/index.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collectRedisKeyStats,
|
|
3
|
-
redisGroupedSampleSizes
|
|
4
|
-
} from "../chunk-jr9y0ghr.js";
|
|
5
|
-
import {
|
|
6
|
-
discoverRedisTopology
|
|
7
|
-
} from "../chunk-c6z919qb.js";
|
|
8
1
|
import {
|
|
9
2
|
RedisAccess,
|
|
10
3
|
RedisClient,
|
|
@@ -12,6 +5,13 @@ import {
|
|
|
12
5
|
openRedis,
|
|
13
6
|
parseRedisInfo
|
|
14
7
|
} from "../chunk-ajhdd4hp.js";
|
|
8
|
+
import {
|
|
9
|
+
collectRedisKeyStats,
|
|
10
|
+
redisGroupedSampleSizes
|
|
11
|
+
} from "../chunk-jr9y0ghr.js";
|
|
12
|
+
import {
|
|
13
|
+
discoverRedisTopology
|
|
14
|
+
} from "../chunk-c6z919qb.js";
|
|
15
15
|
export {
|
|
16
16
|
redisGroupedSampleSizes,
|
|
17
17
|
parseRedisInfo,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Client } from "../client";
|
|
2
|
+
import type { ClientLifecycle, ConnectionSource } from "../datasource";
|
|
3
|
+
import type { S3Target, S3Limits, S3RequestOptions, S3ObjectMetadata, S3ObjectPage, S3BucketPage, S3ObjectRead } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* @spec Read-only S3 operations share a bounded pool; no failed protocol operation is replayed.
|
|
6
|
+
* @spec HTTP errors retain SDK status/code, including ambiguous HEAD 403/404 responses.
|
|
7
|
+
* @why Initialization prepares a route without requiring account-wide bucket-list permission.
|
|
8
|
+
*/
|
|
9
|
+
export declare class S3Client implements Client {
|
|
10
|
+
#private;
|
|
11
|
+
private readonly source;
|
|
12
|
+
private readonly limits;
|
|
13
|
+
private readonly lifecycle;
|
|
14
|
+
readonly signal: AbortSignal;
|
|
15
|
+
constructor(source: ConnectionSource<S3Target>, limits: S3Limits, lifecycle?: ClientLifecycle);
|
|
16
|
+
initialize(): Promise<void>;
|
|
17
|
+
/** A failed HEAD remains an error, not a misleading exists=false. */
|
|
18
|
+
headBucket(bucket: string, options?: S3RequestOptions): Promise<void>;
|
|
19
|
+
headObject(bucket: string, key: string, options?: S3RequestOptions & {
|
|
20
|
+
versionId?: string;
|
|
21
|
+
}): Promise<S3ObjectMetadata>;
|
|
22
|
+
/** One bounded page; callers own traversal and total scan budgets. */
|
|
23
|
+
listObjects(bucket: string, options: S3RequestOptions & {
|
|
24
|
+
maxKeys: number;
|
|
25
|
+
prefix?: string;
|
|
26
|
+
delimiter?: string;
|
|
27
|
+
continuationToken?: string;
|
|
28
|
+
}): Promise<S3ObjectPage>;
|
|
29
|
+
listBuckets(options: S3RequestOptions & {
|
|
30
|
+
maxBuckets: number;
|
|
31
|
+
continuationToken?: string;
|
|
32
|
+
}): Promise<S3BucketPage>;
|
|
33
|
+
getBucketVersioning(bucket: string, options?: S3RequestOptions): Promise<"enabled" | "suspended" | "disabled">;
|
|
34
|
+
/** Read a bounded prefix. Own and close the response stream before returning the pool slot. */
|
|
35
|
+
readObject(bucket: string, key: string, options: S3RequestOptions & {
|
|
36
|
+
maxBytes: number;
|
|
37
|
+
versionId?: string;
|
|
38
|
+
}): Promise<S3ObjectRead>;
|
|
39
|
+
dispose(): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type DataSource } from "../datasource";
|
|
2
|
+
import { type TcpTransport } from "../transport";
|
|
3
|
+
import { S3Client } from "./client";
|
|
4
|
+
import type { S3Limits, S3Target } from "./types";
|
|
5
|
+
/** Connection details are resolved by the caller; route identity includes the cluster/context, not just a transport name. */
|
|
6
|
+
export declare class S3DataSource implements DataSource<S3Client> {
|
|
7
|
+
#private;
|
|
8
|
+
readonly key: string;
|
|
9
|
+
constructor(target: S3Target, limits: S3Limits, route?: {
|
|
10
|
+
key: string;
|
|
11
|
+
transport: TcpTransport;
|
|
12
|
+
});
|
|
13
|
+
createClient(signal: AbortSignal): S3Client;
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
S3DataSource
|
|
3
|
+
} from "../chunk-9j36zvn4.js";
|
|
4
|
+
import"../chunk-hqx7j9xe.js";
|
|
5
|
+
import"../chunk-3dt25p98.js";
|
|
6
|
+
import"../chunk-zp1s833x.js";
|
|
7
|
+
import"../chunk-v3hxvnrc.js";
|
|
8
|
+
import"../chunk-x4ntb898.js";
|
|
9
|
+
import"../chunk-xy4g56g4.js";
|
|
10
|
+
export {
|
|
11
|
+
S3DataSource
|
|
12
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
|
2
|
+
import type { Endpoint } from "../transport";
|
|
3
|
+
import type { S3Limits, S3Target } from "./types";
|
|
4
|
+
/** Routing changes only the socket destination, never the signed HTTP Host or TLS identity. */
|
|
5
|
+
export declare function s3Handler(target: S3Target, route: Endpoint, limits: S3Limits): {
|
|
6
|
+
handler: {
|
|
7
|
+
handle(request: Parameters<NodeHttpHandler["handle"]>[0], options: Parameters<NodeHttpHandler["handle"]>[1]): Promise<{
|
|
8
|
+
response: import("@smithy/core/transport").HttpResponse;
|
|
9
|
+
}>;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
};
|
|
12
|
+
destroy: () => void;
|
|
13
|
+
};
|
package/dist/s3/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
S3DataSource
|
|
3
|
+
} from "../chunk-9j36zvn4.js";
|
|
4
|
+
import"../chunk-hqx7j9xe.js";
|
|
5
|
+
import"../chunk-3dt25p98.js";
|
|
6
|
+
import {
|
|
7
|
+
S3Client
|
|
8
|
+
} from "../chunk-zp1s833x.js";
|
|
9
|
+
import"../chunk-v3hxvnrc.js";
|
|
10
|
+
import"../chunk-x4ntb898.js";
|
|
11
|
+
import"../chunk-xy4g56g4.js";
|
|
12
|
+
export {
|
|
13
|
+
S3DataSource,
|
|
14
|
+
S3Client
|
|
15
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface S3Target {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
region: string;
|
|
4
|
+
credentials: {
|
|
5
|
+
accessKeyId: string;
|
|
6
|
+
secretAccessKey: string;
|
|
7
|
+
sessionToken?: string;
|
|
8
|
+
};
|
|
9
|
+
forcePathStyle?: boolean;
|
|
10
|
+
/** Additional trust is explicit; TLS verification is never disabled for a tunnel. */
|
|
11
|
+
ca?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Root-owned policy, not product defaults. Request time includes queueing and body consumption. */
|
|
14
|
+
export interface S3Limits {
|
|
15
|
+
concurrency: number;
|
|
16
|
+
connectTimeoutMs: number;
|
|
17
|
+
requestTimeoutMs: number;
|
|
18
|
+
}
|
|
19
|
+
export interface S3RequestOptions {
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
}
|
|
22
|
+
export interface S3ObjectMetadata {
|
|
23
|
+
size?: number;
|
|
24
|
+
etag?: string;
|
|
25
|
+
lastModified?: Date;
|
|
26
|
+
contentType?: string;
|
|
27
|
+
versionId?: string;
|
|
28
|
+
metadata: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
export interface S3ObjectPage {
|
|
31
|
+
objects: Array<{
|
|
32
|
+
key: string;
|
|
33
|
+
size?: number;
|
|
34
|
+
etag?: string;
|
|
35
|
+
lastModified?: Date;
|
|
36
|
+
}>;
|
|
37
|
+
prefixes: string[];
|
|
38
|
+
truncated: boolean;
|
|
39
|
+
continuationToken?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface S3BucketPage {
|
|
42
|
+
buckets: Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
creationDate?: Date;
|
|
45
|
+
}>;
|
|
46
|
+
continuationToken?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface S3ObjectRead extends S3ObjectMetadata {
|
|
49
|
+
bytes: Uint8Array;
|
|
50
|
+
truncated: boolean;
|
|
51
|
+
}
|
package/dist/s3/types.js
ADDED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compforge/harness-toolbox",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Shared infrastructure clients, transports and bounded data capture",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -57,6 +57,14 @@
|
|
|
57
57
|
"types": "./dist/opensearch/*.d.ts",
|
|
58
58
|
"import": "./dist/opensearch/*.js"
|
|
59
59
|
},
|
|
60
|
+
"./s3": {
|
|
61
|
+
"types": "./dist/s3/index.d.ts",
|
|
62
|
+
"import": "./dist/s3/index.js"
|
|
63
|
+
},
|
|
64
|
+
"./s3/*": {
|
|
65
|
+
"types": "./dist/s3/*.d.ts",
|
|
66
|
+
"import": "./dist/s3/*.js"
|
|
67
|
+
},
|
|
60
68
|
"./client": {
|
|
61
69
|
"types": "./dist/client.d.ts",
|
|
62
70
|
"import": "./dist/client.js"
|
|
@@ -75,10 +83,12 @@
|
|
|
75
83
|
"README.md"
|
|
76
84
|
],
|
|
77
85
|
"dependencies": {
|
|
78
|
-
"
|
|
79
|
-
"@redis/client": "6.1.0",
|
|
80
|
-
"@opensearch-project/opensearch": "3.6.0",
|
|
86
|
+
"@aws-sdk/client-s3": "3.1133.0",
|
|
81
87
|
"@kubernetes/client-node": "1.4.0",
|
|
88
|
+
"@opensearch-project/opensearch": "3.6.0",
|
|
89
|
+
"@redis/client": "6.1.0",
|
|
90
|
+
"@smithy/node-http-handler": "4.12.1",
|
|
91
|
+
"mysql2": "3.23.1",
|
|
82
92
|
"node-fetch": "2.7.0"
|
|
83
93
|
},
|
|
84
94
|
"devDependencies": {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PortForwardScope
|
|
3
|
-
} from "./chunk-hv2kx47y.js";
|
|
4
1
|
import {
|
|
5
2
|
findPodsForService,
|
|
6
3
|
findService,
|
|
@@ -9,6 +6,9 @@ import {
|
|
|
9
6
|
import {
|
|
10
7
|
findPod
|
|
11
8
|
} from "./chunk-6fnhzhdb.js";
|
|
9
|
+
import {
|
|
10
|
+
PortForwardScope
|
|
11
|
+
} from "./chunk-hv2kx47y.js";
|
|
12
12
|
|
|
13
13
|
// src/kubernetes/service-port-forward.ts
|
|
14
14
|
class ServicePortForwarder {
|