@miragon/pulse-analytics-client 0.0.0-stage → 0.21.0

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.
Files changed (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +14 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/metric-names.d.ts +43 -0
  8. package/dist/metric-names.d.ts.map +1 -0
  9. package/dist/metric-names.js +53 -0
  10. package/dist/metric-names.js.map +1 -0
  11. package/dist/metrics-contract-labels.test.d.ts +2 -0
  12. package/dist/metrics-contract-labels.test.d.ts.map +1 -0
  13. package/dist/metrics-contract-labels.test.js +169 -0
  14. package/dist/metrics-contract-labels.test.js.map +1 -0
  15. package/dist/metrics-contract.test.d.ts +2 -0
  16. package/dist/metrics-contract.test.d.ts.map +1 -0
  17. package/dist/metrics-contract.test.js +247 -0
  18. package/dist/metrics-contract.test.js.map +1 -0
  19. package/dist/prometheus-client.test.d.ts +2 -0
  20. package/dist/prometheus-client.test.d.ts.map +1 -0
  21. package/dist/prometheus-client.test.js +265 -0
  22. package/dist/prometheus-client.test.js.map +1 -0
  23. package/dist/prometheus.d.ts +100 -0
  24. package/dist/prometheus.d.ts.map +1 -0
  25. package/dist/prometheus.js +295 -0
  26. package/dist/prometheus.js.map +1 -0
  27. package/dist/prometheus.test.d.ts +2 -0
  28. package/dist/prometheus.test.d.ts.map +1 -0
  29. package/dist/prometheus.test.js +54 -0
  30. package/dist/prometheus.test.js.map +1 -0
  31. package/dist/promql-labels.test-support.d.ts +53 -0
  32. package/dist/promql-labels.test-support.d.ts.map +1 -0
  33. package/dist/promql-labels.test-support.js +237 -0
  34. package/dist/promql-labels.test-support.js.map +1 -0
  35. package/dist/promql-labels.test.d.ts +2 -0
  36. package/dist/promql-labels.test.d.ts.map +1 -0
  37. package/dist/promql-labels.test.js +190 -0
  38. package/dist/promql-labels.test.js.map +1 -0
  39. package/dist/promql-parse.test-support.d.ts +57 -0
  40. package/dist/promql-parse.test-support.d.ts.map +1 -0
  41. package/dist/promql-parse.test-support.js +284 -0
  42. package/dist/promql-parse.test-support.js.map +1 -0
  43. package/dist/promql-reading-rules.test-support.d.ts +29 -0
  44. package/dist/promql-reading-rules.test-support.d.ts.map +1 -0
  45. package/dist/promql-reading-rules.test-support.js +79 -0
  46. package/dist/promql-reading-rules.test-support.js.map +1 -0
  47. package/dist/promql-reading-rules.test.d.ts +2 -0
  48. package/dist/promql-reading-rules.test.d.ts.map +1 -0
  49. package/dist/promql-reading-rules.test.js +61 -0
  50. package/dist/promql-reading-rules.test.js.map +1 -0
  51. package/dist/promql-sources.test-support.d.ts +29 -0
  52. package/dist/promql-sources.test-support.d.ts.map +1 -0
  53. package/dist/promql-sources.test-support.js +112 -0
  54. package/dist/promql-sources.test-support.js.map +1 -0
  55. package/dist/promql-sources.test.d.ts +2 -0
  56. package/dist/promql-sources.test.d.ts.map +1 -0
  57. package/dist/promql-sources.test.js +93 -0
  58. package/dist/promql-sources.test.js.map +1 -0
  59. package/dist/queries/as-of.test.d.ts +2 -0
  60. package/dist/queries/as-of.test.d.ts.map +1 -0
  61. package/dist/queries/as-of.test.js +80 -0
  62. package/dist/queries/as-of.test.js.map +1 -0
  63. package/dist/queries/cluster-compare.d.ts +62 -0
  64. package/dist/queries/cluster-compare.d.ts.map +1 -0
  65. package/dist/queries/cluster-compare.js +70 -0
  66. package/dist/queries/cluster-compare.js.map +1 -0
  67. package/dist/queries/cluster-compare.test.d.ts +2 -0
  68. package/dist/queries/cluster-compare.test.d.ts.map +1 -0
  69. package/dist/queries/cluster-compare.test.js +81 -0
  70. package/dist/queries/cluster-compare.test.js.map +1 -0
  71. package/dist/queries/dashboard.d.ts +60 -0
  72. package/dist/queries/dashboard.d.ts.map +1 -0
  73. package/dist/queries/dashboard.js +292 -0
  74. package/dist/queries/dashboard.js.map +1 -0
  75. package/dist/queries/dashboard.test.d.ts +2 -0
  76. package/dist/queries/dashboard.test.d.ts.map +1 -0
  77. package/dist/queries/dashboard.test.js +423 -0
  78. package/dist/queries/dashboard.test.js.map +1 -0
  79. package/dist/queries/element.d.ts +68 -0
  80. package/dist/queries/element.d.ts.map +1 -0
  81. package/dist/queries/element.js +98 -0
  82. package/dist/queries/element.js.map +1 -0
  83. package/dist/queries/element.test.d.ts +2 -0
  84. package/dist/queries/element.test.d.ts.map +1 -0
  85. package/dist/queries/element.test.js +105 -0
  86. package/dist/queries/element.test.js.map +1 -0
  87. package/dist/queries/engine-compare.d.ts +52 -0
  88. package/dist/queries/engine-compare.d.ts.map +1 -0
  89. package/dist/queries/engine-compare.js +57 -0
  90. package/dist/queries/engine-compare.js.map +1 -0
  91. package/dist/queries/engine-compare.test.d.ts +2 -0
  92. package/dist/queries/engine-compare.test.d.ts.map +1 -0
  93. package/dist/queries/engine-compare.test.js +159 -0
  94. package/dist/queries/engine-compare.test.js.map +1 -0
  95. package/dist/queries/engine-landscape.d.ts +92 -0
  96. package/dist/queries/engine-landscape.d.ts.map +1 -0
  97. package/dist/queries/engine-landscape.js +183 -0
  98. package/dist/queries/engine-landscape.js.map +1 -0
  99. package/dist/queries/engine-landscape.test.d.ts +2 -0
  100. package/dist/queries/engine-landscape.test.d.ts.map +1 -0
  101. package/dist/queries/engine-landscape.test.js +171 -0
  102. package/dist/queries/engine-landscape.test.js.map +1 -0
  103. package/dist/queries/failures.d.ts +33 -0
  104. package/dist/queries/failures.d.ts.map +1 -0
  105. package/dist/queries/failures.js +31 -0
  106. package/dist/queries/failures.js.map +1 -0
  107. package/dist/queries/health.d.ts +77 -0
  108. package/dist/queries/health.d.ts.map +1 -0
  109. package/dist/queries/health.js +126 -0
  110. package/dist/queries/health.js.map +1 -0
  111. package/dist/queries/health.test.d.ts +2 -0
  112. package/dist/queries/health.test.d.ts.map +1 -0
  113. package/dist/queries/health.test.js +187 -0
  114. package/dist/queries/health.test.js.map +1 -0
  115. package/dist/queries/helpers.d.ts +184 -0
  116. package/dist/queries/helpers.d.ts.map +1 -0
  117. package/dist/queries/helpers.js +219 -0
  118. package/dist/queries/helpers.js.map +1 -0
  119. package/dist/queries/helpers.test.d.ts +2 -0
  120. package/dist/queries/helpers.test.d.ts.map +1 -0
  121. package/dist/queries/helpers.test.js +53 -0
  122. package/dist/queries/helpers.test.js.map +1 -0
  123. package/dist/queries/index.d.ts +19 -0
  124. package/dist/queries/index.d.ts.map +1 -0
  125. package/dist/queries/index.js +10 -0
  126. package/dist/queries/index.js.map +1 -0
  127. package/dist/queries/performance.d.ts +103 -0
  128. package/dist/queries/performance.d.ts.map +1 -0
  129. package/dist/queries/performance.js +173 -0
  130. package/dist/queries/performance.js.map +1 -0
  131. package/dist/queries/performance.test.d.ts +2 -0
  132. package/dist/queries/performance.test.d.ts.map +1 -0
  133. package/dist/queries/performance.test.js +265 -0
  134. package/dist/queries/performance.test.js.map +1 -0
  135. package/dist/queries/version-compare.d.ts +41 -0
  136. package/dist/queries/version-compare.d.ts.map +1 -0
  137. package/dist/queries/version-compare.js +44 -0
  138. package/dist/queries/version-compare.js.map +1 -0
  139. package/dist/queries/version-compare.test.d.ts +2 -0
  140. package/dist/queries/version-compare.test.d.ts.map +1 -0
  141. package/dist/queries/version-compare.test.js +162 -0
  142. package/dist/queries/version-compare.test.js.map +1 -0
  143. package/dist/queries/windows.d.ts +40 -0
  144. package/dist/queries/windows.d.ts.map +1 -0
  145. package/dist/queries/windows.js +56 -0
  146. package/dist/queries/windows.js.map +1 -0
  147. package/dist/query-honesty.test.d.ts +2 -0
  148. package/dist/query-honesty.test.d.ts.map +1 -0
  149. package/dist/query-honesty.test.js +294 -0
  150. package/dist/query-honesty.test.js.map +1 -0
  151. package/dist/query-scenarios.test-support.d.ts +49 -0
  152. package/dist/query-scenarios.test-support.d.ts.map +1 -0
  153. package/dist/query-scenarios.test-support.js +146 -0
  154. package/dist/query-scenarios.test-support.js.map +1 -0
  155. package/dist/schemas/cluster-compare.d.ts +11 -0
  156. package/dist/schemas/cluster-compare.d.ts.map +1 -0
  157. package/dist/schemas/cluster-compare.js +35 -0
  158. package/dist/schemas/cluster-compare.js.map +1 -0
  159. package/dist/schemas/engine-compare.d.ts +10 -0
  160. package/dist/schemas/engine-compare.d.ts.map +1 -0
  161. package/dist/schemas/engine-compare.js +27 -0
  162. package/dist/schemas/engine-compare.js.map +1 -0
  163. package/dist/schemas/engine-landscape.d.ts +5 -0
  164. package/dist/schemas/engine-landscape.d.ts.map +1 -0
  165. package/dist/schemas/engine-landscape.js +8 -0
  166. package/dist/schemas/engine-landscape.js.map +1 -0
  167. package/dist/schemas/failures.d.ts +8 -0
  168. package/dist/schemas/failures.d.ts.map +1 -0
  169. package/dist/schemas/failures.js +9 -0
  170. package/dist/schemas/failures.js.map +1 -0
  171. package/dist/schemas/health.d.ts +5 -0
  172. package/dist/schemas/health.d.ts.map +1 -0
  173. package/dist/schemas/health.js +6 -0
  174. package/dist/schemas/health.js.map +1 -0
  175. package/dist/schemas/index.d.ts +10 -0
  176. package/dist/schemas/index.d.ts.map +1 -0
  177. package/dist/schemas/index.js +10 -0
  178. package/dist/schemas/index.js.map +1 -0
  179. package/dist/schemas/path.d.ts +15 -0
  180. package/dist/schemas/path.d.ts.map +1 -0
  181. package/dist/schemas/path.js +21 -0
  182. package/dist/schemas/path.js.map +1 -0
  183. package/dist/schemas/performance.d.ts +23 -0
  184. package/dist/schemas/performance.d.ts.map +1 -0
  185. package/dist/schemas/performance.js +21 -0
  186. package/dist/schemas/performance.js.map +1 -0
  187. package/dist/schemas/shared.d.ts +34 -0
  188. package/dist/schemas/shared.d.ts.map +1 -0
  189. package/dist/schemas/shared.js +34 -0
  190. package/dist/schemas/shared.js.map +1 -0
  191. package/dist/schemas/version-compare.d.ts +10 -0
  192. package/dist/schemas/version-compare.d.ts.map +1 -0
  193. package/dist/schemas/version-compare.js +25 -0
  194. package/dist/schemas/version-compare.js.map +1 -0
  195. package/dist/widgets.d.ts +138 -0
  196. package/dist/widgets.d.ts.map +1 -0
  197. package/dist/widgets.js +2 -0
  198. package/dist/widgets.js.map +1 -0
  199. package/metrics-contract.json +174 -0
  200. package/package.json +72 -3
@@ -0,0 +1,100 @@
1
+ export interface PrometheusConfig {
2
+ /**
3
+ * Base URL of the Prometheus HTTP API. Userinfo (`https://user:pass@host`)
4
+ * is moved into a Basic `Authorization` header — fetch rejects credentialed
5
+ * URLs — and never appears in an error message.
6
+ */
7
+ url: string;
8
+ /** Bearer token (`Authorization: Bearer …`); exclusive with basic auth. */
9
+ bearerToken?: string;
10
+ /** Basic-auth user (with `password`); exclusive with `bearerToken` and URL userinfo. */
11
+ username?: string;
12
+ password?: string;
13
+ /** Extra request headers, e.g. a tenant header (`X-Scope-OrgID`) for Mimir/Cortex/Thanos. */
14
+ headers?: Record<string, string>;
15
+ /** Per-query deadline in ms (default {@link DEFAULT_PROMETHEUS_TIMEOUT_MS}). */
16
+ timeoutMs?: number;
17
+ }
18
+ /** Default per-query deadline (`timeoutMs`). */
19
+ export declare const DEFAULT_PROMETHEUS_TIMEOUT_MS = 30000;
20
+ /** One labeled sample from a Prometheus instant query. */
21
+ export interface PromSample {
22
+ metric: Record<string, string>;
23
+ value: number;
24
+ }
25
+ /** Per-query options. */
26
+ export interface PrometheusQueryOptions {
27
+ /** Aborts the query (combined with the client's deadline), e.g. an MCP request's `ctx.signal`. */
28
+ signal?: AbortSignal;
29
+ }
30
+ export interface PrometheusClient {
31
+ /** Run an instant PromQL query and return the labeled samples (NaN/∅ dropped). */
32
+ instant(query: string, options?: PrometheusQueryOptions): Promise<PromSample[]>;
33
+ }
34
+ /**
35
+ * Minimal Prometheus HTTP API client. The analytics queries are metric-first:
36
+ * they issue instant PromQL queries (`/api/v1/query`) whose range windows carry
37
+ * the time period, and map the labeled samples into the analytics row shapes.
38
+ *
39
+ * Every query runs under a deadline (`timeoutMs`, combined with an optional
40
+ * caller `signal`), and every failure is an `Error` whose message is safe to
41
+ * hand to the model: no URL, no credentials, upstream text truncated.
42
+ */
43
+ export declare function createPrometheusClient(config: PrometheusConfig): PrometheusClient;
44
+ /**
45
+ * A view of `client` whose queries honor the caller's `signal` (an MCP
46
+ * request's `ctx.signal`) on top of the client's own deadline — every
47
+ * Prometheus query is a read, so cancelling one is always safe. A per-call
48
+ * `signal` wins. Without a signal the client itself is returned.
49
+ */
50
+ export declare function withCallerSignal(client: PrometheusClient, signal: AbortSignal | undefined): PrometheusClient;
51
+ /** Escape a value for safe use inside a PromQL label matcher (`{k="<value>"}`). */
52
+ export declare function escapeLabelValue(value: string): string;
53
+ export type EngineFilterInput = string | string[] | undefined;
54
+ /**
55
+ * The engine ids a filter names, or `null` for no filter (every engine
56
+ * Prometheus holds). Results echo this so a reader always knows which engines
57
+ * an aggregate covers.
58
+ */
59
+ export declare function engineIdsOf(engine: EngineFilterInput): string[] | null;
60
+ /**
61
+ * Builds a PromQL label matcher fragment for the optional `engine_id` filter,
62
+ * e.g. `engine_id="prod-a"` or `engine_id=~"prod-a|prod-b"` — one id is an
63
+ * exact matcher whether it comes as a string or a one-element list (the
64
+ * analytics tools always resolve `engine` to a list). Returns `undefined`
65
+ * when no filter is set so the caller can aggregate across all engines.
66
+ */
67
+ export declare function engineMatcher(engine: EngineFilterInput): string | undefined;
68
+ /**
69
+ * Assembles a `{...}` PromQL label selector from individual matcher fragments,
70
+ * dropping empties. Returns `""` (no selector) when nothing is set.
71
+ */
72
+ export declare function selector(...matchers: Array<string | undefined>): string;
73
+ /**
74
+ * The look-back the analytics module assumes Prometheus retains
75
+ * (`--storage.tsdb.retention.time`, 30 days in the repo's stack). Every
76
+ * window — the rolling periods, the compare windows, explicit date ranges —
77
+ * is held inside `[now − RETENTION_DAYS, now]`: data older than that is gone,
78
+ * so a longer window would silently read partial or zero data.
79
+ */
80
+ export declare const RETENTION_DAYS = 30;
81
+ /**
82
+ * PromQL range windows for the analytics `period` inputs, capped at
83
+ * {@link RETENTION_DAYS}; longer look-backs would silently read partial/zero
84
+ * data, so they are not offered.
85
+ */
86
+ export declare const PERIOD_RANGE: {
87
+ readonly "1d": "1d";
88
+ readonly "3d": "3d";
89
+ readonly "7d": "7d";
90
+ readonly "14d": "14d";
91
+ readonly "30d": "30d";
92
+ };
93
+ export type Period = keyof typeof PERIOD_RANGE;
94
+ /**
95
+ * The period values as a tuple, derived from {@link PERIOD_RANGE} — the single
96
+ * source of truth for every `z.enum`/step-enum/type that offers the periods.
97
+ * Adding a period to PERIOD_RANGE propagates everywhere.
98
+ */
99
+ export declare const PERIODS: [Period, ...Period[]];
100
+ //# sourceMappingURL=prometheus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.d.ts","sourceRoot":"","sources":["../src/prometheus.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,gDAAgD;AAChD,eAAO,MAAM,6BAA6B,QAAS,CAAA;AAQnD,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,yBAAyB;AACzB,MAAM,WAAW,sBAAsB;IACrC,kGAAkG;IAClG,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;CAChF;AAmJD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAwFjF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,gBAAgB,CAMlB;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAWD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;AAE7D;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,EAAE,GAAG,IAAI,CAItE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAS3E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAGvE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,KAAK,CAAA;AAEhC;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;CAMf,CAAA;AACV,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,YAAY,CAAA;AAE9C;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAgC,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA"}
@@ -0,0 +1,295 @@
1
+ /** Default per-query deadline (`timeoutMs`). */
2
+ export const DEFAULT_PROMETHEUS_TIMEOUT_MS = 30_000;
3
+ /** The longest delay a Node timer holds — a longer deadline would fire at once. */
4
+ const MAX_TIMEOUT_MS = 2_147_483_647;
5
+ /** Upper bound for upstream text in an error message (bodies can be pages long). */
6
+ const MAX_ERROR_TEXT = 500;
7
+ /**
8
+ * Base URL without userinfo, plus the userinfo it carried. The raw value is
9
+ * never echoed: it may hold credentials.
10
+ */
11
+ function splitUserinfo(raw) {
12
+ let url;
13
+ try {
14
+ url = new URL(raw); // not URL.parse: that needs Node 22.1, the package supports 22.0
15
+ }
16
+ catch {
17
+ // Not chained: the parse error carries the raw input.
18
+ throw new Error("Prometheus URL (PROMETHEUS_URL) is not a valid URL");
19
+ }
20
+ const username = decodeURIComponent(url.username) || undefined;
21
+ const password = decodeURIComponent(url.password) || undefined;
22
+ url.username = "";
23
+ url.password = "";
24
+ return { base: url.toString().replace(/\/+$/, ""), username, password };
25
+ }
26
+ /**
27
+ * The `Authorization` value from bearer token, explicit basic credentials or
28
+ * URL userinfo — exactly one source; ambiguous combinations fail the boot.
29
+ */
30
+ function authorizationFor(config, userinfo) {
31
+ const explicitBasic = config.username !== undefined || config.password !== undefined;
32
+ if (userinfo.username !== undefined && (explicitBasic || config.bearerToken)) {
33
+ throw new Error("Prometheus auth is ambiguous: the URL carries credentials AND PROMETHEUS_USERNAME/PROMETHEUS_PASSWORD or PROMETHEUS_BEARER_TOKEN are set — use one");
34
+ }
35
+ if (config.bearerToken && explicitBasic) {
36
+ throw new Error("Prometheus auth is ambiguous: set either PROMETHEUS_BEARER_TOKEN or PROMETHEUS_USERNAME + PROMETHEUS_PASSWORD, not both");
37
+ }
38
+ if (explicitBasic && !(config.username && config.password)) {
39
+ throw new Error("Prometheus basic auth needs both PROMETHEUS_USERNAME and PROMETHEUS_PASSWORD");
40
+ }
41
+ if (config.bearerToken) {
42
+ return { authorization: `Bearer ${config.bearerToken}`, secrets: [config.bearerToken] };
43
+ }
44
+ const username = config.username ?? userinfo.username;
45
+ if (username === undefined)
46
+ return { secrets: [] };
47
+ const password = config.password ?? userinfo.password ?? "";
48
+ const basic = Buffer.from(`${username}:${password}`).toString("base64");
49
+ return { authorization: `Basic ${basic}`, secrets: [password, basic] };
50
+ }
51
+ /** RFC 9110 field-name token; a value must not break the header line. */
52
+ const HEADER_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
53
+ const HEADER_VALUE = /^[^\r\n\0]*$/;
54
+ /**
55
+ * Every form a secret can take in echoed text — as configured, trimmed
56
+ * (header values are sent trimmed) and whitespace-collapsed — longest first,
57
+ * so a value is masked whole before any shorter one inside it. Values under
58
+ * 4 characters are skipped: masking those would shred ordinary text.
59
+ */
60
+ function secretForms(secrets) {
61
+ const forms = new Set();
62
+ for (const secret of secrets) {
63
+ if (!secret)
64
+ continue;
65
+ for (const form of [secret, secret.trim(), secret.replace(/\s+/g, " ").trim()]) {
66
+ if (form.length >= 4)
67
+ forms.add(form);
68
+ }
69
+ }
70
+ return [...forms].sort((a, b) => b.length - a.length);
71
+ }
72
+ function mask(text, secrets) {
73
+ let masked = text;
74
+ for (const secret of secrets)
75
+ masked = masked.split(secret).join("***");
76
+ return masked;
77
+ }
78
+ /** A Prometheus API `errorType` (`bad_data`, `timeout`, …); anything else is not echoed. */
79
+ const ERROR_TYPE = /^[a-z_]{1,40}$/;
80
+ /** The request headers (custom + auth) and every secret an upstream could echo back. */
81
+ function buildHeaders(config, userinfo) {
82
+ const headers = new Headers();
83
+ for (const [name, value] of Object.entries(config.headers ?? {})) {
84
+ // Checked up front so the value (often a secret, e.g. an API key) never
85
+ // lands in an error — only the header name is named.
86
+ if (!HEADER_NAME.test(name) || !HEADER_VALUE.test(value)) {
87
+ throw new Error(`Prometheus header "${name}" (PROMETHEUS_HEADERS) has an invalid name or value`);
88
+ }
89
+ headers.set(name, value);
90
+ }
91
+ const { authorization, secrets } = authorizationFor(config, userinfo);
92
+ if (authorization && headers.has("Authorization")) {
93
+ throw new Error("Prometheus auth is ambiguous: PROMETHEUS_HEADERS sets Authorization next to configured credentials");
94
+ }
95
+ if (authorization)
96
+ headers.set("Authorization", authorization);
97
+ // The configured credential (not its header line, so an echo keeps the
98
+ // scheme readable) plus every custom header value.
99
+ return { headers, secrets: secretForms([...secrets, ...Object.values(config.headers ?? {})]) };
100
+ }
101
+ function resolveTimeout(timeoutMs) {
102
+ if (timeoutMs === undefined)
103
+ return DEFAULT_PROMETHEUS_TIMEOUT_MS;
104
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMEOUT_MS) {
105
+ throw new RangeError(`Prometheus timeout must be a positive integer of at most ${MAX_TIMEOUT_MS} ms, got ${timeoutMs}`);
106
+ }
107
+ return timeoutMs;
108
+ }
109
+ /** `JSON.parse` that yields `undefined` instead of throwing (bodies may be HTML or empty). */
110
+ function parseJson(text) {
111
+ try {
112
+ return JSON.parse(text);
113
+ }
114
+ catch {
115
+ return undefined;
116
+ }
117
+ }
118
+ /** The OS-level code of a fetch network error (`ECONNREFUSED`, `ENOTFOUND`, …). */
119
+ function networkCode(error) {
120
+ const cause = error.cause;
121
+ const code = cause?.code ?? cause?.errors?.find((e) => e.code)?.code;
122
+ return typeof code === "string" && code.length > 0 ? code : "network error";
123
+ }
124
+ /**
125
+ * Minimal Prometheus HTTP API client. The analytics queries are metric-first:
126
+ * they issue instant PromQL queries (`/api/v1/query`) whose range windows carry
127
+ * the time period, and map the labeled samples into the analytics row shapes.
128
+ *
129
+ * Every query runs under a deadline (`timeoutMs`, combined with an optional
130
+ * caller `signal`), and every failure is an `Error` whose message is safe to
131
+ * hand to the model: no URL, no credentials, upstream text truncated.
132
+ */
133
+ export function createPrometheusClient(config) {
134
+ const userinfo = splitUserinfo(config.url);
135
+ const { base } = userinfo;
136
+ const { headers, secrets } = buildHeaders(config, userinfo);
137
+ const timeoutMs = resolveTimeout(config.timeoutMs);
138
+ /**
139
+ * Upstream text with every configured secret masked (before AND after
140
+ * collapsing whitespace, so neither form slips through), then truncated.
141
+ */
142
+ const safe = (text) => {
143
+ const flat = mask(mask(text, secrets).replace(/\s+/g, " ").trim(), secrets);
144
+ return flat.length > MAX_ERROR_TEXT ? `${flat.slice(0, MAX_ERROR_TEXT)}…` : flat;
145
+ };
146
+ /** The envelope's `errorType` when it is a Prometheus one (masked all the same). */
147
+ const errorTypeOf = (value) => typeof value === "string" && ERROR_TYPE.test(value) ? safe(value) : undefined;
148
+ function httpError(response, text) {
149
+ // The API's JSON error envelope when it is one, else the raw text.
150
+ const body = parseJson(text);
151
+ const errorType = errorTypeOf(body?.errorType);
152
+ const raw = typeof body?.error === "string" ? body.error : text;
153
+ const detail = safe(raw) || `${response.statusText || "HTTP error"} — empty response body`;
154
+ const auth = response.status === 401 || response.status === 403;
155
+ return new Error(`Prometheus query failed (${response.status}${errorType ? ` ${errorType}` : ""}): ${detail}${auth ? " — check the configured Prometheus credentials" : ""}`);
156
+ }
157
+ /** Response + body under the deadline (and the caller's signal), failures mapped. */
158
+ async function fetchText(url, caller) {
159
+ const deadline = AbortSignal.timeout(timeoutMs);
160
+ const signal = caller ? AbortSignal.any([caller, deadline]) : deadline;
161
+ try {
162
+ const response = await fetch(url, { method: "GET", headers, signal });
163
+ return { response, text: await response.text() };
164
+ }
165
+ catch (err) {
166
+ const message = deadline.aborted
167
+ ? `Prometheus did not respond within ${timeoutMs} ms (timeout)`
168
+ : caller?.aborted
169
+ ? "Prometheus query cancelled by the caller"
170
+ : `Prometheus unreachable (${networkCode(err)})`;
171
+ throw new Error(message, { cause: err });
172
+ }
173
+ }
174
+ /** The API envelope of a 2xx response; a non-JSON body (a login page) says so. */
175
+ function parseEnvelope(response, text) {
176
+ const body = parseJson(text);
177
+ if (body !== null && typeof body === "object")
178
+ return body;
179
+ const type = response.headers.get("Content-Type")?.split(";")[0];
180
+ throw new Error(`Prometheus returned a non-JSON response (HTTP ${response.status}${type ? `, ${type}` : ""}) — is a proxy or login page in front of it?`);
181
+ }
182
+ return {
183
+ async instant(query, options = {}) {
184
+ const url = `${base}/api/v1/query?query=${encodeURIComponent(query)}`;
185
+ const { response, text } = await fetchText(url, options.signal);
186
+ if (!response.ok)
187
+ throw httpError(response, text);
188
+ const body = parseEnvelope(response, text);
189
+ if (body.status !== "success" || !body.data) {
190
+ const errorType = errorTypeOf(body.errorType);
191
+ throw new Error(`Prometheus query error${errorType ? ` (${errorType})` : ""}: ${safe(body.error ?? "unknown")}`);
192
+ }
193
+ const out = [];
194
+ for (const r of body.data.result) {
195
+ const value = Number(r.value[1]);
196
+ if (Number.isNaN(value))
197
+ continue;
198
+ out.push({ metric: r.metric, value });
199
+ }
200
+ return out;
201
+ },
202
+ };
203
+ }
204
+ /**
205
+ * A view of `client` whose queries honor the caller's `signal` (an MCP
206
+ * request's `ctx.signal`) on top of the client's own deadline — every
207
+ * Prometheus query is a read, so cancelling one is always safe. A per-call
208
+ * `signal` wins. Without a signal the client itself is returned.
209
+ */
210
+ export function withCallerSignal(client, signal) {
211
+ if (!signal)
212
+ return client;
213
+ return {
214
+ instant: (query, options) => client.instant(query, { ...options, signal: options?.signal ?? signal }),
215
+ };
216
+ }
217
+ /** Escape a value for safe use inside a PromQL label matcher (`{k="<value>"}`). */
218
+ export function escapeLabelValue(value) {
219
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
220
+ }
221
+ /**
222
+ * Escape RE2 metacharacters so a value matches literally inside a `=~"…"`
223
+ * matcher. Without this, an id like `engine.prod` also matches `engineXprod`,
224
+ * and one containing `|` silently splits into two alternatives.
225
+ */
226
+ function escapeRegexValue(value) {
227
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
228
+ }
229
+ /**
230
+ * The engine ids a filter names, or `null` for no filter (every engine
231
+ * Prometheus holds). Results echo this so a reader always knows which engines
232
+ * an aggregate covers.
233
+ */
234
+ export function engineIdsOf(engine) {
235
+ if (engine === undefined || engine === null)
236
+ return null;
237
+ const ids = (Array.isArray(engine) ? engine : [engine]).filter((id) => id.length > 0);
238
+ return ids.length > 0 ? ids : null;
239
+ }
240
+ /**
241
+ * Builds a PromQL label matcher fragment for the optional `engine_id` filter,
242
+ * e.g. `engine_id="prod-a"` or `engine_id=~"prod-a|prod-b"` — one id is an
243
+ * exact matcher whether it comes as a string or a one-element list (the
244
+ * analytics tools always resolve `engine` to a list). Returns `undefined`
245
+ * when no filter is set so the caller can aggregate across all engines.
246
+ */
247
+ export function engineMatcher(engine) {
248
+ if (engine === undefined || engine === null)
249
+ return undefined;
250
+ if (Array.isArray(engine)) {
251
+ if (engine.length === 0)
252
+ return undefined;
253
+ if (engine.length === 1)
254
+ return engineMatcher(engine[0]);
255
+ return `engine_id=~"${engine.map((e) => escapeLabelValue(escapeRegexValue(e))).join("|")}"`;
256
+ }
257
+ if (engine.length === 0)
258
+ return undefined;
259
+ return `engine_id="${escapeLabelValue(engine)}"`;
260
+ }
261
+ /**
262
+ * Assembles a `{...}` PromQL label selector from individual matcher fragments,
263
+ * dropping empties. Returns `""` (no selector) when nothing is set.
264
+ */
265
+ export function selector(...matchers) {
266
+ const parts = matchers.filter((m) => !!m && m.length > 0);
267
+ return parts.length ? `{${parts.join(",")}}` : "";
268
+ }
269
+ /**
270
+ * The look-back the analytics module assumes Prometheus retains
271
+ * (`--storage.tsdb.retention.time`, 30 days in the repo's stack). Every
272
+ * window — the rolling periods, the compare windows, explicit date ranges —
273
+ * is held inside `[now − RETENTION_DAYS, now]`: data older than that is gone,
274
+ * so a longer window would silently read partial or zero data.
275
+ */
276
+ export const RETENTION_DAYS = 30;
277
+ /**
278
+ * PromQL range windows for the analytics `period` inputs, capped at
279
+ * {@link RETENTION_DAYS}; longer look-backs would silently read partial/zero
280
+ * data, so they are not offered.
281
+ */
282
+ export const PERIOD_RANGE = {
283
+ "1d": "1d",
284
+ "3d": "3d",
285
+ "7d": "7d",
286
+ "14d": "14d",
287
+ "30d": "30d",
288
+ };
289
+ /**
290
+ * The period values as a tuple, derived from {@link PERIOD_RANGE} — the single
291
+ * source of truth for every `z.enum`/step-enum/type that offers the periods.
292
+ * Adding a period to PERIOD_RANGE propagates everywhere.
293
+ */
294
+ export const PERIODS = Object.keys(PERIOD_RANGE);
295
+ //# sourceMappingURL=prometheus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.js","sourceRoot":"","sources":["../src/prometheus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prometheus.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.test.d.ts","sourceRoot":"","sources":["../src/prometheus.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { engineMatcher, escapeLabelValue, selector } from "./prometheus.js";
3
+ describe("escapeLabelValue", () => {
4
+ it("leaves plain values untouched", () => {
5
+ expect(escapeLabelValue("miraveloLeasing")).toBe("miraveloLeasing");
6
+ });
7
+ it("escapes backslashes and double quotes (PromQL label-matcher injection safety)", () => {
8
+ expect(escapeLabelValue('a"b')).toBe('a\\"b');
9
+ expect(escapeLabelValue("a\\b")).toBe("a\\\\b");
10
+ expect(escapeLabelValue('"; bad')).toBe('\\"; bad');
11
+ });
12
+ });
13
+ describe("engineMatcher", () => {
14
+ it("returns undefined when no engine filter is set (aggregate across engines)", () => {
15
+ expect(engineMatcher(undefined)).toBeUndefined();
16
+ expect(engineMatcher("")).toBeUndefined();
17
+ expect(engineMatcher([])).toBeUndefined();
18
+ });
19
+ it("builds an exact matcher for a single engine", () => {
20
+ expect(engineMatcher("prod-a")).toBe('engine_id="prod-a"');
21
+ });
22
+ it("builds a regex matcher for a list of engines", () => {
23
+ expect(engineMatcher(["prod-a", "prod-b"])).toBe('engine_id=~"prod-a|prod-b"');
24
+ });
25
+ it("escapes engine ids", () => {
26
+ expect(engineMatcher('a"b')).toBe('engine_id="a\\"b"');
27
+ });
28
+ it("escapes regex metacharacters in the =~ list matcher (literal match only)", () => {
29
+ // A dot must not act as a wildcard, and a pipe in an id must not split the
30
+ // matcher into two alternatives. The regex backslash is itself doubled for
31
+ // the PromQL string literal (Go escape rules), so the wire text carries
32
+ // `\\.` and RE2 sees `\.`.
33
+ expect(engineMatcher(["engine.prod-1", "x"])).toBe('engine_id=~"engine\\\\.prod-1|x"');
34
+ expect(engineMatcher(["a|b", "c+d"])).toBe('engine_id=~"a\\\\|b|c\\\\+d"');
35
+ });
36
+ it("builds an exact (literal) matcher for a one-element list", () => {
37
+ // The analytics tools always resolve `engine` to a list: one engine must
38
+ // still read as the exact match it is.
39
+ expect(engineMatcher(["engine.prod-1"])).toBe('engine_id="engine.prod-1"');
40
+ });
41
+ });
42
+ describe("selector", () => {
43
+ it("returns an empty string when nothing is set", () => {
44
+ expect(selector()).toBe("");
45
+ expect(selector(undefined, "", undefined)).toBe("");
46
+ });
47
+ it("wraps and comma-joins the non-empty matchers", () => {
48
+ expect(selector('process_definition_key="k"', undefined, 'state="COMPLETED"')).toBe('{process_definition_key="k",state="COMPLETED"}');
49
+ });
50
+ it("wraps a single matcher", () => {
51
+ expect(selector('engine_id="prod-a"')).toBe('{engine_id="prod-a"}');
52
+ });
53
+ });
54
+ //# sourceMappingURL=prometheus.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.test.js","sourceRoot":"","sources":["../src/prometheus.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Test support for the metrics-contract guards — never imported by runtime
3
+ * code. Resolves every selector of a parsed PromQL expression
4
+ * (`promql-parse.test-support.ts`) to its contract series and asserts that
5
+ * each label the expression names — in a matcher, a `by`/`without` grouping, an
6
+ * `on`/`ignoring` vector match or a `group_left`/`group_right` include list —
7
+ * is a label that operand can carry.
8
+ *
9
+ * Label availability propagates the way PromQL evaluates: a selector carries
10
+ * its series' declared labels (`le` added on `_bucket`), `increase()` keeps
11
+ * them, `sum by (a)` narrows to `a`, `histogram_quantile` consumes `le`, a
12
+ * one-to-one `on (a)` match keeps `a`. So `sum by (a)(sum by (b)(x))` fails
13
+ * even when `a` is declared on `x`. Unknown functions and series are reported
14
+ * as violations instead of passing silently.
15
+ */
16
+ /** What a series may carry: its label names plus documented label values. */
17
+ export interface SeriesInfo {
18
+ labels: readonly string[];
19
+ knownValues?: Readonly<Record<string, readonly string[]>>;
20
+ }
21
+ /** Resolves a series name to its declared labels, or undefined when unknown. */
22
+ export type SeriesCatalog = (series: string) => SeriesInfo | undefined;
23
+ /** The subset of a `metrics-contract.json` entry the catalogue needs. */
24
+ export interface ContractMetricLike {
25
+ promName: string;
26
+ type: string;
27
+ labels: string[];
28
+ knownValues?: Record<string, string[]>;
29
+ }
30
+ /**
31
+ * Catalogue over the contract: counters/gauges by `promName`, histograms as
32
+ * their `_sum`/`_count`/`_bucket` series (`_bucket` adds `le`). The bare
33
+ * histogram name is deliberately absent — classic histograms expose no such
34
+ * series, so a query on it reads nothing. `extra` adds non-contract series
35
+ * (Prometheus' own `ALERTS`, `up`, …) with their documented labels.
36
+ */
37
+ export declare function contractCatalog(metrics: readonly ContractMetricLike[], extra?: Readonly<Record<string, SeriesInfo>>): SeriesCatalog;
38
+ export interface LabelCheck {
39
+ /** Human-readable contract violations; empty when the expression is clean. */
40
+ violations: string[];
41
+ /** Every series the expression selects. */
42
+ series: string[];
43
+ /** Label references verified — lets a caller detect a silently empty scan. */
44
+ checkedLabels: number;
45
+ /** Labels the result can carry (undefined for a scalar/string result). */
46
+ resultLabels: string[] | undefined;
47
+ }
48
+ /**
49
+ * Parse `query` and check every label it names against `catalog`. Throws on a
50
+ * syntax outside the supported subset; everything else lands in `violations`.
51
+ */
52
+ export declare function checkQueryLabels(query: string, catalog: SeriesCatalog): LabelCheck;
53
+ //# sourceMappingURL=promql-labels.test-support.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promql-labels.test-support.d.ts","sourceRoot":"","sources":["../src/promql-labels.test-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;CAC1D;AAED,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;AAEtE,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAM,GAC/C,aAAa,CAaf;AAoCD,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACnC;AA8KD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,UAAU,CASlF"}