@geekmidas/studio 9.0.2 → 10.0.0-alpha.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.
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,352 +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 { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
10
- import { TEST_DATABASE_CONFIG } from '../../../../testkit/test/globalSetup';
11
- import { introspectSchema, introspectTable } from '../introspection';
12
-
13
- interface TestDatabase {
14
- studioIntrospectUsers: {
15
- id: Generated<number>;
16
- name: string;
17
- email: string;
18
- isActive: boolean;
19
- metadata: Generated<Record<string, unknown> | null>;
20
- createdAt: Generated<Date>;
21
- updatedAt: Generated<Date>;
22
- };
23
- studioIntrospectPosts: {
24
- id: Generated<string>;
25
- userId: number;
26
- title: string;
27
- content: string | null;
28
- viewCount: number;
29
- publishedAt: Date | null;
30
- createdAt: Generated<Date>;
31
- };
32
- studioIntrospectTags: {
33
- id: Generated<number>;
34
- name: string;
35
- };
36
- studioIntrospectExcluded: {
37
- id: Generated<number>;
38
- value: string;
39
- };
40
- }
41
-
42
- describe('Schema Introspection Integration Tests', () => {
43
- let db: Kysely<TestDatabase>;
44
-
45
- beforeAll(async () => {
46
- db = new Kysely<TestDatabase>({
47
- dialect: new PostgresDialect({
48
- pool: new pg.Pool({
49
- ...TEST_DATABASE_CONFIG,
50
- database: 'postgres',
51
- }),
52
- }),
53
- plugins: [new CamelCasePlugin()],
54
- });
55
-
56
- // Create users table with various column types
57
- await db.schema
58
- .createTable('studio_introspect_users')
59
- .ifNotExists()
60
- .addColumn('id', 'serial', (col) => col.primaryKey())
61
- .addColumn('name', 'varchar(255)', (col) => col.notNull())
62
- .addColumn('email', 'varchar(255)', (col) => col.notNull().unique())
63
- .addColumn('is_active', 'boolean', (col) => col.notNull().defaultTo(true))
64
- .addColumn('metadata', 'jsonb')
65
- .addColumn('created_at', 'timestamptz', (col) =>
66
- col.defaultTo(sql`now()`).notNull(),
67
- )
68
- .addColumn('updated_at', 'timestamptz', (col) =>
69
- col.defaultTo(sql`now()`).notNull(),
70
- )
71
- .execute();
72
-
73
- // Create posts table with foreign key
74
- await db.schema
75
- .createTable('studio_introspect_posts')
76
- .ifNotExists()
77
- .addColumn('id', 'uuid', (col) =>
78
- col.primaryKey().defaultTo(sql`gen_random_uuid()`),
79
- )
80
- .addColumn('user_id', 'integer', (col) =>
81
- col
82
- .notNull()
83
- .references('studio_introspect_users.id')
84
- .onDelete('cascade'),
85
- )
86
- .addColumn('title', 'varchar(500)', (col) => col.notNull())
87
- .addColumn('content', 'text')
88
- .addColumn('view_count', 'integer', (col) => col.notNull().defaultTo(0))
89
- .addColumn('published_at', 'timestamp')
90
- .addColumn('created_at', 'timestamptz', (col) =>
91
- col.defaultTo(sql`now()`).notNull(),
92
- )
93
- .execute();
94
-
95
- // Create simple tags table
96
- await db.schema
97
- .createTable('studio_introspect_tags')
98
- .ifNotExists()
99
- .addColumn('id', 'serial', (col) => col.primaryKey())
100
- .addColumn('name', 'varchar(100)', (col) => col.notNull())
101
- .execute();
102
-
103
- // Create excluded table (for exclusion testing)
104
- await db.schema
105
- .createTable('studio_introspect_excluded')
106
- .ifNotExists()
107
- .addColumn('id', 'serial', (col) => col.primaryKey())
108
- .addColumn('value', 'varchar(100)', (col) => col.notNull())
109
- .execute();
110
- });
111
-
112
- afterEach(async () => {
113
- // Clean up data after each test
114
- await db.deleteFrom('studioIntrospectPosts').execute();
115
- await db.deleteFrom('studioIntrospectUsers').execute();
116
- await db.deleteFrom('studioIntrospectTags').execute();
117
- await db.deleteFrom('studioIntrospectExcluded').execute();
118
- });
119
-
120
- afterAll(async () => {
121
- // Drop tables and close connection
122
- await db.schema.dropTable('studio_introspect_posts').ifExists().execute();
123
- await db.schema.dropTable('studio_introspect_users').ifExists().execute();
124
- await db.schema.dropTable('studio_introspect_tags').ifExists().execute();
125
- await db.schema
126
- .dropTable('studio_introspect_excluded')
127
- .ifExists()
128
- .execute();
129
- await db.destroy();
130
- });
131
-
132
- describe('introspectSchema', () => {
133
- it('should discover all tables in the public schema', async () => {
134
- const schema = await introspectSchema(db, []);
135
-
136
- // Find our test tables
137
- const tableNames = schema.tables.map((t) => t.name);
138
- expect(tableNames).toContain('studio_introspect_users');
139
- expect(tableNames).toContain('studio_introspect_posts');
140
- expect(tableNames).toContain('studio_introspect_tags');
141
- expect(tableNames).toContain('studio_introspect_excluded');
142
- expect(schema.updatedAt).toBeInstanceOf(Date);
143
- });
144
-
145
- it('should exclude specified tables', async () => {
146
- const schema = await introspectSchema(db, ['studio_introspect_excluded']);
147
-
148
- const tableNames = schema.tables.map((t) => t.name);
149
- expect(tableNames).toContain('studio_introspect_users');
150
- expect(tableNames).toContain('studio_introspect_posts');
151
- expect(tableNames).not.toContain('studio_introspect_excluded');
152
- });
153
-
154
- it('should exclude multiple tables', async () => {
155
- const schema = await introspectSchema(db, [
156
- 'studio_introspect_excluded',
157
- 'studio_introspect_tags',
158
- ]);
159
-
160
- const tableNames = schema.tables.map((t) => t.name);
161
- expect(tableNames).toContain('studio_introspect_users');
162
- expect(tableNames).toContain('studio_introspect_posts');
163
- expect(tableNames).not.toContain('studio_introspect_excluded');
164
- expect(tableNames).not.toContain('studio_introspect_tags');
165
- });
166
- });
167
-
168
- describe('introspectTable', () => {
169
- it('should return column information for users table', async () => {
170
- const tableInfo = await introspectTable(db, 'studio_introspect_users');
171
-
172
- expect(tableInfo.name).toBe('studio_introspect_users');
173
- expect(tableInfo.schema).toBe('public');
174
- expect(tableInfo.columns.length).toBe(7);
175
-
176
- // Find specific columns
177
- const idCol = tableInfo.columns.find((c) => c.name === 'id');
178
- const nameCol = tableInfo.columns.find((c) => c.name === 'name');
179
- const emailCol = tableInfo.columns.find((c) => c.name === 'email');
180
- const isActiveCol = tableInfo.columns.find((c) => c.name === 'is_active');
181
- const metadataCol = tableInfo.columns.find((c) => c.name === 'metadata');
182
- const createdAtCol = tableInfo.columns.find(
183
- (c) => c.name === 'created_at',
184
- );
185
-
186
- // Check id column (primary key, serial)
187
- expect(idCol).toBeDefined();
188
- expect(idCol?.type).toBe('number');
189
- expect(idCol?.rawType).toBe('int4');
190
- expect(idCol?.isPrimaryKey).toBe(true);
191
- expect(idCol?.nullable).toBe(false);
192
-
193
- // Check name column (varchar, not null)
194
- expect(nameCol).toBeDefined();
195
- expect(nameCol?.type).toBe('string');
196
- expect(nameCol?.rawType).toBe('varchar');
197
- expect(nameCol?.nullable).toBe(false);
198
-
199
- // Check email column (varchar, unique)
200
- expect(emailCol).toBeDefined();
201
- expect(emailCol?.type).toBe('string');
202
- expect(emailCol?.nullable).toBe(false);
203
-
204
- // Check is_active column (boolean with default)
205
- expect(isActiveCol).toBeDefined();
206
- expect(isActiveCol?.type).toBe('boolean');
207
- expect(isActiveCol?.rawType).toBe('bool');
208
- expect(isActiveCol?.nullable).toBe(false);
209
-
210
- // Check metadata column (jsonb, nullable)
211
- expect(metadataCol).toBeDefined();
212
- expect(metadataCol?.type).toBe('json');
213
- expect(metadataCol?.rawType).toBe('jsonb');
214
- expect(metadataCol?.nullable).toBe(true);
215
-
216
- // Check created_at column (timestamptz)
217
- expect(createdAtCol).toBeDefined();
218
- expect(createdAtCol?.type).toBe('datetime');
219
- expect(createdAtCol?.rawType).toBe('timestamptz');
220
- });
221
-
222
- it('should detect primary key', async () => {
223
- const tableInfo = await introspectTable(db, 'studio_introspect_users');
224
-
225
- expect(tableInfo.primaryKey).toEqual(['id']);
226
-
227
- const idColumn = tableInfo.columns.find((c) => c.name === 'id');
228
- expect(idColumn?.isPrimaryKey).toBe(true);
229
- });
230
-
231
- it('should detect foreign keys', async () => {
232
- const tableInfo = await introspectTable(db, 'studio_introspect_posts');
233
-
234
- const userIdCol = tableInfo.columns.find((c) => c.name === 'user_id');
235
- expect(userIdCol).toBeDefined();
236
- expect(userIdCol?.isForeignKey).toBe(true);
237
- expect(userIdCol?.foreignKeyTable).toBe('studio_introspect_users');
238
- expect(userIdCol?.foreignKeyColumn).toBe('id');
239
- });
240
-
241
- it('should detect uuid primary key', async () => {
242
- const tableInfo = await introspectTable(db, 'studio_introspect_posts');
243
-
244
- expect(tableInfo.primaryKey).toEqual(['id']);
245
-
246
- const idColumn = tableInfo.columns.find((c) => c.name === 'id');
247
- expect(idColumn).toBeDefined();
248
- expect(idColumn?.type).toBe('uuid');
249
- expect(idColumn?.rawType).toBe('uuid');
250
- expect(idColumn?.isPrimaryKey).toBe(true);
251
- });
252
-
253
- it('should map PostgreSQL types correctly', async () => {
254
- const usersTable = await introspectTable(db, 'studio_introspect_users');
255
- const postsTable = await introspectTable(db, 'studio_introspect_posts');
256
-
257
- // Integer types -> number
258
- const userIdCol = usersTable.columns.find((c) => c.name === 'id');
259
- expect(userIdCol?.type).toBe('number');
260
-
261
- // Boolean -> boolean
262
- const isActiveCol = usersTable.columns.find(
263
- (c) => c.name === 'is_active',
264
- );
265
- expect(isActiveCol?.type).toBe('boolean');
266
-
267
- // JSONB -> json
268
- const metadataCol = usersTable.columns.find((c) => c.name === 'metadata');
269
- expect(metadataCol?.type).toBe('json');
270
-
271
- // Timestamptz -> datetime
272
- const createdAtCol = usersTable.columns.find(
273
- (c) => c.name === 'created_at',
274
- );
275
- expect(createdAtCol?.type).toBe('datetime');
276
-
277
- // UUID -> uuid
278
- const postIdCol = postsTable.columns.find((c) => c.name === 'id');
279
- expect(postIdCol?.type).toBe('uuid');
280
-
281
- // Text -> string
282
- const contentCol = postsTable.columns.find((c) => c.name === 'content');
283
- expect(contentCol?.type).toBe('string');
284
- expect(contentCol?.rawType).toBe('text');
285
-
286
- // Timestamp (without tz) -> datetime
287
- const publishedCol = postsTable.columns.find(
288
- (c) => c.name === 'published_at',
289
- );
290
- expect(publishedCol?.type).toBe('datetime');
291
- });
292
-
293
- it('should detect nullable columns', async () => {
294
- const tableInfo = await introspectTable(db, 'studio_introspect_posts');
295
-
296
- const titleCol = tableInfo.columns.find((c) => c.name === 'title');
297
- const contentCol = tableInfo.columns.find((c) => c.name === 'content');
298
- const publishedAtCol = tableInfo.columns.find(
299
- (c) => c.name === 'published_at',
300
- );
301
-
302
- expect(titleCol?.nullable).toBe(false);
303
- expect(contentCol?.nullable).toBe(true);
304
- expect(publishedAtCol?.nullable).toBe(true);
305
- });
306
-
307
- it('should provide estimated row count when data exists', async () => {
308
- // Insert some data
309
- const user = await db
310
- .insertInto('studioIntrospectUsers')
311
- .values({
312
- name: 'Test User',
313
- email: 'test@example.com',
314
- })
315
- .returningAll()
316
- .executeTakeFirstOrThrow();
317
-
318
- await db
319
- .insertInto('studioIntrospectPosts')
320
- .values([
321
- { userId: user.id, title: 'Post 1' },
322
- { userId: user.id, title: 'Post 2' },
323
- { userId: user.id, title: 'Post 3' },
324
- ])
325
- .execute();
326
-
327
- // ANALYZE to update statistics
328
- await sql`ANALYZE studio_introspect_posts`.execute(db);
329
-
330
- const tableInfo = await introspectTable(db, 'studio_introspect_posts');
331
-
332
- // Row count estimate should exist (might not be exact)
333
- expect(tableInfo.estimatedRowCount).toBeDefined();
334
- expect(tableInfo.estimatedRowCount).toBeGreaterThan(0);
335
- });
336
-
337
- it('should return all columns in ordinal position order', async () => {
338
- const tableInfo = await introspectTable(db, 'studio_introspect_users');
339
-
340
- const columnNames = tableInfo.columns.map((c) => c.name);
341
- expect(columnNames).toEqual([
342
- 'id',
343
- 'name',
344
- 'email',
345
- 'is_active',
346
- 'metadata',
347
- 'created_at',
348
- 'updated_at',
349
- ]);
350
- });
351
- });
352
- });
@@ -1,123 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { decodeCursor, encodeCursor } from '../pagination';
3
-
4
- describe('encodeCursor', () => {
5
- it('should encode string values', () => {
6
- const cursor = encodeCursor('test-value');
7
- expect(cursor).toBeTruthy();
8
- expect(typeof cursor).toBe('string');
9
- });
10
-
11
- it('should encode number values', () => {
12
- const cursor = encodeCursor(42);
13
- expect(cursor).toBeTruthy();
14
- });
15
-
16
- it('should encode Date values', () => {
17
- const date = new Date('2024-01-15T10:30:00.000Z');
18
- const cursor = encodeCursor(date);
19
- expect(cursor).toBeTruthy();
20
- });
21
-
22
- it('should encode null values', () => {
23
- const cursor = encodeCursor(null);
24
- expect(cursor).toBeTruthy();
25
- });
26
-
27
- it('should encode boolean values', () => {
28
- const cursor = encodeCursor(true);
29
- expect(cursor).toBeTruthy();
30
- });
31
- });
32
-
33
- describe('decodeCursor', () => {
34
- it('should decode string values', () => {
35
- const original = 'test-value';
36
- const cursor = encodeCursor(original);
37
- const decoded = decodeCursor(cursor);
38
- expect(decoded).toBe(original);
39
- });
40
-
41
- it('should decode number values', () => {
42
- const original = 42;
43
- const cursor = encodeCursor(original);
44
- const decoded = decodeCursor(cursor);
45
- expect(decoded).toBe(original);
46
- });
47
-
48
- it('should decode Date values', () => {
49
- const original = new Date('2024-01-15T10:30:00.000Z');
50
- const cursor = encodeCursor(original);
51
- const decoded = decodeCursor(cursor);
52
-
53
- expect(decoded).toBeInstanceOf(Date);
54
- expect((decoded as Date).toISOString()).toBe(original.toISOString());
55
- });
56
-
57
- it('should decode null values', () => {
58
- const cursor = encodeCursor(null);
59
- const decoded = decodeCursor(cursor);
60
- expect(decoded).toBeNull();
61
- });
62
-
63
- it('should decode boolean values', () => {
64
- const cursorTrue = encodeCursor(true);
65
- const cursorFalse = encodeCursor(false);
66
-
67
- expect(decodeCursor(cursorTrue)).toBe(true);
68
- expect(decodeCursor(cursorFalse)).toBe(false);
69
- });
70
-
71
- it('should throw error for invalid cursor format', () => {
72
- expect(() => decodeCursor('invalid-cursor')).toThrow(
73
- 'Invalid cursor format',
74
- );
75
- });
76
-
77
- it('should throw error for malformed base64', () => {
78
- expect(() => decodeCursor('!!!')).toThrow('Invalid cursor format');
79
- });
80
-
81
- it('should throw error for non-JSON content', () => {
82
- // Valid base64 but not JSON
83
- const invalidCursor = Buffer.from('not json').toString('base64url');
84
- expect(() => decodeCursor(invalidCursor)).toThrow('Invalid cursor format');
85
- });
86
- });
87
-
88
- describe('encodeCursor and decodeCursor roundtrip', () => {
89
- it('should roundtrip string values', () => {
90
- const values = [
91
- 'hello',
92
- '',
93
- 'special chars: !@#$%',
94
- 'unicode: \u00e9\u00e8',
95
- ];
96
-
97
- for (const value of values) {
98
- expect(decodeCursor(encodeCursor(value))).toBe(value);
99
- }
100
- });
101
-
102
- it('should roundtrip number values', () => {
103
- const values = [0, 1, -1, 3.14, Number.MAX_SAFE_INTEGER];
104
-
105
- for (const value of values) {
106
- expect(decodeCursor(encodeCursor(value))).toBe(value);
107
- }
108
- });
109
-
110
- it('should roundtrip Date values', () => {
111
- const dates = [
112
- new Date(),
113
- new Date('2020-01-01T00:00:00.000Z'),
114
- new Date(0),
115
- ];
116
-
117
- for (const date of dates) {
118
- const decoded = decodeCursor(encodeCursor(date));
119
- expect(decoded).toBeInstanceOf(Date);
120
- expect((decoded as Date).getTime()).toBe(date.getTime());
121
- }
122
- });
123
- });
@@ -1,191 +0,0 @@
1
- import type { SelectQueryBuilder } from 'kysely';
2
- import {
3
- type ColumnInfo,
4
- Direction,
5
- type FilterCondition,
6
- FilterOperator,
7
- type SortConfig,
8
- type TableInfo,
9
- } from '../types';
10
-
11
- /**
12
- * Validates that a filter is applicable to the given column.
13
- */
14
- export function validateFilter(
15
- filter: FilterCondition,
16
- columnInfo: ColumnInfo,
17
- ): { valid: boolean; error?: string } {
18
- // Validate operator compatibility with column type
19
- const typeCompatibility: Record<string, FilterOperator[]> = {
20
- string: [
21
- FilterOperator.Eq,
22
- FilterOperator.Neq,
23
- FilterOperator.Like,
24
- FilterOperator.Ilike,
25
- FilterOperator.In,
26
- FilterOperator.Nin,
27
- FilterOperator.IsNull,
28
- FilterOperator.IsNotNull,
29
- ],
30
- number: [
31
- FilterOperator.Eq,
32
- FilterOperator.Neq,
33
- FilterOperator.Gt,
34
- FilterOperator.Gte,
35
- FilterOperator.Lt,
36
- FilterOperator.Lte,
37
- FilterOperator.In,
38
- FilterOperator.Nin,
39
- FilterOperator.IsNull,
40
- FilterOperator.IsNotNull,
41
- ],
42
- boolean: [
43
- FilterOperator.Eq,
44
- FilterOperator.Neq,
45
- FilterOperator.IsNull,
46
- FilterOperator.IsNotNull,
47
- ],
48
- date: [
49
- FilterOperator.Eq,
50
- FilterOperator.Neq,
51
- FilterOperator.Gt,
52
- FilterOperator.Gte,
53
- FilterOperator.Lt,
54
- FilterOperator.Lte,
55
- FilterOperator.IsNull,
56
- FilterOperator.IsNotNull,
57
- ],
58
- datetime: [
59
- FilterOperator.Eq,
60
- FilterOperator.Neq,
61
- FilterOperator.Gt,
62
- FilterOperator.Gte,
63
- FilterOperator.Lt,
64
- FilterOperator.Lte,
65
- FilterOperator.IsNull,
66
- FilterOperator.IsNotNull,
67
- ],
68
- uuid: [
69
- FilterOperator.Eq,
70
- FilterOperator.Neq,
71
- FilterOperator.In,
72
- FilterOperator.Nin,
73
- FilterOperator.IsNull,
74
- FilterOperator.IsNotNull,
75
- ],
76
- json: [FilterOperator.IsNull, FilterOperator.IsNotNull],
77
- binary: [FilterOperator.IsNull, FilterOperator.IsNotNull],
78
- unknown: [
79
- FilterOperator.Eq,
80
- FilterOperator.Neq,
81
- FilterOperator.IsNull,
82
- FilterOperator.IsNotNull,
83
- ],
84
- };
85
-
86
- const allowedOps =
87
- typeCompatibility[columnInfo.type] ?? typeCompatibility.unknown ?? [];
88
-
89
- if (!allowedOps.includes(filter.operator)) {
90
- return {
91
- valid: false,
92
- error: `Operator '${filter.operator}' not supported for column type '${columnInfo.type}'`,
93
- };
94
- }
95
-
96
- return { valid: true };
97
- }
98
-
99
- /**
100
- * Applies filters to a Kysely query builder.
101
- */
102
- export function applyFilters<DB, TB extends keyof DB, O>(
103
- query: SelectQueryBuilder<DB, TB, O>,
104
- filters: FilterCondition[],
105
- tableInfo: TableInfo,
106
- ): SelectQueryBuilder<DB, TB, O> {
107
- let result = query;
108
-
109
- for (const filter of filters) {
110
- const column = tableInfo.columns.find((c) => c.name === filter.column);
111
-
112
- if (!column) {
113
- throw new Error(
114
- `Column '${filter.column}' not found in table '${tableInfo.name}'`,
115
- );
116
- }
117
-
118
- const validation = validateFilter(filter, column);
119
- if (!validation.valid) {
120
- throw new Error(validation.error);
121
- }
122
-
123
- result = applyFilterCondition(result, filter);
124
- }
125
-
126
- return result;
127
- }
128
-
129
- function applyFilterCondition<DB, TB extends keyof DB, O>(
130
- query: SelectQueryBuilder<DB, TB, O>,
131
- filter: FilterCondition,
132
- ): SelectQueryBuilder<DB, TB, O> {
133
- const { column, operator, value } = filter;
134
-
135
- switch (operator) {
136
- case FilterOperator.Eq:
137
- return query.where(column as any, '=', value);
138
- case FilterOperator.Neq:
139
- return query.where(column as any, '!=', value);
140
- case FilterOperator.Gt:
141
- return query.where(column as any, '>', value);
142
- case FilterOperator.Gte:
143
- return query.where(column as any, '>=', value);
144
- case FilterOperator.Lt:
145
- return query.where(column as any, '<', value);
146
- case FilterOperator.Lte:
147
- return query.where(column as any, '<=', value);
148
- case FilterOperator.Like:
149
- return query.where(column as any, 'like', value);
150
- case FilterOperator.Ilike:
151
- return query.where(column as any, 'ilike', value);
152
- case FilterOperator.In:
153
- return query.where(column as any, 'in', value as any[]);
154
- case FilterOperator.Nin:
155
- return query.where(column as any, 'not in', value as any[]);
156
- case FilterOperator.IsNull:
157
- return query.where(column as any, 'is', null);
158
- case FilterOperator.IsNotNull:
159
- return query.where(column as any, 'is not', null);
160
- default:
161
- throw new Error(`Unknown filter operator: ${operator}`);
162
- }
163
- }
164
-
165
- /**
166
- * Applies sorting to a Kysely query builder.
167
- */
168
- export function applySorting<DB, TB extends keyof DB, O>(
169
- query: SelectQueryBuilder<DB, TB, O>,
170
- sorts: SortConfig[],
171
- tableInfo: TableInfo,
172
- ): SelectQueryBuilder<DB, TB, O> {
173
- let result = query;
174
-
175
- for (const sort of sorts) {
176
- const column = tableInfo.columns.find((c) => c.name === sort.column);
177
-
178
- if (!column) {
179
- throw new Error(
180
- `Column '${sort.column}' not found in table '${tableInfo.name}'`,
181
- );
182
- }
183
-
184
- result = result.orderBy(
185
- sort.column as any,
186
- sort.direction === Direction.Asc ? 'asc' : 'desc',
187
- );
188
- }
189
-
190
- return result;
191
- }
package/src/data/index.ts DELETED
@@ -1 +0,0 @@
1
- export { DataBrowser } from './DataBrowser';