@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,291 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { enc, nowIso, readBool, readJson, readTs } from '../col.js';
3
+ import { newId } from '../ids.js';
4
+ export async function upsertDataset(db, _driver, input) {
5
+ const envKey = input.envKey ?? '*';
6
+ const rows = input.rows ?? [];
7
+ const columns = [...new Set(rows.flatMap((r) => Object.keys(r)))];
8
+ const contentHash = createHash('sha256').update(JSON.stringify(rows)).digest('hex').slice(0, 16);
9
+ const existing = await db
10
+ .selectFrom('datasets')
11
+ .select(['id'])
12
+ .where('project_id', '=', input.projectId)
13
+ .where('env_key', '=', envKey)
14
+ .where('name', '=', input.name)
15
+ .executeTakeFirst();
16
+ const values = {
17
+ kind: input.kind,
18
+ storage: input.storage ?? 'db',
19
+ source_path: input.sourcePath ?? null,
20
+ columns_json: enc.json(columns),
21
+ row_count: rows.length,
22
+ content_hash: contentHash,
23
+ updated_at: nowIso(),
24
+ };
25
+ let id;
26
+ if (existing) {
27
+ id = existing.id;
28
+ await db.updateTable('datasets').set(values).where('id', '=', id).execute();
29
+ await db.deleteFrom('dataset_rows').where('dataset_id', '=', id).execute();
30
+ }
31
+ else {
32
+ id = newId();
33
+ await db
34
+ .insertInto('datasets')
35
+ .values({
36
+ id,
37
+ project_id: input.projectId,
38
+ env_key: envKey,
39
+ name: input.name,
40
+ created_by: input.createdBy ?? null,
41
+ created_at: nowIso(),
42
+ ...values,
43
+ })
44
+ .execute();
45
+ }
46
+ if (rows.length) {
47
+ const batch = rows.map((r, i) => ({
48
+ id: newId(),
49
+ dataset_id: id,
50
+ row_index: i,
51
+ data_json: enc.json(r),
52
+ tags_json: enc.json([]),
53
+ created_at: nowIso(),
54
+ }));
55
+ for (let i = 0; i < batch.length; i += 500) {
56
+ await db
57
+ .insertInto('dataset_rows')
58
+ .values(batch.slice(i, i + 500))
59
+ .execute();
60
+ }
61
+ }
62
+ return { id, rowCount: rows.length };
63
+ }
64
+ export async function listDatasets(db, projectId) {
65
+ const rows = await db
66
+ .selectFrom('datasets')
67
+ .selectAll()
68
+ .where('project_id', '=', projectId)
69
+ .orderBy('name')
70
+ .execute();
71
+ return rows.map((d) => ({
72
+ id: d.id,
73
+ envKey: d.env_key,
74
+ name: d.name,
75
+ kind: d.kind,
76
+ storage: d.storage,
77
+ sourcePath: d.source_path,
78
+ columns: readJson(d.columns_json) ?? [],
79
+ rowCount: d.row_count,
80
+ contentHash: d.content_hash,
81
+ updatedAt: readTs(d.updated_at),
82
+ }));
83
+ }
84
+ export async function getDatasetRows(db, datasetId, opts = {}) {
85
+ const rows = await db
86
+ .selectFrom('dataset_rows')
87
+ .selectAll()
88
+ .where('dataset_id', '=', datasetId)
89
+ .orderBy('row_index')
90
+ .offset(opts.offset ?? 0)
91
+ .limit(opts.limit ?? 100)
92
+ .execute();
93
+ return rows.map((r) => ({ index: r.row_index, data: readJson(r.data_json) ?? {} }));
94
+ }
95
+ /** Resolve a dataset for an env: exact env first, then `*`. */
96
+ export async function loadDatasetRows(db, projectId, name, env) {
97
+ for (const envKey of [env, '*']) {
98
+ const ds = await db
99
+ .selectFrom('datasets')
100
+ .select(['id'])
101
+ .where('project_id', '=', projectId)
102
+ .where('env_key', '=', envKey)
103
+ .where('name', '=', name)
104
+ .executeTakeFirst();
105
+ if (ds) {
106
+ const rows = await db
107
+ .selectFrom('dataset_rows')
108
+ .select(['data_json'])
109
+ .where('dataset_id', '=', ds.id)
110
+ .orderBy('row_index')
111
+ .execute();
112
+ return rows.map((r) => readJson(r.data_json) ?? {});
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+ export async function upsertPoolUser(db, driver, input) {
118
+ const poolName = input.poolName ?? 'default';
119
+ const existing = await db
120
+ .selectFrom('user_pool')
121
+ .select(['id'])
122
+ .where('project_id', '=', input.projectId)
123
+ .where('env_name', '=', input.envName)
124
+ .where('pool_name', '=', poolName)
125
+ .where('username', '=', input.username)
126
+ .executeTakeFirst();
127
+ const values = {
128
+ environment_id: input.environmentId ?? null,
129
+ secret_ref: input.secretRef ?? null,
130
+ role: input.role ?? 'standard',
131
+ storage_state_rel: input.storageStateRel ?? null,
132
+ attributes_json: enc.json(input.attributes ?? {}),
133
+ enabled: enc.bool(driver, input.enabled ?? true),
134
+ updated_at: nowIso(),
135
+ };
136
+ if (existing) {
137
+ await db.updateTable('user_pool').set(values).where('id', '=', existing.id).execute();
138
+ return existing.id;
139
+ }
140
+ const id = newId();
141
+ await db
142
+ .insertInto('user_pool')
143
+ .values({
144
+ id,
145
+ project_id: input.projectId,
146
+ env_name: input.envName,
147
+ pool_name: poolName,
148
+ username: input.username,
149
+ created_at: nowIso(),
150
+ ...values,
151
+ })
152
+ .execute();
153
+ return id;
154
+ }
155
+ /**
156
+ * Lease one enabled user of `role` that has no active lease. Single conditional insert per candidate;
157
+ * on both dialects the unique-ish check is enforced by re-reading after insert.
158
+ */
159
+ export async function leasePoolUser(db, input) {
160
+ const now = nowIso();
161
+ const poolName = input.poolName ?? 'default';
162
+ // expire stale leases
163
+ await db
164
+ .updateTable('user_leases')
165
+ .set({ released_at: now })
166
+ .where('released_at', 'is', null)
167
+ .where('expires_at', '<', now)
168
+ .execute();
169
+ const candidates = await db
170
+ .selectFrom('user_pool')
171
+ .selectAll()
172
+ .where('project_id', '=', input.projectId)
173
+ .where('env_name', '=', input.envName)
174
+ .where('pool_name', '=', poolName)
175
+ .where('role', '=', input.role)
176
+ .orderBy('username')
177
+ .execute();
178
+ for (const u of candidates) {
179
+ if (!readBool(u.enabled))
180
+ continue;
181
+ const active = await db
182
+ .selectFrom('user_leases')
183
+ .select(['id', 'holder'])
184
+ .where('pool_user_id', '=', u.id)
185
+ .where('released_at', 'is', null)
186
+ .executeTakeFirst();
187
+ if (active && active.holder !== input.holder)
188
+ continue;
189
+ if (active) {
190
+ return {
191
+ leaseId: active.id,
192
+ userId: u.id,
193
+ username: u.username,
194
+ secretRef: u.secret_ref,
195
+ role: u.role,
196
+ attributes: readJson(u.attributes_json) ?? {},
197
+ };
198
+ }
199
+ const leaseId = newId();
200
+ await db
201
+ .transaction()
202
+ .execute(async (trx) => {
203
+ const again = await trx
204
+ .selectFrom('user_leases')
205
+ .select(['id'])
206
+ .where('pool_user_id', '=', u.id)
207
+ .where('released_at', 'is', null)
208
+ .executeTakeFirst();
209
+ if (again)
210
+ throw new Error('taken');
211
+ await trx
212
+ .insertInto('user_leases')
213
+ .values({
214
+ id: leaseId,
215
+ pool_user_id: u.id,
216
+ run_id: input.runId ?? null,
217
+ worker_index: input.workerIndex ?? null,
218
+ holder: input.holder,
219
+ leased_at: now,
220
+ expires_at: new Date(Date.now() + input.ttlMs).toISOString(),
221
+ released_at: null,
222
+ })
223
+ .execute();
224
+ })
225
+ .then(() => undefined, (e) => {
226
+ if (e.message !== 'taken')
227
+ throw e;
228
+ });
229
+ const mine = await db
230
+ .selectFrom('user_leases')
231
+ .select(['id'])
232
+ .where('id', '=', leaseId)
233
+ .executeTakeFirst();
234
+ if (mine) {
235
+ return {
236
+ leaseId,
237
+ userId: u.id,
238
+ username: u.username,
239
+ secretRef: u.secret_ref,
240
+ role: u.role,
241
+ attributes: readJson(u.attributes_json) ?? {},
242
+ };
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+ export async function releaseLease(db, leaseId) {
248
+ await db
249
+ .updateTable('user_leases')
250
+ .set({ released_at: nowIso() })
251
+ .where('id', '=', leaseId)
252
+ .where('released_at', 'is', null)
253
+ .execute();
254
+ }
255
+ export async function releaseLeasesByHolder(db, holder) {
256
+ const res = await db
257
+ .updateTable('user_leases')
258
+ .set({ released_at: nowIso() })
259
+ .where('holder', '=', holder)
260
+ .where('released_at', 'is', null)
261
+ .executeTakeFirst();
262
+ return Number(res.numUpdatedRows ?? 0);
263
+ }
264
+ export async function poolStatus(db, projectId, envName) {
265
+ const users = await db
266
+ .selectFrom('user_pool')
267
+ .selectAll()
268
+ .where('project_id', '=', projectId)
269
+ .where('env_name', '=', envName)
270
+ .orderBy('role')
271
+ .orderBy('username')
272
+ .execute();
273
+ const leases = await db
274
+ .selectFrom('user_leases')
275
+ .selectAll()
276
+ .where('released_at', 'is', null)
277
+ .execute();
278
+ return users.map((u) => {
279
+ const lease = leases.find((l) => l.pool_user_id === u.id);
280
+ return {
281
+ id: u.id,
282
+ username: u.username,
283
+ role: u.role,
284
+ enabled: readBool(u.enabled),
285
+ leased: Boolean(lease),
286
+ holder: lease?.holder ?? null,
287
+ expiresAt: lease ? readTs(lease.expires_at) : null,
288
+ };
289
+ });
290
+ }
291
+ //# sourceMappingURL=test-data.js.map