mcp-decisive
Version:
MCP server for WRAP decision-making framework with structured output
142 lines (140 loc) • 5.96 kB
JavaScript
import { ok, err } from 'neverthrow';
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Implementation Section
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Business rule: Validate plan creation request
const validateCreateRequest = (request) => {
if (request.name.trim().length === 0) {
return err({
type: 'ValidationFailed',
reason: 'Plan name cannot be empty'
});
}
if (request.tasks.length === 0) {
return err({
type: 'ValidationFailed',
reason: 'Plan must have at least one task'
});
}
return ok(request);
};
// Command implementation using functional composition
const createPlanCommand = (request) => validateCreateRequest(request)
.andThen(validRequest => {
// Create the plan entity
const plan = {
id: generateId(),
name: validRequest.name,
description: validRequest.description,
tasks: validRequest.tasks.map((t, idx) => ({
id: `task-${idx}`,
title: t.title,
status: 'todo'
})),
createdAt: new Date(),
updatedAt: new Date()
};
// Return the event
const event = {
type: 'PlanCreated',
planId: plan.id,
plan
};
return ok(event);
});
const updatePlanCommand = (existingPlan, request) => {
// Apply updates immutably
const updatedPlan = {
...existingPlan,
name: request.name ?? existingPlan.name,
description: request.description ?? existingPlan.description,
tasks: existingPlan.tasks.map(task => {
const update = request.taskUpdates?.find(u => u.id === task.id);
return update ? { ...task, ...update } : task;
}),
updatedAt: new Date()
};
const event = {
type: 'PlanUpdated',
planId: updatedPlan.id,
plan: updatedPlan
};
return ok(event);
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Error Handling Utilities
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const PlanErrorHandler = {
// Smart constructor for validation errors
validationFailed: (reason) => ({
type: 'ValidationFailed',
reason
}),
// Convert errors to user-friendly messages
toString: (error) => {
switch (error.type) {
case 'ValidationFailed':
return `Validation failed: ${error.reason}`;
case 'PlanNotFound':
return `Plan not found: ${error.planId}`;
case 'InvalidStateTransition':
return `Invalid state transition from ${error.from} to ${error.to}`;
default:
// Exhaustive check - TypeScript will error if we miss a case
const _exhaustive = error;
throw new Error(`Unhandled error type: ${_exhaustive}`);
}
}
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Public API - Expose only what's needed
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* Plan Aggregate - The public interface for plan-related commands
*
* @command createPlan - Create a new work plan
* @command updatePlan - Update an existing plan
* @utility toErrorMessage - Convert errors to user-friendly strings
*/
export const PlanAggregate = {
createPlan: createPlanCommand,
updatePlan: updatePlanCommand,
toErrorMessage: PlanErrorHandler.toString,
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Usage Example
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/*
// Creating a plan
const result = PlanAggregate.createPlan({
name: 'Q4 Feature Development',
description: 'New features for Q4 release',
tasks: [
{ title: 'Design API', description: 'Design REST API endpoints' },
{ title: 'Implement backend', description: 'Implement business logic' }
]
});
// Handle the result monadically
result
.map(event => {
console.log('Plan created:', event.plan.id);
// Dispatch event to event store
})
.mapErr(error => {
console.error(PlanAggregate.toErrorMessage(error));
});
// Chaining operations
const updateResult = result
.andThen(createEvent =>
PlanAggregate.updatePlan(createEvent.plan, {
taskUpdates: [
{ id: 'task-0', status: 'in-progress' }
]
})
);
*/
// Helper function (would normally be in a separate utility)
function generateId() {
return `plan-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
//# sourceMappingURL=plan-aggregate-example.js.map