@sdods/db 0.2.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 (87) hide show
  1. package/LICENSE +17 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/col.d.ts +32 -0
  4. package/dist/col.js +58 -0
  5. package/dist/create-db.d.ts +14 -0
  6. package/dist/create-db.js +58 -0
  7. package/dist/driver.d.ts +10 -0
  8. package/dist/driver.js +16 -0
  9. package/dist/ids.d.ts +2 -0
  10. package/dist/ids.js +2 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.js +22 -0
  13. package/dist/ingest/attachments.d.ts +40 -0
  14. package/dist/ingest/attachments.js +154 -0
  15. package/dist/ingest/cucumber-ingest.d.ts +40 -0
  16. package/dist/ingest/cucumber-ingest.js +543 -0
  17. package/dist/ingest/finalize.d.ts +28 -0
  18. package/dist/ingest/finalize.js +132 -0
  19. package/dist/ingest/index.d.ts +26 -0
  20. package/dist/ingest/index.js +193 -0
  21. package/dist/ingest/message-index.d.ts +64 -0
  22. package/dist/ingest/message-index.js +146 -0
  23. package/dist/ingest/parse-ndjson.d.ts +10 -0
  24. package/dist/ingest/parse-ndjson.js +35 -0
  25. package/dist/ingest/pw-json-ingest.d.ts +14 -0
  26. package/dist/ingest/pw-json-ingest.js +259 -0
  27. package/dist/ingest/runner-json-ingest.d.ts +14 -0
  28. package/dist/ingest/runner-json-ingest.js +259 -0
  29. package/dist/ingest/types.d.ts +62 -0
  30. package/dist/ingest/types.js +89 -0
  31. package/dist/insights.d.ts +62 -0
  32. package/dist/insights.js +184 -0
  33. package/dist/migrate.d.ts +30 -0
  34. package/dist/migrate.js +114 -0
  35. package/dist/migrations/0001_platform_init.d.ts +5 -0
  36. package/dist/migrations/0001_platform_init.js +48 -0
  37. package/dist/migrations/0002_results.d.ts +5 -0
  38. package/dist/migrations/0002_results.js +124 -0
  39. package/dist/migrations/0003_improvement.d.ts +5 -0
  40. package/dist/migrations/0003_improvement.js +56 -0
  41. package/dist/migrations/0004_test_data.d.ts +5 -0
  42. package/dist/migrations/0004_test_data.js +48 -0
  43. package/dist/migrations/0005_integrations_agents_audit.d.ts +5 -0
  44. package/dist/migrations/0005_integrations_agents_audit.js +100 -0
  45. package/dist/migrations/0006_schedules.d.ts +5 -0
  46. package/dist/migrations/0006_schedules.js +34 -0
  47. package/dist/migrations/0007_hierarchy.d.ts +5 -0
  48. package/dist/migrations/0007_hierarchy.js +76 -0
  49. package/dist/migrations/0008_runner_naming.d.ts +14 -0
  50. package/dist/migrations/0008_runner_naming.js +35 -0
  51. package/dist/migrations/helpers.d.ts +15 -0
  52. package/dist/migrations/helpers.js +30 -0
  53. package/dist/migrations/index.d.ts +6 -0
  54. package/dist/migrations/index.js +22 -0
  55. package/dist/repos/auth.d.ts +109 -0
  56. package/dist/repos/auth.js +258 -0
  57. package/dist/repos/hierarchy.d.ts +151 -0
  58. package/dist/repos/hierarchy.js +465 -0
  59. package/dist/repos/improvement.d.ts +84 -0
  60. package/dist/repos/improvement.js +242 -0
  61. package/dist/repos/index.d.ts +8 -0
  62. package/dist/repos/index.js +8 -0
  63. package/dist/repos/platform.d.ts +250 -0
  64. package/dist/repos/platform.js +429 -0
  65. package/dist/repos/projects.d.ts +87 -0
  66. package/dist/repos/projects.js +118 -0
  67. package/dist/repos/runs.d.ts +306 -0
  68. package/dist/repos/runs.js +321 -0
  69. package/dist/repos/test-data.d.ts +84 -0
  70. package/dist/repos/test-data.js +291 -0
  71. package/dist/schema.d.ts +514 -0
  72. package/dist/schema.js +37 -0
  73. package/dist/scopes.d.ts +2 -0
  74. package/dist/scopes.js +2 -0
  75. package/dist/switch/export.d.ts +15 -0
  76. package/dist/switch/export.js +67 -0
  77. package/dist/switch/import.d.ts +16 -0
  78. package/dist/switch/import.js +106 -0
  79. package/dist/switch/index.d.ts +5 -0
  80. package/dist/switch/index.js +5 -0
  81. package/dist/switch/prune.d.ts +19 -0
  82. package/dist/switch/prune.js +50 -0
  83. package/dist/switch/switch.d.ts +34 -0
  84. package/dist/switch/switch.js +133 -0
  85. package/dist/test-data.d.ts +22 -0
  86. package/dist/test-data.js +79 -0
  87. package/package.json +50 -0
