@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,106 @@
1
+ import { createReadStream, existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createInterface } from 'node:readline';
4
+ import { sql } from 'kysely';
5
+ import { TABLES_IN_FK_ORDER } from '../schema.js';
6
+ import { isTdTable, listTdColumns } from '../test-data.js';
7
+ const BOOL_COLUMNS = new Set([
8
+ 'archived',
9
+ 'is_default',
10
+ 'flaky',
11
+ 'will_be_retried',
12
+ 'succeeded',
13
+ 'accepted',
14
+ 'quarantined',
15
+ 'enabled',
16
+ 'active',
17
+ 'catch_up',
18
+ ]);
19
+ /**
20
+ * Load `<dir>/<table>.jsonl` files in FK order inside one transaction, `ON CONFLICT DO NOTHING`.
21
+ * td_* tables are created on the fly (text columns) when missing.
22
+ */
23
+ export async function importAll(adb, dir, opts = {}) {
24
+ const batch = opts.batch ?? 500;
25
+ const files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));
26
+ const known = new Set(TABLES_IN_FK_ORDER);
27
+ const ordered = [
28
+ ...TABLES_IN_FK_ORDER.filter((t) => files.includes(`${t}.jsonl`)),
29
+ ...files
30
+ .map((f) => f.replace(/\.jsonl$/, ''))
31
+ .filter((t) => !known.has(t) && isTdTable(t))
32
+ .sort(),
33
+ ];
34
+ const result = { tables: [] };
35
+ await adb.db.transaction().execute(async (trx) => {
36
+ for (const table of ordered) {
37
+ const file = join(dir, `${table}.jsonl`);
38
+ if (!existsSync(file))
39
+ continue;
40
+ const rows = [];
41
+ let inserted = 0;
42
+ let skipped = 0;
43
+ const flush = async () => {
44
+ if (rows.length === 0)
45
+ return;
46
+ const values = rows.map((r) => denormalizeRow(adb, r));
47
+ if (isTdTable(table))
48
+ await ensureTdTable(trx, adb, table, Object.keys(values[0]));
49
+ const q = trx.insertInto(table).values(values);
50
+ const res = isTdTable(table)
51
+ ? await q.execute()
52
+ : await q.onConflict((oc) => oc.doNothing()).execute();
53
+ const n = Number(res?.[0]?.numInsertedOrUpdatedRows ?? rows.length);
54
+ inserted += n;
55
+ skipped += rows.length - n;
56
+ rows.length = 0;
57
+ };
58
+ const rl = createInterface({ input: createReadStream(file), crlfDelay: Infinity });
59
+ for await (const line of rl) {
60
+ const t = line.trim();
61
+ if (!t)
62
+ continue;
63
+ rows.push(JSON.parse(t));
64
+ if (rows.length >= batch)
65
+ await flush();
66
+ }
67
+ await flush();
68
+ result.tables.push({ table, rows: inserted, skipped });
69
+ }
70
+ });
71
+ return result;
72
+ }
73
+ function denormalizeRow(adb, row) {
74
+ const out = {};
75
+ for (const [k, v] of Object.entries(row)) {
76
+ if (v && typeof v === 'object' && '$base64' in v)
77
+ out[k] = Buffer.from(v.$base64, 'base64');
78
+ else if (k.endsWith('_json'))
79
+ out[k] = v == null ? null : JSON.stringify(v);
80
+ else if (BOOL_COLUMNS.has(k) && v != null)
81
+ out[k] = adb.driver === 'postgres' ? Boolean(v) : v ? 1 : 0;
82
+ else
83
+ out[k] = v;
84
+ }
85
+ return out;
86
+ }
87
+ async function ensureTdTable(trx, adb, table, columns) {
88
+ const existing = await listTdColumns(trx, adb.driver, table);
89
+ if (existing.length === 0) {
90
+ let b = trx.schema
91
+ .createTable(table)
92
+ .addColumn('row_index', 'integer', (cb) => cb.notNull())
93
+ .addColumn('env', 'text', (cb) => cb.notNull().defaultTo('*'));
94
+ for (const c of columns)
95
+ if (c !== 'row_index' && c !== 'env')
96
+ b = b.addColumn(c, 'text');
97
+ await b.execute();
98
+ }
99
+ else {
100
+ for (const c of columns)
101
+ if (!existing.includes(c))
102
+ await trx.schema.alterTable(table).addColumn(c, 'text').execute();
103
+ }
104
+ await sql `select 1`.execute(trx);
105
+ }
106
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1,5 @@
1
+ export * from './export.js';
2
+ export * from './import.js';
3
+ export * from './switch.js';
4
+ export * from './prune.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ export * from './export.js';
2
+ export * from './import.js';
3
+ export * from './switch.js';
4
+ export * from './prune.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ import type { SdodsDb } from '../create-db.js';
2
+ export interface PruneOptions {
3
+ keepRuns?: number;
4
+ keepDays?: number;
5
+ projectSlug?: string;
6
+ artifactsRoot?: string;
7
+ dryRun?: boolean;
8
+ }
9
+ export interface PruneResult {
10
+ deleted: Array<{
11
+ id: string;
12
+ projectSlug: string;
13
+ createdAt: string;
14
+ }>;
15
+ kept: number;
16
+ }
17
+ /** Delete old runs (and their artifact directories) beyond `keepRuns` per project or older than `keepDays`. */
18
+ export declare function prune(adb: SdodsDb, opts: PruneOptions): Promise<PruneResult>;
19
+ //# sourceMappingURL=prune.d.ts.map
@@ -0,0 +1,50 @@
1
+ import { existsSync, rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { sql } from 'kysely';
4
+ import { deleteRun } from '../repos/runs.js';
5
+ /** Delete old runs (and their artifact directories) beyond `keepRuns` per project or older than `keepDays`. */
6
+ export async function prune(adb, opts) {
7
+ const { db } = adb;
8
+ let q = db
9
+ .selectFrom('runs')
10
+ .innerJoin('projects', 'projects.id', 'runs.project_id')
11
+ .select([
12
+ 'runs.id as id',
13
+ 'projects.slug as slug',
14
+ 'runs.created_at as created_at',
15
+ 'runs.started_at as started_at',
16
+ 'runs.artifacts_dir as artifacts_dir',
17
+ ])
18
+ .orderBy(sql `coalesce(runs.started_at, runs.created_at)`, 'desc');
19
+ if (opts.projectSlug)
20
+ q = q.where('projects.slug', '=', opts.projectSlug);
21
+ const rows = await q.execute();
22
+ const cutoff = opts.keepDays ? Date.now() - opts.keepDays * 86_400_000 : null;
23
+ const perProject = new Map();
24
+ const victims = [];
25
+ for (const r of rows) {
26
+ const n = (perProject.get(r.slug) ?? 0) + 1;
27
+ perProject.set(r.slug, n);
28
+ const tooMany = opts.keepRuns !== undefined && n > opts.keepRuns;
29
+ const tooOld = cutoff !== null && new Date(String(r.created_at)).getTime() < cutoff;
30
+ if (tooMany || tooOld)
31
+ victims.push(r);
32
+ }
33
+ if (!opts.dryRun) {
34
+ for (const v of victims) {
35
+ await deleteRun(db, v.id);
36
+ const dir = v.artifacts_dir ?? (opts.artifactsRoot ? join(opts.artifactsRoot, v.id) : null);
37
+ if (dir && existsSync(dir))
38
+ rmSync(dir, { recursive: true, force: true });
39
+ }
40
+ }
41
+ return {
42
+ deleted: victims.map((v) => ({
43
+ id: v.id,
44
+ projectSlug: v.slug,
45
+ createdAt: String(v.created_at),
46
+ })),
47
+ kept: rows.length - victims.length,
48
+ };
49
+ }
50
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1,34 @@
1
+ import { type SdodsDb } from '../create-db.js';
2
+ import { type Driver, type DriverConfig } from '../driver.js';
3
+ export interface SwitchOptions {
4
+ source: SdodsDb;
5
+ target: Driver;
6
+ targetUrl?: string;
7
+ targetPath?: string;
8
+ dryRun?: boolean;
9
+ /** clear the target's platform tables before importing (required when the target has data) */
10
+ truncate?: boolean;
11
+ envFile?: string;
12
+ keepExport?: boolean;
13
+ }
14
+ export interface SwitchResult {
15
+ target: DriverConfig;
16
+ dryRun: boolean;
17
+ tables: Array<{
18
+ table: string;
19
+ source: number;
20
+ target: number;
21
+ ok: boolean;
22
+ }>;
23
+ ok: boolean;
24
+ envFile?: string;
25
+ envChanges?: string[];
26
+ }
27
+ /**
28
+ * Copy the platform database to the other driver: migrate target → export → import → verify counts →
29
+ * (unless dry-run) rewrite DB_DRIVER/DATABASE_URL/SQLITE_PATH in `.env`.
30
+ */
31
+ export declare function switchDriver(opts: SwitchOptions): Promise<SwitchResult>;
32
+ /** Rewrite DB_DRIVER and the matching URL/path in a dotenv file, preserving other lines. */
33
+ export declare function rewriteEnv(envFile: string, cfg: DriverConfig): string[];
34
+ //# sourceMappingURL=switch.d.ts.map
@@ -0,0 +1,133 @@
1
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join, resolve } from 'node:path';
4
+ import { createDb } from '../create-db.js';
5
+ import { DEFAULT_SQLITE_PATH } from '../driver.js';
6
+ import { migrateToLatest } from '../migrate.js';
7
+ import { TABLES_IN_FK_ORDER } from '../schema.js';
8
+ import { listTdTables } from '../test-data.js';
9
+ import { countRows, exportAll } from './export.js';
10
+ import { importAll } from './import.js';
11
+ /**
12
+ * Copy the platform database to the other driver: migrate target → export → import → verify counts →
13
+ * (unless dry-run) rewrite DB_DRIVER/DATABASE_URL/SQLITE_PATH in `.env`.
14
+ */
15
+ export async function switchDriver(opts) {
16
+ const targetCfg = opts.target === 'postgres'
17
+ ? { driver: 'postgres', databaseUrl: opts.targetUrl ?? process.env.DATABASE_URL }
18
+ : {
19
+ driver: 'sqlite',
20
+ sqlitePath: opts.targetPath ?? process.env.SQLITE_PATH ?? DEFAULT_SQLITE_PATH,
21
+ };
22
+ if (targetCfg.driver === 'postgres' && !targetCfg.databaseUrl)
23
+ throw new Error('switch to postgres needs --target-url or DATABASE_URL');
24
+ if (opts.source.driver === targetCfg.driver && sameLocation(opts.source.config, targetCfg)) {
25
+ throw new Error(`Source and target are the same ${targetCfg.driver} database.`);
26
+ }
27
+ const target = createDb(targetCfg);
28
+ const dir = mkdtempSync(join(tmpdir(), 'sdods-switch-'));
29
+ try {
30
+ await migrateToLatest(target);
31
+ // A target that already holds platform rows cannot be merged safely: slug conflicts would keep
32
+ // the target's ids while child rows reference the source's ids (foreign key failures).
33
+ const occupied = [];
34
+ for (const table of TABLES_IN_FK_ORDER) {
35
+ const n = await countRows(target, table).catch(() => 0);
36
+ if (n > 0)
37
+ occupied.push(`${table} (${n})`);
38
+ }
39
+ if (occupied.length && !opts.truncate) {
40
+ throw new Error(`Target ${targetCfg.driver} database already has data in ${occupied.join(', ')}. ` +
41
+ 'Point at an empty database, or pass --truncate to clear its platform tables first.');
42
+ }
43
+ if (occupied.length && opts.truncate && !opts.dryRun) {
44
+ for (const table of [...TABLES_IN_FK_ORDER].reverse()) {
45
+ await target.db.deleteFrom(table).execute();
46
+ }
47
+ }
48
+ const tables = [
49
+ ...TABLES_IN_FK_ORDER,
50
+ ...(await listTdTables(opts.source.db, opts.source.driver)),
51
+ ];
52
+ if (opts.dryRun) {
53
+ // Verify without writing: report what would be copied and confirm the target is (or will be) empty.
54
+ const rows = [];
55
+ for (const table of tables) {
56
+ const s = await countRows(opts.source, table);
57
+ const t = await countRows(target, table).catch(() => 0);
58
+ rows.push({
59
+ table,
60
+ source: s,
61
+ target: opts.truncate ? 0 : t,
62
+ ok: opts.truncate || t === 0,
63
+ });
64
+ }
65
+ return { target: targetCfg, dryRun: true, tables: rows, ok: rows.every((r) => r.ok) };
66
+ }
67
+ await exportAll(opts.source, dir);
68
+ await importAll(target, dir);
69
+ const rows = [];
70
+ for (const table of tables) {
71
+ const s = await countRows(opts.source, table);
72
+ const t = await countRows(target, table).catch(() => -1);
73
+ rows.push({ table, source: s, target: t, ok: t >= s });
74
+ }
75
+ const ok = rows.every((r) => r.ok);
76
+ const result = {
77
+ target: targetCfg,
78
+ dryRun: false,
79
+ tables: rows,
80
+ ok,
81
+ };
82
+ if (!ok)
83
+ return result;
84
+ const envFile = resolve(opts.envFile ?? '.env');
85
+ result.envFile = envFile;
86
+ result.envChanges = rewriteEnv(envFile, targetCfg);
87
+ return result;
88
+ }
89
+ finally {
90
+ await target.close();
91
+ if (!opts.keepExport)
92
+ rmSync(dir, { recursive: true, force: true });
93
+ }
94
+ }
95
+ function sameLocation(a, b) {
96
+ if (a.driver !== b.driver)
97
+ return false;
98
+ if (a.driver === 'postgres')
99
+ return a.databaseUrl === b.databaseUrl;
100
+ return resolve(a.sqlitePath ?? '') === resolve(b.sqlitePath ?? '');
101
+ }
102
+ /** Rewrite DB_DRIVER and the matching URL/path in a dotenv file, preserving other lines. */
103
+ export function rewriteEnv(envFile, cfg) {
104
+ const lines = existsSync(envFile)
105
+ ? readFileSync(envFile, 'utf8')
106
+ .split(/\r?\n/)
107
+ .filter((l, i, arr) => !(i === arr.length - 1 && l === ''))
108
+ : [];
109
+ const set = (key, value) => {
110
+ const idx = lines.findIndex((l) => new RegExp(`^\\s*#?\\s*${key}=`).test(l));
111
+ if (value === undefined) {
112
+ if (idx >= 0 && !lines[idx].trim().startsWith('#'))
113
+ lines[idx] = `# ${lines[idx]}`;
114
+ return `${key} commented out`;
115
+ }
116
+ const line = `${key}=${value}`;
117
+ if (idx >= 0)
118
+ lines[idx] = line;
119
+ else
120
+ lines.push(line);
121
+ return line;
122
+ };
123
+ const changes = [set('DB_DRIVER', cfg.driver)];
124
+ if (cfg.driver === 'postgres') {
125
+ changes.push(set('DATABASE_URL', cfg.databaseUrl));
126
+ }
127
+ else {
128
+ changes.push(set('SQLITE_PATH', cfg.sqlitePath));
129
+ }
130
+ writeFileSync(envFile, lines.join('\n').replace(/\n*$/, '\n'));
131
+ return changes;
132
+ }
133
+ //# sourceMappingURL=switch.js.map
@@ -0,0 +1,22 @@
1
+ import { type Kysely } from 'kysely';
2
+ import type { Driver } from './driver.js';
3
+ export type Row = Record<string, unknown>;
4
+ /** `td_<slug>_<name>` — user-defined test-data tables share the platform DB on both dialects. */
5
+ export declare function tdTableName(projectSlug: string, dataset: string): string;
6
+ export declare function isTdTable(name: string): boolean;
7
+ /** Create (if needed) and fill a test-data table. Columns are text; `env` and `row_index` are always present. */
8
+ export declare function importRowsToTable(db: Kysely<any>, driver: Driver, input: {
9
+ projectSlug: string;
10
+ dataset: string;
11
+ env?: string;
12
+ rows: Row[];
13
+ truncate?: boolean;
14
+ }): Promise<{
15
+ table: string;
16
+ inserted: number;
17
+ columns: string[];
18
+ }>;
19
+ export declare function readTableRows(db: Kysely<any>, table: string, env?: string): Promise<Row[]>;
20
+ export declare function listTdTables(db: Kysely<any>, driver: Driver): Promise<string[]>;
21
+ export declare function listTdColumns(db: Kysely<any>, driver: Driver, table: string): Promise<string[]>;
22
+ //# sourceMappingURL=test-data.d.ts.map
@@ -0,0 +1,79 @@
1
+ import { sql } from 'kysely';
2
+ /** `td_<slug>_<name>` — user-defined test-data tables share the platform DB on both dialects. */
3
+ export function tdTableName(projectSlug, dataset) {
4
+ const clean = (s) => s
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]+/g, '_')
7
+ .replace(/^_+|_+$/g, '');
8
+ return `td_${clean(projectSlug)}_${clean(dataset)}`;
9
+ }
10
+ export function isTdTable(name) {
11
+ return /^td_[a-z0-9_]+$/.test(name);
12
+ }
13
+ /** Create (if needed) and fill a test-data table. Columns are text; `env` and `row_index` are always present. */
14
+ export async function importRowsToTable(db, driver, input) {
15
+ const table = tdTableName(input.projectSlug, input.dataset);
16
+ const env = input.env ?? '*';
17
+ const columns = [...new Set(input.rows.flatMap((r) => Object.keys(r)))].filter((c) => c !== 'env' && c !== 'row_index');
18
+ const existing = await listTdColumns(db, driver, table);
19
+ if (existing.length === 0) {
20
+ let b = db.schema
21
+ .createTable(table)
22
+ .addColumn('row_index', 'integer', (cb) => cb.notNull())
23
+ .addColumn('env', 'text', (cb) => cb.notNull().defaultTo('*'));
24
+ for (const c of columns)
25
+ b = b.addColumn(c, 'text');
26
+ await b.execute();
27
+ await db.schema
28
+ .createIndex(`idx_${table}_env`)
29
+ .on(table)
30
+ .columns(['env', 'row_index'])
31
+ .execute();
32
+ }
33
+ else {
34
+ for (const c of columns)
35
+ if (!existing.includes(c))
36
+ await db.schema.alterTable(table).addColumn(c, 'text').execute();
37
+ }
38
+ if (input.truncate)
39
+ await db.deleteFrom(table).where('env', '=', env).execute();
40
+ const values = input.rows.map((r, i) => {
41
+ const out = { row_index: i, env };
42
+ for (const c of columns)
43
+ out[c] = r[c] == null ? null : typeof r[c] === 'object' ? JSON.stringify(r[c]) : String(r[c]);
44
+ return out;
45
+ });
46
+ for (let i = 0; i < values.length; i += 500) {
47
+ await db
48
+ .insertInto(table)
49
+ .values(values.slice(i, i + 500))
50
+ .execute();
51
+ }
52
+ return { table, inserted: values.length, columns };
53
+ }
54
+ export async function readTableRows(db, table, env) {
55
+ let q = db.selectFrom(table).selectAll().orderBy('row_index');
56
+ if (env)
57
+ q = q.where('env', 'in', [env, '*']);
58
+ const rows = (await q.execute());
59
+ return rows.map(({ row_index: _i, env: _e, ...rest }) => rest);
60
+ }
61
+ export async function listTdTables(db, driver) {
62
+ if (driver === 'postgres') {
63
+ const res = await sql `select table_name from information_schema.tables where table_schema = current_schema() and table_name like 'td\_%'`.execute(db);
64
+ return res.rows.map((r) => r.table_name).filter(isTdTable);
65
+ }
66
+ const res = await sql `select name from sqlite_master where type = 'table' and name like 'td\\_%' escape '\\'`.execute(db);
67
+ return res.rows.map((r) => r.name).filter(isTdTable);
68
+ }
69
+ export async function listTdColumns(db, driver, table) {
70
+ if (!isTdTable(table))
71
+ return [];
72
+ if (driver === 'postgres') {
73
+ const res = await sql `select column_name from information_schema.columns where table_schema = current_schema() and table_name = ${table} order by ordinal_position`.execute(db);
74
+ return res.rows.map((r) => r.column_name);
75
+ }
76
+ const res = await sql `select name from pragma_table_info(${table})`.execute(db);
77
+ return res.rows.map((r) => r.name);
78
+ }
79
+ //# sourceMappingURL=test-data.js.map
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@sdods/db",
3
+ "version": "0.2.0",
4
+ "description": "SDODS platform database: one schema on SQLite or Postgres, migrations, result ingest and insights.",
5
+ "license": "Apache-2.0",
6
+ "author": "SDODS <admin@sdods.com>",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./schema": "./dist/schema.js",
11
+ "./ingest": "./dist/ingest/index.js",
12
+ "./switch": "./dist/switch/index.js",
13
+ "./insights": "./dist/insights.js",
14
+ "./test-data": "./dist/test-data.js"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "dependencies": {
26
+ "@sdods/contracts": "0.2.0",
27
+ "better-sqlite3": "^13.0.3",
28
+ "kysely": "^0.29.5",
29
+ "pg": "^8.23.0",
30
+ "zod": "^4.5.4"
31
+ },
32
+ "homepage": "https://sdods.com",
33
+ "bugs": {
34
+ "url": "https://github.com/siri1410/SDODS/issues"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/siri1410/SDODS.git",
39
+ "directory": "packages/db"
40
+ },
41
+ "keywords": [
42
+ "sdods",
43
+ "automation",
44
+ "orchestration",
45
+ "testing",
46
+ "bdd",
47
+ "ai-agents",
48
+ "mcp"
49
+ ]
50
+ }