@hasna/logs 0.3.26 → 0.3.27

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 (130) hide show
  1. package/README.md +33 -10
  2. package/dashboard/dist/assets/index-C0wZYq1m.js +53 -0
  3. package/dashboard/dist/assets/index-DGNrK5qb.css +1 -0
  4. package/dashboard/dist/index.html +14 -0
  5. package/dist/cli/index.js +8511 -177
  6. package/dist/count-bmj4r2zb.js +10 -0
  7. package/dist/{diagnose-e0w5rwbc.js → diagnose-3q5cy9ra.js} +2 -2
  8. package/dist/{export-c3eqjste.js → export-cngdb9fh.js} +1 -1
  9. package/dist/{http-zm3ph78w.js → http-r0xc3d2s.js} +79 -8
  10. package/dist/index-931pbyn5.js +141 -0
  11. package/dist/index-b5c72f1p.js +7 -0
  12. package/dist/{index-gc0zvs88.js → index-bnr19y0h.js} +596 -37
  13. package/dist/{index-7w7v7hnr.js → index-by1pdzbr.js} +14 -5
  14. package/dist/{index-3dr7d80h.js → index-e1930v9b.js} +12 -8
  15. package/dist/{index-eh9bkbpa.js → index-e72k53yq.js} +10 -2
  16. package/dist/{index-edn08m6f.js → index-gcd14q2f.js} +9 -6
  17. package/dist/index-hq6kzaah.js +26 -0
  18. package/dist/index-j34f36wy.js +5672 -0
  19. package/dist/index-p4dbdzx4.js +1849 -0
  20. package/dist/{index-5qznfyah.js → index-q27bgpr1.js} +1086 -1646
  21. package/dist/index-t3x838zw.js +2583 -0
  22. package/dist/{index-ww5ggfv3.js → index-zkb3z95a.js} +12 -9
  23. package/dist/index.js +2982 -22
  24. package/dist/{jobs-ypmmc2ma.js → jobs-hsgyhfvm.js} +2 -1
  25. package/dist/mcp/index.js +1473 -4286
  26. package/dist/{query-7jwj05er.js → query-c5a43zx3.js} +3 -2
  27. package/dist/server/index.js +2944 -417
  28. package/dist/storage.js +50 -0
  29. package/package.json +27 -8
  30. package/biome.json +0 -13
  31. package/bun.lock +0 -376
  32. package/dashboard/README.md +0 -73
  33. package/dashboard/bun.lock +0 -526
  34. package/dashboard/eslint.config.js +0 -23
  35. package/dashboard/index.html +0 -13
  36. package/dashboard/package.json +0 -32
  37. package/dashboard/src/App.css +0 -184
  38. package/dashboard/src/App.tsx +0 -49
  39. package/dashboard/src/api.ts +0 -33
  40. package/dashboard/src/assets/hero.png +0 -0
  41. package/dashboard/src/assets/react.svg +0 -1
  42. package/dashboard/src/assets/vite.svg +0 -1
  43. package/dashboard/src/index.css +0 -111
  44. package/dashboard/src/main.tsx +0 -10
  45. package/dashboard/src/pages/Alerts.tsx +0 -69
  46. package/dashboard/src/pages/Issues.tsx +0 -50
  47. package/dashboard/src/pages/Perf.tsx +0 -75
  48. package/dashboard/src/pages/Projects.tsx +0 -67
  49. package/dashboard/src/pages/Summary.tsx +0 -67
  50. package/dashboard/src/pages/Tail.tsx +0 -65
  51. package/dashboard/tsconfig.app.json +0 -28
  52. package/dashboard/tsconfig.json +0 -7
  53. package/dashboard/tsconfig.node.json +0 -26
  54. package/dashboard/vite.config.ts +0 -14
  55. package/dist/count-x3n7qg3c.js +0 -9
  56. package/dist/index-997bkzr2.js +0 -15
  57. package/dist/index-pen6t0yc.js +0 -10794
  58. package/sdk/package.json +0 -27
  59. package/sdk/src/index.ts +0 -143
  60. package/sdk/src/types.ts +0 -56
  61. package/src/cli/entrypoints.test.ts +0 -63
  62. package/src/cli/index.ts +0 -471
  63. package/src/db/index.test.ts +0 -33
  64. package/src/db/index.ts +0 -189
  65. package/src/db/migrations/001_alert_rules.ts +0 -21
  66. package/src/db/migrations/002_issues.ts +0 -21
  67. package/src/db/migrations/003_retention.ts +0 -15
  68. package/src/db/migrations/004_page_auth.ts +0 -13
  69. package/src/db/pg-migrations.ts +0 -167
  70. package/src/index.ts +0 -1
  71. package/src/lib/alerts.test.ts +0 -67
  72. package/src/lib/alerts.ts +0 -117
  73. package/src/lib/browser-script.test.ts +0 -35
  74. package/src/lib/browser-script.ts +0 -31
  75. package/src/lib/compare.test.ts +0 -52
  76. package/src/lib/compare.ts +0 -85
  77. package/src/lib/count.test.ts +0 -44
  78. package/src/lib/count.ts +0 -55
  79. package/src/lib/diagnose.test.ts +0 -55
  80. package/src/lib/diagnose.ts +0 -91
  81. package/src/lib/export.test.ts +0 -66
  82. package/src/lib/export.ts +0 -65
  83. package/src/lib/github.ts +0 -38
  84. package/src/lib/health.test.ts +0 -48
  85. package/src/lib/health.ts +0 -51
  86. package/src/lib/ingest.test.ts +0 -57
  87. package/src/lib/ingest.ts +0 -78
  88. package/src/lib/issues.test.ts +0 -79
  89. package/src/lib/issues.ts +0 -70
  90. package/src/lib/jobs.test.ts +0 -69
  91. package/src/lib/jobs.ts +0 -63
  92. package/src/lib/lighthouse.ts +0 -65
  93. package/src/lib/package-meta.test.ts +0 -43
  94. package/src/lib/package-meta.ts +0 -80
  95. package/src/lib/page-auth.test.ts +0 -54
  96. package/src/lib/page-auth.ts +0 -48
  97. package/src/lib/parse-time.test.ts +0 -37
  98. package/src/lib/parse-time.ts +0 -14
  99. package/src/lib/perf.test.ts +0 -45
  100. package/src/lib/perf.ts +0 -46
  101. package/src/lib/projects.test.ts +0 -73
  102. package/src/lib/projects.ts +0 -69
  103. package/src/lib/query.test.ts +0 -104
  104. package/src/lib/query.ts +0 -84
  105. package/src/lib/retention.test.ts +0 -42
  106. package/src/lib/retention.ts +0 -62
  107. package/src/lib/rotate.test.ts +0 -37
  108. package/src/lib/rotate.ts +0 -27
  109. package/src/lib/scanner.ts +0 -131
  110. package/src/lib/scheduler.ts +0 -63
  111. package/src/lib/session-context.ts +0 -28
  112. package/src/lib/summarize.test.ts +0 -38
  113. package/src/lib/summarize.ts +0 -23
  114. package/src/mcp/http.test.ts +0 -92
  115. package/src/mcp/http.ts +0 -135
  116. package/src/mcp/index.test.ts +0 -27
  117. package/src/mcp/index.ts +0 -444
  118. package/src/server/index.ts +0 -61
  119. package/src/server/routes/alerts.ts +0 -32
  120. package/src/server/routes/issues.ts +0 -43
  121. package/src/server/routes/jobs.ts +0 -32
  122. package/src/server/routes/logs.ts +0 -113
  123. package/src/server/routes/perf.ts +0 -23
  124. package/src/server/routes/projects.ts +0 -67
  125. package/src/server/routes/stream.ts +0 -43
  126. package/src/server/server.test.ts +0 -194
  127. package/src/types/index.ts +0 -119
  128. package/tsconfig.json +0 -22
  129. /package/dashboard/{public → dist}/favicon.svg +0 -0
  130. /package/dashboard/{public → dist}/icons.svg +0 -0
