@jeffjassky/telemetry 0.3.0 → 0.4.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.
- package/README.md +3 -1
- package/dist/index.cjs +1437 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1423 -64
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +1468 -78
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +1468 -78
- package/dist/mcp.js.map +1 -1
- package/dist/ui/_assets/{index-CXiDp_v6.css → index-COswHpSX.css} +1 -1
- package/dist/ui/_assets/index-Deqhu-hT.js +41 -0
- package/dist/ui/_assets/index-Deqhu-hT.js.map +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/types/index.d.ts +484 -11
- package/types/test-d.ts +173 -2
- package/dist/ui/_assets/index-3jcToTuP.js +0 -41
- package/dist/ui/_assets/index-3jcToTuP.js.map +0 -1
package/dist/ui/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Telemetry</title>
|
|
7
7
|
<!--telemetry-config-->
|
|
8
|
-
<script type="module" crossorigin src="./_assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="./_assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./_assets/index-Deqhu-hT.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="./_assets/index-COswHpSX.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="telemetry-root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffjassky/telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Unified telemetry — product events, errors, traces, state transitions, and billable usage in one Mongo envelope, with typed SDKs and a mountable dashboard.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jeff Jassky <jeff@jeffjassky.com>",
|
package/types/index.d.ts
CHANGED
|
@@ -166,6 +166,351 @@ export declare function boundedMeta(): z.ZodType<Record<string, unknown> | undef
|
|
|
166
166
|
/** Boot-time contract checks — throws on misconfiguration. createTelemetry runs it. */
|
|
167
167
|
export declare function validateRegistry(registry: Registry): void;
|
|
168
168
|
|
|
169
|
+
// ── catalog (reports §3) ────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Everything a reader can ask this instance, inferred from the registry alone:
|
|
173
|
+
* typed dimensions with their value domains, the measures each event supports,
|
|
174
|
+
* and which rollup family answers a sum exactly. Pure and boot-time, like
|
|
175
|
+
* validateRegistry — createDashboard() and createTelemetryMcp() build one each
|
|
176
|
+
* and serve it beside the projection.
|
|
177
|
+
*/
|
|
178
|
+
export interface Catalog {
|
|
179
|
+
events: Record<string, EventFacet>;
|
|
180
|
+
families: Record<string, FamilyFacet>;
|
|
181
|
+
/** name prefix before the first '.' → event names. An undotted name namespaces to itself. */
|
|
182
|
+
namespaces: Record<string, string[]>;
|
|
183
|
+
/** dims every record carries — filterable and groupable raw, whatever the source */
|
|
184
|
+
envelope: DimFacet[];
|
|
185
|
+
/** every subject type named by any spec's `subjects` or any rollup's `subjects` */
|
|
186
|
+
subjectTypes: string[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface EventFacet {
|
|
190
|
+
kind: TelemetryKind;
|
|
191
|
+
origin: Origin | 'any';
|
|
192
|
+
subjects: string[];
|
|
193
|
+
description: string;
|
|
194
|
+
namespace: string;
|
|
195
|
+
/** one per declared attr, typed, followed by this kind's own envelope fields */
|
|
196
|
+
dims: DimFacet[];
|
|
197
|
+
/** 'count' first, then per metric key: sum:, avg:, p50:, p95:, p99: */
|
|
198
|
+
measures: MeasureFacet[];
|
|
199
|
+
/** rollup family names this event feeds (its `as`, or its own name) */
|
|
200
|
+
families: string[];
|
|
201
|
+
indexedAttrs: string[];
|
|
202
|
+
indexedMetrics: string[];
|
|
203
|
+
/** the EFFECTIVE retention — the spec's override, else RETENTION_DAYS[kind] */
|
|
204
|
+
retentionDays: number | null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface FamilyFacet {
|
|
208
|
+
as: string;
|
|
209
|
+
/** the grain, in declared order (pinned per family by validateRegistry) */
|
|
210
|
+
by: DimSource[];
|
|
211
|
+
/** rollups.ts `label(src)` per dim — the `x=` prefix written into `dims` */
|
|
212
|
+
labels: string[];
|
|
213
|
+
bucket: 'hour' | 'day' | 'week' | 'month' | null;
|
|
214
|
+
/** a lifetime rollup has no bucket, and its `firstAt` IS the milestone */
|
|
215
|
+
lifetime: boolean;
|
|
216
|
+
/** the spec's `subjects` when `by` has a subject dim, else [] */
|
|
217
|
+
subjectTypes: string[];
|
|
218
|
+
sums: string[];
|
|
219
|
+
/** labels of `capture` sources */
|
|
220
|
+
capture: string[];
|
|
221
|
+
/** event names declaring this family, registry order */
|
|
222
|
+
feeders: string[];
|
|
223
|
+
retentionDays: number | null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface DimFacet {
|
|
227
|
+
/**
|
|
228
|
+
* The DimSource form, so it passes straight through to a rollup `by`, to a
|
|
229
|
+
* groupBy, and to a filter term: 'attr:model' | 'field:client.platform' |
|
|
230
|
+
* 'subjectType' | 'actorType'.
|
|
231
|
+
*/
|
|
232
|
+
key: string;
|
|
233
|
+
/** what rollups.ts writes before '=' — 'model', 'client.platform'; for the two pseudo-dims, the key */
|
|
234
|
+
label: string;
|
|
235
|
+
type: 'string' | 'enum' | 'number' | 'boolean' | 'date';
|
|
236
|
+
/** closed domain when known: z.enum / z.literal values, envelope enums */
|
|
237
|
+
values?: string[];
|
|
238
|
+
optional: boolean;
|
|
239
|
+
/** true when a real index exists — an `indexedAttrs` attr, or a base-indexed envelope field */
|
|
240
|
+
indexed: boolean;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface MeasureFacet {
|
|
244
|
+
/** 'count' | 'sum:cost_usd' | 'avg:cost_usd' | 'p95:duration_ms' … */
|
|
245
|
+
key: string;
|
|
246
|
+
metric?: string;
|
|
247
|
+
/** families whose `sum` carries this metric — exact answers. Only 'sum:' keys ever have one. */
|
|
248
|
+
exactVia: string[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface DeriveCatalogOptions {
|
|
252
|
+
/** host additions to `client.platform`, exactly as CreateTelemetryConfig.platforms extends them */
|
|
253
|
+
platforms?: readonly string[];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** the projection `/api/registry` and `describe_telemetry` have always returned */
|
|
257
|
+
export interface RegistryProjectionEntry {
|
|
258
|
+
kind: TelemetryKind;
|
|
259
|
+
origin: Origin | 'any';
|
|
260
|
+
subjects: string[];
|
|
261
|
+
description: string;
|
|
262
|
+
attrKeys: string[];
|
|
263
|
+
metricKeys: string[];
|
|
264
|
+
indexedAttrs: string[];
|
|
265
|
+
indexedMetrics: string[];
|
|
266
|
+
rollups: {
|
|
267
|
+
as: string;
|
|
268
|
+
by: DimSource[];
|
|
269
|
+
bucket: 'hour' | 'day' | 'week' | 'month' | null;
|
|
270
|
+
sum: string[];
|
|
271
|
+
subjects: string[];
|
|
272
|
+
}[];
|
|
273
|
+
}
|
|
274
|
+
export type RegistryProjection = Record<string, RegistryProjectionEntry>;
|
|
275
|
+
|
|
276
|
+
/** Pure. No Mongo, no I/O — derive once at boot and cache it on the instance. */
|
|
277
|
+
export declare function deriveCatalog(registry: Registry, opts?: DeriveCatalogOptions): Catalog;
|
|
278
|
+
|
|
279
|
+
/** the catalog narrowed back to the projection, so adding it costs the SPA nothing */
|
|
280
|
+
export declare function projectRegistry(catalog: Catalog): RegistryProjection;
|
|
281
|
+
|
|
282
|
+
// ── suggestions (reports §9) ────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* One registry edit the data is asking for. `message` is the sentence a human
|
|
286
|
+
* reads; `fix` is the line they paste. Nothing here writes anything — the host
|
|
287
|
+
* still edits the registry by hand, the package just stops making it guess.
|
|
288
|
+
*/
|
|
289
|
+
export interface Suggestion {
|
|
290
|
+
kind: 'undeclared_attr' | 'missing_dim_default' | 'unregistered_event';
|
|
291
|
+
/** the registry entry to touch — an event name, or a rollup family name */
|
|
292
|
+
target: string;
|
|
293
|
+
/** attr key or dim label, when the suggestion is about one */
|
|
294
|
+
key?: string;
|
|
295
|
+
count: number;
|
|
296
|
+
message: string;
|
|
297
|
+
/** the registry change, as code */
|
|
298
|
+
fix: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface DeriveSuggestionsInput {
|
|
302
|
+
counters: TelemetryCounters;
|
|
303
|
+
catalog: Catalog;
|
|
304
|
+
/** the quarantine rows the caller already fetched — only `name` and `reason` are read */
|
|
305
|
+
quarantine?: readonly { name?: unknown; reason?: unknown; [k: string]: unknown }[];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Pure, like deriveCatalog: counters + catalog + quarantine in, registry lines
|
|
310
|
+
* out. Served on `GET /api/system` and by the `telemetry_health` MCP tool.
|
|
311
|
+
*/
|
|
312
|
+
export declare function deriveSuggestions(input: DeriveSuggestionsInput): Suggestion[];
|
|
313
|
+
|
|
314
|
+
/** the returned list is capped here — a System page is a thing a human reads */
|
|
315
|
+
export declare const MAX_SUGGESTIONS: 50;
|
|
316
|
+
|
|
317
|
+
// ── reports (reports §4, §6) ────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* A Report is one shape: what a page renders, what a saved view stores, what a
|
|
321
|
+
* URL hash carries, and what `run_report` executes. `resolveReport` turns one
|
|
322
|
+
* into a Plan — the cheapest primitive that answers it exactly, a raw plan when
|
|
323
|
+
* nothing can, and a refusal with a reason when nothing at all can.
|
|
324
|
+
*/
|
|
325
|
+
export type ReportSource =
|
|
326
|
+
| { event: string }
|
|
327
|
+
| { namespace: string }
|
|
328
|
+
| { kind: TelemetryKind }
|
|
329
|
+
| { family: string };
|
|
330
|
+
|
|
331
|
+
/** a shorthand from the UI's RANGES ('7d'), or an explicit half-open ISO pair */
|
|
332
|
+
export type ReportRange = string | { from: string; to: string };
|
|
333
|
+
|
|
334
|
+
export interface ReportFilter {
|
|
335
|
+
/** a DimFacet.key: 'attr:model' | 'field:env' | 'subjectType' | 'field:name' … */
|
|
336
|
+
dim: string;
|
|
337
|
+
op: 'eq' | 'in' | 'gte' | 'lte';
|
|
338
|
+
value: string | string[] | number;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface Report {
|
|
342
|
+
source: ReportSource;
|
|
343
|
+
range: ReportRange;
|
|
344
|
+
interval?: 'hour' | 'day' | 'week' | 'month';
|
|
345
|
+
/** a MeasureFacet.key. Default 'count'; also 'distinct:<subjectType>' and 'funnel' */
|
|
346
|
+
measure?: string;
|
|
347
|
+
/** DimFacet.key[], at most two */
|
|
348
|
+
groupBy?: string[];
|
|
349
|
+
filters?: ReportFilter[];
|
|
350
|
+
excludeActorTypes?: string[];
|
|
351
|
+
/** a rendering hint carried with the Report; no primitive takes it today */
|
|
352
|
+
sort?: 'value' | 'label' | 'time';
|
|
353
|
+
limit?: number;
|
|
354
|
+
/** same length, immediately before */
|
|
355
|
+
compare?: 'previous';
|
|
356
|
+
|
|
357
|
+
// ── funnel-only (`measure: 'funnel'`) ──
|
|
358
|
+
stages?: string[];
|
|
359
|
+
anchor?: string;
|
|
360
|
+
exits?: string[];
|
|
361
|
+
subjectType?: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The pre-Report `ViewSpec.query`, still parsed and lifted by normalizeQuery().
|
|
366
|
+
* Its `display` key is removed — the renderer decides from the Report itself
|
|
367
|
+
* (reports §8) — and a stored view still carrying one keeps parsing.
|
|
368
|
+
* @deprecated write a Report.
|
|
369
|
+
*/
|
|
370
|
+
export interface LegacyQuery {
|
|
371
|
+
range?: string;
|
|
372
|
+
filters?: Record<string, unknown>;
|
|
373
|
+
groupBy?: string;
|
|
374
|
+
sort?: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export type PlanPrimitive =
|
|
378
|
+
| 'records' | 'series' | 'breakdown' | 'distribution'
|
|
379
|
+
| 'rollups' | 'distinctCount' | 'funnel';
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* How the executor folds a `rollups` plan: `rollups()` has no server-side
|
|
383
|
+
* groupBy, and the requested dims ARE the family's dims, so the grouping is a
|
|
384
|
+
* fold over the returned rows. `labels[i]` is the `dims` prefix rollups.ts
|
|
385
|
+
* writes for `groupBy[i]`; a subject dim labels to 'subject' and its stored
|
|
386
|
+
* value is the bare `type:id` ref.
|
|
387
|
+
*/
|
|
388
|
+
export interface PlanShape {
|
|
389
|
+
groupBy: string[];
|
|
390
|
+
labels: string[];
|
|
391
|
+
measure: string;
|
|
392
|
+
interval?: 'hour' | 'day' | 'week' | 'month';
|
|
393
|
+
filters?: { dim: string; label: string; op: ReportFilter['op']; value: ReportFilter['value'] }[];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export interface Plan {
|
|
397
|
+
primitive: PlanPrimitive;
|
|
398
|
+
/** positional args AFTER scope — the executor is literally `q[primitive](scope, ...args)` */
|
|
399
|
+
args: unknown[];
|
|
400
|
+
exactness: 'exact' | 'raw' | 'scan';
|
|
401
|
+
/** the family that answers it, when one does */
|
|
402
|
+
via?: string;
|
|
403
|
+
/** human sentence — the UI badge and the MCP explanation */
|
|
404
|
+
why: string;
|
|
405
|
+
/** how to fold the rows a `rollups` plan returns */
|
|
406
|
+
shape?: PlanShape;
|
|
407
|
+
/** present when `compare: 'previous'` — same primitive, range shifted back by its own length */
|
|
408
|
+
previous?: { args: unknown[] };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export interface Unavailable {
|
|
412
|
+
unavailable: true;
|
|
413
|
+
why: string;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export interface ResolveOptions {
|
|
417
|
+
/** injected so a plan is deterministic — shorthand ranges end here */
|
|
418
|
+
now?: Date;
|
|
419
|
+
limits?: Partial<QueryLimits>;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Report → Plan, pure. No Mongo, no I/O, deterministic given `now` — pinned by
|
|
424
|
+
* unit tests like deriveCatalog and summarizeStages.
|
|
425
|
+
*/
|
|
426
|
+
export declare function resolveReport(
|
|
427
|
+
report: Report,
|
|
428
|
+
catalog: Catalog,
|
|
429
|
+
opts?: ResolveOptions,
|
|
430
|
+
): Plan | Unavailable;
|
|
431
|
+
|
|
432
|
+
/** lift a stored view's legacy query onto a Report. null when nothing names a source. */
|
|
433
|
+
export declare function normalizeQuery(query: Report | LegacyQuery | null | undefined): Report | null;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* A Report is a URL, and these two are inverses: `parseReportQuery(reportToQuery(r))`
|
|
437
|
+
* deep-equals `r`. `source=event:<name>|namespace:<ns>|kind:<kind>|family:<as>`,
|
|
438
|
+
* `range=7d` or `from`+`to`, `groupBy` and `excludeActors` comma-separated, and
|
|
439
|
+
* `filter=<dim>:<op>:<value>` REPEATED — the dim may itself contain a colon, so
|
|
440
|
+
* the first `eq`/`in`/`gte`/`lte` token ends it. Unknown params are ignored; a
|
|
441
|
+
* malformed one throws with `status: 400` naming the param.
|
|
442
|
+
*/
|
|
443
|
+
export declare function parseReportQuery(query: Record<string, unknown>): Report;
|
|
444
|
+
export declare function reportToQuery(report: Report): Record<string, string | string[]>;
|
|
445
|
+
|
|
446
|
+
// ── the executor ────────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
export interface ExecuteOptions {
|
|
449
|
+
/** injected so a plan is deterministic — shorthand ranges end here */
|
|
450
|
+
now?: Date;
|
|
451
|
+
limits?: Partial<QueryLimits>;
|
|
452
|
+
/** applied to a `records` plan's items before they leave (mcp.ts passes its redactor) */
|
|
453
|
+
redact?: (items: any[]) => any[];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export interface ReportResult {
|
|
457
|
+
/** the Report as executed — after a legacy lift, so the caller sees what ran */
|
|
458
|
+
report: Report;
|
|
459
|
+
plan: Plan;
|
|
460
|
+
/** the primitive's own result, EXCEPT a `rollups` plan, which arrives folded */
|
|
461
|
+
result: unknown;
|
|
462
|
+
/** present when `compare: 'previous'` — the same call, range shifted back */
|
|
463
|
+
previous?: unknown;
|
|
464
|
+
dataSource: 'raw' | 'rollups' | 'raw+rollups';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** the fields the fold reads off a rollup doc */
|
|
468
|
+
export interface RollupDoc {
|
|
469
|
+
/** dimension values in the family's `by` order: 'region=eu', or a bare 'user:u_1' */
|
|
470
|
+
dims: string[];
|
|
471
|
+
bucketAt?: Date | string | null;
|
|
472
|
+
count?: number;
|
|
473
|
+
sums?: Record<string, number> | Map<string, number> | null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** what `breakdown()` returns, answered from the rollup store instead */
|
|
477
|
+
export interface FoldedRollups {
|
|
478
|
+
rows: Array<{ dims: (string | null)[]; at?: Date; value: number }>;
|
|
479
|
+
groups: number;
|
|
480
|
+
truncated: boolean;
|
|
481
|
+
dataSource: 'rollups';
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Report → the answer: resolve, then `q[plan.primitive](scope, ...plan.args)`.
|
|
486
|
+
* An `Unavailable` throws with `status: 400` and the `why` as its message —
|
|
487
|
+
* a refusal is an answer to `resolveReport`, but not to someone asking for data.
|
|
488
|
+
*/
|
|
489
|
+
export declare function executeReport(
|
|
490
|
+
q: Queries,
|
|
491
|
+
scope: string,
|
|
492
|
+
report: Report,
|
|
493
|
+
catalog: Catalog,
|
|
494
|
+
opts?: ExecuteOptions,
|
|
495
|
+
): Promise<ReportResult>;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Fold a `rollups` plan's docs into the row shape `breakdown()` returns, per
|
|
499
|
+
* `Plan.shape` — the family's docs ARE the groups, so this is arithmetic rather
|
|
500
|
+
* than a second read, and a renderer never learns which store answered. Pure.
|
|
501
|
+
*/
|
|
502
|
+
export declare function foldRollups(
|
|
503
|
+
rows: readonly RollupDoc[],
|
|
504
|
+
shape: PlanShape,
|
|
505
|
+
truncated?: boolean,
|
|
506
|
+
): FoldedRollups;
|
|
507
|
+
|
|
508
|
+
/** '7d' → a half-open pair ending at `now`; an ISO pair validated. Throws `status: 400`. */
|
|
509
|
+
export declare function rangeOf(range: ReportRange, now?: Date): TimeRange;
|
|
510
|
+
|
|
511
|
+
/** the interval that keeps a range under ~120 buckets — util.js `intervalFor`, for pairs too */
|
|
512
|
+
export declare function intervalForRange(range: ReportRange, now?: Date): 'hour' | 'day' | 'week' | 'month';
|
|
513
|
+
|
|
169
514
|
// ── typed emit ──────────────────────────────────────────────────────────────
|
|
170
515
|
|
|
171
516
|
export type AttrsOf<R extends Registry, N extends keyof R> =
|
|
@@ -275,8 +620,29 @@ export interface TelemetryCounters {
|
|
|
275
620
|
deduped: number;
|
|
276
621
|
/** `body` values clipped to the cap — the row survives, marked */
|
|
277
622
|
truncated: number;
|
|
623
|
+
/**
|
|
624
|
+
* `rollupSkipped`, attributed: `${family}|${dimLabel}` → count. Which family
|
|
625
|
+
* dropped which dim, so the scalar becomes a `dimDefault` you can go and
|
|
626
|
+
* declare. The seven numbers above are unchanged — this is additive.
|
|
627
|
+
*/
|
|
628
|
+
rollupSkippedBy: Record<string, number>;
|
|
629
|
+
/**
|
|
630
|
+
* attrs keys a record carried that its spec does not declare:
|
|
631
|
+
* `${name}|${key}` → count. Those records are REJECTED by the strict parse,
|
|
632
|
+
* not stripped; this groups what the quarantine lists one row at a time.
|
|
633
|
+
*/
|
|
634
|
+
undeclaredAttrs: Record<string, number>;
|
|
278
635
|
}
|
|
279
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Distinct keys either attributed map holds before new ones fold into a single
|
|
639
|
+
* `(other)` bucket. Both are keyed on client-controlled data, so the bound is
|
|
640
|
+
* what stops a hostile client growing the process heap; the totals stay
|
|
641
|
+
* honest, only the attribution stops.
|
|
642
|
+
*/
|
|
643
|
+
export declare const COUNTER_MAP_MAX: 1000;
|
|
644
|
+
export declare const COUNTER_OVERFLOW_KEY: '(other)|(other)';
|
|
645
|
+
|
|
280
646
|
/** What emit() did. `Promise<void>` could not distinguish "written" from "queued". */
|
|
281
647
|
export interface EmitResult {
|
|
282
648
|
/** the record _id — usable for correlation even when the row was not stored */
|
|
@@ -470,12 +836,33 @@ export declare function createIngest(opts: CreateIngestOptions): import('express
|
|
|
470
836
|
|
|
471
837
|
// ── dashboard (dashboards §2–§8) ────────────────────────────────────────────
|
|
472
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Two kinds of cap, and the word "limit" hides the difference. An OUTPUT cap
|
|
841
|
+
* bounds what the response CONTAINS — its `$limit` sits after the `$group`/sort
|
|
842
|
+
* or rides an indexed cursor, so the work behind it is bounded by the range and
|
|
843
|
+
* the indexes, not by the number. A SCAN cap bounds what the primitive READS,
|
|
844
|
+
* so an answer past it is an undercount — which is why all three report
|
|
845
|
+
* `truncated`.
|
|
846
|
+
*/
|
|
473
847
|
export interface QueryLimits {
|
|
848
|
+
// ── output caps ──
|
|
474
849
|
records: number;
|
|
475
850
|
series: number;
|
|
476
851
|
rollups: number;
|
|
477
852
|
trace: number;
|
|
478
853
|
journey: number;
|
|
854
|
+
/**
|
|
855
|
+
* Distinct GROUPS one breakdown() returns — the top N by measure, read as
|
|
856
|
+
* cap+1 so truncation is observed. Never a bound on rows scanned.
|
|
857
|
+
*/
|
|
858
|
+
breakdown: number;
|
|
859
|
+
/**
|
|
860
|
+
* Distinct VALUES one values() lookup returns — the top N by count, read as
|
|
861
|
+
* cap+1 so truncation is observed. Never a bound on rows scanned.
|
|
862
|
+
*/
|
|
863
|
+
values: number;
|
|
864
|
+
|
|
865
|
+
// ── scan caps ──
|
|
479
866
|
/** raw docs distribution will scan before it reports an undercount */
|
|
480
867
|
distribution: number;
|
|
481
868
|
/** rollup docs distinctCount will scan before it reports an undercount */
|
|
@@ -492,7 +879,8 @@ export interface TimeRange {
|
|
|
492
879
|
|
|
493
880
|
export interface RecordFilter {
|
|
494
881
|
kind?: string;
|
|
495
|
-
name
|
|
882
|
+
/** one event name, or a SET of them as an `$in` — a namespace or a family is several */
|
|
883
|
+
name?: string | string[];
|
|
496
884
|
severity?: string;
|
|
497
885
|
env?: string;
|
|
498
886
|
service?: string;
|
|
@@ -630,6 +1018,28 @@ export interface Queries {
|
|
|
630
1018
|
Promise<{ items: any[]; nextCursor: string | null; dataSource: 'raw' }>;
|
|
631
1019
|
series(scope: string, range: TimeRange, filter: RecordFilter, opts?: { measure?: string; interval?: 'hour' | 'day' | 'week' | 'month' }):
|
|
632
1020
|
Promise<{ buckets: Array<{ at: Date; value: number }>; dataSource: 'raw' }>;
|
|
1021
|
+
/**
|
|
1022
|
+
* Top groups of a measure by one or two dimensions. `groupBy` takes
|
|
1023
|
+
* `attr:<key>`, `field:<path>` (an allowlist of envelope paths), `subjectType`
|
|
1024
|
+
* or `actorType`; 0 or 3+ dims, an unlisted path, or a bad interval throw with
|
|
1025
|
+
* `status: 400`. Rows carry `at` only when an `interval` is given.
|
|
1026
|
+
*
|
|
1027
|
+
* `limit` caps the GROUPS returned, never the rows scanned — the scan is
|
|
1028
|
+
* bounded by the range and the indexes exactly as `series` is, and truncation
|
|
1029
|
+
* keeps the TOP groups by measure. A record missing the dim groups under
|
|
1030
|
+
* `null` rather than being dropped. Aggregates across tenants under `'*'`.
|
|
1031
|
+
*
|
|
1032
|
+
* Two truncation flags, because they cut different axes: `truncated` means
|
|
1033
|
+
* groups were dropped, `bucketsTruncated` that the per-interval pass hit its
|
|
1034
|
+
* own ceiling (`limits.series` buckets per returned group) and some group
|
|
1035
|
+
* shown is missing periods. `bucketsTruncated` is always false with no
|
|
1036
|
+
* `interval`.
|
|
1037
|
+
*
|
|
1038
|
+
* `sum:durationMs` / `avg:durationMs` read the ENVELOPE field, not
|
|
1039
|
+
* `metrics.durationMs` — a span's duration is not a declared metric.
|
|
1040
|
+
*/
|
|
1041
|
+
breakdown(scope: string, range: TimeRange, filter: RecordFilter, opts: { groupBy: string[]; measure?: string; interval?: 'hour' | 'day' | 'week' | 'month'; limit?: number }):
|
|
1042
|
+
Promise<{ rows: Array<{ dims: (string | null)[]; at?: Date; value: number }>; groups: number; truncated: boolean; bucketsTruncated: boolean; dataSource: 'raw' }>;
|
|
633
1043
|
/**
|
|
634
1044
|
* The sample is complete — nothing is sampled away between the match and the
|
|
635
1045
|
* math — but `$percentile` is `method: 'approximate'` and the scan stops at
|
|
@@ -678,17 +1088,75 @@ export declare function createQueries(ctx: {
|
|
|
678
1088
|
cacheSize?: number;
|
|
679
1089
|
}): Queries;
|
|
680
1090
|
|
|
1091
|
+
// ── values (reports §5) ─────────────────────────────────────────────────────
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* The observed domain of one dimension — the lookup a report builder makes
|
|
1095
|
+
* before it names a value. NOT a tenth primitive: it reads the catalog, which
|
|
1096
|
+
* the primitives deliberately do not, and it answers from whichever of four
|
|
1097
|
+
* sources is cheapest.
|
|
1098
|
+
*/
|
|
1099
|
+
export interface ValuesParams {
|
|
1100
|
+
/**
|
|
1101
|
+
* A `DimFacet.key` — `attr:model`, `field:client.platform`, `subjectType`,
|
|
1102
|
+
* `actorType`. The literal `'subject'` also works and is the only way to ask
|
|
1103
|
+
* a family for its subject refs.
|
|
1104
|
+
*/
|
|
1105
|
+
dim: string;
|
|
1106
|
+
/** the Report's source events: decides the raw step, narrows the other two */
|
|
1107
|
+
names?: string[];
|
|
1108
|
+
/** required by the raw step; ignored by the others */
|
|
1109
|
+
range?: TimeRange;
|
|
1110
|
+
/** values cap, default `limits.values` (200), clamped to it */
|
|
1111
|
+
limit?: number;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
export interface ValuesResult {
|
|
1115
|
+
/** catalog order for a declared enum, else by count desc then value asc */
|
|
1116
|
+
values: string[];
|
|
1117
|
+
/** parallel to `values` when the source can count — absent for 'catalog' */
|
|
1118
|
+
counts?: number[];
|
|
1119
|
+
/**
|
|
1120
|
+
* Which of the four answered, in preference order: `catalog` (a declared
|
|
1121
|
+
* enum — no read at all), `rollups` (one indexed `$group` over a family keyed
|
|
1122
|
+
* by the dim), `raw` (an indexed attr or envelope dim over the range), or
|
|
1123
|
+
* `none`. `none` is an ANSWER, never an error: the caller offers free-text
|
|
1124
|
+
* equality with a scan badge.
|
|
1125
|
+
*/
|
|
1126
|
+
source: 'catalog' | 'rollups' | 'raw' | 'none';
|
|
1127
|
+
/** the family read, when `source === 'rollups'` */
|
|
1128
|
+
via?: string;
|
|
1129
|
+
/** more values existed than the cap; the ones kept are the top by count */
|
|
1130
|
+
truncated: boolean;
|
|
1131
|
+
dataSource: 'catalog' | 'rollups' | 'raw' | 'none';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export interface ValuesCtx {
|
|
1135
|
+
catalog: Catalog;
|
|
1136
|
+
TelemetryModel: Model<any>;
|
|
1137
|
+
RollupModel: Model<any>;
|
|
1138
|
+
limits?: Partial<QueryLimits>;
|
|
1139
|
+
onSlowQuery?: (info: { op: string; ms: number; params: unknown }) => void;
|
|
1140
|
+
slowMs?: number;
|
|
1141
|
+
cacheTtlMs?: number;
|
|
1142
|
+
cacheSize?: number;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export type Values = (scope: string, params: ValuesParams) => Promise<ValuesResult>;
|
|
1146
|
+
|
|
1147
|
+
/** memoized like `series`; never throws on the `none` path */
|
|
1148
|
+
export declare function createValues(ctx: ValuesCtx): Values;
|
|
1149
|
+
|
|
681
1150
|
export interface ViewSpec {
|
|
682
1151
|
name: string;
|
|
683
1152
|
icon?: string;
|
|
684
|
-
page: 'errors' | 'traces' | 'events' | 'journeys' | 'usage' | 'overview' | 'system';
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
};
|
|
1153
|
+
page: 'errors' | 'traces' | 'events' | 'journeys' | 'usage' | 'overview' | 'system' | 'explore';
|
|
1154
|
+
/**
|
|
1155
|
+
* A Report — or the pre-Report shape, which every stored view still carries
|
|
1156
|
+
* and `normalizeQuery()` lifts. `spec` is a Mixed document, so nothing has to
|
|
1157
|
+
* migrate: a query with no `source` is read as legacy.
|
|
1158
|
+
*/
|
|
1159
|
+
query: Report | LegacyQuery;
|
|
692
1160
|
}
|
|
693
1161
|
|
|
694
1162
|
export interface ResolvedView extends ViewSpec {
|
|
@@ -698,8 +1166,13 @@ export interface ResolvedView extends ViewSpec {
|
|
|
698
1166
|
shared?: boolean;
|
|
699
1167
|
}
|
|
700
1168
|
|
|
701
|
-
/**
|
|
702
|
-
|
|
1169
|
+
/**
|
|
1170
|
+
* Derived views — generated from the registry, zero config. Five shapes, every
|
|
1171
|
+
* one a Report: per event, per rollup family, per namespace, per usage event
|
|
1172
|
+
* that meters money, and one funnel per subject type. Pass the boot-time
|
|
1173
|
+
* catalog to skip re-deriving one.
|
|
1174
|
+
*/
|
|
1175
|
+
export declare function deriveViews(registry: Registry, catalog?: Catalog): ResolvedView[];
|
|
703
1176
|
|
|
704
1177
|
export interface Viewer {
|
|
705
1178
|
/**
|