import { Todo, type IdentifiedTodo } from "@todo-tests/common/data" import { Chunk, Context, Data, Effect, Equal, Layer, Option, Order, Ref, SubscriptionRef, flow } from "effect" import crypto from "node:crypto" export class TodoRepository extends Context.Tag("TodoRepository")() {} export module TodoRepository { export const Live = Layer.effect(TodoRepository, SubscriptionRef.make(Chunk.empty()).pipe( Effect.map(ref => new TodoRepositoryService(ref)) ) ) } export class TodoRepositoryService { constructor( readonly todos: SubscriptionRef.SubscriptionRef> ) {} getByID(id: string) { return this.todos.get.pipe( Effect.map(Chunk.findFirst(todo => Equal.equals(todo.id.value, id))) ) } getIndexByID(id: string) { return this.todos.get.pipe( Effect.map(Chunk.findFirstIndex(todo => Equal.equals(todo.id.value, id))) ) } add(todo: Todo) { return Effect.gen(this, function*() { if (Option.isSome(todo.id)) return yield* Effect.fail(new TodoHasID({ todo })) const id: string = crypto.randomUUID() yield* Ref.update(this.todos, flow( Chunk.append(new Todo({ ...todo, id: Option.some(id), }) as IdentifiedTodo), sortTodos, updateTodosOrder, )) return id }) } update(todo: Todo) { return Effect.gen(this, function*() { if (Option.isNone(todo.id)) return yield* Effect.fail(new TodoHasNoID({ todo })) yield* Ref.update(this.todos, flow( Chunk.replace( yield* yield* this.getIndexByID(todo.id.value), todo as IdentifiedTodo, ), sortTodos, updateTodosOrder, )) }) } remove(todo: Todo) { return Effect.gen(this, function*() { if (Option.isNone(todo.id)) return yield* Effect.fail(new TodoHasNoID({ todo })) yield* Ref.update(this.todos, flow( Chunk.remove( yield* yield* this.getIndexByID(todo.id.value) ), updateTodosOrder, )) }) } } const sortTodos = Chunk.sort( Order.mapInput(Order.number, todo => todo.order) ) const updateTodosOrder = Chunk.map< Chunk.Chunk, IdentifiedTodo >((todo, order) => new Todo({ ...todo, order }, { disableValidation: true }) as IdentifiedTodo ) export class TodoHasID extends Data.TaggedError("TodoHasID")<{ todo: Todo }> {} export class TodoHasNoID extends Data.TaggedError("TodoHasNoID")<{ todo: Todo }> {} export const createDefaultTodos = Effect.gen(function*() { const todos = yield* TodoRepository yield* todos.add(new Todo({ id: Option.none(), order: 0, content: "Sort the socks", due: Option.none(), completed: false, createdAt: new Date(), updatedAt: new Date(), })) yield* todos.add(new Todo({ id: Option.none(), order: 1, content: "Sort the dog", due: Option.none(), completed: false, createdAt: new Date(), updatedAt: new Date(), })) yield* todos.add(new Todo({ id: Option.none(), order: 2, content: "Sort the car", due: Option.none(), completed: false, createdAt: new Date(), updatedAt: new Date(), })) })