@lunora/studio 0.0.0 → 1.0.0-alpha.1

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 (81) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +123 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.ts +1402 -0
  5. package/dist/index.js +41 -0
  6. package/dist/mount.d.ts +21 -0
  7. package/dist/mount.js +26 -0
  8. package/dist/packem_shared/ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
  9. package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
  11. package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
  12. package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
  13. package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-Bxagrip8.js +111 -0
  16. package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
  17. package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
  18. package/dist/packem_shared/DataBrowser-Coz6jJE6.js +4542 -0
  19. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  20. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  21. package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
  22. package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
  23. package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
  24. package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
  25. package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
  26. package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
  27. package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
  28. package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
  29. package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
  30. package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
  31. package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
  32. package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
  33. package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
  34. package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
  35. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  36. package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
  37. package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
  38. package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
  39. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
  40. package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
  41. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
  42. package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
  43. package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
  44. package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
  45. package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
  46. package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
  47. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  48. package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
  49. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  50. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  51. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  52. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  53. package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
  54. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  55. package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
  56. package/dist/packem_shared/data-grid-CCh2Couo.js +183 -0
  57. package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
  58. package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
  59. package/dist/packem_shared/grid-features-DTjG6Sex.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-BBZYexre.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
  64. package/dist/packem_shared/reference-view-BCKIoai7.js +2180 -0
  65. package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
  66. package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
  67. package/dist/packem_shared/sql-editor-panel-CW2y2x9h.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
  74. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
  75. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  76. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  77. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  78. package/dist/standalone/studio.js +356 -0
  79. package/dist/styles.css +2 -0
  80. package/package.json +77 -17
  81. package/src/theme.css +59 -0
