metrickit 0.1.2

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 (50) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +379 -0
  4. package/dist/cache-redis.d.ts +10 -0
  5. package/dist/cache-redis.js +24 -0
  6. package/dist/cache-utils.d.ts +5 -0
  7. package/dist/cache.d.ts +18 -0
  8. package/dist/catalog.d.ts +12 -0
  9. package/dist/define-metric.d.ts +37 -0
  10. package/dist/engine.d.ts +200 -0
  11. package/dist/filters/default-metadata.d.ts +28 -0
  12. package/dist/filters/index.d.ts +3 -0
  13. package/dist/filters/parse.d.ts +7 -0
  14. package/dist/filters/types.d.ts +19 -0
  15. package/dist/frontend/catalog.d.ts +24 -0
  16. package/dist/frontend/dashboard.d.ts +12 -0
  17. package/dist/frontend/format.d.ts +10 -0
  18. package/dist/frontend/index.d.ts +10 -0
  19. package/dist/frontend/markers.d.ts +19 -0
  20. package/dist/frontend/renderers.d.ts +14 -0
  21. package/dist/frontend/requests.d.ts +17 -0
  22. package/dist/frontend/stream-state.d.ts +14 -0
  23. package/dist/frontend/time.d.ts +5 -0
  24. package/dist/frontend/transport.d.ts +19 -0
  25. package/dist/frontend/types.d.ts +108 -0
  26. package/dist/frontend.d.ts +1 -0
  27. package/dist/frontend.js +752 -0
  28. package/dist/helpers/clickhouse.d.ts +27 -0
  29. package/dist/helpers/distribution.d.ts +15 -0
  30. package/dist/helpers/index.d.ts +6 -0
  31. package/dist/helpers/metric-type.d.ts +21 -0
  32. package/dist/helpers/pivot.d.ts +15 -0
  33. package/dist/helpers/prisma.d.ts +20 -0
  34. package/dist/helpers/timeseries.d.ts +6 -0
  35. package/dist/helpers.d.ts +1 -0
  36. package/dist/helpers.js +668 -0
  37. package/dist/index.d.ts +11 -0
  38. package/dist/index.js +1322 -0
  39. package/dist/orpc.d.ts +36 -0
  40. package/dist/orpc.js +1157 -0
  41. package/dist/registry.d.ts +269 -0
  42. package/dist/run-metrics.d.ts +14 -0
  43. package/dist/schemas/index.d.ts +4 -0
  44. package/dist/schemas/inputs.d.ts +19 -0
  45. package/dist/schemas/metric-type.d.ts +7 -0
  46. package/dist/schemas/output.d.ts +842 -0
  47. package/dist/schemas/time.d.ts +24 -0
  48. package/dist/time.d.ts +6 -0
  49. package/dist/type-guards.d.ts +7 -0
  50. package/package.json +91 -0
