This commit is contained in:
Julien Valverdé
2024-07-06 00:02:47 +02:00
parent bb1a422097
commit 89a76f85ac
4 changed files with 62 additions and 41 deletions

View File

@@ -1,9 +1,9 @@
import { Layer } from "effect" import { Layer } from "effect"
import { TodoRepositoryLive } from "./TodoRepository" import { TodoRepository } from "./TodoRepository"
export const ServicesLive = Layer.mergeAll( export const ServicesLive = Layer.mergeAll(
TodoRepositoryLive TodoRepository.Live
) )
export interface Services extends Layer.Layer.Success<typeof ServicesLive> {} export interface Services extends Layer.Layer.Success<typeof ServicesLive> {}

View File

@@ -5,11 +5,13 @@ import crypto from "node:crypto"
export class TodoRepository extends Context.Tag("TodoRepository")<TodoRepository, TodoRepositoryService>() {} export class TodoRepository extends Context.Tag("TodoRepository")<TodoRepository, TodoRepositoryService>() {}
export const TodoRepositoryLive = Layer.effect(TodoRepository, export module TodoRepository {
export const Live = Layer.effect(TodoRepository,
SubscriptionRef.make(Array.empty<Todo & { id: Option.Some<string> }>()).pipe( SubscriptionRef.make(Array.empty<Todo & { id: Option.Some<string> }>()).pipe(
Effect.map(ref => new TodoRepositoryService(ref)) Effect.map(ref => new TodoRepositoryService(ref))
) )
) )
}
export class TodoRepositoryService { export class TodoRepositoryService {
@@ -17,13 +19,13 @@ export class TodoRepositoryService {
readonly todos: SubscriptionRef.SubscriptionRef<(Todo & { id: Option.Some<string> })[]> readonly todos: SubscriptionRef.SubscriptionRef<(Todo & { id: Option.Some<string> })[]>
) {} ) {}
get(id: string) { getByID(id: string) {
return this.todos.get.pipe( return this.todos.get.pipe(
Effect.map(Array.findFirst(todo => Equal.equals(todo.id.value, id))) Effect.map(Array.findFirst(todo => Equal.equals(todo.id.value, id)))
) )
} }
getIndex(id: string) { getIndexByID(id: string) {
return this.todos.get.pipe( return this.todos.get.pipe(
Effect.map(Array.findFirstIndex(todo => Equal.equals(todo.id.value, id))) Effect.map(Array.findFirstIndex(todo => Equal.equals(todo.id.value, id)))
) )
@@ -34,12 +36,14 @@ export class TodoRepositoryService {
if (Option.isSome(todo.id)) if (Option.isSome(todo.id))
return yield* Effect.fail(new TodoHasID({ todo })) return yield* Effect.fail(new TodoHasID({ todo }))
yield* Ref.update(this.todos, todos => const id: string = crypto.randomUUID()
Array.append(todos, new Todo({
yield* Ref.update(this.todos, Array.append(new Todo({
...todo, ...todo,
id: Option.some(crypto.randomUUID()), id: Option.some(id),
}) as Todo & { id: Option.Some<string> }) }) as Todo & { id: Option.Some<string> }))
)
return id
}) })
} }
@@ -49,7 +53,7 @@ export class TodoRepositoryService {
return yield* Effect.fail(new TodoHasNoID({ todo })) return yield* Effect.fail(new TodoHasNoID({ todo }))
yield* Ref.update(this.todos, Array.replace( yield* Ref.update(this.todos, Array.replace(
yield* yield* this.getIndex(todo.id.value), yield* yield* this.getIndexByID(todo.id.value),
todo as Todo & { id: Option.Some<string> }, todo as Todo & { id: Option.Some<string> },
)) ))
}) })
@@ -61,7 +65,7 @@ export class TodoRepositoryService {
return yield* Effect.fail(new TodoHasNoID({ todo })) return yield* Effect.fail(new TodoHasNoID({ todo }))
yield* Ref.update(this.todos, Array.remove( yield* Ref.update(this.todos, Array.remove(
yield* yield* this.getIndex(todo.id.value) yield* yield* this.getIndexByID(todo.id.value)
)) ))
}) })
} }

View File

