@compforge/harness-toolbox 0.3.3 → 0.3.5
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 +47 -0
- package/dist/chunk-1gyshqd9.js +20 -0
- package/dist/chunk-4sqvppz7.js +51 -0
- package/dist/chunk-99p0zt4f.js +37 -0
- package/dist/chunk-pjcrhw3n.js +22 -0
- package/dist/data-loader.d.ts +21 -0
- package/dist/data-loader.js +91 -0
- package/dist/duration.d.ts +2 -0
- package/dist/duration.js +6 -0
- package/dist/kubernetes/client-node-pod-log.js +6 -22
- package/dist/kubernetes/client.js +2 -2
- package/dist/kubernetes/environment.js +2 -2
- package/dist/kubernetes/pod-log-client.js +2 -2
- package/dist/kubernetes/pod-log-datasource.js +2 -2
- package/dist/mysql/client.js +2 -2
- package/dist/mysql/index.d.ts +2 -0
- package/dist/mysql/index.js +18 -6
- package/dist/mysql/limited.d.ts +13 -0
- package/dist/mysql/limited.js +10 -0
- package/dist/mysql/scoped.d.ts +7 -0
- package/dist/mysql/scoped.js +6 -0
- package/dist/mysql/types.d.ts +2 -0
- package/dist/opensearch/index.d.ts +1 -0
- package/dist/opensearch/index.js +8 -4
- package/dist/opensearch/search-after.d.ts +12 -0
- package/dist/opensearch/search-after.js +6 -0
- package/package.json +9 -1
- package/dist/{chunk-jx740hjh.js → chunk-56t4qyp7.js} +4 -4
- package/dist/{chunk-td11zjfb.js → chunk-bg1syayb.js} +3 -3
- package/dist/{chunk-010339ga.js → chunk-mnad4zkp.js} +3 -3
package/README.md
CHANGED
|
@@ -9,6 +9,10 @@ clients to access Kubernetes, MySQL, Redis, OpenSearch and S3-compatible object
|
|
|
9
9
|
command, plugin protocol, test model or verdict.
|
|
10
10
|
|
|
11
11
|
The package supports Node.js 22+ and Bun. Install with `npm install @compforge/harness-toolbox`.
|
|
12
|
+
Use `@compforge/harness-toolbox/duration` to parse a positive duration such as `30m` or `2d` into milliseconds.
|
|
13
|
+
`searchAfterPages` from `@compforge/harness-toolbox/opensearch/search-after` yields raw OpenSearch
|
|
14
|
+
hit pages with a caller supplied query and stable sort. It streams pages without owning the client or
|
|
15
|
+
claiming snapshot consistency; callers supply cancellation and decide how to persist or verify results.
|
|
12
16
|
Protocol adapters have separate entry points; importing lifecycle or transport contracts does
|
|
13
17
|
not load database or Kubernetes drivers.
|
|
14
18
|
|
|
@@ -157,6 +161,49 @@ fall back to Pod Python. Completion, truncation, timeout, and cancellation destr
|
|
|
157
161
|
session without replay or changes to ordinary borrowed sessions. Disconnect does not prove immediate
|
|
158
162
|
server cancellation. The byte budget limits retained JSON rows, not individual wire packets or fields.
|
|
159
163
|
|
|
164
|
+
## Reusable bounded queries and read caches
|
|
165
|
+
|
|
166
|
+
For trusted, caller-owned SELECT statements, `queryLimited(database, target, statement, maxRows)`
|
|
167
|
+
appends an integer `LIMIT maxRows + 1` and returns `{ rows, truncated }`. SQL must not already have
|
|
168
|
+
LIMIT or a trailing terminator; the helper does not parse or rewrite arbitrary SQL. Business values
|
|
169
|
+
stay bound parameters. `limitedStatement` and `limitedRows` expose the same preparation/projection
|
|
170
|
+
for `Database.queryBatch`. `maxRows` must be a positive integer below 2147483647. These helpers
|
|
171
|
+
bound row count only: use `queryReadonly` when a disposable readonly session and byte/time budgets
|
|
172
|
+
are required.
|
|
173
|
+
|
|
174
|
+
`DataLoader` shares results, failures and in-flight work within one explicit read scope, similar to
|
|
175
|
+
Guava's LoadingCache but scoped to a single collection round. Consumers must not mutate shared results.
|
|
176
|
+
Use `withReadScope` to bind caller-approved database reads; do not use it for writes or polling.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { DataLoader } from '@compforge/harness-toolbox/data-loader';
|
|
180
|
+
import { withReadScope, queryLimited } from '@compforge/harness-toolbox/mysql';
|
|
181
|
+
|
|
182
|
+
const scope = new DataLoader({
|
|
183
|
+
maxEntries: 256, maxBytes: 4 * 1024 * 1024, signal: client.signal,
|
|
184
|
+
});
|
|
185
|
+
const reads = withReadScope(client.database, scope);
|
|
186
|
+
try {
|
|
187
|
+
const page = await queryLimited(reads, client.target, {
|
|
188
|
+
sql: 'SELECT id FROM items WHERE owner = ? ORDER BY id', values: [owner],
|
|
189
|
+
}, 100);
|
|
190
|
+
} finally {
|
|
191
|
+
await scope.close(); // Root still owns client.dispose().
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Keys include full database identity, SQL and typed parameters as an opaque digest. Nonserializable
|
|
196
|
+
parameters bypass caching. Batch reads reuse cached entries and send misses as one ordered native
|
|
197
|
+
batch, or sequential queries when batching is unavailable. Failures are shared without replay;
|
|
198
|
+
use a new scope for fresh observations. This is not a consistent database snapshot.
|
|
199
|
+
|
|
200
|
+
Entry capacity includes failures and pending reads; new keys bypass caching when full. Serialized
|
|
201
|
+
result bytes bound retained results, excluding keys/errors and active operation memory. Oversized
|
|
202
|
+
or nonserializable results are returned but not retained. Scope close signals cancellation and joins
|
|
203
|
+
operations, including uncached reads. Operations must honor the signal for prompt cancellation;
|
|
204
|
+
MySQL reads retain their underlying client's timeout/cancellation policy. Adapter close owns neither
|
|
205
|
+
the scope nor the database. A waiter's optional signal to `scope.read` cancels only that waiter.
|
|
206
|
+
|
|
160
207
|
## Shared Pod relay transport
|
|
161
208
|
|
|
162
209
|
`PodRelayTransport` opens TCP routes through an existing Running/Ready Pod. It discovers an
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/mysql/limited.ts
|
|
2
|
+
function validateLimit(maxRows) {
|
|
3
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows >= 2147483647) {
|
|
4
|
+
throw new Error("maxRows must be a positive integer < 2147483647");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
function limitedStatement(statement, maxRows) {
|
|
8
|
+
validateLimit(maxRows);
|
|
9
|
+
return { sql: `${statement.sql} LIMIT ${maxRows + 1}`, values: statement.values };
|
|
10
|
+
}
|
|
11
|
+
function limitedRows(rows, maxRows) {
|
|
12
|
+
validateLimit(maxRows);
|
|
13
|
+
return { rows: rows.slice(0, maxRows), truncated: rows.length > maxRows };
|
|
14
|
+
}
|
|
15
|
+
async function queryLimited(database, target, statement, maxRows) {
|
|
16
|
+
const bounded = limitedStatement(statement, maxRows);
|
|
17
|
+
return limitedRows(await database.query(target, bounded.sql, bounded.values), maxRows);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { limitedStatement, limitedRows, queryLimited };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/mysql/scoped.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { serialize } from "node:v8";
|
|
4
|
+
function queryKey(kind, target, statement) {
|
|
5
|
+
try {
|
|
6
|
+
return createHash("sha256").update(serialize([
|
|
7
|
+
kind,
|
|
8
|
+
target.host,
|
|
9
|
+
target.port,
|
|
10
|
+
target.database,
|
|
11
|
+
target.user,
|
|
12
|
+
target.password,
|
|
13
|
+
statement.sql,
|
|
14
|
+
statement.values
|
|
15
|
+
])).digest("hex");
|
|
16
|
+
} catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function withReadScope(database, scope) {
|
|
21
|
+
const query = (target, sql, values) => scope.read(queryKey("query", target, { sql, values }), () => database.query(target, sql, values));
|
|
22
|
+
return {
|
|
23
|
+
query,
|
|
24
|
+
queryOne: (target, sql, values) => scope.read(queryKey("one", target, { sql, values }), () => database.queryOne(target, sql, values)),
|
|
25
|
+
queryBatch: async (target, statements) => {
|
|
26
|
+
const misses = [];
|
|
27
|
+
let batch;
|
|
28
|
+
if (!statements.length)
|
|
29
|
+
return scope.read(undefined, async () => []);
|
|
30
|
+
return Promise.all(statements.map((statement) => scope.read(queryKey("query", target, statement), (signal) => {
|
|
31
|
+
const position = misses.length;
|
|
32
|
+
misses.push(statement);
|
|
33
|
+
batch ??= Promise.resolve().then(async () => {
|
|
34
|
+
signal.throwIfAborted();
|
|
35
|
+
if (database.queryBatch)
|
|
36
|
+
return database.queryBatch(target, misses);
|
|
37
|
+
const rows = [];
|
|
38
|
+
for (const item of misses) {
|
|
39
|
+
signal.throwIfAborted();
|
|
40
|
+
rows.push(await database.query(target, item.sql, item.values));
|
|
41
|
+
}
|
|
42
|
+
return rows;
|
|
43
|
+
});
|
|
44
|
+
return batch.then((rows) => rows[position]);
|
|
45
|
+
})));
|
|
46
|
+
},
|
|
47
|
+
close: async () => {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { withReadScope };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/opensearch/search-after.ts
|
|
2
|
+
async function* searchAfterPages(search, index, options) {
|
|
3
|
+
if (!Number.isSafeInteger(options.pageSize) || options.pageSize <= 0 || !options.sort.length) {
|
|
4
|
+
throw new Error("search_after requires a positive pageSize and a non-empty sort");
|
|
5
|
+
}
|
|
6
|
+
let cursor;
|
|
7
|
+
for (;; ) {
|
|
8
|
+
options.signal?.throwIfAborted();
|
|
9
|
+
const body = {
|
|
10
|
+
query: options.query,
|
|
11
|
+
sort: options.sort,
|
|
12
|
+
size: options.pageSize,
|
|
13
|
+
track_total_hits: false,
|
|
14
|
+
...cursor ? { search_after: cursor } : {}
|
|
15
|
+
};
|
|
16
|
+
const result = await search.search(index, body);
|
|
17
|
+
if (result.timed_out === true || Number(result._shards?.failed ?? 0) > 0) {
|
|
18
|
+
throw new Error(`OpenSearch search_after page failed for index '${index}'`);
|
|
19
|
+
}
|
|
20
|
+
const hits = result.hits?.hits;
|
|
21
|
+
if (!Array.isArray(hits))
|
|
22
|
+
throw new Error(`OpenSearch search_after response has no hits for index '${index}'`);
|
|
23
|
+
if (!hits.length)
|
|
24
|
+
return;
|
|
25
|
+
const page = hits;
|
|
26
|
+
const next = page.at(-1)?.sort;
|
|
27
|
+
if (!Array.isArray(next) || !next.length || cursor && JSON.stringify(next) === JSON.stringify(cursor)) {
|
|
28
|
+
throw new Error(`OpenSearch search_after cursor did not advance for index '${index}'`);
|
|
29
|
+
}
|
|
30
|
+
yield page;
|
|
31
|
+
cursor = next;
|
|
32
|
+
if (page.length < options.pageSize)
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { searchAfterPages };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/duration.ts
|
|
2
|
+
function parseDuration(value) {
|
|
3
|
+
const units = { ns: 0.000001, us: 0.001, "µs": 0.001, ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
4
|
+
const input = value.trim();
|
|
5
|
+
let total = 0;
|
|
6
|
+
let cursor = 0;
|
|
7
|
+
const segment = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h|d)/gy;
|
|
8
|
+
while (cursor < input.length) {
|
|
9
|
+
segment.lastIndex = cursor;
|
|
10
|
+
const match = segment.exec(input);
|
|
11
|
+
if (!match)
|
|
12
|
+
throw new Error(`Invalid duration: '${value}'`);
|
|
13
|
+
total += Number(match[1]) * units[match[2]];
|
|
14
|
+
cursor = segment.lastIndex;
|
|
15
|
+
}
|
|
16
|
+
if (!Number.isFinite(total) || total <= 0 || total > Number.MAX_SAFE_INTEGER) {
|
|
17
|
+
throw new Error(`Invalid duration: '${value}'`);
|
|
18
|
+
}
|
|
19
|
+
return total;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { parseDuration };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface DataLoaderOptions {
|
|
2
|
+
maxEntries: number;
|
|
3
|
+
/** Serialized result bytes retained, excluding keys/errors and active operation memory. */
|
|
4
|
+
maxBytes: number;
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
/** A scoped async loading cache, similar to Guava's LoadingCache.
|
|
8
|
+
* Shares results, failures and in-flight reads until close; consumers must not mutate results.
|
|
9
|
+
* Unlike a long-lived LoadingCache, this scope has no refresh/expiry or automatic batching.
|
|
10
|
+
* Keys and loading operations belong to adapters; the same key must have the same result type.
|
|
11
|
+
* Operations must honor their signal for prompt cancellation. No underlying client is owned.
|
|
12
|
+
*/
|
|
13
|
+
export declare class DataLoader {
|
|
14
|
+
#private;
|
|
15
|
+
private readonly options;
|
|
16
|
+
constructor(options: DataLoaderOptions);
|
|
17
|
+
/** Undefined keys bypass caching. A waiter's signal cancels only that wait, not shared work. */
|
|
18
|
+
read<T>(key: unknown, operation: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
19
|
+
/** Abort cooperative operations and join all reads, including capacity-bypass reads. */
|
|
20
|
+
close(reason?: unknown): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/data-loader.ts
|
|
2
|
+
import { serialize } from "node:v8";
|
|
3
|
+
|
|
4
|
+
class DataLoader {
|
|
5
|
+
options;
|
|
6
|
+
#entries = new Map;
|
|
7
|
+
#pending = new Set;
|
|
8
|
+
#controller = new AbortController;
|
|
9
|
+
#bytes = 0;
|
|
10
|
+
#closing;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
for (const name of ["maxEntries", "maxBytes"]) {
|
|
14
|
+
if (!Number.isSafeInteger(options[name]) || options[name] < 1)
|
|
15
|
+
throw new Error(`${name} must be a positive safe integer`);
|
|
16
|
+
}
|
|
17
|
+
options.signal.throwIfAborted();
|
|
18
|
+
options.signal.addEventListener("abort", this.#abort, { once: true });
|
|
19
|
+
}
|
|
20
|
+
#abort = () => {
|
|
21
|
+
this.close(this.options.signal.reason);
|
|
22
|
+
};
|
|
23
|
+
async read(key, operation, signal) {
|
|
24
|
+
this.#controller.signal.throwIfAborted();
|
|
25
|
+
signal?.throwIfAborted();
|
|
26
|
+
let pending = key === undefined ? undefined : this.#entries.get(key);
|
|
27
|
+
if (!pending) {
|
|
28
|
+
const retain = key !== undefined && this.#entries.size < this.options.maxEntries;
|
|
29
|
+
pending = Promise.resolve().then(async () => {
|
|
30
|
+
this.#controller.signal.throwIfAborted();
|
|
31
|
+
const value = await operation(this.#controller.signal);
|
|
32
|
+
this.#controller.signal.throwIfAborted();
|
|
33
|
+
if (retain) {
|
|
34
|
+
let bytes;
|
|
35
|
+
try {
|
|
36
|
+
bytes = serialize(value).byteLength;
|
|
37
|
+
} catch {
|
|
38
|
+
bytes = Infinity;
|
|
39
|
+
}
|
|
40
|
+
if (this.#bytes + bytes <= this.options.maxBytes)
|
|
41
|
+
this.#bytes += bytes;
|
|
42
|
+
else
|
|
43
|
+
this.#entries.delete(key);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
});
|
|
47
|
+
if (retain)
|
|
48
|
+
this.#entries.set(key, pending);
|
|
49
|
+
this.#pending.add(pending);
|
|
50
|
+
const done = () => {
|
|
51
|
+
this.#pending.delete(pending);
|
|
52
|
+
};
|
|
53
|
+
pending.then(done, done);
|
|
54
|
+
}
|
|
55
|
+
if (!signal)
|
|
56
|
+
return await pending;
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const abort = () => {
|
|
59
|
+
cleanup();
|
|
60
|
+
reject(signal.reason);
|
|
61
|
+
};
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
signal.removeEventListener("abort", abort);
|
|
64
|
+
};
|
|
65
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
66
|
+
pending.then((value) => {
|
|
67
|
+
cleanup();
|
|
68
|
+
resolve(value);
|
|
69
|
+
}, (error) => {
|
|
70
|
+
cleanup();
|
|
71
|
+
reject(error);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
close(reason = new Error("DataLoader scope is closed")) {
|
|
76
|
+
if (!this.#closing) {
|
|
77
|
+
this.#closing = Promise.resolve().then(async () => {
|
|
78
|
+
await Promise.allSettled([...this.#pending]);
|
|
79
|
+
this.#entries.clear();
|
|
80
|
+
this.#bytes = 0;
|
|
81
|
+
});
|
|
82
|
+
this.options.signal.removeEventListener("abort", this.#abort);
|
|
83
|
+
this.#controller.abort(reason);
|
|
84
|
+
this.#entries.clear();
|
|
85
|
+
}
|
|
86
|
+
return this.#closing;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
DataLoader
|
|
91
|
+
};
|
package/dist/duration.js
ADDED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
logTimestampNanos
|
|
3
3
|
} from "../chunk-mp88s7r7.js";
|
|
4
|
+
import {
|
|
5
|
+
parseDuration
|
|
6
|
+
} from "../chunk-pjcrhw3n.js";
|
|
4
7
|
|
|
5
8
|
// src/kubernetes/client-node-pod-log.ts
|
|
6
9
|
import { closeSync, openSync, writeSync } from "node:fs";
|
|
@@ -14,30 +17,11 @@ var DEFAULT_CLIENT_NODE_LOG_POLICY = {
|
|
|
14
17
|
var RFC3339_LINE = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))(?:\s|$)/;
|
|
15
18
|
var RECENT_LINE_LIMIT = 256;
|
|
16
19
|
function parseSinceSeconds(value) {
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
try {
|
|
21
|
+
return Math.max(1, Math.ceil(parseDuration(value) / 1000));
|
|
22
|
+
} catch {
|
|
19
23
|
return;
|
|
20
|
-
const units = {
|
|
21
|
-
ns: 0.000000001,
|
|
22
|
-
us: 0.000001,
|
|
23
|
-
"µs": 0.000001,
|
|
24
|
-
ms: 0.001,
|
|
25
|
-
s: 1,
|
|
26
|
-
m: 60,
|
|
27
|
-
h: 3600
|
|
28
|
-
};
|
|
29
|
-
let seconds = 0;
|
|
30
|
-
let cursor = 0;
|
|
31
|
-
const segment = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/gy;
|
|
32
|
-
while (cursor < input.length) {
|
|
33
|
-
segment.lastIndex = cursor;
|
|
34
|
-
const match = segment.exec(input);
|
|
35
|
-
if (!match)
|
|
36
|
-
return;
|
|
37
|
-
seconds += Number(match[1]) * units[match[2]];
|
|
38
|
-
cursor = segment.lastIndex;
|
|
39
24
|
}
|
|
40
|
-
return Math.max(1, Math.ceil(seconds));
|
|
41
25
|
}
|
|
42
26
|
function errorMessage(error) {
|
|
43
27
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
KubernetesClient
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import"../chunk-y3dx8jh6.js";
|
|
3
|
+
} from "../chunk-bg1syayb.js";
|
|
5
4
|
import"../chunk-qhpqse1y.js";
|
|
6
5
|
import"../chunk-j46x7j7c.js";
|
|
7
6
|
import"../chunk-6fnhzhdb.js";
|
|
8
7
|
import"../chunk-2m0mh09h.js";
|
|
8
|
+
import"../chunk-y3dx8jh6.js";
|
|
9
9
|
import"../chunk-ntfm5e85.js";
|
|
10
10
|
import"../chunk-v3hxvnrc.js";
|
|
11
11
|
import"../chunk-xh3zwhjh.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
KubernetesClient
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import"../chunk-y3dx8jh6.js";
|
|
3
|
+
} from "../chunk-bg1syayb.js";
|
|
5
4
|
import"../chunk-qhpqse1y.js";
|
|
6
5
|
import"../chunk-j46x7j7c.js";
|
|
7
6
|
import"../chunk-6fnhzhdb.js";
|
|
8
7
|
import"../chunk-2m0mh09h.js";
|
|
8
|
+
import"../chunk-y3dx8jh6.js";
|
|
9
9
|
import {
|
|
10
10
|
clientKey
|
|
11
11
|
} from "../chunk-a9htb6cr.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PodLogClient
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-mnad4zkp.js";
|
|
4
4
|
import"../chunk-8nm9f0hh.js";
|
|
5
|
-
import"../chunk-1vfwvgkj.js";
|
|
6
5
|
import"../chunk-mp88s7r7.js";
|
|
6
|
+
import"../chunk-1vfwvgkj.js";
|
|
7
7
|
import"../chunk-a9htb6cr.js";
|
|
8
8
|
import"../chunk-v3hxvnrc.js";
|
|
9
9
|
export {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PodLogClient
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-mnad4zkp.js";
|
|
4
4
|
import"../chunk-8nm9f0hh.js";
|
|
5
|
-
import"../chunk-1vfwvgkj.js";
|
|
6
5
|
import"../chunk-mp88s7r7.js";
|
|
6
|
+
import"../chunk-1vfwvgkj.js";
|
|
7
7
|
import"../chunk-a9htb6cr.js";
|
|
8
8
|
import {
|
|
9
9
|
clientKey
|
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-kbewz29a.js";
|
|
5
|
+
} from "../chunk-56t4qyp7.js";
|
|
7
6
|
import"../chunk-m91t44jh.js";
|
|
7
|
+
import"../chunk-kbewz29a.js";
|
|
8
8
|
import"../chunk-v3hxvnrc.js";
|
|
9
9
|
import"../chunk-h8fwx0k7.js";
|
|
10
10
|
import"../chunk-5fgbhx2r.js";
|
package/dist/mysql/index.d.ts
CHANGED
package/dist/mysql/index.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MysqlClient,
|
|
3
|
-
MysqlDatabase,
|
|
4
|
-
openMysql
|
|
5
|
-
} from "../chunk-jx740hjh.js";
|
|
6
|
-
import"../chunk-kbewz29a.js";
|
|
7
1
|
import {
|
|
8
2
|
parseMysqlEnvTarget
|
|
9
3
|
} from "../chunk-qq77x9ek.js";
|
|
4
|
+
import {
|
|
5
|
+
limitedRows,
|
|
6
|
+
limitedStatement,
|
|
7
|
+
queryLimited
|
|
8
|
+
} from "../chunk-1gyshqd9.js";
|
|
10
9
|
import"../chunk-pfar9cm3.js";
|
|
10
|
+
import {
|
|
11
|
+
withReadScope
|
|
12
|
+
} from "../chunk-4sqvppz7.js";
|
|
13
|
+
import {
|
|
14
|
+
MysqlClient,
|
|
15
|
+
MysqlDatabase,
|
|
16
|
+
openMysql
|
|
17
|
+
} from "../chunk-56t4qyp7.js";
|
|
11
18
|
import"../chunk-m91t44jh.js";
|
|
19
|
+
import"../chunk-kbewz29a.js";
|
|
12
20
|
import"../chunk-v3hxvnrc.js";
|
|
13
21
|
import"../chunk-h8fwx0k7.js";
|
|
14
22
|
import"../chunk-5fgbhx2r.js";
|
|
@@ -17,8 +25,12 @@ import"../chunk-hmah9t8e.js";
|
|
|
17
25
|
import"../chunk-mn6dwsvh.js";
|
|
18
26
|
import"../chunk-ssj93brx.js";
|
|
19
27
|
export {
|
|
28
|
+
withReadScope,
|
|
29
|
+
queryLimited,
|
|
20
30
|
parseMysqlEnvTarget,
|
|
21
31
|
openMysql,
|
|
32
|
+
limitedStatement,
|
|
33
|
+
limitedRows,
|
|
22
34
|
MysqlDatabase,
|
|
23
35
|
MysqlClient
|
|
24
36
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Database, DatabaseRow, DatabaseTarget, SqlStatement } from "./types";
|
|
2
|
+
export interface LimitedRows {
|
|
3
|
+
rows: DatabaseRow[];
|
|
4
|
+
truncated: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** Append to caller-owned SQL without an existing LIMIT or trailing terminator; never rewrite SQL.
|
|
7
|
+
* An integer literal avoids driver/server differences in numeric LIMIT parameter binding.
|
|
8
|
+
*/
|
|
9
|
+
export declare function limitedStatement(statement: SqlStatement, maxRows: number): SqlStatement;
|
|
10
|
+
/** The extra row proves truncation; batch callers can use the same projection. */
|
|
11
|
+
export declare function limitedRows(rows: DatabaseRow[], maxRows: number): LimitedRows;
|
|
12
|
+
/** Row-count bound for trusted reads on the existing session, not a readonly transaction or byte cap. */
|
|
13
|
+
export declare function queryLimited(database: Pick<Database, "query">, target: DatabaseTarget, statement: SqlStatement, maxRows: number): Promise<LimitedRows>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DataLoader } from "../data-loader";
|
|
2
|
+
import type { Database } from "./types";
|
|
3
|
+
/** Bind caller-approved reads to a borrowed scope. Results are shared and must not be mutated.
|
|
4
|
+
* Close is a no-op: the root owns both scope and database lifetimes. Database operations retain
|
|
5
|
+
* their client's cancellation/timeout policy; they cannot be individually aborted by this adapter.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withReadScope(database: Database, scope: DataLoader): Database & Required<Pick<Database, "queryBatch">>;
|
package/dist/mysql/types.d.ts
CHANGED
|
@@ -29,5 +29,7 @@ export interface DatabaseQueryResult {
|
|
|
29
29
|
export interface Database {
|
|
30
30
|
query(target: DatabaseTarget, sql: string, values: readonly unknown[]): Promise<DatabaseRow[]>;
|
|
31
31
|
queryOne(target: DatabaseTarget, sql: string, values: readonly unknown[]): Promise<DatabaseRow | undefined>;
|
|
32
|
+
/** Independent statements in submission order; fail-fast, without atomicity or replay. */
|
|
33
|
+
queryBatch?(target: DatabaseTarget, statements: readonly SqlStatement[]): Promise<DatabaseRow[][]>;
|
|
32
34
|
close(): Promise<void>;
|
|
33
35
|
}
|
package/dist/opensearch/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
parseOpenSearchEndpoint
|
|
3
|
-
} from "../chunk-s1sjyem7.js";
|
|
4
|
-
import"../chunk-djk4kmxb.js";
|
|
5
1
|
import {
|
|
6
2
|
OpenSearchClient,
|
|
7
3
|
OpenSearchEngine,
|
|
8
4
|
isOpenSearchReadApi,
|
|
9
5
|
openOpenSearch
|
|
10
6
|
} from "../chunk-w3a4p6jm.js";
|
|
7
|
+
import {
|
|
8
|
+
searchAfterPages
|
|
9
|
+
} from "../chunk-99p0zt4f.js";
|
|
10
|
+
import {
|
|
11
|
+
parseOpenSearchEndpoint
|
|
12
|
+
} from "../chunk-s1sjyem7.js";
|
|
13
|
+
import"../chunk-djk4kmxb.js";
|
|
11
14
|
export {
|
|
15
|
+
searchAfterPages,
|
|
12
16
|
parseOpenSearchEndpoint,
|
|
13
17
|
openOpenSearch,
|
|
14
18
|
isOpenSearchReadApi,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SearchEngine, SearchQuery } from "./types";
|
|
2
|
+
export interface SearchAfterOptions {
|
|
3
|
+
query: SearchQuery;
|
|
4
|
+
/** The caller must include a stable tie breaker in this sort. */
|
|
5
|
+
sort: readonly Record<string, unknown>[];
|
|
6
|
+
pageSize: number;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
/** Iterate raw hits with the last hit's sort tuple; caller owns the stable sort and any PIT.
|
|
10
|
+
* Without PIT, this is a live view and concurrent indexing can change later pages.
|
|
11
|
+
*/
|
|
12
|
+
export declare function searchAfterPages(search: SearchEngine, index: string, options: SearchAfterOptions): AsyncGenerator<Record<string, unknown>[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compforge/harness-toolbox",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Shared infrastructure clients, transports and bounded data capture",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"build": "bun scripts/build.ts && tsc -p tsconfig.build.json"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
|
+
"./duration": {
|
|
13
|
+
"types": "./dist/duration.d.ts",
|
|
14
|
+
"import": "./dist/duration.js"
|
|
15
|
+
},
|
|
12
16
|
"./errors": {
|
|
13
17
|
"types": "./dist/errors.d.ts",
|
|
14
18
|
"import": "./dist/errors.js"
|
|
@@ -80,6 +84,10 @@
|
|
|
80
84
|
"./concurrency": {
|
|
81
85
|
"types": "./dist/concurrency.d.ts",
|
|
82
86
|
"import": "./dist/concurrency.js"
|
|
87
|
+
},
|
|
88
|
+
"./data-loader": {
|
|
89
|
+
"types": "./dist/data-loader.d.ts",
|
|
90
|
+
"import": "./dist/data-loader.js"
|
|
83
91
|
}
|
|
84
92
|
},
|
|
85
93
|
"files": [
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
queryReadonlySession,
|
|
3
|
-
validateQueryLimits
|
|
4
|
-
} from "./chunk-kbewz29a.js";
|
|
5
1
|
import {
|
|
6
2
|
queryMysqlViaPod,
|
|
7
3
|
queryMysqlViaPodBatch
|
|
8
4
|
} from "./chunk-m91t44jh.js";
|
|
5
|
+
import {
|
|
6
|
+
queryReadonlySession,
|
|
7
|
+
validateQueryLimits
|
|
8
|
+
} from "./chunk-kbewz29a.js";
|
|
9
9
|
import {
|
|
10
10
|
ConcurrencyPool
|
|
11
11
|
} from "./chunk-v3hxvnrc.js";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveWorkload
|
|
3
|
-
} from "./chunk-y3dx8jh6.js";
|
|
4
1
|
import {
|
|
5
2
|
ServicePortForwarder
|
|
6
3
|
} from "./chunk-qhpqse1y.js";
|
|
7
4
|
import {
|
|
8
5
|
resourceAccess
|
|
9
6
|
} from "./chunk-2m0mh09h.js";
|
|
7
|
+
import {
|
|
8
|
+
resolveWorkload
|
|
9
|
+
} from "./chunk-y3dx8jh6.js";
|
|
10
10
|
import {
|
|
11
11
|
KubernetesError
|
|
12
12
|
} from "./chunk-ntfm5e85.js";
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
PodLogByteBudget,
|
|
3
3
|
runPodLogCapturePlan
|
|
4
4
|
} from "./chunk-8nm9f0hh.js";
|
|
5
|
-
import {
|
|
6
|
-
LogCaptureFile
|
|
7
|
-
} from "./chunk-1vfwvgkj.js";
|
|
8
5
|
import {
|
|
9
6
|
logTimestampNanos
|
|
10
7
|
} from "./chunk-mp88s7r7.js";
|
|
8
|
+
import {
|
|
9
|
+
LogCaptureFile
|
|
10
|
+
} from "./chunk-1vfwvgkj.js";
|
|
11
11
|
import {
|
|
12
12
|
clientKey
|
|
13
13
|
} from "./chunk-a9htb6cr.js";
|