sh-mcp/packages/tasks/src/dev-client.ts

112 lines
3.1 KiB
TypeScript
Raw Normal View History

/**
* InMemoryTasksClient — deterministic in-memory TasksClient for local dev.
*
* Holds tasks in memory partitioned by `sub` so a user only ever sees and
* mutates their own tasks (mirroring the real ABAC LeadingKeys boundary).
* Seeded with a couple of FAKE tasks. No AWS, no network.
*
* This is for build-plan §4 "in-memory dev clients (tools return real data
* locally)".
*/
import type {
CompleteTaskInput,
CreateTaskInput,
DeleteTaskInput,
ListTasksInput,
Task,
TasksClient,
} from './client.js';
function seedTasks(): Map<string, Task[]> {
return new Map<string, Task[]>([
[
'lauren@seahavenind.com',
[
{
taskId: 'task-0001',
sub: 'lauren@seahavenind.com',
title: 'Order replacement dock breakers',
description: 'Confirm 30A marine-grade rating before ordering.',
completed: false,
createdAt: '2026-06-20T08:00:00Z',
},
{
taskId: 'task-0002',
sub: 'lauren@seahavenind.com',
title: 'Send insurance cert to new vendor',
completed: true,
createdAt: '2026-06-18T09:30:00Z',
completedAt: '2026-06-19T10:15:00Z',
},
],
],
[
'adam@seahavenind.com',
[
{
taskId: 'task-0003',
sub: 'adam@seahavenind.com',
title: 'Review Q2 vendor balances in QBO',
completed: false,
createdAt: '2026-06-22T13:00:00Z',
},
],
],
]);
}
/** In-memory TasksClient partitioned by sub. */
export class InMemoryTasksClient implements TasksClient {
private readonly tasksBySub: Map<string, Task[]>;
private counter = 1000;
constructor() {
this.tasksBySub = seedTasks();
}
async createTask(input: CreateTaskInput): Promise<Task> {
const task: Task = {
taskId: `task-${(++this.counter).toString().padStart(4, '0')}`,
sub: input.sub,
title: input.title,
completed: false,
createdAt: new Date().toISOString(),
};
if (input.description !== undefined) {
task.description = input.description;
}
const list = this.tasksBySub.get(input.sub) ?? [];
list.push(task);
this.tasksBySub.set(input.sub, list);
return task;
}
async listTasks(input: ListTasksInput): Promise<Task[]> {
const list = this.tasksBySub.get(input.sub) ?? [];
if (input.includeCompleted === true) {
return [...list];
}
return list.filter((t) => !t.completed);
}
async completeTask(input: CompleteTaskInput): Promise<Task> {
const list = this.tasksBySub.get(input.sub) ?? [];
const task = list.find((t) => t.taskId === input.taskId);
if (!task) {
throw new Error(`Task ${input.taskId} not found for user ${input.sub}`);
}
task.completed = true;
task.completedAt = new Date().toISOString();
return task;
}
async deleteTask(input: DeleteTaskInput): Promise<void> {
const list = this.tasksBySub.get(input.sub) ?? [];
const idx = list.findIndex((t) => t.taskId === input.taskId);
if (idx !== -1) {
list.splice(idx, 1);
}
}
}