Infrastructure build to support third-party app integrations
This commit is contained in:
82
backend/db/seed.ts
Normal file
82
backend/db/seed.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'dotenv/config';
|
||||
import split2 from 'split2';
|
||||
import { from as copyFrom } from 'pg-copy-streams';
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { Transform, type TransformCallback } from 'node:stream';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getPool, close } from './index.js';
|
||||
|
||||
const FIXTURE = fileURLToPath(new URL('./fixtures/notes.jsonl', import.meta.url));
|
||||
|
||||
const COLUMNS = ['id', 'text', 'x', 'y', 'author', 'color'] as const;
|
||||
|
||||
type Column = typeof COLUMNS[number];
|
||||
|
||||
const DEFAULTS: Partial<Record<Column, unknown>> = { x: 0, y: 0, color: 'yellow' };
|
||||
|
||||
const csvField = (value: unknown): string => {
|
||||
if (value === null || value === undefined) return '';
|
||||
return `"${String(value).replaceAll('"', '""')}"`;
|
||||
};
|
||||
|
||||
const toCsvRows = (): Transform => new Transform({
|
||||
writableObjectMode: true,
|
||||
transform(line: string, _encoding: BufferEncoding, callback: TransformCallback) {
|
||||
if (line.trim().length === 0) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
let note: Record<string, unknown>;
|
||||
try {
|
||||
note = JSON.parse(line) as Record<string, unknown>;
|
||||
} catch {
|
||||
callback(new Error(`Fixture contains a malformed JSON line: ${line.slice(0, 80)}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const row = COLUMNS.map((col) => csvField(note[col] ?? DEFAULTS[col]));
|
||||
callback(null, `${row.join(',')}\n`);
|
||||
},
|
||||
});
|
||||
|
||||
const run = async (): Promise<void> => {
|
||||
const client = await getPool().connect();
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// COPY has no ON CONFLICT, so land the fixture in a temp table first and
|
||||
// let a single INSERT ... SELECT apply the existing idempotency.
|
||||
await client.query(
|
||||
'CREATE TEMP TABLE notes_import (LIKE notes INCLUDING DEFAULTS) ON COMMIT DROP'
|
||||
);
|
||||
|
||||
const copy = client.query(
|
||||
copyFrom(`COPY notes_import (${COLUMNS.join(', ')}) FROM STDIN WITH (FORMAT csv)`)
|
||||
);
|
||||
|
||||
await pipeline(createReadStream(FIXTURE), split2(), toCsvRows(), copy);
|
||||
|
||||
const { rowCount: staged } = await client.query('SELECT 1 FROM notes_import');
|
||||
const { rowCount: inserted } = await client.query(
|
||||
`INSERT INTO notes (${COLUMNS.join(', ')})
|
||||
SELECT ${COLUMNS.join(', ')} FROM notes_import
|
||||
ON CONFLICT (id) DO NOTHING`
|
||||
);
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
console.log(`Seed complete — ${inserted} notes inserted (${(staged ?? 0) - (inserted ?? 0)} already existed).`);
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK').catch(() => {});
|
||||
console.error('Seed failed:', err instanceof Error ? err.message : err);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
client.release();
|
||||
await close();
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
Reference in New Issue
Block a user