feat: make queues durable by default and add seed helpers
Quality / quality (ubuntu-latest) (push) Failing after 11m1s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-23 09:50:25 +05:30
parent 8fb96f521f
commit 46195462c3
22 changed files with 283 additions and 15 deletions
+2
View File
@@ -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";
+83
View File
@@ -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]);
}