@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.
- package/LICENSE +17 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/col.d.ts +32 -0
- package/dist/col.js +58 -0
- package/dist/create-db.d.ts +14 -0
- package/dist/create-db.js +58 -0
- package/dist/driver.d.ts +10 -0
- package/dist/driver.js +16 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +2 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +22 -0
- package/dist/ingest/attachments.d.ts +40 -0
- package/dist/ingest/attachments.js +154 -0
- package/dist/ingest/cucumber-ingest.d.ts +40 -0
- package/dist/ingest/cucumber-ingest.js +543 -0
- package/dist/ingest/finalize.d.ts +28 -0
- package/dist/ingest/finalize.js +132 -0
- package/dist/ingest/index.d.ts +26 -0
- package/dist/ingest/index.js +193 -0
- package/dist/ingest/message-index.d.ts +64 -0
- package/dist/ingest/message-index.js +146 -0
- package/dist/ingest/parse-ndjson.d.ts +10 -0
- package/dist/ingest/parse-ndjson.js +35 -0
- package/dist/ingest/pw-json-ingest.d.ts +14 -0
- package/dist/ingest/pw-json-ingest.js +259 -0
- package/dist/ingest/runner-json-ingest.d.ts +14 -0
- package/dist/ingest/runner-json-ingest.js +259 -0
- package/dist/ingest/types.d.ts +62 -0
- package/dist/ingest/types.js +89 -0
- package/dist/insights.d.ts +62 -0
- package/dist/insights.js +184 -0
- package/dist/migrate.d.ts +30 -0
- package/dist/migrate.js +114 -0
- package/dist/migrations/0001_platform_init.d.ts +5 -0
- package/dist/migrations/0001_platform_init.js +48 -0
- package/dist/migrations/0002_results.d.ts +5 -0
- package/dist/migrations/0002_results.js +124 -0
- package/dist/migrations/0003_improvement.d.ts +5 -0
- package/dist/migrations/0003_improvement.js +56 -0
- package/dist/migrations/0004_test_data.d.ts +5 -0
- package/dist/migrations/0004_test_data.js +48 -0
- package/dist/migrations/0005_integrations_agents_audit.d.ts +5 -0
- package/dist/migrations/0005_integrations_agents_audit.js +100 -0
- package/dist/migrations/0006_schedules.d.ts +5 -0
- package/dist/migrations/0006_schedules.js +34 -0
- package/dist/migrations/0007_hierarchy.d.ts +5 -0
- package/dist/migrations/0007_hierarchy.js +76 -0
- package/dist/migrations/0008_runner_naming.d.ts +14 -0
- package/dist/migrations/0008_runner_naming.js +35 -0
- package/dist/migrations/helpers.d.ts +15 -0
- package/dist/migrations/helpers.js +30 -0
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.js +22 -0
- package/dist/repos/auth.d.ts +109 -0
- package/dist/repos/auth.js +258 -0
- package/dist/repos/hierarchy.d.ts +151 -0
- package/dist/repos/hierarchy.js +465 -0
- package/dist/repos/improvement.d.ts +84 -0
- package/dist/repos/improvement.js +242 -0
- package/dist/repos/index.d.ts +8 -0
- package/dist/repos/index.js +8 -0
- package/dist/repos/platform.d.ts +250 -0
- package/dist/repos/platform.js +429 -0
- package/dist/repos/projects.d.ts +87 -0
- package/dist/repos/projects.js +118 -0
- package/dist/repos/runs.d.ts +306 -0
- package/dist/repos/runs.js +321 -0
- package/dist/repos/test-data.d.ts +84 -0
- package/dist/repos/test-data.js +291 -0
- package/dist/schema.d.ts +514 -0
- package/dist/schema.js +37 -0
- package/dist/scopes.d.ts +2 -0
- package/dist/scopes.js +2 -0
- package/dist/switch/export.d.ts +15 -0
- package/dist/switch/export.js +67 -0
- package/dist/switch/import.d.ts +16 -0
- package/dist/switch/import.js +106 -0
- package/dist/switch/index.d.ts +5 -0
- package/dist/switch/index.js +5 -0
- package/dist/switch/prune.d.ts +19 -0
- package/dist/switch/prune.js +50 -0
- package/dist/switch/switch.d.ts +34 -0
- package/dist/switch/switch.js +133 -0
- package/dist/test-data.d.ts +22 -0
- package/dist/test-data.js +79 -0
- 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
|