feat(02-01): define task hierarchy schema
Schema defines 5 tables with proper foreign key relationships: - initiatives: Top-level projects (active/completed/archived) - phases: Major milestones within initiatives - plans: Groups of related tasks within phases - tasks: Individual work items with type, priority, status, order - task_dependencies: Dependency relationships between tasks All tables have cascade deletes and Unix timestamp fields. Types exported: Initiative, Phase, Plan, Task, TaskDependency + New* variants. Relations exported for Drizzle relational queries.
This commit is contained in:
163
src/db/schema.ts
163
src/db/schema.ts
@@ -7,9 +7,164 @@
|
||||
* - Plan: Group of related tasks within phase
|
||||
* - Task: Individual work item
|
||||
*
|
||||
* Schema will be defined in Task 3.
|
||||
* Plus a task_dependencies table for task dependency relationships.
|
||||
*/
|
||||
|
||||
// Placeholder export to allow index.ts to compile
|
||||
// Full schema defined in Task 3
|
||||
export {};
|
||||
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||
import { relations, type InferInsertModel, type InferSelectModel } from 'drizzle-orm';
|
||||
|
||||
// ============================================================================
|
||||
// INITIATIVES
|
||||
// ============================================================================
|
||||
|
||||
export const initiatives = sqliteTable('initiatives', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
status: text('status', { enum: ['active', 'completed', 'archived'] })
|
||||
.notNull()
|
||||
.default('active'),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||
});
|
||||
|
||||
export const initiativesRelations = relations(initiatives, ({ many }) => ({
|
||||
phases: many(phases),
|
||||
}));
|
||||
|
||||
export type Initiative = InferSelectModel<typeof initiatives>;
|
||||
export type NewInitiative = InferInsertModel<typeof initiatives>;
|
||||
|
||||
// ============================================================================
|
||||
// PHASES
|
||||
// ============================================================================
|
||||
|
||||
export const phases = sqliteTable('phases', {
|
||||
id: text('id').primaryKey(),
|
||||
initiativeId: text('initiative_id')
|
||||
.notNull()
|
||||
.references(() => initiatives.id, { onDelete: 'cascade' }),
|
||||
number: integer('number').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
status: text('status', { enum: ['pending', 'in_progress', 'completed'] })
|
||||
.notNull()
|
||||
.default('pending'),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||
});
|
||||
|
||||
export const phasesRelations = relations(phases, ({ one, many }) => ({
|
||||
initiative: one(initiatives, {
|
||||
fields: [phases.initiativeId],
|
||||
references: [initiatives.id],
|
||||
}),
|
||||
plans: many(plans),
|
||||
}));
|
||||
|
||||
export type Phase = InferSelectModel<typeof phases>;
|
||||
export type NewPhase = InferInsertModel<typeof phases>;
|
||||
|
||||
// ============================================================================
|
||||
// PLANS
|
||||
// ============================================================================
|
||||
|
||||
export const plans = sqliteTable('plans', {
|
||||
id: text('id').primaryKey(),
|
||||
phaseId: text('phase_id')
|
||||
.notNull()
|
||||
.references(() => phases.id, { onDelete: 'cascade' }),
|
||||
number: integer('number').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
status: text('status', { enum: ['pending', 'in_progress', 'completed'] })
|
||||
.notNull()
|
||||
.default('pending'),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||
});
|
||||
|
||||
export const plansRelations = relations(plans, ({ one, many }) => ({
|
||||
phase: one(phases, {
|
||||
fields: [plans.phaseId],
|
||||
references: [phases.id],
|
||||
}),
|
||||
tasks: many(tasks),
|
||||
}));
|
||||
|
||||
export type Plan = InferSelectModel<typeof plans>;
|
||||
export type NewPlan = InferInsertModel<typeof plans>;
|
||||
|
||||
// ============================================================================
|
||||
// TASKS
|
||||
// ============================================================================
|
||||
|
||||
export const tasks = sqliteTable('tasks', {
|
||||
id: text('id').primaryKey(),
|
||||
planId: text('plan_id')
|
||||
.notNull()
|
||||
.references(() => plans.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
type: text('type', {
|
||||
enum: ['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action'],
|
||||
})
|
||||
.notNull()
|
||||
.default('auto'),
|
||||
priority: text('priority', { enum: ['low', 'medium', 'high'] })
|
||||
.notNull()
|
||||
.default('medium'),
|
||||
status: text('status', {
|
||||
enum: ['pending_approval', 'pending', 'in_progress', 'completed', 'blocked'],
|
||||
})
|
||||
.notNull()
|
||||
.default('pending'),
|
||||
order: integer('order').notNull().default(0),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||
});
|
||||
|
||||
export const tasksRelations = relations(tasks, ({ one, many }) => ({
|
||||
plan: one(plans, {
|
||||
fields: [tasks.planId],
|
||||
references: [plans.id],
|
||||
}),
|
||||
// Dependencies: tasks this task depends on
|
||||
dependsOn: many(taskDependencies, { relationName: 'dependentTask' }),
|
||||
// Dependents: tasks that depend on this task
|
||||
dependents: many(taskDependencies, { relationName: 'dependencyTask' }),
|
||||
}));
|
||||
|
||||
export type Task = InferSelectModel<typeof tasks>;
|
||||
export type NewTask = InferInsertModel<typeof tasks>;
|
||||
|
||||
// ============================================================================
|
||||
// TASK DEPENDENCIES
|
||||
// ============================================================================
|
||||
|
||||
export const taskDependencies = sqliteTable('task_dependencies', {
|
||||
id: text('id').primaryKey(),
|
||||
taskId: text('task_id')
|
||||
.notNull()
|
||||
.references(() => tasks.id, { onDelete: 'cascade' }),
|
||||
dependsOnTaskId: text('depends_on_task_id')
|
||||
.notNull()
|
||||
.references(() => tasks.id, { onDelete: 'cascade' }),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
});
|
||||
|
||||
export const taskDependenciesRelations = relations(taskDependencies, ({ one }) => ({
|
||||
task: one(tasks, {
|
||||
fields: [taskDependencies.taskId],
|
||||
references: [tasks.id],
|
||||
relationName: 'dependentTask',
|
||||
}),
|
||||
dependsOnTask: one(tasks, {
|
||||
fields: [taskDependencies.dependsOnTaskId],
|
||||
references: [tasks.id],
|
||||
relationName: 'dependencyTask',
|
||||
}),
|
||||
}));
|
||||
|
||||
export type TaskDependency = InferSelectModel<typeof taskDependencies>;
|
||||
export type NewTaskDependency = InferInsertModel<typeof taskDependencies>;
|
||||
|
||||
Reference in New Issue
Block a user