@compforge/harness-toolbox 0.5.1 → 0.5.2
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 +4 -0
- package/dist/data-loader.d.ts +5 -0
- package/dist/data-loader.js +23 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -426,3 +426,7 @@ MySQL, OpenSearch and Redis use the same acquisition lifecycle. `connectTimeoutM
|
|
|
426
426
|
and handshake/probe; optional `acquireTimeoutMs` bounds all routes together (default: per-route budget × route count).
|
|
427
427
|
Cancellation preserves the caller reason and prevents late mappings from starting a handshake. Query and command
|
|
428
428
|
timeouts remain separate, and failed operations are never replayed on another transport.
|
|
429
|
+
|
|
430
|
+
DataLoader accepts an optional `logger.debug(message)` and a non-sensitive `label`.
|
|
431
|
+
Diagnostics distinguish miss, wait, hit, bypass, not-retained and close using scope/read
|
|
432
|
+
identifiers, never keys, values or errors. Logging does not change retention or retry policy.
|
package/dist/data-loader.d.ts
CHANGED
|
@@ -3,6 +3,11 @@ export interface DataLoaderOptions {
|
|
|
3
3
|
/** Serialized result bytes retained, excluding keys/errors and active operation memory. */
|
|
4
4
|
maxBytes: number;
|
|
5
5
|
signal: AbortSignal;
|
|
6
|
+
/** Non-sensitive label supplied by the adapter; keys and values never enter diagnostics. */
|
|
7
|
+
label?: string;
|
|
8
|
+
logger?: {
|
|
9
|
+
debug(message: string): void;
|
|
10
|
+
};
|
|
6
11
|
}
|
|
7
12
|
/** A scoped async loading cache, similar to Guava's LoadingCache.
|
|
8
13
|
* Shares results, failures and in-flight reads until close; consumers must not mutate results.
|
package/dist/data-loader.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// src/data-loader.ts
|
|
2
2
|
import { serialize } from "node:v8";
|
|
3
|
+
var nextScopeId = 0;
|
|
3
4
|
|
|
4
5
|
class DataLoader {
|
|
5
6
|
options;
|
|
7
|
+
#scopeId = ++nextScopeId;
|
|
8
|
+
#readIds = new WeakMap;
|
|
9
|
+
#nextReadId = 0;
|
|
6
10
|
#entries = new Map;
|
|
7
11
|
#pending = new Set;
|
|
8
12
|
#controller = new AbortController;
|
|
@@ -20,30 +24,46 @@ class DataLoader {
|
|
|
20
24
|
#abort = () => {
|
|
21
25
|
this.close(this.options.signal.reason);
|
|
22
26
|
};
|
|
27
|
+
#debug(event, readId, reason) {
|
|
28
|
+
if (!this.options.logger)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
this.options.logger.debug(`[data-loader] scope=${this.#scopeId}` + (this.options.label ? ` label=${this.options.label}` : "") + (readId === undefined ? "" : ` read=${readId}`) + ` event=${event}` + (reason ? ` reason=${reason}` : ""));
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
23
34
|
async read(key, operation, signal) {
|
|
24
35
|
this.#controller.signal.throwIfAborted();
|
|
25
36
|
signal?.throwIfAborted();
|
|
26
37
|
let pending = key === undefined ? undefined : this.#entries.get(key);
|
|
27
|
-
if (
|
|
38
|
+
if (pending) {
|
|
39
|
+
this.#debug(this.#pending.has(pending) ? "wait" : "hit", this.#readIds.get(pending));
|
|
40
|
+
} else {
|
|
41
|
+
const readId = ++this.#nextReadId;
|
|
28
42
|
const retain = key !== undefined && this.#entries.size < this.options.maxEntries;
|
|
43
|
+
this.#debug(retain ? "miss" : "bypass", readId, retain ? undefined : key === undefined ? "no-key" : "entry-limit");
|
|
29
44
|
pending = Promise.resolve().then(async () => {
|
|
30
45
|
this.#controller.signal.throwIfAborted();
|
|
31
46
|
const value = await operation(this.#controller.signal);
|
|
32
47
|
this.#controller.signal.throwIfAborted();
|
|
33
48
|
if (retain) {
|
|
34
49
|
let bytes;
|
|
50
|
+
let reason = "byte-limit";
|
|
35
51
|
try {
|
|
36
52
|
bytes = serialize(value).byteLength;
|
|
37
53
|
} catch {
|
|
38
54
|
bytes = Infinity;
|
|
55
|
+
reason = "not-serializable";
|
|
39
56
|
}
|
|
40
57
|
if (this.#bytes + bytes <= this.options.maxBytes)
|
|
41
58
|
this.#bytes += bytes;
|
|
42
|
-
else
|
|
59
|
+
else {
|
|
43
60
|
this.#entries.delete(key);
|
|
61
|
+
this.#debug("not-retained", readId, reason);
|
|
62
|
+
}
|
|
44
63
|
}
|
|
45
64
|
return value;
|
|
46
65
|
});
|
|
66
|
+
this.#readIds.set(pending, readId);
|
|
47
67
|
if (retain)
|
|
48
68
|
this.#entries.set(key, pending);
|
|
49
69
|
this.#pending.add(pending);
|
|
@@ -74,6 +94,7 @@ class DataLoader {
|
|
|
74
94
|
}
|
|
75
95
|
close(reason = new Error("DataLoader scope is closed")) {
|
|
76
96
|
if (!this.#closing) {
|
|
97
|
+
this.#debug("close");
|
|
77
98
|
this.#closing = Promise.resolve().then(async () => {
|
|
78
99
|
await Promise.allSettled([...this.#pending]);
|
|
79
100
|
this.#entries.clear();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compforge/harness-toolbox",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Shared infrastructure clients, transports and bounded data capture",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
],
|
|
105
105
|
"dependencies": {
|
|
106
106
|
"@aws-sdk/client-s3": "3.1133.0",
|
|
107
|
-
"@compforge/harness-common": "0.5.
|
|
107
|
+
"@compforge/harness-common": "0.5.11",
|
|
108
108
|
"@kubernetes/client-node": "1.4.0",
|
|
109
109
|
"@opensearch-project/opensearch": "3.6.0",
|
|
110
110
|
"@redis/client": "6.1.0",
|