feat: make queues durable by default and add seed helpers
This commit is contained in:
@@ -61,3 +61,5 @@ export {
|
||||
createRepository,
|
||||
} from "./helpers.ts";
|
||||
export type { Repository } from "./helpers.ts";
|
||||
export { addSeedData, removeSeedData, runSeedQuery } from "./seed.ts";
|
||||
export type { AddSeedOptions, SeedDatabase, SeedRow } from "./seed.ts";
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { Db, ExecResult } from "./driver.ts";
|
||||
|
||||
export type SeedRow = Record<string, unknown>;
|
||||
export type SeedDatabase = Db | (() => Db);
|
||||
|
||||
export interface AddSeedOptions {
|
||||
conflict?: "error" | "ignore" | "replace";
|
||||
}
|
||||
|
||||
function identifier(value: string, label: string): string {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
||||
throw new TypeError(`${label} must be a safe SQL identifier`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function database(value: SeedDatabase): Db {
|
||||
return typeof value === "function" ? value() : value;
|
||||
}
|
||||
|
||||
/** Insert one or many object rows with parameterized values. */
|
||||
export async function addSeedData(
|
||||
source: SeedDatabase,
|
||||
data: SeedRow | readonly SeedRow[],
|
||||
table: string,
|
||||
options: AddSeedOptions = {},
|
||||
): Promise<ExecResult[]> {
|
||||
const db = database(source);
|
||||
const target = identifier(table, "seed table");
|
||||
const rows = Array.isArray(data) ? data : [data];
|
||||
const results: ExecResult[] = [];
|
||||
const clause =
|
||||
options.conflict === "ignore"
|
||||
? " OR IGNORE"
|
||||
: options.conflict === "replace"
|
||||
? " OR REPLACE"
|
||||
: "";
|
||||
for (const row of rows) {
|
||||
const entries = Object.entries(row);
|
||||
if (!entries.length) throw new TypeError("seed row cannot be empty");
|
||||
const columns = entries.map(([column]) => identifier(column, "seed column"));
|
||||
results.push(
|
||||
await db.exec(
|
||||
`INSERT${clause} INTO ${target} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`,
|
||||
entries.map(([, value]) => value),
|
||||
),
|
||||
);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Delete matching seed rows. An empty match is refused unless `all` is explicit. */
|
||||
export async function removeSeedData(
|
||||
source: SeedDatabase,
|
||||
match: SeedRow,
|
||||
table: string,
|
||||
options: { all?: boolean } = {},
|
||||
): Promise<ExecResult> {
|
||||
const db = database(source);
|
||||
const target = identifier(table, "seed table");
|
||||
const entries = Object.entries(match);
|
||||
if (!entries.length && !options.all) {
|
||||
throw new TypeError("removeSeedData requires match fields or { all: true }");
|
||||
}
|
||||
const where = entries.length
|
||||
? ` WHERE ${entries.map(([column]) => `${identifier(column, "seed column")} = ?`).join(" AND ")}`
|
||||
: "";
|
||||
return db.exec(
|
||||
`DELETE FROM ${target}${where}`,
|
||||
entries.map(([, value]) => value),
|
||||
);
|
||||
}
|
||||
|
||||
/** Execute an application-owned parameterized seed statement. */
|
||||
export function runSeedQuery(
|
||||
source: SeedDatabase,
|
||||
query: string,
|
||||
data: readonly unknown[] = [],
|
||||
): Promise<ExecResult> {
|
||||
const db = database(source);
|
||||
if (!query.trim()) throw new TypeError("seed query cannot be empty");
|
||||
return db.exec(query, [...data]);
|
||||
}
|
||||
Reference in New Issue
Block a user