@geekmidas/studio 0.0.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 (64) hide show
  1. package/dist/DataBrowser-DQ3-ZxdV.mjs +427 -0
  2. package/dist/DataBrowser-DQ3-ZxdV.mjs.map +1 -0
  3. package/dist/DataBrowser-SOcqmZb2.d.mts +267 -0
  4. package/dist/DataBrowser-c-Gs6PZB.cjs +432 -0
  5. package/dist/DataBrowser-c-Gs6PZB.cjs.map +1 -0
  6. package/dist/DataBrowser-hGwiTffZ.d.cts +267 -0
  7. package/dist/chunk-CUT6urMc.cjs +30 -0
  8. package/dist/data/index.cjs +4 -0
  9. package/dist/data/index.d.cts +2 -0
  10. package/dist/data/index.d.mts +2 -0
  11. package/dist/data/index.mjs +4 -0
  12. package/dist/index.cjs +239 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +132 -0
  15. package/dist/index.d.mts +132 -0
  16. package/dist/index.mjs +230 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/server/hono.cjs +192 -0
  19. package/dist/server/hono.cjs.map +1 -0
  20. package/dist/server/hono.d.cts +19 -0
  21. package/dist/server/hono.d.mts +19 -0
  22. package/dist/server/hono.mjs +191 -0
  23. package/dist/server/hono.mjs.map +1 -0
  24. package/dist/types-BZv87Ikv.mjs +31 -0
  25. package/dist/types-BZv87Ikv.mjs.map +1 -0
  26. package/dist/types-CMttUZYk.cjs +43 -0
  27. package/dist/types-CMttUZYk.cjs.map +1 -0
  28. package/package.json +54 -0
  29. package/src/Studio.ts +318 -0
  30. package/src/data/DataBrowser.ts +166 -0
  31. package/src/data/__tests__/DataBrowser.integration.spec.ts +418 -0
  32. package/src/data/__tests__/filtering.integration.spec.ts +741 -0
  33. package/src/data/__tests__/introspection.integration.spec.ts +352 -0
  34. package/src/data/filtering.ts +191 -0
  35. package/src/data/index.ts +1 -0
  36. package/src/data/introspection.ts +220 -0
  37. package/src/data/pagination.ts +33 -0
  38. package/src/index.ts +31 -0
  39. package/src/server/__tests__/hono.integration.spec.ts +361 -0
  40. package/src/server/hono.ts +225 -0
  41. package/src/types.ts +278 -0
  42. package/src/ui-assets.ts +40 -0
  43. package/tsdown.config.ts +13 -0
  44. package/ui/index.html +12 -0
  45. package/ui/node_modules/.bin/browserslist +21 -0
  46. package/ui/node_modules/.bin/jiti +21 -0
  47. package/ui/node_modules/.bin/terser +21 -0
  48. package/ui/node_modules/.bin/tsc +21 -0
  49. package/ui/node_modules/.bin/tsserver +21 -0
  50. package/ui/node_modules/.bin/tsx +21 -0
  51. package/ui/node_modules/.bin/vite +21 -0
  52. package/ui/package.json +24 -0
  53. package/ui/src/App.tsx +141 -0
  54. package/ui/src/api.ts +71 -0
  55. package/ui/src/components/RowDetail.tsx +113 -0
  56. package/ui/src/components/TableList.tsx +51 -0
  57. package/ui/src/components/TableView.tsx +219 -0
  58. package/ui/src/main.tsx +10 -0
  59. package/ui/src/styles.css +36 -0
  60. package/ui/src/types.ts +50 -0
  61. package/ui/src/vite-env.d.ts +1 -0
  62. package/ui/tsconfig.json +21 -0
  63. package/ui/tsconfig.tsbuildinfo +1 -0
  64. package/ui/vite.config.ts +12 -0
