83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
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();
|