@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,5 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ export declare function up(db: Kysely<any>, c: Col): Promise<void>;
4
+ export declare function down(db: Kysely<any>): Promise<void>;
5
+ //# sourceMappingURL=0003_improvement.d.ts.map
@@ -0,0 +1,56 @@
1
+ import { dropTables, fk, index, table } from './helpers.js';
2
+ export async function up(db, c) {
3
+ await fk(fk(fk(fk(fk(table(db, 'heal_events', c), 'project_id', 'projects', c), 'run_id', 'runs', c), 'scenario_id', 'scenarios', c, { nullable: true }), 'attempt_id', 'scenario_attempts', c, { nullable: true }), 'step_id', 'steps', c, { nullable: true, onDelete: 'set null' })
4
+ .addColumn('page_url', 'text')
5
+ .addColumn('original_selector', 'text', (cb) => cb.notNull())
6
+ .addColumn('context_json', c.json, (cb) => cb.notNull())
7
+ .addColumn('strategy_used', 'text')
8
+ .addColumn('healed_selector', 'text')
9
+ .addColumn('candidates_json', c.json, (cb) => cb.notNull())
10
+ .addColumn('succeeded', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? false : 0))
11
+ .addColumn('duration_ms', c.int)
12
+ .addColumn('source', 'text', (cb) => cb.notNull().defaultTo('runtime'))
13
+ .addColumn('accepted', c.bool)
14
+ .addColumn('accepted_by', 'text')
15
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
16
+ .execute();
17
+ await index(db, 'idx_heal_project_selector', 'heal_events', ['project_id', 'original_selector']);
18
+ await index(db, 'idx_heal_scenario', 'heal_events', ['scenario_id']);
19
+ await fk(table(db, 'locator_stats', c), 'project_id', 'projects', c)
20
+ .addColumn('selector', 'text', (cb) => cb.notNull())
21
+ .addColumn('page_hint', 'text')
22
+ .addColumn('fail_count', c.int, (cb) => cb.notNull().defaultTo(0))
23
+ .addColumn('heal_count', c.int, (cb) => cb.notNull().defaultTo(0))
24
+ .addColumn('use_count', c.int, (cb) => cb.notNull().defaultTo(0))
25
+ .addColumn('last_failed_at', c.ts)
26
+ .addColumn('last_healed_at', c.ts)
27
+ .addColumn('last_strategy', 'text')
28
+ .addColumn('suggested_selector', 'text')
29
+ .addColumn('updated_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
30
+ .execute();
31
+ await index(db, 'uq_locator_stats_project_selector', 'locator_stats', ['project_id', 'selector'], true);
32
+ await fk(table(db, 'flaky_stats', c), 'project_id', 'projects', c)
33
+ .addColumn('fingerprint', 'text', (cb) => cb.notNull())
34
+ .addColumn('pw_project', 'text', (cb) => cb.notNull())
35
+ .addColumn('feature_uri', 'text')
36
+ .addColumn('scenario_name', 'text')
37
+ .addColumn('window_size', c.int, (cb) => cb.notNull().defaultTo(20))
38
+ .addColumn('runs_count', c.int, (cb) => cb.notNull().defaultTo(0))
39
+ .addColumn('pass_count', c.int, (cb) => cb.notNull().defaultTo(0))
40
+ .addColumn('fail_count', c.int, (cb) => cb.notNull().defaultTo(0))
41
+ .addColumn('flaky_count', c.int, (cb) => cb.notNull().defaultTo(0))
42
+ .addColumn('flaky_rate', c.real, (cb) => cb.notNull().defaultTo(0))
43
+ .addColumn('fail_streak', c.int, (cb) => cb.notNull().defaultTo(0))
44
+ .addColumn('last_status', 'text')
45
+ .addColumn('last_run_id', 'text')
46
+ .addColumn('last_failed_at', c.ts)
47
+ .addColumn('quarantined', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? false : 0))
48
+ .addColumn('updated_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
49
+ .execute();
50
+ await index(db, 'uq_flaky_project_fp_pw', 'flaky_stats', ['project_id', 'fingerprint', 'pw_project'], true);
51
+ await index(db, 'idx_flaky_project_rate', 'flaky_stats', ['project_id', 'flaky_rate']);
52
+ }
53
+ export async function down(db) {
54
+ await dropTables(db, ['flaky_stats', 'locator_stats', 'heal_events']);
55
+ }
56
+ //# sourceMappingURL=0003_improvement.js.map
@@ -0,0 +1,5 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ export declare function up(db: Kysely<any>, c: Col): Promise<void>;
4
+ export declare function down(db: Kysely<any>): Promise<void>;
5
+ //# sourceMappingURL=0004_test_data.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { dropTables, fk, index, stamps, table } from './helpers.js';
2
+ export async function up(db, c) {
3
+ await stamps(fk(table(db, 'datasets', c), 'project_id', 'projects', c)
4
+ .addColumn('env_key', 'text', (cb) => cb.notNull().defaultTo('*'))
5
+ .addColumn('name', 'text', (cb) => cb.notNull())
6
+ .addColumn('kind', 'text', (cb) => cb.notNull())
7
+ .addColumn('storage', 'text', (cb) => cb.notNull().defaultTo('db'))
8
+ .addColumn('source_path', 'text')
9
+ .addColumn('columns_json', c.json, (cb) => cb.notNull())
10
+ .addColumn('row_count', c.int, (cb) => cb.notNull().defaultTo(0))
11
+ .addColumn('content_hash', 'text')
12
+ .addColumn('created_by', 'text'), c).execute();
13
+ await index(db, 'uq_datasets_project_env_name', 'datasets', ['project_id', 'env_key', 'name'], true);
14
+ await fk(table(db, 'dataset_rows', c), 'dataset_id', 'datasets', c)
15
+ .addColumn('row_index', c.int, (cb) => cb.notNull())
16
+ .addColumn('data_json', c.json, (cb) => cb.notNull())
17
+ .addColumn('tags_json', c.json, (cb) => cb.notNull())
18
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
19
+ .execute();
20
+ await index(db, 'uq_dataset_rows_dataset_index', 'dataset_rows', ['dataset_id', 'row_index'], true);
21
+ await stamps(fk(fk(table(db, 'user_pool', c), 'project_id', 'projects', c), 'environment_id', 'environments', c, {
22
+ nullable: true,
23
+ onDelete: 'set null',
24
+ })
25
+ .addColumn('env_name', 'text', (cb) => cb.notNull())
26
+ .addColumn('pool_name', 'text', (cb) => cb.notNull().defaultTo('default'))
27
+ .addColumn('username', 'text', (cb) => cb.notNull())
28
+ .addColumn('secret_ref', 'text')
29
+ .addColumn('role', 'text', (cb) => cb.notNull().defaultTo('standard'))
30
+ .addColumn('storage_state_rel', 'text')
31
+ .addColumn('attributes_json', c.json, (cb) => cb.notNull())
32
+ .addColumn('enabled', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? true : 1)), c).execute();
33
+ await index(db, 'uq_user_pool_identity', 'user_pool', ['project_id', 'env_name', 'pool_name', 'username'], true);
34
+ await fk(table(db, 'user_leases', c), 'pool_user_id', 'user_pool', c)
35
+ .addColumn('run_id', 'text')
36
+ .addColumn('worker_index', c.int)
37
+ .addColumn('holder', 'text', (cb) => cb.notNull())
38
+ .addColumn('leased_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
39
+ .addColumn('expires_at', c.ts, (cb) => cb.notNull())
40
+ .addColumn('released_at', c.ts)
41
+ .execute();
42
+ await index(db, 'idx_user_leases_user_released', 'user_leases', ['pool_user_id', 'released_at']);
43
+ await index(db, 'idx_user_leases_expires', 'user_leases', ['expires_at']);
44
+ }
45
+ export async function down(db) {
46
+ await dropTables(db, ['user_leases', 'user_pool', 'dataset_rows', 'datasets']);
47
+ }
48
+ //# sourceMappingURL=0004_test_data.js.map
@@ -0,0 +1,5 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ export declare function up(db: Kysely<any>, c: Col): Promise<void>;
4
+ export declare function down(db: Kysely<any>): Promise<void>;
5
+ //# sourceMappingURL=0005_integrations_agents_audit.d.ts.map
@@ -0,0 +1,100 @@
1
+ import { dropTables, fk, index, stamps, table } from './helpers.js';
2
+ export async function up(db, c) {
3
+ await fk(table(db, 'api_tokens', c), 'user_id', 'users', c)
4
+ .addColumn('name', 'text', (cb) => cb.notNull())
5
+ .addColumn('token_prefix', 'text', (cb) => cb.notNull())
6
+ .addColumn('hash', 'text', (cb) => cb.notNull().unique())
7
+ .addColumn('scopes_json', c.json, (cb) => cb.notNull())
8
+ .addColumn('expires_at', c.ts)
9
+ .addColumn('last_used_at', c.ts)
10
+ .addColumn('revoked_at', c.ts)
11
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
12
+ .execute();
13
+ await index(db, 'idx_api_tokens_user', 'api_tokens', ['user_id']);
14
+ await index(db, 'idx_api_tokens_expires', 'api_tokens', ['expires_at']);
15
+ await stamps(fk(table(db, 'integrations', c), 'project_id', 'projects', c)
16
+ .addColumn('provider', 'text', (cb) => cb.notNull())
17
+ .addColumn('config_json', c.json, (cb) => cb.notNull())
18
+ .addColumn('secret_env_json', c.json, (cb) => cb.notNull())
19
+ .addColumn('enabled', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? false : 0))
20
+ .addColumn('last_sync_at', c.ts), c).execute();
21
+ await index(db, 'uq_integrations_project_provider', 'integrations', ['project_id', 'provider'], true);
22
+ await stamps(fk(fk(table(db, 'issue_links', c), 'project_id', 'projects', c), 'integration_id', 'integrations', c, {
23
+ nullable: true,
24
+ onDelete: 'set null',
25
+ })
26
+ .addColumn('provider', 'text', (cb) => cb.notNull())
27
+ .addColumn('fingerprint', 'text', (cb) => cb.notNull())
28
+ .addColumn('scenario_name', 'text')
29
+ .addColumn('external_key', 'text', (cb) => cb.notNull())
30
+ .addColumn('external_url', 'text')
31
+ .addColumn('status', 'text', (cb) => cb.notNull().defaultTo('unknown'))
32
+ .addColumn('source', 'text', (cb) => cb.notNull().defaultTo('auto'))
33
+ .addColumn('last_run_id', 'text')
34
+ .addColumn('last_synced_at', c.ts), c).execute();
35
+ await index(db, 'uq_issue_links_identity', 'issue_links', ['project_id', 'provider', 'fingerprint', 'external_key'], true);
36
+ await index(db, 'idx_issue_links_fingerprint', 'issue_links', ['fingerprint']);
37
+ await fk(table(db, 'proposals', c), 'project_id', 'projects', c)
38
+ .addColumn('role', 'text', (cb) => cb.notNull())
39
+ .addColumn('status', 'text', (cb) => cb.notNull().defaultTo('pending'))
40
+ .addColumn('summary', 'text')
41
+ .addColumn('manifest_json', c.json, (cb) => cb.notNull())
42
+ .addColumn('cost_usd', c.real)
43
+ .addColumn('model', 'text')
44
+ .addColumn('created_by', 'text')
45
+ .addColumn('reviewed_by', 'text')
46
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
47
+ .addColumn('reviewed_at', c.ts)
48
+ .execute();
49
+ await index(db, 'idx_proposals_project_status', 'proposals', ['project_id', 'status']);
50
+ await fk(fk(table(db, 'agent_jobs', c), 'project_id', 'projects', c), 'proposal_id', 'proposals', c, {
51
+ nullable: true,
52
+ onDelete: 'set null',
53
+ })
54
+ .addColumn('kind', 'text', (cb) => cb.notNull())
55
+ .addColumn('goal', 'text')
56
+ .addColumn('status', 'text', (cb) => cb.notNull().defaultTo('queued'))
57
+ .addColumn('input_json', c.json, (cb) => cb.notNull())
58
+ .addColumn('output_json', c.json)
59
+ .addColumn('diff_text', 'text')
60
+ .addColumn('log_rel', 'text')
61
+ .addColumn('provider', 'text')
62
+ .addColumn('model', 'text')
63
+ .addColumn('tokens_in', c.int)
64
+ .addColumn('tokens_out', c.int)
65
+ .addColumn('cost_usd', c.real)
66
+ .addColumn('turns', c.int)
67
+ .addColumn('started_by', 'text')
68
+ .addColumn('reviewed_by', 'text')
69
+ .addColumn('started_at', c.ts)
70
+ .addColumn('finished_at', c.ts)
71
+ .addColumn('reviewed_at', c.ts)
72
+ .addColumn('error', 'text')
73
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
74
+ .execute();
75
+ await index(db, 'idx_agent_jobs_project_created', 'agent_jobs', ['project_id', 'created_at']);
76
+ await index(db, 'idx_agent_jobs_status', 'agent_jobs', ['status']);
77
+ await table(db, 'audit_log', c)
78
+ .addColumn('actor_user_id', 'text')
79
+ .addColumn('actor_type', 'text', (cb) => cb.notNull().defaultTo('system'))
80
+ .addColumn('action', 'text', (cb) => cb.notNull())
81
+ .addColumn('target_type', 'text')
82
+ .addColumn('target_id', 'text')
83
+ .addColumn('details_json', c.json)
84
+ .addColumn('ip', 'text')
85
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
86
+ .execute();
87
+ await index(db, 'idx_audit_created', 'audit_log', ['created_at']);
88
+ await index(db, 'idx_audit_target', 'audit_log', ['target_type', 'target_id']);
89
+ }
90
+ export async function down(db) {
91
+ await dropTables(db, [
92
+ 'audit_log',
93
+ 'agent_jobs',
94
+ 'proposals',
95
+ 'issue_links',
96
+ 'integrations',
97
+ 'api_tokens',
98
+ ]);
99
+ }
100
+ //# sourceMappingURL=0005_integrations_agents_audit.js.map
@@ -0,0 +1,5 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ export declare function up(db: Kysely<any>, c: Col): Promise<void>;
4
+ export declare function down(db: Kysely<any>): Promise<void>;
5
+ //# sourceMappingURL=0006_schedules.d.ts.map
@@ -0,0 +1,34 @@
1
+ import { dropTables, fk, index, stamps, table } from './helpers.js';
2
+ export async function up(db, c) {
3
+ await stamps(fk(fk(table(db, 'schedules', c), 'project_id', 'projects', c), 'environment_id', 'environments', c, {
4
+ nullable: true,
5
+ onDelete: 'set null',
6
+ })
7
+ .addColumn('name', 'text', (cb) => cb.notNull())
8
+ .addColumn('cron_expr', 'text', (cb) => cb.notNull())
9
+ .addColumn('timezone', 'text', (cb) => cb.notNull().defaultTo('UTC'))
10
+ .addColumn('run_input_json', c.json, (cb) => cb.notNull())
11
+ .addColumn('overlap_policy', 'text', (cb) => cb.notNull().defaultTo('skip'))
12
+ .addColumn('jitter_seconds', c.int, (cb) => cb.notNull().defaultTo(0))
13
+ .addColumn('catch_up', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? false : 0))
14
+ .addColumn('enabled', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? true : 1))
15
+ .addColumn('notify_json', c.json, (cb) => cb.notNull())
16
+ .addColumn('retention_runs', c.int)
17
+ .addColumn('next_run_at', c.ts)
18
+ .addColumn('last_run_id', 'text')
19
+ .addColumn('last_status', 'text')
20
+ .addColumn('created_by', 'text'), c).execute();
21
+ await index(db, 'uq_schedules_project_name', 'schedules', ['project_id', 'name'], true);
22
+ await index(db, 'idx_schedules_next_run', 'schedules', ['enabled', 'next_run_at']);
23
+ await fk(table(db, 'schedule_runs', c), 'schedule_id', 'schedules', c)
24
+ .addColumn('run_id', 'text')
25
+ .addColumn('fired_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
26
+ .addColumn('status', 'text', (cb) => cb.notNull().defaultTo('fired'))
27
+ .addColumn('note', 'text')
28
+ .execute();
29
+ await index(db, 'idx_schedule_runs_schedule', 'schedule_runs', ['schedule_id', 'fired_at']);
30
+ }
31
+ export async function down(db) {
32
+ await dropTables(db, ['schedule_runs', 'schedules']);
33
+ }
34
+ //# sourceMappingURL=0006_schedules.js.map
@@ -0,0 +1,5 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ export declare function up(db: Kysely<any>, c: Col): Promise<void>;
4
+ export declare function down(db: Kysely<any>, c: Col): Promise<void>;
5
+ //# sourceMappingURL=0007_hierarchy.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { dropTables, fk, index, stamps, table } from './helpers.js';
2
+ export async function up(db, c) {
3
+ await stamps(table(db, 'organizations', c)
4
+ .addColumn('slug', 'text', (cb) => cb.notNull().unique())
5
+ .addColumn('name', 'text', (cb) => cb.notNull())
6
+ .addColumn('description', 'text')
7
+ .addColumn('url', 'text'), c).execute();
8
+ await stamps(fk(table(db, 'workspaces', c), 'organization_id', 'organizations', c)
9
+ .addColumn('slug', 'text', (cb) => cb.notNull())
10
+ .addColumn('name', 'text', (cb) => cb.notNull())
11
+ .addColumn('description', 'text'), c).execute();
12
+ await index(db, 'uq_workspaces_org_slug', 'workspaces', ['organization_id', 'slug'], true);
13
+ await fk(fk(table(db, 'org_members', c), 'organization_id', 'organizations', c), 'user_id', 'users', c)
14
+ .addColumn('role', 'text', (cb) => cb.notNull().defaultTo('member'))
15
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
16
+ .execute();
17
+ await index(db, 'uq_org_members_org_user', 'org_members', ['organization_id', 'user_id'], true);
18
+ await fk(fk(table(db, 'workspace_members', c), 'workspace_id', 'workspaces', c), 'user_id', 'users', c)
19
+ .addColumn('role', 'text', (cb) => cb.notNull().defaultTo('viewer'))
20
+ .addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now))
21
+ .execute();
22
+ await index(db, 'uq_workspace_members_ws_user', 'workspace_members', ['workspace_id', 'user_id'], true);
23
+ // sqlite cannot DROP a column that carries a REFERENCES clause, so the FK is declared on Postgres only.
24
+ await db.schema
25
+ .alterTable('projects')
26
+ .addColumn('workspace_id', c.id, (cb) => c.pg ? cb.references('workspaces.id').onDelete('set null') : cb)
27
+ .execute();
28
+ await index(db, 'uq_projects_workspace_slug', 'projects', ['workspace_id', 'slug'], true);
29
+ await stamps(fk(table(db, 'modules', c), 'project_id', 'projects', c)
30
+ .addColumn('name', 'text', (cb) => cb.notNull())
31
+ .addColumn('title', 'text')
32
+ .addColumn('description', 'text')
33
+ .addColumn('path', 'text')
34
+ .addColumn('layers_json', c.json, (cb) => cb.notNull())
35
+ .addColumn('testing_types_json', c.json, (cb) => cb.notNull())
36
+ .addColumn('tags_json', c.json, (cb) => cb.notNull())
37
+ .addColumn('owner', 'text')
38
+ .addColumn('jira_component', 'text')
39
+ .addColumn('routes_json', c.json, (cb) => cb.notNull())
40
+ .addColumn('endpoints_json', c.json, (cb) => cb.notNull()), c).execute();
41
+ await index(db, 'uq_modules_project_name', 'modules', ['project_id', 'name'], true);
42
+ await stamps(fk(fk(table(db, 'processes', c), 'project_id', 'projects', c, { nullable: true }), 'workspace_id', 'workspaces', c, {
43
+ nullable: true,
44
+ })
45
+ .addColumn('name', 'text', (cb) => cb.notNull())
46
+ .addColumn('title', 'text')
47
+ .addColumn('description', 'text')
48
+ .addColumn('trigger', 'text', (cb) => cb.notNull().defaultTo('manual'))
49
+ .addColumn('config_json', c.json, (cb) => cb.notNull())
50
+ .addColumn('enabled', c.bool, (cb) => cb.notNull().defaultTo(c.pg ? true : 1))
51
+ .addColumn('last_run_id', 'text')
52
+ .addColumn('last_status', 'text'), c).execute();
53
+ await index(db, 'uq_processes_project_name', 'processes', ['project_id', 'name'], true);
54
+ await index(db, 'idx_processes_workspace', 'processes', ['workspace_id', 'name']);
55
+ await db.schema.alterTable('scenarios').addColumn('module', 'text').execute();
56
+ await index(db, 'idx_scenarios_module', 'scenarios', ['project_id', 'module']);
57
+ await db.schema.alterTable('runs').addColumn('process', 'text').execute();
58
+ await db.schema
59
+ .alterTable('runs')
60
+ .addColumn('workspace_id', c.id, (cb) => c.pg ? cb.references('workspaces.id').onDelete('set null') : cb)
61
+ .execute();
62
+ await index(db, 'idx_runs_process', 'runs', ['project_id', 'process']);
63
+ }
64
+ export async function down(db, c) {
65
+ await dropTables(db, ['processes', 'modules', 'workspace_members', 'org_members']);
66
+ await db.schema.dropIndex('uq_projects_workspace_slug').ifExists().execute();
67
+ await db.schema.dropIndex('idx_scenarios_module').ifExists().execute();
68
+ await db.schema.dropIndex('idx_runs_process').ifExists().execute();
69
+ await db.schema.alterTable('runs').dropColumn('workspace_id').execute();
70
+ await db.schema.alterTable('runs').dropColumn('process').execute();
71
+ await db.schema.alterTable('scenarios').dropColumn('module').execute();
72
+ await db.schema.alterTable('projects').dropColumn('workspace_id').execute();
73
+ await dropTables(db, ['workspaces', 'organizations']);
74
+ void c;
75
+ }
76
+ //# sourceMappingURL=0007_hierarchy.js.map
@@ -0,0 +1,14 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ /**
4
+ * Engine-neutral column names: `pw_project` → `runner_project`, `pw_report_rel` →
5
+ * `html_report_rel`, and the stored `scenarios.source` value `pw-json` → `runner-json`.
6
+ *
7
+ * Renames are in-place (`ALTER TABLE … RENAME COLUMN`, supported by SQLite ≥ 3.25 and Postgres),
8
+ * so no data is copied. The unique index on flaky_stats names one of the renamed columns and has
9
+ * to be dropped and recreated; SQLite carries the rename into the index definition but the index
10
+ * name itself still mentions the old column, so it is replaced for clarity on both drivers.
11
+ */
12
+ export declare function up(db: Kysely<any>, _c: Col): Promise<void>;
13
+ export declare function down(db: Kysely<any>, _c: Col): Promise<void>;
14
+ //# sourceMappingURL=0008_runner_naming.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { index } from './helpers.js';
2
+ /**
3
+ * Engine-neutral column names: `pw_project` → `runner_project`, `pw_report_rel` →
4
+ * `html_report_rel`, and the stored `scenarios.source` value `pw-json` → `runner-json`.
5
+ *
6
+ * Renames are in-place (`ALTER TABLE … RENAME COLUMN`, supported by SQLite ≥ 3.25 and Postgres),
7
+ * so no data is copied. The unique index on flaky_stats names one of the renamed columns and has
8
+ * to be dropped and recreated; SQLite carries the rename into the index definition but the index
9
+ * name itself still mentions the old column, so it is replaced for clarity on both drivers.
10
+ */
11
+ export async function up(db, _c) {
12
+ await db.schema.alterTable('runs').renameColumn('pw_report_rel', 'html_report_rel').execute();
13
+ await db.schema.alterTable('scenarios').renameColumn('pw_project', 'runner_project').execute();
14
+ await db.schema.dropIndex('uq_flaky_project_fp_pw').ifExists().execute();
15
+ await db.schema.alterTable('flaky_stats').renameColumn('pw_project', 'runner_project').execute();
16
+ await index(db, 'uq_flaky_project_fp_runner', 'flaky_stats', ['project_id', 'fingerprint', 'runner_project'], true);
17
+ await db
18
+ .updateTable('scenarios')
19
+ .set({ source: 'runner-json' })
20
+ .where('source', '=', 'pw-json')
21
+ .execute();
22
+ }
23
+ export async function down(db, _c) {
24
+ await db
25
+ .updateTable('scenarios')
26
+ .set({ source: 'pw-json' })
27
+ .where('source', '=', 'runner-json')
28
+ .execute();
29
+ await db.schema.dropIndex('uq_flaky_project_fp_runner').ifExists().execute();
30
+ await db.schema.alterTable('flaky_stats').renameColumn('runner_project', 'pw_project').execute();
31
+ await index(db, 'uq_flaky_project_fp_pw', 'flaky_stats', ['project_id', 'fingerprint', 'pw_project'], true);
32
+ await db.schema.alterTable('scenarios').renameColumn('runner_project', 'pw_project').execute();
33
+ await db.schema.alterTable('runs').renameColumn('html_report_rel', 'pw_report_rel').execute();
34
+ }
35
+ //# sourceMappingURL=0008_runner_naming.js.map
@@ -0,0 +1,15 @@
1
+ import type { CreateTableBuilder, Kysely } from 'kysely';
2
+ import type { Col } from '../col.js';
3
+ type Builder = CreateTableBuilder<string, string>;
4
+ /** Start a table with the `id text primary key` column. */
5
+ export declare function table(db: Kysely<any>, name: string, c: Col): Builder;
6
+ /** Append `created_at` (+ optional `updated_at`) with a `now()` default. */
7
+ export declare function stamps(b: Builder, c: Col, withUpdated?: boolean): Builder;
8
+ export declare function fk(b: Builder, column: string, refTable: string, c: Col, opts?: {
9
+ nullable?: boolean;
10
+ onDelete?: 'cascade' | 'set null' | 'restrict';
11
+ }): Builder;
12
+ export declare function index(db: Kysely<any>, name: string, on: string, columns: string[], unique?: boolean): Promise<void>;
13
+ export declare function dropTables(db: Kysely<any>, names: string[]): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1,30 @@
1
+ /** Start a table with the `id text primary key` column. */
2
+ export function table(db, name, c) {
3
+ return db.schema.createTable(name).addColumn('id', c.id, (cb) => cb.primaryKey());
4
+ }
5
+ /** Append `created_at` (+ optional `updated_at`) with a `now()` default. */
6
+ export function stamps(b, c, withUpdated = true) {
7
+ let out = b.addColumn('created_at', c.ts, (cb) => cb.notNull().defaultTo(c.now));
8
+ if (withUpdated)
9
+ out = out.addColumn('updated_at', c.ts, (cb) => cb.notNull().defaultTo(c.now));
10
+ return out;
11
+ }
12
+ export function fk(b, column, refTable, c, opts = {}) {
13
+ return b.addColumn(column, c.id, (cb) => {
14
+ let x = cb.references(`${refTable}.id`).onDelete(opts.onDelete ?? 'cascade');
15
+ if (!opts.nullable)
16
+ x = x.notNull();
17
+ return x;
18
+ });
19
+ }
20
+ export async function index(db, name, on, columns, unique = false) {
21
+ let b = db.schema.createIndex(name).on(on).columns(columns);
22
+ if (unique)
23
+ b = b.unique();
24
+ await b.execute();
25
+ }
26
+ export async function dropTables(db, names) {
27
+ for (const n of names)
28
+ await db.schema.dropTable(n).ifExists().execute();
29
+ }
30
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1,6 @@
1
+ import type { SdodsMigration } from '../migrate.js';
2
+ /**
3
+ * Static registry (bundler-friendly, no runtime file discovery). Order matters: Kysely sorts by key.
4
+ */
5
+ export declare const MIGRATIONS: Record<string, SdodsMigration>;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,22 @@
1
+ import * as m0001 from './0001_platform_init.js';
2
+ import * as m0002 from './0002_results.js';
3
+ import * as m0003 from './0003_improvement.js';
4
+ import * as m0004 from './0004_test_data.js';
5
+ import * as m0005 from './0005_integrations_agents_audit.js';
6
+ import * as m0006 from './0006_schedules.js';
7
+ import * as m0007 from './0007_hierarchy.js';
8
+ import * as m0008 from './0008_runner_naming.js';
9
+ /**
10
+ * Static registry (bundler-friendly, no runtime file discovery). Order matters: Kysely sorts by key.
11
+ */
12
+ export const MIGRATIONS = {
13
+ '0001_platform_init': m0001,
14
+ '0002_results': m0002,
15
+ '0003_improvement': m0003,
16
+ '0004_test_data': m0004,
17
+ '0005_integrations_agents_audit': m0005,
18
+ '0006_schedules': m0006,
19
+ '0007_hierarchy': m0007,
20
+ '0008_runner_naming': m0008,
21
+ };
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,109 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { type Role, type Scope } from '@sdods/contracts/scopes';
3
+ import type { Driver } from '../driver.js';
4
+ import type { Database } from '../schema.js';
5
+ /** Seed the three built-in roles (idempotent). */
6
+ export declare function ensureRoles(db: Kysely<Database>): Promise<Record<Role, string>>;
7
+ export declare function createUser(db: Kysely<Database>, driver: Driver, input: {
8
+ username: string;
9
+ passwordHash: string;
10
+ role: Role;
11
+ email?: string | null;
12
+ authProvider?: string;
13
+ externalId?: string | null;
14
+ }): Promise<string>;
15
+ export declare function getUserByUsername(db: Kysely<Database>, username: string): Promise<{
16
+ id: string;
17
+ username: string;
18
+ email: string | null;
19
+ passwordHash: string;
20
+ role: Role;
21
+ active: boolean;
22
+ authProvider: string;
23
+ lastLoginAt: string | null;
24
+ createdAt: string | null;
25
+ } | null>;
26
+ export declare function getUserById(db: Kysely<Database>, id: string): Promise<{
27
+ id: string;
28
+ username: string;
29
+ email: string | null;
30
+ passwordHash: string;
31
+ role: Role;
32
+ active: boolean;
33
+ authProvider: string;
34
+ lastLoginAt: string | null;
35
+ createdAt: string | null;
36
+ } | null>;
37
+ export declare function listUsers(db: Kysely<Database>): Promise<{
38
+ id: string;
39
+ username: string;
40
+ email: string | null;
41
+ passwordHash: string;
42
+ role: Role;
43
+ active: boolean;
44
+ authProvider: string;
45
+ lastLoginAt: string | null;
46
+ createdAt: string | null;
47
+ }[]>;
48
+ export declare function updateUser(db: Kysely<Database>, driver: Driver, id: string, patch: {
49
+ passwordHash?: string;
50
+ role?: Role;
51
+ active?: boolean;
52
+ email?: string | null;
53
+ lastLoginAt?: string;
54
+ }): Promise<void>;
55
+ export declare function countUsers(db: Kysely<Database>): Promise<number>;
56
+ export declare function hashToken(token: string): string;
57
+ export declare function createSession(db: Kysely<Database>, input: {
58
+ userId: string;
59
+ ttlMs: number;
60
+ ip?: string | null;
61
+ userAgent?: string | null;
62
+ }): Promise<{
63
+ token: string;
64
+ csrfToken: string;
65
+ }>;
66
+ export declare function getSession(db: Kysely<Database>, token: string): Promise<{
67
+ id: string;
68
+ userId: string;
69
+ csrfToken: string;
70
+ expiresAt: string;
71
+ lastSeenAt: string;
72
+ } | null>;
73
+ export declare function touchSession(db: Kysely<Database>, token: string, ttlMs: number): Promise<void>;
74
+ export declare function deleteSession(db: Kysely<Database>, token: string): Promise<void>;
75
+ export declare function purgeExpiredSessions(db: Kysely<Database>): Promise<void>;
76
+ export declare function createApiToken(db: Kysely<Database>, input: {
77
+ userId: string;
78
+ name: string;
79
+ scopes: string[];
80
+ expiresInDays?: number | null;
81
+ ownerRole: Role;
82
+ }): Promise<{
83
+ token: string;
84
+ id: string;
85
+ prefix: string;
86
+ scopes: Scope[];
87
+ }>;
88
+ /** Resolve a bearer token to its owner and effective scopes (intersection with the owner's current role). */
89
+ export declare function resolveApiToken(db: Kysely<Database>, token: string): Promise<{
90
+ tokenId: string;
91
+ userId: string;
92
+ username: string;
93
+ role: "viewer" | "editor" | "admin";
94
+ scopes: ("orgs:read" | "orgs:admin" | "workspaces:read" | "workspaces:write" | "projects:read" | "projects:write" | "envs:read" | "envs:write" | "datasets:read" | "datasets:write" | "runs:read" | "runs:write" | "runs:ingest" | "artifacts:read" | "features:read" | "features:write" | "agents:run" | "agents:review" | "integrations:read" | "integrations:write" | "schedules:read" | "schedules:write" | "processes:read" | "processes:write" | "users:admin" | "audit:read")[];
95
+ } | null>;
96
+ export declare function listApiTokens(db: Kysely<Database>, userId?: string): Promise<{
97
+ id: string;
98
+ userId: string;
99
+ username: string;
100
+ name: string;
101
+ prefix: string;
102
+ scopes: string[];
103
+ expiresAt: string | null;
104
+ lastUsedAt: string | null;
105
+ revokedAt: string | null;
106
+ createdAt: string | null;
107
+ }[]>;
108
+ export declare function revokeApiToken(db: Kysely<Database>, id: string): Promise<void>;
109
+ //# sourceMappingURL=auth.d.ts.map