@geekmidas/telescope 1.1.1 → 10.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +14 -4
  2. package/dist/{hono-Bz9PQQzj.mjs → hono-Bw-ey6CT.mjs} +2 -2
  3. package/dist/{hono-Bz9PQQzj.mjs.map → hono-Bw-ey6CT.mjs.map} +1 -1
  4. package/dist/{hono-Z_JIICmt.cjs → hono-CPHX7FAm.cjs} +2 -2
  5. package/dist/{hono-Z_JIICmt.cjs.map → hono-CPHX7FAm.cjs.map} +1 -1
  6. package/dist/{http-Crsriv32.mjs → http-BIvr0YdI.mjs} +1 -1
  7. package/dist/{http-Crsriv32.mjs.map → http-BIvr0YdI.mjs.map} +1 -1
  8. package/dist/{http-JycFRfSe.cjs → http-BsqmhurZ.cjs} +1 -1
  9. package/dist/{http-JycFRfSe.cjs.map → http-BsqmhurZ.cjs.map} +1 -1
  10. package/dist/{http-D5kn3_Mt.d.mts → http-DULM1u2L.d.cts} +3 -3
  11. package/dist/{http-D5kn3_Mt.d.mts.map → http-DULM1u2L.d.cts.map} +1 -1
  12. package/dist/{http-Dvca6fC3.d.cts → http-GB-Nlvwc.d.mts} +3 -3
  13. package/dist/{http-Dvca6fC3.d.cts.map → http-GB-Nlvwc.d.mts.map} +1 -1
  14. package/dist/instrumentation/hono.cjs +2 -2
  15. package/dist/instrumentation/hono.mjs +2 -2
  16. package/dist/instrumentation/http.cjs +1 -1
  17. package/dist/instrumentation/http.d.cts +1 -1
  18. package/dist/instrumentation/http.d.mts +1 -1
  19. package/dist/instrumentation/http.mjs +1 -1
  20. package/dist/instrumentation/index.cjs +5 -5
  21. package/dist/instrumentation/index.d.cts +3 -3
  22. package/dist/instrumentation/index.d.mts +3 -3
  23. package/dist/instrumentation/index.mjs +5 -5
  24. package/dist/instrumentation/middleware.cjs +2 -2
  25. package/dist/instrumentation/middleware.d.cts +1 -1
  26. package/dist/instrumentation/middleware.d.mts +1 -1
  27. package/dist/instrumentation/middleware.mjs +2 -2
  28. package/dist/instrumentation/otel.cjs +2 -2
  29. package/dist/instrumentation/otel.d.cts +1 -1
  30. package/dist/instrumentation/otel.d.mts +1 -1
  31. package/dist/instrumentation/otel.mjs +2 -2
  32. package/dist/instrumentation/setup.cjs +1 -1
  33. package/dist/instrumentation/setup.mjs +1 -1
  34. package/dist/logger/console.d.cts.map +1 -1
  35. package/dist/{middleware-D3v5SnSs.cjs → middleware-Bcq6XXGG.cjs} +3 -3
  36. package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
  37. package/dist/{middleware-Dy1ctr2N.mjs → middleware-D1XEHvyy.mjs} +3 -3
  38. package/dist/middleware-D1XEHvyy.mjs.map +1 -0
  39. package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
  40. package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
  41. package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
  42. package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
  43. package/dist/{otel-DPKNyJ0t.mjs → otel-B9bNoeZ2.mjs} +4 -4
  44. package/dist/otel-B9bNoeZ2.mjs.map +1 -0
  45. package/dist/{otel-1bJPb2Z7.d.mts → otel-Bk89b1lX.d.cts} +3 -3
  46. package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
  47. package/dist/{otel-DuiBeBPU.cjs → otel-BzjOtVxZ.cjs} +4 -4
  48. package/dist/otel-BzjOtVxZ.cjs.map +1 -0
  49. package/dist/{otel-SBVoVEHr.d.cts → otel-lPMsM26e.d.mts} +3 -3
  50. package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
  51. package/dist/otlp/hono.cjs +3 -3
  52. package/dist/otlp/hono.mjs +3 -3
  53. package/dist/otlp/index.cjs +3 -3
  54. package/dist/otlp/index.mjs +3 -3
  55. package/dist/otlp/receiver.cjs +3 -3
  56. package/dist/otlp/receiver.mjs +3 -3
  57. package/dist/otlp/transformer.cjs +2 -2
  58. package/dist/otlp/transformer.mjs +2 -2
  59. package/dist/otlp/types.cjs +1 -1
  60. package/dist/otlp/types.mjs +1 -1
  61. package/dist/{receiver-q4f7eirs.mjs → receiver-D46dfkYI.mjs} +2 -2
  62. package/dist/{receiver-q4f7eirs.mjs.map → receiver-D46dfkYI.mjs.map} +1 -1
  63. package/dist/{receiver-BW_4go_X.cjs → receiver-slCoAFat.cjs} +2 -2
  64. package/dist/{receiver-BW_4go_X.cjs.map → receiver-slCoAFat.cjs.map} +1 -1
  65. package/dist/{setup-BhCp2LUp.mjs → setup-DC9JMEYZ.mjs} +1 -1
  66. package/dist/{setup-BhCp2LUp.mjs.map → setup-DC9JMEYZ.mjs.map} +1 -1
  67. package/dist/{setup-_uT4L_x5.cjs → setup-DufxQpi9.cjs} +1 -1
  68. package/dist/{setup-_uT4L_x5.cjs.map → setup-DufxQpi9.cjs.map} +1 -1
  69. package/dist/storage/kysely.cjs +48 -38
  70. package/dist/storage/kysely.cjs.map +1 -1
  71. package/dist/storage/kysely.d.cts +29 -9
  72. package/dist/storage/kysely.d.cts.map +1 -1
  73. package/dist/storage/kysely.d.mts +29 -9
  74. package/dist/storage/kysely.d.mts.map +1 -1
  75. package/dist/storage/kysely.mjs +48 -38
  76. package/dist/storage/kysely.mjs.map +1 -1
  77. package/dist/tracing-BX8uY2nR.d.cts.map +1 -1
  78. package/dist/tracing-PnECEbEu.d.mts.map +1 -1
  79. package/dist/{transformer-CFrJQkUg.mjs → transformer-CFQ4WFk6.mjs} +2 -2
  80. package/dist/{transformer-CFrJQkUg.mjs.map → transformer-CFQ4WFk6.mjs.map} +1 -1
  81. package/dist/{transformer-DvDYyFRQ.cjs → transformer-Csl6fbgv.cjs} +2 -2
  82. package/dist/{transformer-DvDYyFRQ.cjs.map → transformer-Csl6fbgv.cjs.map} +1 -1
  83. package/dist/types-B1EIa_cQ.d.mts.map +1 -1
  84. package/dist/{types-BBeTDpHr.cjs → types-BMiHgsi4.cjs} +1 -1
  85. package/dist/{types-BBeTDpHr.cjs.map → types-BMiHgsi4.cjs.map} +1 -1
  86. package/dist/{types-CXYLhG_G.mjs → types-Cj59nqy7.mjs} +1 -1
  87. package/dist/{types-CXYLhG_G.mjs.map → types-Cj59nqy7.mjs.map} +1 -1
  88. package/dist/types-l7Rlb5Mg.d.cts.map +1 -1
  89. package/package.json +6 -3
  90. package/CHANGELOG.md +0 -51
  91. package/dist/middleware-D3v5SnSs.cjs.map +0 -1
  92. package/dist/middleware-Dy1ctr2N.mjs.map +0 -1
  93. package/dist/otel-DPKNyJ0t.mjs.map +0 -1
  94. package/dist/otel-DuiBeBPU.cjs.map +0 -1
  95. package/src/Telescope.ts +0 -561
  96. package/src/__tests__/Telescope.spec.ts +0 -438
  97. package/src/__tests__/pino-transport.spec.ts +0 -115
  98. package/src/__tests__/redact.spec.ts +0 -336
  99. package/src/adapters/__tests__/lambda.spec.ts +0 -318
  100. package/src/adapters/__tests__/middy.spec.ts +0 -741
  101. package/src/adapters/index.ts +0 -22
  102. package/src/adapters/lambda.ts +0 -432
  103. package/src/adapters/middy.ts +0 -402
  104. package/src/adapters/types.ts +0 -181
  105. package/src/core.ts +0 -51
  106. package/src/index.ts +0 -34
  107. package/src/instrumentation/__tests__/core.spec.ts +0 -248
  108. package/src/instrumentation/__tests__/hono.spec.ts +0 -348
  109. package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
  110. package/src/instrumentation/__tests__/otel.spec.ts +0 -291
  111. package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
  112. package/src/instrumentation/core.ts +0 -228
  113. package/src/instrumentation/hono.ts +0 -270
  114. package/src/instrumentation/http.ts +0 -306
  115. package/src/instrumentation/index.ts +0 -90
  116. package/src/instrumentation/middleware.ts +0 -297
  117. package/src/instrumentation/otel.ts +0 -271
  118. package/src/instrumentation/setup.ts +0 -246
  119. package/src/instrumentation/tracing.ts +0 -145
  120. package/src/logger/__tests__/console.spec.ts +0 -366
  121. package/src/logger/__tests__/pino.spec.ts +0 -217
  122. package/src/logger/console.ts +0 -230
  123. package/src/logger/pino.ts +0 -201
  124. package/src/metrics/MetricsAggregator.ts +0 -555
  125. package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
  126. package/src/metrics/index.ts +0 -2
  127. package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
  128. package/src/metrics/storage/MetricsStorage.ts +0 -74
  129. package/src/metrics/storage/index.ts +0 -3
  130. package/src/metrics/storage/types.ts +0 -243
  131. package/src/otlp/__tests__/receiver.spec.ts +0 -367
  132. package/src/otlp/__tests__/transformer.spec.ts +0 -455
  133. package/src/otlp/hono.ts +0 -120
  134. package/src/otlp/index.ts +0 -22
  135. package/src/otlp/receiver.ts +0 -162
  136. package/src/otlp/transformer.ts +0 -367
  137. package/src/otlp/types.ts +0 -364
  138. package/src/redact.ts +0 -116
  139. package/src/server/__tests__/hono.spec.ts +0 -814
  140. package/src/server/hono.ts +0 -442
  141. package/src/storage/__tests__/kysely.spec.ts +0 -715
  142. package/src/storage/__tests__/memory.spec.ts +0 -694
  143. package/src/storage/kysely.ts +0 -599
  144. package/src/storage/memory.ts +0 -204
  145. package/src/types.ts +0 -447
  146. package/src/ui-assets.ts +0 -40
  147. package/tsconfig.json +0 -9
  148. package/ui/CHANGELOG.md +0 -7
  149. package/ui/index.html +0 -12
  150. package/ui/node_modules/.bin/tsc +0 -21
  151. package/ui/node_modules/.bin/tsserver +0 -21
  152. package/ui/node_modules/.bin/vite +0 -21
  153. package/ui/package.json +0 -24
  154. package/ui/src/App.tsx +0 -595
  155. package/ui/src/api.ts +0 -88
  156. package/ui/src/components/ExceptionDetail.tsx +0 -100
  157. package/ui/src/components/FilterBar.tsx +0 -162
  158. package/ui/src/components/LogDetail.tsx +0 -91
  159. package/ui/src/components/RequestDetail.tsx +0 -143
  160. package/ui/src/main.tsx +0 -10
  161. package/ui/src/styles.css +0 -11
  162. package/ui/src/types.ts +0 -63
  163. package/ui/src/vite-env.d.ts +0 -1
  164. package/ui/src/vite-plugin-gkm-config.ts +0 -54
  165. package/ui/tsconfig.json +0 -20
  166. package/ui/tsconfig.tsbuildinfo +0 -1
  167. package/ui/vite.config.ts +0 -13
