capman 0.5.1 → 0.5.3
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/CHANGELOG.md +57 -0
- package/CODEBASE.md +2 -1
- package/bin/lib/cmd-demo.js +2 -2
- package/dist/cjs/cache.d.ts +4 -1
- package/dist/cjs/cache.d.ts.map +1 -1
- package/dist/cjs/cache.js +42 -20
- package/dist/cjs/cache.js.map +1 -1
- package/dist/cjs/engine.d.ts +3 -1
- package/dist/cjs/engine.d.ts.map +1 -1
- package/dist/cjs/engine.js +103 -34
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/generator.d.ts.map +1 -1
- package/dist/cjs/generator.js +16 -1
- package/dist/cjs/generator.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/learning.d.ts +20 -11
- package/dist/cjs/learning.d.ts.map +1 -1
- package/dist/cjs/learning.js +169 -135
- package/dist/cjs/learning.js.map +1 -1
- package/dist/cjs/matcher.d.ts +4 -1
- package/dist/cjs/matcher.d.ts.map +1 -1
- package/dist/cjs/matcher.js +33 -13
- package/dist/cjs/matcher.js.map +1 -1
- package/dist/cjs/parser.js +10 -4
- package/dist/cjs/parser.js.map +1 -1
- package/dist/cjs/resolver.d.ts +7 -0
- package/dist/cjs/resolver.d.ts.map +1 -1
- package/dist/cjs/resolver.js +63 -19
- package/dist/cjs/resolver.js.map +1 -1
- package/dist/cjs/schema.d.ts +106 -14
- package/dist/cjs/schema.d.ts.map +1 -1
- package/dist/cjs/schema.js +9 -4
- package/dist/cjs/schema.js.map +1 -1
- package/dist/cjs/types.d.ts +1 -0
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/cache.d.ts +4 -1
- package/dist/esm/cache.js +42 -20
- package/dist/esm/engine.d.ts +3 -1
- package/dist/esm/engine.js +104 -35
- package/dist/esm/generator.js +16 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +1 -0
- package/dist/esm/learning.d.ts +20 -11
- package/dist/esm/learning.js +169 -135
- package/dist/esm/matcher.d.ts +4 -1
- package/dist/esm/matcher.js +31 -12
- package/dist/esm/parser.js +10 -4
- package/dist/esm/resolver.d.ts +7 -0
- package/dist/esm/resolver.js +63 -19
- package/dist/esm/schema.d.ts +106 -14
- package/dist/esm/schema.js +9 -4
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,63 @@ All notable changes to capman are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.5.3] — 2026-04-25
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
**Critical:**
|
|
11
|
+
- `.capman/` added to `.gitignore` — cache and learning files were being committed to git, exposing internal API path structures and cached match data
|
|
12
|
+
- Session `userId` no longer leaks into query string on multi-endpoint capabilities — path template check was joining all endpoint paths before checking for `{param}`, causing false positives. Now checks per-endpoint inside `resolveApi()`
|
|
13
|
+
- File writes are now atomic — `FileCache` and `FileLearningStore` write to a `.tmp` file then rename, preventing corrupt JSON on process crash mid-write
|
|
14
|
+
- `ask()` now caches only after successful resolution — previously cached match result before Step 5 (resolve), permanently poisoning the cache on transient network failures
|
|
15
|
+
|
|
16
|
+
**High:**
|
|
17
|
+
- API path params validated against allowlist in `buildUrl()` — `encodeURIComponent` does not encode `/`, allowing path traversal via params like `../../admin`. Now mirrors the validation already applied in `resolveNav()`
|
|
18
|
+
- Raw query text removed from `info`-level logs — queries like `"orders for jane@corp.com"` were emitted at info level to stdout. Query text now only appears at `debug` level
|
|
19
|
+
- Retry logic now only retries safe/idempotent methods (GET, HEAD, OPTIONS) — previously retried POST/PUT/PATCH/DELETE which could cause duplicate orders, double charges etc. Add `retryAllMethods: true` to `ResolveOptions` to opt in to retrying writes
|
|
20
|
+
- `FileCache` concurrent load guard added — two simultaneous `get()` calls before `loaded = true` both read the file. Now serialized through a shared `loadPromise`
|
|
21
|
+
|
|
22
|
+
**Medium:**
|
|
23
|
+
- Version comparison now validates semver format before parsing — `Number("v0")` → `NaN`, `NaN !== NaN` always true, causing spurious version warnings on every startup for non-semver manifest versions
|
|
24
|
+
- `LearningIndex` class extracted — `updateIndex`, `subtractFromIndex`, `rebuildIndex` were copy-pasted verbatim (~80 lines) between `FileLearningStore` and `MemoryLearningStore`. Both now compose `LearningIndex`
|
|
25
|
+
- `computeStats()` dead code removed — was only used by old `getStats()` before incremental index was introduced in v0.5.0
|
|
26
|
+
- `FileLearningStore` now debounces saves — previously wrote full JSON on every `record()` call (every `ask()`). Now batches with a 5s debounce timer and flushes synchronously on `process.exit`, `SIGTERM`, `SIGINT`
|
|
27
|
+
- YAML catch block now distinguishes `MODULE_NOT_FOUND` from actual parse errors using `err.code` — previously swallowed real YAML syntax errors with a generic message
|
|
28
|
+
- `baseUrl` now required by Zod schema when any capability uses `api` or `hybrid` resolver — previously optional, producing silent relative URLs that fail with opaque connection errors
|
|
29
|
+
- `explain()` now asserts `resolvedVia !== 'cache'` at runtime — makes the invariant that `explain()` never reads from cache explicit and catches future regressions immediately
|
|
30
|
+
- ESM config files now produce an actionable error — `ERR_REQUIRE_ESM` previously surfaced as a generic "Failed to load config" message with no guidance. Now explains the issue and lists three solutions
|
|
31
|
+
|
|
32
|
+
### Tests
|
|
33
|
+
- 90 tests passing
|
|
34
|
+
|
|
35
|
+
## [0.5.2] — 2026-04-20
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
**Critical:**
|
|
39
|
+
- `[from_session]` sentinel string replaced with `null` in `extractParams()` — was leaking literal `[from_session]` into API URLs and POST request bodies
|
|
40
|
+
- `resolveApi()` POST body now strips null params — session values that weren't injected no longer appear as `null` in request bodies
|
|
41
|
+
- `FileCache` and `FileLearningStore` constructors now validate path stays within working directory — prevents path traversal via caller-supplied `filePath`
|
|
42
|
+
- `FileCache.get()` no longer writes to disk on every cache hit — was triggering full JSON rewrite per request under any real load
|
|
43
|
+
- Cache hits now re-extract params from the current query — previously re-served `extractedParams` from the original cached query, leaking one user's param values to another
|
|
44
|
+
|
|
45
|
+
**High:**
|
|
46
|
+
- Query length and type guards added to `ask()` and `explain()` — throws `TypeError` for non-string input, `RangeError` for queries over 1000 characters
|
|
47
|
+
- `FileCache.load()` now normalizes keys on read — prevents duplicate entries from manual edits or older versions accumulating silently
|
|
48
|
+
- `matchWithLLM` now returns all capabilities as candidates with keyword scores as baseline — previously returned only the LLM winner, making learning boost unable to surface alternatives
|
|
49
|
+
- Manifest descriptions truncated to 200 chars and examples to 100 chars in LLM prompt — prevents context window overflow and reduces injection surface from third-party OpenAPI specs
|
|
50
|
+
- Zod schema now enforces `description` max 500 chars and `examples` max 200 chars per entry
|
|
51
|
+
- Template substitution uses `replaceAll` — `String.replace()` only replaced the first occurrence of a `{param}` placeholder, leaving duplicates unsubstituted
|
|
52
|
+
- TypeScript target bumped to ES2021 — enables `replaceAll`, aligns with minimum supported Node version
|
|
53
|
+
|
|
54
|
+
**Medium:**
|
|
55
|
+
- `MemoryLearningStore.rebuildIndex()` dead code removed — was defined but unreachable since `MemoryLearningStore` has no `load()` method
|
|
56
|
+
- `loadSpec()` in parser now uses `fs.promises.readFile` — was blocking event loop with synchronous `readFileSync` in an async function
|
|
57
|
+
- Raw query text no longer stored verbatim in learning entries — tokenized to keywords only before persisting, eliminates PII (emails, names, IDs) from `.capman/learning.json`
|
|
58
|
+
- `resolveApi()` JSDoc documents parallel execution and no-rollback behavior for multi-endpoint capabilities
|
|
59
|
+
- Version comparison now parses major/minor as integers — string comparison (`"0.10" > "0.9"`) is lexicographic and incorrect
|
|
60
|
+
|
|
61
|
+
### Tests
|
|
62
|
+
- 87 tests passing
|
|
63
|
+
|
|
7
64
|
## [0.5.1] — 2026-04-18
|
|
8
65
|
### Fixed
|
|
9
66
|
|
package/CODEBASE.md
CHANGED
|
@@ -147,8 +147,9 @@ Usage analytics and keyword index — now incremental.
|
|
|
147
147
|
|
|
148
148
|
Key exports:
|
|
149
149
|
- `LearningStore` interface — `record(entry)`, `getStats()`, `getTopCapabilities(limit)`, `getIndex()`
|
|
150
|
-
- `FileLearningStore` — persists to `.capman/learning.json`, caps at 10,000 entries
|
|
150
|
+
- `FileLearningStore` — persists to `.capman/learning.json`, caps at 10,000 entries. Saves are debounced (5s) with synchronous flush on process exit
|
|
151
151
|
- `MemoryLearningStore` — in-memory only, used in tests
|
|
152
|
+
- `LearningIndex` — internal class shared by both stores. Maintains keyword index and stats counters incrementally. Eliminates ~80 lines of duplication
|
|
152
153
|
|
|
153
154
|
`LearningEntry`:
|
|
154
155
|
- `query`, `capabilityId`, `confidence`, `intent`, `extractedParams`
|
package/bin/lib/cmd-demo.js
CHANGED
|
@@ -117,13 +117,13 @@ module.exports = function cmdDemo() {
|
|
|
117
117
|
const endpoint = result.capability.resolver.endpoints[0]
|
|
118
118
|
let p = endpoint.path
|
|
119
119
|
for (const [k, v] of Object.entries(result.extractedParams)) {
|
|
120
|
-
if (v) p = p.
|
|
120
|
+
if (v) p = p.replaceAll(`{${k}}`, String(v))
|
|
121
121
|
}
|
|
122
122
|
actionLine = `${endpoint.method} ${config.baseUrl}${p}`
|
|
123
123
|
} else if (result.capability.resolver.type === 'nav') {
|
|
124
124
|
let dest = result.capability.resolver.destination
|
|
125
125
|
for (const [k, v] of Object.entries(result.extractedParams)) {
|
|
126
|
-
if (v) dest = dest.
|
|
126
|
+
if (v) dest = dest.replaceAll(`{${k}}`, String(v))
|
|
127
127
|
}
|
|
128
128
|
actionLine = `navigate → ${dest}`
|
|
129
129
|
}
|
package/dist/cjs/cache.d.ts
CHANGED
|
@@ -29,10 +29,11 @@ export declare class MemoryCache implements CacheStore {
|
|
|
29
29
|
export declare class FileCache implements CacheStore {
|
|
30
30
|
private filePath;
|
|
31
31
|
private store;
|
|
32
|
-
private
|
|
32
|
+
private loadPromise;
|
|
33
33
|
private saveQueue;
|
|
34
34
|
constructor(filePath?: string);
|
|
35
35
|
private load;
|
|
36
|
+
private _doLoad;
|
|
36
37
|
private save;
|
|
37
38
|
private _doSave;
|
|
38
39
|
get(key: string, ttlMs?: number): Promise<CacheEntry | null>;
|
|
@@ -47,6 +48,8 @@ export declare class ComboCache implements CacheStore {
|
|
|
47
48
|
get(key: string, ttlMs?: number): Promise<CacheEntry | null>;
|
|
48
49
|
set(key: string, result: MatchResult): Promise<void>;
|
|
49
50
|
clear(): Promise<void>;
|
|
51
|
+
/** Returns the file-side entry count, not total unique entries across both stores.
|
|
52
|
+
* Memory may have additional promoted entries not reflected here. */
|
|
50
53
|
size(): Promise<number>;
|
|
51
54
|
}
|
|
52
55
|
//# sourceMappingURL=cache.d.ts.map
|
package/dist/cjs/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAOD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAiB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAMD,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAOD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAiB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAMD,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,SAAS,CAA+C;gBAEpD,QAAQ,SAAuB;IAcvC,OAAO,CAAC,IAAI;YAOE,OAAO;IAsBzB,OAAO,CAAC,IAAI;YAKE,OAAO;IAYf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAqB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAI9B;AAID,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAAW;gBAEX,QAAQ,SAAuB;IAKrC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAY5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;0EACsE;IAChE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG9B"}
|
package/dist/cjs/cache.js
CHANGED
|
@@ -105,19 +105,37 @@ const FILE_CACHE_MAX = 2048;
|
|
|
105
105
|
class FileCache {
|
|
106
106
|
constructor(filePath = '.capman/cache.json') {
|
|
107
107
|
this.store = new Map();
|
|
108
|
-
this.
|
|
108
|
+
this.loadPromise = null;
|
|
109
109
|
this.saveQueue = Promise.resolve();
|
|
110
|
-
|
|
110
|
+
const cwd = process.cwd();
|
|
111
|
+
const resolved = path.resolve(cwd, filePath);
|
|
112
|
+
const allowedPrefix = cwd === '/' ? '/' : cwd + path.sep;
|
|
113
|
+
if (!resolved.startsWith(allowedPrefix)) {
|
|
114
|
+
throw new Error(`FileCache path "${filePath}" resolves outside the working directory.\n` +
|
|
115
|
+
`Resolved: ${resolved}\nAllowed: ${cwd}`);
|
|
116
|
+
}
|
|
117
|
+
this.filePath = resolved;
|
|
111
118
|
logger_1.logger.info(`FileCache initialized — writing to: ${this.filePath}`);
|
|
112
119
|
}
|
|
113
|
-
|
|
114
|
-
if (this.
|
|
115
|
-
|
|
120
|
+
load() {
|
|
121
|
+
if (!this.loadPromise) {
|
|
122
|
+
this.loadPromise = this._doLoad();
|
|
123
|
+
}
|
|
124
|
+
return this.loadPromise;
|
|
125
|
+
}
|
|
126
|
+
async _doLoad() {
|
|
116
127
|
try {
|
|
117
128
|
const raw = await fs.promises.readFile(this.filePath, 'utf-8');
|
|
118
129
|
const parsed = JSON.parse(raw);
|
|
119
130
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
120
|
-
|
|
131
|
+
// Normalize keys on load — prevents duplicate entries from older versions,
|
|
132
|
+
// manual edits, or any path that bypassed normalizeQuery() on write.
|
|
133
|
+
// e.g. "Show me articles" and "show me articles" collapse to the same key.
|
|
134
|
+
const normalized = new Map();
|
|
135
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
136
|
+
normalized.set(normalizeQuery(k), v);
|
|
137
|
+
}
|
|
138
|
+
this.store = normalized;
|
|
121
139
|
logger_1.logger.debug(`File cache loaded: ${this.store.size} entries`);
|
|
122
140
|
}
|
|
123
141
|
else {
|
|
@@ -127,7 +145,6 @@ class FileCache {
|
|
|
127
145
|
catch {
|
|
128
146
|
// File doesn't exist yet — start fresh
|
|
129
147
|
}
|
|
130
|
-
this.loaded = true;
|
|
131
148
|
}
|
|
132
149
|
save() {
|
|
133
150
|
this.saveQueue = this.saveQueue.then(() => this._doSave());
|
|
@@ -137,7 +154,9 @@ class FileCache {
|
|
|
137
154
|
try {
|
|
138
155
|
const dir = path.dirname(this.filePath);
|
|
139
156
|
await fs.promises.mkdir(dir, { recursive: true });
|
|
140
|
-
|
|
157
|
+
const tmp = `${this.filePath}.tmp`;
|
|
158
|
+
await fs.promises.writeFile(tmp, JSON.stringify(Object.fromEntries(this.store), null, 2));
|
|
159
|
+
await fs.promises.rename(tmp, this.filePath);
|
|
141
160
|
}
|
|
142
161
|
catch (err) {
|
|
143
162
|
logger_1.logger.warn(`Failed to save file cache to ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -146,20 +165,21 @@ class FileCache {
|
|
|
146
165
|
async get(key, ttlMs) {
|
|
147
166
|
await this.load();
|
|
148
167
|
const entry = this.store.get(key);
|
|
149
|
-
if (entry)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
await this.save();
|
|
153
|
-
logger_1.logger.debug(`Cache entry expired (file): "${key}"`);
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
entry.hits++;
|
|
168
|
+
if (!entry)
|
|
169
|
+
return null;
|
|
170
|
+
if (ttlMs && Date.now() - new Date(entry.cachedAt).getTime() > ttlMs) {
|
|
157
171
|
this.store.delete(key);
|
|
158
|
-
this.
|
|
159
|
-
logger_1.logger.debug(`Cache
|
|
160
|
-
|
|
172
|
+
await this.save(); // eviction must be persisted
|
|
173
|
+
logger_1.logger.debug(`Cache entry expired (file): "${key}"`);
|
|
174
|
+
return null;
|
|
161
175
|
}
|
|
162
|
-
|
|
176
|
+
entry.hits++;
|
|
177
|
+
this.store.delete(key); // reinsert at end for LRU ordering
|
|
178
|
+
this.store.set(key, entry);
|
|
179
|
+
// hits counter is in-memory only — not saved on read
|
|
180
|
+
// saves only happen on set() and eviction to avoid full file rewrite per request
|
|
181
|
+
logger_1.logger.debug(`Cache hit (file): "${key}"`);
|
|
182
|
+
return entry;
|
|
163
183
|
}
|
|
164
184
|
async set(key, result) {
|
|
165
185
|
await this.load();
|
|
@@ -218,6 +238,8 @@ class ComboCache {
|
|
|
218
238
|
this.file.clear(),
|
|
219
239
|
]);
|
|
220
240
|
}
|
|
241
|
+
/** Returns the file-side entry count, not total unique entries across both stores.
|
|
242
|
+
* Memory may have additional promoted entries not reflected here. */
|
|
221
243
|
async size() {
|
|
222
244
|
return this.file.size();
|
|
223
245
|
}
|
package/dist/cjs/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,wCAEC;AASD,sCAYC;AAhDD,uCAAwB;AACxB,2CAA4B;AAE5B,qCAAiC;AAoBjC,iFAAiF;AAEjF,SAAgB,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACxD,CAAC;AAED;;;;;GAKG;AAEH,SAAgB,aAAa,CAC3B,KAAa,EACb,YAA2B,EAC3B,eAA8C;IAE9C,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,cAAc,CAAC,KAAK,CAAC,EAAE,CAAA;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SAC7B,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;SAC5B,IAAI,CAAC,GAAG,CAAC,CAAA;IACZ,OAAO,OAAO,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;AAC/D,CAAC;AAED,iFAAiF;AAEjF,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAG5B,MAAa,WAAW;IAAxB;QACU,UAAK,GAAG,IAAI,GAAG,EAAsB,CAAA;IAoC/C,CAAC;IAlCC,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;gBACrE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBACtB,eAAM,CAAC,KAAK,CAAC,kCAAkC,GAAG,GAAG,CAAC,CAAA;gBACtD,OAAO,IAAI,CAAA;YACb,CAAC;YACD,KAAK,CAAC,IAAI,EAAE,CAAA;YACZ,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACtB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAC1B,eAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,CAAC,CAAA;YAC5C,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,MAAmB;QACxC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC7C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACnD,eAAM,CAAC,KAAK,CAAC,wCAAwC,gBAAgB,WAAW,CAAC,CAAA;QACnF,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK,EAAE,GAAG;YACV,MAAM;YACN,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAClC,IAAI,EAAE,CAAC;SACR,CAAC,CAAA;QACF,eAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,CAAC,CAAA;IAC9C,CAAC;IAED,KAAK,CAAC,KAAK,KAAoB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,KAAsB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA,CAAC,CAAC;CACzD;AArCD,kCAqCC;AAED,iFAAiF;AAEjF,MAAM,cAAc,GAAG,IAAI,CAAA;AAE3B,MAAa,SAAS;IAMpB,YAAY,QAAQ,GAAG,oBAAoB;QAJnC,UAAK,
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,wCAEC;AASD,sCAYC;AAhDD,uCAAwB;AACxB,2CAA4B;AAE5B,qCAAiC;AAoBjC,iFAAiF;AAEjF,SAAgB,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACxD,CAAC;AAED;;;;;GAKG;AAEH,SAAgB,aAAa,CAC3B,KAAa,EACb,YAA2B,EAC3B,eAA8C;IAE9C,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,cAAc,CAAC,KAAK,CAAC,EAAE,CAAA;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SAC7B,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;SAC5B,IAAI,CAAC,GAAG,CAAC,CAAA;IACZ,OAAO,OAAO,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;AAC/D,CAAC;AAED,iFAAiF;AAEjF,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAG5B,MAAa,WAAW;IAAxB;QACU,UAAK,GAAG,IAAI,GAAG,EAAsB,CAAA;IAoC/C,CAAC;IAlCC,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;gBACrE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBACtB,eAAM,CAAC,KAAK,CAAC,kCAAkC,GAAG,GAAG,CAAC,CAAA;gBACtD,OAAO,IAAI,CAAA;YACb,CAAC;YACD,KAAK,CAAC,IAAI,EAAE,CAAA;YACZ,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACtB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAC1B,eAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,CAAC,CAAA;YAC5C,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,MAAmB;QACxC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC7C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACnD,eAAM,CAAC,KAAK,CAAC,wCAAwC,gBAAgB,WAAW,CAAC,CAAA;QACnF,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK,EAAE,GAAG;YACV,MAAM;YACN,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAClC,IAAI,EAAE,CAAC;SACR,CAAC,CAAA;QACF,eAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,CAAC,CAAA;IAC9C,CAAC;IAED,KAAK,CAAC,KAAK,KAAoB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,KAAsB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA,CAAC,CAAC;CACzD;AArCD,kCAqCC;AAED,iFAAiF;AAEjF,MAAM,cAAc,GAAG,IAAI,CAAA;AAE3B,MAAa,SAAS;IAMpB,YAAY,QAAQ,GAAG,oBAAoB;QAJnC,UAAK,GAAkC,IAAI,GAAG,EAAE,CAAA;QAChD,gBAAW,GAA4B,IAAI,CAAA;QAC3C,cAAS,GAA8B,OAAO,CAAC,OAAO,EAAE,CAAA;QAG9D,MAAM,GAAG,GAAQ,OAAO,CAAC,GAAG,EAAE,CAAA;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QAC5C,MAAM,aAAa,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAA;QACxD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACb,mBAAmB,QAAQ,6CAA6C;gBACxE,aAAa,QAAQ,eAAe,GAAG,EAAE,CAC1C,CAAA;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,eAAM,CAAC,IAAI,CAAC,uCAAuC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;IACrE,CAAC;IAEW,IAAI;QACV,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACnC,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAA;IACzB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,CAAC;YACP,MAAM,GAAG,GAAM,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YACjE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnE,2EAA2E;gBAC3E,qEAAqE;gBACrE,2EAA2E;gBAC3E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAA;gBAChD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC5C,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAe,CAAC,CAAA;gBACpD,CAAC;gBACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAA;gBACvB,eAAM,CAAC,KAAK,CAAC,sBAAsB,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,CAAA;YAC/D,CAAC;iBAAM,CAAC;gBACN,eAAM,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,QAAQ,+CAA+C,CAAC,CAAA;YAC5F,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QAC1D,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACvC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACjD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,MAAM,CAAA;YAClC,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;YACzF,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC9C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,eAAM,CAAC,IAAI,CAAC,gCAAgC,IAAI,CAAC,QAAQ,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACnH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc;QACnC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAEvB,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;YACrE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACtB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA,CAAE,6BAA6B;YAChD,eAAM,CAAC,KAAK,CAAC,gCAAgC,GAAG,GAAG,CAAC,CAAA;YACpD,OAAO,IAAI,CAAA;QACb,CAAC;QAED,KAAK,CAAC,IAAI,EAAE,CAAA;QACZ,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA,CAAI,mCAAmC;QAC7D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC1B,qDAAqD;QACrD,iFAAiF;QACjF,eAAM,CAAC,KAAK,CAAC,sBAAsB,GAAG,GAAG,CAAC,CAAA;QAC1C,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,MAAmB;QACxC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QACjB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBACzB,eAAM,CAAC,KAAK,CAAC,6CAA6C,cAAc,WAAW,CAAC,CAAA;YACtF,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK,EAAE,GAAG;YACV,MAAM;YACN,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAClC,IAAI,EAAE,CAAC;SACR,CAAC,CAAA;QACF,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QACjB,eAAM,CAAC,KAAK,CAAC,sBAAsB,GAAG,GAAG,CAAC,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QAClB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;IACnB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;IACxB,CAAC;CACF;AAnHD,8BAmHC;AAED,iFAAiF;AAEjF,MAAa,UAAU;IAIrB,YAAY,QAAQ,GAAG,oBAAoB;QACzC,IAAI,CAAC,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAC/B,IAAI,CAAC,IAAI,GAAK,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc;QACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAChD,IAAI,MAAM;YAAE,OAAO,MAAM,CAAA;QAEzB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC/C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;YAC1C,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,MAAmB;QACxC,MAAM,OAAO,CAAC,GAAG,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC;SAC3B,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,OAAO,CAAC,GAAG,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;YACnB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;SAClB,CAAC,CAAA;IACJ,CAAC;IAED;0EACsE;IACtE,KAAK,CAAC,IAAI;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IACzB,CAAC;CACF;AAxCD,gCAwCC"}
|
package/dist/cjs/engine.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { LLMMatcherOptions } from './matcher';
|
|
|
3
3
|
import type { ResolveOptions, AuthContext } from './resolver';
|
|
4
4
|
import type { CacheStore } from './cache';
|
|
5
5
|
import type { LearningStore } from './learning';
|
|
6
|
-
import type { MatchMode } from './
|
|
6
|
+
import type { MatchMode } from './types';
|
|
7
7
|
/**
|
|
8
8
|
* Options for constructing a CapmanEngine instance.
|
|
9
9
|
*
|
|
@@ -93,6 +93,8 @@ export interface EngineResult {
|
|
|
93
93
|
trace: ExecutionTrace;
|
|
94
94
|
}
|
|
95
95
|
export declare class CapmanEngine {
|
|
96
|
+
/** Maximum allowed query length in characters. Queries exceeding this throw RangeError. */
|
|
97
|
+
static readonly MAX_QUERY_LENGTH = 1000;
|
|
96
98
|
private manifest;
|
|
97
99
|
private mode;
|
|
98
100
|
private llm?;
|
package/dist/cjs/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAa,aAAa,EAA4F,MAAM,SAAS,CAAA;AACvM,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,YAAY,CAAA;AAK7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAMxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,kDAAkD;IAClD,GAAG,CAAC,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;IAC9B,0FAA0F;IAC1F,KAAK,CAAC,EAAE,UAAU,GAAG,KAAK,CAAA;IAC1B,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,aAAa,GAAG,KAAK,CAAA;IAChC,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mDAAmD;IACnD,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAE7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAA;IAEnC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;CAClC;AAID,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAA;IAClB,UAAU,EAAE,aAAa,CAAA;IACzB,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,KAAK,EAAE,cAAc,CAAA;CACtB;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/engine.js
CHANGED
|
@@ -27,7 +27,7 @@ class CapmanEngine {
|
|
|
27
27
|
this.maxLLMCallsPerMinute = options.maxLLMCallsPerMinute ?? 60;
|
|
28
28
|
this.llmCooldownMs = options.llmCooldownMs ?? 0;
|
|
29
29
|
this.llmCircuitBreakerThreshold = options.llmCircuitBreakerThreshold ?? 3;
|
|
30
|
-
this.llmCircuitBreakerResetMs = options.llmCircuitBreakerResetMs ??
|
|
30
|
+
this.llmCircuitBreakerResetMs = options.llmCircuitBreakerResetMs ?? 60_000;
|
|
31
31
|
// Cache — default MemoryCache (no filesystem writes), or disabled with false
|
|
32
32
|
// Use FileCache or ComboCache explicitly for persistence across restarts
|
|
33
33
|
this.cache = options.cache === false
|
|
@@ -40,14 +40,22 @@ class CapmanEngine {
|
|
|
40
40
|
: (options.learning ?? new learning_1.MemoryLearningStore());
|
|
41
41
|
logger_1.logger.info(`CapmanEngine initialized — mode: ${this.mode}, cache: ${this.cache ? 'enabled' : 'disabled'}, learning: ${this.learning ? 'enabled' : 'disabled'}`);
|
|
42
42
|
// ── Manifest version compatibility check ─────────────────────────────────
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
if (options.manifest.version) {
|
|
44
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
45
|
+
if (SEMVER_RE.test(options.manifest.version) && SEMVER_RE.test(version_1.VERSION)) {
|
|
46
|
+
const [mMaj, mMin] = options.manifest.version.split('.').map(Number);
|
|
47
|
+
const [eMaj, eMin] = version_1.VERSION.split('.').map(Number);
|
|
48
|
+
if (mMaj !== eMaj || mMin !== eMin) {
|
|
49
|
+
console.warn(`[capman] Manifest version "${options.manifest.version}" was generated with a ` +
|
|
50
|
+
`different engine version than "${version_1.VERSION}". This is usually fine across patch versions. ` +
|
|
51
|
+
`If you experience unexpected matching issues, regenerate with: npx capman generate`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (options.manifest.version !== version_1.VERSION) {
|
|
55
|
+
//console.warn is used instead of logger.warn to avoid the warning being logged to the console
|
|
56
|
+
console.warn(`[capman] Manifest version "${options.manifest.version}" could not be compared ` +
|
|
57
|
+
`to engine version "${version_1.VERSION}" — version strings are not valid semver.`);
|
|
58
|
+
}
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
@@ -62,6 +70,12 @@ class CapmanEngine {
|
|
|
62
70
|
* console.log(result.resolvedVia) // 'keyword' | 'llm' | 'cache'
|
|
63
71
|
*/
|
|
64
72
|
async ask(query, overrides = {}) {
|
|
73
|
+
if (!query || typeof query !== 'string') {
|
|
74
|
+
throw new TypeError('query must be a non-empty string');
|
|
75
|
+
}
|
|
76
|
+
if (query.length > CapmanEngine.MAX_QUERY_LENGTH) {
|
|
77
|
+
throw new RangeError(`query exceeds maximum length of ${CapmanEngine.MAX_QUERY_LENGTH} characters`);
|
|
78
|
+
}
|
|
65
79
|
const start = Date.now();
|
|
66
80
|
const steps = [];
|
|
67
81
|
// ── Step 1: Check cache ──────────────────────────────────────────────────
|
|
@@ -71,8 +85,20 @@ class CapmanEngine {
|
|
|
71
85
|
const cached = await this.cache.get(queryKey, this.cacheTtlMs ?? undefined);
|
|
72
86
|
if (cached) {
|
|
73
87
|
steps.push({ type: 'cache_check', status: 'hit', durationMs: Date.now() - cacheStart, detail: 'Served from cache' });
|
|
74
|
-
logger_1.logger.info(`Cache hit
|
|
75
|
-
|
|
88
|
+
logger_1.logger.info(`Cache hit — capability: "${cached.result.capability?.id ?? 'none'}"`);
|
|
89
|
+
logger_1.logger.debug(`Cache hit for query: "${query}"`);
|
|
90
|
+
// Re-extract params from the current query — never re-use cached params.
|
|
91
|
+
// Cached params belong to the original query (potentially from a different user).
|
|
92
|
+
// e.g. User A: "show orders for john" → cached with { customer: 'john' }
|
|
93
|
+
// User B: "show orders for jane" → must get { customer: 'jane' }, not john's
|
|
94
|
+
const freshParams = cached.result.capability
|
|
95
|
+
? (0, matcher_1.extractParams)(query, cached.result.capability)
|
|
96
|
+
: {};
|
|
97
|
+
const matchWithFreshParams = {
|
|
98
|
+
...cached.result,
|
|
99
|
+
extractedParams: freshParams,
|
|
100
|
+
};
|
|
101
|
+
const resolution = await (0, resolver_1.resolve)(matchWithFreshParams, freshParams, this.resolveOptions(overrides));
|
|
76
102
|
const trace = {
|
|
77
103
|
query,
|
|
78
104
|
candidates: cached.result.candidates,
|
|
@@ -82,13 +108,13 @@ class CapmanEngine {
|
|
|
82
108
|
totalMs: Date.now() - start,
|
|
83
109
|
};
|
|
84
110
|
const result = {
|
|
85
|
-
match:
|
|
111
|
+
match: matchWithFreshParams,
|
|
86
112
|
resolution,
|
|
87
113
|
resolvedVia: 'cache',
|
|
88
114
|
durationMs: Date.now() - start,
|
|
89
115
|
trace,
|
|
90
116
|
};
|
|
91
|
-
await this.recordLearning(query,
|
|
117
|
+
await this.recordLearning(query, matchWithFreshParams, 'cache');
|
|
92
118
|
return result;
|
|
93
119
|
}
|
|
94
120
|
steps.push({ type: 'cache_check', status: 'miss', durationMs: Date.now() - cacheStart });
|
|
@@ -111,15 +137,7 @@ class CapmanEngine {
|
|
|
111
137
|
detail: `level: ${privacyLevel}`,
|
|
112
138
|
});
|
|
113
139
|
}
|
|
114
|
-
// ── Step 4:
|
|
115
|
-
// Non-public capabilities are never cached — prevents auth bypass where
|
|
116
|
-
// User A's cached match is served to User B without privacy enforcement.
|
|
117
|
-
if (this.cache && matchResult.capability
|
|
118
|
-
&& matchResult.capability.privacy.level === 'public') {
|
|
119
|
-
const queryKey = (0, cache_1.normalizeQuery)(query);
|
|
120
|
-
await this.cache.set(queryKey, matchResult);
|
|
121
|
-
}
|
|
122
|
-
// ── Step 5: Resolve ──────────────────────────────────────────────────────
|
|
140
|
+
// ── Step 4: Resolve ──────────────────────────────────────────────────────
|
|
123
141
|
const resolveStart = Date.now();
|
|
124
142
|
const resolution = await (0, resolver_1.resolve)(matchResult, matchResult.extractedParams, this.resolveOptions(overrides));
|
|
125
143
|
steps.push({
|
|
@@ -128,6 +146,16 @@ class CapmanEngine {
|
|
|
128
146
|
durationMs: Date.now() - resolveStart,
|
|
129
147
|
detail: resolution.error ?? `via ${resolution.resolverType}`,
|
|
130
148
|
});
|
|
149
|
+
// ── Step 5: Cache after successful resolution ────────────────────────────
|
|
150
|
+
// Only cache when resolution succeeded — a failed resolution (network error,
|
|
151
|
+
// auth failure, bad params) must not poison the cache. A cached failed match
|
|
152
|
+
// would cause every subsequent cache hit to attempt the same failing resolution
|
|
153
|
+
// until TTL expires.
|
|
154
|
+
if (this.cache && resolution.success && matchResult.capability
|
|
155
|
+
&& matchResult.capability.privacy.level === 'public') {
|
|
156
|
+
const queryKey = (0, cache_1.normalizeQuery)(query);
|
|
157
|
+
await this.cache.set(queryKey, matchResult);
|
|
158
|
+
}
|
|
131
159
|
// ── Step 6: Build reasoning array ────────────────────────────────────────
|
|
132
160
|
const reasoning = [];
|
|
133
161
|
if (matchResult.candidates.length) {
|
|
@@ -216,9 +244,21 @@ class CapmanEngine {
|
|
|
216
244
|
* console.log(explanation.candidates)
|
|
217
245
|
*/
|
|
218
246
|
async explain(query) {
|
|
247
|
+
if (!query || typeof query !== 'string') {
|
|
248
|
+
throw new TypeError('query must be a non-empty string');
|
|
249
|
+
}
|
|
250
|
+
if (query.length > CapmanEngine.MAX_QUERY_LENGTH) {
|
|
251
|
+
throw new RangeError(`query exceeds maximum length of ${CapmanEngine.MAX_QUERY_LENGTH} characters`);
|
|
252
|
+
}
|
|
219
253
|
const start = Date.now();
|
|
220
254
|
// ── Match — shared with ask() via _runMatch() ─────────────────────────────
|
|
221
255
|
let { matchResult, resolvedVia: _resolvedVia } = await this._runMatch(query);
|
|
256
|
+
// explain() never reads from cache — it always runs a fresh match.
|
|
257
|
+
// This assertion catches any future refactor that accidentally adds
|
|
258
|
+
// cache reads to _runMatch() when called from explain().
|
|
259
|
+
if (_resolvedVia === 'cache') {
|
|
260
|
+
throw new Error('Invariant violation: explain() must never resolve via cache');
|
|
261
|
+
}
|
|
222
262
|
let resolvedVia = _resolvedVia;
|
|
223
263
|
// ── Apply learning boost (same as ask()) ─────────────────────────────────
|
|
224
264
|
matchResult = await this.applyBoostToMatchResult(query, matchResult);
|
|
@@ -264,12 +304,17 @@ class CapmanEngine {
|
|
|
264
304
|
}
|
|
265
305
|
reasoning.push(`Resolved via: ${resolvedVia}`);
|
|
266
306
|
if (matchResult.extractedParams && Object.keys(matchResult.extractedParams).length) {
|
|
267
|
-
const
|
|
307
|
+
const extracted = Object.entries(matchResult.extractedParams)
|
|
268
308
|
.filter(([, v]) => v !== null)
|
|
269
309
|
.map(([k, v]) => `${k}=${v}`)
|
|
270
310
|
.join(', ');
|
|
271
|
-
|
|
272
|
-
|
|
311
|
+
const session = matchResult.capability?.params
|
|
312
|
+
.filter(p => p.source === 'session')
|
|
313
|
+
.map(p => `${p.name}=[from auth]`)
|
|
314
|
+
.join(', ');
|
|
315
|
+
const parts = [extracted, session].filter(Boolean).join(', ');
|
|
316
|
+
if (parts)
|
|
317
|
+
reasoning.push(`Would extract params: ${parts}`);
|
|
273
318
|
}
|
|
274
319
|
// ── Build wouldExecute ───────────────────────────────────────────────────
|
|
275
320
|
const cap = matchResult.capability;
|
|
@@ -302,7 +347,7 @@ class CapmanEngine {
|
|
|
302
347
|
let path = endpoint.path;
|
|
303
348
|
for (const [k, v] of Object.entries(params)) {
|
|
304
349
|
if (v)
|
|
305
|
-
path = path.
|
|
350
|
+
path = path.replaceAll(`{${k}}`, v);
|
|
306
351
|
}
|
|
307
352
|
const base = this.baseUrl ?? '';
|
|
308
353
|
action = `${endpoint.method} ${base}${path}`;
|
|
@@ -311,7 +356,7 @@ class CapmanEngine {
|
|
|
311
356
|
let dest = cap.resolver.destination;
|
|
312
357
|
for (const [k, v] of Object.entries(params)) {
|
|
313
358
|
if (v)
|
|
314
|
-
dest = dest.
|
|
359
|
+
dest = dest.replaceAll(`{${k}}`, v);
|
|
315
360
|
}
|
|
316
361
|
action = `navigate → ${dest}`;
|
|
317
362
|
}
|
|
@@ -321,12 +366,12 @@ class CapmanEngine {
|
|
|
321
366
|
let path = endpoint.path;
|
|
322
367
|
for (const [k, v] of Object.entries(params)) {
|
|
323
368
|
if (v)
|
|
324
|
-
path = path.
|
|
369
|
+
path = path.replaceAll(`{${k}}`, v);
|
|
325
370
|
}
|
|
326
371
|
let dest = hybrid.nav.destination;
|
|
327
372
|
for (const [k, v] of Object.entries(params)) {
|
|
328
373
|
if (v)
|
|
329
|
-
dest = dest.
|
|
374
|
+
dest = dest.replaceAll(`{${k}}`, v);
|
|
330
375
|
}
|
|
331
376
|
const base = this.baseUrl ?? '';
|
|
332
377
|
action = `${endpoint.method} ${base}${path} + navigate → ${dest}`;
|
|
@@ -375,13 +420,13 @@ class CapmanEngine {
|
|
|
375
420
|
}
|
|
376
421
|
// ── Per-minute rate limit ────────────────────────────────────────────────
|
|
377
422
|
const windowElapsed = now - this.llmWindowStart;
|
|
378
|
-
if (windowElapsed >=
|
|
423
|
+
if (windowElapsed >= 60_000) {
|
|
379
424
|
this.llmCallsThisMinute = 0;
|
|
380
425
|
this.llmWindowStart = now;
|
|
381
426
|
}
|
|
382
427
|
if (this.llmCallsThisMinute >= this.maxLLMCallsPerMinute) {
|
|
383
428
|
// Recalculate elapsed after possible window reset above
|
|
384
|
-
const resetIn = Math.ceil((
|
|
429
|
+
const resetIn = Math.ceil((60_000 - (now - this.llmWindowStart)) / 1000);
|
|
385
430
|
return `rate limit reached (${this.maxLLMCallsPerMinute}/min) — resets in ${Math.max(0, resetIn)}s`;
|
|
386
431
|
}
|
|
387
432
|
// Reserve the slot atomically before the call happens
|
|
@@ -435,10 +480,21 @@ class CapmanEngine {
|
|
|
435
480
|
matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
|
|
436
481
|
this.recordLLMSuccess();
|
|
437
482
|
resolvedVia = 'llm';
|
|
483
|
+
// Merge keyword scores into LLM candidates so boost has real signal for alternatives
|
|
484
|
+
const kwResult = (0, matcher_1.match)(query, this.manifest);
|
|
485
|
+
matchResult = {
|
|
486
|
+
...matchResult,
|
|
487
|
+
candidates: matchResult.candidates.map(c => ({
|
|
488
|
+
...c,
|
|
489
|
+
score: c.matched
|
|
490
|
+
? c.score // keep LLM confidence for winner
|
|
491
|
+
: (kwResult.candidates.find(kc => kc.capabilityId === c.capabilityId)?.score ?? 0),
|
|
492
|
+
})),
|
|
493
|
+
};
|
|
438
494
|
steps?.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t, detail: `confidence: ${matchResult.confidence}%` });
|
|
439
495
|
}
|
|
440
496
|
catch (err) {
|
|
441
|
-
const isParseError =
|
|
497
|
+
const isParseError = err instanceof matcher_1.LLMParseError;
|
|
442
498
|
if (!isParseError)
|
|
443
499
|
this.recordLLMFailure();
|
|
444
500
|
logger_1.logger.warn(`LLM call failed — falling back to keyword: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -473,16 +529,27 @@ class CapmanEngine {
|
|
|
473
529
|
matchResult = keywordResult;
|
|
474
530
|
}
|
|
475
531
|
else {
|
|
476
|
-
logger_1.logger.info(`Low confidence (${keywordResult.confidence}%) — escalating to LLM`);
|
|
532
|
+
logger_1.logger.info(`Low keyword confidence (${keywordResult.confidence}%) — escalating to LLM`);
|
|
533
|
+
logger_1.logger.debug(`Query escalated to LLM: "${query}"`);
|
|
477
534
|
const t2 = Date.now();
|
|
478
535
|
try {
|
|
479
536
|
matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
|
|
480
537
|
this.recordLLMSuccess();
|
|
481
538
|
resolvedVia = 'llm';
|
|
539
|
+
// keywordResult already computed above in balanced mode — merge scores
|
|
540
|
+
matchResult = {
|
|
541
|
+
...matchResult,
|
|
542
|
+
candidates: matchResult.candidates.map(c => ({
|
|
543
|
+
...c,
|
|
544
|
+
score: c.matched
|
|
545
|
+
? c.score
|
|
546
|
+
: (keywordResult.candidates.find(kc => kc.capabilityId === c.capabilityId)?.score ?? 0),
|
|
547
|
+
})),
|
|
548
|
+
};
|
|
482
549
|
steps?.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t2, detail: `confidence: ${matchResult.confidence}%` });
|
|
483
550
|
}
|
|
484
551
|
catch (err) {
|
|
485
|
-
const isParseError =
|
|
552
|
+
const isParseError = err instanceof matcher_1.LLMParseError;
|
|
486
553
|
if (!isParseError)
|
|
487
554
|
this.recordLLMFailure();
|
|
488
555
|
logger_1.logger.warn(`LLM call failed — falling back to keyword: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -598,4 +665,6 @@ class CapmanEngine {
|
|
|
598
665
|
}
|
|
599
666
|
}
|
|
600
667
|
exports.CapmanEngine = CapmanEngine;
|
|
668
|
+
/** Maximum allowed query length in characters. Queries exceeding this throw RangeError. */
|
|
669
|
+
CapmanEngine.MAX_QUERY_LENGTH = 1000;
|
|
601
670
|
//# sourceMappingURL=engine.js.map
|