@@ -0,0 +1,306 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { RunRecord } from '@sdods/contracts/types';
3
+ import type { Driver } from '../driver.js';
4
+ import type { Database } from '../schema.js';
5
+ export interface RunUpsert {
6
+ id: string;
7
+ projectId: string;
8
+ workspaceId?: string | null;
9
+ environmentId?: string | null;
10
+ envName: string;
11
+ process?: string | null;
12
+ trigger?: string;
13
+ status?: string;
14
+ suiteTag?: string | null;
15
+ tagsExpr?: string | null;
16
+ layers?: string[];
17
+ browsers?: string[];
18
+ shardTotal?: number | null;
19
+ gitSha?: string | null;
20
+ gitBranch?: string | null;
21
+ ciProvider?: string | null;
22
+ ciRunId?: string | null;
23
+ ciUrl?: string | null;
24
+ startedBy?: string | null;
25
+ command?: string | null;
26
+ startedAt?: string | null;
27
+ finishedAt?: string | null;
28
+ durationMs?: number | null;
29
+ totals?: Record<string, unknown>;
30
+ artifactsDir?: string | null;
31
+ htmlReportRel?: string | null;
32
+ exitCode?: number | null;
33
+ errorText?: string | null;
34
+ }
35
+ export declare function upsertRun(db: Kysely<Database>, _driver: Driver, r: RunUpsert): Promise<void>;
36
+ export declare function getRun(db: Kysely<Database>, id: string): Promise<(RunRecord & {
37
+ projectId: string;
38
+ totalsRaw: Record<string, unknown>;
39
+ }) | null>;
40
+ export declare function listRuns(db: Kysely<Database>, opts?: {
41
+ projectSlug?: string;
42
+ status?: string;
43
+ env?: string;
44
+ limit?: number;
45
+ before?: string;
46
+ }): Promise<(RunRecord & {
47
+ projectId: string;
48
+ totalsRaw: Record<string, unknown>;
49
+ })[]>;
50
+ export declare function mapRun(row: any): RunRecord & {
51
+ projectId: string;
52
+ totalsRaw: Record<string, unknown>;
53
+ };
54
+ export declare function deleteRunChildren(db: Kysely<Database>, runId: string): Promise<void>;
55
+ export declare function deleteRun(db: Kysely<Database>, runId: string): Promise<void>;
56
+ /** Scenario tree for a run: scenarios with their attempts (no steps). */
57
+ export declare function getRunScenarios(db: Kysely<Database>, runId: string): Promise<{
58
+ attempts: any[];
59
+ id: any;
60
+ runId: any;
61
+ projectId: any;
62
+ naturalKey: any;
63
+ fingerprint: any;
64
+ source: any;
65
+ featureUri: any;
66
+ featureName: any;
67
+ scenarioName: any;
68
+ module: any;
69
+ exampleIndex: any;
70
+ runnerProject: any;
71
+ layer: any;
72
+ browser: any;
73
+ suiteTag: any;
74
+ tags: string[];
75
+ jiraKeys: string[];
76
+ status: any;
77
+ attemptsCount: any;
78
+ flaky: boolean;
79
+ durationMs: any;
80
+ errorMessage: any;
81
+ errorStack: any;
82
+ startedAt: string | null;
83
+ finishedAt: string | null;
84
+ }[]>;
85
+ /** One scenario with steps and artifacts grouped by attempt. */
86
+ export declare function getScenarioDetail(db: Kysely<Database>, scenarioId: string): Promise<{
87
+ attempts: {
88
+ steps: {
89
+ id: any;
90
+ attemptId: any;
91
+ scenarioId: any;
92
+ runId: any;
93
+ stepIndex: any;
94
+ kind: any;
95
+ hookType: any;
96
+ keyword: any;
97
+ text: any;
98
+ argument: unknown;
99
+ status: any;
100
+ durationMs: any;
101
+ errorMessage: any;
102
+ errorStack: any;
103
+ definitionLocation: any;
104
+ layerHint: any;
105
+ apiSnapshot: unknown;
106
+ perf: unknown;
107
+ startedAt: string | null;
108
+ finishedAt: string | null;
109
+ }[];
110
+ artifacts: {
111
+ id: any;
112
+ runId: any;
113
+ scenarioId: any;
114
+ attemptId: any;
115
+ stepId: any;
116
+ stepIndex: any;
117
+ kind: any;
118
+ phase: any;
119
+ mediaType: any;
120
+ fileName: any;
121
+ relPath: any;
122
+ sizeBytes: any;
123
+ sha256: any;
124
+ width: any;
125
+ height: any;
126
+ meta: Record<string, unknown> | null;
127
+ }[];
128
+ heals: {
129
+ id: any;
130
+ projectId: any;
131
+ runId: any;
132
+ scenarioId: any;
133
+ attemptId: any;
134
+ stepId: any;
135
+ pageUrl: any;
136
+ originalSelector: any;
137
+ context: Record<string, unknown>;
138
+ strategyUsed: any;
139
+ healedSelector: any;
140
+ candidates: unknown[];
141
+ succeeded: boolean;
142
+ durationMs: any;
143
+ source: any;
144
+ accepted: boolean | null;
145
+ createdAt: string | null;
146
+ }[];
147
+ id: any;
148
+ scenarioId: any;
149
+ attempt: any;
150
+ testCaseStartedId: any;
151
+ status: any;
152
+ durationMs: any;
153
+ errorMessage: any;
154
+ errorStack: any;
155
+ willBeRetried: boolean;
156
+ workerIndex: any;
157
+ startedAt: string | null;
158
+ finishedAt: string | null;
159
+ }[];
160
+ id: any;
161
+ runId: any;
162
+ projectId: any;
163
+ naturalKey: any;
164
+ fingerprint: any;
165
+ source: any;
166
+ featureUri: any;
167
+ featureName: any;
168
+ scenarioName: any;
169
+ module: any;
170
+ exampleIndex: any;
171
+ runnerProject: any;
172
+ layer: any;
173
+ browser: any;
174
+ suiteTag: any;
175
+ tags: string[];
176
+ jiraKeys: string[];
177
+ status: any;
178
+ attemptsCount: any;
179
+ flaky: boolean;
180
+ durationMs: any;
181
+ errorMessage: any;
182
+ errorStack: any;
183
+ startedAt: string | null;
184
+ finishedAt: string | null;
185
+ } | null>;
186
+ export declare function mapScenario(s: any): {
187
+ id: any;
188
+ runId: any;
189
+ projectId: any;
190
+ naturalKey: any;
191
+ fingerprint: any;
192
+ source: any;
193
+ featureUri: any;
194
+ featureName: any;
195
+ scenarioName: any;
196
+ module: any;
197
+ exampleIndex: any;
198
+ runnerProject: any;
199
+ layer: any;
200
+ browser: any;
201
+ suiteTag: any;
202
+ tags: string[];
203
+ jiraKeys: string[];
204
+ status: any;
205
+ attemptsCount: any;
206
+ flaky: boolean;
207
+ durationMs: any;
208
+ errorMessage: any;
209
+ errorStack: any;
210
+ startedAt: string | null;
211
+ finishedAt: string | null;
212
+ };
213
+ export declare function mapAttempt(a: any): {
214
+ id: any;
215
+ scenarioId: any;
216
+ attempt: any;
217
+ testCaseStartedId: any;
218
+ status: any;
219
+ durationMs: any;
220
+ errorMessage: any;
221
+ errorStack: any;
222
+ willBeRetried: boolean;
223
+ workerIndex: any;
224
+ startedAt: string | null;
225
+ finishedAt: string | null;
226
+ };
227
+ export declare function mapStep(s: any): {
228
+ id: any;
229
+ attemptId: any;
230
+ scenarioId: any;
231
+ runId: any;
232
+ stepIndex: any;
233
+ kind: any;
234
+ hookType: any;
235
+ keyword: any;
236
+ text: any;
237
+ argument: unknown;
238
+ status: any;
239
+ durationMs: any;
240
+ errorMessage: any;
241
+ errorStack: any;
242
+ definitionLocation: any;
243
+ layerHint: any;
244
+ apiSnapshot: unknown;
245
+ perf: unknown;
246
+ startedAt: string | null;
247
+ finishedAt: string | null;
248
+ };
249
+ export declare function mapArtifact(a: any): {
250
+ id: any;
251
+ runId: any;
252
+ scenarioId: any;
253
+ attemptId: any;
254
+ stepId: any;
255
+ stepIndex: any;
256
+ kind: any;
257
+ phase: any;
258
+ mediaType: any;
259
+ fileName: any;
260
+ relPath: any;
261
+ sizeBytes: any;
262
+ sha256: any;
263
+ width: any;
264
+ height: any;
265
+ meta: Record<string, unknown> | null;
266
+ };
267
+ export declare function mapHeal(h: any): {
268
+ id: any;
269
+ projectId: any;
270
+ runId: any;
271
+ scenarioId: any;
272
+ attemptId: any;
273
+ stepId: any;
274
+ pageUrl: any;
275
+ originalSelector: any;
276
+ context: Record<string, unknown>;
277
+ strategyUsed: any;
278
+ healedSelector: any;
279
+ candidates: unknown[];
280
+ succeeded: boolean;
281
+ durationMs: any;
282
+ source: any;
283
+ accepted: boolean | null;
284
+ createdAt: string | null;
285
+ };
286
+ export declare function getArtifact(db: Kysely<Database>, id: string): Promise<{
287
+ id: any;
288
+ runId: any;
289
+ scenarioId: any;
290
+ attemptId: any;
291
+ stepId: any;
292
+ stepIndex: any;
293
+ kind: any;
294
+ phase: any;
295
+ mediaType: any;
296
+ fileName: any;
297
+ relPath: any;
298
+ sizeBytes: any;
299
+ sha256: any;
300
+ width: any;
301
+ height: any;
302
+ meta: Record<string, unknown> | null;
303
+ } | null>;
304
+ export declare function newArtifactId(): string;
305
+ export declare function stripUndefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
306
+ //# sourceMappingURL=runs.d.ts.map
@@ -0,0 +1,321 @@
1
+ import { enc, nowIso, readBool, readJson, readTs } from '../col.js';
2
+ import { newId } from '../ids.js';
3
+ export async function upsertRun(db, _driver, r) {
4
+ const existing = await db
5
+ .selectFrom('runs')
6
+ .select(['id', 'totals_json'])
7
+ .where('id', '=', r.id)
8
+ .executeTakeFirst();
9
+ const base = {
10
+ project_id: r.projectId,
11
+ environment_id: r.environmentId ?? null,
12
+ env_name: r.envName,
13
+ updated_at: nowIso(),
14
+ };
15
+ const optional = stripUndefined({
16
+ workspace_id: r.workspaceId,
17
+ process: r.process,
18
+ trigger: r.trigger,
19
+ status: r.status,
20
+ suite_tag: r.suiteTag,
21
+ tags_expr: r.tagsExpr,
22
+ layers_json: r.layers ? enc.json(r.layers) : undefined,
23
+ browsers_json: r.browsers ? enc.json(r.browsers) : undefined,
24
+ shard_total: r.shardTotal,
25
+ git_sha: r.gitSha,
26
+ git_branch: r.gitBranch,
27
+ ci_provider: r.ciProvider,
28
+ ci_run_id: r.ciRunId,
29
+ ci_url: r.ciUrl,
30
+ started_by: r.startedBy,
31
+ command: r.command,
32
+ started_at: r.startedAt,
33
+ finished_at: r.finishedAt,
34
+ duration_ms: r.durationMs,
35
+ totals_json: r.totals ? enc.json(r.totals) : undefined,
36
+ artifacts_dir: r.artifactsDir,
37
+ html_report_rel: r.htmlReportRel,
38
+ exit_code: r.exitCode,
39
+ error_text: r.errorText,
40
+ });
41
+ if (existing) {
42
+ await db
43
+ .updateTable('runs')
44
+ .set({ ...base, ...optional })
45
+ .where('id', '=', r.id)
46
+ .execute();
47
+ return;
48
+ }
49
+ await db
50
+ .insertInto('runs')
51
+ .values({
52
+ id: r.id,
53
+ created_at: nowIso(),
54
+ trigger: r.trigger ?? 'cli',
55
+ status: r.status ?? 'queued',
56
+ layers_json: enc.json(r.layers ?? []),
57
+ browsers_json: enc.json(r.browsers ?? []),
58
+ shards_ingested: 0,
59
+ totals_json: enc.json(r.totals ?? {}),
60
+ ...base,
61
+ ...optional,
62
+ })
63
+ .execute();
64
+ }
65
+ export async function getRun(db, id) {
66
+ const row = await db
67
+ .selectFrom('runs')
68
+ .innerJoin('projects', 'projects.id', 'runs.project_id')
69
+ .selectAll('runs')
70
+ .select('projects.slug as project_slug')
71
+ .where('runs.id', '=', id)
72
+ .executeTakeFirst();
73
+ return row ? mapRun(row) : null;
74
+ }
75
+ export async function listRuns(db, opts = {}) {
76
+ let q = db
77
+ .selectFrom('runs')
78
+ .innerJoin('projects', 'projects.id', 'runs.project_id')
79
+ .selectAll('runs')
80
+ .select('projects.slug as project_slug')
81
+ .orderBy('runs.created_at', 'desc')
82
+ .limit(opts.limit ?? 50);
83
+ if (opts.projectSlug)
84
+ q = q.where('projects.slug', '=', opts.projectSlug);
85
+ if (opts.status)
86
+ q = q.where('runs.status', '=', opts.status);
87
+ if (opts.env)
88
+ q = q.where('runs.env_name', '=', opts.env);
89
+ if (opts.before)
90
+ q = q.where('runs.created_at', '<', opts.before);
91
+ const rows = await q.execute();
92
+ return rows.map(mapRun);
93
+ }
94
+ export function mapRun(row) {
95
+ const totalsRaw = readJson(row.totals_json) ?? {};
96
+ const totals = totalsRaw.total !== undefined ? totalsRaw : undefined;
97
+ return {
98
+ id: row.id,
99
+ projectId: row.project_id,
100
+ projectSlug: row.project_slug,
101
+ workspaceId: row.workspace_id ?? null,
102
+ process: row.process ?? undefined,
103
+ env: row.env_name,
104
+ trigger: row.trigger,
105
+ status: row.status,
106
+ suiteTag: row.suite_tag ?? undefined,
107
+ tagsExpr: row.tags_expr ?? undefined,
108
+ layers: readJson(row.layers_json) ?? [],
109
+ browsers: readJson(row.browsers_json) ?? [],
110
+ gitSha: row.git_sha ?? undefined,
111
+ gitBranch: row.git_branch ?? undefined,
112
+ ciProvider: row.ci_provider ?? undefined,
113
+ ciRunId: row.ci_run_id ?? undefined,
114
+ ciUrl: row.ci_url ?? undefined,
115
+ startedAt: readTs(row.started_at) ?? undefined,
116
+ finishedAt: readTs(row.finished_at) ?? undefined,
117
+ durationMs: row.duration_ms ?? undefined,
118
+ totals,
119
+ totalsRaw,
120
+ artifactsDir: row.artifacts_dir ?? undefined,
121
+ exitCode: row.exit_code ?? undefined,
122
+ errorText: row.error_text ?? undefined,
123
+ };
124
+ }
125
+ export async function deleteRunChildren(db, runId) {
126
+ // FK cascades handle steps/attempts/artifacts/heal_events; delete explicitly for engines without FK enforcement.
127
+ await db.deleteFrom('heal_events').where('run_id', '=', runId).execute();
128
+ await db.deleteFrom('artifacts').where('run_id', '=', runId).execute();
129
+ await db.deleteFrom('steps').where('run_id', '=', runId).execute();
130
+ await db.deleteFrom('scenario_attempts').where('run_id', '=', runId).execute();
131
+ await db.deleteFrom('scenarios').where('run_id', '=', runId).execute();
132
+ }
133
+ export async function deleteRun(db, runId) {
134
+ await deleteRunChildren(db, runId);
135
+ await db.deleteFrom('runs').where('id', '=', runId).execute();
136
+ }
137
+ /** Scenario tree for a run: scenarios with their attempts (no steps). */
138
+ export async function getRunScenarios(db, runId) {
139
+ const scenarios = await db
140
+ .selectFrom('scenarios')
141
+ .selectAll()
142
+ .where('run_id', '=', runId)
143
+ .orderBy('feature_uri')
144
+ .orderBy('scenario_name')
145
+ .execute();
146
+ const attempts = await db
147
+ .selectFrom('scenario_attempts')
148
+ .selectAll()
149
+ .where('run_id', '=', runId)
150
+ .orderBy('attempt')
151
+ .execute();
152
+ const byScenario = new Map();
153
+ for (const a of attempts) {
154
+ const list = byScenario.get(a.scenario_id) ?? [];
155
+ list.push(mapAttempt(a));
156
+ byScenario.set(a.scenario_id, list);
157
+ }
158
+ return scenarios.map((s) => ({ ...mapScenario(s), attempts: byScenario.get(s.id) ?? [] }));
159
+ }
160
+ /** One scenario with steps and artifacts grouped by attempt. */
161
+ export async function getScenarioDetail(db, scenarioId) {
162
+ const scenario = await db
163
+ .selectFrom('scenarios')
164
+ .selectAll()
165
+ .where('id', '=', scenarioId)
166
+ .executeTakeFirst();
167
+ if (!scenario)
168
+ return null;
169
+ const attempts = await db
170
+ .selectFrom('scenario_attempts')
171
+ .selectAll()
172
+ .where('scenario_id', '=', scenarioId)
173
+ .orderBy('attempt')
174
+ .execute();
175
+ const steps = await db
176
+ .selectFrom('steps')
177
+ .selectAll()
178
+ .where('scenario_id', '=', scenarioId)
179
+ .orderBy('step_index')
180
+ .execute();
181
+ const artifacts = await db
182
+ .selectFrom('artifacts')
183
+ .selectAll()
184
+ .where('scenario_id', '=', scenarioId)
185
+ .execute();
186
+ const heals = await db
187
+ .selectFrom('heal_events')
188
+ .selectAll()
189
+ .where('scenario_id', '=', scenarioId)
190
+ .execute();
191
+ return {
192
+ ...mapScenario(scenario),
193
+ attempts: attempts.map((a) => ({
194
+ ...mapAttempt(a),
195
+ steps: steps.filter((s) => s.attempt_id === a.id).map(mapStep),
196
+ artifacts: artifacts.filter((x) => x.attempt_id === a.id).map(mapArtifact),
197
+ heals: heals.filter((h) => h.attempt_id === a.id).map(mapHeal),
198
+ })),
199
+ };
200
+ }
201
+ export function mapScenario(s) {
202
+ return {
203
+ id: s.id,
204
+ runId: s.run_id,
205
+ projectId: s.project_id,
206
+ naturalKey: s.natural_key,
207
+ fingerprint: s.fingerprint,
208
+ source: s.source,
209
+ featureUri: s.feature_uri,
210
+ featureName: s.feature_name,
211
+ scenarioName: s.scenario_name,
212
+ module: s.module ?? null,
213
+ exampleIndex: s.examples_row,
214
+ runnerProject: s.runner_project,
215
+ layer: s.layer,
216
+ browser: s.browser,
217
+ suiteTag: s.suite_tag,
218
+ tags: readJson(s.tags_json) ?? [],
219
+ jiraKeys: readJson(s.jira_keys_json) ?? [],
220
+ status: s.status,
221
+ attemptsCount: s.attempts_count,
222
+ flaky: readBool(s.flaky),
223
+ durationMs: s.duration_ms,
224
+ errorMessage: s.error_message,
225
+ errorStack: s.error_stack,
226
+ startedAt: readTs(s.started_at),
227
+ finishedAt: readTs(s.finished_at),
228
+ };
229
+ }
230
+ export function mapAttempt(a) {
231
+ return {
232
+ id: a.id,
233
+ scenarioId: a.scenario_id,
234
+ attempt: a.attempt,
235
+ testCaseStartedId: a.test_case_started_id,
236
+ status: a.status,
237
+ durationMs: a.duration_ms,
238
+ errorMessage: a.error_message,
239
+ errorStack: a.error_stack,
240
+ willBeRetried: readBool(a.will_be_retried),
241
+ workerIndex: a.worker_index,
242
+ startedAt: readTs(a.started_at),
243
+ finishedAt: readTs(a.finished_at),
244
+ };
245
+ }
246
+ export function mapStep(s) {
247
+ return {
248
+ id: s.id,
249
+ attemptId: s.attempt_id,
250
+ scenarioId: s.scenario_id,
251
+ runId: s.run_id,
252
+ stepIndex: s.step_index,
253
+ kind: s.kind,
254
+ hookType: s.hook_type,
255
+ keyword: s.keyword,
256
+ text: s.text,
257
+ argument: readJson(s.argument_json),
258
+ status: s.status,
259
+ durationMs: s.duration_ms,
260
+ errorMessage: s.error_message,
261
+ errorStack: s.error_stack,
262
+ definitionLocation: s.definition_location,
263
+ layerHint: s.layer_hint,
264
+ apiSnapshot: readJson(s.api_snapshot_json),
265
+ perf: readJson(s.perf_json),
266
+ startedAt: readTs(s.started_at),
267
+ finishedAt: readTs(s.finished_at),
268
+ };
269
+ }
270
+ export function mapArtifact(a) {
271
+ return {
272
+ id: a.id,
273
+ runId: a.run_id,
274
+ scenarioId: a.scenario_id,
275
+ attemptId: a.attempt_id,
276
+ stepId: a.step_id,
277
+ stepIndex: a.step_index,
278
+ kind: a.kind,
279
+ phase: a.phase,
280
+ mediaType: a.media_type,
281
+ fileName: a.file_name,
282
+ relPath: a.rel_path,
283
+ sizeBytes: a.size_bytes,
284
+ sha256: a.sha256,
285
+ width: a.width,
286
+ height: a.height,
287
+ meta: readJson(a.meta_json),
288
+ };
289
+ }
290
+ export function mapHeal(h) {
291
+ return {
292
+ id: h.id,
293
+ projectId: h.project_id,
294
+ runId: h.run_id,
295
+ scenarioId: h.scenario_id,
296
+ attemptId: h.attempt_id,
297
+ stepId: h.step_id,
298
+ pageUrl: h.page_url,
299
+ originalSelector: h.original_selector,
300
+ context: readJson(h.context_json) ?? {},
301
+ strategyUsed: h.strategy_used,
302
+ healedSelector: h.healed_selector,
303
+ candidates: readJson(h.candidates_json) ?? [],
304
+ succeeded: readBool(h.succeeded),
305
+ durationMs: h.duration_ms,
306
+ source: h.source,
307
+ accepted: h.accepted == null ? null : readBool(h.accepted),
308
+ createdAt: readTs(h.created_at),
309
+ };
310
+ }
311
+ export async function getArtifact(db, id) {
312
+ const row = await db.selectFrom('artifacts').selectAll().where('id', '=', id).executeTakeFirst();
313
+ return row ? mapArtifact(row) : null;
314
+ }
315
+ export function newArtifactId() {
316
+ return newId();
317
+ }
318
+ export function stripUndefined(obj) {
319
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
320
+ }
321
+ //# sourceMappingURL=runs.js.map
@@ -0,0 +1,84 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Driver } from '../driver.js';
3
+ import type { Database } from '../schema.js';
4
+ type Row = Record<string, unknown>;
5
+ export declare function upsertDataset(db: Kysely<Database>, _driver: Driver, input: {
6
+ projectId: string;
7
+ envKey?: string;
8
+ name: string;
9
+ kind: string;
10
+ storage?: 'db' | 'file';
11
+ sourcePath?: string | null;
12
+ rows?: Row[];
13
+ createdBy?: string | null;
14
+ }): Promise<{
15
+ id: string;
16
+ rowCount: number;
17
+ }>;
18
+ export declare function listDatasets(db: Kysely<Database>, projectId: string): Promise<{
19
+ id: string;
20
+ envKey: string;
21
+ name: string;
22
+ kind: string;
23
+ storage: string;
24
+ sourcePath: string | null;
25
+ columns: string[];
26
+ rowCount: number;
27
+ contentHash: string | null;
28
+ updatedAt: string | null;
29
+ }[]>;
30
+ export declare function getDatasetRows(db: Kysely<Database>, datasetId: string, opts?: {
31
+ offset?: number;
32
+ limit?: number;
33
+ }): Promise<{
34
+ index: number;
35
+ data: Row;
36
+ }[]>;
37
+ /** Resolve a dataset for an env: exact env first, then `*`. */
38
+ export declare function loadDatasetRows(db: Kysely<Database>, projectId: string, name: string, env: string): Promise<Row[] | null>;
39
+ export declare function upsertPoolUser(db: Kysely<Database>, driver: Driver, input: {
40
+ projectId: string;
41
+ environmentId?: string | null;
42
+ envName: string;
43
+ poolName?: string;
44
+ username: string;
45
+ secretRef?: string | null;
46
+ role?: string;
47
+ storageStateRel?: string | null;
48
+ attributes?: Record<string, unknown>;
49
+ enabled?: boolean;
50
+ }): Promise<string>;
51
+ /**
52
+ * Lease one enabled user of `role` that has no active lease. Single conditional insert per candidate;
53
+ * on both dialects the unique-ish check is enforced by re-reading after insert.
54
+ */
55
+ export declare function leasePoolUser(db: Kysely<Database>, input: {
56
+ projectId: string;
57
+ envName: string;
58
+ poolName?: string;
59
+ role: string;
60
+ holder: string;
61
+ runId?: string;
62
+ workerIndex?: number;
63
+ ttlMs: number;
64
+ }): Promise<{
65
+ leaseId: string;
66
+ userId: string;
67
+ username: string;
68
+ secretRef: string | null;
69
+ role: string;
70
+ attributes: Record<string, unknown>;
71
+ } | null>;
72
+ export declare function releaseLease(db: Kysely<Database>, leaseId: string): Promise<void>;
73
+ export declare function releaseLeasesByHolder(db: Kysely<Database>, holder: string): Promise<number>;
74
+ export declare function poolStatus(db: Kysely<Database>, projectId: string, envName: string): Promise<{
75
+ id: string;
76
+ username: string;
77
+ role: string;
78
+ enabled: boolean;
79
+ leased: boolean;
80
+ holder: string | null;
81
+ expiresAt: string | null;
82
+ }[]>;
83
+ export {};
84
+ //# sourceMappingURL=test-data.d.ts.map