@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,343 @@
1
+ import { useLunora } from '@lunora/react';
2
+ import { useState, useEffect } from 'react';
3
+ import { ShardInput } from './ShardInput-DNCsT1KW.js';
4
+ import { B as Badge } from './badge-B2PKA1-5.js';
5
+ import { B as Button } from './button-BhsN2uZH.js';
6
+ import { I as Input } from './input-XH4r1Pt1.js';
7
+ import { L as Label } from './label-D8ykjn5J.js';
8
+ import { T as Textarea } from './textarea-D3gaCU_-.js';
9
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
10
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
11
+ import { e as errorMessage, d as formatTimestamp, f as fireAndForget, a as adminRef } from './internal-BBZYexre.js';
12
+ import { r as recordShard } from './shard-history-DyebH1R5.js';
13
+ import { jsxDEV } from 'react/jsx-dev-runtime';
14
+
15
+ const argumentType = (argument) => {
16
+ if (argument.kind === "id") {
17
+ return argument.table === void 0 ? "id" : `id<${argument.table}>`;
18
+ }
19
+ if (argument.kind === "array") {
20
+ return argument.element === void 0 ? "array" : `${argument.element}[]`;
21
+ }
22
+ return argument.kind;
23
+ };
24
+ const formatSignature = (arguments_) => {
25
+ if (arguments_ === void 0 || arguments_.length === 0) {
26
+ return "()";
27
+ }
28
+ return `(${arguments_.map((argument) => `${argument.name}${argument.optional ? "?" : ""}: ${argumentType(argument)}`).join(", ")})`;
29
+ };
30
+ const placeholderValue = (kind) => {
31
+ switch (kind) {
32
+ case "array": {
33
+ return [];
34
+ }
35
+ case "bigint":
36
+ case "number": {
37
+ return 0;
38
+ }
39
+ case "boolean": {
40
+ return false;
41
+ }
42
+ case "object":
43
+ case "record": {
44
+ return {};
45
+ }
46
+ // string / id / literal / bytes / date / timestamp / any / unknown
47
+ default: {
48
+ return "";
49
+ }
50
+ }
51
+ };
52
+ const argumentsTemplate = (arguments_) => {
53
+ const template = {};
54
+ for (const argument of arguments_ ?? []) {
55
+ if (!argument.optional) {
56
+ template[argument.name] = placeholderValue(argument.kind);
57
+ }
58
+ }
59
+ return JSON.stringify(template, void 0, 2);
60
+ };
61
+
62
+ const RUN_AS = adminRef(ADMIN_FUNCTIONS.runAs);
63
+ const MAX_HISTORY = 10;
64
+ const formatResult = (value) => {
65
+ if (value === void 0) {
66
+ return "undefined";
67
+ }
68
+ return JSON.stringify(value, null, 2);
69
+ };
70
+ const FunctionRunner = ({
71
+ functions: functionsProp,
72
+ runAsIdentity = false
73
+ } = {}) => {
74
+ const t = useT();
75
+ const client = useLunora();
76
+ const [discovered, setDiscovered] = useState(null);
77
+ const [discoverError, setDiscoverError] = useState(null);
78
+ const functions = functionsProp ?? discovered ?? [];
79
+ const [selectedPath, setSelectedPath] = useState("");
80
+ const [argsText, setArgsText] = useState("{}");
81
+ const [shardKey, setShardKey] = useState("");
82
+ const [runAsUserId, setRunAsUserId] = useState("");
83
+ const [status, setStatus] = useState("idle");
84
+ const [result, setResult] = useState(void 0);
85
+ const [error, setError] = useState(null);
86
+ const [runs, setRuns] = useState([]);
87
+ useEffect(() => {
88
+ if (functionsProp !== void 0) {
89
+ return void 0;
90
+ }
91
+ let cancelled = false;
92
+ client.listFunctions().then((list) => {
93
+ if (!cancelled) {
94
+ setDiscovered(list);
95
+ }
96
+ return list;
97
+ }).catch((error_) => {
98
+ if (!cancelled) {
99
+ setDiscoverError(errorMessage(error_));
100
+ }
101
+ });
102
+ return () => {
103
+ cancelled = true;
104
+ };
105
+ }, [client, functionsProp]);
106
+ const effectivePath = selectedPath === "" ? functions[0]?.path ?? "" : selectedPath;
107
+ const selected = functions.find((descriptor) => descriptor.path === effectivePath);
108
+ const run = async () => {
109
+ if (!selected) {
110
+ return;
111
+ }
112
+ let parsedArgs;
113
+ try {
114
+ parsedArgs = argsText.trim() === "" ? {} : JSON.parse(argsText);
115
+ } catch (parseError) {
116
+ setStatus("error");
117
+ setResult(void 0);
118
+ setError(t("Invalid JSON args: {message}", {
119
+ message: parseError.message
120
+ }));
121
+ return;
122
+ }
123
+ const reference = {
124
+ __lunoraRef: selected.path
125
+ };
126
+ const options = shardKey.trim() === "" ? {} : {
127
+ shardKey: shardKey.trim()
128
+ };
129
+ const forgedUserId = runAsIdentity ? runAsUserId.trim() : "";
130
+ const record = (runStatus) => {
131
+ setRuns((previous) => {
132
+ const entry = {
133
+ argsText,
134
+ at: Date.now(),
135
+ id: (previous[0]?.id ?? 0) + 1,
136
+ kind: selected.kind,
137
+ path: selected.path,
138
+ shardKey,
139
+ status: runStatus
140
+ };
141
+ return [entry, ...previous].slice(0, MAX_HISTORY);
142
+ });
143
+ };
144
+ setStatus("running");
145
+ setError(null);
146
+ try {
147
+ let value;
148
+ if (forgedUserId === "") {
149
+ switch (selected.kind) {
150
+ case "action": {
151
+ value = await client.action(reference, parsedArgs, options);
152
+ break;
153
+ }
154
+ case "mutation": {
155
+ value = await client.mutation(reference, parsedArgs, options);
156
+ break;
157
+ }
158
+ default: {
159
+ value = await client.query(reference, parsedArgs, options);
160
+ }
161
+ }
162
+ } else {
163
+ value = await client.query(RUN_AS, {
164
+ args: parsedArgs,
165
+ functionPath: selected.path,
166
+ userId: forgedUserId
167
+ }, options);
168
+ }
169
+ recordShard(shardKey);
170
+ setResult(value);
171
+ setStatus("success");
172
+ record("success");
173
+ } catch (runError) {
174
+ setResult(void 0);
175
+ setError(runError.message);
176
+ setStatus("error");
177
+ record("error");
178
+ }
179
+ };
180
+ const loadRun = (entry_0) => {
181
+ setSelectedPath(entry_0.path);
182
+ setArgsText(entry_0.argsText);
183
+ setShardKey(entry_0.shardKey);
184
+ };
185
+ const onSelectChange = (event) => {
186
+ setSelectedPath(event.target.value);
187
+ };
188
+ const onArgsChange = (event_0) => {
189
+ setArgsText(event_0.target.value);
190
+ };
191
+ const onRunAsChange = (event_1) => {
192
+ setRunAsUserId(event_1.target.value);
193
+ };
194
+ const runOnce = () => {
195
+ fireAndForget(run());
196
+ };
197
+ const prefillArgs = () => {
198
+ setArgsText(argumentsTemplate(selected?.args));
199
+ };
200
+ return /* @__PURE__ */ jsxDEV("div", {
201
+ className: "flex flex-col gap-4",
202
+ "data-testid": "lunora-function-runner",
203
+ children: [discoverError !== null && /* @__PURE__ */ jsxDEV("p", {
204
+ className: "text-sm text-destructive",
205
+ "data-testid": "function-discover-error",
206
+ role: "alert",
207
+ children: discoverError
208
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
209
+ className: "flex flex-col gap-4 rounded-xl border border-border bg-card p-4",
210
+ children: [/* @__PURE__ */ jsxDEV("div", {
211
+ className: "flex flex-col gap-1.5",
212
+ children: [/* @__PURE__ */ jsxDEV(Label, {
213
+ htmlFor: "function-select",
214
+ children: t("Function")
215
+ }, void 0, false), /* @__PURE__ */ jsxDEV("select", {
216
+ "aria-label": t("Function"),
217
+ className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50",
218
+ "data-testid": "function-select",
219
+ id: "function-select",
220
+ onChange: onSelectChange,
221
+ value: effectivePath,
222
+ children: functions.map((descriptor_0) => /* @__PURE__ */ jsxDEV("option", {
223
+ value: descriptor_0.path,
224
+ children: [descriptor_0.path, " (", descriptor_0.kind, ")"]
225
+ }, descriptor_0.path, true))
226
+ }, void 0, false), selected !== void 0 && /* @__PURE__ */ jsxDEV("p", {
227
+ className: "text-xs text-muted-foreground",
228
+ children: [/* @__PURE__ */ jsxDEV("span", {
229
+ className: "mr-1.5 font-medium",
230
+ children: t("Signature")
231
+ }, void 0, false), /* @__PURE__ */ jsxDEV("code", {
232
+ className: "font-mono",
233
+ "data-testid": "function-signature",
234
+ children: formatSignature(selected.args)
235
+ }, void 0, false)]
236
+ }, void 0, true)]
237
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
238
+ className: "flex flex-col gap-1.5",
239
+ children: [/* @__PURE__ */ jsxDEV("div", {
240
+ className: "flex items-center justify-between gap-2",
241
+ children: [/* @__PURE__ */ jsxDEV(Label, {
242
+ htmlFor: "args-input",
243
+ children: t("Arguments")
244
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Button, {
245
+ "data-testid": "prefill-button",
246
+ disabled: selected === void 0,
247
+ onClick: prefillArgs,
248
+ size: "xs",
249
+ type: "button",
250
+ variant: "ghost",
251
+ children: t("Prefill")
252
+ }, void 0, false)]
253
+ }, void 0, true), /* @__PURE__ */ jsxDEV(Textarea, {
254
+ "aria-label": t("Arguments"),
255
+ className: "font-mono text-xs",
256
+ "data-testid": "args-input",
257
+ id: "args-input",
258
+ onChange: onArgsChange,
259
+ value: argsText
260
+ }, void 0, false)]
261
+ }, void 0, true), /* @__PURE__ */ jsxDEV(ShardInput, {
262
+ onChange: setShardKey,
263
+ testId: "shard-input",
264
+ value: shardKey
265
+ }, void 0, false), runAsIdentity && /* @__PURE__ */ jsxDEV("div", {
266
+ className: "flex flex-col gap-1.5",
267
+ "data-testid": "run-as-field",
268
+ children: [/* @__PURE__ */ jsxDEV(Label, {
269
+ htmlFor: "run-as-input",
270
+ children: t("Run as identity (userId)")
271
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Input, {
272
+ "aria-label": t("Run as identity (userId)"),
273
+ className: "font-mono text-xs",
274
+ "data-testid": "run-as-input",
275
+ id: "run-as-input",
276
+ onChange: onRunAsChange,
277
+ placeholder: t("Leave empty to run as admin"),
278
+ value: runAsUserId
279
+ }, void 0, false), /* @__PURE__ */ jsxDEV("p", {
280
+ className: "text-[11px] text-muted-foreground",
281
+ children: t("Dev only: runs the function as this user so you can test auth and RLS. Forged over the admin gate.")
282
+ }, void 0, false)]
283
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
284
+ className: "flex flex-wrap items-center gap-2",
285
+ children: [/* @__PURE__ */ jsxDEV(Button, {
286
+ "data-testid": "run-button",
287
+ disabled: status === "running" || selected === void 0,
288
+ onClick: runOnce,
289
+ type: "button",
290
+ children: t("Run")
291
+ }, void 0, false), selected !== void 0 && /* @__PURE__ */ jsxDEV(Badge, {
292
+ variant: "outline",
293
+ children: selected.kind
294
+ }, void 0, false), runAsIdentity && runAsUserId.trim() !== "" && /* @__PURE__ */ jsxDEV(Badge, {
295
+ "data-testid": "run-as-badge",
296
+ variant: "secondary",
297
+ children: t("as {userId}", {
298
+ userId: runAsUserId.trim()
299
+ })
300
+ }, void 0, false)]
301
+ }, void 0, true)]
302
+ }, void 0, true), status === "error" && error !== null && /* @__PURE__ */ jsxDEV("pre", {
303
+ className: "overflow-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-xs text-destructive",
304
+ "data-testid": "error",
305
+ role: "alert",
306
+ children: error
307
+ }, void 0, false), status === "success" && /* @__PURE__ */ jsxDEV("pre", {
308
+ className: "overflow-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-xs",
309
+ "data-testid": "result",
310
+ children: formatResult(result)
311
+ }, void 0, false), runs.length > 0 && /* @__PURE__ */ jsxDEV("ul", {
312
+ className: "flex flex-col overflow-hidden rounded-xl border border-border",
313
+ "data-testid": "fn-history",
314
+ children: runs.map((entry_1, index) => /* @__PURE__ */ jsxDEV("li", {
315
+ className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2 text-sm last:border-b-0",
316
+ "data-testid": "fn-history-row",
317
+ children: [/* @__PURE__ */ jsxDEV("span", {
318
+ className: entry_1.status === "success" ? "text-success" : "text-destructive",
319
+ "data-testid": `fn-history-status-${index.toString()}`,
320
+ children: entry_1.status === "success" ? "✓" : "✗"
321
+ }, void 0, false), " ", /* @__PURE__ */ jsxDEV("span", {
322
+ className: "font-mono text-xs",
323
+ children: [entry_1.path, " (", entry_1.kind, ")"]
324
+ }, void 0, true), " ", /* @__PURE__ */ jsxDEV("time", {
325
+ className: "text-xs text-muted-foreground",
326
+ children: formatTimestamp(entry_1.at)
327
+ }, void 0, false), " ", /* @__PURE__ */ jsxDEV(Button, {
328
+ className: "ml-auto",
329
+ "data-testid": `fn-history-load-${index.toString()}`,
330
+ onClick: () => {
331
+ loadRun(entry_1);
332
+ },
333
+ size: "xs",
334
+ type: "button",
335
+ variant: "ghost",
336
+ children: t("Load")
337
+ }, void 0, false)]
338
+ }, entry_1.id, true))
339
+ }, void 0, false)]
340
+ }, void 0, true);
341
+ };
342
+
343
+ export { FunctionRunner };