@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 @@
1
+ {"version":3,"file":"DataBrowser-c-Gs6PZB.cjs","names":["filter: FilterCondition","columnInfo: ColumnInfo","typeCompatibility: Record<string, FilterOperator[]>","FilterOperator","query: SelectQueryBuilder<DB, TB, O>","filters: FilterCondition[]","tableInfo: TableInfo","sorts: SortConfig[]","Direction","db: Kysely<DB>","excludeTables: string[]","tables: TableInfo[]","tableName: string","columns: ColumnInfo[]","estimatedRowCount: number | undefined","udtName: string","typeMap: Record<string, ColumnType>","value: unknown","cursor: string","options: Required<DataBrowserOptions<DB>>","tableName: string","options: QueryOptions","Direction","nextCursor: string | null","prevCursor: string | null"],"sources":["../src/data/filtering.ts","../src/data/introspection.ts","../src/data/pagination.ts","../src/data/DataBrowser.ts"],"sourcesContent":["import type { SelectQueryBuilder } from 'kysely';\nimport {\n type ColumnInfo,\n Direction,\n type FilterCondition,\n FilterOperator,\n type SortConfig,\n type TableInfo,\n} from '../types';\n\n/**\n * Validates that a filter is applicable to the given column.\n */\nexport function validateFilter(\n filter: FilterCondition,\n columnInfo: ColumnInfo,\n): { valid: boolean; error?: string } {\n // Validate operator compatibility with column type\n const typeCompatibility: Record<string, FilterOperator[]> = {\n string: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.Like,\n FilterOperator.Ilike,\n FilterOperator.In,\n FilterOperator.Nin,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n number: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.Gt,\n FilterOperator.Gte,\n FilterOperator.Lt,\n FilterOperator.Lte,\n FilterOperator.In,\n FilterOperator.Nin,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n boolean: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n date: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.Gt,\n FilterOperator.Gte,\n FilterOperator.Lt,\n FilterOperator.Lte,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n datetime: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.Gt,\n FilterOperator.Gte,\n FilterOperator.Lt,\n FilterOperator.Lte,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n uuid: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.In,\n FilterOperator.Nin,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n json: [FilterOperator.IsNull, FilterOperator.IsNotNull],\n binary: [FilterOperator.IsNull, FilterOperator.IsNotNull],\n unknown: [\n FilterOperator.Eq,\n FilterOperator.Neq,\n FilterOperator.IsNull,\n FilterOperator.IsNotNull,\n ],\n };\n\n const allowedOps =\n typeCompatibility[columnInfo.type] ?? typeCompatibility.unknown;\n\n if (!allowedOps.includes(filter.operator)) {\n return {\n valid: false,\n error: `Operator '${filter.operator}' not supported for column type '${columnInfo.type}'`,\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Applies filters to a Kysely query builder.\n */\nexport function applyFilters<DB, TB extends keyof DB, O>(\n query: SelectQueryBuilder<DB, TB, O>,\n filters: FilterCondition[],\n tableInfo: TableInfo,\n): SelectQueryBuilder<DB, TB, O> {\n let result = query;\n\n for (const filter of filters) {\n const column = tableInfo.columns.find((c) => c.name === filter.column);\n\n if (!column) {\n throw new Error(\n `Column '${filter.column}' not found in table '${tableInfo.name}'`,\n );\n }\n\n const validation = validateFilter(filter, column);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n result = applyFilterCondition(result, filter);\n }\n\n return result;\n}\n\nfunction applyFilterCondition<DB, TB extends keyof DB, O>(\n query: SelectQueryBuilder<DB, TB, O>,\n filter: FilterCondition,\n): SelectQueryBuilder<DB, TB, O> {\n const { column, operator, value } = filter;\n\n switch (operator) {\n case FilterOperator.Eq:\n return query.where(column as any, '=', value);\n case FilterOperator.Neq:\n return query.where(column as any, '!=', value);\n case FilterOperator.Gt:\n return query.where(column as any, '>', value);\n case FilterOperator.Gte:\n return query.where(column as any, '>=', value);\n case FilterOperator.Lt:\n return query.where(column as any, '<', value);\n case FilterOperator.Lte:\n return query.where(column as any, '<=', value);\n case FilterOperator.Like:\n return query.where(column as any, 'like', value);\n case FilterOperator.Ilike:\n return query.where(column as any, 'ilike', value);\n case FilterOperator.In:\n return query.where(column as any, 'in', value as any[]);\n case FilterOperator.Nin:\n return query.where(column as any, 'not in', value as any[]);\n case FilterOperator.IsNull:\n return query.where(column as any, 'is', null);\n case FilterOperator.IsNotNull:\n return query.where(column as any, 'is not', null);\n default:\n throw new Error(`Unknown filter operator: ${operator}`);\n }\n}\n\n/**\n * Applies sorting to a Kysely query builder.\n */\nexport function applySorting<DB, TB extends keyof DB, O>(\n query: SelectQueryBuilder<DB, TB, O>,\n sorts: SortConfig[],\n tableInfo: TableInfo,\n): SelectQueryBuilder<DB, TB, O> {\n let result = query;\n\n for (const sort of sorts) {\n const column = tableInfo.columns.find((c) => c.name === sort.column);\n\n if (!column) {\n throw new Error(\n `Column '${sort.column}' not found in table '${tableInfo.name}'`,\n );\n }\n\n result = result.orderBy(\n sort.column as any,\n sort.direction === Direction.Asc ? 'asc' : 'desc',\n );\n }\n\n return result;\n}\n","import type { Kysely } from 'kysely';\nimport type { ColumnInfo, ColumnType, SchemaInfo, TableInfo } from '../types';\n\n/**\n * Introspects the database schema to discover tables and columns.\n * Uses PostgreSQL information_schema for metadata.\n */\nexport async function introspectSchema<DB>(\n db: Kysely<DB>,\n excludeTables: string[],\n): Promise<SchemaInfo> {\n // Query tables from information_schema\n const excludePlaceholders =\n excludeTables.length > 0\n ? excludeTables.map((_, i) => `$${i + 1}`).join(', ')\n : \"''\";\n\n const tablesQuery = `\n SELECT\n table_name,\n table_schema\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n ${excludeTables.length > 0 ? `AND table_name NOT IN (${excludePlaceholders})` : ''}\n ORDER BY table_name\n `;\n\n const tablesResult = await db.executeQuery({\n sql: tablesQuery,\n parameters: excludeTables,\n } as any);\n\n const tables: TableInfo[] = [];\n\n for (const row of tablesResult.rows as any[]) {\n // Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n const tableName = row.table_name ?? row.tableName;\n const tableSchema = row.table_schema ?? row.tableSchema;\n const tableInfo = await introspectTable(db, tableName, tableSchema);\n tables.push(tableInfo);\n }\n\n return {\n tables,\n updatedAt: new Date(),\n };\n}\n\n/**\n * Introspects a single table to get column information.\n */\nexport async function introspectTable<DB>(\n db: Kysely<DB>,\n tableName: string,\n schema = 'public',\n): Promise<TableInfo> {\n // Query columns\n const columnsQuery = `\n SELECT\n c.column_name,\n c.data_type,\n c.udt_name,\n c.is_nullable,\n c.column_default,\n CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as is_primary_key\n FROM information_schema.columns c\n LEFT JOIN (\n SELECT ku.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage ku\n ON tc.constraint_name = ku.constraint_name\n AND tc.table_schema = ku.table_schema\n WHERE tc.table_name = $1\n AND tc.table_schema = $2\n AND tc.constraint_type = 'PRIMARY KEY'\n ) pk ON c.column_name = pk.column_name\n WHERE c.table_name = $1\n AND c.table_schema = $2\n ORDER BY c.ordinal_position\n `;\n\n const columnsResult = await db.executeQuery({\n sql: columnsQuery,\n parameters: [tableName, schema],\n } as any);\n\n // Query foreign keys\n const fkQuery = `\n SELECT\n kcu.column_name,\n ccu.table_name AS foreign_table,\n ccu.column_name AS foreign_column\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage ccu\n ON tc.constraint_name = ccu.constraint_name\n AND tc.table_schema = ccu.table_schema\n WHERE tc.table_name = $1\n AND tc.table_schema = $2\n AND tc.constraint_type = 'FOREIGN KEY'\n `;\n\n const fkResult = await db.executeQuery({\n sql: fkQuery,\n parameters: [tableName, schema],\n } as any);\n\n const foreignKeys = new Map<string, { table: string; column: string }>();\n for (const row of fkResult.rows as any[]) {\n // Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n const colName = row.column_name ?? row.columnName;\n foreignKeys.set(colName, {\n table: row.foreign_table ?? row.foreignTable,\n column: row.foreign_column ?? row.foreignColumn,\n });\n }\n\n const columns: ColumnInfo[] = (columnsResult.rows as any[]).map((row) => {\n // Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n const colName = row.column_name ?? row.columnName;\n const udtName = row.udt_name ?? row.udtName;\n const isNullable = row.is_nullable ?? row.isNullable;\n const isPrimaryKey = row.is_primary_key ?? row.isPrimaryKey;\n const columnDefault = row.column_default ?? row.columnDefault;\n\n const fk = foreignKeys.get(colName);\n return {\n name: colName,\n type: mapPostgresType(udtName),\n rawType: udtName,\n nullable: isNullable === 'YES',\n isPrimaryKey: isPrimaryKey,\n isForeignKey: !!fk,\n foreignKeyTable: fk?.table,\n foreignKeyColumn: fk?.column,\n defaultValue: columnDefault ?? undefined,\n };\n });\n\n const primaryKey = columns.filter((c) => c.isPrimaryKey).map((c) => c.name);\n\n // Get estimated row count\n const countQuery = `\n SELECT reltuples::bigint AS estimate\n FROM pg_class\n WHERE relname = $1\n `;\n\n let estimatedRowCount: number | undefined;\n try {\n const countResult = await db.executeQuery({\n sql: countQuery,\n parameters: [tableName],\n } as any);\n if (countResult.rows.length > 0) {\n const estimate = (countResult.rows[0] as any).estimate;\n estimatedRowCount = estimate > 0 ? Number(estimate) : undefined;\n }\n } catch {\n // Ignore errors, row count is optional\n }\n\n return {\n name: tableName,\n schema,\n columns,\n primaryKey,\n estimatedRowCount,\n };\n}\n\n/**\n * Maps PostgreSQL types to generic column types.\n */\nfunction mapPostgresType(udtName: string): ColumnType {\n const typeMap: Record<string, ColumnType> = {\n // Strings\n varchar: 'string',\n char: 'string',\n text: 'string',\n name: 'string',\n bpchar: 'string',\n\n // Numbers\n int2: 'number',\n int4: 'number',\n int8: 'number',\n float4: 'number',\n float8: 'number',\n numeric: 'number',\n money: 'number',\n serial: 'number',\n bigserial: 'number',\n\n // Boolean\n bool: 'boolean',\n\n // Dates\n date: 'date',\n timestamp: 'datetime',\n timestamptz: 'datetime',\n time: 'datetime',\n timetz: 'datetime',\n\n // JSON\n json: 'json',\n jsonb: 'json',\n\n // Binary\n bytea: 'binary',\n\n // UUID\n uuid: 'uuid',\n };\n\n return typeMap[udtName] ?? 'unknown';\n}\n","/**\n * Cursor encoding/decoding utilities for pagination.\n */\n\n/**\n * Encode a cursor value for safe URL transmission.\n * Supports various types: string, number, Date, etc.\n */\nexport function encodeCursor(value: unknown): string {\n const payload = {\n v: value instanceof Date ? value.toISOString() : value,\n t: value instanceof Date ? 'date' : typeof value,\n };\n return Buffer.from(JSON.stringify(payload)).toString('base64url');\n}\n\n/**\n * Decode a cursor string back to its original value.\n */\nexport function decodeCursor(cursor: string): unknown {\n try {\n const json = Buffer.from(cursor, 'base64url').toString('utf-8');\n const payload = JSON.parse(json);\n\n if (payload.t === 'date') {\n return new Date(payload.v);\n }\n\n return payload.v;\n } catch {\n throw new Error('Invalid cursor format');\n }\n}\n","import type { Kysely, SelectQueryBuilder } from 'kysely';\nimport {\n type CursorConfig,\n type DataBrowserOptions,\n Direction,\n type QueryOptions,\n type QueryResult,\n type SchemaInfo,\n type TableInfo,\n} from '../types';\nimport { applyFilters, applySorting } from './filtering';\nimport { introspectSchema } from './introspection';\nimport { decodeCursor, encodeCursor } from './pagination';\n\n/**\n * Database browser for introspecting and querying PostgreSQL databases.\n *\n * @example\n * ```typescript\n * const browser = new DataBrowser({\n * db: kyselyInstance,\n * cursor: { field: 'id', direction: Direction.Desc },\n * });\n *\n * const schema = await browser.getSchema();\n * const result = await browser.query({ table: 'users', pageSize: 20 });\n * ```\n */\nexport class DataBrowser<DB = unknown> {\n private db: Kysely<DB>;\n private options: Required<DataBrowserOptions<DB>>;\n private schemaCache: SchemaInfo | null = null;\n private schemaCacheExpiry = 0;\n private readonly CACHE_TTL_MS = 60 * 1000; // 1 minute\n\n constructor(options: Required<DataBrowserOptions<DB>>) {\n this.db = options.db;\n this.options = options;\n }\n\n // ============================================\n // Schema Introspection\n // ============================================\n\n /**\n * Get the database schema information.\n * Results are cached for 1 minute.\n *\n * @param forceRefresh - Force a refresh of the cache\n */\n async getSchema(forceRefresh = false): Promise<SchemaInfo> {\n const now = Date.now();\n\n if (!forceRefresh && this.schemaCache && now < this.schemaCacheExpiry) {\n return this.schemaCache;\n }\n\n const schema = await introspectSchema(this.db, this.options.excludeTables);\n\n this.schemaCache = schema;\n this.schemaCacheExpiry = now + this.CACHE_TTL_MS;\n\n return schema;\n }\n\n /**\n * Get information about a specific table.\n */\n async getTableInfo(tableName: string): Promise<TableInfo | null> {\n const schema = await this.getSchema();\n return schema.tables.find((t) => t.name === tableName) ?? null;\n }\n\n // ============================================\n // Data Querying\n // ============================================\n\n /**\n * Query table data with pagination, filtering, and sorting.\n */\n async query(options: QueryOptions): Promise<QueryResult> {\n const tableInfo = await this.getTableInfo(options.table);\n\n if (!tableInfo) {\n throw new Error(`Table '${options.table}' not found`);\n }\n\n const cursorConfig = this.getCursorConfig(options.table);\n const pageSize = Math.min(\n options.pageSize ?? this.options.defaultPageSize,\n 100,\n );\n\n // Build base query selecting all columns\n let query = this.db\n .selectFrom(options.table as any)\n .selectAll() as SelectQueryBuilder<any, any, any>;\n\n // Apply filters if provided\n if (options.filters && options.filters.length > 0) {\n query = applyFilters(query, options.filters, tableInfo);\n }\n\n // Apply sorting if provided, otherwise use cursor field\n if (options.sort && options.sort.length > 0) {\n query = applySorting(query, options.sort, tableInfo);\n } else {\n query = query.orderBy(cursorConfig.field as any, cursorConfig.direction);\n }\n\n // Handle cursor-based pagination\n if (options.cursor) {\n const cursorValue = decodeCursor(options.cursor);\n const operator = cursorConfig.direction === Direction.Asc ? '>' : '<';\n query = query.where(cursorConfig.field as any, operator, cursorValue);\n }\n\n // Fetch one extra row to determine if there are more results\n const rows = await query.limit(pageSize + 1).execute();\n\n const hasMore = rows.length > pageSize;\n const resultRows = hasMore ? rows.slice(0, pageSize) : rows;\n\n // Generate cursors\n let nextCursor: string | null = null;\n let prevCursor: string | null = null;\n\n if (hasMore && resultRows.length > 0) {\n const lastRow = resultRows[resultRows.length - 1];\n nextCursor = encodeCursor(lastRow[cursorConfig.field]);\n }\n\n // For prev cursor, we need to know if there are previous results\n // This would require a separate query, so we'll only set it if there's an input cursor\n if (options.cursor && resultRows.length > 0) {\n const firstRow = resultRows[0];\n prevCursor = encodeCursor(firstRow[cursorConfig.field]);\n }\n\n return {\n rows: resultRows,\n hasMore,\n nextCursor,\n prevCursor,\n };\n }\n\n // ============================================\n // Configuration Access\n // ============================================\n\n /**\n * Get the cursor configuration for a table.\n * Returns the table-specific config if defined, otherwise the default.\n */\n getCursorConfig(tableName: string): CursorConfig {\n return this.options.tableCursors[tableName] ?? this.options.cursor;\n }\n\n /**\n * Get the underlying Kysely database instance.\n */\n get database(): Kysely<DB> {\n return this.db;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,267 @@
1
+ import { Kysely } from "kysely";
2
+ import { TelescopeStorage } from "@geekmidas/telescope";
3
+
4
+ //#region src/types.d.ts
5
+
6
+ /**
7
+ * Sort direction for cursor-based pagination and sorting.
8
+ */
9
+ declare enum Direction {
10
+ Asc = "asc",
11
+ Desc = "desc",
12
+ }
13
+ /**
14
+ * Filter operators for querying data.
15
+ */
16
+ declare enum FilterOperator {
17
+ Eq = "eq",
18
+ Neq = "neq",
19
+ Gt = "gt",
20
+ Gte = "gte",
21
+ Lt = "lt",
22
+ Lte = "lte",
23
+ Like = "like",
24
+ Ilike = "ilike",
25
+ In = "in",
26
+ Nin = "nin",
27
+ IsNull = "is_null",
28
+ IsNotNull = "is_not_null",
29
+ }
30
+ /**
31
+ * Configuration for cursor-based pagination.
32
+ */
33
+ interface CursorConfig {
34
+ /** The field to use for cursor-based pagination (e.g., 'id', 'created_at') */
35
+ field: string;
36
+ /** Sort direction for the cursor field */
37
+ direction: Direction;
38
+ }
39
+ /**
40
+ * Per-table cursor configuration overrides.
41
+ */
42
+ interface TableCursorConfig {
43
+ [tableName: string]: CursorConfig;
44
+ }
45
+ /**
46
+ * Configuration for the monitoring feature (Telescope).
47
+ */
48
+ interface MonitoringOptions {
49
+ /** Storage backend for monitoring data */
50
+ storage: TelescopeStorage;
51
+ /** Patterns to ignore when recording requests (supports wildcards) */
52
+ ignorePatterns?: string[];
53
+ /** Whether to record request/response bodies (default: true) */
54
+ recordBody?: boolean;
55
+ /** Maximum body size to record in bytes (default: 64KB) */
56
+ maxBodySize?: number;
57
+ /** Hours after which to prune old entries */
58
+ pruneAfterHours?: number;
59
+ }
60
+ /**
61
+ * Configuration for the data browser feature.
62
+ */
63
+ interface DataBrowserOptions<DB = unknown> {
64
+ /** Kysely database instance */
65
+ db: Kysely<DB>;
66
+ /** Default cursor configuration for all tables */
67
+ cursor: CursorConfig;
68
+ /** Per-table cursor overrides */
69
+ tableCursors?: TableCursorConfig;
70
+ /** Tables to exclude from browsing */
71
+ excludeTables?: string[];
72
+ /** Maximum rows per page (default: 50, max: 100) */
73
+ defaultPageSize?: number;
74
+ /** Whether to allow viewing of binary/blob columns (default: false) */
75
+ showBinaryColumns?: boolean;
76
+ }
77
+ /**
78
+ * Configuration for the Studio dashboard.
79
+ */
80
+ interface StudioOptions<DB = unknown> {
81
+ /** Monitoring configuration */
82
+ monitoring: MonitoringOptions;
83
+ /** Data browser configuration */
84
+ data: DataBrowserOptions<DB>;
85
+ /** Dashboard path (default: '/__studio') */
86
+ path?: string;
87
+ /** Whether Studio is enabled (default: true) */
88
+ enabled?: boolean;
89
+ }
90
+ /**
91
+ * Normalized Studio options with all defaults applied.
92
+ */
93
+ interface NormalizedStudioOptions<DB = unknown> {
94
+ monitoring: Required<MonitoringOptions>;
95
+ data: Required<DataBrowserOptions<DB>>;
96
+ path: string;
97
+ enabled: boolean;
98
+ }
99
+ /**
100
+ * Generic column type classification.
101
+ */
102
+ type ColumnType = 'string' | 'number' | 'boolean' | 'date' | 'datetime' | 'json' | 'binary' | 'uuid' | 'unknown';
103
+ /**
104
+ * Information about a database column.
105
+ */
106
+ interface ColumnInfo {
107
+ /** Column name */
108
+ name: string;
109
+ /** Generic column type */
110
+ type: ColumnType;
111
+ /** Raw database type (e.g., 'varchar', 'int4') */
112
+ rawType: string;
113
+ /** Whether the column allows NULL values */
114
+ nullable: boolean;
115
+ /** Whether this column is part of the primary key */
116
+ isPrimaryKey: boolean;
117
+ /** Whether this column is a foreign key */
118
+ isForeignKey: boolean;
119
+ /** Referenced table if foreign key */
120
+ foreignKeyTable?: string;
121
+ /** Referenced column if foreign key */
122
+ foreignKeyColumn?: string;
123
+ /** Default value expression */
124
+ defaultValue?: string;
125
+ }
126
+ /**
127
+ * Information about a database table.
128
+ */
129
+ interface TableInfo {
130
+ /** Table name */
131
+ name: string;
132
+ /** Schema name (e.g., 'public') */
133
+ schema: string;
134
+ /** List of columns */
135
+ columns: ColumnInfo[];
136
+ /** Primary key column names */
137
+ primaryKey: string[];
138
+ /** Estimated row count (if available) */
139
+ estimatedRowCount?: number;
140
+ }
141
+ /**
142
+ * Complete schema information.
143
+ */
144
+ interface SchemaInfo {
145
+ /** List of tables */
146
+ tables: TableInfo[];
147
+ /** When the schema was last introspected */
148
+ updatedAt: Date;
149
+ }
150
+ /**
151
+ * A single filter condition.
152
+ */
153
+ interface FilterCondition {
154
+ /** Column to filter on */
155
+ column: string;
156
+ /** Filter operator */
157
+ operator: FilterOperator;
158
+ /** Value to compare against (optional for IsNull/IsNotNull operators) */
159
+ value?: unknown;
160
+ }
161
+ /**
162
+ * Sort configuration for a column.
163
+ */
164
+ interface SortConfig {
165
+ /** Column to sort by */
166
+ column: string;
167
+ /** Sort direction */
168
+ direction: Direction;
169
+ }
170
+ /**
171
+ * Options for querying table data.
172
+ */
173
+ interface QueryOptions {
174
+ /** Table to query */
175
+ table: string;
176
+ /** Filter conditions */
177
+ filters?: FilterCondition[];
178
+ /** Sort configuration */
179
+ sort?: SortConfig[];
180
+ /** Cursor for pagination */
181
+ cursor?: string | null;
182
+ /** Number of rows per page */
183
+ pageSize?: number;
184
+ /** Pagination direction */
185
+ direction?: 'next' | 'prev';
186
+ }
187
+ /**
188
+ * Result of a paginated query.
189
+ */
190
+ interface QueryResult<T = Record<string, unknown>> {
191
+ /** Retrieved rows */
192
+ rows: T[];
193
+ /** Whether there are more rows */
194
+ hasMore: boolean;
195
+ /** Cursor for next page */
196
+ nextCursor: string | null;
197
+ /** Cursor for previous page */
198
+ prevCursor: string | null;
199
+ /** Estimated total row count */
200
+ totalEstimate?: number;
201
+ }
202
+ /**
203
+ * Types of events broadcast via WebSocket.
204
+ */
205
+ type StudioEventType = 'request' | 'exception' | 'log' | 'stats' | 'connected' | 'schema_updated';
206
+ /**
207
+ * A WebSocket event payload.
208
+ */
209
+ interface StudioEvent<T = unknown> {
210
+ /** Event type */
211
+ type: StudioEventType;
212
+ /** Event payload */
213
+ payload: T;
214
+ /** Event timestamp */
215
+ timestamp: number;
216
+ }
217
+ //#endregion
218
+ //#region src/data/DataBrowser.d.ts
219
+ /**
220
+ * Database browser for introspecting and querying PostgreSQL databases.
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const browser = new DataBrowser({
225
+ * db: kyselyInstance,
226
+ * cursor: { field: 'id', direction: Direction.Desc },
227
+ * });
228
+ *
229
+ * const schema = await browser.getSchema();
230
+ * const result = await browser.query({ table: 'users', pageSize: 20 });
231
+ * ```
232
+ */
233
+ declare class DataBrowser<DB = unknown> {
234
+ private db;
235
+ private options;
236
+ private schemaCache;
237
+ private schemaCacheExpiry;
238
+ private readonly CACHE_TTL_MS;
239
+ constructor(options: Required<DataBrowserOptions<DB>>);
240
+ /**
241
+ * Get the database schema information.
242
+ * Results are cached for 1 minute.
243
+ *
244
+ * @param forceRefresh - Force a refresh of the cache
245
+ */
246
+ getSchema(forceRefresh?: boolean): Promise<SchemaInfo>;
247
+ /**
248
+ * Get information about a specific table.
249
+ */
250
+ getTableInfo(tableName: string): Promise<TableInfo | null>;
251
+ /**
252
+ * Query table data with pagination, filtering, and sorting.
253
+ */
254
+ query(options: QueryOptions): Promise<QueryResult>;
255
+ /**
256
+ * Get the cursor configuration for a table.
257
+ * Returns the table-specific config if defined, otherwise the default.
258
+ */
259
+ getCursorConfig(tableName: string): CursorConfig;
260
+ /**
261
+ * Get the underlying Kysely database instance.
262
+ */
263
+ get database(): Kysely<DB>;
264
+ }
265
+ //#endregion
266
+ export { ColumnInfo, ColumnType, CursorConfig, DataBrowser, DataBrowserOptions, Direction, FilterCondition, FilterOperator, MonitoringOptions, NormalizedStudioOptions, QueryOptions, QueryResult, SchemaInfo, SortConfig, StudioEvent, StudioEventType, StudioOptions, TableCursorConfig, TableInfo };
267
+ //# sourceMappingURL=DataBrowser-hGwiTffZ.d.cts.map
@@ -0,0 +1,30 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ Object.defineProperty(exports, '__toESM', {
26
+ enumerable: true,
27
+ get: function () {
28
+ return __toESM;
29
+ }
30
+ });
@@ -0,0 +1,4 @@
1
+ require('../types-CMttUZYk.cjs');
2
+ const require_DataBrowser = require('../DataBrowser-c-Gs6PZB.cjs');
3
+
4
+ exports.DataBrowser = require_DataBrowser.DataBrowser;
@@ -0,0 +1,2 @@
1
+ import { DataBrowser } from "../DataBrowser-hGwiTffZ.cjs";
2
+ export { DataBrowser };
@@ -0,0 +1,2 @@
1
+ import { DataBrowser } from "../DataBrowser-SOcqmZb2.mjs";
2
+ export { DataBrowser };
@@ -0,0 +1,4 @@
1
+ import "../types-BZv87Ikv.mjs";
2
+ import { DataBrowser } from "../DataBrowser-DQ3-ZxdV.mjs";
3
+
4
+ export { DataBrowser };
package/dist/index.cjs ADDED
@@ -0,0 +1,239 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const require_types = require('./types-CMttUZYk.cjs');
3
+ const require_DataBrowser = require('./DataBrowser-c-Gs6PZB.cjs');
4
+ const __geekmidas_telescope = require_chunk.__toESM(require("@geekmidas/telescope"));
5
+ const __geekmidas_telescope_storage_memory = require_chunk.__toESM(require("@geekmidas/telescope/storage/memory"));
6
+
7
+ //#region src/Studio.ts
8
+ /**
9
+ * Unified development tools dashboard combining monitoring and database browsing.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Studio, Direction } from '@geekmidas/studio';
14
+ * import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
15
+ *
16
+ * const studio = new Studio({
17
+ * monitoring: {
18
+ * storage: new InMemoryStorage(),
19
+ * },
20
+ * data: {
21
+ * db: kyselyInstance,
22
+ * cursor: { field: 'id', direction: Direction.Desc },
23
+ * },
24
+ * });
25
+ * ```
26
+ */
27
+ var Studio = class {
28
+ telescope;
29
+ dataBrowser;
30
+ options;
31
+ wsClients = /* @__PURE__ */ new Set();
32
+ constructor(options) {
33
+ this.options = this.normalizeOptions(options);
34
+ this.telescope = new __geekmidas_telescope.Telescope({
35
+ storage: this.options.monitoring.storage,
36
+ enabled: this.options.enabled,
37
+ path: `${this.options.path}/monitoring`,
38
+ recordBody: this.options.monitoring.recordBody,
39
+ maxBodySize: this.options.monitoring.maxBodySize,
40
+ ignorePatterns: [...this.options.monitoring.ignorePatterns, `${this.options.path}/*`],
41
+ pruneAfterHours: this.options.monitoring.pruneAfterHours
42
+ });
43
+ this.dataBrowser = new require_DataBrowser.DataBrowser(this.options.data);
44
+ }
45
+ /**
46
+ * Get the Studio dashboard path.
47
+ */
48
+ get path() {
49
+ return this.options.path;
50
+ }
51
+ /**
52
+ * Check if Studio is enabled.
53
+ */
54
+ get enabled() {
55
+ return this.options.enabled;
56
+ }
57
+ /**
58
+ * Get the data browser instance.
59
+ */
60
+ get data() {
61
+ return this.dataBrowser;
62
+ }
63
+ /**
64
+ * Check if body recording is enabled for monitoring.
65
+ */
66
+ get recordBody() {
67
+ return this.options.monitoring.recordBody;
68
+ }
69
+ /**
70
+ * Get max body size for monitoring.
71
+ */
72
+ get maxBodySize() {
73
+ return this.options.monitoring.maxBodySize;
74
+ }
75
+ /**
76
+ * Record a request entry.
77
+ */
78
+ async recordRequest(entry) {
79
+ return this.telescope.recordRequest(entry);
80
+ }
81
+ /**
82
+ * Record log entries in batch.
83
+ */
84
+ async log(entries) {
85
+ return this.telescope.log(entries);
86
+ }
87
+ /**
88
+ * Log a debug message.
89
+ */
90
+ async debug(message, context, requestId) {
91
+ return this.telescope.debug(message, context, requestId);
92
+ }
93
+ /**
94
+ * Log an info message.
95
+ */
96
+ async info(message, context, requestId) {
97
+ return this.telescope.info(message, context, requestId);
98
+ }
99
+ /**
100
+ * Log a warning message.
101
+ */
102
+ async warn(message, context, requestId) {
103
+ return this.telescope.warn(message, context, requestId);
104
+ }
105
+ /**
106
+ * Log an error message.
107
+ */
108
+ async error(message, context, requestId) {
109
+ return this.telescope.error(message, context, requestId);
110
+ }
111
+ /**
112
+ * Record an exception.
113
+ */
114
+ async exception(error, requestId) {
115
+ return this.telescope.exception(error, requestId);
116
+ }
117
+ /**
118
+ * Get requests from storage.
119
+ */
120
+ async getRequests(options) {
121
+ return this.telescope.getRequests(options);
122
+ }
123
+ /**
124
+ * Get a single request by ID.
125
+ */
126
+ async getRequest(id) {
127
+ return this.telescope.getRequest(id);
128
+ }
129
+ /**
130
+ * Get exceptions from storage.
131
+ */
132
+ async getExceptions(options) {
133
+ return this.telescope.getExceptions(options);
134
+ }
135
+ /**
136
+ * Get a single exception by ID.
137
+ */
138
+ async getException(id) {
139
+ return this.telescope.getException(id);
140
+ }
141
+ /**
142
+ * Get logs from storage.
143
+ */
144
+ async getLogs(options) {
145
+ return this.telescope.getLogs(options);
146
+ }
147
+ /**
148
+ * Get storage statistics.
149
+ */
150
+ async getStats() {
151
+ return this.telescope.getStats();
152
+ }
153
+ /**
154
+ * Add a WebSocket client for real-time updates.
155
+ */
156
+ addWsClient(ws) {
157
+ this.wsClients.add(ws);
158
+ this.telescope.addWsClient(ws);
159
+ }
160
+ /**
161
+ * Remove a WebSocket client.
162
+ */
163
+ removeWsClient(ws) {
164
+ this.wsClients.delete(ws);
165
+ this.telescope.removeWsClient(ws);
166
+ }
167
+ /**
168
+ * Broadcast an event to all connected WebSocket clients.
169
+ */
170
+ broadcast(event) {
171
+ const data = JSON.stringify(event);
172
+ for (const client of this.wsClients) try {
173
+ client.send(data);
174
+ } catch {
175
+ this.wsClients.delete(client);
176
+ }
177
+ }
178
+ /**
179
+ * Check if a path should be ignored for request recording.
180
+ */
181
+ shouldIgnore(path) {
182
+ if (path.startsWith(this.options.path)) return true;
183
+ return this.telescope.shouldIgnore(path);
184
+ }
185
+ /**
186
+ * Manually prune old monitoring entries.
187
+ */
188
+ async prune(olderThan) {
189
+ return this.telescope.prune(olderThan);
190
+ }
191
+ /**
192
+ * Clean up resources.
193
+ */
194
+ destroy() {
195
+ this.telescope.destroy();
196
+ this.wsClients.clear();
197
+ }
198
+ normalizeOptions(options) {
199
+ const path = options.path ?? "/__studio";
200
+ return {
201
+ monitoring: {
202
+ storage: options.monitoring.storage,
203
+ ignorePatterns: options.monitoring.ignorePatterns ?? [],
204
+ recordBody: options.monitoring.recordBody ?? true,
205
+ maxBodySize: options.monitoring.maxBodySize ?? 64 * 1024,
206
+ pruneAfterHours: options.monitoring.pruneAfterHours
207
+ },
208
+ data: {
209
+ db: options.data.db,
210
+ cursor: options.data.cursor,
211
+ tableCursors: options.data.tableCursors ?? {},
212
+ excludeTables: options.data.excludeTables ?? [
213
+ "kysely_migration",
214
+ "kysely_migration_lock",
215
+ "_prisma_migrations",
216
+ "schema_migrations",
217
+ "_migrations",
218
+ "migrations"
219
+ ],
220
+ defaultPageSize: Math.min(options.data.defaultPageSize ?? 50, 100),
221
+ showBinaryColumns: options.data.showBinaryColumns ?? false
222
+ },
223
+ path,
224
+ enabled: options.enabled ?? true
225
+ };
226
+ }
227
+ };
228
+
229
+ //#endregion
230
+ exports.Direction = require_types.Direction;
231
+ exports.FilterOperator = require_types.FilterOperator;
232
+ Object.defineProperty(exports, 'InMemoryMonitoringStorage', {
233
+ enumerable: true,
234
+ get: function () {
235
+ return __geekmidas_telescope_storage_memory.InMemoryStorage;
236
+ }
237
+ });
238
+ exports.Studio = Studio;
239
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["options: StudioOptions<DB>","Telescope","DataBrowser","entry: Parameters<Telescope['recordRequest']>[0]","entries: Parameters<Telescope['log']>[0]","message: string","context?: Record<string, unknown>","requestId?: string","error: Error","options?: Parameters<Telescope['getRequests']>[0]","id: string","options?: Parameters<Telescope['getExceptions']>[0]","options?: Parameters<Telescope['getLogs']>[0]","ws: WebSocket","event: StudioEvent","path: string","olderThan: Date"],"sources":["../src/Studio.ts"],"sourcesContent":["import { Telescope } from '@geekmidas/telescope';\nimport { DataBrowser } from './data/DataBrowser';\nimport type {\n NormalizedStudioOptions,\n StudioEvent,\n StudioOptions,\n} from './types';\n\n/**\n * Unified development tools dashboard combining monitoring and database browsing.\n *\n * @example\n * ```typescript\n * import { Studio, Direction } from '@geekmidas/studio';\n * import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';\n *\n * const studio = new Studio({\n * monitoring: {\n * storage: new InMemoryStorage(),\n * },\n * data: {\n * db: kyselyInstance,\n * cursor: { field: 'id', direction: Direction.Desc },\n * },\n * });\n * ```\n */\nexport class Studio<DB = unknown> {\n private telescope: Telescope;\n private dataBrowser: DataBrowser<DB>;\n private options: NormalizedStudioOptions<DB>;\n private wsClients = new Set<WebSocket>();\n\n constructor(options: StudioOptions<DB>) {\n this.options = this.normalizeOptions(options);\n\n // Initialize Telescope internally\n this.telescope = new Telescope({\n storage: this.options.monitoring.storage,\n enabled: this.options.enabled,\n path: `${this.options.path}/monitoring`,\n recordBody: this.options.monitoring.recordBody,\n maxBodySize: this.options.monitoring.maxBodySize,\n ignorePatterns: [\n ...this.options.monitoring.ignorePatterns,\n `${this.options.path}/*`, // Ignore Studio's own routes\n ],\n pruneAfterHours: this.options.monitoring.pruneAfterHours,\n });\n\n // Initialize DataBrowser\n this.dataBrowser = new DataBrowser(this.options.data);\n }\n\n // ============================================\n // Public API - Configuration\n // ============================================\n\n /**\n * Get the Studio dashboard path.\n */\n get path(): string {\n return this.options.path;\n }\n\n /**\n * Check if Studio is enabled.\n */\n get enabled(): boolean {\n return this.options.enabled;\n }\n\n /**\n * Get the data browser instance.\n */\n get data(): DataBrowser<DB> {\n return this.dataBrowser;\n }\n\n /**\n * Check if body recording is enabled for monitoring.\n */\n get recordBody(): boolean {\n return this.options.monitoring.recordBody;\n }\n\n /**\n * Get max body size for monitoring.\n */\n get maxBodySize(): number {\n return this.options.monitoring.maxBodySize;\n }\n\n // ============================================\n // Public API - Monitoring (delegated to Telescope)\n // ============================================\n\n /**\n * Record a request entry.\n */\n async recordRequest(\n entry: Parameters<Telescope['recordRequest']>[0],\n ): Promise<string> {\n return this.telescope.recordRequest(entry);\n }\n\n /**\n * Record log entries in batch.\n */\n async log(entries: Parameters<Telescope['log']>[0]): Promise<void> {\n return this.telescope.log(entries);\n }\n\n /**\n * Log a debug message.\n */\n async debug(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n return this.telescope.debug(message, context, requestId);\n }\n\n /**\n * Log an info message.\n */\n async info(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n return this.telescope.info(message, context, requestId);\n }\n\n /**\n * Log a warning message.\n */\n async warn(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n return this.telescope.warn(message, context, requestId);\n }\n\n /**\n * Log an error message.\n */\n async error(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n return this.telescope.error(message, context, requestId);\n }\n\n /**\n * Record an exception.\n */\n async exception(error: Error, requestId?: string): Promise<void> {\n return this.telescope.exception(error, requestId);\n }\n\n /**\n * Get requests from storage.\n */\n async getRequests(\n options?: Parameters<Telescope['getRequests']>[0],\n ): ReturnType<Telescope['getRequests']> {\n return this.telescope.getRequests(options);\n }\n\n /**\n * Get a single request by ID.\n */\n async getRequest(id: string): ReturnType<Telescope['getRequest']> {\n return this.telescope.getRequest(id);\n }\n\n /**\n * Get exceptions from storage.\n */\n async getExceptions(\n options?: Parameters<Telescope['getExceptions']>[0],\n ): ReturnType<Telescope['getExceptions']> {\n return this.telescope.getExceptions(options);\n }\n\n /**\n * Get a single exception by ID.\n */\n async getException(id: string): ReturnType<Telescope['getException']> {\n return this.telescope.getException(id);\n }\n\n /**\n * Get logs from storage.\n */\n async getLogs(\n options?: Parameters<Telescope['getLogs']>[0],\n ): ReturnType<Telescope['getLogs']> {\n return this.telescope.getLogs(options);\n }\n\n /**\n * Get storage statistics.\n */\n async getStats(): ReturnType<Telescope['getStats']> {\n return this.telescope.getStats();\n }\n\n // ============================================\n // Public API - WebSocket\n // ============================================\n\n /**\n * Add a WebSocket client for real-time updates.\n */\n addWsClient(ws: WebSocket): void {\n this.wsClients.add(ws);\n // Also add to Telescope for monitoring events\n this.telescope.addWsClient(ws);\n }\n\n /**\n * Remove a WebSocket client.\n */\n removeWsClient(ws: WebSocket): void {\n this.wsClients.delete(ws);\n this.telescope.removeWsClient(ws);\n }\n\n /**\n * Broadcast an event to all connected WebSocket clients.\n */\n broadcast(event: StudioEvent): void {\n const data = JSON.stringify(event);\n for (const client of this.wsClients) {\n try {\n client.send(data);\n } catch {\n this.wsClients.delete(client);\n }\n }\n }\n\n // ============================================\n // Public API - Lifecycle\n // ============================================\n\n /**\n * Check if a path should be ignored for request recording.\n */\n shouldIgnore(path: string): boolean {\n // Ignore Studio's own routes\n if (path.startsWith(this.options.path)) {\n return true;\n }\n return this.telescope.shouldIgnore(path);\n }\n\n /**\n * Manually prune old monitoring entries.\n */\n async prune(olderThan: Date): Promise<number> {\n return this.telescope.prune(olderThan);\n }\n\n /**\n * Clean up resources.\n */\n destroy(): void {\n this.telescope.destroy();\n this.wsClients.clear();\n }\n\n // ============================================\n // Private Methods\n // ============================================\n\n private normalizeOptions(\n options: StudioOptions<DB>,\n ): NormalizedStudioOptions<DB> {\n const path = options.path ?? '/__studio';\n\n return {\n monitoring: {\n storage: options.monitoring.storage,\n ignorePatterns: options.monitoring.ignorePatterns ?? [],\n recordBody: options.monitoring.recordBody ?? true,\n maxBodySize: options.monitoring.maxBodySize ?? 64 * 1024,\n pruneAfterHours: options.monitoring.pruneAfterHours,\n },\n data: {\n db: options.data.db,\n cursor: options.data.cursor,\n tableCursors: options.data.tableCursors ?? {},\n excludeTables: options.data.excludeTables ?? [\n // Kysely\n 'kysely_migration',\n 'kysely_migration_lock',\n // Prisma\n '_prisma_migrations',\n // Rails/Knex\n 'schema_migrations',\n // Generic\n '_migrations',\n 'migrations',\n ],\n defaultPageSize: Math.min(options.data.defaultPageSize ?? 50, 100),\n showBinaryColumns: options.data.showBinaryColumns ?? false,\n },\n path,\n enabled: options.enabled ?? true,\n };\n }\n}\n"],"mappings":"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"}