chore: scaffold monorepo and migrate uhd_web design system (W0-A)
- pnpm workspace: apps/{web,server}, packages/{contracts,db,domain,adapters,tasks,agent,testkit}
- server: Hono + zod-openapi, API Key auth with scopes, redacting pino logger, SSE event bus with replay, worker heartbeat
- db: Kysely + pg, file migrator, 0001_init (settings/api_key/audit_event/outbox_event)
- web: React Router 7 SPA console shell with 12 workspace placeholders, uhd_web tokens/ui/guard scripts
- docs: ADR-001 (Node over Deno), dev runbook, design system rules
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
commit
b22c440139
138 files changed
+14455
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import { Kysely, PostgresDialect } from "kysely";
|
||||
import pg from "pg";
|
||||
import type { DB } from "./types";
|
||||
|
||||
export interface CreateDbOptions {
|
||||
/** 默认读 process.env.DATABASE_URL */
|
||||
connectionString?: string;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
/** 创建带连接池的 Kysely 实例;调用方负责在退出时 db.destroy() */
|
||||
export function createDb(opts: CreateDbOptions = {}): Kysely<DB> {
|
||||
const connectionString = opts.connectionString ?? process.env.DATABASE_URL;
|
||||
if (!connectionString) throw new Error("DATABASE_URL 未设置");
|
||||
const pool = new pg.Pool({ connectionString, max: opts.max ?? 10 });
|
||||
return new Kysely<DB>({ dialect: new PostgresDialect({ pool }) });
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// 统一从本包取 Kysely 与 sql,避免各包各装一份 kysely
|
||||
export { type Kysely, sql, type Transaction } from "kysely";
|
||||
export { type CreateDbOptions, createDb } from "./client";
|
||||
export { createMigrator, MIGRATIONS_DIR, migrateToLatest, resetSchema } from "./migrator";
|
||||
export type * from "./types";
|
||||
@@ -0,0 +1,31 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { type Kysely, sql } from "kysely";
|
||||
import { FileMigrationProvider, type MigrationResultSet, Migrator } from "kysely/migration";
|
||||
|
||||
/** 迁移目录:packages/db/migrations,文件名 NNNN_name.ts,导出 up/down */
|
||||
export const MIGRATIONS_DIR = fileURLToPath(new URL("../migrations", import.meta.url));
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 迁移器不关心具体表类型
|
||||
export function createMigrator(db: Kysely<any>): Migrator {
|
||||
return new Migrator({
|
||||
db,
|
||||
provider: new FileMigrationProvider({ fs, path, migrationFolder: MIGRATIONS_DIR }),
|
||||
});
|
||||
}
|
||||
|
||||
/** 迁移到最新;失败时抛出 */
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
||||
export async function migrateToLatest(db: Kysely<any>): Promise<MigrationResultSet> {
|
||||
const result = await createMigrator(db).migrateToLatest();
|
||||
if (result.error) throw result.error;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 清空 public schema(仅开发/测试用) */
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
||||
export async function resetSchema(db: Kysely<any>): Promise<void> {
|
||||
await sql`drop schema if exists public cascade`.execute(db);
|
||||
await sql`create schema public`.execute(db);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { ColumnType, Generated, Insertable, Selectable, Updateable } from "kysely";
|
||||
|
||||
// 手写的表类型,与 migrations/ 保持同步;后续表多了再考虑 kysely-codegen
|
||||
|
||||
type Timestamptz = ColumnType<Date, Date | string | undefined, Date | string>;
|
||||
type Json = ColumnType<unknown, unknown, unknown>;
|
||||
|
||||
export interface SettingsTable {
|
||||
key: string;
|
||||
value: Json;
|
||||
updated_at: Timestamptz;
|
||||
}
|
||||
|
||||
export interface ApiKeyTable {
|
||||
id: Generated<string>;
|
||||
name: string;
|
||||
key_hash: string;
|
||||
scopes: string[];
|
||||
created_at: Timestamptz;
|
||||
revoked_at: Date | null;
|
||||
last_used_at: Date | null;
|
||||
}
|
||||
|
||||
export interface AuditEventTable {
|
||||
id: Generated<string>;
|
||||
at: Timestamptz;
|
||||
actor_type: string;
|
||||
actor_id: string | null;
|
||||
action: string;
|
||||
target_type: string | null;
|
||||
target_id: string | null;
|
||||
payload: Json;
|
||||
trace_id: string | null;
|
||||
}
|
||||
|
||||
export interface OutboxEventTable {
|
||||
id: Generated<string>;
|
||||
at: Timestamptz;
|
||||
type: string;
|
||||
payload: Json;
|
||||
published_at: Date | null;
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
settings: SettingsTable;
|
||||
api_key: ApiKeyTable;
|
||||
audit_event: AuditEventTable;
|
||||
outbox_event: OutboxEventTable;
|
||||
}
|
||||
|
||||
export type ApiKeyRow = Selectable<ApiKeyTable>;
|
||||
export type NewApiKey = Insertable<ApiKeyTable>;
|
||||
export type ApiKeyUpdate = Updateable<ApiKeyTable>;
|
||||
export type AuditEventRow = Selectable<AuditEventTable>;
|
||||
export type NewAuditEvent = Insertable<AuditEventTable>;
|
||||
export type OutboxEventRow = Selectable<OutboxEventTable>;
|
||||
export type SettingsRow = Selectable<SettingsTable>;
|
||||
Reference in new issue
Block a user