@kirrosh/zond 0.21.0 → 0.23.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 (256) hide show
  1. package/CHANGELOG.md +758 -3
  2. package/README.md +78 -15
  3. package/package.json +17 -10
  4. package/src/cli/argv.ts +122 -0
  5. package/src/cli/commands/add-api.ts +134 -0
  6. package/src/cli/commands/api/annotate/idempotency.ts +59 -0
  7. package/src/cli/commands/api/annotate/index.ts +525 -0
  8. package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
  9. package/src/cli/commands/api/annotate/overlay.ts +206 -0
  10. package/src/cli/commands/api/annotate/pagination.ts +60 -0
  11. package/src/cli/commands/api/annotate/prompts.ts +183 -0
  12. package/src/cli/commands/api/annotate/readback.ts +58 -0
  13. package/src/cli/commands/api/annotate/resources.ts +91 -0
  14. package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
  15. package/src/cli/commands/audit.ts +480 -0
  16. package/src/cli/commands/bootstrap.ts +710 -0
  17. package/src/cli/commands/catalog.ts +35 -0
  18. package/src/cli/commands/check.ts +348 -0
  19. package/src/cli/commands/checks.ts +756 -0
  20. package/src/cli/commands/ci-init.ts +55 -6
  21. package/src/cli/commands/clean.ts +212 -0
  22. package/src/cli/commands/cleanup.ts +262 -0
  23. package/src/cli/commands/completions.ts +192 -0
  24. package/src/cli/commands/coverage.ts +605 -132
  25. package/src/cli/commands/db.ts +180 -8
  26. package/src/cli/commands/describe.ts +37 -2
  27. package/src/cli/commands/discover.ts +1236 -0
  28. package/src/cli/commands/doctor.ts +607 -0
  29. package/src/cli/commands/fixtures.ts +402 -0
  30. package/src/cli/commands/generate.ts +420 -47
  31. package/src/cli/commands/init/agents-md.ts +61 -0
  32. package/src/cli/commands/init/bootstrap.ts +108 -0
  33. package/src/cli/commands/init/index.ts +244 -0
  34. package/src/cli/commands/init/skills.ts +98 -0
  35. package/src/cli/commands/init/templates/agents.md +77 -0
  36. package/src/cli/commands/init/templates/markdown.d.ts +4 -0
  37. package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
  38. package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
  39. package/src/cli/commands/init/templates/skills/zond.md +651 -0
  40. package/src/cli/commands/init/templates/zond-config.yml +14 -0
  41. package/src/cli/commands/prepare-fixtures.ts +135 -0
  42. package/src/cli/commands/probe/mass-assignment.ts +503 -0
  43. package/src/cli/commands/probe/security.ts +454 -0
  44. package/src/cli/commands/probe/static.ts +255 -0
  45. package/src/cli/commands/probe/webhooks.ts +161 -0
  46. package/src/cli/commands/probe.ts +459 -0
  47. package/src/cli/commands/reference.ts +87 -0
  48. package/src/cli/commands/refresh-api.ts +169 -0
  49. package/src/cli/commands/remove-api.ts +150 -0
  50. package/src/cli/commands/report-bundle.ts +318 -0
  51. package/src/cli/commands/report.ts +241 -0
  52. package/src/cli/commands/request.ts +379 -4
  53. package/src/cli/commands/run.ts +911 -33
  54. package/src/cli/commands/session.ts +244 -0
  55. package/src/cli/commands/use.ts +74 -0
  56. package/src/cli/index.ts +36 -607
  57. package/src/cli/json-envelope.ts +112 -3
  58. package/src/cli/json-schemas.ts +263 -0
  59. package/src/cli/program.ts +218 -0
  60. package/src/cli/resolve.ts +105 -0
  61. package/src/cli/status-filter.ts +124 -0
  62. package/src/cli/util/api-context.ts +85 -0
  63. package/src/cli/version.ts +8 -0
  64. package/src/core/anti-fp/bootstrap.ts +34 -0
  65. package/src/core/anti-fp/index.ts +33 -0
  66. package/src/core/anti-fp/registry.ts +44 -0
  67. package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
  68. package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
  69. package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
  70. package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
  71. package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
  72. package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
  73. package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
  74. package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
  75. package/src/core/anti-fp/types.ts +68 -0
  76. package/src/core/checks/checks/_crud-helpers.ts +133 -0
  77. package/src/core/checks/checks/_negative_mutator.ts +133 -0
  78. package/src/core/checks/checks/_readback-helpers.ts +133 -0
  79. package/src/core/checks/checks/content_type_conformance.ts +39 -0
  80. package/src/core/checks/checks/cross_call_references.ts +134 -0
  81. package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
  82. package/src/core/checks/checks/idempotency_replay.ts +246 -0
  83. package/src/core/checks/checks/ignored_auth.ts +211 -0
  84. package/src/core/checks/checks/index.ts +65 -0
  85. package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
  86. package/src/core/checks/checks/missing_required_header.ts +40 -0
  87. package/src/core/checks/checks/negative_data_rejection.ts +45 -0
  88. package/src/core/checks/checks/not_a_server_error.ts +27 -0
  89. package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
  90. package/src/core/checks/checks/pagination_invariants.ts +238 -0
  91. package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
  92. package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
  93. package/src/core/checks/checks/response_headers_conformance.ts +74 -0
  94. package/src/core/checks/checks/response_schema_conformance.ts +30 -0
  95. package/src/core/checks/checks/status_code_conformance.ts +61 -0
  96. package/src/core/checks/checks/unsupported_method.ts +63 -0
  97. package/src/core/checks/checks/use_after_free.ts +78 -0
  98. package/src/core/checks/index.ts +30 -0
  99. package/src/core/checks/mode.ts +79 -0
  100. package/src/core/checks/recommended-action.ts +64 -0
  101. package/src/core/checks/registry.ts +78 -0
  102. package/src/core/checks/runner.ts +874 -0
  103. package/src/core/checks/sarif.ts +230 -0
  104. package/src/core/checks/stateful.ts +121 -0
  105. package/src/core/checks/types.ts +189 -0
  106. package/src/core/classifier/recommended-action.ts +222 -0
  107. package/src/core/context/current.ts +51 -0
  108. package/src/core/context/session.ts +78 -0
  109. package/src/core/coverage/loader.ts +185 -0
  110. package/src/core/coverage/reasons.ts +300 -0
  111. package/src/core/diagnostics/db-analysis.ts +161 -12
  112. package/src/core/diagnostics/failure-class.ts +120 -0
  113. package/src/core/diagnostics/failure-hints.ts +212 -9
  114. package/src/core/diagnostics/spec-pointer.ts +99 -0
  115. package/src/core/diagnostics/suggested-fixes.ts +156 -0
  116. package/src/core/exporter/case-study/index.ts +270 -0
  117. package/src/core/exporter/curl.ts +40 -0
  118. package/src/core/exporter/exporter.ts +48 -0
  119. package/src/core/exporter/html-report/escape.ts +24 -0
  120. package/src/core/exporter/html-report/index.ts +479 -0
  121. package/src/core/exporter/html-report/script.ts +100 -0
  122. package/src/core/exporter/html-report/styles.ts +408 -0
  123. package/src/core/generator/chunker.ts +53 -15
  124. package/src/core/generator/coverage-phase.ts +0 -0
  125. package/src/core/generator/create-body.ts +89 -0
  126. package/src/core/generator/data-factory.ts +490 -33
  127. package/src/core/generator/describe.ts +1 -1
  128. package/src/core/generator/fixtures-builder.ts +325 -0
  129. package/src/core/generator/index.ts +7 -5
  130. package/src/core/generator/openapi-reader.ts +55 -3
  131. package/src/core/generator/path-param-disambig.ts +114 -0
  132. package/src/core/generator/resources-builder.ts +648 -0
  133. package/src/core/generator/schema-utils.ts +11 -3
  134. package/src/core/generator/serializer.ts +114 -15
  135. package/src/core/generator/suite-generator.ts +484 -77
  136. package/src/core/generator/types.ts +8 -0
  137. package/src/core/identity/identity-file.ts +129 -0
  138. package/src/core/lint/affects.ts +28 -0
  139. package/src/core/lint/config.ts +96 -0
  140. package/src/core/lint/format.ts +42 -0
  141. package/src/core/lint/index.ts +94 -0
  142. package/src/core/lint/reporter.ts +128 -0
  143. package/src/core/lint/rules/consistency.ts +158 -0
  144. package/src/core/lint/rules/heuristics.ts +97 -0
  145. package/src/core/lint/rules/strictness.ts +109 -0
  146. package/src/core/lint/types.ts +96 -0
  147. package/src/core/lint/walker.ts +248 -0
  148. package/src/core/meta/meta-store.ts +6 -73
  149. package/src/core/output/README.md +91 -0
  150. package/src/core/output/index.ts +13 -0
  151. package/src/core/output/run.ts +126 -0
  152. package/src/core/output/types.ts +129 -0
  153. package/src/core/parser/env-interpolation.ts +104 -0
  154. package/src/core/parser/filter.ts +57 -0
  155. package/src/core/parser/schema.ts +132 -5
  156. package/src/core/parser/types.ts +29 -2
  157. package/src/core/parser/variables.ts +0 -0
  158. package/src/core/parser/yaml-parser.ts +108 -13
  159. package/src/core/probe/bootstrap.ts +34 -0
  160. package/src/core/probe/dry-run-envelope.ts +57 -0
  161. package/src/core/probe/mass-assignment-probe-class.ts +198 -0
  162. package/src/core/probe/mass-assignment-probe.ts +1122 -0
  163. package/src/core/probe/mass-assignment-template.ts +212 -0
  164. package/src/core/probe/method-probe.ts +164 -0
  165. package/src/core/probe/method-shared.ts +69 -0
  166. package/src/core/probe/negative-probe.ts +691 -0
  167. package/src/core/probe/orphan-tracker.ts +188 -0
  168. package/src/core/probe/path-discovery.ts +440 -0
  169. package/src/core/probe/probe-harness.ts +120 -0
  170. package/src/core/probe/registry.ts +89 -0
  171. package/src/core/probe/runner.ts +136 -0
  172. package/src/core/probe/security-probe-class.ts +201 -0
  173. package/src/core/probe/security-probe.ts +1453 -0
  174. package/src/core/probe/shared.ts +505 -0
  175. package/src/core/probe/static-probe-class.ts +125 -0
  176. package/src/core/probe/types.ts +165 -0
  177. package/src/core/probe/verdict-aggregator.ts +33 -0
  178. package/src/core/probe/webhooks-probe.ts +284 -0
  179. package/src/core/reporter/console.ts +69 -4
  180. package/src/core/reporter/index.ts +2 -3
  181. package/src/core/reporter/json.ts +15 -2
  182. package/src/core/reporter/junit.ts +27 -12
  183. package/src/core/reporter/ndjson.ts +37 -0
  184. package/src/core/reporter/types.ts +3 -0
  185. package/src/core/runner/assertions.ts +62 -2
  186. package/src/core/runner/async-pool.ts +108 -0
  187. package/src/core/runner/auth-path.ts +8 -0
  188. package/src/core/runner/ci-context.ts +72 -0
  189. package/src/core/runner/executor.ts +391 -52
  190. package/src/core/runner/form-encode.ts +51 -0
  191. package/src/core/runner/http-client.ts +115 -7
  192. package/src/core/runner/learn-drift.ts +293 -0
  193. package/src/core/runner/preflight-vars.ts +149 -0
  194. package/src/core/runner/progress-tracker.ts +73 -0
  195. package/src/core/runner/rate-limiter.ts +203 -0
  196. package/src/core/runner/run-kind.ts +39 -0
  197. package/src/core/runner/schema-validator.ts +312 -0
  198. package/src/core/runner/send-request.ts +153 -20
  199. package/src/core/runner/types.ts +38 -0
  200. package/src/core/secrets/registry.ts +164 -0
  201. package/src/core/secrets/secrets-file.ts +115 -0
  202. package/src/core/selectors/operation-filter.ts +144 -0
  203. package/src/core/setup-api.ts +419 -17
  204. package/src/core/severity/category.ts +94 -0
  205. package/src/core/severity/index.ts +121 -0
  206. package/src/core/spec/layers.ts +154 -0
  207. package/src/core/util/format-eta.ts +21 -0
  208. package/src/core/utils.ts +5 -1
  209. package/src/core/workspace/config.ts +129 -0
  210. package/src/core/workspace/manifest.ts +283 -0
  211. package/src/core/workspace/output-rotation.ts +62 -0
  212. package/src/core/workspace/root.ts +94 -0
  213. package/src/core/workspace/triage-path.ts +87 -0
  214. package/src/db/lint-runs.ts +47 -0
  215. package/src/db/migrate.ts +126 -0
  216. package/src/db/migrations/0001_run_kind.sql +25 -0
  217. package/src/db/migrations/sql.d.ts +4 -0
  218. package/src/db/queries/collections.ts +133 -0
  219. package/src/db/queries/coverage.ts +9 -0
  220. package/src/db/queries/dashboard.ts +59 -0
  221. package/src/db/queries/results.ts +128 -0
  222. package/src/db/queries/runs.ts +235 -0
  223. package/src/db/queries/sessions.ts +42 -0
  224. package/src/db/queries/settings.ts +28 -0
  225. package/src/db/queries/types.ts +172 -0
  226. package/src/db/queries.ts +72 -802
  227. package/src/db/schema.ts +179 -48
  228. package/src/cli/commands/export.ts +0 -144
  229. package/src/cli/commands/guide.ts +0 -127
  230. package/src/cli/commands/init.ts +0 -57
  231. package/src/cli/commands/serve.ts +0 -81
  232. package/src/cli/commands/sync.ts +0 -269
  233. package/src/cli/commands/update.ts +0 -189
  234. package/src/cli/commands/validate.ts +0 -34
  235. package/src/core/exporter/postman.ts +0 -963
  236. package/src/core/generator/guide-builder.ts +0 -253
  237. package/src/core/meta/types.ts +0 -21
  238. package/src/core/parser/index.ts +0 -21
  239. package/src/core/runner/execute-run.ts +0 -132
  240. package/src/core/runner/index.ts +0 -12
  241. package/src/core/sync/spec-differ.ts +0 -38
  242. package/src/web/data/collection-state.ts +0 -362
  243. package/src/web/routes/api.ts +0 -314
  244. package/src/web/routes/dashboard.ts +0 -350
  245. package/src/web/routes/runs.ts +0 -64
  246. package/src/web/schemas.ts +0 -121
  247. package/src/web/server.ts +0 -134
  248. package/src/web/static/htmx.min.cjs +0 -1
  249. package/src/web/static/style.css +0 -1148
  250. package/src/web/views/endpoints-tab.ts +0 -174
  251. package/src/web/views/explorer-tab.ts +0 -402
  252. package/src/web/views/health-strip.ts +0 -92
  253. package/src/web/views/layout.ts +0 -48
  254. package/src/web/views/results.ts +0 -210
  255. package/src/web/views/runs-tab.ts +0 -126
  256. package/src/web/views/suites-tab.ts +0 -181