@@ -0,0 +1,371 @@
1
+ import { c } from 'react/compiler-runtime';
2
+ import { useLunora } from '@lunora/react';
3
+ import { useNavigate, useRouter, useSearch } from '@tanstack/react-router';
4
+ import { useRef, useState, useEffect } from 'react';
5
+ import { T as TIER_META } from './storage-tier-CL98eOvn.js';
6
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
7
+ import { f as fireAndForget } from './internal-BBZYexre.js';
8
+ import { DataBrowser } from './DataBrowser-Coz6jJE6.js';
9
+ import { GlobalDataBrowser } from './GlobalDataBrowser-9MhPEfgN.js';
10
+ import { jsxDEV } from 'react/jsx-dev-runtime';
11
+
12
+ const VALID_OPERATORS = /* @__PURE__ */ new Set(["contains", "eq", "gt", "gte", "lt", "lte", "ne"]);
13
+ const isFilterClause = (entry) => {
14
+ if (entry === null || typeof entry !== "object") {
15
+ return false;
16
+ }
17
+ const candidate = entry;
18
+ return typeof candidate.column === "string" && typeof candidate.operator === "string" && VALID_OPERATORS.has(candidate.operator);
19
+ };
20
+ const parseFilters = (raw) => {
21
+ if (typeof raw !== "string" || raw === "") {
22
+ return [];
23
+ }
24
+ try {
25
+ const parsed = JSON.parse(raw);
26
+ return Array.isArray(parsed) ? parsed.filter(isFilterClause) : [];
27
+ } catch {
28
+ return [];
29
+ }
30
+ };
31
+ const parseOrder = (raw) => {
32
+ if (typeof raw !== "string" || raw === "") {
33
+ return void 0;
34
+ }
35
+ const separator = raw.lastIndexOf(":");
36
+ if (separator <= 0) {
37
+ return void 0;
38
+ }
39
+ const column = raw.slice(0, separator);
40
+ const direction = raw.slice(separator + 1);
41
+ if (direction !== "asc" && direction !== "desc") {
42
+ return void 0;
43
+ }
44
+ return {
45
+ column,
46
+ direction
47
+ };
48
+ };
49
+ const searchToDataView = (search) => {
50
+ const filters = parseFilters(search["filters"]);
51
+ const orderBy = parseOrder(search["order"]);
52
+ return {
53
+ filters: filters.length > 0 ? filters : void 0,
54
+ orderBy,
55
+ search: typeof search["search"] === "string" && search["search"] !== "" ? search["search"] : void 0,
56
+ shard: typeof search["shard"] === "string" && search["shard"] !== "" ? search["shard"] : void 0,
57
+ table: typeof search["table"] === "string" && search["table"] !== "" ? search["table"] : void 0,
58
+ tier: search["schema"] === "global" ? "global" : "shard"
59
+ };
60
+ };
61
+ const dataViewToSearch = (view) => {
62
+ const filters = view.filters ?? [];
63
+ return {
64
+ filters: filters.length > 0 ? JSON.stringify(filters) : void 0,
65
+ order: view.orderBy === void 0 ? void 0 : `${view.orderBy.column}:${view.orderBy.direction}`,
66
+ schema: view.tier === "global" ? "global" : void 0,
67
+ search: view.search !== void 0 && view.search !== "" ? view.search : void 0,
68
+ shard: view.shard !== void 0 && view.shard !== "" ? view.shard : void 0,
69
+ table: view.table !== void 0 && view.table !== "" ? view.table : void 0
70
+ };
71
+ };
72
+
73
+ const STORAGE_KEY = "lunora-studio-saved-queries";
74
+ const MAX_SAVED = 50;
75
+ const store = () => {
76
+ try {
77
+ return globalThis.localStorage;
78
+ } catch {
79
+ return void 0;
80
+ }
81
+ };
82
+ const isSavedQuery = (entry) => entry !== null && typeof entry === "object" && typeof entry.name === "string" && typeof entry.view === "object" && entry.view !== null;
83
+ const loadSavedQueries = () => {
84
+ try {
85
+ const raw = store()?.getItem(STORAGE_KEY);
86
+ if (raw === null || raw === void 0) {
87
+ return [];
88
+ }
89
+ const parsed = JSON.parse(raw);
90
+ return Array.isArray(parsed) ? parsed.filter(isSavedQuery) : [];
91
+ } catch {
92
+ return [];
93
+ }
94
+ };
95
+ const persist = (queries) => {
96
+ const storage = store();
97
+ if (storage !== void 0) {
98
+ try {
99
+ storage.setItem(STORAGE_KEY, JSON.stringify(queries));
100
+ } catch {
101
+ }
102
+ }
103
+ };
104
+ const saveQuery = (name, view) => {
105
+ const trimmed = name.trim();
106
+ if (trimmed === "") {
107
+ return loadSavedQueries();
108
+ }
109
+ const next = [{
110
+ name: trimmed,
111
+ view
112
+ }, ...loadSavedQueries().filter((entry) => entry.name !== trimmed)].slice(0, MAX_SAVED);
113
+ persist(next);
114
+ return next;
115
+ };
116
+ const deleteSavedQuery = (name) => {
117
+ const next = loadSavedQueries().filter((entry) => entry.name !== name);
118
+ persist(next);
119
+ return next;
120
+ };
121
+
122
+ const SchemaSwitch = (t0) => {
123
+ const $ = c(15);
124
+ const {
125
+ onChange,
126
+ tier
127
+ } = t0;
128
+ const t = useT();
129
+ let t1;
130
+ if ($[0] !== onChange) {
131
+ t1 = (event) => {
132
+ onChange(event.target.value);
133
+ };
134
+ $[0] = onChange;
135
+ $[1] = t1;
136
+ } else {
137
+ t1 = $[1];
138
+ }
139
+ const onSelect = t1;
140
+ let t2;
141
+ if ($[2] !== t) {
142
+ t2 = t("Storage tier");
143
+ $[2] = t;
144
+ $[3] = t2;
145
+ } else {
146
+ t2 = $[3];
147
+ }
148
+ let t3;
149
+ if ($[4] !== t2) {
150
+ t3 = /* @__PURE__ */ jsxDEV("span", {
151
+ className: "font-mono text-[11px] tracking-wide uppercase text-muted-foreground",
152
+ children: t2
153
+ }, void 0, false);
154
+ $[4] = t2;
155
+ $[5] = t3;
156
+ } else {
157
+ t3 = $[5];
158
+ }
159
+ const t4 = TIER_META[tier];
160
+ let t5;
161
+ let t6;
162
+ if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
163
+ t5 = /* @__PURE__ */ jsxDEV("option", {
164
+ value: "shard",
165
+ children: TIER_META.shard.label
166
+ }, void 0, false);
167
+ t6 = /* @__PURE__ */ jsxDEV("option", {
168
+ value: "global",
169
+ children: TIER_META.global.label
170
+ }, void 0, false);
171
+ $[6] = t5;
172
+ $[7] = t6;
173
+ } else {
174
+ t5 = $[6];
175
+ t6 = $[7];
176
+ }
177
+ let t7;
178
+ if ($[8] !== onSelect || $[9] !== t4.title || $[10] !== tier) {
179
+ t7 = /* @__PURE__ */ jsxDEV("select", {
180
+ className: "h-8 w-full rounded-md border border-border bg-background px-2 text-sm outline-none focus-visible:border-ring",
181
+ "data-testid": "table-editor-schema",
182
+ id: "table-editor-schema",
183
+ onChange: onSelect,
184
+ title: t4.title,
185
+ value: tier,
186
+ children: [t5, t6]
187
+ }, void 0, true);
188
+ $[8] = onSelect;
189
+ $[9] = t4.title;
190
+ $[10] = tier;
191
+ $[11] = t7;
192
+ } else {
193
+ t7 = $[11];
194
+ }
195
+ let t8;
196
+ if ($[12] !== t3 || $[13] !== t7) {
197
+ t8 = /* @__PURE__ */ jsxDEV("label", {
198
+ className: "flex w-full flex-col gap-1",
199
+ htmlFor: "table-editor-schema",
200
+ children: [t3, t7]
201
+ }, void 0, true);
202
+ $[12] = t3;
203
+ $[13] = t7;
204
+ $[14] = t8;
205
+ } else {
206
+ t8 = $[14];
207
+ }
208
+ return t8;
209
+ };
210
+ const TableEditor = ({
211
+ editable = false,
212
+ initialShardKey
213
+ }) => {
214
+ const client = useLunora();
215
+ const navigate = useNavigate();
216
+ const router = useRouter();
217
+ const search = useSearch({
218
+ strict: false
219
+ });
220
+ const view = searchToDataView(search);
221
+ const searchRef = useRef(search);
222
+ searchRef.current = search;
223
+ const onDataRoute = () => router.state.location.pathname.split("/").findLast(Boolean) === "data";
224
+ const tier = view.tier ?? "shard";
225
+ const tableParameter = view.table;
226
+ const [savedQueries, setSavedQueries] = useState(() => loadSavedQueries());
227
+ const [globalTableNames, setGlobalTableNames] = useState(() => /* @__PURE__ */ new Set());
228
+ useEffect(() => {
229
+ fireAndForget((async () => {
230
+ try {
231
+ const tables = await client.listGlobalTables();
232
+ setGlobalTableNames(new Set(tables.map((table) => table.name)));
233
+ } catch {
234
+ }
235
+ })());
236
+ }, [client]);
237
+ useEffect(() => {
238
+ if (tier === "shard" && tableParameter !== void 0 && globalTableNames.has(tableParameter)) {
239
+ fireAndForget(navigate({
240
+ replace: true,
241
+ search: (previous) => {
242
+ return {
243
+ ...previous,
244
+ schema: "global"
245
+ };
246
+ },
247
+ to: "/data"
248
+ }));
249
+ }
250
+ }, [globalTableNames, navigate, tableParameter, tier]);
251
+ const selectTier = (next) => {
252
+ fireAndForget(navigate({
253
+ search: (previous_0) => {
254
+ return {
255
+ ...previous_0,
256
+ schema: next === "global" ? "global" : void 0,
257
+ table: void 0
258
+ };
259
+ },
260
+ to: "/data"
261
+ }));
262
+ };
263
+ const onSelectTable = (table_0) => {
264
+ if (!onDataRoute()) {
265
+ return;
266
+ }
267
+ fireAndForget(navigate({
268
+ search: (previous_1) => {
269
+ return {
270
+ ...previous_1,
271
+ table: table_0
272
+ };
273
+ },
274
+ to: "/data"
275
+ }));
276
+ };
277
+ const onNavigateToGlobal = (table_1) => {
278
+ fireAndForget(navigate({
279
+ search: (previous_2) => {
280
+ return {
281
+ ...previous_2,
282
+ schema: "global",
283
+ table: table_1
284
+ };
285
+ },
286
+ to: "/data"
287
+ }));
288
+ };
289
+ const onViewChange = (next_0) => {
290
+ const patch = dataViewToSearch(next_0);
291
+ const {
292
+ current
293
+ } = searchRef;
294
+ const unchanged = (current["filters"] ?? void 0) === patch.filters && (current["order"] ?? void 0) === patch.order && (current["search"] ?? void 0) === patch.search && (current["shard"] ?? void 0) === patch.shard;
295
+ if (unchanged || !onDataRoute()) {
296
+ return;
297
+ }
298
+ fireAndForget(navigate({
299
+ replace: true,
300
+ search: (previous_3) => {
301
+ return {
302
+ ...previous_3,
303
+ filters: patch.filters,
304
+ order: patch.order,
305
+ search: patch.search,
306
+ shard: patch.shard
307
+ };
308
+ },
309
+ to: "/data"
310
+ }));
311
+ };
312
+ const onCopyLink = () => {
313
+ try {
314
+ fireAndForget(globalThis.navigator.clipboard.writeText(globalThis.location.href));
315
+ } catch {
316
+ }
317
+ };
318
+ const onSaveQuery = (name, snapshot) => {
319
+ setSavedQueries(saveQuery(name, snapshot));
320
+ };
321
+ const onDeleteQuery = (name_0) => {
322
+ setSavedQueries(deleteSavedQuery(name_0));
323
+ };
324
+ const onApplyQuery = (query) => {
325
+ const patch_0 = dataViewToSearch(query.view);
326
+ fireAndForget(navigate({
327
+ search: () => {
328
+ return {
329
+ filters: patch_0.filters,
330
+ order: patch_0.order,
331
+ schema: patch_0.schema,
332
+ search: patch_0.search,
333
+ shard: patch_0.shard,
334
+ table: patch_0.table
335
+ };
336
+ },
337
+ to: "/data"
338
+ }));
339
+ };
340
+ const queryBar = {
341
+ onApplyQuery,
342
+ onCopyLink,
343
+ onDeleteQuery,
344
+ onSaveQuery,
345
+ saved: savedQueries
346
+ };
347
+ const schemaSwitch = /* @__PURE__ */ jsxDEV(SchemaSwitch, {
348
+ onChange: selectTier,
349
+ tier
350
+ }, void 0, false);
351
+ return tier === "global" ? /* @__PURE__ */ jsxDEV(GlobalDataBrowser, {
352
+ initialTable: tableParameter,
353
+ onSelectTable,
354
+ schemaSwitch
355
+ }, void 0, false) : /* @__PURE__ */ jsxDEV(DataBrowser, {
356
+ editable,
357
+ globalTableNames,
358
+ initialFilters: view.filters,
359
+ initialOrderBy: view.orderBy,
360
+ initialSearch: view.search,
361
+ initialShardKey: view.shard ?? initialShardKey,
362
+ onNavigateToGlobal,
363
+ onSelectTable,
364
+ onViewChange,
365
+ queryBar,
366
+ schemaSwitch,
367
+ tableParam: tableParameter
368
+ }, void 0, false);
369
+ };
370
+
371
+ export { TableEditor };
@@ -0,0 +1,159 @@
1
+ import { useState, useMemo } from 'react';
2
+ import { C as Card, a as CardContent } from './card-DURq3ElK.js';
3
+ import { T as Table, a as TableHeader, b as TableRow, c as TableHead, d as TableBody, e as TableCell } from './table-_RzNvy3R.js';
4
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
5
+ import { c as cn } from './utils-B05Dmz_H.js';
6
+ import { jsxDEV } from 'react/jsx-dev-runtime';
7
+
8
+ const ADVISORY_LEVEL = {
9
+ ERROR: "error",
10
+ INFO: "info",
11
+ WARN: "warning"
12
+ };
13
+ const advisoryRow = (finding) => {
14
+ const table = typeof finding.metadata["table"] === "string" ? finding.metadata["table"] : void 0;
15
+ return {
16
+ description: `${finding.detail} ${finding.remediation}`,
17
+ entity: table,
18
+ issueType: finding.title,
19
+ key: finding.cacheKey,
20
+ level: ADVISORY_LEVEL[finding.level]
21
+ };
22
+ };
23
+ const LEVELS = ["error", "warning", "info"];
24
+ const LEVEL_DOT = {
25
+ error: "bg-destructive",
26
+ info: "bg-info",
27
+ warning: "bg-warning"
28
+ };
29
+ const levelLabel = (t, level) => ({
30
+ error: t("Errors"),
31
+ info: t("Info"),
32
+ warning: t("Warnings")
33
+ })[level];
34
+ const levelCount = (t, level, count) => ({
35
+ error: t("{count} errors", {
36
+ count
37
+ }),
38
+ info: t("{count} suggestions", {
39
+ count
40
+ }),
41
+ warning: t("{count} warnings", {
42
+ count
43
+ })
44
+ })[level];
45
+ const emptyTitle = (t, level) => ({
46
+ error: t("No errors detected"),
47
+ info: t("No suggestions"),
48
+ warning: t("No warnings detected")
49
+ })[level];
50
+ const AdvisorView = ({
51
+ error = null,
52
+ rows,
53
+ testId,
54
+ toolbar
55
+ }) => {
56
+ const t = useT();
57
+ const [active, setActive] = useState("error");
58
+ const counts = useMemo(() => {
59
+ const tally = {
60
+ error: 0,
61
+ info: 0,
62
+ warning: 0
63
+ };
64
+ for (const row of rows ?? []) {
65
+ tally[row.level] += 1;
66
+ }
67
+ return tally;
68
+ }, [rows]);
69
+ const visible = (rows ?? []).filter((row_0) => row_0.level === active);
70
+ const selectTab = (event) => {
71
+ setActive(event.currentTarget.dataset.level);
72
+ };
73
+ return /* @__PURE__ */ jsxDEV("div", {
74
+ className: "flex flex-col gap-3",
75
+ "data-testid": testId,
76
+ children: [/* @__PURE__ */ jsxDEV("div", {
77
+ className: "flex border-b border-border",
78
+ role: "tablist",
79
+ children: LEVELS.map((level) => /* @__PURE__ */ jsxDEV("button", {
80
+ "aria-selected": active === level,
81
+ className: "flex min-w-32 flex-col gap-0.5 border-b-2 border-transparent px-4 py-2 text-start outline-none transition-colors hover:bg-muted/40 focus-visible:bg-muted/40 aria-selected:border-foreground",
82
+ "data-level": level,
83
+ "data-testid": `${testId}-tab-${level}`,
84
+ onClick: selectTab,
85
+ role: "tab",
86
+ type: "button",
87
+ children: [/* @__PURE__ */ jsxDEV("span", {
88
+ className: "flex items-center gap-2 text-sm font-medium text-foreground",
89
+ children: [/* @__PURE__ */ jsxDEV("span", {
90
+ "aria-hidden": "true",
91
+ className: cn("size-2 rounded-sm", LEVEL_DOT[level])
92
+ }, void 0, false), levelLabel(t, level)]
93
+ }, void 0, true), /* @__PURE__ */ jsxDEV("span", {
94
+ className: "ps-4 text-xs text-muted-foreground",
95
+ children: levelCount(t, level, counts[level])
96
+ }, void 0, false)]
97
+ }, level, true))
98
+ }, void 0, false), toolbar !== void 0 && /* @__PURE__ */ jsxDEV("div", {
99
+ className: "flex flex-wrap items-center gap-2",
100
+ children: toolbar
101
+ }, void 0, false), error !== null && /* @__PURE__ */ jsxDEV("p", {
102
+ className: "text-sm text-destructive",
103
+ "data-testid": `${testId}-error`,
104
+ role: "alert",
105
+ children: error
106
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Card, {
107
+ className: "overflow-hidden py-0",
108
+ children: /* @__PURE__ */ jsxDEV(CardContent, {
109
+ className: "px-0",
110
+ children: /* @__PURE__ */ jsxDEV(Table, {
111
+ children: [/* @__PURE__ */ jsxDEV(TableHeader, {
112
+ children: /* @__PURE__ */ jsxDEV(TableRow, {
113
+ children: [/* @__PURE__ */ jsxDEV(TableHead, {
114
+ children: t("Issue type")
115
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
116
+ children: t("Entity/item")
117
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
118
+ children: t("Description")
119
+ }, void 0, false)]
120
+ }, void 0, true)
121
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableBody, {
122
+ children: visible.length === 0 ? /* @__PURE__ */ jsxDEV(TableRow, {
123
+ children: /* @__PURE__ */ jsxDEV(TableCell, {
124
+ className: "h-40 text-center align-middle text-muted-foreground",
125
+ colSpan: 3,
126
+ "data-testid": `${testId}-empty`,
127
+ children: [/* @__PURE__ */ jsxDEV("span", {
128
+ className: "block text-sm font-medium text-foreground",
129
+ children: emptyTitle(t, active)
130
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
131
+ className: "block text-sm",
132
+ children: t("Nothing to report for this deployment.")
133
+ }, void 0, false)]
134
+ }, void 0, true)
135
+ }, void 0, false) : visible.map((row_1) => /* @__PURE__ */ jsxDEV(TableRow, {
136
+ children: [/* @__PURE__ */ jsxDEV(TableCell, {
137
+ className: "font-medium text-foreground",
138
+ children: row_1.issueType
139
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
140
+ className: "font-mono text-xs text-muted-foreground",
141
+ children: row_1.entity ?? "—"
142
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
143
+ className: "text-muted-foreground",
144
+ children: [/* @__PURE__ */ jsxDEV("span", {
145
+ children: row_1.description
146
+ }, void 0, false), row_1.action !== void 0 && /* @__PURE__ */ jsxDEV("span", {
147
+ className: "mt-1.5 flex flex-wrap items-center gap-1.5",
148
+ children: row_1.action
149
+ }, void 0, false)]
150
+ }, void 0, true)]
151
+ }, row_1.key, true))
152
+ }, void 0, false)]
153
+ }, void 0, true)
154
+ }, void 0, false)
155
+ }, void 0, false)]
156
+ }, void 0, true);
157
+ };
158
+
159
+ export { AdvisorView as A, advisoryRow as a };
@@ -0,0 +1,108 @@
1
+ const aggregateMetrics = (results) => {
2
+ let totalRequests = 0;
3
+ let totalErrors = 0;
4
+ let totalDatabaseSize = 0;
5
+ let reachable = 0;
6
+ let failed = 0;
7
+ let cacheHits = 0;
8
+ let cacheTotal = 0;
9
+ for (const {
10
+ metrics
11
+ } of results) {
12
+ if (metrics === null) {
13
+ failed += 1;
14
+ continue;
15
+ }
16
+ reachable += 1;
17
+ totalRequests += metrics.requests;
18
+ totalErrors += metrics.errors;
19
+ totalDatabaseSize += metrics.databaseSize ?? 0;
20
+ if (metrics.cache !== null) {
21
+ cacheHits += metrics.cache.hits;
22
+ cacheTotal += metrics.cache.hits + metrics.cache.misses;
23
+ }
24
+ }
25
+ return {
26
+ failed,
27
+ // eslint-disable-next-line unicorn/no-null -- hitRate is part of the public AggregateMetrics type, which models "no cache" as null
28
+ hitRate: cacheTotal === 0 ? null : cacheHits / cacheTotal,
29
+ reachable,
30
+ totalDatabaseSize,
31
+ totalErrors,
32
+ totalRequests
33
+ };
34
+ };
35
+ const shardsToAggregate = (current, recents) => {
36
+ const seen = /* @__PURE__ */ new Set();
37
+ const out = [];
38
+ for (const shard of ["", current.trim(), ...recents]) {
39
+ if (!seen.has(shard)) {
40
+ seen.add(shard);
41
+ out.push(shard);
42
+ }
43
+ }
44
+ return out;
45
+ };
46
+ const percentile = (values, p) => {
47
+ if (values.length === 0) {
48
+ return 0;
49
+ }
50
+ values.sort((a, b) => a - b);
51
+ if (p <= 0) {
52
+ return values[0] ?? 0;
53
+ }
54
+ if (p >= 100) {
55
+ return values.at(-1) ?? 0;
56
+ }
57
+ const index = Math.ceil(p / 100 * values.length) - 1;
58
+ return values[index] ?? 0;
59
+ };
60
+ const computeLatencyPercentiles = (snapshot) => {
61
+ const snap = snapshot;
62
+ const functionList = snap.functions;
63
+ if (!functionList || functionList.length === 0) {
64
+ return {
65
+ p90: 0,
66
+ p95: 0
67
+ };
68
+ }
69
+ const CAP = 1e3;
70
+ const samples = [];
71
+ for (const functionStat of functionList) {
72
+ if (functionStat.calls <= 0) {
73
+ continue;
74
+ }
75
+ const avg = functionStat.totalDurationMs / functionStat.calls;
76
+ const reps = Math.min(functionStat.calls, CAP);
77
+ for (let index = 0; index < reps; index += 1) {
78
+ samples.push(avg);
79
+ }
80
+ }
81
+ if (samples.length === 0) {
82
+ return {
83
+ p90: 0,
84
+ p95: 0
85
+ };
86
+ }
87
+ return {
88
+ p90: percentile([...samples], 90),
89
+ p95: percentile([...samples], 95)
90
+ };
91
+ };
92
+ const computeDelta = (baseline, current, direction = "neutral") => {
93
+ const delta = current - baseline;
94
+ const pct = baseline === 0 ? null : delta / baseline * 100;
95
+ return {
96
+ delta,
97
+ direction,
98
+ pct
99
+ };
100
+ };
101
+ const enrichQueryStats = (entries) => entries.map((entry) => {
102
+ return {
103
+ ...entry,
104
+ avgDurationMs: entry.execCount > 0 ? entry.totalDurationMs / entry.execCount : 0
105
+ };
106
+ });
107
+
108
+ export { aggregateMetrics, computeDelta, computeLatencyPercentiles, enrichQueryStats, percentile, shardsToAggregate };