@geekmidas/studio 0.2.0 → 0.4.0
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.
- package/dist/{DataBrowser-hGwiTffZ.d.cts → DataBrowser-B-jz8KBR.d.mts} +5 -2
- package/dist/DataBrowser-B-jz8KBR.d.mts.map +1 -0
- package/dist/{DataBrowser-SOcqmZb2.d.mts → DataBrowser-BTe9HWJy.d.cts} +5 -2
- package/dist/DataBrowser-BTe9HWJy.d.cts.map +1 -0
- package/dist/{DataBrowser-c-Gs6PZB.cjs → DataBrowser-D8c_pBf4.cjs} +4 -4
- package/dist/DataBrowser-D8c_pBf4.cjs.map +1 -0
- package/dist/{DataBrowser-DQ3-ZxdV.mjs → DataBrowser-kgcI9ApJ.mjs} +4 -4
- package/dist/DataBrowser-kgcI9ApJ.mjs.map +1 -0
- package/dist/Studio-CYzz3wD2.d.cts +152 -0
- package/dist/Studio-CYzz3wD2.d.cts.map +1 -0
- package/dist/Studio-D5yGscb8.d.mts +152 -0
- package/dist/Studio-D5yGscb8.d.mts.map +1 -0
- package/dist/data/index.cjs +1 -1
- package/dist/data/index.d.cts +1 -1
- package/dist/data/index.d.mts +1 -1
- package/dist/data/index.mjs +1 -1
- package/dist/index.cjs +33 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -131
- package/dist/index.d.mts +4 -131
- package/dist/index.mjs +33 -3
- package/dist/index.mjs.map +1 -1
- package/dist/server/hono.cjs +168 -21
- package/dist/server/hono.cjs.map +1 -1
- package/dist/server/hono.d.cts +13 -2
- package/dist/server/hono.d.cts.map +1 -0
- package/dist/server/hono.d.mts +13 -2
- package/dist/server/hono.d.mts.map +1 -0
- package/dist/server/hono.mjs +168 -21
- package/dist/server/hono.mjs.map +1 -1
- package/dist/types-BZv87Ikv.mjs.map +1 -1
- package/dist/types-CMttUZYk.cjs.map +1 -1
- package/package.json +5 -5
- package/src/Studio.ts +341 -292
- package/src/__tests__/Studio.spec.ts +447 -0
- package/src/data/DataBrowser.ts +147 -143
- package/src/data/__tests__/DataBrowser.integration.spec.ts +404 -404
- package/src/data/__tests__/filtering.integration.spec.ts +726 -726
- package/src/data/__tests__/introspection.integration.spec.ts +340 -340
- package/src/data/__tests__/pagination.spec.ts +123 -0
- package/src/data/filtering.ts +154 -154
- package/src/data/introspection.ts +141 -141
- package/src/data/pagination.ts +15 -15
- package/src/index.ts +22 -24
- package/src/server/__tests__/hono.integration.spec.ts +605 -347
- package/src/server/hono.ts +392 -190
- package/src/types.ts +138 -138
- package/src/ui-assets.ts +10 -13
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +9 -9
- package/ui/package.json +28 -22
- package/ui/src/App.tsx +95 -235
- package/ui/src/api.ts +184 -42
- package/ui/src/components/FilterPanel.tsx +198 -198
- package/ui/src/components/NavRail.tsx +183 -0
- package/ui/src/components/RowDetail.tsx +106 -106
- package/ui/src/components/StudioHeader.tsx +109 -0
- package/ui/src/components/TableList.tsx +49 -49
- package/ui/src/components/TableView.tsx +530 -485
- package/ui/src/main.tsx +3 -3
- package/ui/src/pages/DashboardPage.tsx +500 -0
- package/ui/src/pages/DatabasePage.tsx +226 -0
- package/ui/src/pages/EndpointDetailsPage.tsx +288 -0
- package/ui/src/pages/ExceptionsPage.tsx +268 -0
- package/ui/src/pages/LogsPage.tsx +228 -0
- package/ui/src/pages/MonitoringPage.tsx +46 -0
- package/ui/src/pages/PerformancePage.tsx +307 -0
- package/ui/src/pages/RequestsPage.tsx +379 -0
- package/ui/src/providers/StudioProvider.tsx +194 -0
- package/ui/src/styles.css +53 -142
- package/ui/src/types.ts +154 -30
- package/ui/tsconfig.tsbuildinfo +1 -1
- package/ui/vite.config.ts +6 -6
- package/dist/DataBrowser-DQ3-ZxdV.mjs.map +0 -1
- package/dist/DataBrowser-c-Gs6PZB.cjs.map +0 -1
package/src/data/DataBrowser.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Kysely, SelectQueryBuilder } from 'kysely';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
type CursorConfig,
|
|
4
|
+
type DataBrowserOptions,
|
|
5
|
+
Direction,
|
|
6
|
+
type QueryOptions,
|
|
7
|
+
type QueryResult,
|
|
8
|
+
type SchemaInfo,
|
|
9
|
+
type TableInfo,
|
|
10
10
|
} from '../types';
|
|
11
11
|
import { applyFilters, applySorting } from './filtering';
|
|
12
12
|
import { introspectSchema } from './introspection';
|
|
@@ -27,140 +27,144 @@ import { decodeCursor, encodeCursor } from './pagination';
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
export class DataBrowser<DB = unknown> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
30
|
+
private db: Kysely<DB>;
|
|
31
|
+
private options: Required<DataBrowserOptions<DB>>;
|
|
32
|
+
private schemaCache: SchemaInfo | null = null;
|
|
33
|
+
private schemaCacheExpiry = 0;
|
|
34
|
+
private readonly CACHE_TTL_MS = 60 * 1000; // 1 minute
|
|
35
|
+
|
|
36
|
+
constructor(options: Required<DataBrowserOptions<DB>>) {
|
|
37
|
+
this.db = options.db;
|
|
38
|
+
this.options = options;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================
|
|
42
|
+
// Schema Introspection
|
|
43
|
+
// ============================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the database schema information.
|
|
47
|
+
* Results are cached for 1 minute.
|
|
48
|
+
*
|
|
49
|
+
* @param forceRefresh - Force a refresh of the cache
|
|
50
|
+
*/
|
|
51
|
+
async getSchema(forceRefresh = false): Promise<SchemaInfo> {
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
|
|
54
|
+
if (!forceRefresh && this.schemaCache && now < this.schemaCacheExpiry) {
|
|
55
|
+
return this.schemaCache;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const schema = await introspectSchema(this.db, this.options.excludeTables);
|
|
59
|
+
|
|
60
|
+
this.schemaCache = schema;
|
|
61
|
+
this.schemaCacheExpiry = now + this.CACHE_TTL_MS;
|
|
62
|
+
|
|
63
|
+
return schema;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get information about a specific table.
|
|
68
|
+
*/
|
|
69
|
+
async getTableInfo(tableName: string): Promise<TableInfo | null> {
|
|
70
|
+
const schema = await this.getSchema();
|
|
71
|
+
return schema.tables.find((t) => t.name === tableName) ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================
|
|
75
|
+
// Data Querying
|
|
76
|
+
// ============================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Query table data with pagination, filtering, and sorting.
|
|
80
|
+
*/
|
|
81
|
+
async query(options: QueryOptions): Promise<QueryResult> {
|
|
82
|
+
const tableInfo = await this.getTableInfo(options.table);
|
|
83
|
+
|
|
84
|
+
if (!tableInfo) {
|
|
85
|
+
throw new Error(`Table '${options.table}' not found`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const cursorConfig = this.getCursorConfig(options.table);
|
|
89
|
+
const pageSize = Math.min(
|
|
90
|
+
options.pageSize ?? this.options.defaultPageSize,
|
|
91
|
+
100,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Build base query selecting all columns
|
|
95
|
+
let query = this.db
|
|
96
|
+
.selectFrom(options.table as any)
|
|
97
|
+
.selectAll() as SelectQueryBuilder<any, any, any>;
|
|
98
|
+
|
|
99
|
+
// Apply filters if provided
|
|
100
|
+
if (options.filters && options.filters.length > 0) {
|
|
101
|
+
query = applyFilters(query, options.filters, tableInfo);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Apply sorting if provided, otherwise use cursor field
|
|
105
|
+
if (options.sort && options.sort.length > 0) {
|
|
106
|
+
query = applySorting(query, options.sort, tableInfo);
|
|
107
|
+
} else {
|
|
108
|
+
query = query.orderBy(cursorConfig.field as any, cursorConfig.direction);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle cursor-based pagination
|
|
112
|
+
if (options.cursor) {
|
|
113
|
+
const cursorValue = decodeCursor(options.cursor);
|
|
114
|
+
const operator = cursorConfig.direction === Direction.Asc ? '>' : '<';
|
|
115
|
+
query = query.where(cursorConfig.field as any, operator, cursorValue);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Fetch one extra row to determine if there are more results
|
|
119
|
+
const rows = await query.limit(pageSize + 1).execute();
|
|
120
|
+
|
|
121
|
+
const hasMore = rows.length > pageSize;
|
|
122
|
+
const resultRows = hasMore ? rows.slice(0, pageSize) : rows;
|
|
123
|
+
|
|
124
|
+
// Generate cursors
|
|
125
|
+
let nextCursor: string | null = null;
|
|
126
|
+
let prevCursor: string | null = null;
|
|
127
|
+
|
|
128
|
+
if (hasMore && resultRows.length > 0) {
|
|
129
|
+
const lastRow = resultRows[resultRows.length - 1];
|
|
130
|
+
if (lastRow) {
|
|
131
|
+
nextCursor = encodeCursor(lastRow[cursorConfig.field]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// For prev cursor, we need to know if there are previous results
|
|
136
|
+
// This would require a separate query, so we'll only set it if there's an input cursor
|
|
137
|
+
if (options.cursor && resultRows.length > 0) {
|
|
138
|
+
const firstRow = resultRows[0];
|
|
139
|
+
if (firstRow) {
|
|
140
|
+
prevCursor = encodeCursor(firstRow[cursorConfig.field]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
rows: resultRows,
|
|
146
|
+
hasMore,
|
|
147
|
+
nextCursor,
|
|
148
|
+
prevCursor,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================
|
|
153
|
+
// Configuration Access
|
|
154
|
+
// ============================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get the cursor configuration for a table.
|
|
158
|
+
* Returns the table-specific config if defined, otherwise the default.
|
|
159
|
+
*/
|
|
160
|
+
getCursorConfig(tableName: string): CursorConfig {
|
|
161
|
+
return this.options.tableCursors[tableName] ?? this.options.cursor;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the underlying Kysely database instance.
|
|
166
|
+
*/
|
|
167
|
+
get database(): Kysely<DB> {
|
|
168
|
+
return this.db;
|
|
169
|
+
}
|
|
166
170
|
}
|