@geekmidas/studio 9.0.2 → 10.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 (50) hide show
  1. package/package.json +8 -5
  2. package/CHANGELOG.md +0 -82
  3. package/src/Studio.ts +0 -367
  4. package/src/__tests__/Studio.spec.ts +0 -447
  5. package/src/data/DataBrowser.ts +0 -170
  6. package/src/data/__tests__/DataBrowser.integration.spec.ts +0 -418
  7. package/src/data/__tests__/filtering.integration.spec.ts +0 -741
  8. package/src/data/__tests__/introspection.integration.spec.ts +0 -352
  9. package/src/data/__tests__/pagination.spec.ts +0 -123
  10. package/src/data/filtering.ts +0 -191
  11. package/src/data/index.ts +0 -1
  12. package/src/data/introspection.ts +0 -220
  13. package/src/data/pagination.ts +0 -33
  14. package/src/index.ts +0 -29
  15. package/src/server/__tests__/hono.integration.spec.ts +0 -619
  16. package/src/server/hono.ts +0 -427
  17. package/src/types.ts +0 -278
  18. package/src/ui-assets.ts +0 -37
  19. package/tsconfig.json +0 -9
  20. package/tsdown.config.ts +0 -13
  21. package/ui/CHANGELOG.md +0 -26
  22. package/ui/index.html +0 -12
  23. package/ui/node_modules/.bin/tsc +0 -21
  24. package/ui/node_modules/.bin/tsserver +0 -21
  25. package/ui/node_modules/.bin/vite +0 -21
  26. package/ui/package.json +0 -30
  27. package/ui/src/App.tsx +0 -100
  28. package/ui/src/api.ts +0 -213
  29. package/ui/src/components/FilterPanel.tsx +0 -213
  30. package/ui/src/components/NavRail.tsx +0 -183
  31. package/ui/src/components/RowDetail.tsx +0 -119
  32. package/ui/src/components/StudioHeader.tsx +0 -109
  33. package/ui/src/components/TableList.tsx +0 -58
  34. package/ui/src/components/TableView.tsx +0 -564
  35. package/ui/src/main.tsx +0 -10
  36. package/ui/src/pages/DashboardPage.tsx +0 -500
  37. package/ui/src/pages/DatabasePage.tsx +0 -226
  38. package/ui/src/pages/EndpointDetailsPage.tsx +0 -288
  39. package/ui/src/pages/ExceptionsPage.tsx +0 -268
  40. package/ui/src/pages/LogsPage.tsx +0 -228
  41. package/ui/src/pages/MonitoringPage.tsx +0 -46
  42. package/ui/src/pages/PerformancePage.tsx +0 -307
  43. package/ui/src/pages/RequestsPage.tsx +0 -379
  44. package/ui/src/providers/StudioProvider.tsx +0 -194
  45. package/ui/src/styles.css +0 -105
  46. package/ui/src/types.ts +0 -174
  47. package/ui/src/vite-env.d.ts +0 -1
  48. package/ui/tsconfig.json +0 -21
  49. package/ui/tsconfig.tsbuildinfo +0 -1
  50. package/ui/vite.config.ts +0 -12