@@ -1,113 +0,0 @@
1
- import { Hono } from "hono"
2
- import type { Database } from "bun:sqlite"
3
- import { ingestBatch, ingestLog } from "../../lib/ingest.ts"
4
- import { getLogContext, searchLogs, tailLogs } from "../../lib/query.ts"
5
- import { summarizeLogs } from "../../lib/summarize.ts"
6
- import { exportToCsv, exportToJson } from "../../lib/export.ts"
7
- import { countLogs } from "../../lib/count.ts"
8
- import { parseTime } from "../../lib/parse-time.ts"
9
- import { resolveProjectId } from "../../lib/projects.ts"
10
- import type { LogEntry, LogLevel } from "../../types/index.ts"
11
-
12
- export function logsRoutes(db: Database) {
13
- const app = new Hono()
14
-
15
- // POST /api/logs — ingest single or batch
16
- app.post("/", async (c) => {
17
- const body = await c.req.json()
18
- if (Array.isArray(body)) {
19
- const rows = ingestBatch(db, body as LogEntry[])
20
- return c.json({ inserted: rows.length }, 201)
21
- }
22
- const row = ingestLog(db, body as LogEntry)
23
- return c.json(row, 201)
24
- })
25
-
26
- // GET /api/logs
27
- app.get("/", (c) => {
28
- const { project_id, page_id, level, service, since, until, text, trace_id, limit, offset, fields } = c.req.query()
29
- const rows = searchLogs(db, {
30
- project_id: project_id || undefined,
31
- page_id: page_id || undefined,
32
- level: level ? (level.split(",") as LogLevel[]) : undefined,
33
- service: service || undefined,
34
- since: since || undefined,
35
- until: until || undefined,
36
- text: text || undefined,
37
- trace_id: trace_id || undefined,
38
- limit: limit ? Number(limit) : 100,
39
- offset: offset ? Number(offset) : 0,
40
- })
41
- if (fields) {
42
- const keys = fields.split(",")
43
- return c.json(rows.map(r => Object.fromEntries(keys.map(k => [k, (r as Record<string, unknown>)[k]]))))
44
- }
45
- return c.json(rows)
46
- })
47
-
48
- // GET /api/logs/tail
49
- app.get("/tail", (c) => {
50
- const { project_id, n } = c.req.query()
51
- const rows = tailLogs(db, project_id || undefined, n ? Number(n) : 50)
52
- return c.json(rows)
53
- })
54
-
55
- // GET /api/logs/summary
56
- app.get("/summary", (c) => {
57
- const { project_id, since } = c.req.query()
58
- const summary = summarizeLogs(db, resolveProjectId(db, project_id) || undefined, parseTime(since) || since || undefined)
59
- return c.json(summary)
60
- })
61
-
62
- // GET /api/logs/count
63
- app.get("/count", (c) => {
64
- const { project_id, service, level, since, until } = c.req.query()
65
- return c.json(countLogs(db, {
66
- project_id: resolveProjectId(db, project_id) || undefined,
67
- service: service || undefined,
68
- level: level || undefined,
69
- since: since || undefined,
70
- until: until || undefined,
71
- }))
72
- })
73
-
74
- // GET /api/logs/recent-errors
75
- app.get("/recent-errors", (c) => {
76
- const { project_id, since, limit } = c.req.query()
77
- const rows = searchLogs(db, {
78
- project_id: resolveProjectId(db, project_id) || undefined,
79
- level: ["error", "fatal"],
80
- since: parseTime(since || "1h"),
81
- limit: limit ? Number(limit) : 20,
82
- })
83
- return c.json(rows.map(r => ({ id: r.id, timestamp: r.timestamp, level: r.level, message: r.message, service: r.service, age_seconds: Math.floor((Date.now() - new Date(r.timestamp).getTime()) / 1000) })))
84
- })
85
-
86
- // GET /api/logs/:trace_id/context
87
- app.get("/:trace_id/context", (c) => {
88
- const rows = getLogContext(db, c.req.param("trace_id"))
89
- return c.json(rows)
90
- })
91
-
92
- // GET /api/logs/export?format=json|csv&project_id=&since=&level=
93
- app.get("/export", (c) => {
94
- const { project_id, since, until, level, service, format, limit } = c.req.query()
95
- const opts = { project_id: project_id || undefined, since: since || undefined, until: until || undefined, level: level || undefined, service: service || undefined, limit: limit ? Number(limit) : undefined }
96
-
97
- if (format === "csv") {
98
- c.header("Content-Type", "text/csv")
99
- c.header("Content-Disposition", "attachment; filename=logs.csv")
100
- const chunks: string[] = []
101
- exportToCsv(db, opts, s => chunks.push(s))
102
- return c.text(chunks.join(""))
103
- }
104
-
105
- c.header("Content-Type", "application/json")
106
- c.header("Content-Disposition", "attachment; filename=logs.json")
107
- const chunks: string[] = []
108
- exportToJson(db, opts, s => chunks.push(s))
109
- return c.text(chunks.join("\n"))
110
- })
111
-
112
- return app
113
- }
@@ -1,23 +0,0 @@
1
- import { Hono } from "hono"
2
- import type { Database } from "bun:sqlite"
3
- import { getLatestSnapshot, getPerfTrend } from "../../lib/perf.ts"
4
-
5
- export function perfRoutes(db: Database) {
6
- const app = new Hono()
7
-
8
- app.get("/", (c) => {
9
- const { project_id, page_id, since } = c.req.query()
10
- if (!project_id) return c.json({ error: "project_id is required" }, 422)
11
- const snap = getLatestSnapshot(db, project_id, page_id || undefined)
12
- return c.json(snap)
13
- })
14
-
15
- app.get("/trend", (c) => {
16
- const { project_id, page_id, since, limit } = c.req.query()
17
- if (!project_id) return c.json({ error: "project_id is required" }, 422)
18
- const trend = getPerfTrend(db, project_id, page_id || undefined, since || undefined, limit ? Number(limit) : 50)
19
- return c.json(trend)
20
- })
21
-
22
- return app
23
- }
@@ -1,67 +0,0 @@
1
- import { Hono } from "hono"
2
- import type { Database } from "bun:sqlite"
3
- import { createPage, createProject, getProject, listPages, listProjects } from "../../lib/projects.ts"
4
- import { syncGithubRepo } from "../../lib/github.ts"
5
- import { runRetentionForProject, setRetentionPolicy } from "../../lib/retention.ts"
6
- import { deletePageAuth, setPageAuth } from "../../lib/page-auth.ts"
7
-
8
- export function projectsRoutes(db: Database) {
9
- const app = new Hono()
10
-
11
- app.post("/", async (c) => {
12
- const body = await c.req.json()
13
- if (!body.name) return c.json({ error: "name is required" }, 422)
14
- const project = createProject(db, body)
15
- return c.json(project, 201)
16
- })
17
-
18
- app.get("/", (c) => c.json(listProjects(db)))
19
-
20
- app.get("/:id", (c) => {
21
- const project = getProject(db, c.req.param("id"))
22
- if (!project) return c.json({ error: "not found" }, 404)
23
- return c.json(project)
24
- })
25
-
26
- app.post("/:id/pages", async (c) => {
27
- const body = await c.req.json()
28
- if (!body.url) return c.json({ error: "url is required" }, 422)
29
- const page = createPage(db, { ...body, project_id: c.req.param("id") })
30
- return c.json(page, 201)
31
- })
32
-
33
- app.get("/:id/pages", (c) => c.json(listPages(db, c.req.param("id"))))
34
-
35
- app.put("/:id/retention", async (c) => {
36
- const body = await c.req.json()
37
- setRetentionPolicy(db, c.req.param("id"), body)
38
- return c.json({ updated: true })
39
- })
40
-
41
- app.post("/:id/retention/run", (c) => {
42
- const result = runRetentionForProject(db, c.req.param("id"))
43
- return c.json(result)
44
- })
45
-
46
- app.post("/:id/pages/:page_id/auth", async (c) => {
47
- const { type, credentials } = await c.req.json()
48
- if (!type || !credentials) return c.json({ error: "type and credentials required" }, 422)
49
- const result = setPageAuth(db, c.req.param("page_id"), type, credentials)
50
- return c.json({ id: result.id, type: result.type, created_at: result.created_at }, 201)
51
- })
52
-
53
- app.delete("/:id/pages/:page_id/auth", (c) => {
54
- deletePageAuth(db, c.req.param("page_id"))
55
- return c.json({ deleted: true })
56
- })
57
-
58
- app.post("/:id/sync-repo", async (c) => {
59
- const project = getProject(db, c.req.param("id"))
60
- if (!project) return c.json({ error: "not found" }, 404)
61
- if (!project.github_repo) return c.json({ error: "no github_repo set" }, 422)
62
- const updated = await syncGithubRepo(db, project)
63
- return c.json(updated)
64
- })
65
-
66
- return app
67
- }
@@ -1,43 +0,0 @@
1
- import { Hono } from "hono"
2
- import { streamSSE } from "hono/streaming"
3
- import type { Database } from "bun:sqlite"
4
- import type { LogLevel, LogRow } from "../../types/index.ts"
5
-
6
- export function streamRoutes(db: Database) {
7
- const app = new Hono()
8
-
9
- // GET /api/logs/stream?project_id=&level=&service=
10
- app.get("/", (c) => {
11
- const { project_id, level, service } = c.req.query()
12
-
13
- return streamSSE(c, async (stream) => {
14
- let lastId: string | null = null
15
-
16
- // Seed lastId with the most recent log so we only stream new ones
17
- const latest = db.prepare("SELECT id FROM logs ORDER BY timestamp DESC LIMIT 1").get() as { id: string } | null
18
- lastId = latest?.id ?? null
19
-
20
- while (true) {
21
- const conditions: string[] = []
22
- const params: Record<string, unknown> = {}
23
-
24
- if (lastId) { conditions.push("rowid > (SELECT rowid FROM logs WHERE id = $lastId)"); params.$lastId = lastId }
25
- if (project_id) { conditions.push("project_id = $project_id"); params.$project_id = project_id }
26
- if (level) { conditions.push("level IN (" + level.split(",").map((l, i) => `$l${i}`).join(",") + ")"); level.split(",").forEach((l, i) => { params[`$l${i}`] = l }) }
27
- if (service) { conditions.push("service = $service"); params.$service = service }
28
-
29
- const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""
30
- const rows = db.prepare(`SELECT * FROM logs ${where} ORDER BY timestamp ASC LIMIT 50`).all(params) as LogRow[]
31
-
32
- for (const row of rows) {
33
- await stream.writeSSE({ data: JSON.stringify(row), id: row.id, event: row.level })
34
- lastId = row.id
35
- }
36
-
37
- await stream.sleep(500)
38
- }
39
- })
40
- })
41
-
42
- return app
43
- }
@@ -1,194 +0,0 @@
1
- import { describe, expect, it, beforeEach } from "bun:test"
2
- import { Hono } from "hono"
3
- import { cors } from "hono/cors"
4
- import { createTestDb } from "../db/index.ts"
5
- import { logsRoutes } from "./routes/logs.ts"
6
- import { projectsRoutes } from "./routes/projects.ts"
7
- import { jobsRoutes } from "./routes/jobs.ts"
8
- import { perfRoutes } from "./routes/perf.ts"
9
-
10
- function buildApp() {
11
- const db = createTestDb()
12
- const app = new Hono()
13
- app.use("*", cors())
14
- app.route("/api/logs", logsRoutes(db))
15
- app.route("/api/projects", projectsRoutes(db))
16
- app.route("/api/jobs", jobsRoutes(db))
17
- app.route("/api/perf", perfRoutes(db))
18
- return { app, db }
19
- }
20
-
21
- describe("POST /api/logs", () => {
22
- it("ingests a single log", async () => {
23
- const { app } = buildApp()
24
- const res = await app.request("/api/logs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ level: "error", message: "boom" }) })
25
- expect(res.status).toBe(201)
26
- const body = await res.json() as { level: string; message: string }
27
- expect(body.level).toBe("error")
28
- expect(body.message).toBe("boom")
29
- })
30
-
31
- it("ingests a batch", async () => {
32
- const { app } = buildApp()
33
- const res = await app.request("/api/logs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify([{ level: "info", message: "a" }, { level: "warn", message: "b" }]) })
34
- expect(res.status).toBe(201)
35
- const body = await res.json() as { inserted: number }
36
- expect(body.inserted).toBe(2)
37
- })
38
- })
39
-
40
- describe("GET /api/logs", () => {
41
- it("lists logs", async () => {
42
- const { app, db } = buildApp()
43
- const { ingestBatch } = await import("../lib/ingest.ts")
44
- ingestBatch(db, [{ level: "error", message: "e1" }, { level: "info", message: "i1" }])
45
- const res = await app.request("/api/logs")
46
- expect(res.status).toBe(200)
47
- const body = await res.json() as unknown[]
48
- expect(body.length).toBeGreaterThanOrEqual(2)
49
- })
50
-
51
- it("filters by level", async () => {
52
- const { app, db } = buildApp()
53
- const { ingestBatch } = await import("../lib/ingest.ts")
54
- ingestBatch(db, [{ level: "error", message: "e1" }, { level: "info", message: "i1" }])
55
- const res = await app.request("/api/logs?level=error")
56
- const body = await res.json() as { level: string }[]
57
- expect(body.every(r => r.level === "error")).toBe(true)
58
- })
59
-
60
- it("supports ?fields= projection", async () => {
61
- const { app, db } = buildApp()
62
- const { ingestLog } = await import("../lib/ingest.ts")
63
- ingestLog(db, { level: "info", message: "hello" })
64
- const res = await app.request("/api/logs?fields=level,message")
65
- const body = await res.json() as Record<string, unknown>[]
66
- expect(Object.keys(body[0]!).sort()).toEqual(["level", "message"].sort())
67
- })
68
- })
69
-
70
- describe("GET /api/logs/tail", () => {
71
- it("returns recent logs", async () => {
72
- const { app, db } = buildApp()
73
- const { ingestBatch } = await import("../lib/ingest.ts")
74
- ingestBatch(db, Array.from({ length: 10 }, (_, i) => ({ level: "info" as const, message: `m${i}` })))
75
- const res = await app.request("/api/logs/tail?n=5")
76
- const body = await res.json() as unknown[]
77
- expect(body).toHaveLength(5)
78
- })
79
- })
80
-
81
- describe("GET /api/logs/summary", () => {
82
- it("returns summary of errors/warns", async () => {
83
- const { app, db } = buildApp()
84
- const { ingestBatch } = await import("../lib/ingest.ts")
85
- ingestBatch(db, [{ level: "error", message: "x", service: "api" }, { level: "warn", message: "y", service: "db" }])
86
- const res = await app.request("/api/logs/summary")
87
- const body = await res.json() as unknown[]
88
- expect(body.length).toBeGreaterThan(0)
89
- })
90
- })
91
-
92
- describe("GET /api/logs/:trace_id/context", () => {
93
- it("returns logs for trace", async () => {
94
- const { app, db } = buildApp()
95
- const { ingestBatch } = await import("../lib/ingest.ts")
96
- ingestBatch(db, [{ level: "info", message: "a", trace_id: "t99" }, { level: "error", message: "b", trace_id: "t99" }])
97
- const res = await app.request("/api/logs/t99/context")
98
- const body = await res.json() as unknown[]
99
- expect(body).toHaveLength(2)
100
- })
101
- })
102
-
103
- describe("POST /api/projects", () => {
104
- it("creates a project", async () => {
105
- const { app } = buildApp()
106
- const res = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "myapp" }) })
107
- expect(res.status).toBe(201)
108
- const body = await res.json() as { name: string }
109
- expect(body.name).toBe("myapp")
110
- })
111
-
112
- it("returns 422 without name", async () => {
113
- const { app } = buildApp()
114
- const res = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) })
115
- expect(res.status).toBe(422)
116
- })
117
- })
118
-
119
- describe("GET /api/projects", () => {
120
- it("lists projects", async () => {
121
- const { app } = buildApp()
122
- await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "p1" }) })
123
- const res = await app.request("/api/projects")
124
- const body = await res.json() as unknown[]
125
- expect(body.length).toBeGreaterThanOrEqual(1)
126
- })
127
- })
128
-
129
- describe("POST /api/projects/:id/pages", () => {
130
- it("registers a page", async () => {
131
- const { app } = buildApp()
132
- const pRes = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "app" }) })
133
- const project = await pRes.json() as { id: string }
134
- const res = await app.request(`/api/projects/${project.id}/pages`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: "https://app.com/home" }) })
135
- expect(res.status).toBe(201)
136
- const page = await res.json() as { url: string }
137
- expect(page.url).toBe("https://app.com/home")
138
- })
139
-
140
- it("returns 422 without url", async () => {
141
- const { app } = buildApp()
142
- const pRes = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "app2" }) })
143
- const project = await pRes.json() as { id: string }
144
- const res = await app.request(`/api/projects/${project.id}/pages`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) })
145
- expect(res.status).toBe(422)
146
- })
147
- })
148
-
149
- describe("jobs routes", () => {
150
- it("creates and lists jobs", async () => {
151
- const { app } = buildApp()
152
- const pRes = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "appj" }) })
153
- const { id } = await pRes.json() as { id: string }
154
- const jRes = await app.request("/api/jobs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ project_id: id, schedule: "*/5 * * * *" }) })
155
- expect(jRes.status).toBe(201)
156
- const listRes = await app.request(`/api/jobs?project_id=${id}`)
157
- const jobs = await listRes.json() as unknown[]
158
- expect(jobs).toHaveLength(1)
159
- })
160
-
161
- it("returns 422 without required fields", async () => {
162
- const { app } = buildApp()
163
- const res = await app.request("/api/jobs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) })
164
- expect(res.status).toBe(422)
165
- })
166
-
167
- it("deletes a job", async () => {
168
- const { app } = buildApp()
169
- const pRes = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "appd" }) })
170
- const { id } = await pRes.json() as { id: string }
171
- const jRes = await app.request("/api/jobs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ project_id: id, schedule: "*/5 * * * *" }) })
172
- const job = await jRes.json() as { id: string }
173
- const del = await app.request(`/api/jobs/${job.id}`, { method: "DELETE" })
174
- expect(del.status).toBe(200)
175
- })
176
- })
177
-
178
- describe("perf routes", () => {
179
- it("returns 422 without project_id", async () => {
180
- const { app } = buildApp()
181
- const res = await app.request("/api/perf")
182
- expect(res.status).toBe(422)
183
- })
184
-
185
- it("returns null when no snapshot exists", async () => {
186
- const { app } = buildApp()
187
- const pRes = await app.request("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "perf-app" }) })
188
- const { id } = await pRes.json() as { id: string }
189
- const res = await app.request(`/api/perf?project_id=${id}`)
190
- expect(res.status).toBe(200)
191
- const body = await res.json()
192
- expect(body).toBeNull()
193
- })
194
- })
@@ -1,119 +0,0 @@
1
- export type LogLevel = "debug" | "info" | "warn" | "error" | "fatal"
2
- export type LogSource = "sdk" | "script" | "scanner"
3
-
4
- export interface LogEntry {
5
- id?: string
6
- timestamp?: string
7
- project_id?: string
8
- page_id?: string
9
- level: LogLevel
10
- source?: LogSource
11
- service?: string
12
- message: string
13
- trace_id?: string
14
- session_id?: string
15
- agent?: string
16
- url?: string
17
- stack_trace?: string
18
- metadata?: Record<string, unknown>
19
- }
20
-
21
- export interface LogRow {
22
- id: string
23
- timestamp: string
24
- project_id: string | null
25
- page_id: string | null
26
- level: LogLevel
27
- source: LogSource
28
- service: string | null
29
- message: string
30
- trace_id: string | null
31
- session_id: string | null
32
- agent: string | null
33
- url: string | null
34
- stack_trace: string | null
35
- metadata: string | null
36
- }
37
-
38
- export interface Project {
39
- id: string
40
- name: string
41
- github_repo: string | null
42
- base_url: string | null
43
- description: string | null
44
- github_description: string | null
45
- github_branch: string | null
46
- github_sha: string | null
47
- last_synced_at: string | null
48
- created_at: string
49
- }
50
-
51
- export interface Page {
52
- id: string
53
- project_id: string
54
- url: string
55
- path: string
56
- name: string | null
57
- last_scanned_at: string | null
58
- created_at: string
59
- }
60
-
61
- export interface ScanJob {
62
- id: string
63
- project_id: string
64
- page_id: string | null
65
- schedule: string
66
- enabled: number
67
- last_run_at: string | null
68
- created_at: string
69
- }
70
-
71
- export interface ScanRun {
72
- id: string
73
- job_id: string
74
- page_id: string | null
75
- started_at: string
76
- finished_at: string | null
77
- status: "running" | "completed" | "failed"
78
- logs_collected: number
79
- errors_found: number
80
- perf_score: number | null
81
- }
82
-
83
- export interface PerformanceSnapshot {
84
- id: string
85
- timestamp: string
86
- project_id: string
87
- page_id: string | null
88
- url: string
89
- lcp: number | null
90
- fcp: number | null
91
- cls: number | null
92
- tti: number | null
93
- ttfb: number | null
94
- score: number | null
95
- raw_audit: string | null
96
- }
97
-
98
- export interface LogQuery {
99
- project_id?: string
100
- page_id?: string
101
- level?: LogLevel | LogLevel[]
102
- service?: string
103
- since?: string
104
- until?: string
105
- text?: string
106
- trace_id?: string
107
- limit?: number
108
- offset?: number
109
- fields?: string[]
110
- }
111
-
112
- export interface LogSummary {
113
- project_id: string | null
114
- service: string | null
115
- page_id: string | null
116
- level: LogLevel
117
- count: number
118
- latest: string
119
- }
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": ["ESNext"],
4
- "target": "ESNext",
5
- "module": "Preserve",
6
- "moduleDetection": "force",
7
- "jsx": "react-jsx",
8
- "allowJs": true,
9
- "moduleResolution": "bundler",
10
- "allowImportingTsExtensions": true,
11
- "verbatimModuleSyntax": true,
12
- "noEmit": true,
13
- "strict": true,
14
- "skipLibCheck": true,
15
- "noFallthroughCasesInSwitch": true,
16
- "noUncheckedIndexedAccess": true,
17
- "noImplicitOverride": true,
18
- "noUnusedLocals": false,
19
- "noUnusedParameters": false,
20
- "noPropertyAccessFromIndexSignature": false
21
- }
22
- }
File without changes
File without changes