Add database package with Drizzle schema
Postgres schema via Drizzle ORM for agents, goals, issues, projects, and activity log tables. Includes migration runner, seed script, and Drizzle config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
10
packages/db/drizzle.config.ts
Normal file
10
packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "./src/schema/index.ts",
|
||||||
|
out: "./src/migrations",
|
||||||
|
dialect: "postgresql",
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DATABASE_URL!,
|
||||||
|
},
|
||||||
|
});
|
||||||
27
packages/db/package.json
Normal file
27
packages/db/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@paperclip/db",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./*": "./src/*.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"generate": "drizzle-kit generate",
|
||||||
|
"migrate": "tsx src/migrate.ts",
|
||||||
|
"seed": "tsx src/seed.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@paperclip/shared": "workspace:*",
|
||||||
|
"drizzle-orm": "^0.38.4",
|
||||||
|
"postgres": "^3.4.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"drizzle-kit": "^0.30.4",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"vitest": "^3.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/db/src/client.ts
Normal file
10
packages/db/src/client.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
import postgres from "postgres";
|
||||||
|
import * as schema from "./schema/index.js";
|
||||||
|
|
||||||
|
export function createDb(url: string) {
|
||||||
|
const sql = postgres(url);
|
||||||
|
return drizzle(sql, { schema });
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Db = ReturnType<typeof createDb>;
|
||||||
2
packages/db/src/index.ts
Normal file
2
packages/db/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { createDb, type Db } from "./client.js";
|
||||||
|
export * from "./schema/index.js";
|
||||||
13
packages/db/src/migrate.ts
Normal file
13
packages/db/src/migrate.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||||
|
import postgres from "postgres";
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
|
||||||
|
const url = process.env.DATABASE_URL;
|
||||||
|
if (!url) throw new Error("DATABASE_URL is required");
|
||||||
|
|
||||||
|
const sql = postgres(url, { max: 1 });
|
||||||
|
const db = drizzle(sql);
|
||||||
|
|
||||||
|
await migrate(db, { migrationsFolder: new URL("./migrations", import.meta.url).pathname });
|
||||||
|
await sql.end();
|
||||||
|
console.log("Migrations complete");
|
||||||
12
packages/db/src/schema/activity_log.ts
Normal file
12
packages/db/src/schema/activity_log.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { pgTable, uuid, text, timestamp, jsonb } from "drizzle-orm/pg-core";
|
||||||
|
import { agents } from "./agents.js";
|
||||||
|
|
||||||
|
export const activityLog = pgTable("activity_log", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
action: text("action").notNull(),
|
||||||
|
entityType: text("entity_type").notNull(),
|
||||||
|
entityId: uuid("entity_id").notNull(),
|
||||||
|
agentId: uuid("agent_id").references(() => agents.id),
|
||||||
|
details: jsonb("details"),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
15
packages/db/src/schema/agents.ts
Normal file
15
packages/db/src/schema/agents.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { type AnyPgColumn, pgTable, uuid, text, integer, timestamp, jsonb } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const agents = pgTable("agents", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
role: text("role").notNull().default("general"),
|
||||||
|
status: text("status").notNull().default("idle"),
|
||||||
|
budgetCents: integer("budget_cents").notNull().default(0),
|
||||||
|
spentCents: integer("spent_cents").notNull().default(0),
|
||||||
|
lastHeartbeat: timestamp("last_heartbeat", { withTimezone: true }),
|
||||||
|
reportsTo: uuid("reports_to").references((): AnyPgColumn => agents.id),
|
||||||
|
metadata: jsonb("metadata"),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
13
packages/db/src/schema/goals.ts
Normal file
13
packages/db/src/schema/goals.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { type AnyPgColumn, pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||||
|
import { agents } from "./agents.js";
|
||||||
|
|
||||||
|
export const goals = pgTable("goals", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
title: text("title").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
level: text("level").notNull().default("task"),
|
||||||
|
parentId: uuid("parent_id").references((): AnyPgColumn => goals.id),
|
||||||
|
ownerId: uuid("owner_id").references(() => agents.id),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
5
packages/db/src/schema/index.ts
Normal file
5
packages/db/src/schema/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export { agents } from "./agents.js";
|
||||||
|
export { projects } from "./projects.js";
|
||||||
|
export { goals } from "./goals.js";
|
||||||
|
export { issues } from "./issues.js";
|
||||||
|
export { activityLog } from "./activity_log.js";
|
||||||
17
packages/db/src/schema/issues.ts
Normal file
17
packages/db/src/schema/issues.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||||
|
import { agents } from "./agents.js";
|
||||||
|
import { projects } from "./projects.js";
|
||||||
|
import { goals } from "./goals.js";
|
||||||
|
|
||||||
|
export const issues = pgTable("issues", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
title: text("title").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
status: text("status").notNull().default("backlog"),
|
||||||
|
priority: text("priority").notNull().default("medium"),
|
||||||
|
projectId: uuid("project_id").references(() => projects.id),
|
||||||
|
assigneeId: uuid("assignee_id").references(() => agents.id),
|
||||||
|
goalId: uuid("goal_id").references(() => goals.id),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
9
packages/db/src/schema/projects.ts
Normal file
9
packages/db/src/schema/projects.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const projects = pgTable("projects", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
66
packages/db/src/seed.ts
Normal file
66
packages/db/src/seed.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { eq, inArray } from "drizzle-orm";
|
||||||
|
import { createDb } from "./client.js";
|
||||||
|
import { agents, projects, issues, goals } from "./schema/index.js";
|
||||||
|
|
||||||
|
const url = process.env.DATABASE_URL;
|
||||||
|
if (!url) throw new Error("DATABASE_URL is required");
|
||||||
|
|
||||||
|
const db = createDb(url);
|
||||||
|
|
||||||
|
console.log("Seeding database...");
|
||||||
|
|
||||||
|
const [ceo, engineer, researcher] = await db
|
||||||
|
.insert(agents)
|
||||||
|
.values([
|
||||||
|
{ name: "CEO Agent", role: "ceo", status: "active" },
|
||||||
|
{ name: "Engineer Agent", role: "engineer", status: "idle" },
|
||||||
|
{ name: "Researcher Agent", role: "researcher", status: "idle" },
|
||||||
|
])
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
// Wire up reporting hierarchy: engineer and researcher report to CEO
|
||||||
|
await db
|
||||||
|
.update(agents)
|
||||||
|
.set({ reportsTo: ceo!.id })
|
||||||
|
.where(inArray(agents.id, [engineer!.id, researcher!.id]));
|
||||||
|
|
||||||
|
const [project] = await db
|
||||||
|
.insert(projects)
|
||||||
|
.values([{ name: "Paperclip MVP", description: "Build the initial paperclip management platform" }])
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
const [goal] = await db
|
||||||
|
.insert(goals)
|
||||||
|
.values([
|
||||||
|
{
|
||||||
|
title: "Launch MVP",
|
||||||
|
description: "Ship the minimum viable product",
|
||||||
|
level: "milestone",
|
||||||
|
ownerId: ceo!.id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
await db.insert(issues).values([
|
||||||
|
{
|
||||||
|
title: "Set up database schema",
|
||||||
|
description: "Create initial Drizzle schema with all core tables",
|
||||||
|
status: "done",
|
||||||
|
priority: "high",
|
||||||
|
projectId: project!.id,
|
||||||
|
assigneeId: engineer!.id,
|
||||||
|
goalId: goal!.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Implement agent heartbeat",
|
||||||
|
description: "Add periodic heartbeat mechanism for agent health monitoring",
|
||||||
|
status: "in_progress",
|
||||||
|
priority: "medium",
|
||||||
|
projectId: project!.id,
|
||||||
|
assigneeId: engineer!.id,
|
||||||
|
goalId: goal!.id,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("Seed complete");
|
||||||
|
process.exit(0);
|
||||||
8
packages/db/tsconfig.json
Normal file
8
packages/db/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
packages/db/vitest.config.ts
Normal file
7
packages/db/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "node",
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user