@sdods/db 0.11.2 → 0.12.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.
- package/LICENSE +187 -2
- package/NOTICE +29 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/migrations/0010_run_params.d.ts +11 -0
- package/dist/migrations/0010_run_params.js +13 -0
- package/dist/migrations/0011_user_preferences.d.ts +12 -0
- package/dist/migrations/0011_user_preferences.js +18 -0
- package/dist/migrations/index.js +4 -0
- package/dist/repos/index.d.ts +1 -0
- package/dist/repos/index.js +1 -0
- package/dist/repos/preferences.d.ts +12 -0
- package/dist/repos/preferences.js +56 -0
- package/dist/repos/runs.d.ts +12 -0
- package/dist/repos/runs.js +48 -0
- package/dist/schema.d.ts +11 -0
- package/dist/schema.js +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { Col } from '../col.js';
|
|
3
|
+
/**
|
|
4
|
+
* The full selection a run was started with (feature, scenario, workers, headed, HAR mode…), so
|
|
5
|
+
* Rerun can replay it exactly. The existing columns only cover env/tags/layers/browsers, and
|
|
6
|
+
* `command` is an argv joined on spaces that cannot be split back once a scenario name has one.
|
|
7
|
+
* Nullable: runs started before this, and runs from the CLI, fall back to those columns.
|
|
8
|
+
*/
|
|
9
|
+
export declare function up(db: Kysely<any>, c: Col): Promise<void>;
|
|
10
|
+
export declare function down(db: Kysely<any>, _c: Col): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=0010_run_params.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full selection a run was started with (feature, scenario, workers, headed, HAR mode…), so
|
|
3
|
+
* Rerun can replay it exactly. The existing columns only cover env/tags/layers/browsers, and
|
|
4
|
+
* `command` is an argv joined on spaces that cannot be split back once a scenario name has one.
|
|
5
|
+
* Nullable: runs started before this, and runs from the CLI, fall back to those columns.
|
|
6
|
+
*/
|
|
7
|
+
export async function up(db, c) {
|
|
8
|
+
await db.schema.alterTable('runs').addColumn('params_json', c.json).execute();
|
|
9
|
+
}
|
|
10
|
+
export async function down(db, _c) {
|
|
11
|
+
await db.schema.alterTable('runs').dropColumn('params_json').execute();
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=0010_run_params.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { Col } from '../col.js';
|
|
3
|
+
/**
|
|
4
|
+
* Per-user UI preferences, such as the last run selection for a project (`runForm:<ws>:<project>`).
|
|
5
|
+
*
|
|
6
|
+
* Server-side because browser storage is per origin, and the desktop app's origin is
|
|
7
|
+
* `http://127.0.0.1:<port>`: when its preferred port is taken it binds another, and everything
|
|
8
|
+
* the browser had stored is gone. It also carries a preference from the desktop app to the web UI.
|
|
9
|
+
*/
|
|
10
|
+
export declare function up(db: Kysely<any>, c: Col): Promise<void>;
|
|
11
|
+
export declare function down(db: Kysely<any>, _c: Col): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=0011_user_preferences.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { dropTables, fk, index, stamps, table } from './helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-user UI preferences, such as the last run selection for a project (`runForm:<ws>:<project>`).
|
|
4
|
+
*
|
|
5
|
+
* Server-side because browser storage is per origin, and the desktop app's origin is
|
|
6
|
+
* `http://127.0.0.1:<port>`: when its preferred port is taken it binds another, and everything
|
|
7
|
+
* the browser had stored is gone. It also carries a preference from the desktop app to the web UI.
|
|
8
|
+
*/
|
|
9
|
+
export async function up(db, c) {
|
|
10
|
+
await stamps(fk(table(db, 'user_preferences', c), 'user_id', 'users', c)
|
|
11
|
+
.addColumn('key', 'text', (cb) => cb.notNull())
|
|
12
|
+
.addColumn('value_json', c.json, (cb) => cb.notNull()), c).execute();
|
|
13
|
+
await index(db, 'uq_user_preferences_user_key', 'user_preferences', ['user_id', 'key'], true);
|
|
14
|
+
}
|
|
15
|
+
export async function down(db, _c) {
|
|
16
|
+
await dropTables(db, ['user_preferences']);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=0011_user_preferences.js.map
|
package/dist/migrations/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import * as m0006 from './0006_schedules.js';
|
|
|
7
7
|
import * as m0007 from './0007_hierarchy.js';
|
|
8
8
|
import * as m0008 from './0008_runner_naming.js';
|
|
9
9
|
import * as m0009 from './0009_user_profile.js';
|
|
10
|
+
import * as m0010 from './0010_run_params.js';
|
|
11
|
+
import * as m0011 from './0011_user_preferences.js';
|
|
10
12
|
/**
|
|
11
13
|
* Static registry (bundler-friendly, no runtime file discovery). Order matters: Kysely sorts by key.
|
|
12
14
|
*/
|
|
@@ -20,5 +22,7 @@ export const MIGRATIONS = {
|
|
|
20
22
|
'0007_hierarchy': m0007,
|
|
21
23
|
'0008_runner_naming': m0008,
|
|
22
24
|
'0009_user_profile': m0009,
|
|
25
|
+
'0010_run_params': m0010,
|
|
26
|
+
'0011_user_preferences': m0011,
|
|
23
27
|
};
|
|
24
28
|
//# sourceMappingURL=index.js.map
|
package/dist/repos/index.d.ts
CHANGED
package/dist/repos/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { Database } from '../schema.js';
|
|
3
|
+
export interface UserPreference<T = unknown> {
|
|
4
|
+
key: string;
|
|
5
|
+
value: T;
|
|
6
|
+
updatedAt: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function getUserPreference<T = unknown>(db: Kysely<Database>, userId: string, key: string): Promise<UserPreference<T> | null>;
|
|
9
|
+
/** Insert or replace one preference; returns the stored row. */
|
|
10
|
+
export declare function setUserPreference<T = unknown>(db: Kysely<Database>, userId: string, key: string, value: T): Promise<UserPreference<T>>;
|
|
11
|
+
export declare function deleteUserPreference(db: Kysely<Database>, userId: string, key: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=preferences.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { enc, nowIso, readJson, readTs } from '../col.js';
|
|
2
|
+
import { newId } from '../ids.js';
|
|
3
|
+
export async function getUserPreference(db, userId, key) {
|
|
4
|
+
const row = await db
|
|
5
|
+
.selectFrom('user_preferences')
|
|
6
|
+
.select(['key', 'value_json', 'updated_at'])
|
|
7
|
+
.where('user_id', '=', userId)
|
|
8
|
+
.where('key', '=', key)
|
|
9
|
+
.executeTakeFirst();
|
|
10
|
+
if (!row)
|
|
11
|
+
return null;
|
|
12
|
+
return {
|
|
13
|
+
key: row.key,
|
|
14
|
+
value: readJson(row.value_json),
|
|
15
|
+
updatedAt: readTs(row.updated_at),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Insert or replace one preference; returns the stored row. */
|
|
19
|
+
export async function setUserPreference(db, userId, key, value) {
|
|
20
|
+
const now = nowIso();
|
|
21
|
+
const existing = await db
|
|
22
|
+
.selectFrom('user_preferences')
|
|
23
|
+
.select('id')
|
|
24
|
+
.where('user_id', '=', userId)
|
|
25
|
+
.where('key', '=', key)
|
|
26
|
+
.executeTakeFirst();
|
|
27
|
+
if (existing) {
|
|
28
|
+
await db
|
|
29
|
+
.updateTable('user_preferences')
|
|
30
|
+
.set({ value_json: enc.json(value), updated_at: now })
|
|
31
|
+
.where('id', '=', existing.id)
|
|
32
|
+
.execute();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
await db
|
|
36
|
+
.insertInto('user_preferences')
|
|
37
|
+
.values({
|
|
38
|
+
id: newId(),
|
|
39
|
+
user_id: userId,
|
|
40
|
+
key,
|
|
41
|
+
value_json: enc.json(value),
|
|
42
|
+
created_at: now,
|
|
43
|
+
updated_at: now,
|
|
44
|
+
})
|
|
45
|
+
.execute();
|
|
46
|
+
}
|
|
47
|
+
return { key, value, updatedAt: now };
|
|
48
|
+
}
|
|
49
|
+
export async function deleteUserPreference(db, userId, key) {
|
|
50
|
+
await db
|
|
51
|
+
.deleteFrom('user_preferences')
|
|
52
|
+
.where('user_id', '=', userId)
|
|
53
|
+
.where('key', '=', key)
|
|
54
|
+
.execute();
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=preferences.js.map
|
package/dist/repos/runs.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface RunUpsert {
|
|
|
31
31
|
htmlReportRel?: string | null;
|
|
32
32
|
exitCode?: number | null;
|
|
33
33
|
errorText?: string | null;
|
|
34
|
+
/** The selection a server-started run was launched with, for Rerun. */
|
|
35
|
+
params?: Record<string, unknown>;
|
|
34
36
|
}
|
|
35
37
|
export declare function upsertRun(db: Kysely<Database>, _driver: Driver, r: RunUpsert): Promise<void>;
|
|
36
38
|
export declare function getRun(db: Kysely<Database>, id: string): Promise<(RunRecord & {
|
|
@@ -51,6 +53,16 @@ export declare function mapRun(row: any): RunRecord & {
|
|
|
51
53
|
projectId: string;
|
|
52
54
|
totalsRaw: Record<string, unknown>;
|
|
53
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* What a run was started with, for Rerun: the stored selection when the server started it, else
|
|
58
|
+
* the parts the row always records. `null` when the run does not exist.
|
|
59
|
+
*/
|
|
60
|
+
export declare function getRunParams(db: Kysely<Database>, id: string): Promise<{
|
|
61
|
+
project: string;
|
|
62
|
+
params: Record<string, unknown>;
|
|
63
|
+
} | null>;
|
|
64
|
+
/** Names of the scenarios that failed in a run, deduplicated (a scenario can run per browser). */
|
|
65
|
+
export declare function getFailedScenarioNames(db: Kysely<Database>, runId: string): Promise<string[]>;
|
|
54
66
|
export declare function deleteRunChildren(db: Kysely<Database>, runId: string): Promise<void>;
|
|
55
67
|
export declare function deleteRun(db: Kysely<Database>, runId: string): Promise<void>;
|
|
56
68
|
/** Scenario tree for a run: scenarios with their attempts (no steps). */
|
package/dist/repos/runs.js
CHANGED
|
@@ -38,6 +38,7 @@ export async function upsertRun(db, _driver, r) {
|
|
|
38
38
|
html_report_rel: r.htmlReportRel,
|
|
39
39
|
exit_code: r.exitCode,
|
|
40
40
|
error_text: r.errorText,
|
|
41
|
+
params_json: r.params ? enc.json(r.params) : undefined,
|
|
41
42
|
});
|
|
42
43
|
if (existing) {
|
|
43
44
|
await db
|
|
@@ -124,6 +125,53 @@ export function mapRun(row) {
|
|
|
124
125
|
errorText: row.error_text ?? undefined,
|
|
125
126
|
};
|
|
126
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* What a run was started with, for Rerun: the stored selection when the server started it, else
|
|
130
|
+
* the parts the row always records. `null` when the run does not exist.
|
|
131
|
+
*/
|
|
132
|
+
export async function getRunParams(db, id) {
|
|
133
|
+
const row = await db
|
|
134
|
+
.selectFrom('runs')
|
|
135
|
+
.innerJoin('projects', 'projects.id', 'runs.project_id')
|
|
136
|
+
.select([
|
|
137
|
+
'projects.slug as project_slug',
|
|
138
|
+
'runs.params_json',
|
|
139
|
+
'runs.env_name',
|
|
140
|
+
'runs.process',
|
|
141
|
+
'runs.tags_expr',
|
|
142
|
+
'runs.layers_json',
|
|
143
|
+
'runs.browsers_json',
|
|
144
|
+
])
|
|
145
|
+
.where('runs.id', '=', id)
|
|
146
|
+
.executeTakeFirst();
|
|
147
|
+
if (!row)
|
|
148
|
+
return null;
|
|
149
|
+
const stored = readJson(row.params_json);
|
|
150
|
+
if (stored)
|
|
151
|
+
return { project: row.project_slug, params: stored };
|
|
152
|
+
const layers = readJson(row.layers_json) ?? [];
|
|
153
|
+
const browsers = readJson(row.browsers_json) ?? [];
|
|
154
|
+
return {
|
|
155
|
+
project: row.project_slug,
|
|
156
|
+
params: stripUndefined({
|
|
157
|
+
env: row.env_name || undefined,
|
|
158
|
+
process: row.process ?? undefined,
|
|
159
|
+
tags: row.tags_expr ?? undefined,
|
|
160
|
+
layers: layers.length ? layers : undefined,
|
|
161
|
+
browsers: browsers.length ? browsers : undefined,
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Names of the scenarios that failed in a run, deduplicated (a scenario can run per browser). */
|
|
166
|
+
export async function getFailedScenarioNames(db, runId) {
|
|
167
|
+
const rows = await db
|
|
168
|
+
.selectFrom('scenarios')
|
|
169
|
+
.select('scenario_name')
|
|
170
|
+
.where('run_id', '=', runId)
|
|
171
|
+
.where('status', 'in', ['failed', 'timedOut', 'interrupted'])
|
|
172
|
+
.execute();
|
|
173
|
+
return [...new Set(rows.map((r) => r.scenario_name))];
|
|
174
|
+
}
|
|
127
175
|
export async function deleteRunChildren(db, runId) {
|
|
128
176
|
// FK cascades handle steps/attempts/artifacts/heal_events; delete explicitly for engines without FK enforcement.
|
|
129
177
|
await db.deleteFrom('heal_events').where('run_id', '=', runId).execute();
|
package/dist/schema.d.ts
CHANGED
|
@@ -133,6 +133,8 @@ export interface RunsTable {
|
|
|
133
133
|
html_report_rel: string | null;
|
|
134
134
|
exit_code: number | null;
|
|
135
135
|
error_text: string | null;
|
|
136
|
+
/** The StartRunInput a server-started run was launched with; null for CLI runs and older rows. */
|
|
137
|
+
params_json: Json<Record<string, unknown>> | null;
|
|
136
138
|
created_at: Ts;
|
|
137
139
|
updated_at: Ts;
|
|
138
140
|
}
|
|
@@ -475,6 +477,14 @@ export interface AuditLogTable {
|
|
|
475
477
|
ip: string | null;
|
|
476
478
|
created_at: Ts;
|
|
477
479
|
}
|
|
480
|
+
export interface UserPreferencesTable {
|
|
481
|
+
id: string;
|
|
482
|
+
user_id: string;
|
|
483
|
+
key: string;
|
|
484
|
+
value_json: Json<unknown>;
|
|
485
|
+
created_at: Ts;
|
|
486
|
+
updated_at: Ts;
|
|
487
|
+
}
|
|
478
488
|
export interface Database {
|
|
479
489
|
organizations: OrganizationsTable;
|
|
480
490
|
workspaces: WorkspacesTable;
|
|
@@ -507,6 +517,7 @@ export interface Database {
|
|
|
507
517
|
schedules: SchedulesTable;
|
|
508
518
|
schedule_runs: ScheduleRunsTable;
|
|
509
519
|
audit_log: AuditLogTable;
|
|
520
|
+
user_preferences: UserPreferencesTable;
|
|
510
521
|
}
|
|
511
522
|
/** Platform tables in foreign-key order (parents first). Used by export/import/switch/prune. */
|
|
512
523
|
export declare const TABLES_IN_FK_ORDER: Array<keyof Database>;
|
package/dist/schema.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdods/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "SDODS platform database: one schema on SQLite or Postgres, migrations, result ingest and insights.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SDODS <admin@sdods.com>",
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"NOTICE"
|
|
21
22
|
],
|
|
22
23
|
"engines": {
|
|
23
24
|
"node": ">=22"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"@sdods/contracts": "0.
|
|
27
|
+
"@sdods/contracts": "0.12.0",
|
|
27
28
|
"better-sqlite3": "^13.0.3",
|
|
28
29
|
"kysely": "^0.29.5",
|
|
29
30
|
"pg": "^8.23.0",
|