mirror of
https://github.com/Sea-Haven-Industries/sh-mcp.git
synced 2026-10-01 19:53:17 +00:00
112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
|
|
/**
|
||
|
|
* 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|