@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Kysely } from "kysely";
|
|
2
1
|
import { TelescopeStorage } from "@geekmidas/telescope";
|
|
2
|
+
import { Kysely } from "kysely";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
|
|
@@ -214,6 +214,7 @@ interface StudioEvent<T = unknown> {
|
|
|
214
214
|
/** Event timestamp */
|
|
215
215
|
timestamp: number;
|
|
216
216
|
}
|
|
217
|
+
//# sourceMappingURL=types.d.ts.map
|
|
217
218
|
//#endregion
|
|
218
219
|
//#region src/data/DataBrowser.d.ts
|
|
219
220
|
/**
|
|
@@ -262,6 +263,8 @@ declare class DataBrowser<DB = unknown> {
|
|
|
262
263
|
*/
|
|
263
264
|
get database(): Kysely<DB>;
|
|
264
265
|
}
|
|
266
|
+
//# sourceMappingURL=DataBrowser.d.ts.map
|
|
267
|
+
|
|
265
268
|
//#endregion
|
|
266
269
|
export { ColumnInfo, ColumnType, CursorConfig, DataBrowser, DataBrowserOptions, Direction, FilterCondition, FilterOperator, MonitoringOptions, NormalizedStudioOptions, QueryOptions, QueryResult, SchemaInfo, SortConfig, StudioEvent, StudioEventType, StudioOptions, TableCursorConfig, TableInfo };
|
|
267
|
-
//# sourceMappingURL=DataBrowser-
|
|
270
|
+
//# sourceMappingURL=DataBrowser-B-jz8KBR.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataBrowser-B-jz8KBR.d.mts","names":[],"sources":["../src/types.ts","../src/data/DataBrowser.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAQY,aARA,SAAA;EA8BK,GAAA,GAAA,KAAA;EAUA,IAAA,GAAA,MAAA;AAWjB;AAoBA;;;AAEK,aAjEO,cAAA;EAiED,EAAA,GAEF,IAAA;EAAY,GAEL,GAAA,KAAA;EAAiB,EAAA,GAAA,IAAA;EAgBhB,GAAA,GAAA,KAAA;EAAa,EAAA,GAAA,IAAA;EAAA,GAEjB,GAAA,KAAA;EAAiB,IAEJ,GAAA,MAAA;EAAE,KAArB,GAAA,OAAA;EAAkB,EAAA,GAAA,IAAA;EAUR,GAAA,GAAA,KAAA;EAAuB,MAAA,GAAA,SAAA;EAAA,SAClB,GAAA,aAAA;;;;;AACP,UA/EE,YAAA,CA+EF;EAYH;EAcK,KAAA,EAAA,MAAU;EAwBV;EAgBA,SAAA,EA7IL,SA6Ie;;;;AAIX;AAUC,UArJA,iBAAA,CAyJN;EAQM,CAAA,SAAA,EAAA,MAAU,CAAA,EAhKL,YAoKD;AAMrB;;;;AAMkB,UAtKD,iBAAA,CAsKC;EAYD;EAAW,OAAA,EAhLlB,gBAgLkB;EAAA;EAAW,cAEhC,CAAA,EAAA,MAAA,EAAA;EAAC;EAkBI,UAAA,CAAA,EAAA,OAAe;EAWV;EAAW,WAAA,CAAA,EAAA,MAAA;EAAA;EAEN,eAEZ,CAAA,EAAA,MAAA;AAAC;;;;ACtPE,UDqDI,kBCrDO,CAAA,KAAA,OAAA,CAAA,CAAA;EAAA;EAAA,EAAA,EDuDnB,MChD6C,CDgDtC,EChDsC,CAAA;EAAE;EAAH,MAA3B,EDkDb,YClDa;EAAQ;EAe4B,YAAlB,CAAA,EDqCxB,iBCrCwB;EAAO;EAkBU,aAAjB,CAAA,EAAA,MAAA,EAAA;EAAO;EAYb,eAAW,CAAA,EAAA,MAAA;EAAW;EAAZ,iBA+EP,CAAA,EAAA,OAAA;;;AAOd;;UD/DN;;cAEJ;;QAEN,mBAAmB;;;;;;;;;UAUT;cACJ,SAAS;QACf,SAAS,mBAAmB;;;;;;;KAYvB,UAAA;;;;UAcK,UAAA;;;;QAIV;;;;;;;;;;;;;;;;;;;UAoBU,SAAA;;;;;;WAMP;;;;;;;;;UAUO,UAAA;;UAER;;aAEG;;;;;UAUK,eAAA;;;;YAIN;;;;;;;UAQM,UAAA;;;;aAIL;;;;;UAMK,YAAA;;;;YAIN;;SAEH;;;;;;;;;;;UAYS,gBAAgB;;QAE1B;;;;;;;;;;;;;KAkBK,eAAA;;;;UAWK;;QAEV;;WAEG;;;;;;;;;AAxQV;AAQA;AAsBA;AAUA;AAWA;AAoBA;;;;;;AAMiC;AAgBhB,cC3EJ,WD2EiB,CAAA,KAAA,OAAA,CAAA,CAAA;EAAA,QAAA,EAAA;EAAA,QAEjB,OAAA;EAAiB,QAEJ,WAAA;EAAE,QAArB,iBAAA;EAAkB,iBAAA,YAAA;EAUR,WAAA,CAAA,OAAA,EClFK,QDkFkB,CClFT,kBDkFS,CClFU,EDkFV,CAAA,CAAA;EAAA;;;;;;EAEzB,SAAA,CAAA,YAAA,CAAA,EAAA,OAAA,CAAA,ECrEyB,ODqEzB,CCrEiC,UDqEjC,CAAA;EAYH;AAcZ;AAwBA;EAgBiB,YAAA,CAAU,SAAA,EAAA,MAAA,CAAA,ECrHa,ODqHb,CCrHqB,SDqHrB,GAAA,IAAA,CAAA;EAAA;;;EAIX,KAAA,CAAA,OAAA,EC7GM,YD6GN,CAAA,EC7GqB,OD6GrB,CC7G6B,WD6G7B,CAAA;EAUC;AAYjB;AAUA;;EAA6B,eAIlB,CAAA,SAAA,EAAA,MAAA,CAAA,EClE0B,YDkE1B;EAAe;AAER;AAYlB;EAA4B,IAAA,QAAA,CAAA,CAAA,ECzEX,MDyEW,CCzEJ,EDyEI,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TelescopeStorage } from "@geekmidas/telescope";
|
|
2
1
|
import { Kysely } from "kysely";
|
|
2
|
+
import { TelescopeStorage } from "@geekmidas/telescope";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
|
|
@@ -214,6 +214,7 @@ interface StudioEvent<T = unknown> {
|
|
|
214
214
|
/** Event timestamp */
|
|
215
215
|
timestamp: number;
|
|
216
216
|
}
|
|
217
|
+
//# sourceMappingURL=types.d.ts.map
|
|
217
218
|
//#endregion
|
|
218
219
|
//#region src/data/DataBrowser.d.ts
|
|
219
220
|
/**
|
|
@@ -262,6 +263,8 @@ declare class DataBrowser<DB = unknown> {
|
|
|
262
263
|
*/
|
|
263
264
|
get database(): Kysely<DB>;
|
|
264
265
|
}
|
|
266
|
+
//# sourceMappingURL=DataBrowser.d.ts.map
|
|
267
|
+
|
|
265
268
|
//#endregion
|
|
266
269
|
export { ColumnInfo, ColumnType, CursorConfig, DataBrowser, DataBrowserOptions, Direction, FilterCondition, FilterOperator, MonitoringOptions, NormalizedStudioOptions, QueryOptions, QueryResult, SchemaInfo, SortConfig, StudioEvent, StudioEventType, StudioOptions, TableCursorConfig, TableInfo };
|
|
267
|
-
//# sourceMappingURL=DataBrowser-
|
|
270
|
+
//# sourceMappingURL=DataBrowser-BTe9HWJy.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataBrowser-BTe9HWJy.d.cts","names":[],"sources":["../src/types.ts","../src/data/DataBrowser.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAQY,aARA,SAAA;EA8BK,GAAA,GAAA,KAAA;EAUA,IAAA,GAAA,MAAA;AAWjB;AAoBA;;;AAEK,aAjEO,cAAA;EAiED,EAAA,GAEF,IAAA;EAAY,GAEL,GAAA,KAAA;EAAiB,EAAA,GAAA,IAAA;EAgBhB,GAAA,GAAA,KAAA;EAAa,EAAA,GAAA,IAAA;EAAA,GAEjB,GAAA,KAAA;EAAiB,IAEJ,GAAA,MAAA;EAAE,KAArB,GAAA,OAAA;EAAkB,EAAA,GAAA,IAAA;EAUR,GAAA,GAAA,KAAA;EAAuB,MAAA,GAAA,SAAA;EAAA,SAClB,GAAA,aAAA;;;;;AACP,UA/EE,YAAA,CA+EF;EAYH;EAcK,KAAA,EAAA,MAAU;EAwBV;EAgBA,SAAA,EA7IL,SA6Ie;;;;AAIX;AAUC,UArJA,iBAAA,CAyJN;EAQM,CAAA,SAAA,EAAA,MAAU,CAAA,EAhKL,YAoKD;AAMrB;;;;AAMkB,UAtKD,iBAAA,CAsKC;EAYD;EAAW,OAAA,EAhLlB,gBAgLkB;EAAA;EAAW,cAEhC,CAAA,EAAA,MAAA,EAAA;EAAC;EAkBI,UAAA,CAAA,EAAA,OAAe;EAWV;EAAW,WAAA,CAAA,EAAA,MAAA;EAAA;EAEN,eAEZ,CAAA,EAAA,MAAA;AAAC;;;;ACtPE,UDqDI,kBCrDO,CAAA,KAAA,OAAA,CAAA,CAAA;EAAA;EAAA,EAAA,EDuDnB,MChD6C,CDgDtC,EChDsC,CAAA;EAAE;EAAH,MAA3B,EDkDb,YClDa;EAAQ;EAe4B,YAAlB,CAAA,EDqCxB,iBCrCwB;EAAO;EAkBU,aAAjB,CAAA,EAAA,MAAA,EAAA;EAAO;EAYb,eAAW,CAAA,EAAA,MAAA;EAAW;EAAZ,iBA+EP,CAAA,EAAA,OAAA;;;AAOd;;UD/DN;;cAEJ;;QAEN,mBAAmB;;;;;;;;;UAUT;cACJ,SAAS;QACf,SAAS,mBAAmB;;;;;;;KAYvB,UAAA;;;;UAcK,UAAA;;;;QAIV;;;;;;;;;;;;;;;;;;;UAoBU,SAAA;;;;;;WAMP;;;;;;;;;UAUO,UAAA;;UAER;;aAEG;;;;;UAUK,eAAA;;;;YAIN;;;;;;;UAQM,UAAA;;;;aAIL;;;;;UAMK,YAAA;;;;YAIN;;SAEH;;;;;;;;;;;UAYS,gBAAgB;;QAE1B;;;;;;;;;;;;;KAkBK,eAAA;;;;UAWK;;QAEV;;WAEG;;;;;;;;;AAxQV;AAQA;AAsBA;AAUA;AAWA;AAoBA;;;;;;AAMiC;AAgBhB,cC3EJ,WD2EiB,CAAA,KAAA,OAAA,CAAA,CAAA;EAAA,QAAA,EAAA;EAAA,QAEjB,OAAA;EAAiB,QAEJ,WAAA;EAAE,QAArB,iBAAA;EAAkB,iBAAA,YAAA;EAUR,WAAA,CAAA,OAAA,EClFK,QDkFkB,CClFT,kBDkFS,CClFU,EDkFV,CAAA,CAAA;EAAA;;;;;;EAEzB,SAAA,CAAA,YAAA,CAAA,EAAA,OAAA,CAAA,ECrEyB,ODqEzB,CCrEiC,UDqEjC,CAAA;EAYH;AAcZ;AAwBA;EAgBiB,YAAA,CAAU,SAAA,EAAA,MAAA,CAAA,ECrHa,ODqHb,CCrHqB,SDqHrB,GAAA,IAAA,CAAA;EAAA;;;EAIX,KAAA,CAAA,OAAA,EC7GM,YD6GN,CAAA,EC7GqB,OD6GrB,CC7G6B,WD6G7B,CAAA;EAUC;AAYjB;AAUA;;EAA6B,eAIlB,CAAA,SAAA,EAAA,MAAA,CAAA,EClE0B,YDkE1B;EAAe;AAER;AAYlB;EAA4B,IAAA,QAAA,CAAA,CAAA,ECzEX,MDyEW,CCzEJ,EDyEI,CAAA"}
|
|
@@ -71,7 +71,7 @@ function validateFilter(filter, columnInfo) {
|
|
|
71
71
|
require_types.FilterOperator.IsNotNull
|
|
72
72
|
]
|
|
73
73
|
};
|
|
74
|
-
const allowedOps = typeCompatibility[columnInfo.type] ?? typeCompatibility.unknown;
|
|
74
|
+
const allowedOps = typeCompatibility[columnInfo.type] ?? typeCompatibility.unknown ?? [];
|
|
75
75
|
if (!allowedOps.includes(filter.operator)) return {
|
|
76
76
|
valid: false,
|
|
77
77
|
error: `Operator '${filter.operator}' not supported for column type '${columnInfo.type}'`
|
|
@@ -394,11 +394,11 @@ var DataBrowser = class {
|
|
|
394
394
|
let prevCursor = null;
|
|
395
395
|
if (hasMore && resultRows.length > 0) {
|
|
396
396
|
const lastRow = resultRows[resultRows.length - 1];
|
|
397
|
-
nextCursor = encodeCursor(lastRow[cursorConfig.field]);
|
|
397
|
+
if (lastRow) nextCursor = encodeCursor(lastRow[cursorConfig.field]);
|
|
398
398
|
}
|
|
399
399
|
if (options.cursor && resultRows.length > 0) {
|
|
400
400
|
const firstRow = resultRows[0];
|
|
401
|
-
prevCursor = encodeCursor(firstRow[cursorConfig.field]);
|
|
401
|
+
if (firstRow) prevCursor = encodeCursor(firstRow[cursorConfig.field]);
|
|
402
402
|
}
|
|
403
403
|
return {
|
|
404
404
|
rows: resultRows,
|
|
@@ -429,4 +429,4 @@ Object.defineProperty(exports, 'DataBrowser', {
|
|
|
429
429
|
return DataBrowser;
|
|
430
430
|
}
|
|
431
431
|
});
|
|
432
|
-
//# sourceMappingURL=DataBrowser-
|
|
432
|
+
//# sourceMappingURL=DataBrowser-D8c_pBf4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataBrowser-D8c_pBf4.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\ttype ColumnInfo,\n\tDirection,\n\ttype FilterCondition,\n\tFilterOperator,\n\ttype SortConfig,\n\ttype TableInfo,\n} from '../types';\n\n/**\n * Validates that a filter is applicable to the given column.\n */\nexport function validateFilter(\n\tfilter: FilterCondition,\n\tcolumnInfo: ColumnInfo,\n): { valid: boolean; error?: string } {\n\t// Validate operator compatibility with column type\n\tconst typeCompatibility: Record<string, FilterOperator[]> = {\n\t\tstring: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Like,\n\t\t\tFilterOperator.Ilike,\n\t\t\tFilterOperator.In,\n\t\t\tFilterOperator.Nin,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tnumber: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Gt,\n\t\t\tFilterOperator.Gte,\n\t\t\tFilterOperator.Lt,\n\t\t\tFilterOperator.Lte,\n\t\t\tFilterOperator.In,\n\t\t\tFilterOperator.Nin,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tboolean: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tdate: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Gt,\n\t\t\tFilterOperator.Gte,\n\t\t\tFilterOperator.Lt,\n\t\t\tFilterOperator.Lte,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tdatetime: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Gt,\n\t\t\tFilterOperator.Gte,\n\t\t\tFilterOperator.Lt,\n\t\t\tFilterOperator.Lte,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tuuid: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.In,\n\t\t\tFilterOperator.Nin,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tjson: [FilterOperator.IsNull, FilterOperator.IsNotNull],\n\t\tbinary: [FilterOperator.IsNull, FilterOperator.IsNotNull],\n\t\tunknown: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t};\n\n\tconst allowedOps =\n\t\ttypeCompatibility[columnInfo.type] ?? typeCompatibility.unknown ?? [];\n\n\tif (!allowedOps.includes(filter.operator)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `Operator '${filter.operator}' not supported for column type '${columnInfo.type}'`,\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Applies filters to a Kysely query builder.\n */\nexport function applyFilters<DB, TB extends keyof DB, O>(\n\tquery: SelectQueryBuilder<DB, TB, O>,\n\tfilters: FilterCondition[],\n\ttableInfo: TableInfo,\n): SelectQueryBuilder<DB, TB, O> {\n\tlet result = query;\n\n\tfor (const filter of filters) {\n\t\tconst column = tableInfo.columns.find((c) => c.name === filter.column);\n\n\t\tif (!column) {\n\t\t\tthrow new Error(\n\t\t\t\t`Column '${filter.column}' not found in table '${tableInfo.name}'`,\n\t\t\t);\n\t\t}\n\n\t\tconst validation = validateFilter(filter, column);\n\t\tif (!validation.valid) {\n\t\t\tthrow new Error(validation.error);\n\t\t}\n\n\t\tresult = applyFilterCondition(result, filter);\n\t}\n\n\treturn result;\n}\n\nfunction applyFilterCondition<DB, TB extends keyof DB, O>(\n\tquery: SelectQueryBuilder<DB, TB, O>,\n\tfilter: FilterCondition,\n): SelectQueryBuilder<DB, TB, O> {\n\tconst { column, operator, value } = filter;\n\n\tswitch (operator) {\n\t\tcase FilterOperator.Eq:\n\t\t\treturn query.where(column as any, '=', value);\n\t\tcase FilterOperator.Neq:\n\t\t\treturn query.where(column as any, '!=', value);\n\t\tcase FilterOperator.Gt:\n\t\t\treturn query.where(column as any, '>', value);\n\t\tcase FilterOperator.Gte:\n\t\t\treturn query.where(column as any, '>=', value);\n\t\tcase FilterOperator.Lt:\n\t\t\treturn query.where(column as any, '<', value);\n\t\tcase FilterOperator.Lte:\n\t\t\treturn query.where(column as any, '<=', value);\n\t\tcase FilterOperator.Like:\n\t\t\treturn query.where(column as any, 'like', value);\n\t\tcase FilterOperator.Ilike:\n\t\t\treturn query.where(column as any, 'ilike', value);\n\t\tcase FilterOperator.In:\n\t\t\treturn query.where(column as any, 'in', value as any[]);\n\t\tcase FilterOperator.Nin:\n\t\t\treturn query.where(column as any, 'not in', value as any[]);\n\t\tcase FilterOperator.IsNull:\n\t\t\treturn query.where(column as any, 'is', null);\n\t\tcase FilterOperator.IsNotNull:\n\t\t\treturn query.where(column as any, 'is not', null);\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown filter operator: ${operator}`);\n\t}\n}\n\n/**\n * Applies sorting to a Kysely query builder.\n */\nexport function applySorting<DB, TB extends keyof DB, O>(\n\tquery: SelectQueryBuilder<DB, TB, O>,\n\tsorts: SortConfig[],\n\ttableInfo: TableInfo,\n): SelectQueryBuilder<DB, TB, O> {\n\tlet result = query;\n\n\tfor (const sort of sorts) {\n\t\tconst column = tableInfo.columns.find((c) => c.name === sort.column);\n\n\t\tif (!column) {\n\t\t\tthrow new Error(\n\t\t\t\t`Column '${sort.column}' not found in table '${tableInfo.name}'`,\n\t\t\t);\n\t\t}\n\n\t\tresult = result.orderBy(\n\t\t\tsort.column as any,\n\t\t\tsort.direction === Direction.Asc ? 'asc' : 'desc',\n\t\t);\n\t}\n\n\treturn 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\tdb: Kysely<DB>,\n\texcludeTables: string[],\n): Promise<SchemaInfo> {\n\t// Query tables from information_schema\n\tconst excludePlaceholders =\n\t\texcludeTables.length > 0\n\t\t\t? excludeTables.map((_, i) => `$${i + 1}`).join(', ')\n\t\t\t: \"''\";\n\n\tconst 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\tconst tablesResult = await db.executeQuery({\n\t\tsql: tablesQuery,\n\t\tparameters: excludeTables,\n\t} as any);\n\n\tconst tables: TableInfo[] = [];\n\n\tfor (const row of tablesResult.rows as any[]) {\n\t\t// Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n\t\tconst tableName = row.table_name ?? row.tableName;\n\t\tconst tableSchema = row.table_schema ?? row.tableSchema;\n\t\tconst tableInfo = await introspectTable(db, tableName, tableSchema);\n\t\ttables.push(tableInfo);\n\t}\n\n\treturn {\n\t\ttables,\n\t\tupdatedAt: new Date(),\n\t};\n}\n\n/**\n * Introspects a single table to get column information.\n */\nexport async function introspectTable<DB>(\n\tdb: Kysely<DB>,\n\ttableName: string,\n\tschema = 'public',\n): Promise<TableInfo> {\n\t// Query columns\n\tconst 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\tconst columnsResult = await db.executeQuery({\n\t\tsql: columnsQuery,\n\t\tparameters: [tableName, schema],\n\t} as any);\n\n\t// Query foreign keys\n\tconst 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\tconst fkResult = await db.executeQuery({\n\t\tsql: fkQuery,\n\t\tparameters: [tableName, schema],\n\t} as any);\n\n\tconst foreignKeys = new Map<string, { table: string; column: string }>();\n\tfor (const row of fkResult.rows as any[]) {\n\t\t// Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n\t\tconst colName = row.column_name ?? row.columnName;\n\t\tforeignKeys.set(colName, {\n\t\t\ttable: row.foreign_table ?? row.foreignTable,\n\t\t\tcolumn: row.foreign_column ?? row.foreignColumn,\n\t\t});\n\t}\n\n\tconst columns: ColumnInfo[] = (columnsResult.rows as any[]).map((row) => {\n\t\t// Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n\t\tconst colName = row.column_name ?? row.columnName;\n\t\tconst udtName = row.udt_name ?? row.udtName;\n\t\tconst isNullable = row.is_nullable ?? row.isNullable;\n\t\tconst isPrimaryKey = row.is_primary_key ?? row.isPrimaryKey;\n\t\tconst columnDefault = row.column_default ?? row.columnDefault;\n\n\t\tconst fk = foreignKeys.get(colName);\n\t\treturn {\n\t\t\tname: colName,\n\t\t\ttype: mapPostgresType(udtName),\n\t\t\trawType: udtName,\n\t\t\tnullable: isNullable === 'YES',\n\t\t\tisPrimaryKey: isPrimaryKey,\n\t\t\tisForeignKey: !!fk,\n\t\t\tforeignKeyTable: fk?.table,\n\t\t\tforeignKeyColumn: fk?.column,\n\t\t\tdefaultValue: columnDefault ?? undefined,\n\t\t};\n\t});\n\n\tconst primaryKey = columns.filter((c) => c.isPrimaryKey).map((c) => c.name);\n\n\t// Get estimated row count\n\tconst countQuery = `\n SELECT reltuples::bigint AS estimate\n FROM pg_class\n WHERE relname = $1\n `;\n\n\tlet estimatedRowCount: number | undefined;\n\ttry {\n\t\tconst countResult = await db.executeQuery({\n\t\t\tsql: countQuery,\n\t\t\tparameters: [tableName],\n\t\t} as any);\n\t\tif (countResult.rows.length > 0) {\n\t\t\tconst estimate = (countResult.rows[0] as any).estimate;\n\t\t\testimatedRowCount = estimate > 0 ? Number(estimate) : undefined;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, row count is optional\n\t}\n\n\treturn {\n\t\tname: tableName,\n\t\tschema,\n\t\tcolumns,\n\t\tprimaryKey,\n\t\testimatedRowCount,\n\t};\n}\n\n/**\n * Maps PostgreSQL types to generic column types.\n */\nfunction mapPostgresType(udtName: string): ColumnType {\n\tconst typeMap: Record<string, ColumnType> = {\n\t\t// Strings\n\t\tvarchar: 'string',\n\t\tchar: 'string',\n\t\ttext: 'string',\n\t\tname: 'string',\n\t\tbpchar: 'string',\n\n\t\t// Numbers\n\t\tint2: 'number',\n\t\tint4: 'number',\n\t\tint8: 'number',\n\t\tfloat4: 'number',\n\t\tfloat8: 'number',\n\t\tnumeric: 'number',\n\t\tmoney: 'number',\n\t\tserial: 'number',\n\t\tbigserial: 'number',\n\n\t\t// Boolean\n\t\tbool: 'boolean',\n\n\t\t// Dates\n\t\tdate: 'date',\n\t\ttimestamp: 'datetime',\n\t\ttimestamptz: 'datetime',\n\t\ttime: 'datetime',\n\t\ttimetz: 'datetime',\n\n\t\t// JSON\n\t\tjson: 'json',\n\t\tjsonb: 'json',\n\n\t\t// Binary\n\t\tbytea: 'binary',\n\n\t\t// UUID\n\t\tuuid: 'uuid',\n\t};\n\n\treturn 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\tconst payload = {\n\t\tv: value instanceof Date ? value.toISOString() : value,\n\t\tt: value instanceof Date ? 'date' : typeof value,\n\t};\n\treturn 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\ttry {\n\t\tconst json = Buffer.from(cursor, 'base64url').toString('utf-8');\n\t\tconst payload = JSON.parse(json);\n\n\t\tif (payload.t === 'date') {\n\t\t\treturn new Date(payload.v);\n\t\t}\n\n\t\treturn payload.v;\n\t} catch {\n\t\tthrow new Error('Invalid cursor format');\n\t}\n}\n","import type { Kysely, SelectQueryBuilder } from 'kysely';\nimport {\n\ttype CursorConfig,\n\ttype DataBrowserOptions,\n\tDirection,\n\ttype QueryOptions,\n\ttype QueryResult,\n\ttype SchemaInfo,\n\ttype 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\tprivate db: Kysely<DB>;\n\tprivate options: Required<DataBrowserOptions<DB>>;\n\tprivate schemaCache: SchemaInfo | null = null;\n\tprivate schemaCacheExpiry = 0;\n\tprivate readonly CACHE_TTL_MS = 60 * 1000; // 1 minute\n\n\tconstructor(options: Required<DataBrowserOptions<DB>>) {\n\t\tthis.db = options.db;\n\t\tthis.options = options;\n\t}\n\n\t// ============================================\n\t// Schema Introspection\n\t// ============================================\n\n\t/**\n\t * Get the database schema information.\n\t * Results are cached for 1 minute.\n\t *\n\t * @param forceRefresh - Force a refresh of the cache\n\t */\n\tasync getSchema(forceRefresh = false): Promise<SchemaInfo> {\n\t\tconst now = Date.now();\n\n\t\tif (!forceRefresh && this.schemaCache && now < this.schemaCacheExpiry) {\n\t\t\treturn this.schemaCache;\n\t\t}\n\n\t\tconst schema = await introspectSchema(this.db, this.options.excludeTables);\n\n\t\tthis.schemaCache = schema;\n\t\tthis.schemaCacheExpiry = now + this.CACHE_TTL_MS;\n\n\t\treturn schema;\n\t}\n\n\t/**\n\t * Get information about a specific table.\n\t */\n\tasync getTableInfo(tableName: string): Promise<TableInfo | null> {\n\t\tconst schema = await this.getSchema();\n\t\treturn schema.tables.find((t) => t.name === tableName) ?? null;\n\t}\n\n\t// ============================================\n\t// Data Querying\n\t// ============================================\n\n\t/**\n\t * Query table data with pagination, filtering, and sorting.\n\t */\n\tasync query(options: QueryOptions): Promise<QueryResult> {\n\t\tconst tableInfo = await this.getTableInfo(options.table);\n\n\t\tif (!tableInfo) {\n\t\t\tthrow new Error(`Table '${options.table}' not found`);\n\t\t}\n\n\t\tconst cursorConfig = this.getCursorConfig(options.table);\n\t\tconst pageSize = Math.min(\n\t\t\toptions.pageSize ?? this.options.defaultPageSize,\n\t\t\t100,\n\t\t);\n\n\t\t// Build base query selecting all columns\n\t\tlet query = this.db\n\t\t\t.selectFrom(options.table as any)\n\t\t\t.selectAll() as SelectQueryBuilder<any, any, any>;\n\n\t\t// Apply filters if provided\n\t\tif (options.filters && options.filters.length > 0) {\n\t\t\tquery = applyFilters(query, options.filters, tableInfo);\n\t\t}\n\n\t\t// Apply sorting if provided, otherwise use cursor field\n\t\tif (options.sort && options.sort.length > 0) {\n\t\t\tquery = applySorting(query, options.sort, tableInfo);\n\t\t} else {\n\t\t\tquery = query.orderBy(cursorConfig.field as any, cursorConfig.direction);\n\t\t}\n\n\t\t// Handle cursor-based pagination\n\t\tif (options.cursor) {\n\t\t\tconst cursorValue = decodeCursor(options.cursor);\n\t\t\tconst operator = cursorConfig.direction === Direction.Asc ? '>' : '<';\n\t\t\tquery = query.where(cursorConfig.field as any, operator, cursorValue);\n\t\t}\n\n\t\t// Fetch one extra row to determine if there are more results\n\t\tconst rows = await query.limit(pageSize + 1).execute();\n\n\t\tconst hasMore = rows.length > pageSize;\n\t\tconst resultRows = hasMore ? rows.slice(0, pageSize) : rows;\n\n\t\t// Generate cursors\n\t\tlet nextCursor: string | null = null;\n\t\tlet prevCursor: string | null = null;\n\n\t\tif (hasMore && resultRows.length > 0) {\n\t\t\tconst lastRow = resultRows[resultRows.length - 1];\n\t\t\tif (lastRow) {\n\t\t\t\tnextCursor = encodeCursor(lastRow[cursorConfig.field]);\n\t\t\t}\n\t\t}\n\n\t\t// For prev cursor, we need to know if there are previous results\n\t\t// This would require a separate query, so we'll only set it if there's an input cursor\n\t\tif (options.cursor && resultRows.length > 0) {\n\t\t\tconst firstRow = resultRows[0];\n\t\t\tif (firstRow) {\n\t\t\t\tprevCursor = encodeCursor(firstRow[cursorConfig.field]);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\trows: resultRows,\n\t\t\thasMore,\n\t\t\tnextCursor,\n\t\t\tprevCursor,\n\t\t};\n\t}\n\n\t// ============================================\n\t// Configuration Access\n\t// ============================================\n\n\t/**\n\t * Get the cursor configuration for a table.\n\t * Returns the table-specific config if defined, otherwise the default.\n\t */\n\tgetCursorConfig(tableName: string): CursorConfig {\n\t\treturn this.options.tableCursors[tableName] ?? this.options.cursor;\n\t}\n\n\t/**\n\t * Get the underlying Kysely database instance.\n\t */\n\tget database(): Kysely<DB> {\n\t\treturn this.db;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -71,7 +71,7 @@ function validateFilter(filter, columnInfo) {
|
|
|
71
71
|
FilterOperator.IsNotNull
|
|
72
72
|
]
|
|
73
73
|
};
|
|
74
|
-
const allowedOps = typeCompatibility[columnInfo.type] ?? typeCompatibility.unknown;
|
|
74
|
+
const allowedOps = typeCompatibility[columnInfo.type] ?? typeCompatibility.unknown ?? [];
|
|
75
75
|
if (!allowedOps.includes(filter.operator)) return {
|
|
76
76
|
valid: false,
|
|
77
77
|
error: `Operator '${filter.operator}' not supported for column type '${columnInfo.type}'`
|
|
@@ -394,11 +394,11 @@ var DataBrowser = class {
|
|
|
394
394
|
let prevCursor = null;
|
|
395
395
|
if (hasMore && resultRows.length > 0) {
|
|
396
396
|
const lastRow = resultRows[resultRows.length - 1];
|
|
397
|
-
nextCursor = encodeCursor(lastRow[cursorConfig.field]);
|
|
397
|
+
if (lastRow) nextCursor = encodeCursor(lastRow[cursorConfig.field]);
|
|
398
398
|
}
|
|
399
399
|
if (options.cursor && resultRows.length > 0) {
|
|
400
400
|
const firstRow = resultRows[0];
|
|
401
|
-
prevCursor = encodeCursor(firstRow[cursorConfig.field]);
|
|
401
|
+
if (firstRow) prevCursor = encodeCursor(firstRow[cursorConfig.field]);
|
|
402
402
|
}
|
|
403
403
|
return {
|
|
404
404
|
rows: resultRows,
|
|
@@ -424,4 +424,4 @@ var DataBrowser = class {
|
|
|
424
424
|
|
|
425
425
|
//#endregion
|
|
426
426
|
export { DataBrowser };
|
|
427
|
-
//# sourceMappingURL=DataBrowser-
|
|
427
|
+
//# sourceMappingURL=DataBrowser-kgcI9ApJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataBrowser-kgcI9ApJ.mjs","names":["filter: FilterCondition","columnInfo: ColumnInfo","typeCompatibility: Record<string, FilterOperator[]>","query: SelectQueryBuilder<DB, TB, O>","filters: FilterCondition[]","tableInfo: TableInfo","sorts: SortConfig[]","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","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\ttype ColumnInfo,\n\tDirection,\n\ttype FilterCondition,\n\tFilterOperator,\n\ttype SortConfig,\n\ttype TableInfo,\n} from '../types';\n\n/**\n * Validates that a filter is applicable to the given column.\n */\nexport function validateFilter(\n\tfilter: FilterCondition,\n\tcolumnInfo: ColumnInfo,\n): { valid: boolean; error?: string } {\n\t// Validate operator compatibility with column type\n\tconst typeCompatibility: Record<string, FilterOperator[]> = {\n\t\tstring: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Like,\n\t\t\tFilterOperator.Ilike,\n\t\t\tFilterOperator.In,\n\t\t\tFilterOperator.Nin,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tnumber: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Gt,\n\t\t\tFilterOperator.Gte,\n\t\t\tFilterOperator.Lt,\n\t\t\tFilterOperator.Lte,\n\t\t\tFilterOperator.In,\n\t\t\tFilterOperator.Nin,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tboolean: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tdate: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Gt,\n\t\t\tFilterOperator.Gte,\n\t\t\tFilterOperator.Lt,\n\t\t\tFilterOperator.Lte,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tdatetime: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.Gt,\n\t\t\tFilterOperator.Gte,\n\t\t\tFilterOperator.Lt,\n\t\t\tFilterOperator.Lte,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tuuid: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.In,\n\t\t\tFilterOperator.Nin,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t\tjson: [FilterOperator.IsNull, FilterOperator.IsNotNull],\n\t\tbinary: [FilterOperator.IsNull, FilterOperator.IsNotNull],\n\t\tunknown: [\n\t\t\tFilterOperator.Eq,\n\t\t\tFilterOperator.Neq,\n\t\t\tFilterOperator.IsNull,\n\t\t\tFilterOperator.IsNotNull,\n\t\t],\n\t};\n\n\tconst allowedOps =\n\t\ttypeCompatibility[columnInfo.type] ?? typeCompatibility.unknown ?? [];\n\n\tif (!allowedOps.includes(filter.operator)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `Operator '${filter.operator}' not supported for column type '${columnInfo.type}'`,\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Applies filters to a Kysely query builder.\n */\nexport function applyFilters<DB, TB extends keyof DB, O>(\n\tquery: SelectQueryBuilder<DB, TB, O>,\n\tfilters: FilterCondition[],\n\ttableInfo: TableInfo,\n): SelectQueryBuilder<DB, TB, O> {\n\tlet result = query;\n\n\tfor (const filter of filters) {\n\t\tconst column = tableInfo.columns.find((c) => c.name === filter.column);\n\n\t\tif (!column) {\n\t\t\tthrow new Error(\n\t\t\t\t`Column '${filter.column}' not found in table '${tableInfo.name}'`,\n\t\t\t);\n\t\t}\n\n\t\tconst validation = validateFilter(filter, column);\n\t\tif (!validation.valid) {\n\t\t\tthrow new Error(validation.error);\n\t\t}\n\n\t\tresult = applyFilterCondition(result, filter);\n\t}\n\n\treturn result;\n}\n\nfunction applyFilterCondition<DB, TB extends keyof DB, O>(\n\tquery: SelectQueryBuilder<DB, TB, O>,\n\tfilter: FilterCondition,\n): SelectQueryBuilder<DB, TB, O> {\n\tconst { column, operator, value } = filter;\n\n\tswitch (operator) {\n\t\tcase FilterOperator.Eq:\n\t\t\treturn query.where(column as any, '=', value);\n\t\tcase FilterOperator.Neq:\n\t\t\treturn query.where(column as any, '!=', value);\n\t\tcase FilterOperator.Gt:\n\t\t\treturn query.where(column as any, '>', value);\n\t\tcase FilterOperator.Gte:\n\t\t\treturn query.where(column as any, '>=', value);\n\t\tcase FilterOperator.Lt:\n\t\t\treturn query.where(column as any, '<', value);\n\t\tcase FilterOperator.Lte:\n\t\t\treturn query.where(column as any, '<=', value);\n\t\tcase FilterOperator.Like:\n\t\t\treturn query.where(column as any, 'like', value);\n\t\tcase FilterOperator.Ilike:\n\t\t\treturn query.where(column as any, 'ilike', value);\n\t\tcase FilterOperator.In:\n\t\t\treturn query.where(column as any, 'in', value as any[]);\n\t\tcase FilterOperator.Nin:\n\t\t\treturn query.where(column as any, 'not in', value as any[]);\n\t\tcase FilterOperator.IsNull:\n\t\t\treturn query.where(column as any, 'is', null);\n\t\tcase FilterOperator.IsNotNull:\n\t\t\treturn query.where(column as any, 'is not', null);\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown filter operator: ${operator}`);\n\t}\n}\n\n/**\n * Applies sorting to a Kysely query builder.\n */\nexport function applySorting<DB, TB extends keyof DB, O>(\n\tquery: SelectQueryBuilder<DB, TB, O>,\n\tsorts: SortConfig[],\n\ttableInfo: TableInfo,\n): SelectQueryBuilder<DB, TB, O> {\n\tlet result = query;\n\n\tfor (const sort of sorts) {\n\t\tconst column = tableInfo.columns.find((c) => c.name === sort.column);\n\n\t\tif (!column) {\n\t\t\tthrow new Error(\n\t\t\t\t`Column '${sort.column}' not found in table '${tableInfo.name}'`,\n\t\t\t);\n\t\t}\n\n\t\tresult = result.orderBy(\n\t\t\tsort.column as any,\n\t\t\tsort.direction === Direction.Asc ? 'asc' : 'desc',\n\t\t);\n\t}\n\n\treturn 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\tdb: Kysely<DB>,\n\texcludeTables: string[],\n): Promise<SchemaInfo> {\n\t// Query tables from information_schema\n\tconst excludePlaceholders =\n\t\texcludeTables.length > 0\n\t\t\t? excludeTables.map((_, i) => `$${i + 1}`).join(', ')\n\t\t\t: \"''\";\n\n\tconst 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\tconst tablesResult = await db.executeQuery({\n\t\tsql: tablesQuery,\n\t\tparameters: excludeTables,\n\t} as any);\n\n\tconst tables: TableInfo[] = [];\n\n\tfor (const row of tablesResult.rows as any[]) {\n\t\t// Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n\t\tconst tableName = row.table_name ?? row.tableName;\n\t\tconst tableSchema = row.table_schema ?? row.tableSchema;\n\t\tconst tableInfo = await introspectTable(db, tableName, tableSchema);\n\t\ttables.push(tableInfo);\n\t}\n\n\treturn {\n\t\ttables,\n\t\tupdatedAt: new Date(),\n\t};\n}\n\n/**\n * Introspects a single table to get column information.\n */\nexport async function introspectTable<DB>(\n\tdb: Kysely<DB>,\n\ttableName: string,\n\tschema = 'public',\n): Promise<TableInfo> {\n\t// Query columns\n\tconst 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\tconst columnsResult = await db.executeQuery({\n\t\tsql: columnsQuery,\n\t\tparameters: [tableName, schema],\n\t} as any);\n\n\t// Query foreign keys\n\tconst 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\tconst fkResult = await db.executeQuery({\n\t\tsql: fkQuery,\n\t\tparameters: [tableName, schema],\n\t} as any);\n\n\tconst foreignKeys = new Map<string, { table: string; column: string }>();\n\tfor (const row of fkResult.rows as any[]) {\n\t\t// Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n\t\tconst colName = row.column_name ?? row.columnName;\n\t\tforeignKeys.set(colName, {\n\t\t\ttable: row.foreign_table ?? row.foreignTable,\n\t\t\tcolumn: row.foreign_column ?? row.foreignColumn,\n\t\t});\n\t}\n\n\tconst columns: ColumnInfo[] = (columnsResult.rows as any[]).map((row) => {\n\t\t// Support both snake_case (raw) and camelCase (with CamelCasePlugin)\n\t\tconst colName = row.column_name ?? row.columnName;\n\t\tconst udtName = row.udt_name ?? row.udtName;\n\t\tconst isNullable = row.is_nullable ?? row.isNullable;\n\t\tconst isPrimaryKey = row.is_primary_key ?? row.isPrimaryKey;\n\t\tconst columnDefault = row.column_default ?? row.columnDefault;\n\n\t\tconst fk = foreignKeys.get(colName);\n\t\treturn {\n\t\t\tname: colName,\n\t\t\ttype: mapPostgresType(udtName),\n\t\t\trawType: udtName,\n\t\t\tnullable: isNullable === 'YES',\n\t\t\tisPrimaryKey: isPrimaryKey,\n\t\t\tisForeignKey: !!fk,\n\t\t\tforeignKeyTable: fk?.table,\n\t\t\tforeignKeyColumn: fk?.column,\n\t\t\tdefaultValue: columnDefault ?? undefined,\n\t\t};\n\t});\n\n\tconst primaryKey = columns.filter((c) => c.isPrimaryKey).map((c) => c.name);\n\n\t// Get estimated row count\n\tconst countQuery = `\n SELECT reltuples::bigint AS estimate\n FROM pg_class\n WHERE relname = $1\n `;\n\n\tlet estimatedRowCount: number | undefined;\n\ttry {\n\t\tconst countResult = await db.executeQuery({\n\t\t\tsql: countQuery,\n\t\t\tparameters: [tableName],\n\t\t} as any);\n\t\tif (countResult.rows.length > 0) {\n\t\t\tconst estimate = (countResult.rows[0] as any).estimate;\n\t\t\testimatedRowCount = estimate > 0 ? Number(estimate) : undefined;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, row count is optional\n\t}\n\n\treturn {\n\t\tname: tableName,\n\t\tschema,\n\t\tcolumns,\n\t\tprimaryKey,\n\t\testimatedRowCount,\n\t};\n}\n\n/**\n * Maps PostgreSQL types to generic column types.\n */\nfunction mapPostgresType(udtName: string): ColumnType {\n\tconst typeMap: Record<string, ColumnType> = {\n\t\t// Strings\n\t\tvarchar: 'string',\n\t\tchar: 'string',\n\t\ttext: 'string',\n\t\tname: 'string',\n\t\tbpchar: 'string',\n\n\t\t// Numbers\n\t\tint2: 'number',\n\t\tint4: 'number',\n\t\tint8: 'number',\n\t\tfloat4: 'number',\n\t\tfloat8: 'number',\n\t\tnumeric: 'number',\n\t\tmoney: 'number',\n\t\tserial: 'number',\n\t\tbigserial: 'number',\n\n\t\t// Boolean\n\t\tbool: 'boolean',\n\n\t\t// Dates\n\t\tdate: 'date',\n\t\ttimestamp: 'datetime',\n\t\ttimestamptz: 'datetime',\n\t\ttime: 'datetime',\n\t\ttimetz: 'datetime',\n\n\t\t// JSON\n\t\tjson: 'json',\n\t\tjsonb: 'json',\n\n\t\t// Binary\n\t\tbytea: 'binary',\n\n\t\t// UUID\n\t\tuuid: 'uuid',\n\t};\n\n\treturn 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\tconst payload = {\n\t\tv: value instanceof Date ? value.toISOString() : value,\n\t\tt: value instanceof Date ? 'date' : typeof value,\n\t};\n\treturn 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\ttry {\n\t\tconst json = Buffer.from(cursor, 'base64url').toString('utf-8');\n\t\tconst payload = JSON.parse(json);\n\n\t\tif (payload.t === 'date') {\n\t\t\treturn new Date(payload.v);\n\t\t}\n\n\t\treturn payload.v;\n\t} catch {\n\t\tthrow new Error('Invalid cursor format');\n\t}\n}\n","import type { Kysely, SelectQueryBuilder } from 'kysely';\nimport {\n\ttype CursorConfig,\n\ttype DataBrowserOptions,\n\tDirection,\n\ttype QueryOptions,\n\ttype QueryResult,\n\ttype SchemaInfo,\n\ttype 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\tprivate db: Kysely<DB>;\n\tprivate options: Required<DataBrowserOptions<DB>>;\n\tprivate schemaCache: SchemaInfo | null = null;\n\tprivate schemaCacheExpiry = 0;\n\tprivate readonly CACHE_TTL_MS = 60 * 1000; // 1 minute\n\n\tconstructor(options: Required<DataBrowserOptions<DB>>) {\n\t\tthis.db = options.db;\n\t\tthis.options = options;\n\t}\n\n\t// ============================================\n\t// Schema Introspection\n\t// ============================================\n\n\t/**\n\t * Get the database schema information.\n\t * Results are cached for 1 minute.\n\t *\n\t * @param forceRefresh - Force a refresh of the cache\n\t */\n\tasync getSchema(forceRefresh = false): Promise<SchemaInfo> {\n\t\tconst now = Date.now();\n\n\t\tif (!forceRefresh && this.schemaCache && now < this.schemaCacheExpiry) {\n\t\t\treturn this.schemaCache;\n\t\t}\n\n\t\tconst schema = await introspectSchema(this.db, this.options.excludeTables);\n\n\t\tthis.schemaCache = schema;\n\t\tthis.schemaCacheExpiry = now + this.CACHE_TTL_MS;\n\n\t\treturn schema;\n\t}\n\n\t/**\n\t * Get information about a specific table.\n\t */\n\tasync getTableInfo(tableName: string): Promise<TableInfo | null> {\n\t\tconst schema = await this.getSchema();\n\t\treturn schema.tables.find((t) => t.name === tableName) ?? null;\n\t}\n\n\t// ============================================\n\t// Data Querying\n\t// ============================================\n\n\t/**\n\t * Query table data with pagination, filtering, and sorting.\n\t */\n\tasync query(options: QueryOptions): Promise<QueryResult> {\n\t\tconst tableInfo = await this.getTableInfo(options.table);\n\n\t\tif (!tableInfo) {\n\t\t\tthrow new Error(`Table '${options.table}' not found`);\n\t\t}\n\n\t\tconst cursorConfig = this.getCursorConfig(options.table);\n\t\tconst pageSize = Math.min(\n\t\t\toptions.pageSize ?? this.options.defaultPageSize,\n\t\t\t100,\n\t\t);\n\n\t\t// Build base query selecting all columns\n\t\tlet query = this.db\n\t\t\t.selectFrom(options.table as any)\n\t\t\t.selectAll() as SelectQueryBuilder<any, any, any>;\n\n\t\t// Apply filters if provided\n\t\tif (options.filters && options.filters.length > 0) {\n\t\t\tquery = applyFilters(query, options.filters, tableInfo);\n\t\t}\n\n\t\t// Apply sorting if provided, otherwise use cursor field\n\t\tif (options.sort && options.sort.length > 0) {\n\t\t\tquery = applySorting(query, options.sort, tableInfo);\n\t\t} else {\n\t\t\tquery = query.orderBy(cursorConfig.field as any, cursorConfig.direction);\n\t\t}\n\n\t\t// Handle cursor-based pagination\n\t\tif (options.cursor) {\n\t\t\tconst cursorValue = decodeCursor(options.cursor);\n\t\t\tconst operator = cursorConfig.direction === Direction.Asc ? '>' : '<';\n\t\t\tquery = query.where(cursorConfig.field as any, operator, cursorValue);\n\t\t}\n\n\t\t// Fetch one extra row to determine if there are more results\n\t\tconst rows = await query.limit(pageSize + 1).execute();\n\n\t\tconst hasMore = rows.length > pageSize;\n\t\tconst resultRows = hasMore ? rows.slice(0, pageSize) : rows;\n\n\t\t// Generate cursors\n\t\tlet nextCursor: string | null = null;\n\t\tlet prevCursor: string | null = null;\n\n\t\tif (hasMore && resultRows.length > 0) {\n\t\t\tconst lastRow = resultRows[resultRows.length - 1];\n\t\t\tif (lastRow) {\n\t\t\t\tnextCursor = encodeCursor(lastRow[cursorConfig.field]);\n\t\t\t}\n\t\t}\n\n\t\t// For prev cursor, we need to know if there are previous results\n\t\t// This would require a separate query, so we'll only set it if there's an input cursor\n\t\tif (options.cursor && resultRows.length > 0) {\n\t\t\tconst firstRow = resultRows[0];\n\t\t\tif (firstRow) {\n\t\t\t\tprevCursor = encodeCursor(firstRow[cursorConfig.field]);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\trows: resultRows,\n\t\t\thasMore,\n\t\t\tnextCursor,\n\t\t\tprevCursor,\n\t\t};\n\t}\n\n\t// ============================================\n\t// Configuration Access\n\t// ============================================\n\n\t/**\n\t * Get the cursor configuration for a table.\n\t * Returns the table-specific config if defined, otherwise the default.\n\t */\n\tgetCursorConfig(tableName: string): CursorConfig {\n\t\treturn this.options.tableCursors[tableName] ?? this.options.cursor;\n\t}\n\n\t/**\n\t * Get the underlying Kysely database instance.\n\t */\n\tget database(): Kysely<DB> {\n\t\treturn this.db;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { DataBrowser, StudioEvent, StudioOptions } from "./DataBrowser-BTe9HWJy.cjs";
|
|
2
|
+
import { Telescope } from "@geekmidas/telescope";
|
|
3
|
+
|
|
4
|
+
//#region src/Studio.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Unified development tools dashboard combining monitoring and database browsing.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { Studio, Direction } from '@geekmidas/studio';
|
|
12
|
+
* import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
|
|
13
|
+
*
|
|
14
|
+
* const studio = new Studio({
|
|
15
|
+
* monitoring: {
|
|
16
|
+
* storage: new InMemoryStorage(),
|
|
17
|
+
* },
|
|
18
|
+
* data: {
|
|
19
|
+
* db: kyselyInstance,
|
|
20
|
+
* cursor: { field: 'id', direction: Direction.Desc },
|
|
21
|
+
* },
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare class Studio<DB = unknown> {
|
|
26
|
+
private telescope;
|
|
27
|
+
private dataBrowser;
|
|
28
|
+
private options;
|
|
29
|
+
private wsClients;
|
|
30
|
+
constructor(options: StudioOptions<DB>);
|
|
31
|
+
/**
|
|
32
|
+
* Get the Studio dashboard path.
|
|
33
|
+
*/
|
|
34
|
+
get path(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Check if Studio is enabled.
|
|
37
|
+
*/
|
|
38
|
+
get enabled(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Get the data browser instance.
|
|
41
|
+
*/
|
|
42
|
+
get data(): DataBrowser<DB>;
|
|
43
|
+
/**
|
|
44
|
+
* Check if body recording is enabled for monitoring.
|
|
45
|
+
*/
|
|
46
|
+
get recordBody(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Get max body size for monitoring.
|
|
49
|
+
*/
|
|
50
|
+
get maxBodySize(): number;
|
|
51
|
+
/**
|
|
52
|
+
* Record a request entry.
|
|
53
|
+
*/
|
|
54
|
+
recordRequest(entry: Parameters<Telescope['recordRequest']>[0]): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Record log entries in batch.
|
|
57
|
+
*/
|
|
58
|
+
log(entries: Parameters<Telescope['log']>[0]): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Log a debug message.
|
|
61
|
+
*/
|
|
62
|
+
debug(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Log an info message.
|
|
65
|
+
*/
|
|
66
|
+
info(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Log a warning message.
|
|
69
|
+
*/
|
|
70
|
+
warn(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Log an error message.
|
|
73
|
+
*/
|
|
74
|
+
error(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Record an exception.
|
|
77
|
+
*/
|
|
78
|
+
exception(error: Error, requestId?: string): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Get requests from storage.
|
|
81
|
+
*/
|
|
82
|
+
getRequests(options?: Parameters<Telescope['getRequests']>[0]): ReturnType<Telescope['getRequests']>;
|
|
83
|
+
/**
|
|
84
|
+
* Get a single request by ID.
|
|
85
|
+
*/
|
|
86
|
+
getRequest(id: string): ReturnType<Telescope['getRequest']>;
|
|
87
|
+
/**
|
|
88
|
+
* Get exceptions from storage.
|
|
89
|
+
*/
|
|
90
|
+
getExceptions(options?: Parameters<Telescope['getExceptions']>[0]): ReturnType<Telescope['getExceptions']>;
|
|
91
|
+
/**
|
|
92
|
+
* Get a single exception by ID.
|
|
93
|
+
*/
|
|
94
|
+
getException(id: string): ReturnType<Telescope['getException']>;
|
|
95
|
+
/**
|
|
96
|
+
* Get logs from storage.
|
|
97
|
+
*/
|
|
98
|
+
getLogs(options?: Parameters<Telescope['getLogs']>[0]): ReturnType<Telescope['getLogs']>;
|
|
99
|
+
/**
|
|
100
|
+
* Get storage statistics.
|
|
101
|
+
*/
|
|
102
|
+
getStats(): ReturnType<Telescope['getStats']>;
|
|
103
|
+
/**
|
|
104
|
+
* Get aggregated request metrics.
|
|
105
|
+
*/
|
|
106
|
+
getMetrics(options?: Parameters<Telescope['getMetrics']>[0]): ReturnType<Telescope['getMetrics']>;
|
|
107
|
+
/**
|
|
108
|
+
* Get metrics grouped by endpoint.
|
|
109
|
+
*/
|
|
110
|
+
getEndpointMetrics(options?: Parameters<Telescope['getEndpointMetrics']>[0]): ReturnType<Telescope['getEndpointMetrics']>;
|
|
111
|
+
/**
|
|
112
|
+
* Get detailed metrics for a specific endpoint.
|
|
113
|
+
*/
|
|
114
|
+
getEndpointDetails(method: string, path: string, options?: Parameters<Telescope['getEndpointDetails']>[2]): ReturnType<Telescope['getEndpointDetails']>;
|
|
115
|
+
/**
|
|
116
|
+
* Get HTTP status code distribution.
|
|
117
|
+
*/
|
|
118
|
+
getStatusDistribution(options?: Parameters<Telescope['getStatusDistribution']>[0]): ReturnType<Telescope['getStatusDistribution']>;
|
|
119
|
+
/**
|
|
120
|
+
* Reset all metrics.
|
|
121
|
+
*/
|
|
122
|
+
resetMetrics(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Add a WebSocket client for real-time updates.
|
|
125
|
+
*/
|
|
126
|
+
addWsClient(ws: WebSocket): void;
|
|
127
|
+
/**
|
|
128
|
+
* Remove a WebSocket client.
|
|
129
|
+
*/
|
|
130
|
+
removeWsClient(ws: WebSocket): void;
|
|
131
|
+
/**
|
|
132
|
+
* Broadcast an event to all connected WebSocket clients.
|
|
133
|
+
*/
|
|
134
|
+
broadcast(event: StudioEvent): void;
|
|
135
|
+
/**
|
|
136
|
+
* Check if a path should be ignored for request recording.
|
|
137
|
+
*/
|
|
138
|
+
shouldIgnore(path: string): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Manually prune old monitoring entries.
|
|
141
|
+
*/
|
|
142
|
+
prune(olderThan: Date): Promise<number>;
|
|
143
|
+
/**
|
|
144
|
+
* Clean up resources.
|
|
145
|
+
*/
|
|
146
|
+
destroy(): void;
|
|
147
|
+
private normalizeOptions;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=Studio.d.ts.map
|
|
150
|
+
//#endregion
|
|
151
|
+
export { Studio };
|
|
152
|
+
//# sourceMappingURL=Studio-CYzz3wD2.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Studio-CYzz3wD2.d.cts","names":[],"sources":["../src/Studio.ts"],"sourcesContent":[],"mappings":";;;;;;AA2BA;;;;;;;;;;;;;;;;;;AA4HY,cA5HC,MA4HD,CAAA,KAAA,OAAA,CAAA,CAAA;EAAM,QAEd,SAAA;EAAO,QAOa,WAAA;EAAK,QAAuB,OAAA;EAAO,QAQpC,SAAA;EAAS,WAApB,CAAA,OAAA,EAvIU,aAuIV,CAvIwB,EAuIxB,CAAA;EAAU;;;EAQ6B,IAApB,IAAA,CAAA,CAAA,EAAA,MAAA;EAAU;;;EASjB,IAApB,OAAA,CAAA,CAAA,EAAA,OAAA;EAAU;;;EAekB,IAApB,IAAA,CAAA,CAAA,EA7HC,WA6HD,CA7Ha,EA6Hb,CAAA;EAAU;;;EAQiB,IAApB,UAAA,CAAA,CAAA,EAAA,OAAA;EAAU;;;EAaL,IAApB,WAAA,CAAA,CAAA,EAAA,MAAA;EAAU;;;EASU,aAApB,CAAA,KAAA,EAjIK,UAiIL,CAjIgB,SAiIhB,CAAA,eAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAhIA,OAgIA,CAAA,MAAA,CAAA;EAAU;;;EAWU,GAApB,CAAA,OAAA,EApIgB,UAoIhB,CApI2B,SAoI3B,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EApIkD,OAoIlD,CAAA,IAAA,CAAA;EAAU;;;EASU,KAApB,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EApIQ,MAoIR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAlIA,OAkIA,CAAA,IAAA,CAAA;EAAU;;;EAmCe,IA6BL,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAzLZ,MAyLY,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAvLpB,OAuLoB,CAAA,IAAA,CAAA;EAAI;AAAU;;kCA9K1B,8CAER;;;;mCASQ,8CAER;;;;mBAOoB,4BAA4B;;;;wBAQxC,WAAW,+BACnB,WAAW;;;;0BAOgB,WAAW;;;;0BAQ9B,WAAW,iCACnB,WAAW;;;;4BAOkB,WAAW;;;;oBAQhC,WAAW,2BACnB,WAAW;;;;cAOI,WAAW;;;;uBAYlB,WAAW,8BACnB,WAAW;;;;+BAQH,WAAW,sCACnB,WAAW;;;;6DAUH,WAAW,sCACnB,WAAW;;;;kCAQH,WAAW,yCACnB,WAAW;;;;;;;;kBAkBE;;;;qBASG;;;;mBAQF;;;;;;;;mBA6BM,OAAO"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { DataBrowser, StudioEvent, StudioOptions } from "./DataBrowser-B-jz8KBR.mjs";
|
|
2
|
+
import { Telescope } from "@geekmidas/telescope";
|
|
3
|
+
|
|
4
|
+
//#region src/Studio.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Unified development tools dashboard combining monitoring and database browsing.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { Studio, Direction } from '@geekmidas/studio';
|
|
12
|
+
* import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
|
|
13
|
+
*
|
|
14
|
+
* const studio = new Studio({
|
|
15
|
+
* monitoring: {
|
|
16
|
+
* storage: new InMemoryStorage(),
|
|
17
|
+
* },
|
|
18
|
+
* data: {
|
|
19
|
+
* db: kyselyInstance,
|
|
20
|
+
* cursor: { field: 'id', direction: Direction.Desc },
|
|
21
|
+
* },
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare class Studio<DB = unknown> {
|
|
26
|
+
private telescope;
|
|
27
|
+
private dataBrowser;
|
|
28
|
+
private options;
|
|
29
|
+
private wsClients;
|
|
30
|
+
constructor(options: StudioOptions<DB>);
|
|
31
|
+
/**
|
|
32
|
+
* Get the Studio dashboard path.
|
|
33
|
+
*/
|
|
34
|
+
get path(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Check if Studio is enabled.
|
|
37
|
+
*/
|
|
38
|
+
get enabled(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Get the data browser instance.
|
|
41
|
+
*/
|
|
42
|
+
get data(): DataBrowser<DB>;
|
|
43
|
+
/**
|
|
44
|
+
* Check if body recording is enabled for monitoring.
|
|
45
|
+
*/
|
|
46
|
+
get recordBody(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Get max body size for monitoring.
|
|
49
|
+
*/
|
|
50
|
+
get maxBodySize(): number;
|
|
51
|
+
/**
|
|
52
|
+
* Record a request entry.
|
|
53
|
+
*/
|
|
54
|
+
recordRequest(entry: Parameters<Telescope['recordRequest']>[0]): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Record log entries in batch.
|
|
57
|
+
*/
|
|
58
|
+
log(entries: Parameters<Telescope['log']>[0]): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Log a debug message.
|
|
61
|
+
*/
|
|
62
|
+
debug(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Log an info message.
|
|
65
|
+
*/
|
|
66
|
+
info(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Log a warning message.
|
|
69
|
+
*/
|
|
70
|
+
warn(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Log an error message.
|
|
73
|
+
*/
|
|
74
|
+
error(message: string, context?: Record<string, unknown>, requestId?: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Record an exception.
|
|
77
|
+
*/
|
|
78
|
+
exception(error: Error, requestId?: string): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Get requests from storage.
|
|
81
|
+
*/
|
|
82
|
+
getRequests(options?: Parameters<Telescope['getRequests']>[0]): ReturnType<Telescope['getRequests']>;
|
|
83
|
+
/**
|
|
84
|
+
* Get a single request by ID.
|
|
85
|
+
*/
|
|
86
|
+
getRequest(id: string): ReturnType<Telescope['getRequest']>;
|
|
87
|
+
/**
|
|
88
|
+
* Get exceptions from storage.
|
|
89
|
+
*/
|
|
90
|
+
getExceptions(options?: Parameters<Telescope['getExceptions']>[0]): ReturnType<Telescope['getExceptions']>;
|
|
91
|
+
/**
|
|
92
|
+
* Get a single exception by ID.
|
|
93
|
+
*/
|
|
94
|
+
getException(id: string): ReturnType<Telescope['getException']>;
|
|
95
|
+
/**
|
|
96
|
+
* Get logs from storage.
|
|
97
|
+
*/
|
|
98
|
+
getLogs(options?: Parameters<Telescope['getLogs']>[0]): ReturnType<Telescope['getLogs']>;
|
|
99
|
+
/**
|
|
100
|
+
* Get storage statistics.
|
|
101
|
+
*/
|
|
102
|
+
getStats(): ReturnType<Telescope['getStats']>;
|
|
103
|
+
/**
|
|
104
|
+
* Get aggregated request metrics.
|
|
105
|
+
*/
|
|
106
|
+
getMetrics(options?: Parameters<Telescope['getMetrics']>[0]): ReturnType<Telescope['getMetrics']>;
|
|
107
|
+
/**
|
|
108
|
+
* Get metrics grouped by endpoint.
|
|
109
|
+
*/
|
|
110
|
+
getEndpointMetrics(options?: Parameters<Telescope['getEndpointMetrics']>[0]): ReturnType<Telescope['getEndpointMetrics']>;
|
|
111
|
+
/**
|
|
112
|
+
* Get detailed metrics for a specific endpoint.
|
|
113
|
+
*/
|
|
114
|
+
getEndpointDetails(method: string, path: string, options?: Parameters<Telescope['getEndpointDetails']>[2]): ReturnType<Telescope['getEndpointDetails']>;
|
|
115
|
+
/**
|
|
116
|
+
* Get HTTP status code distribution.
|
|
117
|
+
*/
|
|
118
|
+
getStatusDistribution(options?: Parameters<Telescope['getStatusDistribution']>[0]): ReturnType<Telescope['getStatusDistribution']>;
|
|
119
|
+
/**
|
|
120
|
+
* Reset all metrics.
|
|
121
|
+
*/
|
|
122
|
+
resetMetrics(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Add a WebSocket client for real-time updates.
|
|
125
|
+
*/
|
|
126
|
+
addWsClient(ws: WebSocket): void;
|
|
127
|
+
/**
|
|
128
|
+
* Remove a WebSocket client.
|
|
129
|
+
*/
|
|
130
|
+
removeWsClient(ws: WebSocket): void;
|
|
131
|
+
/**
|
|
132
|
+
* Broadcast an event to all connected WebSocket clients.
|
|
133
|
+
*/
|
|
134
|
+
broadcast(event: StudioEvent): void;
|
|
135
|
+
/**
|
|
136
|
+
* Check if a path should be ignored for request recording.
|
|
137
|
+
*/
|
|
138
|
+
shouldIgnore(path: string): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Manually prune old monitoring entries.
|
|
141
|
+
*/
|
|
142
|
+
prune(olderThan: Date): Promise<number>;
|
|
143
|
+
/**
|
|
144
|
+
* Clean up resources.
|
|
145
|
+
*/
|
|
146
|
+
destroy(): void;
|
|
147
|
+
private normalizeOptions;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=Studio.d.ts.map
|
|
150
|
+
//#endregion
|
|
151
|
+
export { Studio };
|
|
152
|
+
//# sourceMappingURL=Studio-D5yGscb8.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Studio-D5yGscb8.d.mts","names":[],"sources":["../src/Studio.ts"],"sourcesContent":[],"mappings":";;;;;;AA2BA;;;;;;;;;;;;;;;;;;AA4HY,cA5HC,MA4HD,CAAA,KAAA,OAAA,CAAA,CAAA;EAAM,QAEd,SAAA;EAAO,QAOa,WAAA;EAAK,QAAuB,OAAA;EAAO,QAQpC,SAAA;EAAS,WAApB,CAAA,OAAA,EAvIU,aAuIV,CAvIwB,EAuIxB,CAAA;EAAU;;;EAQ6B,IAApB,IAAA,CAAA,CAAA,EAAA,MAAA;EAAU;;;EASjB,IAApB,OAAA,CAAA,CAAA,EAAA,OAAA;EAAU;;;EAekB,IAApB,IAAA,CAAA,CAAA,EA7HC,WA6HD,CA7Ha,EA6Hb,CAAA;EAAU;;;EAQiB,IAApB,UAAA,CAAA,CAAA,EAAA,OAAA;EAAU;;;EAaL,IAApB,WAAA,CAAA,CAAA,EAAA,MAAA;EAAU;;;EASU,aAApB,CAAA,KAAA,EAjIK,UAiIL,CAjIgB,SAiIhB,CAAA,eAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAhIA,OAgIA,CAAA,MAAA,CAAA;EAAU;;;EAWU,GAApB,CAAA,OAAA,EApIgB,UAoIhB,CApI2B,SAoI3B,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EApIkD,OAoIlD,CAAA,IAAA,CAAA;EAAU;;;EASU,KAApB,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EApIQ,MAoIR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAlIA,OAkIA,CAAA,IAAA,CAAA;EAAU;;;EAmCe,IA6BL,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAzLZ,MAyLY,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAvLpB,OAuLoB,CAAA,IAAA,CAAA;EAAI;AAAU;;kCA9K1B,8CAER;;;;mCASQ,8CAER;;;;mBAOoB,4BAA4B;;;;wBAQxC,WAAW,+BACnB,WAAW;;;;0BAOgB,WAAW;;;;0BAQ9B,WAAW,iCACnB,WAAW;;;;4BAOkB,WAAW;;;;oBAQhC,WAAW,2BACnB,WAAW;;;;cAOI,WAAW;;;;uBAYlB,WAAW,8BACnB,WAAW;;;;+BAQH,WAAW,sCACnB,WAAW;;;;6DAUH,WAAW,sCACnB,WAAW;;;;kCAQH,WAAW,yCACnB,WAAW;;;;;;;;kBAkBE;;;;qBASG;;;;mBAQF;;;;;;;;mBA6BM,OAAO"}
|
package/dist/data/index.cjs
CHANGED
package/dist/data/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { DataBrowser } from "../DataBrowser-
|
|
1
|
+
import { DataBrowser } from "../DataBrowser-BTe9HWJy.cjs";
|
|
2
2
|
export { DataBrowser };
|
package/dist/data/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { DataBrowser } from "../DataBrowser-
|
|
1
|
+
import { DataBrowser } from "../DataBrowser-B-jz8KBR.mjs";
|
|
2
2
|
export { DataBrowser };
|
package/dist/data/index.mjs
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
2
|
const require_types = require('./types-CMttUZYk.cjs');
|
|
3
|
-
const require_DataBrowser = require('./DataBrowser-
|
|
4
|
-
const __geekmidas_telescope = require_chunk.__toESM(require("@geekmidas/telescope"));
|
|
3
|
+
const require_DataBrowser = require('./DataBrowser-D8c_pBf4.cjs');
|
|
5
4
|
const __geekmidas_telescope_storage_memory = require_chunk.__toESM(require("@geekmidas/telescope/storage/memory"));
|
|
5
|
+
const __geekmidas_telescope = require_chunk.__toESM(require("@geekmidas/telescope"));
|
|
6
6
|
|
|
7
7
|
//#region src/Studio.ts
|
|
8
8
|
/**
|
|
@@ -151,6 +151,36 @@ var Studio = class {
|
|
|
151
151
|
return this.telescope.getStats();
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
|
+
* Get aggregated request metrics.
|
|
155
|
+
*/
|
|
156
|
+
getMetrics(options) {
|
|
157
|
+
return this.telescope.getMetrics(options);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get metrics grouped by endpoint.
|
|
161
|
+
*/
|
|
162
|
+
getEndpointMetrics(options) {
|
|
163
|
+
return this.telescope.getEndpointMetrics(options);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get detailed metrics for a specific endpoint.
|
|
167
|
+
*/
|
|
168
|
+
getEndpointDetails(method, path, options) {
|
|
169
|
+
return this.telescope.getEndpointDetails(method, path, options);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get HTTP status code distribution.
|
|
173
|
+
*/
|
|
174
|
+
getStatusDistribution(options) {
|
|
175
|
+
return this.telescope.getStatusDistribution(options);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Reset all metrics.
|
|
179
|
+
*/
|
|
180
|
+
resetMetrics() {
|
|
181
|
+
this.telescope.resetMetrics();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
154
184
|
* Add a WebSocket client for real-time updates.
|
|
155
185
|
*/
|
|
156
186
|
addWsClient(ws) {
|
|
@@ -203,7 +233,7 @@ var Studio = class {
|
|
|
203
233
|
ignorePatterns: options.monitoring.ignorePatterns ?? [],
|
|
204
234
|
recordBody: options.monitoring.recordBody ?? true,
|
|
205
235
|
maxBodySize: options.monitoring.maxBodySize ?? 64 * 1024,
|
|
206
|
-
pruneAfterHours: options.monitoring.pruneAfterHours
|
|
236
|
+
pruneAfterHours: options.monitoring.pruneAfterHours ?? 24
|
|
207
237
|
},
|
|
208
238
|
data: {
|
|
209
239
|
db: options.data.db,
|