@alepha/devtools 0.13.6 → 0.13.8

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 (103) hide show
  1. package/assets/devtools/asset.BZV40eAE.css +1 -0
  2. package/assets/devtools/asset.Bhpm0ujk.css +1 -0
  3. package/assets/devtools/chunk.17gtbQUO.js +1 -0
  4. package/assets/devtools/chunk.1mem8WHh.js +1 -0
  5. package/assets/devtools/chunk.B0aYes_4.js +1 -0
  6. package/assets/devtools/chunk.BULoWCgJ.js +7 -0
  7. package/assets/devtools/chunk.BYrPfJRg.js +1 -0
  8. package/assets/devtools/chunk.BjFrJKj1.js +1 -0
  9. package/assets/devtools/chunk.BlqFPyLh.js +1 -0
  10. package/assets/devtools/chunk.BmJ7-uBd.js +1 -0
  11. package/assets/devtools/chunk.BzE7YYkj.js +6 -0
  12. package/assets/devtools/chunk.C49FcqzR.js +1 -0
  13. package/assets/devtools/chunk.CBGZOsrp.js +9 -0
  14. package/assets/devtools/chunk.CHRbA_gU.js +1 -0
  15. package/assets/devtools/chunk.Ct58VlQl.js +1 -0
  16. package/assets/devtools/chunk.Cw2RCl4F.js +1 -0
  17. package/assets/devtools/chunk.CyQeq1kA.js +1 -0
  18. package/assets/devtools/chunk.CzbujtK7.js +1 -0
  19. package/assets/devtools/chunk.DA9XnVAa.js +1 -0
  20. package/assets/devtools/chunk.DEUHUxKv.js +1 -0
  21. package/assets/devtools/chunk.DGW-W4Kc.js +1 -0
  22. package/assets/devtools/chunk.DHWcJNNS.js +1 -0
  23. package/assets/devtools/chunk.DIfRZc20.js +1 -0
  24. package/assets/devtools/chunk.DJJIo7HU.js +1 -0
  25. package/assets/devtools/chunk.DJOi4_So.js +1 -0
  26. package/assets/devtools/chunk.DR0SHXXd.js +1 -0
  27. package/assets/devtools/chunk.DinJSUfH.js +1 -0
  28. package/assets/devtools/chunk.DooL4OcT.js +1 -0
  29. package/assets/devtools/chunk.Dry2LXOT.js +1 -0
  30. package/assets/devtools/chunk.IVvrfXp1.js +1 -0
  31. package/assets/devtools/chunk.OlMI8g2F.js +1 -0
  32. package/assets/devtools/chunk.Pj_uCbSv.js +1 -0
  33. package/assets/devtools/chunk.RTodzvo0.js +2 -0
  34. package/assets/devtools/chunk.YXYL4YAO.js +2 -0
  35. package/assets/devtools/chunk.fpKvkQeU.js +1 -0
  36. package/assets/devtools/chunk.qDx9cjbN.js +1 -0
  37. package/assets/devtools/chunk.rJToME5k.js +1 -0
  38. package/assets/devtools/chunk.rohGhT-A.js +1 -0
  39. package/assets/devtools/chunk.uyVen0u2.js +1 -0
  40. package/assets/devtools/entry.BY4L2Uc6.js +75 -0
  41. package/assets/devtools/index.html +11 -0
  42. package/dist/index.d.ts +249 -32
  43. package/dist/index.js +253 -22
  44. package/dist/index.js.map +1 -1
  45. package/package.json +18 -12
  46. package/src/{DevToolsProvider.ts → api/DevToolsProvider.ts} +29 -1
  47. package/src/{providers → api/providers}/DevToolsMetadataProvider.ts +210 -2
  48. package/src/api/schemas/DevAtomMetadata.ts +26 -0
  49. package/src/api/schemas/DevCommandMetadata.ts +9 -0
  50. package/src/api/schemas/DevEntityMetadata.ts +57 -0
  51. package/src/api/schemas/DevEnvMetadata.ts +22 -0
  52. package/src/{schemas → api/schemas}/DevMetadata.ts +10 -1
  53. package/src/api/schemas/DevRouteMetadata.ts +8 -0
  54. package/src/index.ts +23 -16
  55. package/src/ui/AppRouter.tsx +85 -2
  56. package/src/ui/components/DevAtomsViewer.tsx +636 -0
  57. package/src/ui/components/DevCacheInspector.tsx +423 -0
  58. package/src/ui/components/DevDashboard.tsx +188 -0
  59. package/src/ui/components/DevEnvExplorer.tsx +462 -0
  60. package/src/ui/components/DevLayout.tsx +65 -4
  61. package/src/ui/components/DevLogViewer.tsx +161 -163
  62. package/src/ui/components/DevQueueMonitor.tsx +51 -0
  63. package/src/ui/components/DevTopicsViewer.tsx +690 -0
  64. package/src/ui/components/actions/ActionGroup.tsx +37 -0
  65. package/src/ui/components/actions/ActionItem.tsx +138 -0
  66. package/src/ui/components/actions/DevActionsExplorer.tsx +132 -0
  67. package/src/ui/components/actions/MethodBadge.tsx +18 -0
  68. package/src/ui/components/actions/SchemaViewer.tsx +21 -0
  69. package/src/ui/components/actions/TryItPanel.tsx +140 -0
  70. package/src/ui/components/actions/constants.ts +7 -0
  71. package/src/ui/components/actions/helpers.ts +18 -0
  72. package/src/ui/components/actions/index.ts +8 -0
  73. package/src/ui/components/db/ColumnBadge.tsx +55 -0
  74. package/src/ui/components/db/DevDbStudio.tsx +485 -0
  75. package/src/ui/components/db/constants.ts +11 -0
  76. package/src/ui/components/db/index.ts +4 -0
  77. package/src/ui/components/db/types.ts +7 -0
  78. package/src/ui/components/graph/DevDependencyGraph.tsx +358 -0
  79. package/src/ui/components/graph/GraphControls.tsx +162 -0
  80. package/src/ui/components/graph/NodeDetails.tsx +181 -0
  81. package/src/ui/components/graph/ProviderNode.tsx +97 -0
  82. package/src/ui/components/graph/constants.ts +35 -0
  83. package/src/ui/components/graph/helpers.ts +443 -0
  84. package/src/ui/components/graph/index.ts +7 -0
  85. package/src/ui/components/graph/types.ts +28 -0
  86. package/src/ui/styles.css +0 -6
  87. package/src/ui/resources/wotfardregular/stylesheet.css +0 -12
  88. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.eot +0 -0
  89. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.ttf +0 -0
  90. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.woff2 +0 -0
  91. /package/src/{entities → api/entities}/logs.ts +0 -0
  92. /package/src/{providers → api/providers}/DevToolsDatabaseProvider.ts +0 -0
  93. /package/src/{repositories → api/repositories}/LogRepository.ts +0 -0
  94. /package/src/{schemas → api/schemas}/DevActionMetadata.ts +0 -0
  95. /package/src/{schemas → api/schemas}/DevBucketMetadata.ts +0 -0
  96. /package/src/{schemas → api/schemas}/DevCacheMetadata.ts +0 -0
  97. /package/src/{schemas → api/schemas}/DevModuleMetadata.ts +0 -0
  98. /package/src/{schemas → api/schemas}/DevPageMetadata.ts +0 -0
  99. /package/src/{schemas → api/schemas}/DevProviderMetadata.ts +0 -0
  100. /package/src/{schemas → api/schemas}/DevQueueMetadata.ts +0 -0
  101. /package/src/{schemas → api/schemas}/DevRealmMetadata.ts +0 -0
  102. /package/src/{schemas → api/schemas}/DevSchedulerMetadata.ts +0 -0
  103. /package/src/{schemas → api/schemas}/DevTopicMetadata.ts +0 -0
@@ -0,0 +1,37 @@
1
+ import { ui } from "@alepha/ui";
2
+ import { Accordion, Badge, Box, Group, Text } from "@mantine/core";
3
+ import { IconChevronRight } from "@tabler/icons-react";
4
+ import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
5
+ import { ActionItem } from "./ActionItem.tsx";
6
+
7
+ interface ActionGroupProps {
8
+ group: string;
9
+ actions: DevActionMetadata[];
10
+ }
11
+
12
+ export const ActionGroup = ({ group, actions }: ActionGroupProps) => (
13
+ <Box>
14
+ <Group gap="xs" mb="xs">
15
+ <IconChevronRight size={14} opacity={0.5} />
16
+ <Text size="xs" fw={600} tt="uppercase" c="dimmed">
17
+ {group}
18
+ </Text>
19
+ <Badge variant="light" color="gray" size="xs">
20
+ {actions.length}
21
+ </Badge>
22
+ </Group>
23
+ <Accordion
24
+ variant="separated"
25
+ styles={{
26
+ item: {
27
+ backgroundColor: ui.colors.surface,
28
+ border: `1px solid ${ui.colors.border}`,
29
+ },
30
+ }}
31
+ >
32
+ {actions.map((action) => (
33
+ <ActionItem key={action.fullPath} action={action} />
34
+ ))}
35
+ </Accordion>
36
+ </Box>
37
+ );
@@ -0,0 +1,138 @@
1
+ import {
2
+ Accordion,
3
+ ActionIcon,
4
+ Badge,
5
+ Box,
6
+ Code,
7
+ CopyButton,
8
+ Group,
9
+ Stack,
10
+ Tabs,
11
+ Text,
12
+ Tooltip,
13
+ } from "@mantine/core";
14
+ import {
15
+ IconCheck,
16
+ IconCopy,
17
+ IconLock,
18
+ IconTerminal,
19
+ } from "@tabler/icons-react";
20
+ import { useMemo } from "react";
21
+ import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
22
+ import { generateCurl } from "./helpers.ts";
23
+ import { MethodBadge } from "./MethodBadge.tsx";
24
+ import { SchemaViewer } from "./SchemaViewer.tsx";
25
+ import { TryItPanel } from "./TryItPanel.tsx";
26
+
27
+ interface ActionItemProps {
28
+ action: DevActionMetadata;
29
+ }
30
+
31
+ export const ActionItem = ({ action }: ActionItemProps) => {
32
+ const curl = useMemo(() => generateCurl(action), [action]);
33
+
34
+ return (
35
+ <Accordion.Item value={action.fullPath} opacity={action.disabled ? 0.5 : 1}>
36
+ <Accordion.Control>
37
+ <Group gap="sm" wrap="nowrap">
38
+ <MethodBadge method={action.method} />
39
+ <Text size="sm" ff="monospace" style={{ wordBreak: "break-all" }}>
40
+ {action.fullPath}
41
+ </Text>
42
+ {action.secure && (
43
+ <Tooltip label="Requires authentication">
44
+ <IconLock size={14} opacity={0.5} />
45
+ </Tooltip>
46
+ )}
47
+ {action.disabled && (
48
+ <Badge size="xs" variant="light" color="gray">
49
+ disabled
50
+ </Badge>
51
+ )}
52
+ </Group>
53
+ </Accordion.Control>
54
+ <Accordion.Panel>
55
+ <Stack gap="md">
56
+ {(action.description || action.summary) && (
57
+ <Text size="sm" c="dimmed">
58
+ {action.description || action.summary}
59
+ </Text>
60
+ )}
61
+
62
+ <Tabs defaultValue="schema" variant="outline">
63
+ <Tabs.List>
64
+ <Tabs.Tab value="schema" size="xs">
65
+ Schema
66
+ </Tabs.Tab>
67
+ <Tabs.Tab value="try" size="xs">
68
+ Try It
69
+ </Tabs.Tab>
70
+ <Tabs.Tab
71
+ value="curl"
72
+ size="xs"
73
+ leftSection={<IconTerminal size={12} />}
74
+ >
75
+ cURL
76
+ </Tabs.Tab>
77
+ </Tabs.List>
78
+
79
+ <Tabs.Panel value="schema" pt="md">
80
+ <Stack gap="md">
81
+ <Group gap="xl" align="flex-start">
82
+ <SchemaViewer
83
+ schema={action.params}
84
+ label="Path Parameters"
85
+ />
86
+ <SchemaViewer
87
+ schema={action.query}
88
+ label="Query Parameters"
89
+ />
90
+ </Group>
91
+ <SchemaViewer schema={action.body} label="Request Body" />
92
+ <SchemaViewer schema={action.response} label="Response" />
93
+ {!action.params &&
94
+ !action.query &&
95
+ !action.body &&
96
+ !action.response && (
97
+ <Text size="sm" c="dimmed">
98
+ No schema defined
99
+ </Text>
100
+ )}
101
+ </Stack>
102
+ </Tabs.Panel>
103
+
104
+ <Tabs.Panel value="try" pt="md">
105
+ <TryItPanel action={action} />
106
+ </Tabs.Panel>
107
+
108
+ <Tabs.Panel value="curl" pt="md">
109
+ <Box pos="relative">
110
+ <CopyButton value={curl}>
111
+ {({ copied, copy }) => (
112
+ <ActionIcon
113
+ size="sm"
114
+ variant="subtle"
115
+ onClick={copy}
116
+ pos="absolute"
117
+ top={8}
118
+ right={8}
119
+ >
120
+ {copied ? (
121
+ <IconCheck size={14} />
122
+ ) : (
123
+ <IconCopy size={14} />
124
+ )}
125
+ </ActionIcon>
126
+ )}
127
+ </CopyButton>
128
+ <Code block style={{ fontSize: 11 }}>
129
+ {curl}
130
+ </Code>
131
+ </Box>
132
+ </Tabs.Panel>
133
+ </Tabs>
134
+ </Stack>
135
+ </Accordion.Panel>
136
+ </Accordion.Item>
137
+ );
138
+ };
@@ -0,0 +1,132 @@
1
+ import { useInject } from "@alepha/react";
2
+ import {
3
+ Badge,
4
+ Flex,
5
+ Group,
6
+ Loader,
7
+ ScrollArea,
8
+ SegmentedControl,
9
+ Stack,
10
+ Text,
11
+ TextInput,
12
+ } from "@mantine/core";
13
+ import { IconApi, IconSearch } from "@tabler/icons-react";
14
+ import { HttpClient } from "alepha/server";
15
+ import { useEffect, useMemo, useState } from "react";
16
+ import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
17
+ import { devMetadataSchema } from "../../../api/schemas/DevMetadata.ts";
18
+ import { ActionGroup } from "./ActionGroup.tsx";
19
+
20
+ export const DevActionsExplorer = () => {
21
+ const http = useInject(HttpClient);
22
+ const [actions, setActions] = useState<DevActionMetadata[]>([]);
23
+ const [loading, setLoading] = useState(true);
24
+ const [search, setSearch] = useState("");
25
+ const [methodFilter, setMethodFilter] = useState("all");
26
+
27
+ useEffect(() => {
28
+ http
29
+ .fetch("/devtools/api/metadata", {
30
+ schema: { response: devMetadataSchema },
31
+ })
32
+ .then((res) => {
33
+ setActions(res.data.actions);
34
+ setLoading(false);
35
+ });
36
+ }, []);
37
+
38
+ const filteredActions = useMemo(() => {
39
+ return actions.filter((action) => {
40
+ if (action.hide) return false;
41
+ if (
42
+ methodFilter !== "all" &&
43
+ action.method.toUpperCase() !== methodFilter
44
+ ) {
45
+ return false;
46
+ }
47
+ if (search) {
48
+ const searchLower = search.toLowerCase();
49
+ return (
50
+ action.fullPath.toLowerCase().includes(searchLower) ||
51
+ action.name.toLowerCase().includes(searchLower) ||
52
+ action.group.toLowerCase().includes(searchLower) ||
53
+ action.description?.toLowerCase().includes(searchLower)
54
+ );
55
+ }
56
+ return true;
57
+ });
58
+ }, [actions, search, methodFilter]);
59
+
60
+ const groupedActions = useMemo(() => {
61
+ const groups: Record<string, DevActionMetadata[]> = {};
62
+ for (const action of filteredActions) {
63
+ const group = action.group || "Other";
64
+ if (!groups[group]) groups[group] = [];
65
+ groups[group].push(action);
66
+ }
67
+ return Object.entries(groups).sort(([a], [b]) => a.localeCompare(b));
68
+ }, [filteredActions]);
69
+
70
+ if (loading) {
71
+ return (
72
+ <Flex align="center" justify="center" h="100%">
73
+ <Loader size="sm" />
74
+ </Flex>
75
+ );
76
+ }
77
+
78
+ return (
79
+ <Flex direction="column" gap="md" w="100%" h="100%" p={"xl"}>
80
+ <Group justify="space-between" wrap="nowrap">
81
+ <Group gap="sm">
82
+ <IconApi size={24} opacity={0.7} />
83
+ <Text size="lg" fw={500}>
84
+ Actions
85
+ </Text>
86
+ <Badge variant="light" color="gray" size="sm">
87
+ {filteredActions.length}
88
+ </Badge>
89
+ </Group>
90
+ </Group>
91
+
92
+ <Group gap="sm">
93
+ <TextInput
94
+ placeholder="Search actions..."
95
+ leftSection={<IconSearch size={14} />}
96
+ value={search}
97
+ onChange={(e) => setSearch(e.currentTarget.value)}
98
+ style={{ flex: 1 }}
99
+ size="sm"
100
+ />
101
+ <SegmentedControl
102
+ size="xs"
103
+ value={methodFilter}
104
+ onChange={setMethodFilter}
105
+ data={[
106
+ { label: "All", value: "all" },
107
+ { label: "GET", value: "GET" },
108
+ { label: "POST", value: "POST" },
109
+ { label: "PUT", value: "PUT" },
110
+ { label: "DELETE", value: "DELETE" },
111
+ ]}
112
+ />
113
+ </Group>
114
+
115
+ <ScrollArea flex={1} offsetScrollbars>
116
+ {groupedActions.length === 0 ? (
117
+ <Flex align="center" justify="center" h={200}>
118
+ <Text c="dimmed">No actions found</Text>
119
+ </Flex>
120
+ ) : (
121
+ <Stack gap="md">
122
+ {groupedActions.map(([group, groupActions]) => (
123
+ <ActionGroup key={group} group={group} actions={groupActions} />
124
+ ))}
125
+ </Stack>
126
+ )}
127
+ </ScrollArea>
128
+ </Flex>
129
+ );
130
+ };
131
+
132
+ export default DevActionsExplorer;
@@ -0,0 +1,18 @@
1
+ import { Badge } from "@mantine/core";
2
+ import { METHOD_COLORS } from "./constants.ts";
3
+
4
+ interface MethodBadgeProps {
5
+ method: string;
6
+ }
7
+
8
+ export const MethodBadge = ({ method }: MethodBadgeProps) => (
9
+ <Badge
10
+ variant="light"
11
+ color={METHOD_COLORS[method.toUpperCase()] || "gray"}
12
+ size="sm"
13
+ radius="sm"
14
+ w={60}
15
+ >
16
+ {method.toUpperCase()}
17
+ </Badge>
18
+ );
@@ -0,0 +1,21 @@
1
+ import { Box, Code, Text } from "@mantine/core";
2
+
3
+ interface SchemaViewerProps {
4
+ schema: unknown;
5
+ label: string;
6
+ }
7
+
8
+ export const SchemaViewer = ({ schema, label }: SchemaViewerProps) => {
9
+ if (!schema) return null;
10
+
11
+ return (
12
+ <Box>
13
+ <Text size="xs" fw={500} c="dimmed" mb={4}>
14
+ {label}
15
+ </Text>
16
+ <Code block style={{ fontSize: 11, maxHeight: 200, overflow: "auto" }}>
17
+ {JSON.stringify(schema, null, 2)}
18
+ </Code>
19
+ </Box>
20
+ );
21
+ };
@@ -0,0 +1,140 @@
1
+ import { useInject } from "@alepha/react";
2
+ import {
3
+ ActionIcon,
4
+ Box,
5
+ Button,
6
+ Code,
7
+ CopyButton,
8
+ Group,
9
+ JsonInput,
10
+ Loader,
11
+ Paper,
12
+ ScrollArea,
13
+ Stack,
14
+ Text,
15
+ } from "@mantine/core";
16
+ import { IconCheck, IconCopy, IconPlayerPlay } from "@tabler/icons-react";
17
+ import { HttpClient } from "alepha/server";
18
+ import { useCallback, useState } from "react";
19
+ import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
20
+
21
+ interface TryItPanelProps {
22
+ action: DevActionMetadata;
23
+ }
24
+
25
+ export const TryItPanel = ({ action }: TryItPanelProps) => {
26
+ const http = useInject(HttpClient);
27
+ const [body, setBody] = useState("{}");
28
+ const [response, setResponse] = useState<string | null>(null);
29
+ const [loading, setLoading] = useState(false);
30
+ const [timing, setTiming] = useState<number | null>(null);
31
+ const [error, setError] = useState<string | null>(null);
32
+
33
+ const execute = useCallback(async () => {
34
+ setLoading(true);
35
+ setError(null);
36
+ setResponse(null);
37
+
38
+ const start = performance.now();
39
+
40
+ try {
41
+ let parsedBody: unknown;
42
+ if (action.body && body) {
43
+ try {
44
+ parsedBody = JSON.parse(body);
45
+ } catch {
46
+ setError("Invalid JSON body");
47
+ setLoading(false);
48
+ return;
49
+ }
50
+ }
51
+
52
+ const res = await http.fetch(action.fullPath, {
53
+ method: action.method.toUpperCase() as
54
+ | "GET"
55
+ | "POST"
56
+ | "PUT"
57
+ | "DELETE"
58
+ | "PATCH",
59
+ body: parsedBody ? JSON.stringify(parsedBody) : undefined,
60
+ headers: parsedBody
61
+ ? { "Content-Type": "application/json" }
62
+ : undefined,
63
+ });
64
+
65
+ setTiming(Math.round(performance.now() - start));
66
+ setResponse(JSON.stringify(res.data, null, 2));
67
+ } catch (err) {
68
+ setTiming(Math.round(performance.now() - start));
69
+ setError(err instanceof Error ? err.message : "Request failed");
70
+ } finally {
71
+ setLoading(false);
72
+ }
73
+ }, [http, action, body]);
74
+
75
+ return (
76
+ <Stack gap="md">
77
+ {action.body && (
78
+ <JsonInput
79
+ label="Request Body"
80
+ placeholder='{"key": "value"}'
81
+ value={body}
82
+ onChange={setBody}
83
+ minRows={4}
84
+ maxRows={10}
85
+ autosize
86
+ formatOnBlur
87
+ validationError="Invalid JSON"
88
+ />
89
+ )}
90
+
91
+ <Group>
92
+ <Button
93
+ leftSection={
94
+ loading ? <Loader size={14} /> : <IconPlayerPlay size={14} />
95
+ }
96
+ onClick={execute}
97
+ disabled={loading}
98
+ size="sm"
99
+ >
100
+ Execute
101
+ </Button>
102
+ {timing !== null && (
103
+ <Text size="xs" c="dimmed">
104
+ {timing}ms
105
+ </Text>
106
+ )}
107
+ </Group>
108
+
109
+ {error && (
110
+ <Paper p="sm" bg="var(--mantine-color-red-light)">
111
+ <Text size="sm" c="red">
112
+ {error}
113
+ </Text>
114
+ </Paper>
115
+ )}
116
+
117
+ {response && (
118
+ <Box>
119
+ <Group justify="space-between" mb={4}>
120
+ <Text size="xs" fw={500} c="dimmed">
121
+ Response
122
+ </Text>
123
+ <CopyButton value={response}>
124
+ {({ copied, copy }) => (
125
+ <ActionIcon size="xs" variant="subtle" onClick={copy}>
126
+ {copied ? <IconCheck size={12} /> : <IconCopy size={12} />}
127
+ </ActionIcon>
128
+ )}
129
+ </CopyButton>
130
+ </Group>
131
+ <ScrollArea h={200}>
132
+ <Code block style={{ fontSize: 11 }}>
133
+ {response}
134
+ </Code>
135
+ </ScrollArea>
136
+ </Box>
137
+ )}
138
+ </Stack>
139
+ );
140
+ };
@@ -0,0 +1,7 @@
1
+ export const METHOD_COLORS: Record<string, string> = {
2
+ GET: "teal",
3
+ POST: "blue",
4
+ PUT: "orange",
5
+ PATCH: "yellow",
6
+ DELETE: "red",
7
+ };
@@ -0,0 +1,18 @@
1
+ import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
2
+
3
+ export const generateCurl = (
4
+ action: DevActionMetadata,
5
+ body?: string,
6
+ ): string => {
7
+ const parts = [`curl -X ${action.method.toUpperCase()}`];
8
+ parts.push(` '${window.location.origin}${action.fullPath}'`);
9
+
10
+ if (action.body && body) {
11
+ parts.push(
12
+ ` -H 'Content-Type: ${action.bodyContentType || "application/json"}'`,
13
+ );
14
+ parts.push(` -d '${body}'`);
15
+ }
16
+
17
+ return parts.join(" \\\n");
18
+ };
@@ -0,0 +1,8 @@
1
+ export { ActionGroup } from "./ActionGroup.tsx";
2
+ export { ActionItem } from "./ActionItem.tsx";
3
+ export { METHOD_COLORS } from "./constants.ts";
4
+ export { DevActionsExplorer } from "./DevActionsExplorer.tsx";
5
+ export { generateCurl } from "./helpers.ts";
6
+ export { MethodBadge } from "./MethodBadge.tsx";
7
+ export { SchemaViewer } from "./SchemaViewer.tsx";
8
+ export { TryItPanel } from "./TryItPanel.tsx";
@@ -0,0 +1,55 @@
1
+ import { Badge, Tooltip } from "@mantine/core";
2
+ import type { DevEntityColumn } from "./types.ts";
3
+
4
+ interface ColumnBadgeProps {
5
+ column: DevEntityColumn;
6
+ size?: "xs" | "sm";
7
+ }
8
+
9
+ export const ColumnBadge = ({ column, size = "xs" }: ColumnBadgeProps) => {
10
+ const badges: Array<{ label: string; tooltip: string }> = [];
11
+
12
+ if (column.primaryKey) {
13
+ badges.push({ label: "PK", tooltip: "Primary Key" });
14
+ }
15
+ if (column.identity) {
16
+ badges.push({ label: "ID", tooltip: "Auto-increment Identity" });
17
+ }
18
+ if (column.ref) {
19
+ badges.push({
20
+ label: "FK",
21
+ tooltip: `Foreign Key → ${column.ref.entity}.${column.ref.column}`,
22
+ });
23
+ }
24
+ if (column.createdAt) {
25
+ badges.push({ label: "C", tooltip: "Created At (auto-set on insert)" });
26
+ }
27
+ if (column.updatedAt) {
28
+ badges.push({ label: "U", tooltip: "Updated At (auto-set on update)" });
29
+ }
30
+ if (column.deletedAt) {
31
+ badges.push({ label: "D", tooltip: "Deleted At (soft delete)" });
32
+ }
33
+ if (column.version) {
34
+ badges.push({ label: "V", tooltip: "Version (optimistic locking)" });
35
+ }
36
+
37
+ return (
38
+ <>
39
+ {badges.map((b) => (
40
+ <Tooltip key={b.label} label={b.tooltip}>
41
+ <Badge size={size} variant="light" color="blue">
42
+ {b.label}
43
+ </Badge>
44
+ </Tooltip>
45
+ ))}
46
+ {column.nullable && (
47
+ <Tooltip label="Nullable">
48
+ <Badge size={size} variant="outline" color="gray">
49
+ ?
50
+ </Badge>
51
+ </Tooltip>
52
+ )}
53
+ </>
54
+ );
55
+ };