@geekmidas/telescope 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 (103) hide show
  1. package/README.md +521 -0
  2. package/dist/Telescope-B3Wd82yk.cjs +602 -0
  3. package/dist/Telescope-B3Wd82yk.cjs.map +1 -0
  4. package/dist/Telescope-C5dyDYYB.d.cts +133 -0
  5. package/dist/Telescope-D-uoZB6b.mjs +596 -0
  6. package/dist/Telescope-D-uoZB6b.mjs.map +1 -0
  7. package/dist/Telescope-DyIWgh9-.d.mts +133 -0
  8. package/dist/Telescope.cjs +3 -0
  9. package/dist/Telescope.d.cts +3 -0
  10. package/dist/Telescope.d.mts +3 -0
  11. package/dist/Telescope.mjs +3 -0
  12. package/dist/chunk-CUT6urMc.cjs +30 -0
  13. package/dist/index.cjs +5 -0
  14. package/dist/index.d.cts +4 -0
  15. package/dist/index.d.mts +4 -0
  16. package/dist/index.mjs +4 -0
  17. package/dist/logger/console.cjs +161 -0
  18. package/dist/logger/console.cjs.map +1 -0
  19. package/dist/logger/console.d.cts +109 -0
  20. package/dist/logger/console.d.mts +109 -0
  21. package/dist/logger/console.mjs +159 -0
  22. package/dist/logger/console.mjs.map +1 -0
  23. package/dist/logger/pino.cjs +118 -0
  24. package/dist/logger/pino.cjs.map +1 -0
  25. package/dist/logger/pino.d.cts +89 -0
  26. package/dist/logger/pino.d.mts +89 -0
  27. package/dist/logger/pino.mjs +116 -0
  28. package/dist/logger/pino.mjs.map +1 -0
  29. package/dist/memory-9-B9WACq.cjs +110 -0
  30. package/dist/memory-9-B9WACq.cjs.map +1 -0
  31. package/dist/memory-Cm0eevCS.d.mts +38 -0
  32. package/dist/memory-DiP1a-pp.d.cts +38 -0
  33. package/dist/memory-SdN5vtG9.mjs +104 -0
  34. package/dist/memory-SdN5vtG9.mjs.map +1 -0
  35. package/dist/server/hono.cjs +180 -0
  36. package/dist/server/hono.cjs.map +1 -0
  37. package/dist/server/hono.d.cts +26 -0
  38. package/dist/server/hono.d.mts +26 -0
  39. package/dist/server/hono.mjs +176 -0
  40. package/dist/server/hono.mjs.map +1 -0
  41. package/dist/storage/kysely.cjs +336 -0
  42. package/dist/storage/kysely.cjs.map +1 -0
  43. package/dist/storage/kysely.d.cts +161 -0
  44. package/dist/storage/kysely.d.mts +161 -0
  45. package/dist/storage/kysely.mjs +334 -0
  46. package/dist/storage/kysely.mjs.map +1 -0
  47. package/dist/storage/memory.cjs +3 -0
  48. package/dist/storage/memory.d.cts +3 -0
  49. package/dist/storage/memory.d.mts +3 -0
  50. package/dist/storage/memory.mjs +3 -0
  51. package/dist/types-BGDhFv4R.d.cts +170 -0
  52. package/dist/types-CZbzz8kx.d.mts +170 -0
  53. package/dist/types.cjs +0 -0
  54. package/dist/types.d.cts +2 -0
  55. package/dist/types.d.mts +2 -0
  56. package/dist/types.mjs +0 -0
  57. package/dist/ui-assets-D6-8TAr_.mjs +30 -0
  58. package/dist/ui-assets-D6-8TAr_.mjs.map +1 -0
  59. package/dist/ui-assets-ulevVble.cjs +48 -0
  60. package/dist/ui-assets-ulevVble.cjs.map +1 -0
  61. package/dist/ui-assets.cjs +5 -0
  62. package/dist/ui-assets.d.cts +12 -0
  63. package/dist/ui-assets.d.mts +12 -0
  64. package/dist/ui-assets.mjs +3 -0
  65. package/package.json +83 -0
  66. package/scripts/embed-ui.ts +90 -0
  67. package/src/Telescope.ts +714 -0
  68. package/src/__tests__/Telescope.spec.ts +356 -0
  69. package/src/index.ts +23 -0
  70. package/src/logger/__tests__/console.spec.ts +266 -0
  71. package/src/logger/__tests__/pino.spec.ts +217 -0
  72. package/src/logger/console.ts +230 -0
  73. package/src/logger/pino.ts +191 -0
  74. package/src/server/__tests__/hono.spec.ts +340 -0
  75. package/src/server/hono.ts +247 -0
  76. package/src/storage/__tests__/kysely.spec.ts +715 -0
  77. package/src/storage/__tests__/memory.spec.ts +411 -0
  78. package/src/storage/kysely.ts +572 -0
  79. package/src/storage/memory.ts +168 -0
  80. package/src/types.ts +188 -0
  81. package/src/ui-assets.ts +40 -0
  82. package/ui/index.html +12 -0
  83. package/ui/node_modules/.bin/browserslist +21 -0
  84. package/ui/node_modules/.bin/jiti +21 -0
  85. package/ui/node_modules/.bin/terser +21 -0
  86. package/ui/node_modules/.bin/tsc +21 -0
  87. package/ui/node_modules/.bin/tsserver +21 -0
  88. package/ui/node_modules/.bin/tsx +21 -0
  89. package/ui/node_modules/.bin/vite +21 -0
  90. package/ui/package.json +24 -0
  91. package/ui/src/App.tsx +342 -0
  92. package/ui/src/api.ts +75 -0
  93. package/ui/src/components/ExceptionDetail.tsx +100 -0
  94. package/ui/src/components/LogDetail.tsx +91 -0
  95. package/ui/src/components/RequestDetail.tsx +143 -0
  96. package/ui/src/main.tsx +10 -0
  97. package/ui/src/styles.css +10 -0
  98. package/ui/src/types.ts +63 -0
  99. package/ui/src/vite-env.d.ts +1 -0
  100. package/ui/src/vite-plugin-gkm-config.ts +54 -0
  101. package/ui/tsconfig.json +20 -0
  102. package/ui/tsconfig.tsbuildinfo +14 -0
  103. package/ui/vite.config.ts +13 -0