package/src/db/queries.ts CHANGED
@@ -1,802 +1,72 @@
1
- import { getDb } from "./schema.ts";
2
- import { resolve } from "path";
3
- import type { StepResult, TestRunResult } from "../core/runner/types.ts";
4
-
5
- // ──────────────────────────────────────────────
6
- // Path normalization
7
- // ──────────────────────────────────────────────
8
-
9
- export function normalizePath(p: string): string {
10
- return resolve(p).replace(/\\/g, "/");
11
- }
12
-
13
- // ──────────────────────────────────────────────
14
- // Types
15
- // ──────────────────────────────────────────────
16
-
17
- export interface CreateRunOpts {
18
- started_at: string;
19
- environment?: string;
20
- trigger?: string;
21
- commit_sha?: string;
22
- branch?: string;
23
- collection_id?: number;
24
- }
25
-
26
- export interface RunRecord {
27
- id: number;
28
- started_at: string;
29
- finished_at: string | null;
30
- total: number;
31
- passed: number;
32
- failed: number;
33
- skipped: number;
34
- trigger: string;
35
- commit_sha: string | null;
36
- branch: string | null;
37
- environment: string | null;
38
- duration_ms: number | null;
39
- collection_id: number | null;
40
- }
41
-
42
- export interface RunSummary {
43
- id: number;
44
- started_at: string;
45
- finished_at: string | null;
46
- total: number;
47
- passed: number;
48
- failed: number;
49
- skipped: number;
50
- environment: string | null;
51
- duration_ms: number | null;
52
- collection_id: number | null;
53
- }
54
-
55
- // ──────────────────────────────────────────────
56
- // Collection types
57
- // ──────────────────────────────────────────────
58
-
59
- export interface CollectionRecord {
60
- id: number;
61
- name: string;
62
- base_dir: string | null;
63
- test_path: string;
64
- openapi_spec: string | null;
65
- created_at: string;
66
- }
67
-
68
- export interface CollectionSummary {
69
- id: number;
70
- name: string;
71
- base_dir: string | null;
72
- test_path: string;
73
- openapi_spec: string | null;
74
- created_at: string;
75
- total_runs: number;
76
- pass_rate: number;
77
- last_run_at: string | null;
78
- last_run_passed: number;
79
- last_run_failed: number;
80
- last_run_total: number;
81
- }
82
-
83
- export interface CreateCollectionOpts {
84
- name: string;
85
- base_dir?: string;
86
- test_path: string;
87
- openapi_spec?: string;
88
- }
89
-
90
- export interface StoredStepResult {
91
- id: number;
92
- run_id: number;
93
- suite_name: string;
94
- test_name: string;
95
- status: string;
96
- duration_ms: number;
97
- request_method: string | null;
98
- request_url: string | null;
99
- request_body: string | null;
100
- response_status: number | null;
101
- response_body: string | null;
102
- response_headers: string | null;
103
- error_message: string | null;
104
- assertions: import("../core/runner/types.ts").AssertionResult[];
105
- captures: Record<string, unknown>;
106
- suite_file: string | null;
107
- }
108
-
109
- // ──────────────────────────────────────────────
110
- // Runs
111
- // ──────────────────────────────────────────────
112
-
113
- export function createRun(opts: CreateRunOpts): number {
114
- const db = getDb();
115
- const stmt = db.prepare(`
116
- INSERT INTO runs (started_at, environment, trigger, commit_sha, branch, collection_id)
117
- VALUES ($started_at, $environment, $trigger, $commit_sha, $branch, $collection_id)
118
- `);
119
- const result = stmt.run({
120
- $started_at: opts.started_at,
121
- $environment: opts.environment ?? null,
122
- $trigger: opts.trigger ?? "manual",
123
- $commit_sha: opts.commit_sha ?? null,
124
- $branch: opts.branch ?? null,
125
- $collection_id: opts.collection_id ?? null,
126
- });
127
- return Number(result.lastInsertRowid);
128
- }
129
-
130
- export function finalizeRun(runId: number, results: TestRunResult[]): void {
131
- const db = getDb();
132
-
133
- const total = results.reduce((s, r) => s + r.total, 0);
134
- const passed = results.reduce((s, r) => s + r.passed, 0);
135
- const failed = results.reduce((s, r) => s + r.failed, 0);
136
- const skipped = results.reduce((s, r) => s + r.skipped, 0);
137
-
138
- const started = results[0]?.started_at ?? new Date().toISOString();
139
- const finished = results[results.length - 1]?.finished_at ?? new Date().toISOString();
140
- const durationMs = new Date(finished).getTime() - new Date(started).getTime();
141
-
142
- db.prepare(`
143
- UPDATE runs
144
- SET finished_at = $finished_at,
145
- total = $total,
146
- passed = $passed,
147
- failed = $failed,
148
- skipped = $skipped,
149
- duration_ms = $duration_ms
150
- WHERE id = $id
151
- `).run({
152
- $finished_at: finished,
153
- $total: total,
154
- $passed: passed,
155
- $failed: failed,
156
- $skipped: skipped,
157
- $duration_ms: durationMs,
158
- $id: runId,
159
- });
160
- }
161
-
162
- export function getRunById(runId: number): RunRecord | null {
163
- const db = getDb();
164
- return db.query("SELECT * FROM runs WHERE id = ?").get(runId) as RunRecord | null;
165
- }
166
-
167
- export interface RunFilters {
168
- status?: string;
169
- environment?: string;
170
- date_from?: string;
171
- date_to?: string;
172
- test_name?: string;
173
- }
174
-
175
- function buildRunFilterSQL(filters: RunFilters): { where: string; params: unknown[] } {
176
- const clauses: string[] = [];
177
- const params: unknown[] = [];
178
-
179
- if (filters.status === "has_failures") {
180
- clauses.push("r.failed > 0");
181
- } else if (filters.status === "all_passed") {
182
- clauses.push("r.failed = 0 AND r.total > 0");
183
- }
184
-
185
- if (filters.environment) {
186
- clauses.push("r.environment = ?");
187
- params.push(filters.environment);
188
- }
189
-
190
- if (filters.date_from) {
191
- clauses.push("r.started_at >= ?");
192
- params.push(filters.date_from);
193
- }
194
-
195
- if (filters.date_to) {
196
- clauses.push("r.started_at <= ?");
197
- params.push(filters.date_to + "T23:59:59");
198
- }
199
-
200
- if (filters.test_name) {
201
- clauses.push("r.id IN (SELECT DISTINCT run_id FROM results WHERE test_name LIKE ?)");
202
- params.push(`%${filters.test_name}%`);
203
- }
204
-
205
- const where = clauses.length > 0 ? "WHERE " + clauses.join(" AND ") : "";
206
- return { where, params };
207
- }
208
-
209
- export function listRuns(limit = 20, offset = 0, filters?: RunFilters): RunSummary[] {
210
- const db = getDb();
211
- if (filters && Object.values(filters).some(Boolean)) {
212
- const { where, params } = buildRunFilterSQL(filters);
213
- return db.query(`
214
- SELECT r.id, r.started_at, r.finished_at, r.total, r.passed, r.failed, r.skipped, r.environment, r.duration_ms, r.collection_id
215
- FROM runs r
216
- ${where}
217
- ORDER BY r.started_at DESC
218
- LIMIT ? OFFSET ?
219
- `).all(...(params as (string | number)[]), limit, offset) as RunSummary[];
220
- }
221
- return db.query(`
222
- SELECT id, started_at, finished_at, total, passed, failed, skipped, environment, duration_ms, collection_id
223
- FROM runs
224
- ORDER BY started_at DESC
225
- LIMIT ? OFFSET ?
226
- `).all(limit, offset) as RunSummary[];
227
- }
228
-
229
- export function deleteRun(runId: number): boolean {
230
- const db = getDb();
231
- // results are cascade-deleted via FK; but SQLite FK delete cascade requires explicit config
232
- db.prepare("DELETE FROM results WHERE run_id = ?").run(runId);
233
- const result = db.prepare("DELETE FROM runs WHERE id = ?").run(runId);
234
- return result.changes > 0;
235
- }
236
-
237
- // ──────────────────────────────────────────────
238
- // Results (steps)
239
- // ──────────────────────────────────────────────
240
-
241
- export function saveResults(runId: number, suiteResults: TestRunResult[]): void {
242
- const db = getDb();
243
-
244
- const stmt = db.prepare(`
245
- INSERT INTO results
246
- (run_id, suite_name, test_name, status, duration_ms,
247
- request_method, request_url, request_body,
248
- response_status, response_body, response_headers, error_message, assertions, captures, suite_file)
249
- VALUES
250
- ($run_id, $suite_name, $test_name, $status, $duration_ms,
251
- $request_method, $request_url, $request_body,
252
- $response_status, $response_body, $response_headers, $error_message, $assertions, $captures, $suite_file)
253
- `);
254
-
255
- db.transaction(() => {
256
- for (const suite of suiteResults) {
257
- for (const step of suite.steps) {
258
- const maxBodySize = 50_000;
259
- const truncBody = (s: string | null | undefined) =>
260
- s && s.length > maxBodySize ? s.slice(0, maxBodySize) + "\n...[truncated]" : (s ?? null);
261
- stmt.run({
262
- $run_id: runId,
263
- $suite_name: suite.suite_name,
264
- $test_name: step.name,
265
- $status: step.status,
266
- $duration_ms: step.duration_ms,
267
- $request_method: step.request.method,
268
- $request_url: step.request.url,
269
- $request_body: truncBody(step.request.body),
270
- $response_status: step.response?.status ?? null,
271
- $response_body: truncBody(step.response?.body),
272
- $response_headers: step.response?.headers
273
- ? JSON.stringify(step.response.headers)
274
- : null,
275
- $error_message: step.error ?? null,
276
- $assertions: step.assertions.length > 0 ? JSON.stringify(step.assertions) : null,
277
- $captures: Object.keys(step.captures).length > 0 ? JSON.stringify(step.captures) : null,
278
- $suite_file: suite.suite_file ?? null,
279
- });
280
- }
281
- }
282
- })();
283
- }
284
-
285
- export function getResultsByRunId(runId: number): StoredStepResult[] {
286
- const db = getDb();
287
- const rows = db.query("SELECT * FROM results WHERE run_id = ? ORDER BY id").all(runId) as Array<
288
- Omit<StoredStepResult, "assertions" | "captures"> & { assertions: string | null; captures: string | null }
289
- >;
290
- return rows.map((row) => ({
291
- ...row,
292
- assertions: row.assertions ? JSON.parse(row.assertions) : [],
293
- captures: row.captures ? JSON.parse(row.captures) : {},
294
- }));
295
- }
296
-
297
- export function getFilteredResults(runId: number, filters: { method?: string; status?: number }): StoredStepResult[] {
298
- const db = getDb();
299
- const conditions = ["run_id = ?"];
300
- const params: (string | number)[] = [runId];
301
-
302
- if (filters.method) {
303
- conditions.push("request_method = ?");
304
- params.push(filters.method.toUpperCase());
305
- }
306
- if (filters.status !== undefined) {
307
- conditions.push("response_status = ?");
308
- params.push(filters.status);
309
- }
310
-
311
- const rows = db.query(`SELECT * FROM results WHERE ${conditions.join(" AND ")} ORDER BY id`).all(...params) as Array<
312
- Omit<StoredStepResult, "assertions" | "captures"> & { assertions: string | null; captures: string | null }
313
- >;
314
- return rows.map((row) => ({
315
- ...row,
316
- assertions: row.assertions ? JSON.parse(row.assertions) : [],
317
- captures: row.captures ? JSON.parse(row.captures) : {},
318
- }));
319
- }
320
-
321
- // ──────────────────────────────────────────────
322
- // Dashboard metrics
323
- // ──────────────────────────────────────────────
324
-
325
- export interface DashboardStats {
326
- totalRuns: number;
327
- totalTests: number;
328
- overallPassRate: number;
329
- avgDuration: number;
330
- }
331
-
332
- export function getDashboardStats(): DashboardStats {
333
- const db = getDb();
334
- const row = db.query(`
335
- SELECT
336
- COUNT(*) AS totalRuns,
337
- COALESCE(SUM(total), 0) AS totalTests,
338
- CASE WHEN SUM(total) > 0
339
- THEN ROUND(SUM(passed) * 100.0 / SUM(total), 1)
340
- ELSE 0 END AS overallPassRate,
341
- COALESCE(ROUND(AVG(duration_ms), 0), 0) AS avgDuration
342
- FROM runs
343
- WHERE finished_at IS NOT NULL
344
- `).get() as { totalRuns: number; totalTests: number; overallPassRate: number; avgDuration: number };
345
- return row;
346
- }
347
-
348
- export interface PassRateTrendPoint {
349
- run_id: number;
350
- started_at: string;
351
- pass_rate: number;
352
- }
353
-
354
- export function getPassRateTrend(limit = 30): PassRateTrendPoint[] {
355
- const db = getDb();
356
- return db.query(`
357
- SELECT id AS run_id, started_at,
358
- CASE WHEN total > 0 THEN ROUND(passed * 100.0 / total, 1) ELSE 0 END AS pass_rate
359
- FROM runs
360
- WHERE finished_at IS NOT NULL
361
- ORDER BY started_at DESC
362
- LIMIT ?
363
- `).all(limit) as PassRateTrendPoint[];
364
- }
365
-
366
- export interface SlowestTest {
367
- suite_name: string;
368
- test_name: string;
369
- avg_duration: number;
370
- }
371
-
372
- export function getSlowestTests(limit = 5): SlowestTest[] {
373
- const db = getDb();
374
- return db.query(`
375
- SELECT suite_name, test_name, ROUND(AVG(duration_ms), 0) AS avg_duration
376
- FROM results
377
- GROUP BY suite_name, test_name
378
- ORDER BY avg_duration DESC
379
- LIMIT ?
380
- `).all(limit) as SlowestTest[];
381
- }
382
-
383
- export interface FlakyTest {
384
- suite_name: string;
385
- test_name: string;
386
- distinct_statuses: number;
387
- }
388
-
389
- export function getFlakyTests(runsBack = 20, limit = 5): FlakyTest[] {
390
- const db = getDb();
391
- return db.query(`
392
- SELECT r.suite_name, r.test_name, COUNT(DISTINCT r.status) AS distinct_statuses
393
- FROM results r
394
- INNER JOIN (SELECT id FROM runs ORDER BY started_at DESC LIMIT ?) recent ON r.run_id = recent.id
395
- GROUP BY r.suite_name, r.test_name
396
- HAVING COUNT(DISTINCT r.status) > 1
397
- ORDER BY distinct_statuses DESC
398
- LIMIT ?
399
- `).all(runsBack, limit) as FlakyTest[];
400
- }
401
-
402
- export function countRuns(filters?: RunFilters): number {
403
- const db = getDb();
404
- if (filters && Object.values(filters).some(Boolean)) {
405
- const { where, params } = buildRunFilterSQL(filters);
406
- const row = db.query(`SELECT COUNT(*) AS cnt FROM runs r ${where}`).get(...(params as (string | number)[])) as { cnt: number };
407
- return row.cnt;
408
- }
409
- const row = db.query("SELECT COUNT(*) AS cnt FROM runs").get() as { cnt: number };
410
- return row.cnt;
411
- }
412
-
413
- export function getDistinctEnvironments(): string[] {
414
- const db = getDb();
415
- const rows = db.query("SELECT DISTINCT environment FROM runs WHERE environment IS NOT NULL ORDER BY environment").all() as { environment: string }[];
416
- return rows.map((r) => r.environment);
417
- }
418
-
419
- // ──────────────────────────────────────────────
420
- // Collections
421
- // ──────────────────────────────────────────────
422
-
423
- export function createCollection(opts: CreateCollectionOpts): number {
424
- const db = getDb();
425
- const stmt = db.prepare(`
426
- INSERT INTO collections (name, base_dir, test_path, openapi_spec)
427
- VALUES ($name, $base_dir, $test_path, $openapi_spec)
428
- `);
429
- const result = stmt.run({
430
- $name: opts.name,
431
- $base_dir: opts.base_dir ?? null,
432
- $test_path: opts.test_path,
433
- $openapi_spec: opts.openapi_spec ?? null,
434
- });
435
- return Number(result.lastInsertRowid);
436
- }
437
-
438
- export function getCollectionById(id: number): CollectionRecord | null {
439
- const db = getDb();
440
- return db.query("SELECT * FROM collections WHERE id = ?").get(id) as CollectionRecord | null;
441
- }
442
-
443
- export function listCollections(): CollectionSummary[] {
444
- const db = getDb();
445
- return db.query(`
446
- SELECT
447
- c.id, c.name, c.base_dir, c.test_path, c.openapi_spec, c.created_at,
448
- COUNT(r.id) AS total_runs,
449
- CASE WHEN SUM(r.total) > 0
450
- THEN ROUND(SUM(r.passed) * 100.0 / SUM(r.total), 1)
451
- ELSE 0 END AS pass_rate,
452
- MAX(r.started_at) AS last_run_at,
453
- COALESCE((SELECT passed FROM runs WHERE collection_id = c.id ORDER BY started_at DESC LIMIT 1), 0) AS last_run_passed,
454
- COALESCE((SELECT failed FROM runs WHERE collection_id = c.id ORDER BY started_at DESC LIMIT 1), 0) AS last_run_failed,
455
- COALESCE((SELECT total FROM runs WHERE collection_id = c.id ORDER BY started_at DESC LIMIT 1), 0) AS last_run_total
456
- FROM collections c
457
- LEFT JOIN runs r ON r.collection_id = c.id AND r.finished_at IS NOT NULL
458
- GROUP BY c.id
459
- ORDER BY c.name
460
- `).all() as CollectionSummary[];
461
- }
462
-
463
- export function updateCollection(id: number, opts: Partial<CreateCollectionOpts>): boolean {
464
- const db = getDb();
465
- const sets: string[] = [];
466
- const params: Record<string, any> = { $id: id };
467
-
468
- if (opts.name !== undefined) { sets.push("name = $name"); params.$name = opts.name; }
469
- if (opts.base_dir !== undefined) { sets.push("base_dir = $base_dir"); params.$base_dir = opts.base_dir; }
470
- if (opts.test_path !== undefined) { sets.push("test_path = $test_path"); params.$test_path = opts.test_path; }
471
- if (opts.openapi_spec !== undefined) { sets.push("openapi_spec = $openapi_spec"); params.$openapi_spec = opts.openapi_spec; }
472
-
473
- if (sets.length === 0) return false;
474
-
475
- const result = db.prepare(`UPDATE collections SET ${sets.join(", ")} WHERE id = $id`).run(params);
476
- return result.changes > 0;
477
- }
478
-
479
- export function deleteCollection(id: number, deleteRuns = false): boolean {
480
- const db = getDb();
481
- if (deleteRuns) {
482
- const runIds = db.query("SELECT id FROM runs WHERE collection_id = ?").all(id) as { id: number }[];
483
- for (const row of runIds) {
484
- db.prepare("DELETE FROM results WHERE run_id = ?").run(row.id);
485
- }
486
- db.prepare("DELETE FROM runs WHERE collection_id = ?").run(id);
487
- } else {
488
- db.prepare("UPDATE runs SET collection_id = NULL WHERE collection_id = ?").run(id);
489
- }
490
- const result = db.prepare("DELETE FROM collections WHERE id = ?").run(id);
491
- return result.changes > 0;
492
- }
493
-
494
- export function findCollectionByTestPath(path: string): CollectionRecord | null {
495
- const db = getDb();
496
- const normalized = normalizePath(path);
497
- return db.query("SELECT * FROM collections WHERE test_path = ?").get(normalized) as CollectionRecord | null;
498
- }
499
-
500
- export function findCollectionByNameOrId(nameOrId: string): CollectionRecord | null {
501
- const db = getDb();
502
- // Try as numeric ID first
503
- const id = parseInt(nameOrId, 10);
504
- if (!isNaN(id)) {
505
- const byId = db.query("SELECT * FROM collections WHERE id = ?").get(id) as CollectionRecord | null;
506
- if (byId) return byId;
507
- }
508
- // Then by name (case-insensitive)
509
- return db.query("SELECT * FROM collections WHERE lower(name) = lower(?)").get(nameOrId) as CollectionRecord | null;
510
- }
511
-
512
- export function findCollectionBySpec(spec: string): CollectionRecord | null {
513
- const db = getDb();
514
- return db.query("SELECT * FROM collections WHERE openapi_spec = ?").get(spec) as CollectionRecord | null;
515
- }
516
-
517
- export function listRunsByCollection(collectionId: number, limit = 20, offset = 0): RunSummary[] {
518
- const db = getDb();
519
- return db.query(`
520
- SELECT id, started_at, finished_at, total, passed, failed, skipped, environment, duration_ms, collection_id
521
- FROM runs
522
- WHERE collection_id = ?
523
- ORDER BY started_at DESC
524
- LIMIT ? OFFSET ?
525
- `).all(collectionId, limit, offset) as RunSummary[];
526
- }
527
-
528
- export function getCollectionPassRateTrend(collectionId: number, limit = 30): PassRateTrendPoint[] {
529
- const db = getDb();
530
- return db.query(`
531
- SELECT id AS run_id, started_at,
532
- CASE WHEN total > 0 THEN ROUND(passed * 100.0 / total, 1) ELSE 0 END AS pass_rate
533
- FROM runs
534
- WHERE collection_id = ? AND finished_at IS NOT NULL
535
- ORDER BY started_at DESC
536
- LIMIT ?
537
- `).all(collectionId, limit) as PassRateTrendPoint[];
538
- }
539
-
540
- export function countRunsByCollection(collectionId: number): number {
541
- const db = getDb();
542
- const row = db.query("SELECT COUNT(*) AS cnt FROM runs WHERE collection_id = ?").get(collectionId) as { cnt: number };
543
- return row.cnt;
544
- }
545
-
546
- export function getCollectionStats(collectionId: number): DashboardStats {
547
- const db = getDb();
548
- const row = db.query(`
549
- SELECT
550
- COUNT(*) AS totalRuns,
551
- COALESCE(SUM(total), 0) AS totalTests,
552
- CASE WHEN SUM(total) > 0
553
- THEN ROUND(SUM(passed) * 100.0 / SUM(total), 1)
554
- ELSE 0 END AS overallPassRate,
555
- COALESCE(ROUND(AVG(duration_ms), 0), 0) AS avgDuration
556
- FROM runs
557
- WHERE collection_id = ? AND finished_at IS NOT NULL
558
- `).get(collectionId) as { totalRuns: number; totalTests: number; overallPassRate: number; avgDuration: number };
559
- return row;
560
- }
561
-
562
- export function linkRunToCollection(runId: number, collectionId: number): void {
563
- const db = getDb();
564
- db.prepare("UPDATE runs SET collection_id = ? WHERE id = ?").run(collectionId, runId);
565
- }
566
-
567
- // ──────────────────────────────────────────────
568
- // AI Generations
569
- // ──────────────────────────────────────────────
570
-
571
- export interface AIGenerationRecord {
572
- id: number;
573
- collection_id: number | null;
574
- prompt: string;
575
- model: string;
576
- provider: string;
577
- generated_yaml: string | null;
578
- output_path: string | null;
579
- status: string;
580
- error_message: string | null;
581
- prompt_tokens: number | null;
582
- completion_tokens: number | null;
583
- duration_ms: number | null;
584
- created_at: string;
585
- }
586
-
587
- export interface SaveAIGenerationOpts {
588
- collection_id?: number;
589
- prompt: string;
590
- model: string;
591
- provider: string;
592
- generated_yaml?: string;
593
- output_path?: string;
594
- status: string;
595
- error_message?: string;
596
- prompt_tokens?: number;
597
- completion_tokens?: number;
598
- duration_ms?: number;
599
- }
600
-
601
- export function saveAIGeneration(opts: SaveAIGenerationOpts): number {
602
- const db = getDb();
603
- const result = db.prepare(`
604
- INSERT INTO ai_generations
605
- (collection_id, prompt, model, provider, generated_yaml, output_path,
606
- status, error_message, prompt_tokens, completion_tokens, duration_ms)
607
- VALUES ($collection_id, $prompt, $model, $provider, $generated_yaml, $output_path,
608
- $status, $error_message, $prompt_tokens, $completion_tokens, $duration_ms)
609
- `).run({
610
- $collection_id: opts.collection_id ?? null,
611
- $prompt: opts.prompt,
612
- $model: opts.model,
613
- $provider: opts.provider,
614
- $generated_yaml: opts.generated_yaml ?? null,
615
- $output_path: opts.output_path ?? null,
616
- $status: opts.status,
617
- $error_message: opts.error_message ?? null,
618
- $prompt_tokens: opts.prompt_tokens ?? null,
619
- $completion_tokens: opts.completion_tokens ?? null,
620
- $duration_ms: opts.duration_ms ?? null,
621
- });
622
- return Number(result.lastInsertRowid);
623
- }
624
-
625
- export function listAIGenerations(collectionId: number, limit = 10): AIGenerationRecord[] {
626
- const db = getDb();
627
- return db.query(`
628
- SELECT * FROM ai_generations
629
- WHERE collection_id = ?
630
- ORDER BY created_at DESC
631
- LIMIT ?
632
- `).all(collectionId, limit) as AIGenerationRecord[];
633
- }
634
-
635
- export function getAIGeneration(id: number): AIGenerationRecord | null {
636
- const db = getDb();
637
- return db.query("SELECT * FROM ai_generations WHERE id = ?").get(id) as AIGenerationRecord | null;
638
- }
639
-
640
- export function updateAIGenerationOutputPath(id: number, outputPath: string): boolean {
641
- const db = getDb();
642
- const result = db.prepare("UPDATE ai_generations SET output_path = ? WHERE id = ?").run(outputPath, id);
643
- return result.changes > 0;
644
- }
645
-
646
- export function listSavedAIGenerations(collectionId: number): AIGenerationRecord[] {
647
- const db = getDb();
648
- return db.query(`
649
- SELECT * FROM ai_generations
650
- WHERE collection_id = ? AND output_path IS NOT NULL AND output_path != ''
651
- ORDER BY created_at DESC
652
- `).all(collectionId) as AIGenerationRecord[];
653
- }
654
-
655
- export function findAIGenerationByYaml(collectionId: number, yaml: string): AIGenerationRecord | null {
656
- const db = getDb();
657
- return db.query(
658
- "SELECT * FROM ai_generations WHERE collection_id = ? AND generated_yaml = ? ORDER BY created_at DESC LIMIT 1"
659
- ).get(collectionId, yaml) as AIGenerationRecord | null;
660
- }
661
-
662
- // ──────────────────────────────────────────────
663
- // Settings
664
- // ──────────────────────────────────────────────
665
-
666
- export function getSetting(key: string): string | null {
667
- const db = getDb();
668
- const row = db.query("SELECT value FROM settings WHERE key = ?").get(key) as { value: string } | null;
669
- return row?.value ?? null;
670
- }
671
-
672
- export function setSetting(key: string, value: string): void {
673
- const db = getDb();
674
- db.prepare(`
675
- INSERT INTO settings (key, value) VALUES ($key, $value)
676
- ON CONFLICT(key) DO UPDATE SET value = excluded.value
677
- `).run({ $key: key, $value: value });
678
- }
679
-
680
- export interface AISettingsConfig {
681
- provider: string;
682
- model: string;
683
- base_url: string;
684
- api_key: string;
685
- }
686
-
687
- export function getAISettings(): AISettingsConfig {
688
- return {
689
- provider: getSetting("ai_provider") ?? "ollama",
690
- model: getSetting("ai_model") ?? "",
691
- base_url: getSetting("ai_base_url") ?? "",
692
- api_key: getSetting("ai_api_key") ?? "",
693
- };
694
- }
695
-
696
- export function setAISettings(config: Partial<AISettingsConfig>): void {
697
- if (config.provider !== undefined) setSetting("ai_provider", config.provider);
698
- if (config.model !== undefined) setSetting("ai_model", config.model);
699
- if (config.base_url !== undefined) setSetting("ai_base_url", config.base_url);
700
- if (config.api_key !== undefined) setSetting("ai_api_key", config.api_key);
701
- }
702
-
703
- // ──────────────────────────────────────────────
704
- // Chat Sessions & Messages
705
- // ──────────────────────────────────────────────
706
-
707
- export interface ChatSessionRecord {
708
- id: number;
709
- title: string | null;
710
- provider: string;
711
- model: string;
712
- created_at: string;
713
- last_active: string;
714
- }
715
-
716
- export interface ChatMessageRecord {
717
- id: number;
718
- session_id: number;
719
- role: string;
720
- content: string;
721
- tool_name: string | null;
722
- tool_args: string | null;
723
- tool_result: string | null;
724
- input_tokens: number | null;
725
- output_tokens: number | null;
726
- created_at: string;
727
- }
728
-
729
- export interface SaveChatMessageOpts {
730
- session_id: number;
731
- role: string;
732
- content: string;
733
- tool_name?: string;
734
- tool_args?: string;
735
- tool_result?: string;
736
- input_tokens?: number;
737
- output_tokens?: number;
738
- }
739
-
740
- export function createChatSession(provider: string, model: string, title?: string): number {
741
- const db = getDb();
742
- const result = db.prepare(`
743
- INSERT INTO chat_sessions (title, provider, model)
744
- VALUES ($title, $provider, $model)
745
- `).run({
746
- $title: title ?? null,
747
- $provider: provider,
748
- $model: model,
749
- });
750
- return Number(result.lastInsertRowid);
751
- }
752
-
753
- export function saveChatMessage(opts: SaveChatMessageOpts): number {
754
- const db = getDb();
755
-
756
- // Update session last_active
757
- db.prepare("UPDATE chat_sessions SET last_active = datetime('now') WHERE id = ?").run(opts.session_id);
758
-
759
- const result = db.prepare(`
760
- INSERT INTO chat_messages (session_id, role, content, tool_name, tool_args, tool_result, input_tokens, output_tokens)
761
- VALUES ($session_id, $role, $content, $tool_name, $tool_args, $tool_result, $input_tokens, $output_tokens)
762
- `).run({
763
- $session_id: opts.session_id,
764
- $role: opts.role,
765
- $content: opts.content,
766
- $tool_name: opts.tool_name ?? null,
767
- $tool_args: opts.tool_args ?? null,
768
- $tool_result: opts.tool_result ?? null,
769
- $input_tokens: opts.input_tokens ?? null,
770
- $output_tokens: opts.output_tokens ?? null,
771
- });
772
- return Number(result.lastInsertRowid);
773
- }
774
-
775
- export function getChatMessages(sessionId: number): ChatMessageRecord[] {
776
- const db = getDb();
777
- return db.query(
778
- "SELECT * FROM chat_messages WHERE session_id = ? ORDER BY created_at ASC"
779
- ).all(sessionId) as ChatMessageRecord[];
780
- }
781
-
782
- export function listChatSessions(limit = 20): ChatSessionRecord[] {
783
- const db = getDb();
784
- return db.query(
785
- "SELECT * FROM chat_sessions ORDER BY last_active DESC LIMIT ?"
786
- ).all(limit) as ChatSessionRecord[];
787
- }
788
-
789
- export interface CoreMessageFormat {
790
- role: "user" | "assistant";
791
- content: string;
792
- }
793
-
794
- export function loadSessionHistory(sessionId: number): CoreMessageFormat[] {
795
- const messages = getChatMessages(sessionId);
796
- return messages
797
- .filter((m) => m.role === "user" || m.role === "assistant")
798
- .map((m) => ({
799
- role: m.role as "user" | "assistant",
800
- content: m.content,
801
- }));
802
- }
1
+ /**
2
+ * Façade over the per-domain query modules in `src/db/queries/`.
3
+ * All the implementations moved out in TASK-187 (m-11). This file only
4
+ * re-exports them so existing call sites stay stable; it will be
5
+ * deleted in the next minor (call sites should import from
6
+ * `src/db/queries/<domain>` directly).
7
+ *
8
+ * Layout:
9
+ * queries/types.ts — shared interfaces, normalizePath
10
+ * queries/runs.ts — createRun, listRuns, finalizeRun, …
11
+ * queries/sessions.ts — listSessions, countSessions, …
12
+ * queries/results.ts — saveResults, getResultsByRunId, …
13
+ * queries/collections.ts — collections CRUD
14
+ * queries/dashboard.ts — getDashboardStats, getPassRateTrend, …
15
+ * queries/settings.ts — kv settings (currently dormant)
16
+ * queries/coverage.ts — reserved (future coverage queries)
17
+ */
18
+
19
+ export {
20
+ normalizePath,
21
+ type CreateRunOpts,
22
+ type RunRecord,
23
+ type RunSummary,
24
+ type SessionSummary,
25
+ type CollectionRecord,
26
+ type CollectionSummary,
27
+ type CreateCollectionOpts,
28
+ type StoredStepResult,
29
+ type RunFilters,
30
+ type DashboardStats,
31
+ type PassRateTrendPoint,
32
+ type SlowestTest,
33
+ type FlakyTest,
34
+ } from "./queries/types.ts";
35
+
36
+ export {
37
+ createRun,
38
+ finalizeRun,
39
+ getRunById,
40
+ listRuns,
41
+ deleteRun,
42
+ countRuns,
43
+ listRunsByCollectionFiltered,
44
+ getLatestFailingRunId,
45
+ getLatestRunId,
46
+ } from "./queries/runs.ts";
47
+
48
+ export { listSessions, countSessions, listRunsBySession } from "./queries/sessions.ts";
49
+
50
+ export {
51
+ saveResults,
52
+ getResultsByRunId,
53
+ getFilteredResults,
54
+ } from "./queries/results.ts";
55
+
56
+ export {
57
+ createCollection,
58
+ getCollectionById,
59
+ getLatestRunByCollection,
60
+ listCollections,
61
+ updateCollection,
62
+ deleteCollection,
63
+ findCollectionByTestPath,
64
+ findCollectionByNameOrId,
65
+ } from "./queries/collections.ts";
66
+
67
+ export {
68
+ getDashboardStats,
69
+ getPassRateTrend,
70
+ getSlowestTests,
71
+ getFlakyTests,
72
+ } from "./queries/dashboard.ts";