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
|
* - Plan: Group of related tasks within phase
|
||||||
* - Task: Individual work item
|
* - 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
|
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||||
// Full schema defined in Task 3
|
import { relations, type InferInsertModel, type InferSelectModel } from 'drizzle-orm';
|
||||||
export {};
|
|
||||||
|
// ============================================================================
|
||||||
|
// 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