better formatting
This commit is contained in:
parent
b3b9b40d3a
commit
8ada5294c4
3 changed files with 97 additions and 36 deletions
|
|
@ -1,19 +1,39 @@
|
|||
import type { TableClass } from "muddle/contracts/typegen/orm/index.ts";
|
||||
import { getTableMetadata } from "./decorators/Table.ts";
|
||||
|
||||
export interface SchemaColumn { name: string; type: string; nullable: boolean }
|
||||
export interface SchemaTable { name: string; columns: SchemaColumn[] }
|
||||
export interface Migration { up: string[]; down: string[] }
|
||||
export interface SchemaColumn {
|
||||
name: string;
|
||||
type: string;
|
||||
nullable: boolean;
|
||||
}
|
||||
export interface SchemaTable {
|
||||
name: string;
|
||||
columns: SchemaColumn[];
|
||||
}
|
||||
export interface Migration {
|
||||
up: string[];
|
||||
down: string[];
|
||||
}
|
||||
|
||||
export function schemaOf(tables: TableClass[]): SchemaTable[] {
|
||||
return tables.map((table) => {
|
||||
const metadata = getTableMetadata(table);
|
||||
if (!metadata) throw new Error(`Missing @Table metadata for ${table.name}`);
|
||||
return { name: metadata.name, columns: [...metadata.fields.values()].map((field) => ({ name: field.column, type: field.type, nullable: field.nullable })) };
|
||||
return {
|
||||
name: metadata.name,
|
||||
columns: [...metadata.fields.values()].map((field) => ({
|
||||
name: field.column,
|
||||
type: field.type,
|
||||
nullable: field.nullable,
|
||||
})),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function diffSchema(previous: SchemaTable[], next: SchemaTable[]): Migration {
|
||||
export function diffSchema(
|
||||
previous: SchemaTable[],
|
||||
next: SchemaTable[],
|
||||
): Migration {
|
||||
const oldTables = new Map(previous.map((table) => [table.name, table]));
|
||||
const nextTables = new Map(next.map((table) => [table.name, table]));
|
||||
const up: string[] = [];
|
||||
|
|
@ -21,19 +41,27 @@ export function diffSchema(previous: SchemaTable[], next: SchemaTable[]): Migrat
|
|||
for (const table of next) {
|
||||
const old = oldTables.get(table.name);
|
||||
if (!old) {
|
||||
up.push(`CREATE TABLE ${table.name} (${table.columns.map(columnSql).join(", ")});`);
|
||||
up.push(
|
||||
`CREATE TABLE ${table.name} (${table.columns.map(columnSql).join(", ")});`,
|
||||
);
|
||||
down.unshift(`DROP TABLE ${table.name};`);
|
||||
continue;
|
||||
}
|
||||
const oldColumns = new Map(old.columns.map((column) => [column.name, column]));
|
||||
for (const column of table.columns) if (!oldColumns.has(column.name)) {
|
||||
const oldColumns = new Map(
|
||||
old.columns.map((column) => [column.name, column]),
|
||||
);
|
||||
for (const column of table.columns)
|
||||
if (!oldColumns.has(column.name)) {
|
||||
up.push(`ALTER TABLE ${table.name} ADD COLUMN ${columnSql(column)};`);
|
||||
down.unshift(`ALTER TABLE ${table.name} DROP COLUMN ${column.name};`);
|
||||
}
|
||||
}
|
||||
for (const table of previous) if (!nextTables.has(table.name)) {
|
||||
for (const table of previous)
|
||||
if (!nextTables.has(table.name)) {
|
||||
up.push(`DROP TABLE ${table.name};`);
|
||||
down.unshift(`CREATE TABLE ${table.name} (${table.columns.map(columnSql).join(", ")});`);
|
||||
down.unshift(
|
||||
`CREATE TABLE ${table.name} (${table.columns.map(columnSql).join(", ")});`,
|
||||
);
|
||||
}
|
||||
return { up, down };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,33 +3,66 @@ import type { SqlDriver } from "muddle/contracts/typegen/orm/index.ts";
|
|||
|
||||
/** Persistent sessions and atomic OAuth transaction consumption for SQLite, D1 and Postgres. */
|
||||
export class DatabaseSessionStore implements SessionStore {
|
||||
private constructor(private readonly driver: SqlDriver, private readonly table: string) {}
|
||||
private constructor(
|
||||
private readonly driver: SqlDriver,
|
||||
private readonly table: string,
|
||||
) {}
|
||||
|
||||
static async create(driver: SqlDriver, table = "muddle_sessions"): Promise<DatabaseSessionStore> {
|
||||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) throw new Error("Invalid session table name");
|
||||
await driver.execute(`CREATE TABLE IF NOT EXISTS "${table}" (id TEXT PRIMARY KEY, value TEXT NOT NULL, expires BIGINT NOT NULL)`);
|
||||
static async create(
|
||||
driver: SqlDriver,
|
||||
table = "muddle_sessions",
|
||||
): Promise<DatabaseSessionStore> {
|
||||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table))
|
||||
throw new Error("Invalid session table name");
|
||||
await driver.execute(
|
||||
`CREATE TABLE IF NOT EXISTS "${table}" (id TEXT PRIMARY KEY, value TEXT NOT NULL, expires BIGINT NOT NULL)`,
|
||||
);
|
||||
return new DatabaseSessionStore(driver, table);
|
||||
}
|
||||
|
||||
async get(key: string): Promise<SessionRecord | undefined> {
|
||||
const rows = await this.driver.query<{ value: string; expires: number }>(`SELECT value, expires FROM "${this.table}" WHERE id = ${this.driver.placeholder(1)}`, [key]);
|
||||
const rows = await this.driver.query<{ value: string; expires: number }>(
|
||||
`SELECT value, expires FROM "${this.table}" WHERE id = ${this.driver.placeholder(1)}`,
|
||||
[key],
|
||||
);
|
||||
const row = rows[0];
|
||||
if (!row || Number(row.expires) <= Date.now()) { await this.delete(key); return undefined; }
|
||||
if (!row || Number(row.expires) <= Date.now()) {
|
||||
await this.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
return { value: JSON.parse(row.value), expires: Number(row.expires) };
|
||||
}
|
||||
|
||||
async set(key: string, record: SessionRecord): Promise<void> {
|
||||
const args = [key, JSON.stringify(record.value), record.expires];
|
||||
await this.driver.execute(`INSERT INTO "${this.table}" (id, value, expires) VALUES (${args.map((_, index) => this.driver.placeholder(index + 1)).join(", ")}) ON CONFLICT (id) DO UPDATE SET value = excluded.value, expires = excluded.expires`, args);
|
||||
await this.driver.execute(
|
||||
`INSERT INTO "${this.table}" (id, value, expires) VALUES (${args.map((_, index) => this.driver.placeholder(index + 1)).join(", ")}) ON CONFLICT (id) DO UPDATE SET value = excluded.value, expires = excluded.expires`,
|
||||
args,
|
||||
);
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> { await this.driver.execute(`DELETE FROM "${this.table}" WHERE id = ${this.driver.placeholder(1)}`, [key]); }
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.driver.execute(
|
||||
`DELETE FROM "${this.table}" WHERE id = ${this.driver.placeholder(1)}`,
|
||||
[key],
|
||||
);
|
||||
}
|
||||
|
||||
async consume(key: string): Promise<SessionRecord | undefined> {
|
||||
const rows = await this.driver.query<{ value: string; expires: number }>(`DELETE FROM "${this.table}" WHERE id = ${this.driver.placeholder(1)} RETURNING value, expires`, [key]);
|
||||
const rows = await this.driver.query<{ value: string; expires: number }>(
|
||||
`DELETE FROM "${this.table}" WHERE id = ${this.driver.placeholder(1)} RETURNING value, expires`,
|
||||
[key],
|
||||
);
|
||||
const row = rows[0];
|
||||
return row && Number(row.expires) > Date.now() ? { value: JSON.parse(row.value), expires: Number(row.expires) } : undefined;
|
||||
return row && Number(row.expires) > Date.now()
|
||||
? { value: JSON.parse(row.value), expires: Number(row.expires) }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
async prune(): Promise<void> { await this.driver.execute(`DELETE FROM "${this.table}" WHERE expires <= ${this.driver.placeholder(1)}`, [Date.now()]); }
|
||||
async prune(): Promise<void> {
|
||||
await this.driver.execute(
|
||||
`DELETE FROM "${this.table}" WHERE expires <= ${this.driver.placeholder(1)}`,
|
||||
[Date.now()],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue