@alepha/devtools 0.13.5 → 0.13.7

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 (62) hide show
  1. package/dist/{index.d.mts → index.d.ts} +250 -32
  2. package/dist/{index.mjs → index.js} +254 -22
  3. package/dist/index.js.map +1 -0
  4. package/package.json +12 -6
  5. package/src/{DevToolsProvider.ts → api/DevToolsProvider.ts} +29 -1
  6. package/src/{providers → api/providers}/DevToolsMetadataProvider.ts +210 -2
  7. package/src/api/schemas/DevAtomMetadata.ts +26 -0
  8. package/src/api/schemas/DevCommandMetadata.ts +9 -0
  9. package/src/api/schemas/DevEntityMetadata.ts +57 -0
  10. package/src/api/schemas/DevEnvMetadata.ts +22 -0
  11. package/src/{schemas → api/schemas}/DevMetadata.ts +10 -1
  12. package/src/api/schemas/DevRouteMetadata.ts +8 -0
  13. package/src/index.ts +23 -16
  14. package/src/ui/AppRouter.tsx +85 -2
  15. package/src/ui/components/DevAtomsViewer.tsx +636 -0
  16. package/src/ui/components/DevCacheInspector.tsx +423 -0
  17. package/src/ui/components/DevDashboard.tsx +188 -0
  18. package/src/ui/components/DevEnvExplorer.tsx +462 -0
  19. package/src/ui/components/DevLayout.tsx +65 -4
  20. package/src/ui/components/DevLogViewer.tsx +161 -163
  21. package/src/ui/components/DevQueueMonitor.tsx +51 -0
  22. package/src/ui/components/DevTopicsViewer.tsx +690 -0
  23. package/src/ui/components/actions/ActionGroup.tsx +37 -0
  24. package/src/ui/components/actions/ActionItem.tsx +138 -0
  25. package/src/ui/components/actions/DevActionsExplorer.tsx +132 -0
  26. package/src/ui/components/actions/MethodBadge.tsx +18 -0
  27. package/src/ui/components/actions/SchemaViewer.tsx +21 -0
  28. package/src/ui/components/actions/TryItPanel.tsx +140 -0
  29. package/src/ui/components/actions/constants.ts +7 -0
  30. package/src/ui/components/actions/helpers.ts +18 -0
  31. package/src/ui/components/actions/index.ts +8 -0
  32. package/src/ui/components/db/ColumnBadge.tsx +55 -0
  33. package/src/ui/components/db/DevDbStudio.tsx +485 -0
  34. package/src/ui/components/db/constants.ts +11 -0
  35. package/src/ui/components/db/index.ts +4 -0
  36. package/src/ui/components/db/types.ts +7 -0
  37. package/src/ui/components/graph/DevDependencyGraph.tsx +358 -0
  38. package/src/ui/components/graph/GraphControls.tsx +162 -0
  39. package/src/ui/components/graph/NodeDetails.tsx +181 -0
  40. package/src/ui/components/graph/ProviderNode.tsx +97 -0
  41. package/src/ui/components/graph/constants.ts +35 -0
  42. package/src/ui/components/graph/helpers.ts +443 -0
  43. package/src/ui/components/graph/index.ts +7 -0
  44. package/src/ui/components/graph/types.ts +28 -0
  45. package/src/ui/styles.css +0 -6
  46. package/src/ui/resources/wotfardregular/stylesheet.css +0 -12
  47. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.eot +0 -0
  48. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.ttf +0 -0
  49. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.woff2 +0 -0
  50. /package/src/{entities → api/entities}/logs.ts +0 -0
  51. /package/src/{providers → api/providers}/DevToolsDatabaseProvider.ts +0 -0
  52. /package/src/{repositories → api/repositories}/LogRepository.ts +0 -0
  53. /package/src/{schemas → api/schemas}/DevActionMetadata.ts +0 -0
  54. /package/src/{schemas → api/schemas}/DevBucketMetadata.ts +0 -0
  55. /package/src/{schemas → api/schemas}/DevCacheMetadata.ts +0 -0
  56. /package/src/{schemas → api/schemas}/DevModuleMetadata.ts +0 -0
  57. /package/src/{schemas → api/schemas}/DevPageMetadata.ts +0 -0
  58. /package/src/{schemas → api/schemas}/DevProviderMetadata.ts +0 -0
  59. /package/src/{schemas → api/schemas}/DevQueueMetadata.ts +0 -0
  60. /package/src/{schemas → api/schemas}/DevRealmMetadata.ts +0 -0
  61. /package/src/{schemas → api/schemas}/DevSchedulerMetadata.ts +0 -0
  62. /package/src/{schemas → api/schemas}/DevTopicMetadata.ts +0 -0
@@ -0,0 +1,485 @@
1
+ import { useInject } from "@alepha/react";
2
+ import { ui } from "@alepha/ui";
3
+ import {
4
+ Badge,
5
+ Box,
6
+ Flex,
7
+ ScrollArea,
8
+ Stack,
9
+ Table,
10
+ Tabs,
11
+ Text,
12
+ TextInput,
13
+ } from "@mantine/core";
14
+ import { IconDatabase, IconSearch, IconTable } from "@tabler/icons-react";
15
+ import { HttpClient } from "alepha/server";
16
+ import { useEffect, useMemo, useState } from "react";
17
+ import { devMetadataSchema } from "../../../api/schemas/DevMetadata.ts";
18
+ import { ColumnBadge } from "./ColumnBadge.tsx";
19
+ import { getProviderColor } from "./constants.ts";
20
+ import type { DevEntityMetadata } from "./types.ts";
21
+
22
+ const EntitySidebar = ({
23
+ entities,
24
+ selectedEntity,
25
+ onSelectEntity,
26
+ search,
27
+ onSearchChange,
28
+ }: {
29
+ entities: DevEntityMetadata[];
30
+ selectedEntity: DevEntityMetadata | null;
31
+ onSelectEntity: (entity: DevEntityMetadata) => void;
32
+ search: string;
33
+ onSearchChange: (search: string) => void;
34
+ }) => {
35
+ // Group entities by provider
36
+ const grouped = useMemo(() => {
37
+ const groups: Record<string, DevEntityMetadata[]> = {};
38
+ for (const entity of entities) {
39
+ const provider = entity.provider.replace("Provider", "");
40
+ if (!groups[provider]) groups[provider] = [];
41
+ groups[provider].push(entity);
42
+ }
43
+ return groups;
44
+ }, [entities]);
45
+
46
+ const providers = Object.keys(grouped).sort();
47
+
48
+ return (
49
+ <Stack gap={0} h="100%">
50
+ <Box p="sm" style={{ borderBottom: `1px solid ${ui.colors.border}` }}>
51
+ <TextInput
52
+ placeholder="Search tables..."
53
+ leftSection={<IconSearch size={14} />}
54
+ size="xs"
55
+ value={search}
56
+ onChange={(e) => onSearchChange(e.target.value)}
57
+ />
58
+ </Box>
59
+ <ScrollArea style={{ flex: 1 }}>
60
+ {providers.map((provider) => (
61
+ <Box key={provider}>
62
+ <Text
63
+ size="xs"
64
+ fw={600}
65
+ c="dimmed"
66
+ px="sm"
67
+ py="xs"
68
+ style={{
69
+ backgroundColor: ui.colors.background,
70
+ borderBottom: `1px solid ${ui.colors.border}`,
71
+ textTransform: "uppercase",
72
+ letterSpacing: "0.05em",
73
+ }}
74
+ >
75
+ {provider}
76
+ </Text>
77
+ {grouped[provider].map((entity) => {
78
+ const isSelected = selectedEntity?.name === entity.name;
79
+ const providerColor = getProviderColor(entity.provider);
80
+ return (
81
+ <Flex
82
+ key={entity.name}
83
+ align="center"
84
+ gap="xs"
85
+ px="sm"
86
+ py={6}
87
+ onClick={() => onSelectEntity(entity)}
88
+ style={{
89
+ cursor: "pointer",
90
+ backgroundColor: isSelected
91
+ ? `${providerColor}15`
92
+ : undefined,
93
+ borderLeft: isSelected
94
+ ? `2px solid ${providerColor}`
95
+ : "2px solid transparent",
96
+ borderBottom: `1px solid ${ui.colors.border}`,
97
+ }}
98
+ >
99
+ <IconTable size={14} opacity={0.5} />
100
+ <Text size="sm" style={{ flex: 1 }} truncate>
101
+ {entity.name}
102
+ </Text>
103
+ <Text size="xs" c="dimmed">
104
+ {entity.columns.length}
105
+ </Text>
106
+ </Flex>
107
+ );
108
+ })}
109
+ </Box>
110
+ ))}
111
+ </ScrollArea>
112
+ </Stack>
113
+ );
114
+ };
115
+
116
+ const StructureTab = ({ entity }: { entity: DevEntityMetadata }) => {
117
+ return (
118
+ <ScrollArea h="100%" p="md">
119
+ <Stack gap="lg">
120
+ {/* Columns */}
121
+ <Box>
122
+ <Text size="sm" fw={600} mb="xs">
123
+ Columns
124
+ </Text>
125
+ <Table striped highlightOnHover withTableBorder>
126
+ <Table.Thead>
127
+ <Table.Tr>
128
+ <Table.Th>Name</Table.Th>
129
+ <Table.Th>Type</Table.Th>
130
+ <Table.Th>Attributes</Table.Th>
131
+ </Table.Tr>
132
+ </Table.Thead>
133
+ <Table.Tbody>
134
+ {entity.columns.map((col) => (
135
+ <Table.Tr key={col.name}>
136
+ <Table.Td>
137
+ <Text
138
+ size="sm"
139
+ ff="monospace"
140
+ fw={col.primaryKey ? 600 : 400}
141
+ >
142
+ {col.name}
143
+ </Text>
144
+ </Table.Td>
145
+ <Table.Td>
146
+ <Text size="sm" ff="monospace" c="dimmed">
147
+ {col.type}
148
+ </Text>
149
+ </Table.Td>
150
+ <Table.Td>
151
+ <Flex gap={4} wrap="wrap">
152
+ <ColumnBadge column={col} />
153
+ </Flex>
154
+ </Table.Td>
155
+ </Table.Tr>
156
+ ))}
157
+ </Table.Tbody>
158
+ </Table>
159
+ </Box>
160
+
161
+ {/* Indexes */}
162
+ {entity.indexes.length > 0 && (
163
+ <Box>
164
+ <Text size="sm" fw={600} mb="xs">
165
+ Indexes
166
+ </Text>
167
+ <Table striped highlightOnHover withTableBorder>
168
+ <Table.Thead>
169
+ <Table.Tr>
170
+ <Table.Th>Name</Table.Th>
171
+ <Table.Th>Columns</Table.Th>
172
+ <Table.Th>Unique</Table.Th>
173
+ </Table.Tr>
174
+ </Table.Thead>
175
+ <Table.Tbody>
176
+ {entity.indexes.map((idx, i) => (
177
+ <Table.Tr key={idx.name || i}>
178
+ <Table.Td>
179
+ <Text size="sm" ff="monospace">
180
+ {idx.name || "-"}
181
+ </Text>
182
+ </Table.Td>
183
+ <Table.Td>
184
+ <Flex gap={4}>
185
+ {idx.columns.map((c) => (
186
+ <Badge key={c} size="xs" variant="light">
187
+ {c}
188
+ </Badge>
189
+ ))}
190
+ </Flex>
191
+ </Table.Td>
192
+ <Table.Td>
193
+ {idx.unique && (
194
+ <Badge size="xs" color="yellow">
195
+ UNIQUE
196
+ </Badge>
197
+ )}
198
+ </Table.Td>
199
+ </Table.Tr>
200
+ ))}
201
+ </Table.Tbody>
202
+ </Table>
203
+ </Box>
204
+ )}
205
+
206
+ {/* Foreign Keys */}
207
+ {entity.foreignKeys.length > 0 && (
208
+ <Box>
209
+ <Text size="sm" fw={600} mb="xs">
210
+ Foreign Keys
211
+ </Text>
212
+ <Table striped highlightOnHover withTableBorder>
213
+ <Table.Thead>
214
+ <Table.Tr>
215
+ <Table.Th>Name</Table.Th>
216
+ <Table.Th>Column</Table.Th>
217
+ <Table.Th>References</Table.Th>
218
+ </Table.Tr>
219
+ </Table.Thead>
220
+ <Table.Tbody>
221
+ {entity.foreignKeys.map((fk, i) => (
222
+ <Table.Tr key={fk.name || i}>
223
+ <Table.Td>
224
+ <Text size="sm" ff="monospace">
225
+ {fk.name || "-"}
226
+ </Text>
227
+ </Table.Td>
228
+ <Table.Td>
229
+ <Text size="sm" ff="monospace">
230
+ {fk.columns.join(", ")}
231
+ </Text>
232
+ </Table.Td>
233
+ <Table.Td>
234
+ <Text size="sm" ff="monospace" c="pink">
235
+ {fk.foreignEntity}.{fk.foreignColumns.join(", ")}
236
+ </Text>
237
+ </Table.Td>
238
+ </Table.Tr>
239
+ ))}
240
+ </Table.Tbody>
241
+ </Table>
242
+ </Box>
243
+ )}
244
+
245
+ {/* Alepha Column References */}
246
+ {entity.columns.some((c) => c.ref) && (
247
+ <Box>
248
+ <Text size="sm" fw={600} mb="xs">
249
+ Column References
250
+ </Text>
251
+ <Table striped highlightOnHover withTableBorder>
252
+ <Table.Thead>
253
+ <Table.Tr>
254
+ <Table.Th>Column</Table.Th>
255
+ <Table.Th>References</Table.Th>
256
+ </Table.Tr>
257
+ </Table.Thead>
258
+ <Table.Tbody>
259
+ {entity.columns
260
+ .filter((c) => c.ref)
261
+ .map((col) => (
262
+ <Table.Tr key={col.name}>
263
+ <Table.Td>
264
+ <Text size="sm" ff="monospace">
265
+ {col.name}
266
+ </Text>
267
+ </Table.Td>
268
+ <Table.Td>
269
+ <Text size="sm" ff="monospace" c="pink">
270
+ {col.ref?.entity}.{col.ref?.column}
271
+ </Text>
272
+ </Table.Td>
273
+ </Table.Tr>
274
+ ))}
275
+ </Table.Tbody>
276
+ </Table>
277
+ </Box>
278
+ )}
279
+
280
+ {/* Constraints */}
281
+ {entity.constraints.length > 0 && (
282
+ <Box>
283
+ <Text size="sm" fw={600} mb="xs">
284
+ Constraints
285
+ </Text>
286
+ <Table striped highlightOnHover withTableBorder>
287
+ <Table.Thead>
288
+ <Table.Tr>
289
+ <Table.Th>Name</Table.Th>
290
+ <Table.Th>Columns</Table.Th>
291
+ <Table.Th>Type</Table.Th>
292
+ </Table.Tr>
293
+ </Table.Thead>
294
+ <Table.Tbody>
295
+ {entity.constraints.map((c, i) => (
296
+ <Table.Tr key={c.name || i}>
297
+ <Table.Td>
298
+ <Text size="sm" ff="monospace">
299
+ {c.name || "-"}
300
+ </Text>
301
+ </Table.Td>
302
+ <Table.Td>
303
+ <Flex gap={4}>
304
+ {c.columns.map((col) => (
305
+ <Badge key={col} size="xs" variant="outline">
306
+ {col}
307
+ </Badge>
308
+ ))}
309
+ </Flex>
310
+ </Table.Td>
311
+ <Table.Td>
312
+ <Flex gap={4}>
313
+ {c.unique && (
314
+ <Badge size="xs" color="yellow">
315
+ UNIQUE
316
+ </Badge>
317
+ )}
318
+ {c.hasCheck && (
319
+ <Badge size="xs" color="blue">
320
+ CHECK
321
+ </Badge>
322
+ )}
323
+ </Flex>
324
+ </Table.Td>
325
+ </Table.Tr>
326
+ ))}
327
+ </Table.Tbody>
328
+ </Table>
329
+ </Box>
330
+ )}
331
+ </Stack>
332
+ </ScrollArea>
333
+ );
334
+ };
335
+
336
+ const DataTab = () => {
337
+ return (
338
+ <Flex align="center" justify="center" h="100%" c="dimmed">
339
+ <Stack align="center" gap="xs">
340
+ <IconDatabase size={48} opacity={0.3} />
341
+ <Text size="sm">Data browser coming soon</Text>
342
+ </Stack>
343
+ </Flex>
344
+ );
345
+ };
346
+
347
+ const EntityPanel = ({ entity }: { entity: DevEntityMetadata }) => {
348
+ const providerColor = getProviderColor(entity.provider);
349
+
350
+ return (
351
+ <Flex direction="column" h="100%">
352
+ {/* Header */}
353
+ <Box
354
+ px="md"
355
+ py="sm"
356
+ style={{
357
+ borderBottom: `1px solid ${ui.colors.border}`,
358
+ backgroundColor: `${providerColor}08`,
359
+ }}
360
+ >
361
+ <Flex align="center" gap="sm">
362
+ <IconTable size={18} opacity={0.7} />
363
+ <Text size="md" fw={600}>
364
+ {entity.name}
365
+ </Text>
366
+ <Badge size="xs" variant="light" color="gray">
367
+ {entity.columns.length} columns
368
+ </Badge>
369
+ </Flex>
370
+ </Box>
371
+
372
+ {/* Tabs */}
373
+ <Tabs
374
+ defaultValue="structure"
375
+ style={{ flex: 1, display: "flex", flexDirection: "column" }}
376
+ >
377
+ <Tabs.List px="md">
378
+ <Tabs.Tab value="structure">Structure</Tabs.Tab>
379
+ <Tabs.Tab value="data">Data</Tabs.Tab>
380
+ </Tabs.List>
381
+
382
+ <Tabs.Panel value="structure" style={{ flex: 1, overflow: "hidden" }}>
383
+ <StructureTab entity={entity} />
384
+ </Tabs.Panel>
385
+
386
+ <Tabs.Panel value="data" style={{ flex: 1, overflow: "hidden" }}>
387
+ <DataTab />
388
+ </Tabs.Panel>
389
+ </Tabs>
390
+ </Flex>
391
+ );
392
+ };
393
+
394
+ const EmptyState = () => (
395
+ <Flex align="center" justify="center" h="100%" c="dimmed">
396
+ <Stack align="center" gap="xs">
397
+ <IconTable size={48} opacity={0.3} />
398
+ <Text size="sm">Select a table to view its structure</Text>
399
+ </Stack>
400
+ </Flex>
401
+ );
402
+
403
+ const NoEntitiesState = () => (
404
+ <Flex align="center" justify="center" h="100%" c="dimmed">
405
+ <Stack align="center" gap="xs">
406
+ <IconDatabase size={48} opacity={0.3} />
407
+ <Text>No entities found</Text>
408
+ <Text size="sm" c="dimmed">
409
+ Add entities using $entity primitive to see them here
410
+ </Text>
411
+ </Stack>
412
+ </Flex>
413
+ );
414
+
415
+ export const DevDbStudio = () => {
416
+ const http = useInject(HttpClient);
417
+ const [entities, setEntities] = useState<DevEntityMetadata[]>([]);
418
+ const [loading, setLoading] = useState(true);
419
+ const [selectedEntity, setSelectedEntity] =
420
+ useState<DevEntityMetadata | null>(null);
421
+ const [search, setSearch] = useState("");
422
+
423
+ // Fetch entities
424
+ useEffect(() => {
425
+ http
426
+ .fetch("/devtools/api/metadata", {
427
+ schema: { response: devMetadataSchema },
428
+ })
429
+ .then((res) => {
430
+ setEntities(res.data.entities);
431
+ setLoading(false);
432
+ });
433
+ }, []);
434
+
435
+ // Filter entities by search
436
+ const filteredEntities = useMemo(() => {
437
+ if (!search) return entities;
438
+ const searchLower = search.toLowerCase();
439
+ return entities.filter((e) => e.name.toLowerCase().includes(searchLower));
440
+ }, [entities, search]);
441
+
442
+ if (loading) {
443
+ return (
444
+ <Flex align="center" justify="center" h="100%">
445
+ <Text c="dimmed">Loading...</Text>
446
+ </Flex>
447
+ );
448
+ }
449
+
450
+ if (entities.length === 0) {
451
+ return <NoEntitiesState />;
452
+ }
453
+
454
+ return (
455
+ <Flex h="100%" style={{ overflow: "hidden" }}>
456
+ {/* Sidebar */}
457
+ <Box
458
+ w={240}
459
+ style={{
460
+ borderRight: `1px solid ${ui.colors.border}`,
461
+ backgroundColor: ui.colors.surface,
462
+ }}
463
+ >
464
+ <EntitySidebar
465
+ entities={filteredEntities}
466
+ selectedEntity={selectedEntity}
467
+ onSelectEntity={setSelectedEntity}
468
+ search={search}
469
+ onSearchChange={setSearch}
470
+ />
471
+ </Box>
472
+
473
+ {/* Main content */}
474
+ <Box style={{ flex: 1, overflow: "hidden" }}>
475
+ {selectedEntity ? (
476
+ <EntityPanel entity={selectedEntity} />
477
+ ) : (
478
+ <EmptyState />
479
+ )}
480
+ </Box>
481
+ </Flex>
482
+ );
483
+ };
484
+
485
+ export default DevDbStudio;
@@ -0,0 +1,11 @@
1
+ // Provider colors
2
+ export const PROVIDER_COLORS: Record<string, string> = {
3
+ PostgresProvider: "#336791",
4
+ SqliteProvider: "#003B57",
5
+ DatabaseProvider: "#495057",
6
+ default: "#495057",
7
+ };
8
+
9
+ export const getProviderColor = (provider: string): string => {
10
+ return PROVIDER_COLORS[provider] ?? PROVIDER_COLORS.default;
11
+ };
@@ -0,0 +1,4 @@
1
+ export { ColumnBadge } from "./ColumnBadge.tsx";
2
+ export * from "./constants.ts";
3
+ export { DevDbStudio } from "./DevDbStudio.tsx";
4
+ export * from "./types.ts";
@@ -0,0 +1,7 @@
1
+ export type {
2
+ DevEntityColumn,
3
+ DevEntityConstraint,
4
+ DevEntityForeignKey,
5
+ DevEntityIndex,
6
+ DevEntityMetadata,
7
+ } from "../../../api/schemas/DevEntityMetadata.ts";