@@ -23,15 +23,20 @@
23
23
  */
24
24
  var KyselyStorage = class {
25
25
  db;
26
- requestsTable;
27
- exceptionsTable;
28
- logsTable;
26
+ /**
27
+ * Unqualified, and deliberately.
28
+ *
29
+ * These were `telescope_requests`, `telescope_exceptions` and
30
+ * `telescope_logs` — a prefix, which is what you reach for when the tables
31
+ * have to share a schema with an application's own. They do not: a telescope
32
+ * derived from a database is a schema tenant, so the names are just the
33
+ * names, and `DROP SCHEMA telescope CASCADE` is the whole cleanup.
34
+ */
35
+ requestsTable = "requests";
36
+ exceptionsTable = "exceptions";
37
+ logsTable = "logs";
29
38
  constructor(config) {
30
- this.db = config.db;
31
- const prefix = config.tablePrefix ?? "telescope";
32
- this.requestsTable = `${prefix}_requests`;
33
- this.exceptionsTable = `${prefix}_exceptions`;
34
- this.logsTable = `${prefix}_logs`;
39
+ this.db = config.schema ? config.db.withSchema(config.schema) : config.db;
35
40
  }
36
41
  async saveRequest(entry) {
37
42
  const row = this.requestToRow(entry);
@@ -254,8 +259,11 @@ var KyselyStorage = class {
254
259
  }
255
260
  };
256
261
  /**
257
- * SQL migration to create telescope tables.
258
- * Use this to set up the required tables in your database.
262
+ * SQL migration to create telescope's tables.
263
+ *
264
+ * Pass a schema to have them created in one, and to have `down` drop the whole
265
+ * schema rather than three tables. Pass nothing where the connection's
266
+ * `search_path` already places them.
259
267
  *
260
268
  * @example
261
269
  * ```typescript
@@ -273,11 +281,13 @@ var KyselyStorage = class {
273
281
  * }
274
282
  * ```
275
283
  */
276
- function getTelescopeMigration(tablePrefix = "telescope") {
284
+ function getTelescopeMigration(schema) {
285
+ const q = (name) => schema ? `${schema}.${name}` : name;
286
+ const idx = (name) => schema ? `${schema}_${name}` : name;
277
287
  return {
278
- up: `
279
- -- Telescope requests table
280
- CREATE TABLE IF NOT EXISTS ${tablePrefix}_requests (
288
+ up: `${schema ? `CREATE SCHEMA IF NOT EXISTS ${schema};\n` : ""}
289
+ -- Telescope requests
290
+ CREATE TABLE IF NOT EXISTS ${q("requests")} (
281
291
  id VARCHAR(21) PRIMARY KEY,
282
292
  method VARCHAR(10) NOT NULL,
283
293
  path TEXT NOT NULL,
@@ -295,15 +305,15 @@ CREATE TABLE IF NOT EXISTS ${tablePrefix}_requests (
295
305
  tags JSONB
296
306
  );
297
307
 
298
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_timestamp
299
- ON ${tablePrefix}_requests (timestamp DESC);
300
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_path
301
- ON ${tablePrefix}_requests (path);
302
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_status
303
- ON ${tablePrefix}_requests (status);
308
+ CREATE INDEX IF NOT EXISTS idx_${idx("requests")}_timestamp
309
+ ON ${q("requests")} (timestamp DESC);
310
+ CREATE INDEX IF NOT EXISTS idx_${idx("requests")}_path
311
+ ON ${q("requests")} (path);
312
+ CREATE INDEX IF NOT EXISTS idx_${idx("requests")}_status
313
+ ON ${q("requests")} (status);
304
314
 
305
- -- Telescope exceptions table
306
- CREATE TABLE IF NOT EXISTS ${tablePrefix}_exceptions (
315
+ -- Telescope exceptions
316
+ CREATE TABLE IF NOT EXISTS ${q("exceptions")} (
307
317
  id VARCHAR(21) PRIMARY KEY,
308
318
  name VARCHAR(255) NOT NULL,
309
319
  message TEXT NOT NULL,
@@ -315,13 +325,13 @@ CREATE TABLE IF NOT EXISTS ${tablePrefix}_exceptions (
315
325
  tags JSONB
316
326
  );
317
327
 
318
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_timestamp
319
- ON ${tablePrefix}_exceptions (timestamp DESC);
320
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_request_id
321
- ON ${tablePrefix}_exceptions (request_id);
328
+ CREATE INDEX IF NOT EXISTS idx_${idx("exceptions")}_timestamp
329
+ ON ${q("exceptions")} (timestamp DESC);
330
+ CREATE INDEX IF NOT EXISTS idx_${idx("exceptions")}_request_id
331
+ ON ${q("exceptions")} (request_id);
322
332
 
323
- -- Telescope logs table
324
- CREATE TABLE IF NOT EXISTS ${tablePrefix}_logs (
333
+ -- Telescope logs
334
+ CREATE TABLE IF NOT EXISTS ${q("logs")} (
325
335
  id VARCHAR(21) PRIMARY KEY,
326
336
  level VARCHAR(10) NOT NULL,
327
337
  message TEXT NOT NULL,
@@ -330,17 +340,17 @@ CREATE TABLE IF NOT EXISTS ${tablePrefix}_logs (
330
340
  timestamp TIMESTAMPTZ NOT NULL
331
341
  );
332
342
 
333
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_timestamp
334
- ON ${tablePrefix}_logs (timestamp DESC);
335
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_level
336
- ON ${tablePrefix}_logs (level);
337
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_request_id
338
- ON ${tablePrefix}_logs (request_id);
343
+ CREATE INDEX IF NOT EXISTS idx_${idx("logs")}_timestamp
344
+ ON ${q("logs")} (timestamp DESC);
345
+ CREATE INDEX IF NOT EXISTS idx_${idx("logs")}_level
346
+ ON ${q("logs")} (level);
347
+ CREATE INDEX IF NOT EXISTS idx_${idx("logs")}_request_id
348
+ ON ${q("logs")} (request_id);
339
349
  `,
340
- down: `
341
- DROP TABLE IF EXISTS ${tablePrefix}_logs;
342
- DROP TABLE IF EXISTS ${tablePrefix}_exceptions;
343
- DROP TABLE IF EXISTS ${tablePrefix}_requests;
350
+ down: schema ? `DROP SCHEMA IF EXISTS ${schema} CASCADE;\n` : `
351
+ DROP TABLE IF EXISTS logs;
352
+ DROP TABLE IF EXISTS exceptions;
353
+ DROP TABLE IF EXISTS requests;
344
354
  `
345
355
  };
346
356
  }
@@ -1 +1 @@
1
- {"version":3,"file":"kysely.cjs","names":["config: KyselyStorageConfig<DB>","entry: RequestEntry","entries: RequestEntry[]","options?: QueryOptions","row: TelescopeRequestTable","id: string","entry: ExceptionEntry","entries: ExceptionEntry[]","row: TelescopeExceptionTable","entry: LogEntry","entries: LogEntry[]","row: TelescopeLogTable","olderThan: Date","eb: any","query: any","value: unknown"],"sources":["../../src/storage/kysely.ts"],"sourcesContent":["import type { Kysely } from 'kysely';\nimport type {\n\tExceptionEntry,\n\tLogEntry,\n\tQueryOptions,\n\tRequestEntry,\n\tTelescopeStats,\n\tTelescopeStorage,\n} from '../types';\n\n/**\n * Database table interface for telescope requests.\n * Use this to define your telescope_requests table in your Kysely database schema.\n */\nexport interface TelescopeRequestTable {\n\tid: string;\n\tmethod: string;\n\tpath: string;\n\turl: string;\n\theaders: unknown;\n\tbody: unknown | null;\n\tquery: unknown | null;\n\tstatus: number;\n\tresponse_headers: unknown;\n\tresponse_body: unknown | null;\n\tduration: number;\n\ttimestamp: Date;\n\tip: string | null;\n\tuser_id: string | null;\n\ttags: unknown | null;\n}\n\n/**\n * Database table interface for telescope exceptions.\n */\nexport interface TelescopeExceptionTable {\n\tid: string;\n\tname: string;\n\tmessage: string;\n\tstack: unknown;\n\tsource: unknown | null;\n\trequest_id: string | null;\n\ttimestamp: Date;\n\thandled: boolean;\n\ttags: unknown | null;\n}\n\n/**\n * Database table interface for telescope logs.\n */\nexport interface TelescopeLogTable {\n\tid: string;\n\tlevel: string;\n\tmessage: string;\n\tcontext: unknown | null;\n\trequest_id: string | null;\n\ttimestamp: Date;\n}\n\n/**\n * Combined database interface for all telescope tables.\n * Use this to extend your database schema.\n *\n * @example\n * ```typescript\n * import type { TelescopeTables } from '@geekmidas/telescope/storage/kysely';\n *\n * interface Database extends TelescopeTables {\n * users: UserTable;\n * // ... other tables\n * }\n * ```\n */\nexport interface TelescopeTables {\n\ttelescope_requests: TelescopeRequestTable;\n\ttelescope_exceptions: TelescopeExceptionTable;\n\ttelescope_logs: TelescopeLogTable;\n}\n\n/**\n * Configuration for KyselyStorage.\n */\nexport interface KyselyStorageConfig<DB> {\n\t/** Kysely database instance */\n\tdb: Kysely<DB>;\n\t/**\n\t * Table name prefix (default: 'telescope').\n\t * Tables will be named: {prefix}_requests, {prefix}_exceptions, {prefix}_logs\n\t */\n\ttablePrefix?: string;\n}\n\n/**\n * Kysely-based storage implementation for Telescope.\n * Stores telescope data in PostgreSQL, MySQL, or SQLite using Kysely.\n *\n * @template DB - Your Kysely database schema (must include TelescopeTables)\n *\n * @example\n * ```typescript\n * import { Kysely, PostgresDialect } from 'kysely';\n * import { KyselyStorage, type TelescopeTables } from '@geekmidas/telescope/storage/kysely';\n *\n * interface Database extends TelescopeTables {\n * users: UserTable;\n * }\n *\n * const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });\n * const storage = new KyselyStorage({ db });\n *\n * const telescope = new Telescope({ storage });\n * ```\n */\nexport class KyselyStorage<DB> implements TelescopeStorage {\n\tprivate readonly db: Kysely<DB>;\n\tprivate readonly requestsTable: string;\n\tprivate readonly exceptionsTable: string;\n\tprivate readonly logsTable: string;\n\n\tconstructor(config: KyselyStorageConfig<DB>) {\n\t\tthis.db = config.db;\n\t\tconst prefix = config.tablePrefix ?? 'telescope';\n\t\tthis.requestsTable = `${prefix}_requests`;\n\t\tthis.exceptionsTable = `${prefix}_exceptions`;\n\t\tthis.logsTable = `${prefix}_logs`;\n\t}\n\n\t// ============================================\n\t// Requests\n\t// ============================================\n\n\tasync saveRequest(entry: RequestEntry): Promise<void> {\n\t\tconst row = this.requestToRow(entry);\n\t\tawait (this.db as any).insertInto(this.requestsTable).values(row).execute();\n\t}\n\n\tasync saveRequests(entries: RequestEntry[]): Promise<void> {\n\t\tif (entries.length === 0) return;\n\n\t\tconst rows = entries.map((e) => this.requestToRow(e));\n\t\tawait (this.db as any)\n\t\t\t.insertInto(this.requestsTable)\n\t\t\t.values(rows)\n\t\t\t.execute();\n\t}\n\n\tasync getRequests(options?: QueryOptions): Promise<RequestEntry[]> {\n\t\tlet query = (this.db as any)\n\t\t\t.selectFrom(this.requestsTable)\n\t\t\t.selectAll()\n\t\t\t.orderBy('timestamp', 'desc');\n\n\t\t// Apply request-specific filters\n\t\tif (options?.method) {\n\t\t\tquery = query.where('method', '=', options.method);\n\t\t}\n\n\t\tif (options?.status) {\n\t\t\tconst statusFilter = options.status;\n\t\t\tif (statusFilter.endsWith('xx')) {\n\t\t\t\t// Handle status ranges like \"2xx\", \"4xx\", \"5xx\"\n\t\t\t\tconst firstChar = statusFilter[0];\n\t\t\t\tif (firstChar) {\n\t\t\t\t\tconst category = parseInt(firstChar, 10);\n\t\t\t\t\tconst minStatus = category * 100;\n\t\t\t\t\tconst maxStatus = minStatus + 99;\n\t\t\t\t\tquery = query\n\t\t\t\t\t\t.where('status', '>=', minStatus)\n\t\t\t\t\t\t.where('status', '<=', maxStatus);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Handle exact status codes\n\t\t\t\tquery = query.where('status', '=', parseInt(statusFilter, 10));\n\t\t\t}\n\t\t}\n\n\t\tquery = this.applyQueryOptions(query, options);\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row: TelescopeRequestTable) => this.rowToRequest(row));\n\t}\n\n\tasync getRequest(id: string): Promise<RequestEntry | null> {\n\t\tconst row = await (this.db as any)\n\t\t\t.selectFrom(this.requestsTable)\n\t\t\t.selectAll()\n\t\t\t.where('id', '=', id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToRequest(row) : null;\n\t}\n\n\t// ============================================\n\t// Exceptions\n\t// ============================================\n\n\tasync saveException(entry: ExceptionEntry): Promise<void> {\n\t\tconst row = this.exceptionToRow(entry);\n\t\tawait (this.db as any)\n\t\t\t.insertInto(this.exceptionsTable)\n\t\t\t.values(row)\n\t\t\t.execute();\n\t}\n\n\tasync saveExceptions(entries: ExceptionEntry[]): Promise<void> {\n\t\tif (entries.length === 0) return;\n\n\t\tconst rows = entries.map((e) => this.exceptionToRow(e));\n\t\tawait (this.db as any)\n\t\t\t.insertInto(this.exceptionsTable)\n\t\t\t.values(rows)\n\t\t\t.execute();\n\t}\n\n\tasync getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]> {\n\t\tlet query = (this.db as any)\n\t\t\t.selectFrom(this.exceptionsTable)\n\t\t\t.selectAll()\n\t\t\t.orderBy('timestamp', 'desc');\n\n\t\tquery = this.applyQueryOptions(query, options);\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row: TelescopeExceptionTable) => this.rowToException(row));\n\t}\n\n\tasync getException(id: string): Promise<ExceptionEntry | null> {\n\t\tconst row = await (this.db as any)\n\t\t\t.selectFrom(this.exceptionsTable)\n\t\t\t.selectAll()\n\t\t\t.where('id', '=', id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToException(row) : null;\n\t}\n\n\t// ============================================\n\t// Logs\n\t// ============================================\n\n\tasync saveLog(entry: LogEntry): Promise<void> {\n\t\tconst row = this.logToRow(entry);\n\t\tawait (this.db as any).insertInto(this.logsTable).values(row).execute();\n\t}\n\n\tasync saveLogs(entries: LogEntry[]): Promise<void> {\n\t\tif (entries.length === 0) return;\n\n\t\tconst rows = entries.map((e) => this.logToRow(e));\n\t\tawait (this.db as any).insertInto(this.logsTable).values(rows).execute();\n\t}\n\n\tasync getLogs(options?: QueryOptions): Promise<LogEntry[]> {\n\t\tlet query = (this.db as any)\n\t\t\t.selectFrom(this.logsTable)\n\t\t\t.selectAll()\n\t\t\t.orderBy('timestamp', 'desc');\n\n\t\t// Apply log-specific filters\n\t\tif (options?.level) {\n\t\t\tquery = query.where('level', '=', options.level);\n\t\t}\n\n\t\tquery = this.applyQueryOptions(query, options);\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row: TelescopeLogTable) => this.rowToLog(row));\n\t}\n\n\t// ============================================\n\t// Cleanup\n\t// ============================================\n\n\tasync prune(olderThan: Date): Promise<number> {\n\t\tconst results = await Promise.all([\n\t\t\t(this.db as any)\n\t\t\t\t.deleteFrom(this.requestsTable)\n\t\t\t\t.where('timestamp', '<', olderThan)\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.deleteFrom(this.exceptionsTable)\n\t\t\t\t.where('timestamp', '<', olderThan)\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.deleteFrom(this.logsTable)\n\t\t\t\t.where('timestamp', '<', olderThan)\n\t\t\t\t.executeTakeFirst(),\n\t\t]);\n\n\t\treturn results.reduce(\n\t\t\t(sum, result) => sum + Number(result.numDeletedRows ?? 0),\n\t\t\t0,\n\t\t);\n\t}\n\n\t// ============================================\n\t// Stats\n\t// ============================================\n\n\tasync getStats(): Promise<TelescopeStats> {\n\t\tconst [requestsResult, exceptionsResult, logsResult] = await Promise.all([\n\t\t\t(this.db as any)\n\t\t\t\t.selectFrom(this.requestsTable)\n\t\t\t\t.select((eb: any) => [\n\t\t\t\t\teb.fn.count('id').as('count'),\n\t\t\t\t\teb.fn.min('timestamp').as('oldest'),\n\t\t\t\t\teb.fn.max('timestamp').as('newest'),\n\t\t\t\t])\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.selectFrom(this.exceptionsTable)\n\t\t\t\t.select((eb: any) => [\n\t\t\t\t\teb.fn.count('id').as('count'),\n\t\t\t\t\teb.fn.min('timestamp').as('oldest'),\n\t\t\t\t\teb.fn.max('timestamp').as('newest'),\n\t\t\t\t])\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.selectFrom(this.logsTable)\n\t\t\t\t.select((eb: any) => [\n\t\t\t\t\teb.fn.count('id').as('count'),\n\t\t\t\t\teb.fn.min('timestamp').as('oldest'),\n\t\t\t\t\teb.fn.max('timestamp').as('newest'),\n\t\t\t\t])\n\t\t\t\t.executeTakeFirst(),\n\t\t]);\n\n\t\tconst allDates = [\n\t\t\trequestsResult?.oldest,\n\t\t\trequestsResult?.newest,\n\t\t\texceptionsResult?.oldest,\n\t\t\texceptionsResult?.newest,\n\t\t\tlogsResult?.oldest,\n\t\t\tlogsResult?.newest,\n\t\t]\n\t\t\t.filter((d): d is Date => d != null)\n\t\t\t.sort((a, b) => new Date(a).getTime() - new Date(b).getTime());\n\n\t\tconst newestDate = allDates[allDates.length - 1];\n\t\treturn {\n\t\t\trequests: Number(requestsResult?.count ?? 0),\n\t\t\texceptions: Number(exceptionsResult?.count ?? 0),\n\t\t\tlogs: Number(logsResult?.count ?? 0),\n\t\t\toldestEntry: allDates[0] ? new Date(allDates[0]) : undefined,\n\t\t\tnewestEntry: newestDate ? new Date(newestDate) : undefined,\n\t\t};\n\t}\n\n\t// ============================================\n\t// Private Helpers\n\t// ============================================\n\n\tprivate applyQueryOptions(query: any, options?: QueryOptions): any {\n\t\tif (!options) {\n\t\t\treturn query.limit(50);\n\t\t}\n\n\t\tif (options.after) {\n\t\t\tquery = query.where('timestamp', '>=', options.after);\n\t\t}\n\n\t\tif (options.before) {\n\t\t\tquery = query.where('timestamp', '<=', options.before);\n\t\t}\n\n\t\tif (options.search) {\n\t\t\t// Search in relevant text fields - using ILIKE for case-insensitive\n\t\t\t// This is a simple implementation; for production you'd want full-text search\n\t\t\tquery = query.where((eb: any) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb('message', 'ilike', `%${options.search}%`),\n\t\t\t\t\teb('path', 'ilike', `%${options.search}%`),\n\t\t\t\t\teb('url', 'ilike', `%${options.search}%`),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\t// Tags filter would require array contains operation\n\t\t// which is database-specific (PostgreSQL: @>, etc.)\n\n\t\tconst limit = options.limit ?? 50;\n\t\tconst offset = options.offset ?? 0;\n\n\t\treturn query.limit(limit).offset(offset);\n\t}\n\n\tprivate requestToRow(entry: RequestEntry): TelescopeRequestTable {\n\t\treturn {\n\t\t\tid: entry.id,\n\t\t\tmethod: entry.method,\n\t\t\tpath: entry.path,\n\t\t\turl: entry.url,\n\t\t\theaders: entry.headers,\n\t\t\tbody: entry.body ?? null,\n\t\t\tquery: entry.query ?? null,\n\t\t\tstatus: entry.status,\n\t\t\tresponse_headers: entry.responseHeaders,\n\t\t\tresponse_body: entry.responseBody ?? null,\n\t\t\tduration: entry.duration,\n\t\t\ttimestamp: entry.timestamp,\n\t\t\tip: entry.ip ?? null,\n\t\t\tuser_id: entry.userId ?? null,\n\t\t\ttags: entry.tags ?? null,\n\t\t};\n\t}\n\n\tprivate rowToRequest(row: TelescopeRequestTable): RequestEntry {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tmethod: row.method,\n\t\t\tpath: row.path,\n\t\t\turl: row.url,\n\t\t\theaders: this.parseJson(row.headers) as Record<string, string>,\n\t\t\tbody: row.body ? this.parseJson(row.body) : undefined,\n\t\t\tquery: row.query\n\t\t\t\t? (this.parseJson(row.query) as Record<string, string>)\n\t\t\t\t: undefined,\n\t\t\tstatus: row.status,\n\t\t\tresponseHeaders: this.parseJson(row.response_headers) as Record<\n\t\t\t\tstring,\n\t\t\t\tstring\n\t\t\t>,\n\t\t\tresponseBody: row.response_body\n\t\t\t\t? this.parseJson(row.response_body)\n\t\t\t\t: undefined,\n\t\t\tduration: row.duration,\n\t\t\ttimestamp: new Date(row.timestamp),\n\t\t\tip: row.ip ?? undefined,\n\t\t\tuserId: row.user_id ?? undefined,\n\t\t\ttags: row.tags ? (this.parseJson(row.tags) as string[]) : undefined,\n\t\t};\n\t}\n\n\tprivate exceptionToRow(entry: ExceptionEntry): TelescopeExceptionTable {\n\t\treturn {\n\t\t\tid: entry.id,\n\t\t\tname: entry.name,\n\t\t\tmessage: entry.message,\n\t\t\tstack: entry.stack,\n\t\t\tsource: entry.source ?? null,\n\t\t\trequest_id: entry.requestId ?? null,\n\t\t\ttimestamp: entry.timestamp,\n\t\t\thandled: entry.handled,\n\t\t\ttags: entry.tags ?? null,\n\t\t};\n\t}\n\n\tprivate rowToException(row: TelescopeExceptionTable): ExceptionEntry {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tmessage: row.message,\n\t\t\tstack: this.parseJson(row.stack) as ExceptionEntry['stack'],\n\t\t\tsource: row.source\n\t\t\t\t? (this.parseJson(row.source) as ExceptionEntry['source'])\n\t\t\t\t: undefined,\n\t\t\trequestId: row.request_id ?? undefined,\n\t\t\ttimestamp: new Date(row.timestamp),\n\t\t\thandled: row.handled,\n\t\t\ttags: row.tags ? (this.parseJson(row.tags) as string[]) : undefined,\n\t\t};\n\t}\n\n\tprivate logToRow(entry: LogEntry): TelescopeLogTable {\n\t\treturn {\n\t\t\tid: entry.id,\n\t\t\tlevel: entry.level,\n\t\t\tmessage: entry.message,\n\t\t\tcontext: entry.context ?? null,\n\t\t\trequest_id: entry.requestId ?? null,\n\t\t\ttimestamp: entry.timestamp,\n\t\t};\n\t}\n\n\tprivate rowToLog(row: TelescopeLogTable): LogEntry {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tlevel: row.level as LogEntry['level'],\n\t\t\tmessage: row.message,\n\t\t\tcontext: row.context\n\t\t\t\t? (this.parseJson(row.context) as Record<string, unknown>)\n\t\t\t\t: undefined,\n\t\t\trequestId: row.request_id ?? undefined,\n\t\t\ttimestamp: new Date(row.timestamp),\n\t\t};\n\t}\n\n\t/**\n\t * Parse a JSON value that may already be parsed (e.g., from jsonb columns).\n\t */\n\tprivate parseJson(value: unknown): unknown {\n\t\tif (typeof value === 'object' && value !== null) {\n\t\t\treturn value;\n\t\t}\n\t\tif (typeof value === 'string') {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(value);\n\t\t\t} catch {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n}\n\n/**\n * SQL migration to create telescope tables.\n * Use this to set up the required tables in your database.\n *\n * @example\n * ```typescript\n * import { getTelescopeMigration } from '@geekmidas/telescope/storage/kysely';\n *\n * // In your migration file\n * export async function up(db: Kysely<any>): Promise<void> {\n * const migration = getTelescopeMigration();\n * await db.schema.executeRaw(migration.up).execute();\n * }\n *\n * export async function down(db: Kysely<any>): Promise<void> {\n * const migration = getTelescopeMigration();\n * await db.schema.executeRaw(migration.down).execute();\n * }\n * ```\n */\nexport function getTelescopeMigration(tablePrefix = 'telescope'): {\n\tup: string;\n\tdown: string;\n} {\n\treturn {\n\t\tup: `\n-- Telescope requests table\nCREATE TABLE IF NOT EXISTS ${tablePrefix}_requests (\n id VARCHAR(21) PRIMARY KEY,\n method VARCHAR(10) NOT NULL,\n path TEXT NOT NULL,\n url TEXT NOT NULL,\n headers JSONB NOT NULL,\n body JSONB,\n query JSONB,\n status INTEGER NOT NULL,\n response_headers JSONB NOT NULL,\n response_body JSONB,\n duration DOUBLE PRECISION NOT NULL,\n timestamp TIMESTAMPTZ NOT NULL,\n ip VARCHAR(45),\n user_id VARCHAR(255),\n tags JSONB\n);\n\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_timestamp\n ON ${tablePrefix}_requests (timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_path\n ON ${tablePrefix}_requests (path);\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_status\n ON ${tablePrefix}_requests (status);\n\n-- Telescope exceptions table\nCREATE TABLE IF NOT EXISTS ${tablePrefix}_exceptions (\n id VARCHAR(21) PRIMARY KEY,\n name VARCHAR(255) NOT NULL,\n message TEXT NOT NULL,\n stack JSONB NOT NULL,\n source JSONB,\n request_id VARCHAR(21),\n timestamp TIMESTAMPTZ NOT NULL,\n handled BOOLEAN NOT NULL DEFAULT FALSE,\n tags JSONB\n);\n\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_timestamp\n ON ${tablePrefix}_exceptions (timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_request_id\n ON ${tablePrefix}_exceptions (request_id);\n\n-- Telescope logs table\nCREATE TABLE IF NOT EXISTS ${tablePrefix}_logs (\n id VARCHAR(21) PRIMARY KEY,\n level VARCHAR(10) NOT NULL,\n message TEXT NOT NULL,\n context JSONB,\n request_id VARCHAR(21),\n timestamp TIMESTAMPTZ NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_timestamp\n ON ${tablePrefix}_logs (timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_level\n ON ${tablePrefix}_logs (level);\nCREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_request_id\n ON ${tablePrefix}_logs (request_id);\n`,\n\t\tdown: `\nDROP TABLE IF EXISTS ${tablePrefix}_logs;\nDROP TABLE IF EXISTS ${tablePrefix}_exceptions;\nDROP TABLE IF EXISTS ${tablePrefix}_requests;\n`,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kysely.cjs","names":["config: KyselyStorageConfig<DB>","entry: RequestEntry","entries: RequestEntry[]","options?: QueryOptions","row: TelescopeRequestTable","id: string","entry: ExceptionEntry","entries: ExceptionEntry[]","row: TelescopeExceptionTable","entry: LogEntry","entries: LogEntry[]","row: TelescopeLogTable","olderThan: Date","eb: any","query: any","value: unknown","schema?: string","name: string"],"sources":["../../src/storage/kysely.ts"],"sourcesContent":["import type { Kysely } from 'kysely';\nimport type {\n\tExceptionEntry,\n\tLogEntry,\n\tQueryOptions,\n\tRequestEntry,\n\tTelescopeStats,\n\tTelescopeStorage,\n} from '../types';\n\n/**\n * Database table interface for telescope requests.\n * Use this to define your telescope_requests table in your Kysely database schema.\n */\nexport interface TelescopeRequestTable {\n\tid: string;\n\tmethod: string;\n\tpath: string;\n\turl: string;\n\theaders: unknown;\n\tbody: unknown | null;\n\tquery: unknown | null;\n\tstatus: number;\n\tresponse_headers: unknown;\n\tresponse_body: unknown | null;\n\tduration: number;\n\ttimestamp: Date;\n\tip: string | null;\n\tuser_id: string | null;\n\ttags: unknown | null;\n}\n\n/**\n * Database table interface for telescope exceptions.\n */\nexport interface TelescopeExceptionTable {\n\tid: string;\n\tname: string;\n\tmessage: string;\n\tstack: unknown;\n\tsource: unknown | null;\n\trequest_id: string | null;\n\ttimestamp: Date;\n\thandled: boolean;\n\ttags: unknown | null;\n}\n\n/**\n * Database table interface for telescope logs.\n */\nexport interface TelescopeLogTable {\n\tid: string;\n\tlevel: string;\n\tmessage: string;\n\tcontext: unknown | null;\n\trequest_id: string | null;\n\ttimestamp: Date;\n}\n\n/**\n * Combined database interface for all telescope tables.\n * Use this to extend your database schema.\n *\n * @example\n * ```typescript\n * import type { TelescopeTables } from '@geekmidas/telescope/storage/kysely';\n *\n * interface Database extends TelescopeTables {\n * users: UserTable;\n * // ... other tables\n * }\n * ```\n */\nexport interface TelescopeTables {\n\trequests: TelescopeRequestTable;\n\texceptions: TelescopeExceptionTable;\n\tlogs: TelescopeLogTable;\n}\n\n/**\n * Configuration for KyselyStorage.\n */\nexport interface KyselyStorageConfig<DB> {\n\t/** Kysely database instance */\n\tdb: Kysely<DB>;\n\t/**\n\t * The schema holding `requests`, `exceptions` and `logs`.\n\t *\n\t * Normally omitted. A telescope derived from a database gets a schema of its\n\t * own and a role whose `search_path` is pinned to it, so an unqualified name\n\t * already resolves there — which is the same rule every other tenant here\n\t * follows, and the reason a connection string never has to remember where\n\t * its tables live.\n\t *\n\t * Name one only when the connection is *not* pinned: a shared pool, a\n\t * migration run as an owner whose `search_path` finds `public` first.\n\t */\n\tschema?: string;\n}\n\n/**\n * Kysely-based storage implementation for Telescope.\n * Stores telescope data in PostgreSQL, MySQL, or SQLite using Kysely.\n *\n * @template DB - Your Kysely database schema (must include TelescopeTables)\n *\n * @example\n * ```typescript\n * import { Kysely, PostgresDialect } from 'kysely';\n * import { KyselyStorage, type TelescopeTables } from '@geekmidas/telescope/storage/kysely';\n *\n * interface Database extends TelescopeTables {\n * users: UserTable;\n * }\n *\n * const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });\n * const storage = new KyselyStorage({ db });\n *\n * const telescope = new Telescope({ storage });\n * ```\n */\nexport class KyselyStorage<DB> implements TelescopeStorage {\n\tprivate readonly db: Kysely<DB>;\n\n\t/**\n\t * Unqualified, and deliberately.\n\t *\n\t * These were `telescope_requests`, `telescope_exceptions` and\n\t * `telescope_logs` — a prefix, which is what you reach for when the tables\n\t * have to share a schema with an application's own. They do not: a telescope\n\t * derived from a database is a schema tenant, so the names are just the\n\t * names, and `DROP SCHEMA telescope CASCADE` is the whole cleanup.\n\t */\n\tprivate readonly requestsTable = 'requests';\n\tprivate readonly exceptionsTable = 'exceptions';\n\tprivate readonly logsTable = 'logs';\n\n\tconstructor(config: KyselyStorageConfig<DB>) {\n\t\t// `withSchema` only where the connection is not already pinned to one.\n\t\tthis.db = config.schema\n\t\t\t? (config.db.withSchema(config.schema) as Kysely<DB>)\n\t\t\t: config.db;\n\t}\n\n\t// ============================================\n\t// Requests\n\t// ============================================\n\n\tasync saveRequest(entry: RequestEntry): Promise<void> {\n\t\tconst row = this.requestToRow(entry);\n\t\tawait (this.db as any).insertInto(this.requestsTable).values(row).execute();\n\t}\n\n\tasync saveRequests(entries: RequestEntry[]): Promise<void> {\n\t\tif (entries.length === 0) return;\n\n\t\tconst rows = entries.map((e) => this.requestToRow(e));\n\t\tawait (this.db as any)\n\t\t\t.insertInto(this.requestsTable)\n\t\t\t.values(rows)\n\t\t\t.execute();\n\t}\n\n\tasync getRequests(options?: QueryOptions): Promise<RequestEntry[]> {\n\t\tlet query = (this.db as any)\n\t\t\t.selectFrom(this.requestsTable)\n\t\t\t.selectAll()\n\t\t\t.orderBy('timestamp', 'desc');\n\n\t\t// Apply request-specific filters\n\t\tif (options?.method) {\n\t\t\tquery = query.where('method', '=', options.method);\n\t\t}\n\n\t\tif (options?.status) {\n\t\t\tconst statusFilter = options.status;\n\t\t\tif (statusFilter.endsWith('xx')) {\n\t\t\t\t// Handle status ranges like \"2xx\", \"4xx\", \"5xx\"\n\t\t\t\tconst firstChar = statusFilter[0];\n\t\t\t\tif (firstChar) {\n\t\t\t\t\tconst category = parseInt(firstChar, 10);\n\t\t\t\t\tconst minStatus = category * 100;\n\t\t\t\t\tconst maxStatus = minStatus + 99;\n\t\t\t\t\tquery = query\n\t\t\t\t\t\t.where('status', '>=', minStatus)\n\t\t\t\t\t\t.where('status', '<=', maxStatus);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Handle exact status codes\n\t\t\t\tquery = query.where('status', '=', parseInt(statusFilter, 10));\n\t\t\t}\n\t\t}\n\n\t\tquery = this.applyQueryOptions(query, options);\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row: TelescopeRequestTable) => this.rowToRequest(row));\n\t}\n\n\tasync getRequest(id: string): Promise<RequestEntry | null> {\n\t\tconst row = await (this.db as any)\n\t\t\t.selectFrom(this.requestsTable)\n\t\t\t.selectAll()\n\t\t\t.where('id', '=', id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToRequest(row) : null;\n\t}\n\n\t// ============================================\n\t// Exceptions\n\t// ============================================\n\n\tasync saveException(entry: ExceptionEntry): Promise<void> {\n\t\tconst row = this.exceptionToRow(entry);\n\t\tawait (this.db as any)\n\t\t\t.insertInto(this.exceptionsTable)\n\t\t\t.values(row)\n\t\t\t.execute();\n\t}\n\n\tasync saveExceptions(entries: ExceptionEntry[]): Promise<void> {\n\t\tif (entries.length === 0) return;\n\n\t\tconst rows = entries.map((e) => this.exceptionToRow(e));\n\t\tawait (this.db as any)\n\t\t\t.insertInto(this.exceptionsTable)\n\t\t\t.values(rows)\n\t\t\t.execute();\n\t}\n\n\tasync getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]> {\n\t\tlet query = (this.db as any)\n\t\t\t.selectFrom(this.exceptionsTable)\n\t\t\t.selectAll()\n\t\t\t.orderBy('timestamp', 'desc');\n\n\t\tquery = this.applyQueryOptions(query, options);\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row: TelescopeExceptionTable) => this.rowToException(row));\n\t}\n\n\tasync getException(id: string): Promise<ExceptionEntry | null> {\n\t\tconst row = await (this.db as any)\n\t\t\t.selectFrom(this.exceptionsTable)\n\t\t\t.selectAll()\n\t\t\t.where('id', '=', id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToException(row) : null;\n\t}\n\n\t// ============================================\n\t// Logs\n\t// ============================================\n\n\tasync saveLog(entry: LogEntry): Promise<void> {\n\t\tconst row = this.logToRow(entry);\n\t\tawait (this.db as any).insertInto(this.logsTable).values(row).execute();\n\t}\n\n\tasync saveLogs(entries: LogEntry[]): Promise<void> {\n\t\tif (entries.length === 0) return;\n\n\t\tconst rows = entries.map((e) => this.logToRow(e));\n\t\tawait (this.db as any).insertInto(this.logsTable).values(rows).execute();\n\t}\n\n\tasync getLogs(options?: QueryOptions): Promise<LogEntry[]> {\n\t\tlet query = (this.db as any)\n\t\t\t.selectFrom(this.logsTable)\n\t\t\t.selectAll()\n\t\t\t.orderBy('timestamp', 'desc');\n\n\t\t// Apply log-specific filters\n\t\tif (options?.level) {\n\t\t\tquery = query.where('level', '=', options.level);\n\t\t}\n\n\t\tquery = this.applyQueryOptions(query, options);\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row: TelescopeLogTable) => this.rowToLog(row));\n\t}\n\n\t// ============================================\n\t// Cleanup\n\t// ============================================\n\n\tasync prune(olderThan: Date): Promise<number> {\n\t\tconst results = await Promise.all([\n\t\t\t(this.db as any)\n\t\t\t\t.deleteFrom(this.requestsTable)\n\t\t\t\t.where('timestamp', '<', olderThan)\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.deleteFrom(this.exceptionsTable)\n\t\t\t\t.where('timestamp', '<', olderThan)\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.deleteFrom(this.logsTable)\n\t\t\t\t.where('timestamp', '<', olderThan)\n\t\t\t\t.executeTakeFirst(),\n\t\t]);\n\n\t\treturn results.reduce(\n\t\t\t(sum, result) => sum + Number(result.numDeletedRows ?? 0),\n\t\t\t0,\n\t\t);\n\t}\n\n\t// ============================================\n\t// Stats\n\t// ============================================\n\n\tasync getStats(): Promise<TelescopeStats> {\n\t\tconst [requestsResult, exceptionsResult, logsResult] = await Promise.all([\n\t\t\t(this.db as any)\n\t\t\t\t.selectFrom(this.requestsTable)\n\t\t\t\t.select((eb: any) => [\n\t\t\t\t\teb.fn.count('id').as('count'),\n\t\t\t\t\teb.fn.min('timestamp').as('oldest'),\n\t\t\t\t\teb.fn.max('timestamp').as('newest'),\n\t\t\t\t])\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.selectFrom(this.exceptionsTable)\n\t\t\t\t.select((eb: any) => [\n\t\t\t\t\teb.fn.count('id').as('count'),\n\t\t\t\t\teb.fn.min('timestamp').as('oldest'),\n\t\t\t\t\teb.fn.max('timestamp').as('newest'),\n\t\t\t\t])\n\t\t\t\t.executeTakeFirst(),\n\t\t\t(this.db as any)\n\t\t\t\t.selectFrom(this.logsTable)\n\t\t\t\t.select((eb: any) => [\n\t\t\t\t\teb.fn.count('id').as('count'),\n\t\t\t\t\teb.fn.min('timestamp').as('oldest'),\n\t\t\t\t\teb.fn.max('timestamp').as('newest'),\n\t\t\t\t])\n\t\t\t\t.executeTakeFirst(),\n\t\t]);\n\n\t\tconst allDates = [\n\t\t\trequestsResult?.oldest,\n\t\t\trequestsResult?.newest,\n\t\t\texceptionsResult?.oldest,\n\t\t\texceptionsResult?.newest,\n\t\t\tlogsResult?.oldest,\n\t\t\tlogsResult?.newest,\n\t\t]\n\t\t\t.filter((d): d is Date => d != null)\n\t\t\t.sort((a, b) => new Date(a).getTime() - new Date(b).getTime());\n\n\t\tconst newestDate = allDates[allDates.length - 1];\n\t\treturn {\n\t\t\trequests: Number(requestsResult?.count ?? 0),\n\t\t\texceptions: Number(exceptionsResult?.count ?? 0),\n\t\t\tlogs: Number(logsResult?.count ?? 0),\n\t\t\toldestEntry: allDates[0] ? new Date(allDates[0]) : undefined,\n\t\t\tnewestEntry: newestDate ? new Date(newestDate) : undefined,\n\t\t};\n\t}\n\n\t// ============================================\n\t// Private Helpers\n\t// ============================================\n\n\tprivate applyQueryOptions(query: any, options?: QueryOptions): any {\n\t\tif (!options) {\n\t\t\treturn query.limit(50);\n\t\t}\n\n\t\tif (options.after) {\n\t\t\tquery = query.where('timestamp', '>=', options.after);\n\t\t}\n\n\t\tif (options.before) {\n\t\t\tquery = query.where('timestamp', '<=', options.before);\n\t\t}\n\n\t\tif (options.search) {\n\t\t\t// Search in relevant text fields - using ILIKE for case-insensitive\n\t\t\t// This is a simple implementation; for production you'd want full-text search\n\t\t\tquery = query.where((eb: any) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb('message', 'ilike', `%${options.search}%`),\n\t\t\t\t\teb('path', 'ilike', `%${options.search}%`),\n\t\t\t\t\teb('url', 'ilike', `%${options.search}%`),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\t// Tags filter would require array contains operation\n\t\t// which is database-specific (PostgreSQL: @>, etc.)\n\n\t\tconst limit = options.limit ?? 50;\n\t\tconst offset = options.offset ?? 0;\n\n\t\treturn query.limit(limit).offset(offset);\n\t}\n\n\tprivate requestToRow(entry: RequestEntry): TelescopeRequestTable {\n\t\treturn {\n\t\t\tid: entry.id,\n\t\t\tmethod: entry.method,\n\t\t\tpath: entry.path,\n\t\t\turl: entry.url,\n\t\t\theaders: entry.headers,\n\t\t\tbody: entry.body ?? null,\n\t\t\tquery: entry.query ?? null,\n\t\t\tstatus: entry.status,\n\t\t\tresponse_headers: entry.responseHeaders,\n\t\t\tresponse_body: entry.responseBody ?? null,\n\t\t\tduration: entry.duration,\n\t\t\ttimestamp: entry.timestamp,\n\t\t\tip: entry.ip ?? null,\n\t\t\tuser_id: entry.userId ?? null,\n\t\t\ttags: entry.tags ?? null,\n\t\t};\n\t}\n\n\tprivate rowToRequest(row: TelescopeRequestTable): RequestEntry {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tmethod: row.method,\n\t\t\tpath: row.path,\n\t\t\turl: row.url,\n\t\t\theaders: this.parseJson(row.headers) as Record<string, string>,\n\t\t\tbody: row.body ? this.parseJson(row.body) : undefined,\n\t\t\tquery: row.query\n\t\t\t\t? (this.parseJson(row.query) as Record<string, string>)\n\t\t\t\t: undefined,\n\t\t\tstatus: row.status,\n\t\t\tresponseHeaders: this.parseJson(row.response_headers) as Record<\n\t\t\t\tstring,\n\t\t\t\tstring\n\t\t\t>,\n\t\t\tresponseBody: row.response_body\n\t\t\t\t? this.parseJson(row.response_body)\n\t\t\t\t: undefined,\n\t\t\tduration: row.duration,\n\t\t\ttimestamp: new Date(row.timestamp),\n\t\t\tip: row.ip ?? undefined,\n\t\t\tuserId: row.user_id ?? undefined,\n\t\t\ttags: row.tags ? (this.parseJson(row.tags) as string[]) : undefined,\n\t\t};\n\t}\n\n\tprivate exceptionToRow(entry: ExceptionEntry): TelescopeExceptionTable {\n\t\treturn {\n\t\t\tid: entry.id,\n\t\t\tname: entry.name,\n\t\t\tmessage: entry.message,\n\t\t\tstack: entry.stack,\n\t\t\tsource: entry.source ?? null,\n\t\t\trequest_id: entry.requestId ?? null,\n\t\t\ttimestamp: entry.timestamp,\n\t\t\thandled: entry.handled,\n\t\t\ttags: entry.tags ?? null,\n\t\t};\n\t}\n\n\tprivate rowToException(row: TelescopeExceptionTable): ExceptionEntry {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tmessage: row.message,\n\t\t\tstack: this.parseJson(row.stack) as ExceptionEntry['stack'],\n\t\t\tsource: row.source\n\t\t\t\t? (this.parseJson(row.source) as ExceptionEntry['source'])\n\t\t\t\t: undefined,\n\t\t\trequestId: row.request_id ?? undefined,\n\t\t\ttimestamp: new Date(row.timestamp),\n\t\t\thandled: row.handled,\n\t\t\ttags: row.tags ? (this.parseJson(row.tags) as string[]) : undefined,\n\t\t};\n\t}\n\n\tprivate logToRow(entry: LogEntry): TelescopeLogTable {\n\t\treturn {\n\t\t\tid: entry.id,\n\t\t\tlevel: entry.level,\n\t\t\tmessage: entry.message,\n\t\t\tcontext: entry.context ?? null,\n\t\t\trequest_id: entry.requestId ?? null,\n\t\t\ttimestamp: entry.timestamp,\n\t\t};\n\t}\n\n\tprivate rowToLog(row: TelescopeLogTable): LogEntry {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tlevel: row.level as LogEntry['level'],\n\t\t\tmessage: row.message,\n\t\t\tcontext: row.context\n\t\t\t\t? (this.parseJson(row.context) as Record<string, unknown>)\n\t\t\t\t: undefined,\n\t\t\trequestId: row.request_id ?? undefined,\n\t\t\ttimestamp: new Date(row.timestamp),\n\t\t};\n\t}\n\n\t/**\n\t * Parse a JSON value that may already be parsed (e.g., from jsonb columns).\n\t */\n\tprivate parseJson(value: unknown): unknown {\n\t\tif (typeof value === 'object' && value !== null) {\n\t\t\treturn value;\n\t\t}\n\t\tif (typeof value === 'string') {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(value);\n\t\t\t} catch {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n}\n\n/**\n * SQL migration to create telescope's tables.\n *\n * Pass a schema to have them created in one, and to have `down` drop the whole\n * schema rather than three tables. Pass nothing where the connection's\n * `search_path` already places them.\n *\n * @example\n * ```typescript\n * import { getTelescopeMigration } from '@geekmidas/telescope/storage/kysely';\n *\n * // In your migration file\n * export async function up(db: Kysely<any>): Promise<void> {\n * const migration = getTelescopeMigration();\n * await db.schema.executeRaw(migration.up).execute();\n * }\n *\n * export async function down(db: Kysely<any>): Promise<void> {\n * const migration = getTelescopeMigration();\n * await db.schema.executeRaw(migration.down).execute();\n * }\n * ```\n */\nexport function getTelescopeMigration(schema?: string): {\n\tup: string;\n\tdown: string;\n} {\n\t// Qualified only when a schema is named. Unqualified otherwise, so the\n\t// connection's `search_path` places them — the same rule the tables\n\t// themselves follow at query time.\n\tconst q = (name: string) => (schema ? `${schema}.${name}` : name);\n\tconst idx = (name: string) => (schema ? `${schema}_${name}` : name);\n\n\treturn {\n\t\tup: `${schema ? `CREATE SCHEMA IF NOT EXISTS ${schema};\\n` : ''}\n-- Telescope requests\nCREATE TABLE IF NOT EXISTS ${q('requests')} (\n id VARCHAR(21) PRIMARY KEY,\n method VARCHAR(10) NOT NULL,\n path TEXT NOT NULL,\n url TEXT NOT NULL,\n headers JSONB NOT NULL,\n body JSONB,\n query JSONB,\n status INTEGER NOT NULL,\n response_headers JSONB NOT NULL,\n response_body JSONB,\n duration DOUBLE PRECISION NOT NULL,\n timestamp TIMESTAMPTZ NOT NULL,\n ip VARCHAR(45),\n user_id VARCHAR(255),\n tags JSONB\n);\n\nCREATE INDEX IF NOT EXISTS idx_${idx('requests')}_timestamp\n ON ${q('requests')} (timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_${idx('requests')}_path\n ON ${q('requests')} (path);\nCREATE INDEX IF NOT EXISTS idx_${idx('requests')}_status\n ON ${q('requests')} (status);\n\n-- Telescope exceptions\nCREATE TABLE IF NOT EXISTS ${q('exceptions')} (\n id VARCHAR(21) PRIMARY KEY,\n name VARCHAR(255) NOT NULL,\n message TEXT NOT NULL,\n stack JSONB NOT NULL,\n source JSONB,\n request_id VARCHAR(21),\n timestamp TIMESTAMPTZ NOT NULL,\n handled BOOLEAN NOT NULL DEFAULT FALSE,\n tags JSONB\n);\n\nCREATE INDEX IF NOT EXISTS idx_${idx('exceptions')}_timestamp\n ON ${q('exceptions')} (timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_${idx('exceptions')}_request_id\n ON ${q('exceptions')} (request_id);\n\n-- Telescope logs\nCREATE TABLE IF NOT EXISTS ${q('logs')} (\n id VARCHAR(21) PRIMARY KEY,\n level VARCHAR(10) NOT NULL,\n message TEXT NOT NULL,\n context JSONB,\n request_id VARCHAR(21),\n timestamp TIMESTAMPTZ NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_${idx('logs')}_timestamp\n ON ${q('logs')} (timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_${idx('logs')}_level\n ON ${q('logs')} (level);\nCREATE INDEX IF NOT EXISTS idx_${idx('logs')}_request_id\n ON ${q('logs')} (request_id);\n`,\n\t\t// A schema tenant drops whole; only the unqualified case drops tables.\n\t\tdown: schema\n\t\t\t? `DROP SCHEMA IF EXISTS ${schema} CASCADE;\\n`\n\t\t\t: `\nDROP TABLE IF EXISTS logs;\nDROP TABLE IF EXISTS exceptions;\nDROP TABLE IF EXISTS requests;\n`,\n\t};\n}\n"],"mappings":"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"}
@@ -64,9 +64,9 @@ interface TelescopeLogTable {
64
64
  * ```
65
65
  */
66
66
  interface TelescopeTables {
67
- telescope_requests: TelescopeRequestTable;
68
- telescope_exceptions: TelescopeExceptionTable;
69
- telescope_logs: TelescopeLogTable;
67
+ requests: TelescopeRequestTable;
68
+ exceptions: TelescopeExceptionTable;
69
+ logs: TelescopeLogTable;
70
70
  }
71
71
  /**
72
72
  * Configuration for KyselyStorage.
@@ -75,10 +75,18 @@ interface KyselyStorageConfig<DB> {
75
75
  /** Kysely database instance */
76
76
  db: Kysely<DB>;
77
77
  /**
78
- * Table name prefix (default: 'telescope').
79
- * Tables will be named: {prefix}_requests, {prefix}_exceptions, {prefix}_logs
78
+ * The schema holding `requests`, `exceptions` and `logs`.
79
+ *
80
+ * Normally omitted. A telescope derived from a database gets a schema of its
81
+ * own and a role whose `search_path` is pinned to it, so an unqualified name
82
+ * already resolves there — which is the same rule every other tenant here
83
+ * follows, and the reason a connection string never has to remember where
84
+ * its tables live.
85
+ *
86
+ * Name one only when the connection is *not* pinned: a shared pool, a
87
+ * migration run as an owner whose `search_path` finds `public` first.
80
88
  */
81
- tablePrefix?: string;
89
+ schema?: string;
82
90
  }
83
91
  /**
84
92
  * Kysely-based storage implementation for Telescope.
@@ -103,6 +111,15 @@ interface KyselyStorageConfig<DB> {
103
111
  */
104
112
  declare class KyselyStorage<DB> implements TelescopeStorage {
105
113
  private readonly db;
114
+ /**
115
+ * Unqualified, and deliberately.
116
+ *
117
+ * These were `telescope_requests`, `telescope_exceptions` and
118
+ * `telescope_logs` — a prefix, which is what you reach for when the tables
119
+ * have to share a schema with an application's own. They do not: a telescope
120
+ * derived from a database is a schema tenant, so the names are just the
121
+ * names, and `DROP SCHEMA telescope CASCADE` is the whole cleanup.
122
+ */
106
123
  private readonly requestsTable;
107
124
  private readonly exceptionsTable;
108
125
  private readonly logsTable;
@@ -133,8 +150,11 @@ declare class KyselyStorage<DB> implements TelescopeStorage {
133
150
  private parseJson;
134
151
  }
135
152
  /**
136
- * SQL migration to create telescope tables.
137
- * Use this to set up the required tables in your database.
153
+ * SQL migration to create telescope's tables.
154
+ *
155
+ * Pass a schema to have them created in one, and to have `down` drop the whole
156
+ * schema rather than three tables. Pass nothing where the connection's
157
+ * `search_path` already places them.
138
158
  *
139
159
  * @example
140
160
  * ```typescript
@@ -152,7 +172,7 @@ declare class KyselyStorage<DB> implements TelescopeStorage {
152
172
  * }
153
173
  * ```
154
174
  */
155
- declare function getTelescopeMigration(tablePrefix?: string): {
175
+ declare function getTelescopeMigration(schema?: string): {
156
176
  up: string;
157
177
  down: string;
158
178
  };
@@ -1 +1 @@
1
- {"version":3,"file":"kysely.d.cts","names":[],"sources":["../../src/storage/kysely.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;AAqBA;AAeiB,UApCA,qBAAA,CA0CL;EAiBK,EAAA,EAAA,MAAA;EAAe,MAAA,EAAA,MAAA;EAAA,IACX,EAAA,MAAA;EAAqB,GACnB,EAAA,MAAA;EAAuB,OAC7B,EAAA,OAAA;EAAiB,IAAA,EAAA,OAAA,GAAA,IAAA;EAMjB,KAAA,EAAA,OAAA,GAAA,IAAA;EAAmB,MAAA,EAAA,MAAA;EAAA,gBAExB,EAAA,OAAA;EAAE,aAAT,EAAA,OAAA,GAAA,IAAA;EAAM,QAAA,EAAA,MAAA;EA6BE,SAAA,EAvFD,IAuFC;EAAa,EAAA,EAAA,MAAA,GAAA,IAAA;EAAA,OAMe,EAAA,MAAA,GAAA,IAAA;EAAE,IAAtB,EAAA,OAAA,GAAA,IAAA;;;;;AA2BQ,UA/GZ,uBAAA,CA+GY;EAAY,EAAA,EAAW,MAAA;EAAY,IAApB,EAAA,MAAA;EAAO,OAoCZ,EAAA,MAAA;EAAY,KAApB,EAAA,OAAA;EAAO,MAcV,EAAA,OAAA,GAAA,IAAA;EAAc,UAAG,EAAA,MAAA,GAAA,IAAA;EAAO,SAQrB,EAlKnB,IAkKmB;EAAc,OAAK,EAAA,OAAA;EAAO,IAU1B,EAAA,OAAA,GAAA,IAAA;;;;;AA0BT,UA9LL,iBAAA,CA8LK;EAAQ,EAAA,EAAG,MAAA;EAAO,KAKf,EAAA,MAAA;EAAQ,OAAK,EAAA,MAAA;EAAO,OAOpB,EAAA,OAAA,GAAA,IAAA;EAAY,UAAW,EAAA,MAAA,GAAA,IAAA;EAAQ,SAAhB,EApM5B,IAoM4B;;;;;;AA3IkB;AA4Z1D;;;;;;;;;UApciB,eAAA;sBACI;wBACE;kBACN;;;;;UAMA;;MAEZ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6BC,6BAA6B;;;;;sBAMrB,oBAAoB;qBAYf,eAAe;wBAKZ,iBAAiB;wBAUjB,eAAe,QAAQ;0BAoCrB,QAAQ;uBAcX,iBAAiB;0BAQd,mBAAmB;0BAUnB,eAAe,QAAQ;4BAYrB,QAAQ;iBAcnB,WAAW;oBAKR,aAAa;oBAOb,eAAe,QAAQ;mBAqBxB,OAAO;cA0BZ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkOX,qBAAA"}
1
+ {"version":3,"file":"kysely.d.cts","names":[],"sources":["../../src/storage/kysely.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;AAqBA;AAeiB,UApCA,qBAAA,CA0CL;EAiBK,EAAA,EAAA,MAAA;EAAe,MAAA,EAAA,MAAA;EAAA,IACrB,EAAA,MAAA;EAAqB,GACnB,EAAA,MAAA;EAAuB,OAC7B,EAAA,OAAA;EAAiB,IAAA,EAAA,OAAA,GAAA,IAAA;EAMP,KAAA,EAAA,OAAA,GAAA,IAAA;EAAmB,MAAA,EAAA,MAAA;EAAA,gBAExB,EAAA,OAAA;EAAE,aAAT,EAAA,OAAA,GAAA,IAAA;EAAM,QAAA,EAAA,MAAA;EAqCE,SAAA,EA/FD,IA+FC;EAAa,EAAA,EAAA,MAAA,GAAA,IAAA;EAAA,OAgBe,EAAA,MAAA,GAAA,IAAA;EAAE,IAAtB,EAAA,OAAA,GAAA,IAAA;;;;;AA0BQ,UAhIZ,uBAAA,CAgIY;EAAY,EAAA,EAAW,MAAA;EAAY,IAApB,EAAA,MAAA;EAAO,OAoCZ,EAAA,MAAA;EAAY,KAApB,EAAA,OAAA;EAAO,MAcV,EAAA,OAAA,GAAA,IAAA;EAAc,UAAG,EAAA,MAAA,GAAA,IAAA;EAAO,SAQrB,EAnLnB,IAmLmB;EAAc,OAAK,EAAA,OAAA;EAAO,IAU1B,EAAA,OAAA,GAAA,IAAA;;;;;AA0BT,UA/ML,iBAAA,CA+MK;EAAQ,EAAA,EAAG,MAAA;EAAO,KAKf,EAAA,MAAA;EAAQ,OAAK,EAAA,MAAA;EAAO,OAOpB,EAAA,OAAA,GAAA,IAAA;EAAY,UAAW,EAAA,MAAA,GAAA,IAAA;EAAQ,SAAhB,EArN5B,IAqN4B;;;;;;AApJkB;AAwa1D;;;;;;;;;UAxdiB,eAAA;YACN;cACE;QACN;;;;;UAMU;;MAEZ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCC,6BAA6B;;;;;;;;;;;;;;sBAgBrB,oBAAoB;qBAWf,eAAe;wBAKZ,iBAAiB;wBAUjB,eAAe,QAAQ;0BAoCrB,QAAQ;uBAcX,iBAAiB;0BAQd,mBAAmB;0BAUnB,eAAe,QAAQ;4BAYrB,QAAQ;iBAcnB,WAAW;oBAKR,aAAa;oBAOb,eAAe,QAAQ;mBAqBxB,OAAO;cA0BZ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqOX,qBAAA"}
@@ -64,9 +64,9 @@ interface TelescopeLogTable {
64
64
  * ```
65
65
  */
66
66
  interface TelescopeTables {
67
- telescope_requests: TelescopeRequestTable;
68
- telescope_exceptions: TelescopeExceptionTable;
69
- telescope_logs: TelescopeLogTable;
67
+ requests: TelescopeRequestTable;
68
+ exceptions: TelescopeExceptionTable;
69
+ logs: TelescopeLogTable;
70
70
  }
71
71
  /**
72
72
  * Configuration for KyselyStorage.
@@ -75,10 +75,18 @@ interface KyselyStorageConfig<DB> {
75
75
  /** Kysely database instance */
76
76
  db: Kysely<DB>;
77
77
  /**
78
- * Table name prefix (default: 'telescope').
79
- * Tables will be named: {prefix}_requests, {prefix}_exceptions, {prefix}_logs
78
+ * The schema holding `requests`, `exceptions` and `logs`.
79
+ *
80
+ * Normally omitted. A telescope derived from a database gets a schema of its
81
+ * own and a role whose `search_path` is pinned to it, so an unqualified name
82
+ * already resolves there — which is the same rule every other tenant here
83
+ * follows, and the reason a connection string never has to remember where
84
+ * its tables live.
85
+ *
86
+ * Name one only when the connection is *not* pinned: a shared pool, a
87
+ * migration run as an owner whose `search_path` finds `public` first.
80
88
  */
81
- tablePrefix?: string;
89
+ schema?: string;
82
90
  }
83
91
  /**
84
92
  * Kysely-based storage implementation for Telescope.
@@ -103,6 +111,15 @@ interface KyselyStorageConfig<DB> {
103
111
  */
104
112
  declare class KyselyStorage<DB> implements TelescopeStorage {
105
113
  private readonly db;
114
+ /**
115
+ * Unqualified, and deliberately.
116
+ *
117
+ * These were `telescope_requests`, `telescope_exceptions` and
118
+ * `telescope_logs` — a prefix, which is what you reach for when the tables
119
+ * have to share a schema with an application's own. They do not: a telescope
120
+ * derived from a database is a schema tenant, so the names are just the
121
+ * names, and `DROP SCHEMA telescope CASCADE` is the whole cleanup.
122
+ */
106
123
  private readonly requestsTable;
107
124
  private readonly exceptionsTable;
108
125
  private readonly logsTable;
@@ -133,8 +150,11 @@ declare class KyselyStorage<DB> implements TelescopeStorage {
133
150
  private parseJson;
134
151
  }
135
152
  /**
136
- * SQL migration to create telescope tables.
137
- * Use this to set up the required tables in your database.
153
+ * SQL migration to create telescope's tables.
154
+ *
155
+ * Pass a schema to have them created in one, and to have `down` drop the whole
156
+ * schema rather than three tables. Pass nothing where the connection's
157
+ * `search_path` already places them.
138
158
  *
139
159
  * @example
140
160
  * ```typescript
@@ -152,7 +172,7 @@ declare class KyselyStorage<DB> implements TelescopeStorage {
152
172
  * }
153
173
  * ```
154
174
  */
155
- declare function getTelescopeMigration(tablePrefix?: string): {
175
+ declare function getTelescopeMigration(schema?: string): {
156
176
  up: string;
157
177
  down: string;
158
178
  };
@@ -1 +1 @@
1
- {"version":3,"file":"kysely.d.mts","names":[],"sources":["../../src/storage/kysely.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;AAqBA;AAeiB,UApCA,qBAAA,CA0CL;EAiBK,EAAA,EAAA,MAAA;EAAe,MAAA,EAAA,MAAA;EAAA,IACX,EAAA,MAAA;EAAqB,GACnB,EAAA,MAAA;EAAuB,OAC7B,EAAA,OAAA;EAAiB,IAAA,EAAA,OAAA,GAAA,IAAA;EAMjB,KAAA,EAAA,OAAA,GAAA,IAAA;EAAmB,MAAA,EAAA,MAAA;EAAA,gBAExB,EAAA,OAAA;EAAE,aAAT,EAAA,OAAA,GAAA,IAAA;EAAM,QAAA,EAAA,MAAA;EA6BE,SAAA,EAvFD,IAuFC;EAAa,EAAA,EAAA,MAAA,GAAA,IAAA;EAAA,OAMe,EAAA,MAAA,GAAA,IAAA;EAAE,IAAtB,EAAA,OAAA,GAAA,IAAA;;;;;AA2BQ,UA/GZ,uBAAA,CA+GY;EAAY,EAAA,EAAW,MAAA;EAAY,IAApB,EAAA,MAAA;EAAO,OAoCZ,EAAA,MAAA;EAAY,KAApB,EAAA,OAAA;EAAO,MAcV,EAAA,OAAA,GAAA,IAAA;EAAc,UAAG,EAAA,MAAA,GAAA,IAAA;EAAO,SAQrB,EAlKnB,IAkKmB;EAAc,OAAK,EAAA,OAAA;EAAO,IAU1B,EAAA,OAAA,GAAA,IAAA;;;;;AA0BT,UA9LL,iBAAA,CA8LK;EAAQ,EAAA,EAAG,MAAA;EAAO,KAKf,EAAA,MAAA;EAAQ,OAAK,EAAA,MAAA;EAAO,OAOpB,EAAA,OAAA,GAAA,IAAA;EAAY,UAAW,EAAA,MAAA,GAAA,IAAA;EAAQ,SAAhB,EApM5B,IAoM4B;;;;;;AA3IkB;AA4Z1D;;;;;;;;;UApciB,eAAA;sBACI;wBACE;kBACN;;;;;UAMA;;MAEZ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6BC,6BAA6B;;;;;sBAMrB,oBAAoB;qBAYf,eAAe;wBAKZ,iBAAiB;wBAUjB,eAAe,QAAQ;0BAoCrB,QAAQ;uBAcX,iBAAiB;0BAQd,mBAAmB;0BAUnB,eAAe,QAAQ;4BAYrB,QAAQ;iBAcnB,WAAW;oBAKR,aAAa;oBAOb,eAAe,QAAQ;mBAqBxB,OAAO;cA0BZ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkOX,qBAAA"}
1
+ {"version":3,"file":"kysely.d.mts","names":[],"sources":["../../src/storage/kysely.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;AAqBA;AAeiB,UApCA,qBAAA,CA0CD;EAiBC,EAAA,EAAA,MAAA;EAAe,MAAA,EAAA,MAAA;EAAA,IACrB,EAAA,MAAA;EAAqB,GACnB,EAAA,MAAA;EAAuB,OAC7B,EAAA,OAAA;EAAiB,IAAA,EAAA,OAAA,GAAA,IAAA;EAMP,KAAA,EAAA,OAAA,GAAA,IAAA;EAAmB,MAAA,EAAA,MAAA;EAAA,gBAExB,EAAA,OAAA;EAAE,aAAT,EAAA,OAAA,GAAA,IAAA;EAAM,QAAA,EAAA,MAAA;EAqCE,SAAA,EA/FD,IA+FC;EAAa,EAAA,EAAA,MAAA,GAAA,IAAA;EAAA,OAgBe,EAAA,MAAA,GAAA,IAAA;EAAE,IAAtB,EAAA,OAAA,GAAA,IAAA;;;;;AA0BQ,UAhIZ,uBAAA,CAgIY;EAAY,EAAA,EAAW,MAAA;EAAY,IAApB,EAAA,MAAA;EAAO,OAoCZ,EAAA,MAAA;EAAY,KAApB,EAAA,OAAA;EAAO,MAcV,EAAA,OAAA,GAAA,IAAA;EAAc,UAAG,EAAA,MAAA,GAAA,IAAA;EAAO,SAQrB,EAnLnB,IAmLmB;EAAc,OAAK,EAAA,OAAA;EAAO,IAU1B,EAAA,OAAA,GAAA,IAAA;;;;;AA0BT,UA/ML,iBAAA,CA+MK;EAAQ,EAAA,EAAG,MAAA;EAAO,KAKf,EAAA,MAAA;EAAQ,OAAK,EAAA,MAAA;EAAO,OAOpB,EAAA,OAAA,GAAA,IAAA;EAAY,UAAW,EAAA,MAAA,GAAA,IAAA;EAAQ,SAAhB,EArN5B,IAqN4B;;;;;;AApJkB;AAwa1D;;;;;;;;;UAxdiB,eAAA;YACN;cACE;QACN;;;;;UAMU;;MAEZ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCC,6BAA6B;;;;;;;;;;;;;;sBAgBrB,oBAAoB;qBAWf,eAAe;wBAKZ,iBAAiB;wBAUjB,eAAe,QAAQ;0BAoCrB,QAAQ;uBAcX,iBAAiB;0BAQd,mBAAmB;0BAUnB,eAAe,QAAQ;4BAYrB,QAAQ;iBAcnB,WAAW;oBAKR,aAAa;oBAOb,eAAe,QAAQ;mBAqBxB,OAAO;cA0BZ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqOX,qBAAA"}
@@ -22,15 +22,20 @@
22
22
  */
23
23
  var KyselyStorage = class {
24
24
  db;
25
- requestsTable;
26
- exceptionsTable;
27
- logsTable;
25
+ /**
26
+ * Unqualified, and deliberately.
27
+ *
28
+ * These were `telescope_requests`, `telescope_exceptions` and
29
+ * `telescope_logs` — a prefix, which is what you reach for when the tables
30
+ * have to share a schema with an application's own. They do not: a telescope
31
+ * derived from a database is a schema tenant, so the names are just the
32
+ * names, and `DROP SCHEMA telescope CASCADE` is the whole cleanup.
33
+ */
34
+ requestsTable = "requests";
35
+ exceptionsTable = "exceptions";
36
+ logsTable = "logs";
28
37
  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`;
38
+ this.db = config.schema ? config.db.withSchema(config.schema) : config.db;
34
39
  }
35
40
  async saveRequest(entry) {
36
41
  const row = this.requestToRow(entry);
@@ -253,8 +258,11 @@ var KyselyStorage = class {
253
258
  }
254
259
  };
255
260
  /**
256
- * SQL migration to create telescope tables.
257
- * Use this to set up the required tables in your database.
261
+ * SQL migration to create telescope's tables.
262
+ *
263
+ * Pass a schema to have them created in one, and to have `down` drop the whole
264
+ * schema rather than three tables. Pass nothing where the connection's
265
+ * `search_path` already places them.
258
266
  *
259
267
  * @example
260
268
  * ```typescript
@@ -272,11 +280,13 @@ var KyselyStorage = class {
272
280
  * }
273
281
  * ```
274
282
  */
275
- function getTelescopeMigration(tablePrefix = "telescope") {
283
+ function getTelescopeMigration(schema) {
284
+ const q = (name) => schema ? `${schema}.${name}` : name;
285
+ const idx = (name) => schema ? `${schema}_${name}` : name;
276
286
  return {
277
- up: `
278
- -- Telescope requests table
279
- CREATE TABLE IF NOT EXISTS ${tablePrefix}_requests (
287
+ up: `${schema ? `CREATE SCHEMA IF NOT EXISTS ${schema};\n` : ""}
288
+ -- Telescope requests
289
+ CREATE TABLE IF NOT EXISTS ${q("requests")} (
280
290
  id VARCHAR(21) PRIMARY KEY,
281
291
  method VARCHAR(10) NOT NULL,
282
292
  path TEXT NOT NULL,
@@ -294,15 +304,15 @@ CREATE TABLE IF NOT EXISTS ${tablePrefix}_requests (
294
304
  tags JSONB
295
305
  );
296
306
 
297
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_timestamp
298
- ON ${tablePrefix}_requests (timestamp DESC);
299
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_path
300
- ON ${tablePrefix}_requests (path);
301
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_requests_status
302
- ON ${tablePrefix}_requests (status);
307
+ CREATE INDEX IF NOT EXISTS idx_${idx("requests")}_timestamp
308
+ ON ${q("requests")} (timestamp DESC);
309
+ CREATE INDEX IF NOT EXISTS idx_${idx("requests")}_path
310
+ ON ${q("requests")} (path);
311
+ CREATE INDEX IF NOT EXISTS idx_${idx("requests")}_status
312
+ ON ${q("requests")} (status);
303
313
 
304
- -- Telescope exceptions table
305
- CREATE TABLE IF NOT EXISTS ${tablePrefix}_exceptions (
314
+ -- Telescope exceptions
315
+ CREATE TABLE IF NOT EXISTS ${q("exceptions")} (
306
316
  id VARCHAR(21) PRIMARY KEY,
307
317
  name VARCHAR(255) NOT NULL,
308
318
  message TEXT NOT NULL,
@@ -314,13 +324,13 @@ CREATE TABLE IF NOT EXISTS ${tablePrefix}_exceptions (
314
324
  tags JSONB
315
325
  );
316
326
 
317
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_timestamp
318
- ON ${tablePrefix}_exceptions (timestamp DESC);
319
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_exceptions_request_id
320
- ON ${tablePrefix}_exceptions (request_id);
327
+ CREATE INDEX IF NOT EXISTS idx_${idx("exceptions")}_timestamp
328
+ ON ${q("exceptions")} (timestamp DESC);
329
+ CREATE INDEX IF NOT EXISTS idx_${idx("exceptions")}_request_id
330
+ ON ${q("exceptions")} (request_id);
321
331
 
322
- -- Telescope logs table
323
- CREATE TABLE IF NOT EXISTS ${tablePrefix}_logs (
332
+ -- Telescope logs
333
+ CREATE TABLE IF NOT EXISTS ${q("logs")} (
324
334
  id VARCHAR(21) PRIMARY KEY,
325
335
  level VARCHAR(10) NOT NULL,
326
336
  message TEXT NOT NULL,
@@ -329,17 +339,17 @@ CREATE TABLE IF NOT EXISTS ${tablePrefix}_logs (
329
339
  timestamp TIMESTAMPTZ NOT NULL
330
340
  );
331
341
 
332
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_timestamp
333
- ON ${tablePrefix}_logs (timestamp DESC);
334
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_level
335
- ON ${tablePrefix}_logs (level);
336
- CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_logs_request_id
337
- ON ${tablePrefix}_logs (request_id);
342
+ CREATE INDEX IF NOT EXISTS idx_${idx("logs")}_timestamp
343
+ ON ${q("logs")} (timestamp DESC);
344
+ CREATE INDEX IF NOT EXISTS idx_${idx("logs")}_level
345
+ ON ${q("logs")} (level);
346
+ CREATE INDEX IF NOT EXISTS idx_${idx("logs")}_request_id
347
+ ON ${q("logs")} (request_id);
338
348
  `,
339
- down: `
340
- DROP TABLE IF EXISTS ${tablePrefix}_logs;
341
- DROP TABLE IF EXISTS ${tablePrefix}_exceptions;
342
- DROP TABLE IF EXISTS ${tablePrefix}_requests;
349
+ down: schema ? `DROP SCHEMA IF EXISTS ${schema} CASCADE;\n` : `
350
+ DROP TABLE IF EXISTS logs;
351
+ DROP TABLE IF EXISTS exceptions;
352
+ DROP TABLE IF EXISTS requests;
343
353
  `
344
354
  };
345
355
  }