@@ -0,0 +1,219 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import * as api from '../api';
3
+ import type { QueryResult, SortConfig, TableInfo } from '../types';
4
+
5
+ interface TableViewProps {
6
+ tableName: string;
7
+ tableInfo: TableInfo | null;
8
+ onRowSelect: (row: Record<string, unknown>) => void;
9
+ }
10
+
11
+ export function TableView({
12
+ tableName,
13
+ tableInfo,
14
+ onRowSelect,
15
+ }: TableViewProps) {
16
+ const [data, setData] = useState<QueryResult | null>(null);
17
+ const [loading, setLoading] = useState(true);
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [sort, setSort] = useState<SortConfig[]>([]);
20
+ const [pageSize] = useState(50);
21
+
22
+ const loadData = useCallback(
23
+ async (cursor?: string) => {
24
+ setLoading(true);
25
+ setError(null);
26
+ try {
27
+ const result = await api.queryTable(tableName, {
28
+ pageSize,
29
+ cursor,
30
+ sort: sort.length > 0 ? sort : undefined,
31
+ });
32
+ setData(result);
33
+ } catch (err) {
34
+ setError(err instanceof Error ? err.message : 'Failed to load data');
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ },
39
+ [tableName, pageSize, sort],
40
+ );
41
+
42
+ useEffect(() => {
43
+ loadData();
44
+ }, [loadData]);
45
+
46
+ const handleSort = useCallback((column: string) => {
47
+ setSort((prev) => {
48
+ const existing = prev.find((s) => s.column === column);
49
+ if (!existing) {
50
+ return [{ column, direction: 'asc' }];
51
+ }
52
+ if (existing.direction === 'asc') {
53
+ return [{ column, direction: 'desc' }];
54
+ }
55
+ return [];
56
+ });
57
+ }, []);
58
+
59
+ const handleNextPage = useCallback(() => {
60
+ if (data?.nextCursor) {
61
+ loadData(data.nextCursor);
62
+ }
63
+ }, [data, loadData]);
64
+
65
+ const formatValue = (value: unknown): string => {
66
+ if (value === null) return 'NULL';
67
+ if (value === undefined) return '';
68
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
69
+ if (typeof value === 'object') return JSON.stringify(value);
70
+ return String(value);
71
+ };
72
+
73
+ const getColumnWidth = (columnName: string): string => {
74
+ // Give more space to certain columns
75
+ if (columnName.includes('id')) return 'min-w-24';
76
+ if (columnName.includes('name') || columnName.includes('title'))
77
+ return 'min-w-48';
78
+ if (columnName.includes('description') || columnName.includes('content'))
79
+ return 'min-w-64';
80
+ if (columnName.includes('created') || columnName.includes('updated'))
81
+ return 'min-w-40';
82
+ return 'min-w-32';
83
+ };
84
+
85
+ if (!tableInfo) {
86
+ return (
87
+ <div className="flex-1 flex items-center justify-center text-slate-500">
88
+ Loading table info...
89
+ </div>
90
+ );
91
+ }
92
+
93
+ if (error) {
94
+ return (
95
+ <div className="flex-1 flex items-center justify-center text-red-400">
96
+ {error}
97
+ </div>
98
+ );
99
+ }
100
+
101
+ const columns = tableInfo.columns;
102
+
103
+ return (
104
+ <div className="flex-1 flex flex-col overflow-hidden">
105
+ {/* Column Info Bar */}
106
+ <div className="bg-bg-secondary border-b border-border px-4 py-2 flex items-center gap-4 text-sm">
107
+ <span className="text-slate-400">{columns.length} columns</span>
108
+ {tableInfo.estimatedRowCount !== undefined && (
109
+ <span className="text-slate-400">
110
+ ~{tableInfo.estimatedRowCount.toLocaleString()} rows
111
+ </span>
112
+ )}
113
+ {sort.length > 0 && (
114
+ <span className="text-purple-400">
115
+ Sorted by: {sort[0].column} ({sort[0].direction})
116
+ </span>
117
+ )}
118
+ </div>
119
+
120
+ {/* Table */}
121
+ <div className="flex-1 overflow-auto">
122
+ <table className="w-full border-collapse">
123
+ <thead className="sticky top-0 bg-bg-secondary z-10">
124
+ <tr>
125
+ {columns.map((col) => (
126
+ <th
127
+ key={col.name}
128
+ onClick={() => handleSort(col.name)}
129
+ className={`text-left px-3 py-2 text-sm font-medium text-slate-300 border-b border-border cursor-pointer hover:bg-bg-tertiary transition-colors ${getColumnWidth(col.name)}`}
130
+ >
131
+ <div className="flex items-center gap-2">
132
+ <span className="truncate">{col.name}</span>
133
+ {col.isPrimaryKey && (
134
+ <span className="text-xs text-amber-400">PK</span>
135
+ )}
136
+ {col.isForeignKey && (
137
+ <span className="text-xs text-blue-400">FK</span>
138
+ )}
139
+ {sort.find((s) => s.column === col.name) && (
140
+ <span className="text-purple-400">
141
+ {sort[0].direction === 'asc' ? '↑' : '↓'}
142
+ </span>
143
+ )}
144
+ </div>
145
+ <div className="text-xs text-slate-500 font-normal">
146
+ {col.rawType}
147
+ {col.nullable && ' ?'}
148
+ </div>
149
+ </th>
150
+ ))}
151
+ </tr>
152
+ </thead>
153
+ <tbody>
154
+ {loading && !data ? (
155
+ <tr>
156
+ <td
157
+ colSpan={columns.length}
158
+ className="text-center py-8 text-slate-500"
159
+ >
160
+ Loading...
161
+ </td>
162
+ </tr>
163
+ ) : data?.rows.length === 0 ? (
164
+ <tr>
165
+ <td
166
+ colSpan={columns.length}
167
+ className="text-center py-8 text-slate-500"
168
+ >
169
+ No data found
170
+ </td>
171
+ </tr>
172
+ ) : (
173
+ data?.rows.map((row, idx) => (
174
+ <tr
175
+ key={idx}
176
+ onClick={() => onRowSelect(row)}
177
+ className="border-b border-border hover:bg-bg-tertiary cursor-pointer transition-colors"
178
+ >
179
+ {columns.map((col) => (
180
+ <td
181
+ key={col.name}
182
+ className={`px-3 py-2 text-sm truncate max-w-xs ${
183
+ row[col.name] === null
184
+ ? 'text-slate-500 italic'
185
+ : 'text-slate-300'
186
+ }`}
187
+ >
188
+ {formatValue(row[col.name])}
189
+ </td>
190
+ ))}
191
+ </tr>
192
+ ))
193
+ )}
194
+ </tbody>
195
+ </table>
196
+ </div>
197
+
198
+ {/* Pagination */}
199
+ {data && (
200
+ <div className="bg-bg-secondary border-t border-border px-4 py-2 flex items-center justify-between text-sm">
201
+ <span className="text-slate-400">
202
+ Showing {data.rows.length} rows
203
+ </span>
204
+ <div className="flex items-center gap-2">
205
+ {data.hasMore && (
206
+ <button
207
+ onClick={handleNextPage}
208
+ disabled={loading}
209
+ className="px-3 py-1 bg-bg-tertiary hover:bg-slate-600 rounded transition-colors disabled:opacity-50"
210
+ >
211
+ {loading ? 'Loading...' : 'Load More'}
212
+ </button>
213
+ )}
214
+ </div>
215
+ </div>
216
+ )}
217
+ </div>
218
+ );
219
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+ import './styles.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,36 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-bg-primary: #0f172a;
5
+ --color-bg-secondary: #1e293b;
6
+ --color-bg-tertiary: #334155;
7
+ --color-border: #334155;
8
+ }
9
+
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
14
+ "Liberation Mono", monospace;
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+ }
18
+
19
+ /* Custom scrollbar */
20
+ ::-webkit-scrollbar {
21
+ width: 8px;
22
+ height: 8px;
23
+ }
24
+
25
+ ::-webkit-scrollbar-track {
26
+ background: var(--color-bg-secondary);
27
+ }
28
+
29
+ ::-webkit-scrollbar-thumb {
30
+ background: var(--color-bg-tertiary);
31
+ border-radius: 4px;
32
+ }
33
+
34
+ ::-webkit-scrollbar-thumb:hover {
35
+ background: #475569;
36
+ }
@@ -0,0 +1,50 @@
1
+ export interface ColumnInfo {
2
+ name: string;
3
+ type: string;
4
+ rawType: string;
5
+ nullable: boolean;
6
+ isPrimaryKey: boolean;
7
+ isForeignKey: boolean;
8
+ foreignKeyTable?: string;
9
+ foreignKeyColumn?: string;
10
+ defaultValue?: string;
11
+ }
12
+
13
+ export interface TableInfo {
14
+ name: string;
15
+ schema: string;
16
+ columns: ColumnInfo[];
17
+ primaryKey: string[];
18
+ estimatedRowCount?: number;
19
+ }
20
+
21
+ export interface TableSummary {
22
+ name: string;
23
+ schema: string;
24
+ columnCount: number;
25
+ primaryKey: string[];
26
+ estimatedRowCount?: number;
27
+ }
28
+
29
+ export interface SchemaInfo {
30
+ tables: TableInfo[];
31
+ updatedAt: string;
32
+ }
33
+
34
+ export interface QueryResult {
35
+ rows: Record<string, unknown>[];
36
+ hasMore: boolean;
37
+ nextCursor: string | null;
38
+ prevCursor: string | null;
39
+ }
40
+
41
+ export interface FilterConfig {
42
+ column: string;
43
+ operator: string;
44
+ value: string;
45
+ }
46
+
47
+ export interface SortConfig {
48
+ column: string;
49
+ direction: 'asc' | 'desc';
50
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/rowdetail.tsx","./src/components/tablelist.tsx","./src/components/tableview.tsx"],"version":"5.8.2"}
@@ -0,0 +1,12 @@
1
+ import tailwindcss from '@tailwindcss/vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { defineConfig } from 'vite';
4
+
5
+ export default defineConfig({
6
+ plugins: [react(), tailwindcss()],
7
+ base: '/__studio/',
8
+ build: {
9
+ outDir: '../dist/ui',
10
+ emptyOutDir: true,
11
+ },
12
+ });