139 lines
3.7 KiB
TypeScript
139 lines
3.7 KiB
TypeScript
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")<TodoRepository, TodoRepositoryService>() {}
|
|
|
|
export module TodoRepository {
|
|
export const Live = Layer.effect(TodoRepository,
|
|
SubscriptionRef.make(Chunk.empty<IdentifiedTodo>()).pipe(
|
|
Effect.map(ref => new TodoRepositoryService(ref))
|
|
)
|
|
)
|
|
}
|
|
|
|
|
|
export class TodoRepositoryService {
|
|
|
|
constructor(
|
|
readonly todos: SubscriptionRef.SubscriptionRef<Chunk.Chunk<IdentifiedTodo>>
|
|
) {}
|
|
|
|
|
|
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<IdentifiedTodo>(
|
|
Order.mapInput(Order.number, todo => todo.order)
|
|
)
|
|
|
|
const updateTodosOrder = Chunk.map<
|
|
Chunk.Chunk<IdentifiedTodo>,
|
|
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(),
|
|
}))
|
|
})
|