@@ -0,0 +1,161 @@
1
+ import { ExceptionEntry, LogEntry, QueryOptions, RequestEntry, TelescopeStats, TelescopeStorage } from "../types-BGDhFv4R.cjs";
2
+ import { Kysely } from "kysely";
3
+
4
+ //#region src/storage/kysely.d.ts
5
+
6
+ /**
7
+ * Database table interface for telescope requests.
8
+ * Use this to define your telescope_requests table in your Kysely database schema.
9
+ */
10
+ interface TelescopeRequestTable {
11
+ id: string;
12
+ method: string;
13
+ path: string;
14
+ url: string;
15
+ headers: unknown;
16
+ body: unknown | null;
17
+ query: unknown | null;
18
+ status: number;
19
+ response_headers: unknown;
20
+ response_body: unknown | null;
21
+ duration: number;
22
+ timestamp: Date;
23
+ ip: string | null;
24
+ user_id: string | null;
25
+ tags: unknown | null;
26
+ }
27
+ /**
28
+ * Database table interface for telescope exceptions.
29
+ */
30
+ interface TelescopeExceptionTable {
31
+ id: string;
32
+ name: string;
33
+ message: string;
34
+ stack: unknown;
35
+ source: unknown | null;
36
+ request_id: string | null;
37
+ timestamp: Date;
38
+ handled: boolean;
39
+ tags: unknown | null;
40
+ }
41
+ /**
42
+ * Database table interface for telescope logs.
43
+ */
44
+ interface TelescopeLogTable {
45
+ id: string;
46
+ level: string;
47
+ message: string;
48
+ context: unknown | null;
49
+ request_id: string | null;
50
+ timestamp: Date;
51
+ }
52
+ /**
53
+ * Combined database interface for all telescope tables.
54
+ * Use this to extend your database schema.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import type { TelescopeTables } from '@geekmidas/telescope/storage/kysely';
59
+ *
60
+ * interface Database extends TelescopeTables {
61
+ * users: UserTable;
62
+ * // ... other tables
63
+ * }
64
+ * ```
65
+ */
66
+ interface TelescopeTables {
67
+ telescope_requests: TelescopeRequestTable;
68
+ telescope_exceptions: TelescopeExceptionTable;
69
+ telescope_logs: TelescopeLogTable;
70
+ }
71
+ /**
72
+ * Configuration for KyselyStorage.
73
+ */
74
+ interface KyselyStorageConfig<DB> {
75
+ /** Kysely database instance */
76
+ db: Kysely<DB>;
77
+ /**
78
+ * Table name prefix (default: 'telescope').
79
+ * Tables will be named: {prefix}_requests, {prefix}_exceptions, {prefix}_logs
80
+ */
81
+ tablePrefix?: string;
82
+ }
83
+ /**
84
+ * Kysely-based storage implementation for Telescope.
85
+ * Stores telescope data in PostgreSQL, MySQL, or SQLite using Kysely.
86
+ *
87
+ * @template DB - Your Kysely database schema (must include TelescopeTables)
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * import { Kysely, PostgresDialect } from 'kysely';
92
+ * import { KyselyStorage, type TelescopeTables } from '@geekmidas/telescope/storage/kysely';
93
+ *
94
+ * interface Database extends TelescopeTables {
95
+ * users: UserTable;
96
+ * }
97
+ *
98
+ * const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });
99
+ * const storage = new KyselyStorage({ db });
100
+ *
101
+ * const telescope = new Telescope({ storage });
102
+ * ```
103
+ */
104
+ declare class KyselyStorage<DB> implements TelescopeStorage {
105
+ private readonly db;
106
+ private readonly requestsTable;
107
+ private readonly exceptionsTable;
108
+ private readonly logsTable;
109
+ constructor(config: KyselyStorageConfig<DB>);
110
+ saveRequest(entry: RequestEntry): Promise<void>;
111
+ saveRequests(entries: RequestEntry[]): Promise<void>;
112
+ getRequests(options?: QueryOptions): Promise<RequestEntry[]>;
113
+ getRequest(id: string): Promise<RequestEntry | null>;
114
+ saveException(entry: ExceptionEntry): Promise<void>;
115
+ saveExceptions(entries: ExceptionEntry[]): Promise<void>;
116
+ getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]>;
117
+ getException(id: string): Promise<ExceptionEntry | null>;
118
+ saveLog(entry: LogEntry): Promise<void>;
119
+ saveLogs(entries: LogEntry[]): Promise<void>;
120
+ getLogs(options?: QueryOptions): Promise<LogEntry[]>;
121
+ prune(olderThan: Date): Promise<number>;
122
+ getStats(): Promise<TelescopeStats>;
123
+ private applyQueryOptions;
124
+ private requestToRow;
125
+ private rowToRequest;
126
+ private exceptionToRow;
127
+ private rowToException;
128
+ private logToRow;
129
+ private rowToLog;
130
+ /**
131
+ * Parse a JSON value that may already be parsed (e.g., from jsonb columns).
132
+ */
133
+ private parseJson;
134
+ }
135
+ /**
136
+ * SQL migration to create telescope tables.
137
+ * Use this to set up the required tables in your database.
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * import { getTelescopeMigration } from '@geekmidas/telescope/storage/kysely';
142
+ *
143
+ * // In your migration file
144
+ * export async function up(db: Kysely<any>): Promise<void> {
145
+ * const migration = getTelescopeMigration();
146
+ * await db.schema.executeRaw(migration.up).execute();
147
+ * }
148
+ *
149
+ * export async function down(db: Kysely<any>): Promise<void> {
150
+ * const migration = getTelescopeMigration();
151
+ * await db.schema.executeRaw(migration.down).execute();
152
+ * }
153
+ * ```
154
+ */
155
+ declare function getTelescopeMigration(tablePrefix?: string): {
156
+ up: string;
157
+ down: string;
158
+ };
159
+ //#endregion
160
+ export { KyselyStorage, KyselyStorageConfig, TelescopeExceptionTable, TelescopeLogTable, TelescopeRequestTable, TelescopeTables, getTelescopeMigration };
161
+ //# sourceMappingURL=kysely.d.cts.map
@@ -0,0 +1,161 @@
1
+ import { ExceptionEntry, LogEntry, QueryOptions, RequestEntry, TelescopeStats, TelescopeStorage } from "../types-CZbzz8kx.mjs";
2
+ import { Kysely } from "kysely";
3
+
4
+ //#region src/storage/kysely.d.ts
5
+
6
+ /**
7
+ * Database table interface for telescope requests.
8
+ * Use this to define your telescope_requests table in your Kysely database schema.
9
+ */
10
+ interface TelescopeRequestTable {
11
+ id: string;
12
+ method: string;
13
+ path: string;
14
+ url: string;
15
+ headers: unknown;
16
+ body: unknown | null;
17
+ query: unknown | null;
18
+ status: number;
19
+ response_headers: unknown;
20
+ response_body: unknown | null;
21
+ duration: number;
22
+ timestamp: Date;
23
+ ip: string | null;
24
+ user_id: string | null;
25
+ tags: unknown | null;
26
+ }
27
+ /**
28
+ * Database table interface for telescope exceptions.
29
+ */
30
+ interface TelescopeExceptionTable {
31
+ id: string;
32
+ name: string;
33
+ message: string;
34
+ stack: unknown;
35
+ source: unknown | null;
36
+ request_id: string | null;
37
+ timestamp: Date;
38
+ handled: boolean;
39
+ tags: unknown | null;
40
+ }
41
+ /**
42
+ * Database table interface for telescope logs.
43
+ */
44
+ interface TelescopeLogTable {
45
+ id: string;
46
+ level: string;
47
+ message: string;
48
+ context: unknown | null;
49
+ request_id: string | null;
50
+ timestamp: Date;
51
+ }
52
+ /**
53
+ * Combined database interface for all telescope tables.
54
+ * Use this to extend your database schema.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import type { TelescopeTables } from '@geekmidas/telescope/storage/kysely';
59
+ *
60
+ * interface Database extends TelescopeTables {
61
+ * users: UserTable;
62
+ * // ... other tables
63
+ * }
64
+ * ```
65
+ */
66
+ interface TelescopeTables {
67
+ telescope_requests: TelescopeRequestTable;
68
+ telescope_exceptions: TelescopeExceptionTable;
69
+ telescope_logs: TelescopeLogTable;
70
+ }
71
+ /**
72
+ * Configuration for KyselyStorage.
73
+ */
74
+ interface KyselyStorageConfig<DB> {
75
+ /** Kysely database instance */
76
+ db: Kysely<DB>;
77
+ /**
78
+ * Table name prefix (default: 'telescope').
79
+ * Tables will be named: {prefix}_requests, {prefix}_exceptions, {prefix}_logs
80
+ */
81
+ tablePrefix?: string;
82
+ }
83
+ /**
84
+ * Kysely-based storage implementation for Telescope.
85
+ * Stores telescope data in PostgreSQL, MySQL, or SQLite using Kysely.
86
+ *
87
+ * @template DB - Your Kysely database schema (must include TelescopeTables)
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * import { Kysely, PostgresDialect } from 'kysely';
92
+ * import { KyselyStorage, type TelescopeTables } from '@geekmidas/telescope/storage/kysely';
93
+ *
94
+ * interface Database extends TelescopeTables {
95
+ * users: UserTable;
96
+ * }
97
+ *
98
+ * const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });
99
+ * const storage = new KyselyStorage({ db });
100
+ *
101
+ * const telescope = new Telescope({ storage });
102
+ * ```
103
+ */
104
+ declare class KyselyStorage<DB> implements TelescopeStorage {
105
+ private readonly db;
106
+ private readonly requestsTable;
107
+ private readonly exceptionsTable;
108
+ private readonly logsTable;
109
+ constructor(config: KyselyStorageConfig<DB>);
110
+ saveRequest(entry: RequestEntry): Promise<void>;
111
+ saveRequests(entries: RequestEntry[]): Promise<void>;
112
+ getRequests(options?: QueryOptions): Promise<RequestEntry[]>;
113
+ getRequest(id: string): Promise<RequestEntry | null>;
114
+ saveException(entry: ExceptionEntry): Promise<void>;
115
+ saveExceptions(entries: ExceptionEntry[]): Promise<void>;
116
+ getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]>;
117
+ getException(id: string): Promise<ExceptionEntry | null>;
118
+ saveLog(entry: LogEntry): Promise<void>;
119
+ saveLogs(entries: LogEntry[]): Promise<void>;
120
+ getLogs(options?: QueryOptions): Promise<LogEntry[]>;
121
+ prune(olderThan: Date): Promise<number>;
122
+ getStats(): Promise<TelescopeStats>;
123
+ private applyQueryOptions;
124
+ private requestToRow;
125
+ private rowToRequest;
126
+ private exceptionToRow;
127
+ private rowToException;
128
+ private logToRow;
129
+ private rowToLog;
130
+ /**
131
+ * Parse a JSON value that may already be parsed (e.g., from jsonb columns).
132
+ */
133
+ private parseJson;
134
+ }
135
+ /**
136
+ * SQL migration to create telescope tables.
137
+ * Use this to set up the required tables in your database.
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * import { getTelescopeMigration } from '@geekmidas/telescope/storage/kysely';
142
+ *
143
+ * // In your migration file
144
+ * export async function up(db: Kysely<any>): Promise<void> {
145
+ * const migration = getTelescopeMigration();
146
+ * await db.schema.executeRaw(migration.up).execute();
147
+ * }
148
+ *
149
+ * export async function down(db: Kysely<any>): Promise<void> {
150
+ * const migration = getTelescopeMigration();
151
+ * await db.schema.executeRaw(migration.down).execute();
152
+ * }
153
+ * ```
154
+ */
155
+ declare function getTelescopeMigration(tablePrefix?: string): {
156
+ up: string;
157
+ down: string;
158
+ };
159
+ //#endregion
160
+ export { KyselyStorage, KyselyStorageConfig, TelescopeExceptionTable, TelescopeLogTable, TelescopeRequestTable, TelescopeTables, getTelescopeMigration };
161
+ //# sourceMappingURL=kysely.d.mts.map
@@ -0,0 +1,334 @@
1
+ //#region src/storage/kysely.ts
2
+ /**
3
+ * Kysely-based storage implementation for Telescope.
4
+ * Stores telescope data in PostgreSQL, MySQL, or SQLite using Kysely.
5
+ *
6
+ * @template DB - Your Kysely database schema (must include TelescopeTables)
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { Kysely, PostgresDialect } from 'kysely';
11
+ * import { KyselyStorage, type TelescopeTables } from '@geekmidas/telescope/storage/kysely';
12
+ *
13
+ * interface Database extends TelescopeTables {
14
+ * users: UserTable;
15
+ * }
16
+ *
17
+ * const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });
18
+ * const storage = new KyselyStorage({ db });
19
+ *
20
+ * const telescope = new Telescope({ storage });
21
+ * ```
22
+ */
23
+ var KyselyStorage = class {
24
+ db;
25
+ requestsTable;
26
+ exceptionsTable;
27
+ logsTable;
28
+ constructor(config) {
29
+ this.db = config.db;
30
+ const prefix = config.tablePrefix ?? "telescope";
31
+ this.requestsTable = `${prefix}_requests`;
32
+ this.exceptionsTable = `${prefix}_exceptions`;
33
+ this.logsTable = `${prefix}_logs`;
34
+ }
35
+ async saveRequest(entry) {
36
+ const row = this.requestToRow(entry);
37
+ await this.db.insertInto(this.requestsTable).values(row).execute();
38
+ }
39
+ async saveRequests(entries) {
40
+ if (entries.length === 0) return;
41
+ const rows = entries.map((e) => this.requestToRow(e));
42
+ await this.db.insertInto(this.requestsTable).values(rows).execute();
43
+ }
44
+ async getRequests(options) {
45
+ let query = this.db.selectFrom(this.requestsTable).selectAll().orderBy("timestamp", "desc");
46
+ query = this.applyQueryOptions(query, options);
47
+ const rows = await query.execute();
48
+ return rows.map((row) => this.rowToRequest(row));
49
+ }
50
+ async getRequest(id) {
51
+ const row = await this.db.selectFrom(this.requestsTable).selectAll().where("id", "=", id).executeTakeFirst();
52
+ return row ? this.rowToRequest(row) : null;
53
+ }
54
+ async saveException(entry) {
55
+ const row = this.exceptionToRow(entry);
56
+ await this.db.insertInto(this.exceptionsTable).values(row).execute();
57
+ }
58
+ async saveExceptions(entries) {
59
+ if (entries.length === 0) return;
60
+ const rows = entries.map((e) => this.exceptionToRow(e));
61
+ await this.db.insertInto(this.exceptionsTable).values(rows).execute();
62
+ }
63
+ async getExceptions(options) {
64
+ let query = this.db.selectFrom(this.exceptionsTable).selectAll().orderBy("timestamp", "desc");
65
+ query = this.applyQueryOptions(query, options);
66
+ const rows = await query.execute();
67
+ return rows.map((row) => this.rowToException(row));
68
+ }
69
+ async getException(id) {
70
+ const row = await this.db.selectFrom(this.exceptionsTable).selectAll().where("id", "=", id).executeTakeFirst();
71
+ return row ? this.rowToException(row) : null;
72
+ }
73
+ async saveLog(entry) {
74
+ const row = this.logToRow(entry);
75
+ await this.db.insertInto(this.logsTable).values(row).execute();
76
+ }
77
+ async saveLogs(entries) {
78
+ if (entries.length === 0) return;
79
+ const rows = entries.map((e) => this.logToRow(e));
80
+ await this.db.insertInto(this.logsTable).values(rows).execute();
81
+ }
82
+ async getLogs(options) {
83
+ let query = this.db.selectFrom(this.logsTable).selectAll().orderBy("timestamp", "desc");
84
+ query = this.applyQueryOptions(query, options);
85
+ const rows = await query.execute();
86
+ return rows.map((row) => this.rowToLog(row));
87
+ }
88
+ async prune(olderThan) {
89
+ const results = await Promise.all([
90
+ this.db.deleteFrom(this.requestsTable).where("timestamp", "<", olderThan).executeTakeFirst(),
91
+ this.db.deleteFrom(this.exceptionsTable).where("timestamp", "<", olderThan).executeTakeFirst(),
92
+ this.db.deleteFrom(this.logsTable).where("timestamp", "<", olderThan).executeTakeFirst()
93
+ ]);
94
+ return results.reduce((sum, result) => sum + Number(result.numDeletedRows ?? 0), 0);
95
+ }
96
+ async getStats() {
97
+ const [requestsResult, exceptionsResult, logsResult] = await Promise.all([
98
+ this.db.selectFrom(this.requestsTable).select((eb) => [
99
+ eb.fn.count("id").as("count"),
100
+ eb.fn.min("timestamp").as("oldest"),
101
+ eb.fn.max("timestamp").as("newest")
102
+ ]).executeTakeFirst(),
103
+ this.db.selectFrom(this.exceptionsTable).select((eb) => [
104
+ eb.fn.count("id").as("count"),
105
+ eb.fn.min("timestamp").as("oldest"),
106
+ eb.fn.max("timestamp").as("newest")
107
+ ]).executeTakeFirst(),
108
+ this.db.selectFrom(this.logsTable).select((eb) => [
109
+ eb.fn.count("id").as("count"),
110
+ eb.fn.min("timestamp").as("oldest"),
111
+ eb.fn.max("timestamp").as("newest")
112
+ ]).executeTakeFirst()
113
+ ]);
114
+ const allDates = [
115
+ requestsResult?.oldest,
116
+ requestsResult?.newest,
117
+ exceptionsResult?.oldest,
118
+ exceptionsResult?.newest,
119
+ logsResult?.oldest,
120
+ logsResult?.newest
121
+ ].filter((d) => d != null).sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
122
+ return {
123
+ requests: Number(requestsResult?.count ?? 0),
124
+ exceptions: Number(exceptionsResult?.count ?? 0),
125
+ logs: Number(logsResult?.count ?? 0),
126
+ oldestEntry: allDates[0] ? new Date(allDates[0]) : void 0,
127
+ newestEntry: allDates.length > 0 ? new Date(allDates[allDates.length - 1]) : void 0
128
+ };
129
+ }
130
+ applyQueryOptions(query, options) {
131
+ if (!options) return query.limit(50);
132
+ if (options.after) query = query.where("timestamp", ">=", options.after);
133
+ if (options.before) query = query.where("timestamp", "<=", options.before);
134
+ if (options.search) query = query.where((eb) => eb.or([
135
+ eb("message", "ilike", `%${options.search}%`),
136
+ eb("path", "ilike", `%${options.search}%`),
137
+ eb("url", "ilike", `%${options.search}%`)
138
+ ]));
139
+ const limit = options.limit ?? 50;
140
+ const offset = options.offset ?? 0;
141
+ return query.limit(limit).offset(offset);
142
+ }
143
+ requestToRow(entry) {
144
+ return {
145
+ id: entry.id,
146
+ method: entry.method,
147
+ path: entry.path,
148
+ url: entry.url,
149
+ headers: entry.headers,
150
+ body: entry.body ?? null,
151
+ query: entry.query ?? null,
152
+ status: entry.status,
153
+ response_headers: entry.responseHeaders,
154
+ response_body: entry.responseBody ?? null,
155
+ duration: entry.duration,
156
+ timestamp: entry.timestamp,
157
+ ip: entry.ip ?? null,
158
+ user_id: entry.userId ?? null,
159
+ tags: entry.tags ?? null
160
+ };
161
+ }
162
+ rowToRequest(row) {
163
+ return {
164
+ id: row.id,
165
+ method: row.method,
166
+ path: row.path,
167
+ url: row.url,
168
+ headers: this.parseJson(row.headers),
169
+ body: row.body ? this.parseJson(row.body) : void 0,
170
+ query: row.query ? this.parseJson(row.query) : void 0,
171
+ status: row.status,
172
+ responseHeaders: this.parseJson(row.response_headers),
173
+ responseBody: row.response_body ? this.parseJson(row.response_body) : void 0,
174
+ duration: row.duration,
175
+ timestamp: new Date(row.timestamp),
176
+ ip: row.ip ?? void 0,
177
+ userId: row.user_id ?? void 0,
178
+ tags: row.tags ? this.parseJson(row.tags) : void 0
179
+ };
180
+ }
181
+ exceptionToRow(entry) {
182
+ return {
183
+ id: entry.id,
184
+ name: entry.name,
185
+ message: entry.message,
186
+ stack: entry.stack,
187
+ source: entry.source ?? null,
188
+ request_id: entry.requestId ?? null,
189
+ timestamp: entry.timestamp,
190
+ handled: entry.handled,
191
+ tags: entry.tags ?? null
192
+ };
193
+ }
194
+ rowToException(row) {
195
+ return {
196
+ id: row.id,
197
+ name: row.name,
198
+ message: row.message,
199
+ stack: this.parseJson(row.stack),
200
+ source: row.source ? this.parseJson(row.source) : void 0,
201
+ requestId: row.request_id ?? void 0,
202
+ timestamp: new Date(row.timestamp),
203
+ handled: row.handled,
204
+ tags: row.tags ? this.parseJson(row.tags) : void 0
205
+ };
206
+ }
207
+ logToRow(entry) {
208
+ return {
209
+ id: entry.id,
210
+ level: entry.level,
211
+ message: entry.message,
212
+ context: entry.context ?? null,
213
+ request_id: entry.requestId ?? null,
214
+ timestamp: entry.timestamp
215
+ };
216
+ }
217
+ rowToLog(row) {
218
+ return {
219
+ id: row.id,
220
+ level: row.level,
221
+ message: row.message,
222
+ context: row.context ? this.parseJson(row.context) : void 0,
223
+ requestId: row.request_id ?? void 0,
224
+ timestamp: new Date(row.timestamp)
225
+ };
226
+ }
227
+ /**
228
+ * Parse a JSON value that may already be parsed (e.g., from jsonb columns).
229
+ */
230
+ parseJson(value) {
231
+ if (typeof value === "object" && value !== null) return value;
232
+ if (typeof value === "string") try {
233
+ return JSON.parse(value);
234
+ } catch {
235
+ return value;
236
+ }
237
+ return value;
238
+ }
239
+ };
240
+ /**
241
+ * SQL migration to create telescope tables.
242
+ * Use this to set up the required tables in your database.
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * import { getTelescopeMigration } from '@geekmidas/telescope/storage/kysely';
247
+ *
248
+ * // In your migration file
249
+ * export async function up(db: Kysely<any>): Promise<void> {
250
+ * const migration = getTelescopeMigration();
251
+ * await db.schema.executeRaw(migration.up).execute();
252
+ * }
253
+ *
254
+ * export async function down(db: Kysely<any>): Promise<void> {
255
+ * const migration = getTelescopeMigration();
256
+ * await db.schema.executeRaw(migration.down).execute();
257
+ * }
258
+ * ```
259
+ */
260
+ function getTelescopeMigration(tablePrefix = "telescope") {
261
+ return {
262
+ up: `
263
+ -- Telescope requests table
264
+ CREATE TABLE IF NOT EXISTS ${tablePrefix}_requests (
265
+ id VARCHAR(21) PRIMARY KEY,
266
+ method VARCHAR(10) NOT NULL,
267
+ path TEXT NOT NULL,
268
+ url TEXT NOT NULL,
269
+ headers JSONB NOT NULL,
270
+ body JSONB,
271
+ query JSONB,
272
+ status INTEGER NOT NULL,
273
+ response_headers JSONB NOT NULL,
274
+ response_body JSONB,
275
+ duration DOUBLE PRECISION NOT NULL,
276
+ timestamp TIMESTAMPTZ NOT NULL,
277
+ ip VARCHAR(45),
278
+ user_id VARCHAR(255),
279
+ tags JSONB
280
+ );
281
+
282
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_timestamp
283
+ ON ${tablePrefix}_requests (timestamp DESC);
284
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_path
285
+ ON ${tablePrefix}_requests (path);
286
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_status
287
+ ON ${tablePrefix}_requests (status);
288
+
289
+ -- Telescope exceptions table
290
+ CREATE TABLE IF NOT EXISTS ${tablePrefix}_exceptions (
291
+ id VARCHAR(21) PRIMARY KEY,
292
+ name VARCHAR(255) NOT NULL,
293
+ message TEXT NOT NULL,
294
+ stack JSONB NOT NULL,
295
+ source JSONB,
296
+ request_id VARCHAR(21),
297
+ timestamp TIMESTAMPTZ NOT NULL,
298
+ handled BOOLEAN NOT NULL DEFAULT FALSE,
299
+ tags JSONB
300
+ );
301
+
302
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_timestamp
303
+ ON ${tablePrefix}_exceptions (timestamp DESC);
304
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_request_id
305
+ ON ${tablePrefix}_exceptions (request_id);
306
+
307
+ -- Telescope logs table
308
+ CREATE TABLE IF NOT EXISTS ${tablePrefix}_logs (
309
+ id VARCHAR(21) PRIMARY KEY,
310
+ level VARCHAR(10) NOT NULL,
311
+ message TEXT NOT NULL,
312
+ context JSONB,
313
+ request_id VARCHAR(21),
314
+ timestamp TIMESTAMPTZ NOT NULL
315
+ );
316
+
317
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_timestamp
318
+ ON ${tablePrefix}_logs (timestamp DESC);
319
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_level
320
+ ON ${tablePrefix}_logs (level);
321
+ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_request_id
322
+ ON ${tablePrefix}_logs (request_id);
323
+ `,
324
+ down: `
325
+ DROP TABLE IF EXISTS ${tablePrefix}_logs;
326
+ DROP TABLE IF EXISTS ${tablePrefix}_exceptions;
327
+ DROP TABLE IF EXISTS ${tablePrefix}_requests;
328
+ `
329
+ };
330
+ }
331
+
332
+ //#endregion
333
+ export { KyselyStorage, getTelescopeMigration };
334
+ //# sourceMappingURL=kysely.mjs.map