@@ -0,0 +1,668 @@
1
+ // src/time.ts
2
+ function getPreviousPeriod(range) {
3
+ if (!range.from || !range.to)
4
+ return null;
5
+ const durationMs = range.to.getTime() - range.from.getTime();
6
+ return {
7
+ from: new Date(range.from.getTime() - durationMs),
8
+ to: new Date(range.from.getTime())
9
+ };
10
+ }
11
+ function getBucketStart(date, granularity) {
12
+ const d = new Date(date);
13
+ switch (granularity) {
14
+ case "hour":
15
+ d.setUTCMinutes(0, 0, 0);
16
+ break;
17
+ case "day":
18
+ d.setUTCHours(0, 0, 0, 0);
19
+ break;
20
+ case "week": {
21
+ const day = d.getUTCDay();
22
+ const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1);
23
+ d.setUTCDate(diff);
24
+ d.setUTCHours(0, 0, 0, 0);
25
+ break;
26
+ }
27
+ case "month":
28
+ d.setUTCDate(1);
29
+ d.setUTCHours(0, 0, 0, 0);
30
+ break;
31
+ case "quarter": {
32
+ const month = d.getUTCMonth();
33
+ const quarterStart = month - month % 3;
34
+ d.setUTCMonth(quarterStart, 1);
35
+ d.setUTCHours(0, 0, 0, 0);
36
+ break;
37
+ }
38
+ case "year":
39
+ d.setUTCMonth(0, 1);
40
+ d.setUTCHours(0, 0, 0, 0);
41
+ break;
42
+ }
43
+ return d;
44
+ }
45
+ function normalizeUserDate(date) {
46
+ const d = new Date(date);
47
+ const hours = d.getUTCHours();
48
+ if (hours >= 20) {
49
+ d.setUTCDate(d.getUTCDate() + 1);
50
+ }
51
+ d.setUTCHours(0, 0, 0, 0);
52
+ return d;
53
+ }
54
+ function generateBuckets(from, to, granularity) {
55
+ const buckets = [];
56
+ const normalizedFrom = granularity === "hour" ? from : normalizeUserDate(from);
57
+ const normalizedTo = granularity === "hour" ? to : normalizeUserDate(to);
58
+ let current = getBucketStart(normalizedFrom, granularity);
59
+ while (current <= normalizedTo) {
60
+ buckets.push(new Date(current));
61
+ current = getNextBucket(current, granularity);
62
+ }
63
+ return buckets;
64
+ }
65
+ function getNextBucket(date, granularity) {
66
+ const d = new Date(date);
67
+ switch (granularity) {
68
+ case "hour":
69
+ d.setUTCHours(d.getUTCHours() + 1);
70
+ break;
71
+ case "day":
72
+ d.setUTCDate(d.getUTCDate() + 1);
73
+ break;
74
+ case "week":
75
+ d.setUTCDate(d.getUTCDate() + 7);
76
+ break;
77
+ case "month":
78
+ d.setUTCMonth(d.getUTCMonth() + 1);
79
+ break;
80
+ case "quarter":
81
+ d.setUTCMonth(d.getUTCMonth() + 3);
82
+ break;
83
+ case "year":
84
+ d.setUTCFullYear(d.getUTCFullYear() + 1);
85
+ break;
86
+ }
87
+ return d;
88
+ }
89
+ function inferGranularity(range) {
90
+ if (!range.from || !range.to)
91
+ return "day";
92
+ const diffMs = range.to.getTime() - range.from.getTime();
93
+ const diffDays = diffMs / (1000 * 60 * 60 * 24);
94
+ if (diffDays <= 2)
95
+ return "hour";
96
+ if (diffDays <= 31)
97
+ return "day";
98
+ if (diffDays <= 90)
99
+ return "week";
100
+ if (diffDays <= 365)
101
+ return "month";
102
+ if (diffDays <= 730)
103
+ return "quarter";
104
+ return "year";
105
+ }
106
+
107
+ // src/helpers/clickhouse.ts
108
+ var CLICKHOUSE_GRANULARITY_FUNCTIONS = {
109
+ hour: "toStartOfHour",
110
+ day: "toStartOfDay",
111
+ week: "toStartOfWeek",
112
+ month: "toStartOfMonth",
113
+ quarter: "toStartOfQuarter",
114
+ year: "toStartOfYear"
115
+ };
116
+ function getClickhouseGranularity(granularity) {
117
+ return CLICKHOUSE_GRANULARITY_FUNCTIONS[granularity];
118
+ }
119
+ function buildClickhouseBucketExpr(columnName, granularity) {
120
+ const fn = CLICKHOUSE_GRANULARITY_FUNCTIONS[granularity];
121
+ if (granularity === "week") {
122
+ return `${fn}(${columnName}, 1)`;
123
+ }
124
+ return `${fn}(${columnName})`;
125
+ }
126
+ function toClickhouseDatetime(date, boundary) {
127
+ let d = date;
128
+ if (boundary === "start") {
129
+ d = normalizeUserDate(date);
130
+ } else if (boundary === "end") {
131
+ d = new Date(date.getTime() + 24 * 60 * 60 * 1000 - 1000);
132
+ }
133
+ return d.toISOString().slice(0, 19).replace("T", " ");
134
+ }
135
+ function buildClickhouseTimeParams(filters) {
136
+ return {
137
+ ...filters.from ? { from: toClickhouseDatetime(filters.from) } : {},
138
+ ...filters.to ? { to: toClickhouseDatetime(filters.to) } : {}
139
+ };
140
+ }
141
+ function buildClickhouseTimeWhere(columnName, options) {
142
+ const parts = [];
143
+ if (options.from) {
144
+ parts.push(`${columnName} >= {from:DateTime}`);
145
+ }
146
+ if (options.to) {
147
+ parts.push(`${columnName} <= {to:DateTime}`);
148
+ }
149
+ return parts.join(" AND ");
150
+ }
151
+ function parseClickhouseBucketToTimestamp(bucket) {
152
+ const isoString = `${bucket.replace(" ", "T")}Z`;
153
+ return new Date(isoString).getTime();
154
+ }
155
+ // src/helpers/prisma.ts
156
+ function buildTimeRangeWhere(filters) {
157
+ if (!filters.from && !filters.to) {
158
+ return;
159
+ }
160
+ return {
161
+ ...filters.from ? { gte: filters.from } : {},
162
+ ...filters.to ? { lte: filters.to } : {}
163
+ };
164
+ }
165
+ function buildTimeRangeWhereObject(columnName, filters) {
166
+ const timeRange = buildTimeRangeWhere(filters);
167
+ if (!timeRange) {
168
+ return {};
169
+ }
170
+ return { [columnName]: timeRange };
171
+ }
172
+ function buildScopedTimeWhere(idColumn, timeColumn, filters) {
173
+ const idWhere = filters.scopeIds?.length ? { [idColumn]: { in: filters.scopeIds } } : filters.scopeId ? { [idColumn]: filters.scopeId } : {};
174
+ return {
175
+ ...idWhere,
176
+ ...buildTimeRangeWhereObject(timeColumn, filters)
177
+ };
178
+ }
179
+ // src/helpers/distribution.ts
180
+ function calculateDistribution(segments, options) {
181
+ const total = segments.reduce((sum, s) => sum + s.value, 0);
182
+ return {
183
+ kind: "distribution",
184
+ total,
185
+ segments: segments.map((s) => ({
186
+ key: s.key,
187
+ label: s.label,
188
+ value: s.value,
189
+ percent: total > 0 ? s.value / total * 100 : 0
190
+ })),
191
+ chartType: options?.chartType
192
+ };
193
+ }
194
+ function calculateDistributionFromGroups(groups, keyField, labelMap = {}, options) {
195
+ return calculateDistribution(groups.map((g) => ({
196
+ key: g[keyField],
197
+ label: labelMap[g[keyField]] ?? g[keyField],
198
+ value: g._count._all
199
+ })), options);
200
+ }
201
+ // src/helpers/timeseries.ts
202
+ function mapBucketsToPoints(buckets, rows, valueExtractor) {
203
+ const dataMap = new Map;
204
+ for (const row of rows) {
205
+ if (row.bucket) {
206
+ const ts = parseClickhouseBucketToTimestamp(row.bucket);
207
+ dataMap.set(ts, row);
208
+ }
209
+ }
210
+ return buckets.map((ts) => ({
211
+ ts,
212
+ value: dataMap.has(ts.getTime()) ? valueExtractor(dataMap.get(ts.getTime())) : 0
213
+ }));
214
+ }
215
+ function createEmptyPoints(from, to, granularity) {
216
+ const buckets = generateBuckets(from, to, granularity);
217
+ return buckets.map((ts) => ({ ts, value: 0 }));
218
+ }
219
+ // src/helpers/metric-type.ts
220
+ import { typedSwitch } from "typedswitch";
221
+ function resolveMetricType(metric, defaultMetric) {
222
+ return metric ?? defaultMetric;
223
+ }
224
+ function calculateMetricModeValue(args) {
225
+ const value = typedSwitch(resolveMetricType(args.metric, args.defaultMetric), {
226
+ TOTAL: () => args.total,
227
+ AVG: () => args.averageDivisor > 0 ? args.total / args.averageDivisor : 0,
228
+ PER_BUCKET: () => args.bucketCount && args.bucketCount > 0 ? args.total / args.bucketCount : 0
229
+ });
230
+ return args.postProcess ? args.postProcess(value) : value;
231
+ }
232
+ function getMetricModeLabel(args) {
233
+ return args.labels[resolveMetricType(args.metric, args.defaultMetric)];
234
+ }
235
+ function getBucketCountForMetricMode(args) {
236
+ if (resolveMetricType(args.metric, args.defaultMetric) !== "PER_BUCKET") {
237
+ return;
238
+ }
239
+ return Math.max(1, generateBuckets(args.from, args.to, inferGranularity({ from: args.from, to: args.to })).length);
240
+ }
241
+ // src/schemas/time.ts
242
+ import { z } from "zod";
243
+ var TimeGranularitySchema = z.enum([
244
+ "hour",
245
+ "day",
246
+ "week",
247
+ "month",
248
+ "quarter",
249
+ "year"
250
+ ]);
251
+ var TimeRangeSchema = z.object({
252
+ from: z.date().optional(),
253
+ to: z.date().optional()
254
+ });
255
+ var RequiredTimeRangeSchema = z.object({
256
+ from: z.date(),
257
+ to: z.date()
258
+ });
259
+ var CompareSchema = z.object({
260
+ compareToPrevious: z.boolean().optional().default(false)
261
+ });
262
+ // src/schemas/output.ts
263
+ import { z as z2 } from "zod";
264
+ var OutputKindSchema = z2.enum([
265
+ "kpi",
266
+ "timeseries",
267
+ "distribution",
268
+ "table",
269
+ "leaderboard",
270
+ "pivot"
271
+ ]);
272
+ var MetricUnitSchema = z2.enum([
273
+ "DKK",
274
+ "EUR",
275
+ "USD",
276
+ "GBP",
277
+ "SEK",
278
+ "NOK",
279
+ "PERCENTAGE"
280
+ ]);
281
+ var KpiOutputSchema = z2.object({
282
+ kind: z2.literal("kpi"),
283
+ value: z2.number(),
284
+ label: z2.string().optional(),
285
+ unit: MetricUnitSchema.optional(),
286
+ prefix: z2.string().optional(),
287
+ suffix: z2.string().optional(),
288
+ trend: z2.enum(["up", "down", "flat"]).optional()
289
+ });
290
+ function defineKpiOutput(output) {
291
+ return {
292
+ kind: "kpi",
293
+ ...output
294
+ };
295
+ }
296
+ var TimeSeriesPointSchema = z2.object({
297
+ ts: z2.date(),
298
+ value: z2.number(),
299
+ label: z2.string().optional()
300
+ });
301
+ var TimeSeriesSeriesSchema = z2.object({
302
+ key: z2.string(),
303
+ label: z2.string().optional(),
304
+ points: z2.array(TimeSeriesPointSchema),
305
+ chartType: z2.enum(["line", "bar"]).optional(),
306
+ axis: z2.enum(["left", "right"]).optional(),
307
+ meta: z2.record(z2.string(), z2.unknown()).optional()
308
+ });
309
+ var TimeSeriesOutputSchema = z2.object({
310
+ kind: z2.literal("timeseries"),
311
+ granularity: TimeGranularitySchema,
312
+ series: z2.array(TimeSeriesSeriesSchema)
313
+ });
314
+ function defineTimeSeriesOutput(output) {
315
+ if (Array.isArray(output.series)) {
316
+ return {
317
+ kind: "timeseries",
318
+ ...output,
319
+ series: [...output.series]
320
+ };
321
+ }
322
+ return {
323
+ kind: "timeseries",
324
+ ...output,
325
+ series: Object.entries(output.series).map(([key, series]) => ({
326
+ key,
327
+ ...series
328
+ }))
329
+ };
330
+ }
331
+ var DistributionSegmentSchema = z2.object({
332
+ key: z2.string(),
333
+ label: z2.string(),
334
+ value: z2.number(),
335
+ percent: z2.number().optional()
336
+ });
337
+ var DistributionChartTypeSchema = z2.enum([
338
+ "bar",
339
+ "donut",
340
+ "pie",
341
+ "funnel"
342
+ ]);
343
+ var DistributionOutputSchema = z2.object({
344
+ kind: z2.literal("distribution"),
345
+ total: z2.number(),
346
+ segments: z2.array(DistributionSegmentSchema),
347
+ chartType: DistributionChartTypeSchema.optional()
348
+ });
349
+ function defineDistributionOutput(output) {
350
+ return {
351
+ kind: "distribution",
352
+ ...output,
353
+ segments: [...output.segments]
354
+ };
355
+ }
356
+ var TableColumnSchema = z2.object({
357
+ key: z2.string(),
358
+ label: z2.string(),
359
+ type: z2.enum(["string", "number", "date", "boolean"]).optional(),
360
+ nullable: z2.boolean().optional()
361
+ });
362
+ var TableOutputSchema = z2.object({
363
+ kind: z2.literal("table"),
364
+ columns: z2.array(TableColumnSchema),
365
+ rows: z2.array(z2.record(z2.string(), z2.unknown())),
366
+ total: z2.number().optional()
367
+ });
368
+ function defineTableOutput(output) {
369
+ return {
370
+ kind: "table",
371
+ ...output,
372
+ columns: [...output.columns],
373
+ rows: [...output.rows]
374
+ };
375
+ }
376
+ var PivotDimensionSchema = z2.object({
377
+ key: z2.string(),
378
+ label: z2.string()
379
+ });
380
+ var PivotTotalsSchema = z2.object({
381
+ rowTotals: z2.array(z2.number()).optional(),
382
+ columnTotals: z2.array(z2.number()).optional(),
383
+ grandTotal: z2.number().optional()
384
+ });
385
+ function addGrandTotalMismatchIssue(ctx, totalsName, totals, grandTotal) {
386
+ const totalsSum = totals?.reduce((total, value) => total + value, 0);
387
+ if (totalsSum === undefined)
388
+ return;
389
+ if (Math.abs(totalsSum - grandTotal) <= Number.EPSILON)
390
+ return;
391
+ ctx.addIssue({
392
+ code: z2.ZodIssueCode.custom,
393
+ path: ["totals", "grandTotal"],
394
+ message: `grandTotal must equal the sum of ${totalsName}`
395
+ });
396
+ }
397
+ function clonePivotTotals(totals) {
398
+ if (!totals)
399
+ return;
400
+ return {
401
+ rowTotals: totals.rowTotals ? [...totals.rowTotals] : undefined,
402
+ columnTotals: totals.columnTotals ? [...totals.columnTotals] : undefined,
403
+ grandTotal: totals.grandTotal
404
+ };
405
+ }
406
+ function clonePivotCellTooltips(cellTooltips) {
407
+ if (!cellTooltips)
408
+ return;
409
+ return cellTooltips.map((row) => [...row]);
410
+ }
411
+ var PivotOutputSchema = z2.object({
412
+ kind: z2.literal("pivot"),
413
+ rowDimension: PivotDimensionSchema,
414
+ columnDimension: PivotDimensionSchema,
415
+ rows: z2.array(z2.string()),
416
+ columns: z2.array(z2.string()),
417
+ values: z2.array(z2.array(z2.number())),
418
+ cellTooltips: z2.array(z2.array(z2.string())).optional(),
419
+ totals: PivotTotalsSchema.optional()
420
+ }).superRefine((pivot, ctx) => {
421
+ const rowCount = pivot.rows.length;
422
+ const columnCount = pivot.columns.length;
423
+ if (pivot.values.length !== rowCount) {
424
+ ctx.addIssue({
425
+ code: z2.ZodIssueCode.custom,
426
+ path: ["values"],
427
+ message: `Pivot values must have ${rowCount} rows`
428
+ });
429
+ }
430
+ for (const [rowIndex, row] of pivot.values.entries()) {
431
+ if (row.length !== columnCount) {
432
+ ctx.addIssue({
433
+ code: z2.ZodIssueCode.custom,
434
+ path: ["values", rowIndex],
435
+ message: `Pivot row ${rowIndex} must have ${columnCount} columns`
436
+ });
437
+ }
438
+ }
439
+ if (pivot.cellTooltips !== undefined) {
440
+ if (pivot.cellTooltips.length !== rowCount) {
441
+ ctx.addIssue({
442
+ code: z2.ZodIssueCode.custom,
443
+ path: ["cellTooltips"],
444
+ message: `cellTooltips must have ${rowCount} rows`
445
+ });
446
+ }
447
+ for (const [rowIndex, row] of pivot.cellTooltips.entries()) {
448
+ if (row.length !== columnCount) {
449
+ ctx.addIssue({
450
+ code: z2.ZodIssueCode.custom,
451
+ path: ["cellTooltips", rowIndex],
452
+ message: `cellTooltips row ${rowIndex} must have ${columnCount} columns`
453
+ });
454
+ }
455
+ }
456
+ }
457
+ if (pivot.totals?.rowTotals && pivot.totals.rowTotals.length !== rowCount) {
458
+ ctx.addIssue({
459
+ code: z2.ZodIssueCode.custom,
460
+ path: ["totals", "rowTotals"],
461
+ message: `rowTotals must have ${rowCount} entries`
462
+ });
463
+ }
464
+ if (pivot.totals?.columnTotals && pivot.totals.columnTotals.length !== columnCount) {
465
+ ctx.addIssue({
466
+ code: z2.ZodIssueCode.custom,
467
+ path: ["totals", "columnTotals"],
468
+ message: `columnTotals must have ${columnCount} entries`
469
+ });
470
+ }
471
+ if (pivot.totals?.grandTotal !== undefined) {
472
+ addGrandTotalMismatchIssue(ctx, "rowTotals", pivot.totals.rowTotals, pivot.totals.grandTotal);
473
+ addGrandTotalMismatchIssue(ctx, "columnTotals", pivot.totals.columnTotals, pivot.totals.grandTotal);
474
+ }
475
+ });
476
+ function definePivotOutput(output) {
477
+ return {
478
+ kind: "pivot",
479
+ ...output,
480
+ rows: [...output.rows],
481
+ columns: [...output.columns],
482
+ values: output.values.map((row) => [...row]),
483
+ cellTooltips: clonePivotCellTooltips(output.cellTooltips),
484
+ totals: clonePivotTotals(output.totals)
485
+ };
486
+ }
487
+ var LeaderboardItemSchema = z2.object({
488
+ rank: z2.number(),
489
+ id: z2.string(),
490
+ label: z2.string(),
491
+ value: z2.number(),
492
+ meta: z2.record(z2.string(), z2.unknown()).optional()
493
+ });
494
+ var LeaderboardOutputSchema = z2.object({
495
+ kind: z2.literal("leaderboard"),
496
+ items: z2.array(LeaderboardItemSchema),
497
+ total: z2.number().optional()
498
+ });
499
+ function defineLeaderboardOutput(output) {
500
+ return {
501
+ kind: "leaderboard",
502
+ ...output,
503
+ items: [...output.items]
504
+ };
505
+ }
506
+ function defineMetricOutput(kind, output) {
507
+ const handlers = {
508
+ kpi: () => defineKpiOutput(output),
509
+ timeseries: () => defineTimeSeriesOutput(output),
510
+ distribution: () => defineDistributionOutput(output),
511
+ table: () => defineTableOutput(output),
512
+ leaderboard: () => defineLeaderboardOutput(output),
513
+ pivot: () => definePivotOutput(output)
514
+ };
515
+ return handlers[kind]();
516
+ }
517
+ function validateOutput(kind, output) {
518
+ return OutputSchemaMap[kind].parse(output);
519
+ }
520
+ var MetricOutputSchema = z2.discriminatedUnion("kind", [
521
+ KpiOutputSchema,
522
+ TimeSeriesOutputSchema,
523
+ DistributionOutputSchema,
524
+ TableOutputSchema,
525
+ LeaderboardOutputSchema,
526
+ PivotOutputSchema
527
+ ]);
528
+ var OutputSchemaMap = {
529
+ kpi: KpiOutputSchema,
530
+ timeseries: TimeSeriesOutputSchema,
531
+ distribution: DistributionOutputSchema,
532
+ table: TableOutputSchema,
533
+ leaderboard: LeaderboardOutputSchema,
534
+ pivot: PivotOutputSchema
535
+ };
536
+ var MetricExecutionCacheStatusSchema = z2.enum([
537
+ "hit",
538
+ "partialHit",
539
+ "miss",
540
+ "bypassed"
541
+ ]);
542
+ var MetricExecutionSchema = z2.object({
543
+ cacheStatus: MetricExecutionCacheStatusSchema,
544
+ durationMs: z2.number().nonnegative(),
545
+ granularity: TimeGranularitySchema.optional()
546
+ });
547
+ var MetricResultSchema = z2.object({
548
+ current: MetricOutputSchema,
549
+ previous: MetricOutputSchema.optional(),
550
+ supportsTimeRange: z2.boolean(),
551
+ execution: MetricExecutionSchema.optional()
552
+ });
553
+ // src/schemas/inputs.ts
554
+ import { z as z3 } from "zod";
555
+ var BaseFiltersSchema = z3.object({
556
+ organizationIds: z3.array(z3.string()).optional()
557
+ }).merge(TimeRangeSchema);
558
+ var TimeSeriesFiltersSchema = z3.object({
559
+ organizationIds: z3.array(z3.string()).optional()
560
+ }).merge(RequiredTimeRangeSchema);
561
+ function extendBaseFilters(shape) {
562
+ const optionalShape = Object.fromEntries(Object.entries(shape).map(([key, schema]) => [
563
+ key,
564
+ schema.optional()
565
+ ]));
566
+ return BaseFiltersSchema.extend(optionalShape);
567
+ }
568
+ function extendTimeSeriesFilters(shape) {
569
+ const optionalShape = Object.fromEntries(Object.entries(shape).map(([key, schema]) => [
570
+ key,
571
+ schema.optional()
572
+ ]));
573
+ return TimeSeriesFiltersSchema.extend(optionalShape);
574
+ }
575
+ // src/schemas/metric-type.ts
576
+ import { z as z4 } from "zod";
577
+ var MetricTypeSchema = z4.enum(["TOTAL", "AVG", "PER_BUCKET"]);
578
+ // src/helpers/pivot.ts
579
+ function createPivotMatrix(rowCount, columnCount) {
580
+ return Array.from({ length: rowCount }, () => Array.from({ length: columnCount }, () => 0));
581
+ }
582
+ function addPivotCell(values, rowIndex, columnIndex, delta) {
583
+ const row = values[rowIndex];
584
+ if (!row)
585
+ return;
586
+ if (columnIndex < 0 || columnIndex >= row.length)
587
+ return;
588
+ row[columnIndex] = (row[columnIndex] ?? 0) + delta;
589
+ }
590
+ function computePivotTotals(values) {
591
+ const rowTotals = values.map((row) => row.reduce((acc, value) => acc + value, 0));
592
+ const columnCount = values[0]?.length ?? 0;
593
+ const columnTotals = Array.from({ length: columnCount }, (_, columnIndex) => values.reduce((acc, row) => acc + (row[columnIndex] ?? 0), 0));
594
+ const grandTotal = rowTotals.reduce((acc, value) => acc + value, 0);
595
+ return {
596
+ rowTotals,
597
+ columnTotals,
598
+ grandTotal
599
+ };
600
+ }
601
+ function formatPivotBucketLabel(bucket, granularity) {
602
+ const year = bucket.getUTCFullYear();
603
+ const month = String(bucket.getUTCMonth() + 1).padStart(2, "0");
604
+ const day = String(bucket.getUTCDate()).padStart(2, "0");
605
+ const hour = String(bucket.getUTCHours()).padStart(2, "0");
606
+ switch (granularity) {
607
+ case "hour":
608
+ return `${year}-${month}-${day} ${hour}:00`;
609
+ case "day":
610
+ return `${year}-${month}-${day}`;
611
+ case "week": {
612
+ const { week, weekYear } = getIsoWeekAndYear(bucket);
613
+ return `Week ${week} ${weekYear}`;
614
+ }
615
+ case "month":
616
+ return `${year}-${month}`;
617
+ case "quarter": {
618
+ const quarter = Math.floor(bucket.getUTCMonth() / 3) + 1;
619
+ return `Q${quarter} ${year}`;
620
+ }
621
+ case "year":
622
+ return `${year}`;
623
+ }
624
+ }
625
+ function getIsoWeekAndYear(date) {
626
+ const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
627
+ const day = d.getUTCDay() || 7;
628
+ d.setUTCDate(d.getUTCDate() + 4 - day);
629
+ const weekYear = d.getUTCFullYear();
630
+ const yearStart = new Date(Date.UTC(weekYear, 0, 1));
631
+ const week = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
632
+ return { week, weekYear };
633
+ }
634
+ function buildPivotOutput(args) {
635
+ return definePivotOutput({
636
+ rowDimension: args.rowDimension,
637
+ columnDimension: args.columnDimension,
638
+ rows: args.rows,
639
+ columns: args.columns,
640
+ values: args.values,
641
+ cellTooltips: args.cellTooltips,
642
+ totals: args.totals ?? computePivotTotals(args.values)
643
+ });
644
+ }
645
+ export {
646
+ toClickhouseDatetime,
647
+ resolveMetricType,
648
+ parseClickhouseBucketToTimestamp,
649
+ mapBucketsToPoints,
650
+ getMetricModeLabel,
651
+ getClickhouseGranularity,
652
+ getBucketCountForMetricMode,
653
+ formatPivotBucketLabel,
654
+ createPivotMatrix,
655
+ createEmptyPoints,
656
+ computePivotTotals,
657
+ calculateMetricModeValue,
658
+ calculateDistributionFromGroups,
659
+ calculateDistribution,
660
+ buildTimeRangeWhereObject,
661
+ buildTimeRangeWhere,
662
+ buildScopedTimeWhere,
663
+ buildPivotOutput,
664
+ buildClickhouseTimeWhere,
665
+ buildClickhouseTimeParams,
666
+ buildClickhouseBucketExpr,
667
+ addPivotCell
668
+ };
@@ -0,0 +1,11 @@
1
+ export { createMetricsEngine, type MetricKindSchemaMap, type MetricsEngineConfig } from './engine.ts';
2
+ export { createRegistry, getMetric, type AnyRegistry, type InferBaseFilters, type InferRegistryMetrics, type InferAvailableMetricKey, type GetMetricByKey, type MetricOutputFor, type MetricFiltersFor, type MetricKindFor, type MetricSeriesKeysFor, type MetricRequestFiltersFor, type MetricRequestFor, type MetricResultChunkFor, type MetricsExecutionResult, type MetricsRequestFor, type IsValidMetricKey, type MetricKeysOfKind } from './registry.ts';
3
+ export { defineKpiMetric, defineTimeSeriesMetric, defineDistributionMetric, defineTableMetric, defineLeaderboardMetric, definePivotMetric, getOutputSchema, validateMetricOutput, defineMetricWithSchema, type MetricDefinition, type MetricDefinitionInput, type MetricFilterFieldMetadataMap, type AnyMetricDefinition, type MetricKey, type MetricOutput as MetricOutputFromDef } from './define-metric.ts';
4
+ export { runMetrics, runMetricsStream, type MetricsRequest, type MetricsResult, type MetricResultChunk, type RunMetricsOptions } from './run-metrics.ts';
5
+ export { noopCacheAdapter, parseCache, type CacheAdapter } from './cache.ts';
6
+ export { redisCacheAdapter, type RedisLikeClient, type RedisPipeline } from './cache-redis.ts';
7
+ export * from './schemas/index.ts';
8
+ export { getPreviousPeriod, getBucketStart, normalizeUserDate, generateBuckets, inferGranularity } from './time.ts';
9
+ export { isKpi, isTimeSeries, isDistribution, isTable, isLeaderboard, isPivot } from './type-guards.ts';
10
+ export { defineMetricCatalogMetadata, type MetricCatalogMetadata, type MetricCatalogFreshness, type MetricCatalogSource } from './catalog.ts';
11
+ export * from './filters/index.ts';