@qase/mcp-server 1.1.8 → 2.0.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/CHANGELOG.md +38 -0
- package/README.md +75 -542
- package/build/auth/client-context.d.ts +12 -0
- package/build/auth/client-context.d.ts.map +1 -0
- package/build/auth/client-context.js +12 -0
- package/build/auth/client-context.js.map +1 -0
- package/build/auth/jwks-verifier.d.ts +15 -0
- package/build/auth/jwks-verifier.d.ts.map +1 -0
- package/build/auth/jwks-verifier.js +31 -0
- package/build/auth/jwks-verifier.js.map +1 -0
- package/build/auth/mcp-guard.d.ts +15 -0
- package/build/auth/mcp-guard.d.ts.map +1 -0
- package/build/auth/mcp-guard.js +44 -0
- package/build/auth/mcp-guard.js.map +1 -0
- package/build/auth/oauth-config.d.ts +40 -0
- package/build/auth/oauth-config.d.ts.map +1 -0
- package/build/auth/oauth-config.js +34 -0
- package/build/auth/oauth-config.js.map +1 -0
- package/build/auth/proxy-provider.d.ts +31 -0
- package/build/auth/proxy-provider.d.ts.map +1 -0
- package/build/auth/proxy-provider.js +87 -0
- package/build/auth/proxy-provider.js.map +1 -0
- package/build/auth/token-type.d.ts +7 -0
- package/build/auth/token-type.d.ts.map +1 -0
- package/build/auth/token-type.js +10 -0
- package/build/auth/token-type.js.map +1 -0
- package/build/cache/circuit-breaker.d.ts +33 -0
- package/build/cache/circuit-breaker.d.ts.map +1 -0
- package/build/cache/circuit-breaker.js +68 -0
- package/build/cache/circuit-breaker.js.map +1 -0
- package/build/cache/index.d.ts +24 -0
- package/build/cache/index.d.ts.map +1 -0
- package/build/cache/index.js +86 -0
- package/build/cache/index.js.map +1 -0
- package/build/cache/keys.d.ts +25 -0
- package/build/cache/keys.d.ts.map +1 -0
- package/build/cache/keys.js +36 -0
- package/build/cache/keys.js.map +1 -0
- package/build/cache/layered.d.ts +31 -0
- package/build/cache/layered.d.ts.map +1 -0
- package/build/cache/layered.js +88 -0
- package/build/cache/layered.js.map +1 -0
- package/build/cache/memory.d.ts +32 -0
- package/build/cache/memory.d.ts.map +1 -0
- package/build/cache/memory.js +108 -0
- package/build/cache/memory.js.map +1 -0
- package/build/cache/metrics.d.ts +31 -0
- package/build/cache/metrics.d.ts.map +1 -0
- package/build/cache/metrics.js +95 -0
- package/build/cache/metrics.js.map +1 -0
- package/build/cache/prom-format.d.ts +7 -0
- package/build/cache/prom-format.d.ts.map +1 -0
- package/build/cache/prom-format.js +16 -0
- package/build/cache/prom-format.js.map +1 -0
- package/build/cache/redis-bus.d.ts +29 -0
- package/build/cache/redis-bus.d.ts.map +1 -0
- package/build/cache/redis-bus.js +50 -0
- package/build/cache/redis-bus.js.map +1 -0
- package/build/cache/redis.d.ts +32 -0
- package/build/cache/redis.d.ts.map +1 -0
- package/build/cache/redis.js +89 -0
- package/build/cache/redis.js.map +1 -0
- package/build/cache/types.d.ts +38 -0
- package/build/cache/types.d.ts.map +1 -0
- package/build/cache/types.js +2 -0
- package/build/cache/types.js.map +1 -0
- package/build/client/index.d.ts +10 -3
- package/build/client/index.d.ts.map +1 -1
- package/build/client/index.js +75 -50
- package/build/client/index.js.map +1 -1
- package/build/http/agent.d.ts +16 -0
- package/build/http/agent.d.ts.map +1 -0
- package/build/http/agent.js +16 -0
- package/build/http/agent.js.map +1 -0
- package/build/http/dedupe.d.ts +14 -0
- package/build/http/dedupe.d.ts.map +1 -0
- package/build/http/dedupe.js +69 -0
- package/build/http/dedupe.js.map +1 -0
- package/build/http/index.d.ts +6 -0
- package/build/http/index.d.ts.map +1 -0
- package/build/http/index.js +4 -0
- package/build/http/index.js.map +1 -0
- package/build/http/retry.d.ts +18 -0
- package/build/http/retry.d.ts.map +1 -0
- package/build/http/retry.js +52 -0
- package/build/http/retry.js.map +1 -0
- package/build/index.d.ts +1 -18
- package/build/index.d.ts.map +1 -1
- package/build/index.js +95 -63
- package/build/index.js.map +1 -1
- package/build/operations-v2/composites/ci-report.d.ts +2 -0
- package/build/operations-v2/composites/ci-report.d.ts.map +1 -0
- package/build/operations-v2/composites/ci-report.js +99 -0
- package/build/operations-v2/composites/ci-report.js.map +1 -0
- package/build/operations-v2/composites/regression-run.d.ts +2 -0
- package/build/operations-v2/composites/regression-run.d.ts.map +1 -0
- package/build/operations-v2/composites/regression-run.js +81 -0
- package/build/operations-v2/composites/regression-run.js.map +1 -0
- package/build/operations-v2/composites/triage-defect.d.ts +2 -0
- package/build/operations-v2/composites/triage-defect.d.ts.map +1 -0
- package/build/operations-v2/composites/triage-defect.js +67 -0
- package/build/operations-v2/composites/triage-defect.js.map +1 -0
- package/build/operations-v2/escape/api.d.ts +2 -0
- package/build/operations-v2/escape/api.d.ts.map +1 -0
- package/build/operations-v2/escape/api.js +34 -0
- package/build/operations-v2/escape/api.js.map +1 -0
- package/build/operations-v2/index.d.ts +21 -0
- package/build/operations-v2/index.d.ts.map +1 -0
- package/build/operations-v2/index.js +27 -0
- package/build/operations-v2/index.js.map +1 -0
- package/build/operations-v2/meta/discover.d.ts +9 -0
- package/build/operations-v2/meta/discover.d.ts.map +1 -0
- package/build/operations-v2/meta/discover.js +79 -0
- package/build/operations-v2/meta/discover.js.map +1 -0
- package/build/{operations/search.d.ts → operations-v2/qql/index.d.ts} +1 -1
- package/build/operations-v2/qql/index.d.ts.map +1 -0
- package/build/{operations/search.js → operations-v2/qql/index.js} +34 -9
- package/build/operations-v2/qql/index.js.map +1 -0
- package/build/operations-v2/read/get.d.ts +2 -0
- package/build/operations-v2/read/get.d.ts.map +1 -0
- package/build/operations-v2/read/get.js +115 -0
- package/build/operations-v2/read/get.js.map +1 -0
- package/build/operations-v2/read/project-context.d.ts +2 -0
- package/build/operations-v2/read/project-context.d.ts.map +1 -0
- package/build/operations-v2/read/project-context.js +102 -0
- package/build/operations-v2/read/project-context.js.map +1 -0
- package/build/operations-v2/write/attachments.d.ts +2 -0
- package/build/operations-v2/write/attachments.d.ts.map +1 -0
- package/build/operations-v2/write/attachments.js +58 -0
- package/build/operations-v2/write/attachments.js.map +1 -0
- package/build/operations-v2/write/case-fields.d.ts +206 -0
- package/build/operations-v2/write/case-fields.d.ts.map +1 -0
- package/build/operations-v2/write/case-fields.js +79 -0
- package/build/operations-v2/write/case-fields.js.map +1 -0
- package/build/operations-v2/write/cases-bulk.d.ts +2 -0
- package/build/operations-v2/write/cases-bulk.d.ts.map +1 -0
- package/build/operations-v2/write/cases-bulk.js +54 -0
- package/build/operations-v2/write/cases-bulk.js.map +1 -0
- package/build/operations-v2/write/cases.d.ts +2 -0
- package/build/operations-v2/write/cases.d.ts.map +1 -0
- package/build/operations-v2/write/cases.js +52 -0
- package/build/operations-v2/write/cases.js.map +1 -0
- package/build/operations-v2/write/defects.d.ts +2 -0
- package/build/operations-v2/write/defects.d.ts.map +1 -0
- package/build/operations-v2/write/defects.js +81 -0
- package/build/operations-v2/write/defects.js.map +1 -0
- package/build/operations-v2/write/environments.d.ts +2 -0
- package/build/operations-v2/write/environments.d.ts.map +1 -0
- package/build/operations-v2/write/environments.js +56 -0
- package/build/operations-v2/write/environments.js.map +1 -0
- package/build/operations-v2/write/external-issues.d.ts +2 -0
- package/build/operations-v2/write/external-issues.d.ts.map +1 -0
- package/build/operations-v2/write/external-issues.js +96 -0
- package/build/operations-v2/write/external-issues.js.map +1 -0
- package/build/operations-v2/write/milestones.d.ts +2 -0
- package/build/operations-v2/write/milestones.d.ts.map +1 -0
- package/build/operations-v2/write/milestones.js +56 -0
- package/build/operations-v2/write/milestones.js.map +1 -0
- package/build/operations-v2/write/plans.d.ts +2 -0
- package/build/operations-v2/write/plans.d.ts.map +1 -0
- package/build/operations-v2/write/plans.js +55 -0
- package/build/operations-v2/write/plans.js.map +1 -0
- package/build/operations-v2/write/results.d.ts +2 -0
- package/build/operations-v2/write/results.d.ts.map +1 -0
- package/build/operations-v2/write/results.js +72 -0
- package/build/operations-v2/write/results.js.map +1 -0
- package/build/operations-v2/write/runs.d.ts +2 -0
- package/build/operations-v2/write/runs.d.ts.map +1 -0
- package/build/operations-v2/write/runs.js +84 -0
- package/build/operations-v2/write/runs.js.map +1 -0
- package/build/operations-v2/write/shared-steps.d.ts +2 -0
- package/build/operations-v2/write/shared-steps.d.ts.map +1 -0
- package/build/operations-v2/write/shared-steps.js +60 -0
- package/build/operations-v2/write/shared-steps.js.map +1 -0
- package/build/operations-v2/write/suites.d.ts +2 -0
- package/build/operations-v2/write/suites.d.ts.map +1 -0
- package/build/operations-v2/write/suites.js +63 -0
- package/build/operations-v2/write/suites.js.map +1 -0
- package/build/prompts/index.d.ts +31 -0
- package/build/prompts/index.d.ts.map +1 -0
- package/build/prompts/index.js +229 -0
- package/build/prompts/index.js.map +1 -0
- package/build/transports/sse.d.ts.map +1 -1
- package/build/transports/sse.js +6 -0
- package/build/transports/sse.js.map +1 -1
- package/build/transports/streamableHttp.d.ts +6 -1
- package/build/transports/streamableHttp.d.ts.map +1 -1
- package/build/transports/streamableHttp.js +128 -16
- package/build/transports/streamableHttp.js.map +1 -1
- package/build/utils/auth-context.d.ts +8 -0
- package/build/utils/auth-context.d.ts.map +1 -1
- package/build/utils/auth-context.js +18 -0
- package/build/utils/auth-context.js.map +1 -1
- package/build/utils/case-enums.d.ts +5 -3
- package/build/utils/case-enums.d.ts.map +1 -1
- package/build/utils/case-enums.js +77 -59
- package/build/utils/case-enums.js.map +1 -1
- package/build/utils/output-schemas.d.ts +16 -0
- package/build/utils/output-schemas.d.ts.map +1 -0
- package/build/utils/output-schemas.js +82 -0
- package/build/utils/output-schemas.js.map +1 -0
- package/build/utils/registry.d.ts +42 -9
- package/build/utils/registry.d.ts.map +1 -1
- package/build/utils/registry.js +64 -11
- package/build/utils/registry.js.map +1 -1
- package/build/utils/response-shape.d.ts +23 -0
- package/build/utils/response-shape.d.ts.map +1 -0
- package/build/utils/response-shape.js +73 -0
- package/build/utils/response-shape.js.map +1 -0
- package/build/utils/rich-response.d.ts +58 -0
- package/build/utils/rich-response.d.ts.map +1 -0
- package/build/utils/rich-response.js +93 -0
- package/build/utils/rich-response.js.map +1 -0
- package/build/utils/server-context.d.ts +65 -0
- package/build/utils/server-context.d.ts.map +1 -0
- package/build/utils/server-context.js +67 -0
- package/build/utils/server-context.js.map +1 -0
- package/build/version.d.ts +1 -1
- package/build/version.js +1 -1
- package/docs/connect.md +81 -0
- package/docs/migration.md +351 -0
- package/docs/self-run.md +270 -0
- package/docs/tools.md +111 -0
- package/docs/troubleshooting.md +144 -0
- package/package.json +11 -2
- package/server.json +8 -2
- package/build/operations/attachments.d.ts +0 -8
- package/build/operations/attachments.d.ts.map +0 -1
- package/build/operations/attachments.js +0 -146
- package/build/operations/attachments.js.map +0 -1
- package/build/operations/authors.d.ts +0 -8
- package/build/operations/authors.d.ts.map +0 -1
- package/build/operations/authors.js +0 -70
- package/build/operations/authors.js.map +0 -1
- package/build/operations/cases.d.ts +0 -8
- package/build/operations/cases.d.ts.map +0 -1
- package/build/operations/cases.js +0 -370
- package/build/operations/cases.js.map +0 -1
- package/build/operations/configurations.d.ts +0 -10
- package/build/operations/configurations.d.ts.map +0 -1
- package/build/operations/configurations.js +0 -125
- package/build/operations/configurations.js.map +0 -1
- package/build/operations/custom-fields.d.ts +0 -8
- package/build/operations/custom-fields.d.ts.map +0 -1
- package/build/operations/custom-fields.js +0 -172
- package/build/operations/custom-fields.js.map +0 -1
- package/build/operations/defects.d.ts +0 -8
- package/build/operations/defects.d.ts.map +0 -1
- package/build/operations/defects.js +0 -256
- package/build/operations/defects.js.map +0 -1
- package/build/operations/environments.d.ts +0 -8
- package/build/operations/environments.d.ts.map +0 -1
- package/build/operations/environments.js +0 -154
- package/build/operations/environments.js.map +0 -1
- package/build/operations/milestones.d.ts +0 -8
- package/build/operations/milestones.d.ts.map +0 -1
- package/build/operations/milestones.js +0 -155
- package/build/operations/milestones.js.map +0 -1
- package/build/operations/plans.d.ts +0 -8
- package/build/operations/plans.d.ts.map +0 -1
- package/build/operations/plans.js +0 -158
- package/build/operations/plans.js.map +0 -1
- package/build/operations/projects.d.ts +0 -8
- package/build/operations/projects.d.ts.map +0 -1
- package/build/operations/projects.js +0 -192
- package/build/operations/projects.js.map +0 -1
- package/build/operations/results.d.ts +0 -8
- package/build/operations/results.d.ts.map +0 -1
- package/build/operations/results.js +0 -214
- package/build/operations/results.js.map +0 -1
- package/build/operations/runs.d.ts +0 -8
- package/build/operations/runs.d.ts.map +0 -1
- package/build/operations/runs.js +0 -224
- package/build/operations/runs.js.map +0 -1
- package/build/operations/search.d.ts.map +0 -1
- package/build/operations/search.js.map +0 -1
- package/build/operations/shared-parameters.d.ts +0 -10
- package/build/operations/shared-parameters.d.ts.map +0 -1
- package/build/operations/shared-parameters.js +0 -93
- package/build/operations/shared-parameters.js.map +0 -1
- package/build/operations/shared-steps.d.ts +0 -8
- package/build/operations/shared-steps.d.ts.map +0 -1
- package/build/operations/shared-steps.js +0 -160
- package/build/operations/shared-steps.js.map +0 -1
- package/build/operations/suites.d.ts +0 -8
- package/build/operations/suites.d.ts.map +0 -1
- package/build/operations/suites.js +0 -169
- package/build/operations/suites.js.map +0 -1
- package/build/operations/system-fields.d.ts +0 -10
- package/build/operations/system-fields.d.ts.map +0 -1
- package/build/operations/system-fields.js +0 -47
- package/build/operations/system-fields.js.map +0 -1
- package/build/operations/users.d.ts +0 -10
- package/build/operations/users.d.ts.map +0 -1
- package/build/operations/users.js +0 -78
- package/build/operations/users.js.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash an API token into a stable, non-reversible tenant identifier.
|
|
3
|
+
*
|
|
4
|
+
* Why not raw tokens: tokens placed in Map keys land in heap snapshots, error
|
|
5
|
+
* dumps, and debug logs. A sha256 truncation gives us a stable key that is
|
|
6
|
+
* useless to anyone who sees it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function hashToken(token: string): string;
|
|
9
|
+
export interface CacheKeyParts {
|
|
10
|
+
host: string;
|
|
11
|
+
tenantId: string;
|
|
12
|
+
resource: string;
|
|
13
|
+
scope?: string;
|
|
14
|
+
params?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a composite cache key:
|
|
18
|
+
* v1:{host}:{tenantId}:{resource}:{scope}:{paramsJson}
|
|
19
|
+
*
|
|
20
|
+
* The `v1` prefix lets us wholesale-invalidate old entries on schema change.
|
|
21
|
+
* Params are JSON-stringified with sorted keys so equivalent param objects
|
|
22
|
+
* collapse to the same key.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildCacheKey(parts: CacheKeyParts): string;
|
|
25
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/cache/keys.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAI1D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Hash an API token into a stable, non-reversible tenant identifier.
|
|
4
|
+
*
|
|
5
|
+
* Why not raw tokens: tokens placed in Map keys land in heap snapshots, error
|
|
6
|
+
* dumps, and debug logs. A sha256 truncation gives us a stable key that is
|
|
7
|
+
* useless to anyone who sees it.
|
|
8
|
+
*/
|
|
9
|
+
export function hashToken(token) {
|
|
10
|
+
if (!token) {
|
|
11
|
+
throw new Error('token must be non-empty');
|
|
12
|
+
}
|
|
13
|
+
return createHash('sha256').update(token).digest('hex').slice(0, 32);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a composite cache key:
|
|
17
|
+
* v1:{host}:{tenantId}:{resource}:{scope}:{paramsJson}
|
|
18
|
+
*
|
|
19
|
+
* The `v1` prefix lets us wholesale-invalidate old entries on schema change.
|
|
20
|
+
* Params are JSON-stringified with sorted keys so equivalent param objects
|
|
21
|
+
* collapse to the same key.
|
|
22
|
+
*/
|
|
23
|
+
export function buildCacheKey(parts) {
|
|
24
|
+
const scope = parts.scope ?? '';
|
|
25
|
+
const params = parts.params ? stableStringify(parts.params) : '';
|
|
26
|
+
return `v1:${parts.host}:${parts.tenantId}:${parts.resource}:${scope}:${params}`;
|
|
27
|
+
}
|
|
28
|
+
function stableStringify(obj) {
|
|
29
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
30
|
+
const pairs = {};
|
|
31
|
+
for (const k of sortedKeys) {
|
|
32
|
+
pairs[k] = obj[k];
|
|
33
|
+
}
|
|
34
|
+
return JSON.stringify(pairs);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/cache/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAUD;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAoB;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,OAAO,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;AACnF,CAAC;AAED,SAAS,eAAe,CAAC,GAA4B;IACnD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CacheBackend, InvalidationBus } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Composite cache with L1 (process-local) + L2 (shared, typically Redis) and
|
|
4
|
+
* an InvalidationBus for cross-instance L1 coherence.
|
|
5
|
+
*
|
|
6
|
+
* Read: L1 → L2 → miss. L2 hits are promoted to L1 with the *remaining* TTL,
|
|
7
|
+
* not a fresh TTL — this prevents resurrecting near-expired data forever
|
|
8
|
+
* after a chain of L2→L1 promotions across a fleet.
|
|
9
|
+
*
|
|
10
|
+
* Write: parallel L1 + L2. Values written to L2 are wrapped in an envelope
|
|
11
|
+
* `{v, exp}` so `exp` (absolute time) survives TTL-bounded native storage
|
|
12
|
+
* and can be used to compute promotion TTL.
|
|
13
|
+
*
|
|
14
|
+
* Invalidation: local L1 + L2 delete, then `bus.publish(prefix)`. Other
|
|
15
|
+
* instances' subscribers evict the matching L1 entries.
|
|
16
|
+
*/
|
|
17
|
+
export declare class LayeredCache implements CacheBackend {
|
|
18
|
+
private readonly l1;
|
|
19
|
+
private readonly l2;
|
|
20
|
+
private readonly bus;
|
|
21
|
+
private readonly subscribeReady;
|
|
22
|
+
private readonly selfPublishing;
|
|
23
|
+
constructor(l1: CacheBackend, l2: CacheBackend, bus: InvalidationBus);
|
|
24
|
+
ready(): Promise<void>;
|
|
25
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
26
|
+
set<T>(key: string, value: T, ttlMs: number): Promise<void>;
|
|
27
|
+
delete(key: string): Promise<void>;
|
|
28
|
+
deleteByPrefix(prefix: string): Promise<void>;
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=layered.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layered.d.ts","sourceRoot":"","sources":["../../src/cache/layered.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAQ3D;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,YAAW,YAAY;IAK7C,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;gBAGjC,EAAE,EAAE,YAAY,EAChB,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,eAAe;IAQjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAc3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite cache with L1 (process-local) + L2 (shared, typically Redis) and
|
|
3
|
+
* an InvalidationBus for cross-instance L1 coherence.
|
|
4
|
+
*
|
|
5
|
+
* Read: L1 → L2 → miss. L2 hits are promoted to L1 with the *remaining* TTL,
|
|
6
|
+
* not a fresh TTL — this prevents resurrecting near-expired data forever
|
|
7
|
+
* after a chain of L2→L1 promotions across a fleet.
|
|
8
|
+
*
|
|
9
|
+
* Write: parallel L1 + L2. Values written to L2 are wrapped in an envelope
|
|
10
|
+
* `{v, exp}` so `exp` (absolute time) survives TTL-bounded native storage
|
|
11
|
+
* and can be used to compute promotion TTL.
|
|
12
|
+
*
|
|
13
|
+
* Invalidation: local L1 + L2 delete, then `bus.publish(prefix)`. Other
|
|
14
|
+
* instances' subscribers evict the matching L1 entries.
|
|
15
|
+
*/
|
|
16
|
+
export class LayeredCache {
|
|
17
|
+
l1;
|
|
18
|
+
l2;
|
|
19
|
+
bus;
|
|
20
|
+
subscribeReady;
|
|
21
|
+
selfPublishing = new Set();
|
|
22
|
+
constructor(l1, l2, bus) {
|
|
23
|
+
this.l1 = l1;
|
|
24
|
+
this.l2 = l2;
|
|
25
|
+
this.bus = bus;
|
|
26
|
+
this.subscribeReady = this.bus.subscribe((prefix) => {
|
|
27
|
+
if (this.selfPublishing.has(prefix))
|
|
28
|
+
return;
|
|
29
|
+
this.l1.deleteByPrefix(prefix).catch(() => { });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async ready() {
|
|
33
|
+
await this.subscribeReady;
|
|
34
|
+
}
|
|
35
|
+
async get(key) {
|
|
36
|
+
const fromL1 = await this.l1.get(key);
|
|
37
|
+
if (fromL1 !== undefined)
|
|
38
|
+
return fromL1;
|
|
39
|
+
const env = await this.l2.get(key);
|
|
40
|
+
if (env === undefined || env.v === undefined || env.exp === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
const remaining = env.exp - Date.now();
|
|
43
|
+
if (remaining <= 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
await this.l1.set(key, env.v, remaining);
|
|
46
|
+
return env.v;
|
|
47
|
+
}
|
|
48
|
+
async set(key, value, ttlMs) {
|
|
49
|
+
const envelope = { v: value, exp: Date.now() + ttlMs };
|
|
50
|
+
const results = await Promise.allSettled([
|
|
51
|
+
this.l1.set(key, value, ttlMs),
|
|
52
|
+
this.l2.set(key, envelope, ttlMs),
|
|
53
|
+
]);
|
|
54
|
+
const l1Result = results[0];
|
|
55
|
+
if (l1Result.status === 'rejected')
|
|
56
|
+
throw l1Result.reason;
|
|
57
|
+
}
|
|
58
|
+
async delete(key) {
|
|
59
|
+
await Promise.allSettled([this.l1.delete(key), this.l2.delete(key)]);
|
|
60
|
+
this.selfPublishing.add(key);
|
|
61
|
+
try {
|
|
62
|
+
await this.bus.publish(key);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Swallow — coherence drift is bounded by L1 TTL
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
this.selfPublishing.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async deleteByPrefix(prefix) {
|
|
72
|
+
await Promise.allSettled([this.l1.deleteByPrefix(prefix), this.l2.deleteByPrefix(prefix)]);
|
|
73
|
+
this.selfPublishing.add(prefix);
|
|
74
|
+
try {
|
|
75
|
+
await this.bus.publish(prefix);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Swallow
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
this.selfPublishing.delete(prefix);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async close() {
|
|
85
|
+
await Promise.allSettled([this.l1.close(), this.l2.close(), this.bus.close()]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=layered.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layered.js","sourceRoot":"","sources":["../../src/cache/layered.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CacheBackend } from './types.js';
|
|
2
|
+
export interface InMemoryCacheOptions {
|
|
3
|
+
/** Global cap across all tenants. */
|
|
4
|
+
maxEntries: number;
|
|
5
|
+
/** Per-tenant cap. Inferred from the 3rd segment of the key (`v1:host:tenantId:...`). */
|
|
6
|
+
maxPerTenant: number;
|
|
7
|
+
/** Optional periodic sweep interval for defensive cleanup. Default: 60 seconds. */
|
|
8
|
+
sweepIntervalMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Process-local LRU cache with TTL and per-tenant cap.
|
|
12
|
+
*
|
|
13
|
+
* Tenant extraction: keys built by `buildCacheKey` have the shape
|
|
14
|
+
* `v1:{host}:{tenantId}:{resource}:{scope}:{paramsJson}`
|
|
15
|
+
* We parse out `tenantId` to enforce per-tenant quotas without adding another
|
|
16
|
+
* indirection layer.
|
|
17
|
+
*/
|
|
18
|
+
export declare class InMemoryCache implements CacheBackend {
|
|
19
|
+
private readonly store;
|
|
20
|
+
private readonly perTenantCount;
|
|
21
|
+
private readonly maxPerTenant;
|
|
22
|
+
private readonly sweepTimer?;
|
|
23
|
+
constructor(opts: InMemoryCacheOptions);
|
|
24
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
25
|
+
set<T>(key: string, value: T, ttlMs: number): Promise<void>;
|
|
26
|
+
delete(key: string): Promise<void>;
|
|
27
|
+
deleteByPrefix(prefix: string): Promise<void>;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
private sweep;
|
|
30
|
+
private decTenant;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/cache/memory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,MAAM,WAAW,oBAAoB;IACnC,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAOD;;;;;;;GAOG;AACH,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAiC;gBAEjD,IAAI,EAAE,oBAAoB;IAwBhC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAe3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe3D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,SAAS;CAWlB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
import { getMetrics } from './metrics.js';
|
|
3
|
+
/**
|
|
4
|
+
* Process-local LRU cache with TTL and per-tenant cap.
|
|
5
|
+
*
|
|
6
|
+
* Tenant extraction: keys built by `buildCacheKey` have the shape
|
|
7
|
+
* `v1:{host}:{tenantId}:{resource}:{scope}:{paramsJson}`
|
|
8
|
+
* We parse out `tenantId` to enforce per-tenant quotas without adding another
|
|
9
|
+
* indirection layer.
|
|
10
|
+
*/
|
|
11
|
+
export class InMemoryCache {
|
|
12
|
+
store;
|
|
13
|
+
perTenantCount = new Map();
|
|
14
|
+
maxPerTenant;
|
|
15
|
+
sweepTimer;
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.maxPerTenant = opts.maxPerTenant;
|
|
18
|
+
this.store = new LRUCache({
|
|
19
|
+
max: opts.maxEntries,
|
|
20
|
+
dispose: (_value, key, reason) => {
|
|
21
|
+
// Decrement on every disposal reason except overwrite ('set'):
|
|
22
|
+
// - 'evict' → LRU global cap exceeded, entry removed from a different tenant's pressure
|
|
23
|
+
// - 'delete' → explicit delete()/deleteByPrefix()
|
|
24
|
+
// - 'expire' → lru-cache's own TTL (we don't use it but it's still possible)
|
|
25
|
+
// - 'fetch' → we don't use fetchMethod, but cover for completeness
|
|
26
|
+
// - 'set' → overwrite: old value disposed, but key still exists with new value, count unchanged
|
|
27
|
+
if (reason !== 'set') {
|
|
28
|
+
this.decTenant(key);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const sweepMs = opts.sweepIntervalMs ?? 60_000;
|
|
33
|
+
if (sweepMs > 0) {
|
|
34
|
+
this.sweepTimer = setInterval(() => this.sweep(), sweepMs);
|
|
35
|
+
this.sweepTimer.unref?.();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async get(key) {
|
|
39
|
+
const entry = this.store.get(key);
|
|
40
|
+
if (!entry) {
|
|
41
|
+
getMetrics().incCounter('qase_mcp_cache_misses_total', { tier: 'l1' });
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (entry.expiresAt <= Date.now()) {
|
|
45
|
+
this.store.delete(key);
|
|
46
|
+
getMetrics().incCounter('qase_mcp_cache_misses_total', { tier: 'l1' });
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
getMetrics().incCounter('qase_mcp_cache_hits_total', { tier: 'l1' });
|
|
50
|
+
return entry.value;
|
|
51
|
+
}
|
|
52
|
+
async set(key, value, ttlMs) {
|
|
53
|
+
const existed = this.store.has(key);
|
|
54
|
+
const tenant = extractTenantId(key);
|
|
55
|
+
if (!existed && tenant) {
|
|
56
|
+
const current = this.perTenantCount.get(tenant) ?? 0;
|
|
57
|
+
if (current >= this.maxPerTenant) {
|
|
58
|
+
return; // silently drop — tenant is at quota
|
|
59
|
+
}
|
|
60
|
+
this.perTenantCount.set(tenant, current + 1);
|
|
61
|
+
}
|
|
62
|
+
this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
63
|
+
}
|
|
64
|
+
async delete(key) {
|
|
65
|
+
this.store.delete(key);
|
|
66
|
+
}
|
|
67
|
+
async deleteByPrefix(prefix) {
|
|
68
|
+
for (const key of this.store.keys()) {
|
|
69
|
+
if (key.startsWith(prefix)) {
|
|
70
|
+
this.store.delete(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async close() {
|
|
75
|
+
if (this.sweepTimer)
|
|
76
|
+
clearInterval(this.sweepTimer);
|
|
77
|
+
this.store.clear();
|
|
78
|
+
this.perTenantCount.clear();
|
|
79
|
+
}
|
|
80
|
+
sweep() {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
for (const [key, entry] of this.store.entries()) {
|
|
83
|
+
if (entry.expiresAt <= now) {
|
|
84
|
+
this.store.delete(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
decTenant(key) {
|
|
89
|
+
const tenant = extractTenantId(key);
|
|
90
|
+
if (!tenant)
|
|
91
|
+
return;
|
|
92
|
+
const current = this.perTenantCount.get(tenant);
|
|
93
|
+
if (!current)
|
|
94
|
+
return;
|
|
95
|
+
if (current <= 1) {
|
|
96
|
+
this.perTenantCount.delete(tenant);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.perTenantCount.set(tenant, current - 1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function extractTenantId(key) {
|
|
104
|
+
// key: v1:{host}:{tenantId}:...
|
|
105
|
+
const parts = key.split(':');
|
|
106
|
+
return parts.length >= 3 ? parts[2] : undefined;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/cache/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type Labels = Record<string, string>;
|
|
2
|
+
/**
|
|
3
|
+
* Minimal, dependency-free metrics registry.
|
|
4
|
+
*
|
|
5
|
+
* Supports Prometheus text-format rendering. Designed for a small fixed set
|
|
6
|
+
* of metrics (cache hits/misses/errors, CB state, entry gauges); not a
|
|
7
|
+
* general-purpose replacement for prom-client.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Metrics {
|
|
10
|
+
private readonly defs;
|
|
11
|
+
private readonly counters;
|
|
12
|
+
private readonly gauges;
|
|
13
|
+
registerCounter(name: string, help: string): void;
|
|
14
|
+
registerGauge(name: string, help: string): void;
|
|
15
|
+
incCounter(name: string, labels: Labels, by?: number): void;
|
|
16
|
+
getCounter(name: string, labels: Labels): number;
|
|
17
|
+
setGauge(name: string, labels: Labels, value: number): void;
|
|
18
|
+
getGauge(name: string, labels: Labels): number;
|
|
19
|
+
/**
|
|
20
|
+
* Render all registered metrics as Prometheus exposition text.
|
|
21
|
+
* Series from unregistered metric names are rendered too (with no HELP/TYPE
|
|
22
|
+
* lines) — this keeps `incCounter` safe to call even before `registerCounter`.
|
|
23
|
+
*/
|
|
24
|
+
renderPrometheus(): string;
|
|
25
|
+
private seriesKey;
|
|
26
|
+
}
|
|
27
|
+
export declare function getMetrics(): Metrics;
|
|
28
|
+
/** @internal — used by tests to reset the registry. */
|
|
29
|
+
export declare function resetMetricsForTest(): void;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/cache/metrics.ts"],"names":[],"mappings":"AAEA,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAOrC;;;;;;GAMG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAEjE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKjD,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,SAAI,GAAG,IAAI;IAQtD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIhD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAM3D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAI9C;;;;OAIG;IACH,gBAAgB,IAAI,MAAM;IAqB1B,OAAO,CAAC,SAAS;CAOlB;AAOD,wBAAgB,UAAU,IAAI,OAAO,CAMpC;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { formatLabels } from './prom-format.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal, dependency-free metrics registry.
|
|
4
|
+
*
|
|
5
|
+
* Supports Prometheus text-format rendering. Designed for a small fixed set
|
|
6
|
+
* of metrics (cache hits/misses/errors, CB state, entry gauges); not a
|
|
7
|
+
* general-purpose replacement for prom-client.
|
|
8
|
+
*/
|
|
9
|
+
export class Metrics {
|
|
10
|
+
defs = new Map();
|
|
11
|
+
counters = new Map();
|
|
12
|
+
gauges = new Map();
|
|
13
|
+
registerCounter(name, help) {
|
|
14
|
+
this.defs.set(name, { help, type: 'counter' });
|
|
15
|
+
if (!this.counters.has(name))
|
|
16
|
+
this.counters.set(name, new Map());
|
|
17
|
+
}
|
|
18
|
+
registerGauge(name, help) {
|
|
19
|
+
this.defs.set(name, { help, type: 'gauge' });
|
|
20
|
+
if (!this.gauges.has(name))
|
|
21
|
+
this.gauges.set(name, new Map());
|
|
22
|
+
}
|
|
23
|
+
incCounter(name, labels, by = 1) {
|
|
24
|
+
const series = this.counters.get(name) ??
|
|
25
|
+
this.counters.set(name, new Map()).get(name);
|
|
26
|
+
const key = this.seriesKey(labels);
|
|
27
|
+
series.set(key, (series.get(key) ?? 0) + by);
|
|
28
|
+
}
|
|
29
|
+
getCounter(name, labels) {
|
|
30
|
+
return this.counters.get(name)?.get(this.seriesKey(labels)) ?? 0;
|
|
31
|
+
}
|
|
32
|
+
setGauge(name, labels, value) {
|
|
33
|
+
const series = this.gauges.get(name) ?? this.gauges.set(name, new Map()).get(name);
|
|
34
|
+
series.set(this.seriesKey(labels), value);
|
|
35
|
+
}
|
|
36
|
+
getGauge(name, labels) {
|
|
37
|
+
return this.gauges.get(name)?.get(this.seriesKey(labels)) ?? 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Render all registered metrics as Prometheus exposition text.
|
|
41
|
+
* Series from unregistered metric names are rendered too (with no HELP/TYPE
|
|
42
|
+
* lines) — this keeps `incCounter` safe to call even before `registerCounter`.
|
|
43
|
+
*/
|
|
44
|
+
renderPrometheus() {
|
|
45
|
+
const lines = [];
|
|
46
|
+
const emit = (name, series, type) => {
|
|
47
|
+
const def = this.defs.get(name);
|
|
48
|
+
if (def) {
|
|
49
|
+
lines.push(`# HELP ${name} ${def.help}`);
|
|
50
|
+
lines.push(`# TYPE ${name} ${type}`);
|
|
51
|
+
}
|
|
52
|
+
for (const [labelKey, val] of series.entries()) {
|
|
53
|
+
const labels = labelKey ? JSON.parse(labelKey) : {};
|
|
54
|
+
lines.push(`${name}${formatLabels(labels)} ${val}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
for (const [name, series] of this.counters.entries())
|
|
58
|
+
emit(name, series, 'counter');
|
|
59
|
+
for (const [name, series] of this.gauges.entries())
|
|
60
|
+
emit(name, series, 'gauge');
|
|
61
|
+
return lines.join('\n') + '\n';
|
|
62
|
+
}
|
|
63
|
+
seriesKey(labels) {
|
|
64
|
+
const keys = Object.keys(labels);
|
|
65
|
+
if (keys.length === 0)
|
|
66
|
+
return '';
|
|
67
|
+
const sorted = {};
|
|
68
|
+
for (const k of keys.sort())
|
|
69
|
+
sorted[k] = labels[k];
|
|
70
|
+
return JSON.stringify(sorted);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Process-wide singleton accessor. Reset only from tests.
|
|
75
|
+
*/
|
|
76
|
+
let instance = null;
|
|
77
|
+
export function getMetrics() {
|
|
78
|
+
if (!instance) {
|
|
79
|
+
instance = new Metrics();
|
|
80
|
+
registerDefaults(instance);
|
|
81
|
+
}
|
|
82
|
+
return instance;
|
|
83
|
+
}
|
|
84
|
+
/** @internal — used by tests to reset the registry. */
|
|
85
|
+
export function resetMetricsForTest() {
|
|
86
|
+
instance = null;
|
|
87
|
+
}
|
|
88
|
+
function registerDefaults(m) {
|
|
89
|
+
m.registerCounter('qase_mcp_cache_hits_total', 'Cache hits by tier (l1 or l2)');
|
|
90
|
+
m.registerCounter('qase_mcp_cache_misses_total', 'Cache misses by tier');
|
|
91
|
+
m.registerCounter('qase_mcp_cache_errors_total', 'Cache errors by tier');
|
|
92
|
+
m.registerGauge('qase_mcp_cache_entries', 'Current cache entries by tier');
|
|
93
|
+
m.registerGauge('qase_mcp_circuit_breaker_state', 'Circuit breaker state (0=closed, 1=half_open, 2=open) by name');
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/cache/metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a label object into the Prometheus `{k="v",…}` format.
|
|
3
|
+
* Label values are escaped per the Prometheus text format spec:
|
|
4
|
+
* `\` → `\\`, `"` → `\"`, `\n` → `\n`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatLabels(labels: Record<string, string>): string;
|
|
7
|
+
//# sourceMappingURL=prom-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prom-format.d.ts","sourceRoot":"","sources":["../../src/cache/prom-format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAKnE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a label object into the Prometheus `{k="v",…}` format.
|
|
3
|
+
* Label values are escaped per the Prometheus text format spec:
|
|
4
|
+
* `\` → `\\`, `"` → `\"`, `\n` → `\n`.
|
|
5
|
+
*/
|
|
6
|
+
export function formatLabels(labels) {
|
|
7
|
+
const keys = Object.keys(labels);
|
|
8
|
+
if (keys.length === 0)
|
|
9
|
+
return '';
|
|
10
|
+
const parts = keys.sort().map((k) => `${k}="${escapeLabelValue(labels[k])}"`);
|
|
11
|
+
return `{${parts.join(',')}}`;
|
|
12
|
+
}
|
|
13
|
+
function escapeLabelValue(v) {
|
|
14
|
+
return v.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=prom-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prom-format.js","sourceRoot":"","sources":["../../src/cache/prom-format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAA8B;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9E,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAChC,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { InvalidationBus } from './types.js';
|
|
2
|
+
export interface RedisLikePub {
|
|
3
|
+
publish(channel: string, message: string): Promise<number>;
|
|
4
|
+
quit(): Promise<'OK'>;
|
|
5
|
+
}
|
|
6
|
+
export interface RedisLikeSub {
|
|
7
|
+
subscribe(channel: string): Promise<number>;
|
|
8
|
+
on(event: 'message', handler: (channel: string, message: string) => void): this;
|
|
9
|
+
quit(): Promise<'OK'>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* InvalidationBus over Redis Pub/Sub.
|
|
13
|
+
*
|
|
14
|
+
* Message format: `{"pattern":"..."}` (JSON). Malformed messages are dropped
|
|
15
|
+
* silently — we never throw on receive so a single bad publisher cannot
|
|
16
|
+
* poison the bus for every subscriber.
|
|
17
|
+
*/
|
|
18
|
+
export declare class RedisInvalidationBus implements InvalidationBus {
|
|
19
|
+
private readonly pub;
|
|
20
|
+
private readonly sub;
|
|
21
|
+
private readonly channel;
|
|
22
|
+
private handlers;
|
|
23
|
+
private subscribed;
|
|
24
|
+
constructor(pub: RedisLikePub, sub: RedisLikeSub, channel: string);
|
|
25
|
+
publish(prefix: string): Promise<void>;
|
|
26
|
+
subscribe(handler: (pattern: string) => void): Promise<void>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=redis-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-bus.d.ts","sourceRoot":"","sources":["../../src/cache/redis-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAChF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAMD;;;;;;GAMG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAKxD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,UAAU,CAAS;gBAGR,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,MAAM;IAG5B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InvalidationBus over Redis Pub/Sub.
|
|
3
|
+
*
|
|
4
|
+
* Message format: `{"pattern":"..."}` (JSON). Malformed messages are dropped
|
|
5
|
+
* silently — we never throw on receive so a single bad publisher cannot
|
|
6
|
+
* poison the bus for every subscriber.
|
|
7
|
+
*/
|
|
8
|
+
export class RedisInvalidationBus {
|
|
9
|
+
pub;
|
|
10
|
+
sub;
|
|
11
|
+
channel;
|
|
12
|
+
handlers = [];
|
|
13
|
+
subscribed = false;
|
|
14
|
+
constructor(pub, sub, channel) {
|
|
15
|
+
this.pub = pub;
|
|
16
|
+
this.sub = sub;
|
|
17
|
+
this.channel = channel;
|
|
18
|
+
}
|
|
19
|
+
async publish(prefix) {
|
|
20
|
+
const env = { pattern: prefix };
|
|
21
|
+
await this.pub.publish(this.channel, JSON.stringify(env));
|
|
22
|
+
}
|
|
23
|
+
async subscribe(handler) {
|
|
24
|
+
this.handlers.push(handler);
|
|
25
|
+
if (this.subscribed)
|
|
26
|
+
return;
|
|
27
|
+
this.subscribed = true;
|
|
28
|
+
this.sub.on('message', (channel, message) => {
|
|
29
|
+
if (channel !== this.channel)
|
|
30
|
+
return;
|
|
31
|
+
let env;
|
|
32
|
+
try {
|
|
33
|
+
env = JSON.parse(message);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (typeof env?.pattern !== 'string')
|
|
39
|
+
return;
|
|
40
|
+
for (const h of this.handlers)
|
|
41
|
+
h(env.pattern);
|
|
42
|
+
});
|
|
43
|
+
await this.sub.subscribe(this.channel);
|
|
44
|
+
}
|
|
45
|
+
async close() {
|
|
46
|
+
this.handlers = [];
|
|
47
|
+
await Promise.allSettled([this.pub.quit(), this.sub.quit()]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=redis-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-bus.js","sourceRoot":"","sources":["../../src/cache/redis-bus.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,OAAO,oBAAoB;IAKZ;IACA;IACA;IANX,QAAQ,GAAqC,EAAE,CAAC;IAChD,UAAU,GAAG,KAAK,CAAC;IAE3B,YACmB,GAAiB,EACjB,GAAiB,EACjB,OAAe;QAFf,QAAG,GAAH,GAAG,CAAc;QACjB,QAAG,GAAH,GAAG,CAAc;QACjB,YAAO,GAAP,OAAO,CAAQ;IAC/B,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,GAAG,GAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QACtD,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,OAAkC;QAChD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;YAC1C,IAAI,OAAO,KAAK,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrC,IAAI,GAAyB,CAAC;YAC9B,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YACD,IAAI,OAAO,GAAG,EAAE,OAAO,KAAK,QAAQ;gBAAE,OAAO;YAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ;gBAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QACnB,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;CACF"}
|