@marinoscar/platform-contract 0.0.0-stage → 0.1.0-next.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +145 -2
  4. package/dist/cjs/doctor/constants.d.ts +59 -0
  5. package/dist/cjs/doctor/constants.d.ts.map +1 -0
  6. package/dist/cjs/doctor/constants.js +57 -0
  7. package/dist/cjs/doctor/constants.js.map +1 -0
  8. package/dist/cjs/doctor/index.d.ts +5 -0
  9. package/dist/cjs/doctor/index.d.ts.map +1 -0
  10. package/dist/cjs/doctor/index.js +24 -0
  11. package/dist/cjs/doctor/index.js.map +1 -0
  12. package/dist/cjs/doctor/schemas.d.ts +186 -0
  13. package/dist/cjs/doctor/schemas.d.ts.map +1 -0
  14. package/dist/cjs/doctor/schemas.js +183 -0
  15. package/dist/cjs/doctor/schemas.js.map +1 -0
  16. package/dist/cjs/index.d.ts +13 -0
  17. package/dist/cjs/index.d.ts.map +1 -0
  18. package/dist/cjs/index.js +16 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/package.json +3 -0
  21. package/dist/cjs/telemetry/assistant.d.ts +173 -0
  22. package/dist/cjs/telemetry/assistant.d.ts.map +1 -0
  23. package/dist/cjs/telemetry/assistant.js +43 -0
  24. package/dist/cjs/telemetry/assistant.js.map +1 -0
  25. package/dist/cjs/telemetry/config.d.ts +92 -0
  26. package/dist/cjs/telemetry/config.d.ts.map +1 -0
  27. package/dist/cjs/telemetry/config.js +101 -0
  28. package/dist/cjs/telemetry/config.js.map +1 -0
  29. package/dist/cjs/telemetry/connection.d.ts +355 -0
  30. package/dist/cjs/telemetry/connection.d.ts.map +1 -0
  31. package/dist/cjs/telemetry/connection.js +451 -0
  32. package/dist/cjs/telemetry/connection.js.map +1 -0
  33. package/dist/cjs/telemetry/constants.d.ts +442 -0
  34. package/dist/cjs/telemetry/constants.d.ts.map +1 -0
  35. package/dist/cjs/telemetry/constants.js +400 -0
  36. package/dist/cjs/telemetry/constants.js.map +1 -0
  37. package/dist/cjs/telemetry/dashboard.d.ts +826 -0
  38. package/dist/cjs/telemetry/dashboard.d.ts.map +1 -0
  39. package/dist/cjs/telemetry/dashboard.js +697 -0
  40. package/dist/cjs/telemetry/dashboard.js.map +1 -0
  41. package/dist/cjs/telemetry/enum.d.ts +5 -0
  42. package/dist/cjs/telemetry/enum.d.ts.map +1 -0
  43. package/dist/cjs/telemetry/enum.js +12 -0
  44. package/dist/cjs/telemetry/enum.js.map +1 -0
  45. package/dist/cjs/telemetry/index.d.ts +5 -0
  46. package/dist/cjs/telemetry/index.d.ts.map +1 -0
  47. package/dist/cjs/telemetry/index.js +128 -0
  48. package/dist/cjs/telemetry/index.js.map +1 -0
  49. package/dist/cjs/telemetry/query.d.ts +147 -0
  50. package/dist/cjs/telemetry/query.d.ts.map +1 -0
  51. package/dist/cjs/telemetry/query.js +141 -0
  52. package/dist/cjs/telemetry/query.js.map +1 -0
  53. package/dist/cjs/telemetry/schemas.d.ts +17 -0
  54. package/dist/cjs/telemetry/schemas.d.ts.map +1 -0
  55. package/dist/cjs/telemetry/schemas.js +89 -0
  56. package/dist/cjs/telemetry/schemas.js.map +1 -0
  57. package/dist/cjs/telemetry/settings.d.ts +65 -0
  58. package/dist/cjs/telemetry/settings.d.ts.map +1 -0
  59. package/dist/cjs/telemetry/settings.js +77 -0
  60. package/dist/cjs/telemetry/settings.js.map +1 -0
  61. package/dist/cjs/telemetry/stack.d.ts +87 -0
  62. package/dist/cjs/telemetry/stack.d.ts.map +1 -0
  63. package/dist/cjs/telemetry/stack.js +93 -0
  64. package/dist/cjs/telemetry/stack.js.map +1 -0
  65. package/dist/cjs/telemetry/status.d.ts +65 -0
  66. package/dist/cjs/telemetry/status.d.ts.map +1 -0
  67. package/dist/cjs/telemetry/status.js +65 -0
  68. package/dist/cjs/telemetry/status.js.map +1 -0
  69. package/dist/esm/doctor/constants.d.ts +59 -0
  70. package/dist/esm/doctor/constants.d.ts.map +1 -0
  71. package/dist/esm/doctor/constants.js +54 -0
  72. package/dist/esm/doctor/constants.js.map +1 -0
  73. package/dist/esm/doctor/index.d.ts +5 -0
  74. package/dist/esm/doctor/index.d.ts.map +1 -0
  75. package/dist/esm/doctor/index.js +10 -0
  76. package/dist/esm/doctor/index.js.map +1 -0
  77. package/dist/esm/doctor/schemas.d.ts +186 -0
  78. package/dist/esm/doctor/schemas.d.ts.map +1 -0
  79. package/dist/esm/doctor/schemas.js +180 -0
  80. package/dist/esm/doctor/schemas.js.map +1 -0
  81. package/dist/esm/index.d.ts +13 -0
  82. package/dist/esm/index.d.ts.map +1 -0
  83. package/dist/esm/index.js +13 -0
  84. package/dist/esm/index.js.map +1 -0
  85. package/dist/esm/package.json +3 -0
  86. package/dist/esm/telemetry/assistant.d.ts +173 -0
  87. package/dist/esm/telemetry/assistant.d.ts.map +1 -0
  88. package/dist/esm/telemetry/assistant.js +40 -0
  89. package/dist/esm/telemetry/assistant.js.map +1 -0
  90. package/dist/esm/telemetry/config.d.ts +92 -0
  91. package/dist/esm/telemetry/config.d.ts.map +1 -0
  92. package/dist/esm/telemetry/config.js +98 -0
  93. package/dist/esm/telemetry/config.js.map +1 -0
  94. package/dist/esm/telemetry/connection.d.ts +355 -0
  95. package/dist/esm/telemetry/connection.d.ts.map +1 -0
  96. package/dist/esm/telemetry/connection.js +447 -0
  97. package/dist/esm/telemetry/connection.js.map +1 -0
  98. package/dist/esm/telemetry/constants.d.ts +442 -0
  99. package/dist/esm/telemetry/constants.d.ts.map +1 -0
  100. package/dist/esm/telemetry/constants.js +397 -0
  101. package/dist/esm/telemetry/constants.js.map +1 -0
  102. package/dist/esm/telemetry/dashboard.d.ts +826 -0
  103. package/dist/esm/telemetry/dashboard.d.ts.map +1 -0
  104. package/dist/esm/telemetry/dashboard.js +690 -0
  105. package/dist/esm/telemetry/dashboard.js.map +1 -0
  106. package/dist/esm/telemetry/enum.d.ts +5 -0
  107. package/dist/esm/telemetry/enum.d.ts.map +1 -0
  108. package/dist/esm/telemetry/enum.js +9 -0
  109. package/dist/esm/telemetry/enum.js.map +1 -0
  110. package/dist/esm/telemetry/index.d.ts +5 -0
  111. package/dist/esm/telemetry/index.d.ts.map +1 -0
  112. package/dist/esm/telemetry/index.js +10 -0
  113. package/dist/esm/telemetry/index.js.map +1 -0
  114. package/dist/esm/telemetry/query.d.ts +147 -0
  115. package/dist/esm/telemetry/query.d.ts.map +1 -0
  116. package/dist/esm/telemetry/query.js +138 -0
  117. package/dist/esm/telemetry/query.js.map +1 -0
  118. package/dist/esm/telemetry/schemas.d.ts +17 -0
  119. package/dist/esm/telemetry/schemas.d.ts.map +1 -0
  120. package/dist/esm/telemetry/schemas.js +12 -0
  121. package/dist/esm/telemetry/schemas.js.map +1 -0
  122. package/dist/esm/telemetry/settings.d.ts +65 -0
  123. package/dist/esm/telemetry/settings.d.ts.map +1 -0
  124. package/dist/esm/telemetry/settings.js +74 -0
  125. package/dist/esm/telemetry/settings.js.map +1 -0
  126. package/dist/esm/telemetry/stack.d.ts +87 -0
  127. package/dist/esm/telemetry/stack.d.ts.map +1 -0
  128. package/dist/esm/telemetry/stack.js +90 -0
  129. package/dist/esm/telemetry/stack.js.map +1 -0
  130. package/dist/esm/telemetry/status.d.ts +65 -0
  131. package/dist/esm/telemetry/status.d.ts.map +1 -0
  132. package/dist/esm/telemetry/status.js +62 -0
  133. package/dist/esm/telemetry/status.js.map +1 -0
  134. package/package.json +78 -4