@@ -1,418 +0,0 @@
1
- import {
2
- CamelCasePlugin,
3
- type Generated,
4
- Kysely,
5
- PostgresDialect,
6
- sql,
7
- } from 'kysely';
8
- import pg from 'pg';
9
- import {
10
- afterAll,
11
- afterEach,
12
- beforeAll,
13
- beforeEach,
14
- describe,
15
- expect,
16
- it,
17
- } from 'vitest';
18
- import { TEST_DATABASE_CONFIG } from '../../../../testkit/test/globalSetup';
19
- import { Direction, FilterOperator } from '../../types';
20
- import { DataBrowser } from '../DataBrowser';
21
-
22
- interface TestDatabase {
23
- studioBrowserProducts: {
24
- id: Generated<number>;
25
- name: string;
26
- price: number;
27
- category: string;
28
- inStock: boolean;
29
- createdAt: Generated<Date>;
30
- };
31
- studioBrowserOrders: {
32
- id: Generated<number>;
33
- productId: number;
34
- quantity: number;
35
- total: number;
36
- createdAt: Generated<Date>;
37
- };
38
- studioBrowserExcluded: {
39
- id: Generated<number>;
40
- value: string;
41
- };
42
- }
43
-
44
- describe('DataBrowser Integration Tests', () => {
45
- let db: Kysely<TestDatabase>;
46
- let browser: DataBrowser<TestDatabase>;
47
-
48
- beforeAll(async () => {
49
- db = new Kysely<TestDatabase>({
50
- dialect: new PostgresDialect({
51
- pool: new pg.Pool({
52
- ...TEST_DATABASE_CONFIG,
53
- database: 'postgres',
54
- }),
55
- }),
56
- plugins: [new CamelCasePlugin()],
57
- });
58
-
59
- // Create products table
60
- await db.schema
61
- .createTable('studio_browser_products')
62
- .ifNotExists()
63
- .addColumn('id', 'serial', (col) => col.primaryKey())
64
- .addColumn('name', 'varchar(255)', (col) => col.notNull())
65
- .addColumn('price', 'numeric(10, 2)', (col) => col.notNull())
66
- .addColumn('category', 'varchar(100)', (col) => col.notNull())
67
- .addColumn('in_stock', 'boolean', (col) => col.notNull().defaultTo(true))
68
- .addColumn('created_at', 'timestamptz', (col) =>
69
- col.defaultTo(sql`now()`).notNull(),
70
- )
71
- .execute();
72
-
73
- // Create orders table with foreign key
74
- await db.schema
75
- .createTable('studio_browser_orders')
76
- .ifNotExists()
77
- .addColumn('id', 'serial', (col) => col.primaryKey())
78
- .addColumn('product_id', 'integer', (col) =>
79
- col
80
- .notNull()
81
- .references('studio_browser_products.id')
82
- .onDelete('cascade'),
83
- )
84
- .addColumn('quantity', 'integer', (col) => col.notNull())
85
- .addColumn('total', 'numeric(10, 2)', (col) => col.notNull())
86
- .addColumn('created_at', 'timestamptz', (col) =>
87
- col.defaultTo(sql`now()`).notNull(),
88
- )
89
- .execute();
90
-
91
- // Create excluded table
92
- await db.schema
93
- .createTable('studio_browser_excluded')
94
- .ifNotExists()
95
- .addColumn('id', 'serial', (col) => col.primaryKey())
96
- .addColumn('value', 'varchar(100)', (col) => col.notNull())
97
- .execute();
98
- });
99
-
100
- beforeEach(async () => {
101
- // Create fresh browser instance for each test
102
- browser = new DataBrowser({
103
- db,
104
- cursor: { field: 'id', direction: Direction.Asc },
105
- tableCursors: {},
106
- excludeTables: ['studio_browser_excluded'],
107
- defaultPageSize: 10,
108
- showBinaryColumns: false,
109
- });
110
-
111
- // Insert test products
112
- await db
113
- .insertInto('studioBrowserProducts')
114
- .values([
115
- {
116
- name: 'Product A',
117
- price: 100,
118
- category: 'electronics',
119
- inStock: true,
120
- },
121
- {
122
- name: 'Product B',
123
- price: 200,
124
- category: 'electronics',
125
- inStock: true,
126
- },
127
- { name: 'Product C', price: 50, category: 'clothing', inStock: false },
128
- { name: 'Product D', price: 150, category: 'clothing', inStock: true },
129
- {
130
- name: 'Product E',
131
- price: 300,
132
- category: 'electronics',
133
- inStock: true,
134
- },
135
- ])
136
- .execute();
137
- });
138
-
139
- afterEach(async () => {
140
- await db.deleteFrom('studioBrowserOrders').execute();
141
- await db.deleteFrom('studioBrowserProducts').execute();
142
- await db.deleteFrom('studioBrowserExcluded').execute();
143
- });
144
-
145
- afterAll(async () => {
146
- await db.schema.dropTable('studio_browser_orders').ifExists().execute();
147
- await db.schema.dropTable('studio_browser_products').ifExists().execute();
148
- await db.schema.dropTable('studio_browser_excluded').ifExists().execute();
149
- await db.destroy();
150
- });
151
-
152
- describe('getSchema', () => {
153
- it('should return schema with tables', async () => {
154
- const schema = await browser.getSchema();
155
-
156
- const tableNames = schema.tables.map((t) => t.name);
157
- expect(tableNames).toContain('studio_browser_products');
158
- expect(tableNames).toContain('studio_browser_orders');
159
- expect(tableNames).not.toContain('studio_browser_excluded');
160
- });
161
-
162
- it('should cache schema results', async () => {
163
- const schema1 = await browser.getSchema();
164
- const schema2 = await browser.getSchema();
165
-
166
- // Same reference means cached
167
- expect(schema1).toBe(schema2);
168
- });
169
-
170
- it('should refresh cache when forceRefresh is true', async () => {
171
- const schema1 = await browser.getSchema();
172
- const schema2 = await browser.getSchema(true);
173
-
174
- // Different reference means new fetch
175
- expect(schema1).not.toBe(schema2);
176
- });
177
- });
178
-
179
- describe('getTableInfo', () => {
180
- it('should return table info for existing table', async () => {
181
- const tableInfo = await browser.getTableInfo('studio_browser_products');
182
-
183
- expect(tableInfo).not.toBeNull();
184
- expect(tableInfo?.name).toBe('studio_browser_products');
185
- expect(tableInfo?.columns.length).toBeGreaterThan(0);
186
- });
187
-
188
- it('should return null for non-existent table', async () => {
189
- const tableInfo = await browser.getTableInfo('non_existent_table');
190
-
191
- expect(tableInfo).toBeNull();
192
- });
193
- });
194
-
195
- describe('query', () => {
196
- it('should return paginated results', async () => {
197
- const result = await browser.query({
198
- table: 'studio_browser_products',
199
- pageSize: 3,
200
- });
201
-
202
- expect(result.rows).toHaveLength(3);
203
- expect(result.hasMore).toBe(true);
204
- expect(result.nextCursor).toBeDefined();
205
- });
206
-
207
- it('should throw error for non-existent table', async () => {
208
- await expect(
209
- browser.query({ table: 'non_existent_table' }),
210
- ).rejects.toThrow("Table 'non_existent_table' not found");
211
- });
212
-
213
- it('should respect pageSize limit', async () => {
214
- const result = await browser.query({
215
- table: 'studio_browser_products',
216
- pageSize: 2,
217
- });
218
-
219
- expect(result.rows).toHaveLength(2);
220
- });
221
-
222
- it('should cap pageSize at 100', async () => {
223
- // Insert more products
224
- const products = [];
225
- for (let i = 0; i < 110; i++) {
226
- products.push({
227
- name: `Product ${i}`,
228
- price: i * 10,
229
- category: 'test',
230
- inStock: true,
231
- });
232
- }
233
- await db.insertInto('studioBrowserProducts').values(products).execute();
234
-
235
- const result = await browser.query({
236
- table: 'studio_browser_products',
237
- pageSize: 150,
238
- });
239
-
240
- expect(result.rows.length).toBeLessThanOrEqual(100);
241
- });
242
-
243
- it('should paginate using cursor', async () => {
244
- const firstPage = await browser.query({
245
- table: 'studio_browser_products',
246
- pageSize: 2,
247
- });
248
-
249
- expect(firstPage.rows).toHaveLength(2);
250
- expect(firstPage.nextCursor).toBeDefined();
251
-
252
- const secondPage = await browser.query({
253
- table: 'studio_browser_products',
254
- pageSize: 2,
255
- cursor: firstPage.nextCursor,
256
- });
257
-
258
- expect(secondPage.rows).toHaveLength(2);
259
-
260
- // Items should be different
261
- const firstIds = firstPage.rows.map((r: any) => r.id);
262
- const secondIds = secondPage.rows.map((r: any) => r.id);
263
- expect(firstIds.every((id: number) => !secondIds.includes(id))).toBe(
264
- true,
265
- );
266
- });
267
-
268
- it('should apply filters', async () => {
269
- const result = await browser.query({
270
- table: 'studio_browser_products',
271
- filters: [
272
- {
273
- column: 'category',
274
- operator: FilterOperator.Eq,
275
- value: 'electronics',
276
- },
277
- ],
278
- });
279
-
280
- expect(result.rows).toHaveLength(3);
281
- result.rows.forEach((row: any) => {
282
- expect(row.category).toBe('electronics');
283
- });
284
- });
285
-
286
- it('should apply multiple filters', async () => {
287
- const result = await browser.query({
288
- table: 'studio_browser_products',
289
- filters: [
290
- {
291
- column: 'category',
292
- operator: FilterOperator.Eq,
293
- value: 'electronics',
294
- },
295
- { column: 'price', operator: FilterOperator.Gt, value: 150 },
296
- ],
297
- });
298
-
299
- expect(result.rows).toHaveLength(2);
300
- result.rows.forEach((row: any) => {
301
- expect(row.category).toBe('electronics');
302
- expect(Number(row.price)).toBeGreaterThan(150);
303
- });
304
- });
305
-
306
- it('should apply sorting', async () => {
307
- const result = await browser.query({
308
- table: 'studio_browser_products',
309
- sort: [{ column: 'price', direction: Direction.Desc }],
310
- });
311
-
312
- const prices = result.rows.map((r: any) => Number(r.price));
313
- for (let i = 1; i < prices.length; i++) {
314
- expect(prices[i]).toBeLessThanOrEqual(prices[i - 1]);
315
- }
316
- });
317
-
318
- it('should handle empty results', async () => {
319
- const result = await browser.query({
320
- table: 'studio_browser_products',
321
- filters: [
322
- {
323
- column: 'category',
324
- operator: FilterOperator.Eq,
325
- value: 'nonexistent',
326
- },
327
- ],
328
- });
329
-
330
- expect(result.rows).toHaveLength(0);
331
- expect(result.hasMore).toBe(false);
332
- expect(result.nextCursor).toBeNull();
333
- });
334
-
335
- it('should set prevCursor when cursor is provided', async () => {
336
- const firstPage = await browser.query({
337
- table: 'studio_browser_products',
338
- pageSize: 2,
339
- });
340
-
341
- const secondPage = await browser.query({
342
- table: 'studio_browser_products',
343
- pageSize: 2,
344
- cursor: firstPage.nextCursor,
345
- });
346
-
347
- expect(secondPage.prevCursor).toBeDefined();
348
- });
349
-
350
- it('should not set prevCursor on first page', async () => {
351
- const result = await browser.query({
352
- table: 'studio_browser_products',
353
- pageSize: 2,
354
- });
355
-
356
- expect(result.prevCursor).toBeNull();
357
- });
358
- });
359
-
360
- describe('getCursorConfig', () => {
361
- it('should return default cursor config', () => {
362
- const config = browser.getCursorConfig('studio_browser_products');
363
-
364
- expect(config.field).toBe('id');
365
- expect(config.direction).toBe(Direction.Asc);
366
- });
367
-
368
- it('should return table-specific cursor config', () => {
369
- const customBrowser = new DataBrowser({
370
- db,
371
- cursor: { field: 'id', direction: Direction.Asc },
372
- tableCursors: {
373
- studio_browser_products: {
374
- field: 'created_at',
375
- direction: Direction.Desc,
376
- },
377
- },
378
- excludeTables: [],
379
- defaultPageSize: 10,
380
- showBinaryColumns: false,
381
- });
382
-
383
- const config = customBrowser.getCursorConfig('studio_browser_products');
384
-
385
- expect(config.field).toBe('created_at');
386
- expect(config.direction).toBe(Direction.Desc);
387
- });
388
- });
389
-
390
- describe('database getter', () => {
391
- it('should return the underlying database instance', () => {
392
- expect(browser.database).toBe(db);
393
- });
394
- });
395
-
396
- describe('cursor direction in query', () => {
397
- it('should use descending cursor direction correctly', async () => {
398
- const descBrowser = new DataBrowser({
399
- db,
400
- cursor: { field: 'id', direction: Direction.Desc },
401
- tableCursors: {},
402
- excludeTables: [],
403
- defaultPageSize: 10,
404
- showBinaryColumns: false,
405
- });
406
-
407
- const result = await descBrowser.query({
408
- table: 'studio_browser_products',
409
- pageSize: 3,
410
- });
411
-
412
- const ids = result.rows.map((r: any) => r.id);
413
- for (let i = 1; i < ids.length; i++) {
414
- expect(ids[i]).toBeLessThan(ids[i - 1]);
415
- }
416
- });
417
- });
418
- });