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