@@ -0,0 +1,690 @@
1
+ // =============================================================================
2
+ // Telemetry dashboard (issue #577; moved here from the API's
3
+ // `telemetry/dto/telemetry-dashboard.dto.ts` by #702)
4
+ // =============================================================================
5
+ //
6
+ // GET /api/admin/telemetry/dashboard/summary → telemetryDashboardSummarySchema
7
+ // GET /api/admin/telemetry/dashboard/timeseries → telemetryDashboardTimeseriesSchema
8
+ // GET /api/admin/telemetry/dashboard/top → telemetryDashboardTopSchema
9
+ // GET /api/admin/telemetry/dashboard/events → telemetryDashboardEventsSchema
10
+ // GET /api/admin/telemetry/dashboard/filters → telemetryDashboardFiltersSchema
11
+ // GET /api/admin/telemetry/dashboard/metrics → telemetryDashboardMetricsSchema (#601)
12
+ // GET /api/admin/telemetry/dashboard/metric-groups → telemetryDashboardMetricGroupsSchema (#680)
13
+ //
14
+ // Common query: `range` (15m|1h|6h|24h|7d, default 1h) OR `from`+`to` (ISO,
15
+ // from < to, to <= now + 1 min, span <= 30 days); `service`, `instance`
16
+ // (checked by the service against the values seen in the range);
17
+ // `buckets` (30|60, default 60). Every query value arrives as a string.
18
+ //
19
+ // METRIC GROUP IDS ARE AN OPEN SET (#680): the platform registers six and an
20
+ // app may register more, so the contract types `group` as an id (a string
21
+ // matching `METRIC_GROUP_ID_PATTERN`). A server that knows its registered set
22
+ // builds its own `/metrics` schemas with `createTelemetryDashboardMetricsQuerySchema`
23
+ // and `createTelemetryDashboardMetricsSchema`, passing the group schema it
24
+ // documents and checks (the API passes one read from its live registry).
25
+ //
26
+ // ⚠ OpenAPI is generated from these schemas. Field order, `.describe()` texts
27
+ // and enum order are part of the published document.
28
+ // =============================================================================
29
+ import { z } from 'zod';
30
+ import { DASHBOARD_BUCKET_COUNTS, DASHBOARD_CURSOR_MAX, DASHBOARD_EVENT_SEVERITIES, DASHBOARD_FILTER_VALUE_MAX, DASHBOARD_FUTURE_SKEW_MS, DASHBOARD_MAX_SPAN_MS, DASHBOARD_PANELS, DASHBOARD_RANGES, DASHBOARD_SEARCH_MAX_LENGTH, DASHBOARD_TOP_KINDS, METRIC_GROUP_ID_PATTERN, METRIC_UNITS, VERDICT_LEVELS, } from './constants.js';
31
+ import { wireEnum } from './enum.js';
32
+ // ---- queries -----------------------------------------------------------------
33
+ const commonShape = {
34
+ /** Relative window ending now. Default `1h`. Not with `from`/`to`. */
35
+ range: wireEnum(DASHBOARD_RANGES).optional().describe('Relative window ending now. Default `1h`. Not with `from`/`to`.'),
36
+ /** Absolute window start (ISO 8601). Requires `to`. */
37
+ from: z.iso.datetime({ offset: true }).optional().describe('Absolute window start (ISO 8601). Requires `to`.'),
38
+ /** Absolute window end (ISO 8601), at most 1 minute in the future. Requires `from`; span at most 30 days. */
39
+ to: z.iso
40
+ .datetime({ offset: true })
41
+ .optional()
42
+ .describe('Absolute window end (ISO 8601), at most 1 minute in the future. Requires `from`; span <= 30 days.'),
43
+ /** Only this service. Must be one of `/filters` `services` for the range. */
44
+ service: z
45
+ .string()
46
+ .min(1)
47
+ .max(DASHBOARD_FILTER_VALUE_MAX)
48
+ .optional()
49
+ .describe('Only this service. Must be one of `/filters` `services` for the range.'),
50
+ /** Only this instance (`app.instance.id`). Must be one of `/filters` `instances` for the range. */
51
+ instance: z
52
+ .string()
53
+ .min(1)
54
+ .max(DASHBOARD_FILTER_VALUE_MAX)
55
+ .optional()
56
+ .describe('Only this instance (`app.instance.id`). Must be one of `/filters` `instances` for the range.'),
57
+ /** Target number of buckets: `30` or `60` (default). */
58
+ buckets: wireEnum(DASHBOARD_BUCKET_COUNTS).optional().describe('Target number of buckets: `30` or `60` (default).'),
59
+ };
60
+ /**
61
+ * The cross-field rules of a dashboard window, as a `superRefine`: `range`
62
+ * or `from`+`to` (never both, never one of the pair), `from < to`, `to` at
63
+ * most 1 minute in the future and a span of at most 30 days. Every dashboard
64
+ * query schema applies it; an app's own dashboard query applies it too.
65
+ *
66
+ * @stability stable
67
+ */
68
+ export function refineWindow(value, ctx) {
69
+ const hasFrom = value.from !== undefined;
70
+ const hasTo = value.to !== undefined;
71
+ if (value.range !== undefined && (hasFrom || hasTo)) {
72
+ ctx.addIssue({ code: 'custom', path: ['range'], message: 'Use either `range` or `from`/`to`, not both.' });
73
+ return;
74
+ }
75
+ if (hasFrom !== hasTo) {
76
+ ctx.addIssue({ code: 'custom', path: [hasFrom ? 'to' : 'from'], message: '`from` and `to` go together.' });
77
+ return;
78
+ }
79
+ if (!hasFrom)
80
+ return;
81
+ const from = Date.parse(value.from);
82
+ const to = Date.parse(value.to);
83
+ if (!(from < to)) {
84
+ ctx.addIssue({ code: 'custom', path: ['from'], message: '`from` must be before `to`.' });
85
+ return;
86
+ }
87
+ if (to > Date.now() + DASHBOARD_FUTURE_SKEW_MS) {
88
+ ctx.addIssue({ code: 'custom', path: ['to'], message: '`to` may be at most 1 minute in the future.' });
89
+ }
90
+ if (to - from > DASHBOARD_MAX_SPAN_MS) {
91
+ ctx.addIssue({ code: 'custom', path: ['from'], message: 'The window may span at most 30 days.' });
92
+ }
93
+ }
94
+ /**
95
+ * The query every dashboard route shares: `range` or `from`+`to`, `service`,
96
+ * `instance` and `buckets`, with {@link refineWindow}.
97
+ *
98
+ * @extensionPoint schema
99
+ * @stability stable
100
+ */
101
+ export const telemetryDashboardQuerySchema = z.object(commonShape).superRefine(refineWindow);
102
+ /**
103
+ * `GET …/dashboard/timeseries` query: the common query plus `panel`
104
+ * (`api` or `logs`).
105
+ *
106
+ * @extensionPoint schema
107
+ * @stability stable
108
+ */
109
+ export const telemetryDashboardTimeseriesQuerySchema = z
110
+ .object({
111
+ ...commonShape,
112
+ /** `api` (status classes, p95) or `logs` (severity bands). */
113
+ panel: wireEnum(DASHBOARD_PANELS).describe('`api` (status classes, p95) or `logs` (severity bands).'),
114
+ })
115
+ .superRefine(refineWindow);
116
+ /**
117
+ * `GET …/dashboard/top` query: the common query plus `kind` (`routes` or
118
+ * `errors`).
119
+ *
120
+ * @extensionPoint schema
121
+ * @stability stable
122
+ */
123
+ export const telemetryDashboardTopQuerySchema = z
124
+ .object({
125
+ ...commonShape,
126
+ /** `routes` (by 5xx, then 4xx except 401, then p95) or `errors` (log messages). */
127
+ kind: wireEnum(DASHBOARD_TOP_KINDS).describe('`routes` (by 5xx, then 4xx except 401, then p95) or `errors` (log messages).'),
128
+ })
129
+ .superRefine(refineWindow);
130
+ const SEVERITY_LIST = new RegExp(`^(${DASHBOARD_EVENT_SEVERITIES.join('|')})(,(${DASHBOARD_EVENT_SEVERITIES.join('|')}))*$`);
131
+ /**
132
+ * `GET …/dashboard/events` query: the common query plus `severity` (a comma
133
+ * list of {@link DASHBOARD_EVENT_SEVERITIES}), `q` (a log body substring) and
134
+ * `cursor`.
135
+ *
136
+ * @extensionPoint schema
137
+ * @stability stable
138
+ */
139
+ export const telemetryDashboardEventsQuerySchema = z
140
+ .object({
141
+ ...commonShape,
142
+ /** Comma-separated severities: `error`, `warn`, `info`. Default `error,warn`. */
143
+ severity: z
144
+ .string()
145
+ .regex(SEVERITY_LIST, 'A comma-separated list of error, warn, info.')
146
+ .optional()
147
+ .describe('Comma-separated severities: `error`, `warn`, `info`. Default `error,warn`.'),
148
+ /** Case-insensitive substring of the log body (at most 200 characters; `%` and `_` are literal). */
149
+ q: z
150
+ .string()
151
+ .max(DASHBOARD_SEARCH_MAX_LENGTH)
152
+ .optional()
153
+ .describe('Case-insensitive substring of the log body (at most 200 characters; `%` and `_` are literal).'),
154
+ /** `nextCursor` of the previous page. */
155
+ cursor: z
156
+ .string()
157
+ .max(DASHBOARD_CURSOR_MAX)
158
+ .optional()
159
+ .describe('`nextCursor` of the previous page.'),
160
+ })
161
+ .superRefine(refineWindow);
162
+ /** `a`, `a or b`, `a, b or c`. */
163
+ function orList(items) {
164
+ return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} or ${items[items.length - 1]}`;
165
+ }
166
+ /**
167
+ * A metric group id as the contract knows it: any lower snake-case id
168
+ * ({@link METRIC_GROUP_ID_PATTERN}). The set is open; a server checks it
169
+ * against what it registered.
170
+ *
171
+ * @extensionPoint schema
172
+ * @stability stable
173
+ */
174
+ export const metricGroupIdSchema = z.string().regex(METRIC_GROUP_ID_PATTERN);
175
+ /**
176
+ * The `/metrics` `group` query value of a server with a registry: checked
177
+ * against the LIVE set (`isKnown`), so a group registered after the schema
178
+ * was built is accepted, and DOCUMENTED as the enum of `documented`, exactly
179
+ * as a `z.enum` would be, so the OpenAPI document reads the same.
180
+ *
181
+ * @extensionPoint schema
182
+ * @stability stable
183
+ */
184
+ export function metricGroupQuerySchema(options) {
185
+ const { documented, isKnown, knownIds } = options;
186
+ return z
187
+ .string()
188
+ .refine(isKnown, {
189
+ error: () => `Invalid option: expected one of ${knownIds().map((id) => `"${id}"`).join('|')}`,
190
+ })
191
+ .meta({ enum: [...documented] })
192
+ .describe(`The metric group: ${orList(documented.map((id) => `\`${id}\``))}.`);
193
+ }
194
+ /**
195
+ * Builds the `GET …/dashboard/metrics` query schema around a `group` schema:
196
+ * the common query plus `group` and `host`, with {@link refineWindow}.
197
+ *
198
+ * @extensionPoint schema
199
+ * @stability stable
200
+ */
201
+ export function createTelemetryDashboardMetricsQuerySchema(group) {
202
+ return z
203
+ .object({
204
+ ...commonShape,
205
+ /** The metric group: the schema passed in. */
206
+ group,
207
+ /** Only this host (`host_name`). Must be one of `/filters` `hosts` for the range. */
208
+ host: z
209
+ .string()
210
+ .min(1)
211
+ .max(DASHBOARD_FILTER_VALUE_MAX)
212
+ .optional()
213
+ .describe('Only this host (`host_name`). Must be one of `/filters` `hosts` for the range.'),
214
+ })
215
+ .superRefine(refineWindow);
216
+ }
217
+ /**
218
+ * `GET …/dashboard/metrics` query, with `group` any metric group id
219
+ * ({@link metricGroupIdSchema}).
220
+ *
221
+ * @extensionPoint schema
222
+ * @stability stable
223
+ */
224
+ export const telemetryDashboardMetricsQuerySchema = createTelemetryDashboardMetricsQuerySchema(metricGroupIdSchema);
225
+ // ---- responses ---------------------------------------------------------------
226
+ const envelope = {
227
+ /** Window start, ISO 8601. */
228
+ range: z.object({
229
+ /** Window start, ISO 8601. */
230
+ from: z.string().describe('Window start, ISO 8601.'),
231
+ /** Window end (exclusive), ISO 8601. */
232
+ to: z.string().describe('Window end (exclusive), ISO 8601.'),
233
+ /** Bucket size used for series and sparklines. */
234
+ bucketSeconds: z.number().int().describe('Bucket size used for series and sparklines.'),
235
+ }),
236
+ /** When the telemetry store was read (results are cached for 15 s). */
237
+ generatedAt: z.string().describe('When the telemetry store was read (results are cached for 15 s).'),
238
+ /** A row cap cut a list short. */
239
+ truncated: z.boolean().describe('A row cap cut a list short.'),
240
+ /** The exact statement(s) run, primary first. */
241
+ sql: z
242
+ .union([z.string(), z.array(z.string())])
243
+ .describe('The exact statement(s) run, primary first.'),
244
+ };
245
+ /**
246
+ * The fields every dashboard response except `/metrics` starts with:
247
+ * `range` (`from`, `to`, `bucketSeconds`), `generatedAt`, `truncated` and
248
+ * `sql` (the statement or statements run, primary first).
249
+ *
250
+ * @extensionPoint schema
251
+ * @stability stable
252
+ */
253
+ export const telemetryDashboardEnvelopeSchema = z.object(envelope);
254
+ const tileValue = z.union([z.number(), z.string()]).nullable();
255
+ /**
256
+ * One tile: `key`, `label`, `value`, `previous` (the previous window of equal
257
+ * length), `unit` and `sparkline` (one value per bucket, `null` a gap).
258
+ *
259
+ * @extensionPoint schema
260
+ * @stability stable
261
+ */
262
+ export const telemetryDashboardTileSchema = z.object({
263
+ /** Stable tile key. */
264
+ key: z.string(),
265
+ /** Display label. */
266
+ label: z.string(),
267
+ /** Current window value; null when there is nothing to measure. */
268
+ value: tileValue.describe('Current window value; null when there is nothing to measure.'),
269
+ /** Same measure over the previous window of equal length. */
270
+ previous: tileValue.describe('Same measure over the previous window of equal length.'),
271
+ /** `req/min`, `%`, `ms`, `count`, `bytes` or `timestamp`; metric tiles (`/metrics`) also use `bytes/s`, `per_s`, `per_min`, `seconds`, `hours`, `days`, `cores` and `load`. */
272
+ unit: z
273
+ .string()
274
+ .describe('`req/min`, `%`, `ms`, `count`, `bytes` or `timestamp`; metric tiles (`/metrics`) also use ' +
275
+ '`bytes/s`, `per_s`, `per_min`, `seconds`, `hours`, `days`, `cores` and `load`.'),
276
+ /** One value per bucket of the window; null where unmeasurable. */
277
+ sparkline: z.array(z.number().nullable()).describe('One value per bucket of the window; null where unmeasurable.'),
278
+ });
279
+ /**
280
+ * One unknown API route of the summary (#650): `method`, the normalized
281
+ * `route`, `count`, and how many were `bearer` or `anonymous`.
282
+ *
283
+ * @extensionPoint schema
284
+ * @stability stable
285
+ */
286
+ export const unknownRouteSchema = z.object({
287
+ /** The HTTP method, or `null`. */
288
+ method: z.string().nullable(),
289
+ /** The request path with numeric/UUID/hex segments normalized to `:id` (a route shape, not a value). */
290
+ route: z
291
+ .string()
292
+ .nullable()
293
+ .describe('The request path with numeric/UUID/hex segments normalized to `:id` (a route shape, not a value).'),
294
+ /** Requests to this unknown route in the window. */
295
+ count: z.number().describe('Requests to this unknown route in the window.'),
296
+ /** Of those, requests carrying an `Authorization: Bearer` header (the application's own clients). */
297
+ bearer: z.number().describe('Of those, requests carrying an `Authorization: Bearer` header (the application\'s own clients).'),
298
+ /** Of those, requests without a bearer (typically internet scanners). */
299
+ anonymous: z.number().describe('Of those, requests without a bearer (typically internet scanners).'),
300
+ });
301
+ /**
302
+ * Requests to API routes that do not exist (#650): totals, the previous
303
+ * window's, the top routes and the statements run.
304
+ *
305
+ * @extensionPoint schema
306
+ * @stability stable
307
+ */
308
+ export const telemetryDashboardUnknownRoutesSchema = z
309
+ .object({
310
+ /** Requests answered by the not-found handler (404, no matched route) in the window. */
311
+ requests: z.number().describe('Requests answered by the not-found handler (404, no matched route) in the window.'),
312
+ /** Of those, requests with an `Authorization: Bearer` header. Any of these degrades the verdict. */
313
+ bearer: z.number().describe('Of those, requests with an `Authorization: Bearer` header. Any of these degrades the verdict.'),
314
+ /** Of those, requests without a bearer. Counted, never alarming. */
315
+ anonymous: z.number().describe('Of those, requests without a bearer. Counted, never alarming.'),
316
+ /** `requests` over the previous window of equal length. */
317
+ previousRequests: z.number().describe('`requests` over the previous window of equal length.'),
318
+ /** `bearer` over the previous window of equal length. */
319
+ previousBearer: z.number().describe('`bearer` over the previous window of equal length.'),
320
+ /** Most-hit unknown routes by `METHOD /normalized-path`, bearer requests first (at most 5). */
321
+ topRoutes: z
322
+ .array(unknownRouteSchema)
323
+ .describe('Most-hit unknown routes by `METHOD /normalized-path`, bearer requests first (at most 5).'),
324
+ /** More unknown routes exist than `topRoutes` lists. */
325
+ truncated: z.boolean().describe('More unknown routes exist than `topRoutes` lists.'),
326
+ /** The exact statements run for this block, per-route list first, then the window totals (the same text that also appears in the summary's `sql`), for "Open in Explorer". */
327
+ sql: z
328
+ .array(z.string())
329
+ .describe('The exact statements run for this block, per-route list first, then the window totals ' +
330
+ '(the same text that also appears in the summary\'s `sql`), for "Open in Explorer".'),
331
+ })
332
+ .describe('Requests to API routes that do not exist (issue #650). Absent when the store has no ' +
333
+ '`span_attributes.app.route.matched` column yet (no unknown route recorded since the API started ' +
334
+ 'writing it): the figure is then unknown, not zero.');
335
+ /**
336
+ * `GET …/dashboard/summary`: the envelope plus `verdict` (`level` and
337
+ * `reasons`), `tiles`, `runtime` (optional) and `unknownRoutes` (optional).
338
+ *
339
+ * @extensionPoint schema
340
+ * @stability stable
341
+ */
342
+ export const telemetryDashboardSummarySchema = z.object({
343
+ ...envelope,
344
+ /** The overall health verdict. */
345
+ verdict: z.object({
346
+ /** How healthy the window is. */
347
+ level: wireEnum(VERDICT_LEVELS),
348
+ /** Why, one line per rule that fired. */
349
+ reasons: z.array(z.string()),
350
+ }),
351
+ /** Fixed tiles: `requestsPerMin`, `errorRatePct`, `p95Ms`, `errorLogs`, `warnLogs`, `unknownRoutes` (requests to unknown API routes; value null when the store cannot tell yet, see `unknownRoutes`) and `lastDataAt`. */
352
+ tiles: z
353
+ .array(telemetryDashboardTileSchema)
354
+ .describe('Fixed tiles: `requestsPerMin`, `errorRatePct`, `p95Ms`, `errorLogs`, `warnLogs`, `unknownRoutes` ' +
355
+ '(requests to unknown API routes; value null when the store cannot tell yet, see `unknownRoutes`) and `lastDataAt`.'),
356
+ /** Heap used and event-loop delay p99, when the runtime metric tables exist. Not filtered by instance. */
357
+ runtime: z
358
+ .array(telemetryDashboardTileSchema)
359
+ .optional()
360
+ .describe('Heap used and event-loop delay p99, when the runtime metric tables exist. Not filtered by instance.'),
361
+ /** Requests to unknown API routes; absent while the store cannot tell. */
362
+ unknownRoutes: telemetryDashboardUnknownRoutesSchema.optional(),
363
+ });
364
+ /**
365
+ * One `api` timeseries bucket: `t`, the status classes and `p95Ms`.
366
+ *
367
+ * @extensionPoint schema
368
+ * @stability stable
369
+ */
370
+ export const apiBucketSchema = z.object({
371
+ /** Bucket start, ISO 8601. */
372
+ t: z.string(),
373
+ /** 2xx responses. */
374
+ s2xx: z.number(),
375
+ /** 3xx responses. */
376
+ s3xx: z.number(),
377
+ /** 4xx responses. */
378
+ s4xx: z.number(),
379
+ /** 5xx responses. */
380
+ s5xx: z.number(),
381
+ /** 95th percentile latency in ms, or `null` with no requests. */
382
+ p95Ms: z.number().nullable(),
383
+ });
384
+ /**
385
+ * One `logs` timeseries bucket: `t` and the severity bands.
386
+ *
387
+ * @extensionPoint schema
388
+ * @stability stable
389
+ */
390
+ export const logsBucketSchema = z.object({
391
+ /** Bucket start, ISO 8601. */
392
+ t: z.string(),
393
+ /** Error-level log records. */
394
+ error: z.number(),
395
+ /** Warn-level log records. */
396
+ warn: z.number(),
397
+ /** Info-level log records. */
398
+ info: z.number(),
399
+ /** Records of any other (or no) severity. */
400
+ other: z.number(),
401
+ });
402
+ /**
403
+ * `GET …/dashboard/timeseries`: the envelope plus `panel` and `buckets` (api
404
+ * buckets for `api`, logs buckets for `logs`).
405
+ *
406
+ * @extensionPoint schema
407
+ * @stability stable
408
+ */
409
+ export const telemetryDashboardTimeseriesSchema = z.object({
410
+ ...envelope,
411
+ /** The panel requested. */
412
+ panel: wireEnum(DASHBOARD_PANELS),
413
+ /** One entry per bucket: api buckets for `api`, logs buckets for `logs`. */
414
+ buckets: z.union([z.array(apiBucketSchema), z.array(logsBucketSchema)]),
415
+ });
416
+ /**
417
+ * One route of `top?kind=routes`: request counts, 5xx `errors`, 4xx
418
+ * `clientErrors`, `unknownRequests`, `unknown` and `p95Ms`.
419
+ *
420
+ * @extensionPoint schema
421
+ * @stability stable
422
+ */
423
+ export const topRouteSchema = z.object({
424
+ /** The HTTP method, or `null`. */
425
+ method: z.string().nullable(),
426
+ /** The request path with numeric/UUID/hex segments normalized to `:id`. */
427
+ route: z.string().nullable().describe('The request path with numeric/UUID/hex segments normalized to `:id`.'),
428
+ /** Requests in the window. */
429
+ count: z.number(),
430
+ /** 5xx responses. */
431
+ errors: z.number().describe('5xx responses.'),
432
+ /** `errors` as a percentage of `count`. */
433
+ errorRatePct: z.number(),
434
+ /** 4xx responses except 401 (an expired access token is routine), unknown routes included. */
435
+ clientErrors: z.number().describe('4xx responses except 401 (an expired access token is routine), unknown routes included.'),
436
+ /** Requests answered by the not-found handler: no API route matches this method and path. 0 when the store cannot tell yet. */
437
+ unknownRequests: z
438
+ .number()
439
+ .describe('Requests answered by the not-found handler: no API route matches this method and path. 0 when the store cannot tell yet.'),
440
+ /** `unknownRequests > 0`: this method + path is not a route of the running API build. */
441
+ unknown: z.boolean().describe('`unknownRequests > 0`: this method + path is not a route of the running API build.'),
442
+ /** 95th percentile latency in ms, or `null`. */
443
+ p95Ms: z.number().nullable(),
444
+ });
445
+ /**
446
+ * One error message of `top?kind=errors`: `message`, `count`, first and last
447
+ * seen, a sample trace and the `service`.
448
+ *
449
+ * @extensionPoint schema
450
+ * @stability stable
451
+ */
452
+ export const topErrorSchema = z.object({
453
+ /** The grouped log message, or `null`. */
454
+ message: z.string().nullable(),
455
+ /** Occurrences in the window. */
456
+ count: z.number(),
457
+ /** First occurrence in the window, or `null`. */
458
+ firstSeen: z.string().nullable(),
459
+ /** Last occurrence in the window, or `null`. */
460
+ lastSeen: z.string().nullable(),
461
+ /** A trace of one occurrence, or `null`. */
462
+ sampleTraceId: z.string().nullable(),
463
+ /** The service that logged it, or `null`. */
464
+ service: z.string().nullable(),
465
+ });
466
+ /**
467
+ * `GET …/dashboard/top`: the envelope plus `kind` and `items` (routes for
468
+ * `routes`, errors for `errors`).
469
+ *
470
+ * @extensionPoint schema
471
+ * @stability stable
472
+ */
473
+ export const telemetryDashboardTopSchema = z.object({
474
+ ...envelope,
475
+ /** The kind requested. */
476
+ kind: wireEnum(DASHBOARD_TOP_KINDS),
477
+ /** Routes for `routes`, error messages for `errors`. */
478
+ items: z.union([z.array(topRouteSchema), z.array(topErrorSchema)]),
479
+ });
480
+ /**
481
+ * One log event: `timestamp` (full precision, UTC), `severity`, `service`,
482
+ * `body`, `traceId` and `spanId`.
483
+ *
484
+ * @extensionPoint schema
485
+ * @stability stable
486
+ */
487
+ export const dashboardEventSchema = z.object({
488
+ /** Full precision (up to nanoseconds), UTC. */
489
+ timestamp: z.string().describe('Full precision (up to nanoseconds), UTC.'),
490
+ /** Lower-case severity text (`error`, `warn`, `info`, `debug`, …). */
491
+ severity: z.string(),
492
+ /** The emitting service, or `null`. */
493
+ service: z.string().nullable(),
494
+ /** The log body, or `null`. */
495
+ body: z.string().nullable(),
496
+ /** The trace it belongs to, or `null`. */
497
+ traceId: z.string().nullable(),
498
+ /** The span it belongs to, or `null`. */
499
+ spanId: z.string().nullable(),
500
+ });
501
+ /**
502
+ * `GET …/dashboard/events`: the envelope plus `items` and `nextCursor`.
503
+ *
504
+ * @extensionPoint schema
505
+ * @stability stable
506
+ */
507
+ export const telemetryDashboardEventsSchema = z.object({
508
+ ...envelope,
509
+ /** The events, newest first. */
510
+ items: z.array(dashboardEventSchema),
511
+ /** The `cursor` of the next page, or `null` on the last. */
512
+ nextCursor: z.string().nullable(),
513
+ });
514
+ /**
515
+ * `GET …/dashboard/filters`: the envelope plus the `services`, `instances`
516
+ * and `hosts` seen in the window.
517
+ *
518
+ * @extensionPoint schema
519
+ * @stability stable
520
+ */
521
+ export const telemetryDashboardFiltersSchema = z.object({
522
+ ...envelope,
523
+ /** Services seen in the window: the values `service` accepts. */
524
+ services: z.array(z.string()),
525
+ /** Instances (`app.instance.id`) seen in the window: the values `instance` accepts. */
526
+ instances: z.array(z.string()),
527
+ /** Host names (`host_name`) seen in the host metrics — the values `/metrics` `host` accepts. */
528
+ hosts: z
529
+ .array(z.string())
530
+ .describe('Host names (`host_name`) seen in the host metrics — the values `/metrics` `host` accepts.'),
531
+ });
532
+ // ---- metrics (#601) ------------------------------------------------------------
533
+ const metricUnit = wireEnum(METRIC_UNITS);
534
+ /**
535
+ * One point of a metric series: `t` (bucket start) and `v` (`null` where
536
+ * nothing was measured).
537
+ *
538
+ * @extensionPoint schema
539
+ * @stability stable
540
+ */
541
+ export const metricPointSchema = z.object({
542
+ /** Bucket start, ISO 8601. */
543
+ t: z.string().describe('Bucket start, ISO 8601.'),
544
+ /** The value, or `null` where nothing was measured: a gap, not a zero. */
545
+ v: z.number().nullable(),
546
+ });
547
+ /**
548
+ * One metric series: `key`, `label`, `unit`, `dimension`, `groupBy` and
549
+ * `points` (one per bucket).
550
+ *
551
+ * @extensionPoint schema
552
+ * @stability stable
553
+ */
554
+ export const metricSeriesSchema = z.object({
555
+ /** The catalog family or ratio key. */
556
+ key: z.string().describe('The catalog family or ratio key.'),
557
+ /** Display label. */
558
+ label: z.string(),
559
+ /** Display unit of `points`. */
560
+ unit: metricUnit,
561
+ /** The label column the family is split by (e.g. `mountpoint`), or null. */
562
+ dimension: z.string().nullable().describe('The label column the family is split by (e.g. `mountpoint`), or null.'),
563
+ /** This series' value of `dimension` (e.g. `/`), or null. */
564
+ groupBy: z.string().nullable().describe("This series' value of `dimension` (e.g. `/`), or null."),
565
+ /** One point per bucket of the window; null where nothing was measured. */
566
+ points: z
567
+ .array(metricPointSchema)
568
+ .describe('One point per bucket of the window; null where nothing was measured.'),
569
+ });
570
+ /**
571
+ * One column of a metric table: `key`, `label` and `unit`.
572
+ *
573
+ * @extensionPoint schema
574
+ * @stability stable
575
+ */
576
+ export const metricColumnSchema = z.object({
577
+ /** The column key (a key of each row). */
578
+ key: z.string(),
579
+ /** Display label. */
580
+ label: z.string(),
581
+ /** Display unit of the column's values. */
582
+ unit: metricUnit,
583
+ });
584
+ /**
585
+ * One cell of a metric table row: a string, number, boolean or `null`.
586
+ *
587
+ * @extensionPoint schema
588
+ * @stability stable
589
+ */
590
+ export const metricCellSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
591
+ /**
592
+ * One per-key metric table: `key`, `label`, `columns` and `rows`.
593
+ *
594
+ * @extensionPoint schema
595
+ * @stability stable
596
+ */
597
+ export const metricTableSchema = z.object({
598
+ /** The table key. */
599
+ key: z.string(),
600
+ /** Display label. */
601
+ label: z.string(),
602
+ /** The columns, `key` first. */
603
+ columns: z.array(metricColumnSchema),
604
+ /** One row per key; `key` holds the key column, then one entry per column. At most 50 rows (500 for `largestTables`); `truncated` says a cap cut the list. */
605
+ rows: z
606
+ .array(z.record(z.string(), metricCellSchema))
607
+ .describe('One row per key; `key` holds the key column, then one entry per column. At most 50 rows ' +
608
+ '(500 for `largestTables`); `truncated` says a cap cut the list.'),
609
+ });
610
+ /**
611
+ * Builds the `GET …/dashboard/metrics` response schema around a `group`
612
+ * schema: `range`, `generatedAt`, `truncated`, `sql`, `group`, `available`,
613
+ * `tiles`, `series`, `tables`, `skipped` and the optional `freshMs`.
614
+ *
615
+ * @extensionPoint schema
616
+ * @stability stable
617
+ */
618
+ export function createTelemetryDashboardMetricsSchema(group) {
619
+ return z.object({
620
+ /** The window read. */
621
+ range: envelope.range,
622
+ /** When the telemetry store was read. */
623
+ generatedAt: envelope.generatedAt,
624
+ /** A series, table or histogram row cap cut a list short. */
625
+ truncated: z.boolean().describe('A series, table or histogram row cap cut a list short.'),
626
+ /** The exact statements run, in order — for "Open in Explorer". */
627
+ sql: z.array(z.string()).describe('The exact statements run, in order — for "Open in Explorer".'),
628
+ /** The metric group served: the schema passed in. */
629
+ group,
630
+ /** At least one family or table of the group has its table in the store. */
631
+ available: z.boolean().describe('At least one family or table of the group has its table in the store.'),
632
+ /** The group's tiles. */
633
+ tiles: z.array(telemetryDashboardTileSchema),
634
+ /** The group's series. */
635
+ series: z.array(metricSeriesSchema),
636
+ /** The group's per-key tables. */
637
+ tables: z.array(metricTableSchema),
638
+ /** Catalog keys (families, ratios, tables) skipped because a table or column they need is absent. */
639
+ skipped: z
640
+ .array(z.string())
641
+ .describe('Catalog keys (families, ratios, tables) skipped because a table or column they need is absent.'),
642
+ /** The freshness window in milliseconds: a table cell older than its table's newest reading by more than this is not current. Absent from servers that predate it. */
643
+ freshMs: z
644
+ .number()
645
+ .int()
646
+ .positive()
647
+ .optional()
648
+ .describe("The freshness window in milliseconds: a table cell older than its table's newest reading by more than " +
649
+ 'this is not current. Absent from servers that predate it.'),
650
+ });
651
+ }
652
+ /**
653
+ * `GET …/dashboard/metrics`, with `group` any metric group id
654
+ * ({@link metricGroupIdSchema}).
655
+ *
656
+ * @extensionPoint schema
657
+ * @stability stable
658
+ */
659
+ export const telemetryDashboardMetricsSchema = createTelemetryDashboardMetricsSchema(metricGroupIdSchema);
660
+ // ---- metric groups (#680) ------------------------------------------------------
661
+ /**
662
+ * One registered metric group: `id`, `label`, `title` and `order`.
663
+ *
664
+ * @extensionPoint schema
665
+ * @stability stable
666
+ */
667
+ export const telemetryDashboardMetricGroupSchema = z.object({
668
+ /** The group id: the `/metrics` `group` value and the section anchor. */
669
+ id: z.string().describe('The group id: the `/metrics` `group` value and the section anchor.'),
670
+ /** The API label (e.g. `Host`). */
671
+ label: z.string().describe('The API label (e.g. `Host`).'),
672
+ /** The dashboard section title (e.g. `Infrastructure`). */
673
+ title: z.string().describe('The dashboard section title (e.g. `Infrastructure`).'),
674
+ /** Dashboard order, ascending; `data` is already sorted by it (ties by id). */
675
+ order: z.number().describe('Dashboard order, ascending; `data` is already sorted by it (ties by id).'),
676
+ });
677
+ /**
678
+ * `GET …/dashboard/metric-groups`: `data`, every registered group in
679
+ * dashboard order.
680
+ *
681
+ * @extensionPoint schema
682
+ * @stability stable
683
+ */
684
+ export const telemetryDashboardMetricGroupsSchema = z.object({
685
+ /** Every registered metric group (the platform's and the app's), in dashboard order. */
686
+ data: z
687
+ .array(telemetryDashboardMetricGroupSchema)
688
+ .describe('Every registered metric group (the platform\'s and the app\'s), in dashboard order.'),
689
+ });
690
+ //# sourceMappingURL=dashboard.js.map