@@ -1,7 +1,7 @@
import { BunRuntime } from "@effect/platform-bun" import { BunRuntime } from "@effect/platform-bun"
import { Todo } from "@todo-tests/common/data" import { Todo } from "@todo-tests/common/data"
import { Identifiable } from "@todo-tests/common/traits" import { Identifiable } from "@todo-tests/common/traits"
import { Array, Duration, Effect, Layer, Option, Stream } from "effect" import { Array, Effect, Layer, Option, Stream } from "effect"
import { ServicesLive } from "./Services" import { ServicesLive } from "./Services"
import { TodoRepository, createDefaultTodos } from "./TodoRepository" import { TodoRepository, createDefaultTodos } from "./TodoRepository"
import { ExpressApp } from "./express/ExpressApp" import { ExpressApp } from "./express/ExpressApp"
@@ -41,10 +41,10 @@ const watchTodoChanges = Effect.gen(function*() {
}) })
const main = Effect.gen(function*() { const main = Effect.gen(function*() {
const watcher = yield* Effect.fork(watchTodoChanges) // const watcher = yield* Effect.fork(watchTodoChanges)
yield* createDefaultTodos yield* createDefaultTodos
const todos = yield* TodoRepository // const todos = yield* TodoRepository
// const secondTodo = yield* yield* todos.todos.get.pipe( // const secondTodo = yield* yield* todos.todos.get.pipe(
// Effect.map(Array.get(1)) // Effect.map(Array.get(1))
@@ -55,17 +55,17 @@ const main = Effect.gen(function*() {
// }) // })
// yield* todos.update(secondTodoModified) // yield* todos.update(secondTodoModified)
yield* todos.add(new Todo({ // yield* todos.add(new Todo({
id: Option.none(), // id: Option.none(),
title: "Put the dishes in the dishwasher", // title: "Put the dishes in the dishwasher",
content: "Lorem ipsum", // content: "Lorem ipsum",
due: Option.none(), // due: Option.none(),
completed: false, // completed: false,
createdAt: new Date(), // createdAt: new Date(),
updatedAt: new Date(), // updatedAt: new Date(),
})).pipe( // })).pipe(
Effect.delay(Duration.seconds(1)) // Effect.delay(Duration.seconds(1))
) // )
// yield* Fiber.join(watcher) // yield* Fiber.join(watcher)
yield* Layer.launch(ServerLive) yield* Layer.launch(ServerLive)

View File

@@ -11,7 +11,8 @@ export const todosRouter = Effect.gen(function*() {
const procedure = yield* RPCProcedureBuilder const procedure = yield* RPCProcedureBuilder
return t.router({ return t.router({
all: procedure.query(({ ctx }) => ctx.run(Effect.gen(function*() { all: procedure
.query(({ ctx }) => ctx.run(Effect.gen(function*() {
const todos = yield* TodoRepository const todos = yield* TodoRepository
return yield* S.encode(S.Array(JsonifiableTodo))( return yield* S.encode(S.Array(JsonifiableTodo))(
@@ -19,16 +20,32 @@ export const todosRouter = Effect.gen(function*() {
) )
}))), }))),
get: procedure getByID: procedure
.input(S.decodeUnknownSync( .input(S.decodeUnknownPromise(S.String))
S.Struct({ id: S.String })
))
.query(({ ctx, input }) => ctx.run(Effect.gen(function*() { .query(({ ctx, input }) => ctx.run(Effect.gen(function*() {
const todos = yield* TodoRepository const todos = yield* TodoRepository
return yield* S.encode(S.OptionFromNullOr(JsonifiableTodo))( return yield* S.encode(S.OptionFromNullOr(JsonifiableTodo))(
yield* todos.get(input.id) yield* todos.getByID(input)
) )
}))), }))),
add: procedure
.input(S.decodeUnknownPromise(JsonifiableTodo))
.mutation(({ ctx, input }) => ctx.run(TodoRepository.pipe(
Effect.flatMap(todos => todos.add(input))
))),
update: procedure
.input(S.decodeUnknownPromise(JsonifiableTodo))
.mutation(({ ctx, input }) => ctx.run(TodoRepository.pipe(
Effect.flatMap(todos => todos.update(input))
))),
remove: procedure
.input(S.decodeUnknownPromise(JsonifiableTodo))
.mutation(({ ctx, input }) => ctx.run(TodoRepository.pipe(
Effect.flatMap(todos => todos.remove(input))
))),
}) })
}) })