@tekki-forge/runtime-api 0.2.0 → 0.3.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.
@@ -1,12 +1,31 @@
1
+ import type { WidgetIR } from "@tekki-forge/compiler";
1
2
  import type { ForgeRequestContext } from "./extension.js";
2
3
  import type { ForgeService } from "./service.js";
3
4
  type Row = Record<string, any>;
5
+ type Bucket = "day" | "week" | "month" | "year";
4
6
  /** "$today", "$today-30d", "$now", "$startOfMonth", "$me" → concrete values. */
5
7
  export declare function resolveTokens(where: Record<string, string>, userId: string | null, now?: Date): Record<string, string>;
6
- export declare function bucketKey(d: Date, by: "day" | "week" | "month" | "year"): string;
8
+ /** `d` as wall-clock time in `timeZone`, expressed as a Date whose UTC fields hold the local values. */
9
+ export declare function zonedWallClock(d: Date, timeZone: string): Date;
10
+ /** The instant at which the wall clock in `timeZone` shows midnight of `date` (YYYY-MM-DD); DST-safe. */
11
+ export declare function zonedMidnight(date: string, timeZone: string): Date;
12
+ export declare function bucketKey(d: Date, by: Bucket, timeZone?: string): string;
13
+ /**
14
+ * List filters selecting one bucket: dates for date fields (and UTC dashboards), instants of local midnight otherwise,
15
+ * so clicking a point opens exactly the records it counts.
16
+ */
17
+ export declare function bucketRange(key: string, by: Bucket, kind: string, timeZone?: string): {
18
+ gte: string;
19
+ lt: string;
20
+ };
7
21
  export type ComparePeriod = "week" | "month" | "quarter" | "year";
8
22
  /** Periods in a metric's sparkline (the last one is the current period). */
9
23
  export declare const TREND_PERIODS = 12;
24
+ /** Start of the current calendar period (weeks start on Monday) in `timeZone`: the local date and its instant. */
25
+ export declare function periodStart(now: Date, period: ComparePeriod, timeZone?: string): {
26
+ date: string;
27
+ instant: Date;
28
+ };
10
29
  /** `d` moved by `n` periods; months clamp to the month's last day (31 Mar − 1 month = 28/29 Feb). */
11
30
  export declare function shiftPeriod(d: Date, period: ComparePeriod, n: number): Date;
12
31
  /**
@@ -35,24 +54,102 @@ export interface MetricTrend {
35
54
  value: number | null;
36
55
  }[];
37
56
  }
57
+ /** One point of a chart: `label` in the default language, `key` the raw group value (clients localize it). */
58
+ export interface ChartPoint {
59
+ label: string;
60
+ key: string | null;
61
+ value: number;
62
+ href?: string;
63
+ }
64
+ /** A series of a multi-series chart, aligned with the chart's categories (`data`). */
65
+ export interface ChartSeriesData {
66
+ /** Series id (config series) or the stack value (stackBy; null = none, "__other" = the rest). */
67
+ key: string | null;
68
+ label: string;
69
+ entity: string;
70
+ path: string;
71
+ aggregate: WidgetIR["aggregate"];
72
+ field?: string;
73
+ axis: "left" | "right";
74
+ values: number[];
75
+ hrefs: (string | null)[];
76
+ }
77
+ /** Ratio of two aggregates, null without a denominator. */
78
+ export declare const ratioOf: (numerator: number | null, denominator: number | null) => number | null;
38
79
  export declare class DashboardService {
39
80
  private readonly service;
40
81
  constructor(service: ForgeService);
82
+ private entityReadable;
83
+ private sourceReadable;
41
84
  private readable;
42
85
  private fieldReadable;
43
- /** `now` is injectable for tests; relative dates in filters ($today…) are resolved against it. */
44
- compute(ctx: ForgeRequestContext, now?: Date): Promise<{
86
+ /** Tabs the viewer may see (none when the dashboard has no tabs). */
87
+ visibleTabs(ctx: ForgeRequestContext): {
88
+ id: string;
89
+ title: string;
90
+ }[];
91
+ /**
92
+ * The widgets computed for the viewer. With tabs, `tab` picks one (default: every visible tab's widgets);
93
+ * `now` is injectable for tests; relative dates in filters ($today…) are resolved against it.
94
+ */
95
+ compute(ctx: ForgeRequestContext, now?: Date, tab?: string): Promise<{
45
96
  widgets: null;
46
97
  } | {
98
+ tabs?: {
99
+ id: string;
100
+ title: string;
101
+ }[] | undefined;
47
102
  widgets: Row[];
48
103
  }>;
104
+ private get timeZone();
105
+ /** The viewer's row-level, tenant and owner rules plus the widget's filter. */
106
+ private where;
107
+ /**
108
+ * The whole current calendar period: `{ start of this period <= dateField < start of the next }`, so records
109
+ * dated later this month (upcoming bookings, due dates) count too. Date fields compare by local date,
110
+ * datetimes by instant.
111
+ */
112
+ private periodFilter;
113
+ private metric;
114
+ /** One ratio side, within the viewer's rules, optionally limited to a calendar period or a [start, end) window. */
115
+ private sourceValue;
49
116
  private aggregate;
50
117
  /**
51
118
  * The metric over each of the last TREND_PERIODS rolling periods, within exactly the same row-level, tenant and
52
119
  * owner rules as the value itself; relative dates in the filter ($today…) are resolved as of each period.
53
120
  */
54
121
  private trend;
55
- /** `label` is ready to show (default language); `key` is the raw group value so clients can localize it ("__other" = the rest). */
56
- private series;
122
+ private chart;
123
+ private href;
124
+ /** Contiguous date buckets (from the first to the last record), computed from the rows so every permission rule stays in the Prisma where. */
125
+ private dateBuckets;
126
+ /** `where` and the date present (createdAt and required fields always are: Prisma rejects `not: null` on them). */
127
+ private notNull;
128
+ /** Date-only fields hold a calendar date: they are bucketed as stored, whatever the dashboard's timezone. */
129
+ private fieldTimeZone;
130
+ private fieldKind;
131
+ private rangeParams;
132
+ private dateSeries;
133
+ /** Labels of category values in the default language (enum labels, yes/no, related records' display field). */
134
+ private labeler;
135
+ private column;
136
+ private groupRows;
137
+ /** One point per category value, largest first. */
138
+ private categories;
139
+ /** Enum stages in order; cumulative = records at a stage or any later one. */
140
+ private funnel;
141
+ /** groupBy × stackBy: categories plus one series per stack value (the smallest beyond MAX_STACKS merged as "Other"). */
142
+ private stacked;
143
+ /**
144
+ * Several series (possibly from several entities) on one axis. Each series is computed within its entity's
145
+ * rules; a series the viewer can't read is dropped (not the chart).
146
+ */
147
+ private multiSeries;
148
+ /**
149
+ * Heatmap cells. `by: "hour"`: day of week (rows, Monday first) × hour of day (columns), in the dashboard's
150
+ * timezone. `by: "day"`: one cell per day, a column per week (Monday), up to the last 53 weeks.
151
+ */
152
+ private heatmap;
57
153
  }
154
+ export declare function reduce(values: number[], aggregate: WidgetIR["aggregate"]): number;
58
155
  export {};